dolibarr  20.0.0-alpha
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-2024 Frédéric France <frederic.france@free.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  * Copyright (C) 2023 William Mead <william.mead@manchenumerique.fr>
22  * Copyright (C) 2024 MDW <mdeweerd@users.noreply.github.com>
23  *
24  * This program is free software; you can redistribute it and/or modify
25  * it under the terms of the GNU General Public License as published by
26  * the Free Software Foundation; either version 3 of the License, or
27  * (at your option) any later version.
28  *
29  * This program is distributed in the hope that it will be useful,
30  * but WITHOUT ANY WARRANTY; without even the implied warranty of
31  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
32  * GNU General Public License for more details.
33  *
34  * You should have received a copy of the GNU General Public License
35  * along with this program. If not, see <https://www.gnu.org/licenses/>.
36  */
37 
43 require_once DOL_DOCUMENT_ROOT.'/core/class/commonobject.class.php';
44 require_once DOL_DOCUMENT_ROOT."/core/class/commonobjectline.class.php";
45 require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
46 require_once DOL_DOCUMENT_ROOT.'/contact/class/contact.class.php';
47 require_once DOL_DOCUMENT_ROOT.'/margin/lib/margins.lib.php';
48 require_once DOL_DOCUMENT_ROOT.'/multicurrency/class/multicurrency.class.php';
49 require_once DOL_DOCUMENT_ROOT.'/core/class/commonincoterm.class.php';
50 
54 class Propal extends CommonObject
55 {
56  use CommonIncoterm;
57 
61  public $code = "";
62 
66  public $element = 'propal';
67 
71  public $table_element = 'propal';
72 
76  public $table_element_line = 'propaldet';
77 
81  public $fk_element = 'fk_propal';
82 
86  public $picto = 'propal';
87 
92  public $ismultientitymanaged = 1;
93 
98  public $restrictiononfksoc = 1;
99 
103  protected $table_ref_field = 'ref';
104 
109  public $socid;
110 
115  public $contactid;
116  public $author;
117 
124  public $ref_client;
125 
130  public $ref_customer;
131 
135  public $oldcopy;
136 
143  public $statut;
144 
150  public $status;
151 
156  public $datec;
157 
161  public $date_creation;
162 
167  public $datev;
168 
172  public $date_validation;
173 
177  public $date_signature;
178 
182  public $user_signature;
183 
187  public $date;
188 
193  public $datep;
194 
198  public $delivery_date; // Date expected of shipment (date starting shipment, not the reception that occurs some days after)
199 
200 
201  public $fin_validite;
202 
203  public $user_author_id;
204 
209  public $price;
214  public $tva;
219  public $total;
220 
221  public $cond_reglement_code; // code
222  public $cond_reglement; // label
223  public $cond_reglement_doc; // label doc
224 
225  public $mode_reglement_code; // code
226  public $mode_reglement; // label
227 
228  public $deposit_percent;
229 
234  public $fk_address;
235 
236  public $address_type;
237  public $address;
238 
242  public $availability_id;
243 
249  public $fk_availability;
250 
254  public $availability_code;
255 
259  public $availability;
260 
261  public $duree_validite;
262 
263  public $demand_reason_id; // id
264  public $demand_reason_code; // code
265  public $demand_reason; // label
266 
267  public $warehouse_id;
268 
269  public $extraparams = array();
270 
274  public $lines = array();
275 
279  public $line;
280 
281  public $labelStatus = array();
282  public $labelStatusShort = array();
283 
284 
309  // BEGIN MODULEBUILDER PROPERTIES
313  public $fields = array(
314  'rowid' => array('type' => 'integer', 'label' => 'TechnicalID', 'enabled' => 1, 'visible' => -1, 'notnull' => 1, 'position' => 10),
315  'entity' => array('type' => 'integer', 'label' => 'Entity', 'default' => '1', 'enabled' => 1, 'visible' => -2, 'notnull' => 1, 'position' => 15, 'index' => 1),
316  'ref' => array('type' => 'varchar(30)', 'label' => 'Ref', 'enabled' => 1, 'visible' => -1, 'notnull' => 1, 'showoncombobox' => 1, 'position' => 20),
317  'ref_client' => array('type' => 'varchar(255)', 'label' => 'RefCustomer', 'enabled' => 1, 'visible' => -1, 'position' => 22),
318  'ref_ext' => array('type' => 'varchar(255)', 'label' => 'RefExt', 'enabled' => 1, 'visible' => 0, 'position' => 40),
319  'fk_soc' => array('type' => 'integer:Societe:societe/class/societe.class.php', 'label' => 'ThirdParty', 'enabled' => 'isModEnabled("societe")', 'visible' => -1, 'position' => 23),
320  '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),
321  'tms' => array('type' => 'timestamp', 'label' => 'DateModification', 'enabled' => 1, 'visible' => -1, 'notnull' => 1, 'position' => 25),
322  'datec' => array('type' => 'datetime', 'label' => 'DateCreation', 'enabled' => 1, 'visible' => -1, 'position' => 55),
323  'datep' => array('type' => 'date', 'label' => 'Date', 'enabled' => 1, 'visible' => -1, 'position' => 60),
324  'fin_validite' => array('type' => 'datetime', 'label' => 'DateEnd', 'enabled' => 1, 'visible' => -1, 'position' => 65),
325  'date_valid' => array('type' => 'datetime', 'label' => 'DateValidation', 'enabled' => 1, 'visible' => -1, 'position' => 70),
326  'date_cloture' => array('type' => 'datetime', 'label' => 'DateClosing', 'enabled' => 1, 'visible' => -1, 'position' => 75),
327  'fk_user_author' => array('type' => 'integer:User:user/class/user.class.php', 'label' => 'Fk user author', 'enabled' => 1, 'visible' => -1, 'position' => 80),
328  'fk_user_modif' => array('type' => 'integer:User:user/class/user.class.php', 'label' => 'UserModif', 'enabled' => 1, 'visible' => -2, 'notnull' => -1, 'position' => 85),
329  'fk_user_valid' => array('type' => 'integer:User:user/class/user.class.php', 'label' => 'UserValidation', 'enabled' => 1, 'visible' => -1, 'position' => 90),
330  'fk_user_cloture' => array('type' => 'integer:User:user/class/user.class.php', 'label' => 'Fk user cloture', 'enabled' => 1, 'visible' => -1, 'position' => 95),
331  'price' => array('type' => 'double', 'label' => 'Price', 'enabled' => 1, 'visible' => -1, 'position' => 105),
332  'total_ht' => array('type' => 'double(24,8)', 'label' => 'TotalHT', 'enabled' => 1, 'visible' => -1, 'position' => 125, 'isameasure' => 1),
333  'total_tva' => array('type' => 'double(24,8)', 'label' => 'VAT', 'enabled' => 1, 'visible' => -1, 'position' => 130, 'isameasure' => 1),
334  'localtax1' => array('type' => 'double(24,8)', 'label' => 'LocalTax1', 'enabled' => 1, 'visible' => -1, 'position' => 135, 'isameasure' => 1),
335  'localtax2' => array('type' => 'double(24,8)', 'label' => 'LocalTax2', 'enabled' => 1, 'visible' => -1, 'position' => 140, 'isameasure' => 1),
336  'total_ttc' => array('type' => 'double(24,8)', 'label' => 'TotalTTC', 'enabled' => 1, 'visible' => -1, 'position' => 145, 'isameasure' => 1),
337  'fk_account' => array('type' => 'integer', 'label' => 'BankAccount', 'enabled' => 'isModEnabled("bank")', 'visible' => -1, 'position' => 150),
338  'fk_currency' => array('type' => 'varchar(3)', 'label' => 'Currency', 'enabled' => 1, 'visible' => -1, 'position' => 155),
339  'fk_cond_reglement' => array('type' => 'integer', 'label' => 'PaymentTerm', 'enabled' => 1, 'visible' => -1, 'position' => 160),
340  'deposit_percent' => array('type' => 'varchar(63)', 'label' => 'DepositPercent', 'enabled' => 1, 'visible' => -1, 'position' => 161),
341  'fk_mode_reglement' => array('type' => 'integer', 'label' => 'PaymentMode', 'enabled' => 1, 'visible' => -1, 'position' => 165),
342  'note_private' => array('type' => 'html', 'label' => 'NotePrivate', 'enabled' => 1, 'visible' => 0, 'position' => 170),
343  'note_public' => array('type' => 'html', 'label' => 'NotePublic', 'enabled' => 1, 'visible' => 0, 'position' => 175),
344  'model_pdf' => array('type' => 'varchar(255)', 'label' => 'PDFTemplate', 'enabled' => 1, 'visible' => 0, 'position' => 180),
345  'date_livraison' => array('type' => 'date', 'label' => 'DateDeliveryPlanned', 'enabled' => 1, 'visible' => -1, 'position' => 185),
346  'fk_shipping_method' => array('type' => 'integer', 'label' => 'ShippingMethod', 'enabled' => 1, 'visible' => -1, 'position' => 190),
347  'fk_warehouse' => array('type' => 'integer:Entrepot:product/stock/class/entrepot.class.php', 'label' => 'Fk warehouse', 'enabled' => 'isModEnabled("stock")', 'visible' => -1, 'position' => 191),
348  'fk_availability' => array('type' => 'integer', 'label' => 'Availability', 'enabled' => 1, 'visible' => -1, 'position' => 195),
349  'fk_delivery_address' => array('type' => 'integer', 'label' => 'DeliveryAddress', 'enabled' => 1, 'visible' => 0, 'position' => 200), // deprecated
350  'fk_input_reason' => array('type' => 'integer', 'label' => 'InputReason', 'enabled' => 1, 'visible' => -1, 'position' => 205),
351  'extraparams' => array('type' => 'varchar(255)', 'label' => 'Extraparams', 'enabled' => 1, 'visible' => -1, 'position' => 215),
352  'fk_incoterms' => array('type' => 'integer', 'label' => 'IncotermCode', 'enabled' => '$conf->incoterm->enabled', 'visible' => -1, 'position' => 220),
353  'location_incoterms' => array('type' => 'varchar(255)', 'label' => 'IncotermLabel', 'enabled' => '$conf->incoterm->enabled', 'visible' => -1, 'position' => 225),
354  'fk_multicurrency' => array('type' => 'integer', 'label' => 'MulticurrencyID', 'enabled' => 1, 'visible' => -1, 'position' => 230),
355  'multicurrency_code' => array('type' => 'varchar(255)', 'label' => 'MulticurrencyCurrency', 'enabled' => 'isModEnabled("multicurrency")', 'visible' => -1, 'position' => 235),
356  'multicurrency_tx' => array('type' => 'double(24,8)', 'label' => 'MulticurrencyRate', 'enabled' => 'isModEnabled("multicurrency")', 'visible' => -1, 'position' => 240, 'isameasure' => 1),
357  'multicurrency_total_ht' => array('type' => 'double(24,8)', 'label' => 'MulticurrencyAmountHT', 'enabled' => 'isModEnabled("multicurrency")', 'visible' => -1, 'position' => 245, 'isameasure' => 1),
358  'multicurrency_total_tva' => array('type' => 'double(24,8)', 'label' => 'MulticurrencyAmountVAT', 'enabled' => 'isModEnabled("multicurrency")', 'visible' => -1, 'position' => 250, 'isameasure' => 1),
359  'multicurrency_total_ttc' => array('type' => 'double(24,8)', 'label' => 'MulticurrencyAmountTTC', 'enabled' => 'isModEnabled("multicurrency")', 'visible' => -1, 'position' => 255, 'isameasure' => 1),
360  'last_main_doc' => array('type' => 'varchar(255)', 'label' => 'LastMainDoc', 'enabled' => 1, 'visible' => -1, 'position' => 260),
361  'fk_statut' => array('type' => 'smallint(6)', 'label' => 'Status', 'enabled' => 1, 'visible' => -1, 'notnull' => 1, 'position' => 500),
362  'import_key' => array('type' => 'varchar(14)', 'label' => 'ImportId', 'enabled' => 1, 'visible' => -2, 'position' => 900),
363  );
364  // END MODULEBUILDER PROPERTIES
365 
369  const STATUS_CANCELED = -1;
373  const STATUS_DRAFT = 0;
377  const STATUS_VALIDATED = 1;
381  const STATUS_SIGNED = 2;
385  const STATUS_NOTSIGNED = 3;
389  const STATUS_BILLED = 4; // Todo rename into STATUS_CLOSE ?
390 
391 
399  public function __construct($db, $socid = 0, $propalid = 0)
400  {
401  $this->db = $db;
402 
403  $this->socid = $socid;
404  $this->id = $propalid;
405 
406  $this->duree_validite = getDolGlobalInt('PROPALE_VALIDITY_DURATION', 0);
407  }
408 
409 
410  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
422  public function add_product($idproduct, $qty, $remise_percent = 0)
423  {
424  // phpcs:enable
425  global $conf, $mysoc;
426 
427  if (!$qty) {
428  $qty = 1;
429  }
430 
431  dol_syslog(get_class($this)."::add_product $idproduct, $qty, $remise_percent");
432  if ($idproduct > 0) {
433  $prod = new Product($this->db);
434  $prod->fetch($idproduct);
435 
436  $productdesc = $prod->description;
437 
438  $tva_tx = get_default_tva($mysoc, $this->thirdparty, $prod->id);
439  $tva_npr = get_default_npr($mysoc, $this->thirdparty, $prod->id);
440  if (empty($tva_tx)) {
441  $tva_npr = 0;
442  }
443  $vat_src_code = ''; // May be defined into tva_tx
444 
445  $localtax1_tx = get_localtax($tva_tx, 1, $mysoc, $this->thirdparty, $tva_npr);
446  $localtax2_tx = get_localtax($tva_tx, 2, $mysoc, $this->thirdparty, $tva_npr);
447 
448  // multiprices
449  if ($conf->global->PRODUIT_MULTIPRICES && $this->thirdparty->price_level) {
450  $price = $prod->multiprices[$this->thirdparty->price_level];
451  } else {
452  $price = $prod->price;
453  }
454 
455  $line = new PropaleLigne($this->db);
456 
457  $line->fk_product = $idproduct;
458  $line->desc = $productdesc;
459  $line->qty = $qty;
460  $line->subprice = $price;
461  $line->remise_percent = $remise_percent;
462  $line->vat_src_code = $vat_src_code;
463  $line->tva_tx = $tva_tx;
464  $line->fk_unit = $prod->fk_unit;
465  if ($tva_npr) {
466  $line->info_bits = 1;
467  }
468 
469  $this->lines[] = $line;
470  }
471 
472  return 1;
473  }
474 
475  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
482  public function insert_discount($idremise)
483  {
484  // phpcs:enable
485  global $langs;
486 
487  include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
488  include_once DOL_DOCUMENT_ROOT.'/core/class/discount.class.php';
489 
490  $this->db->begin();
491 
492  $remise = new DiscountAbsolute($this->db);
493  $result = $remise->fetch($idremise);
494 
495  if ($result > 0) {
496  if ($remise->fk_facture) { // Protection against multiple submission
497  $this->error = $langs->trans("ErrorDiscountAlreadyUsed");
498  $this->db->rollback();
499  return -5;
500  }
501 
502  $line = new PropaleLigne($this->db);
503 
504  $line->context = $this->context;
505 
506  $line->fk_propal = $this->id;
507  $line->fk_remise_except = $remise->id;
508  $line->desc = $remise->description; // Description ligne
509  $line->vat_src_code = $remise->vat_src_code;
510  $line->tva_tx = $remise->tva_tx;
511  $line->subprice = -$remise->amount_ht;
512  $line->fk_product = 0; // Id produit predefined
513  $line->qty = 1;
514  $line->remise_percent = 0;
515  $line->rang = -1;
516  $line->info_bits = 2;
517 
518  // TODO deprecated
519  $line->price = -$remise->amount_ht;
520 
521  $line->total_ht = -$remise->amount_ht;
522  $line->total_tva = -$remise->amount_tva;
523  $line->total_ttc = -$remise->amount_ttc;
524 
525  $result = $line->insert();
526  if ($result > 0) {
527  $result = $this->update_price(1);
528  if ($result > 0) {
529  $this->db->commit();
530  return 1;
531  } else {
532  $this->db->rollback();
533  return -1;
534  }
535  } else {
536  $this->error = $line->error;
537  $this->errors = $line->errors;
538  $this->db->rollback();
539  return -2;
540  }
541  } else {
542  $this->db->rollback();
543  return -2;
544  }
545  }
546 
584  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 = array(), $fk_unit = null, $origin = '', $origin_id = 0, $pu_ht_devise = 0, $fk_remise_except = 0, $noupdateafterinsertline = 0)
585  {
586  global $mysoc, $conf, $langs;
587 
588  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);
589 
590  if ($this->statut == self::STATUS_DRAFT) {
591  include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
592 
593  // Clean parameters
594  if (empty($remise_percent)) {
595  $remise_percent = 0;
596  }
597  if (empty($qty)) {
598  $qty = 0;
599  }
600  if (empty($info_bits)) {
601  $info_bits = 0;
602  }
603  if (empty($rang)) {
604  $rang = 0;
605  }
606  if (empty($fk_parent_line) || $fk_parent_line < 0) {
607  $fk_parent_line = 0;
608  }
609 
610  $remise_percent = price2num($remise_percent);
611  $qty = price2num($qty);
612  $pu_ht = price2num($pu_ht);
613  $pu_ht_devise = price2num($pu_ht_devise);
614  $pu_ttc = price2num($pu_ttc);
615  if (!preg_match('/\‍((.*)\‍)/', (string) $txtva)) {
616  $txtva = price2num($txtva); // $txtva can have format '5,1' or '5.1' or '5.1(XXX)', we must clean only if '5,1'
617  }
618  $txlocaltax1 = price2num($txlocaltax1);
619  $txlocaltax2 = price2num($txlocaltax2);
620  $pa_ht = price2num($pa_ht);
621  if ($price_base_type == 'HT') {
622  $pu = $pu_ht;
623  } else {
624  $pu = $pu_ttc;
625  }
626 
627  // Check parameters
628  if ($type < 0) {
629  return -1;
630  }
631 
632  if ($date_start && $date_end && $date_start > $date_end) {
633  $langs->load("errors");
634  $this->error = $langs->trans('ErrorStartDateGreaterEnd');
635  return -1;
636  }
637 
638  $this->db->begin();
639 
640  $product_type = $type;
641  if (!empty($fk_product) && $fk_product > 0) {
642  $product = new Product($this->db);
643  $result = $product->fetch($fk_product);
644  $product_type = $product->type;
645 
646  if (getDolGlobalString('STOCK_MUST_BE_ENOUGH_FOR_PROPOSAL') && $product_type == 0 && $product->stock_reel < $qty) {
647  $langs->load("errors");
648  $this->error = $langs->trans('ErrorStockIsNotEnoughToAddProductOnProposal', $product->ref);
649  $this->db->rollback();
650  return -3;
651  }
652  }
653 
654  // Calcul du total TTC et de la TVA pour la ligne a partir de
655  // qty, pu, remise_percent et txtva
656  // TRES IMPORTANT: C'est au moment de l'insertion ligne qu'on doit stocker
657  // la part ht, tva et ttc, et ce au niveau de la ligne qui a son propre taux tva.
658 
659  $localtaxes_type = getLocalTaxesFromRate($txtva, 0, $this->thirdparty, $mysoc);
660 
661  // Clean vat code
662  $reg = array();
663  $vat_src_code = '';
664  if (preg_match('/\‍((.*)\‍)/', $txtva, $reg)) {
665  $vat_src_code = $reg[1];
666  $txtva = preg_replace('/\s*\‍(.*\‍)/', '', $txtva); // Remove code into vatrate.
667  }
668 
669  $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);
670 
671  $total_ht = $tabprice[0];
672  $total_tva = $tabprice[1];
673  $total_ttc = $tabprice[2];
674  $total_localtax1 = $tabprice[9];
675  $total_localtax2 = $tabprice[10];
676  $pu_ht = $tabprice[3];
677  $pu_tva = $tabprice[4];
678  $pu_ttc = $tabprice[5];
679 
680  // MultiCurrency
681  $multicurrency_total_ht = $tabprice[16];
682  $multicurrency_total_tva = $tabprice[17];
683  $multicurrency_total_ttc = $tabprice[18];
684  $pu_ht_devise = $tabprice[19];
685 
686  // Rang to use
687  $ranktouse = $rang;
688  if ($ranktouse == -1) {
689  $rangmax = $this->line_max($fk_parent_line);
690  $ranktouse = $rangmax + 1;
691  }
692 
693  // TODO A virer
694  // Anciens indicateurs: $price, $remise (a ne plus utiliser)
695  $price = $pu;
696  $remise = 0;
697  if ((float) $remise_percent > 0) {
698  $remise = round(((float) $pu * (float) $remise_percent / 100), 2);
699  $price = (float) $pu - $remise;
700  }
701 
702  // Insert line
703  $this->line = new PropaleLigne($this->db);
704 
705  $this->line->context = $this->context;
706 
707  $this->line->fk_propal = $this->id;
708  $this->line->label = $label;
709  $this->line->desc = $desc;
710  $this->line->qty = $qty;
711 
712  $this->line->vat_src_code = $vat_src_code;
713  $this->line->tva_tx = $txtva;
714  $this->line->localtax1_tx = ($total_localtax1 ? $localtaxes_type[1] : 0);
715  $this->line->localtax2_tx = ($total_localtax2 ? $localtaxes_type[3] : 0);
716  $this->line->localtax1_type = empty($localtaxes_type[0]) ? '' : $localtaxes_type[0];
717  $this->line->localtax2_type = empty($localtaxes_type[2]) ? '' : $localtaxes_type[2];
718  $this->line->fk_product = $fk_product;
719  $this->line->product_type = $type;
720  $this->line->fk_remise_except = $fk_remise_except;
721  $this->line->remise_percent = $remise_percent;
722  $this->line->subprice = $pu_ht;
723  $this->line->rang = $ranktouse;
724  $this->line->info_bits = $info_bits;
725  $this->line->total_ht = $total_ht;
726  $this->line->total_tva = $total_tva;
727  $this->line->total_localtax1 = $total_localtax1;
728  $this->line->total_localtax2 = $total_localtax2;
729  $this->line->total_ttc = $total_ttc;
730  $this->line->special_code = $special_code;
731  $this->line->fk_parent_line = $fk_parent_line;
732  $this->line->fk_unit = $fk_unit;
733 
734  $this->line->date_start = $date_start;
735  $this->line->date_end = $date_end;
736 
737  $this->line->fk_fournprice = $fk_fournprice;
738  $this->line->pa_ht = $pa_ht;
739 
740  $this->line->origin_id = $origin_id;
741  $this->line->origin = $origin;
742 
743  // Multicurrency
744  $this->line->fk_multicurrency = $this->fk_multicurrency;
745  $this->line->multicurrency_code = $this->multicurrency_code;
746  $this->line->multicurrency_subprice = $pu_ht_devise;
747  $this->line->multicurrency_total_ht = $multicurrency_total_ht;
748  $this->line->multicurrency_total_tva = $multicurrency_total_tva;
749  $this->line->multicurrency_total_ttc = $multicurrency_total_ttc;
750 
751  // Mise en option de la ligne
752  if (empty($qty) && empty($special_code)) {
753  $this->line->special_code = 3;
754  }
755 
756  // TODO deprecated
757  $this->line->price = $price;
758 
759  if (is_array($array_options) && count($array_options) > 0) {
760  $this->line->array_options = $array_options;
761  }
762 
763  $result = $this->line->insert();
764  if ($result > 0) {
765  // Reorder if child line
766  if (!empty($fk_parent_line)) {
767  $this->line_order(true, 'DESC');
768  } elseif ($ranktouse > 0 && $ranktouse <= count($this->lines)) { // Update all rank of all other lines
769  $linecount = count($this->lines);
770  for ($ii = $ranktouse; $ii <= $linecount; $ii++) {
771  $this->updateRangOfLine($this->lines[$ii - 1]->id, $ii + 1);
772  }
773  }
774 
775  // Mise a jour information denormalisees au niveau de la propale meme
776  if (empty($noupdateafterinsertline)) {
777  $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.
778  }
779 
780  if ($result > 0) {
781  $this->db->commit();
782  return $this->line->id;
783  } else {
784  $this->error = $this->db->error();
785  $this->db->rollback();
786  return -1;
787  }
788  } else {
789  $this->error = $this->line->error;
790  $this->errors = $this->line->errors;
791  $this->db->rollback();
792  return -2;
793  }
794  } else {
795  dol_syslog(get_class($this)."::addline status of proposal must be Draft to allow use of ->addline()", LOG_ERR);
796  return -3;
797  }
798  }
799 
800 
830  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 = array(), $fk_unit = null, $pu_ht_devise = 0, $notrigger = 0, $rang = 0)
831  {
832  global $mysoc, $langs;
833 
834  dol_syslog(get_class($this)."::updateLine rowid=$rowid, pu=$pu, qty=$qty, remise_percent=$remise_percent,
835  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");
836  include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
837 
838  // Clean parameters
839  $remise_percent = price2num($remise_percent);
840  $qty = price2num($qty);
841  $pu = price2num($pu);
842  $pu_ht_devise = price2num($pu_ht_devise);
843  if (!preg_match('/\‍((.*)\‍)/', (string) $txtva)) {
844  $txtva = price2num($txtva); // $txtva can have format '5.0(XXX)' or '5'
845  }
846  $txlocaltax1 = price2num($txlocaltax1);
847  $txlocaltax2 = price2num($txlocaltax2);
848  $pa_ht = price2num($pa_ht);
849  if (empty($qty) && empty($special_code)) {
850  $special_code = 3; // Set option tag
851  }
852  if (!empty($qty) && $special_code == 3) {
853  $special_code = 0; // Remove option tag
854  }
855  if (empty($type)) {
856  $type = 0;
857  }
858 
859  if ($date_start && $date_end && $date_start > $date_end) {
860  $langs->load("errors");
861  $this->error = $langs->trans('ErrorStartDateGreaterEnd');
862  return -1;
863  }
864 
865  if ($this->statut == self::STATUS_DRAFT) {
866  $this->db->begin();
867 
868  // Calcul du total TTC et de la TVA pour la ligne a partir de
869  // qty, pu, remise_percent et txtva
870  // TRES IMPORTANT: C'est au moment de l'insertion ligne qu'on doit stocker
871  // la part ht, tva et ttc, et ce au niveau de la ligne qui a son propre taux tva.
872 
873  $localtaxes_type = getLocalTaxesFromRate($txtva, 0, $this->thirdparty, $mysoc);
874 
875  // Clean vat code
876  $reg = array();
877  $vat_src_code = '';
878  if (preg_match('/\‍((.*)\‍)/', $txtva, $reg)) {
879  $vat_src_code = $reg[1];
880  $txtva = preg_replace('/\s*\‍(.*\‍)/', '', $txtva); // Remove code into vatrate.
881  }
882 
883  // TODO Implement if (getDolGlobalInt('MAIN_UNIT_PRICE_WITH_TAX_IS_FOR_ALL_TAXES')) ?
884 
885  $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);
886  $total_ht = $tabprice[0];
887  $total_tva = $tabprice[1];
888  $total_ttc = $tabprice[2];
889  $total_localtax1 = $tabprice[9];
890  $total_localtax2 = $tabprice[10];
891  $pu_ht = $tabprice[3];
892  $pu_tva = $tabprice[4];
893  $pu_ttc = $tabprice[5];
894 
895  // MultiCurrency
896  $multicurrency_total_ht = $tabprice[16];
897  $multicurrency_total_tva = $tabprice[17];
898  $multicurrency_total_ttc = $tabprice[18];
899  $pu_ht_devise = $tabprice[19];
900 
901  // Anciens indicateurs: $price, $remise (a ne plus utiliser)
902  $price = $pu;
903  $remise = 0;
904  if ((float) $remise_percent > 0) {
905  $remise = round(((float) $pu * (float) $remise_percent / 100), 2);
906  $price = (float) $pu - $remise;
907  }
908 
909  //Fetch current line from the database and then clone the object and set it in $oldline property
910  $line = new PropaleLigne($this->db);
911  $line->fetch($rowid);
912 
913  $staticline = clone $line;
914 
915  $line->oldline = $staticline;
916  $this->line = $line;
917  $this->line->context = $this->context;
918  $this->line->rang = $rang;
919 
920  // Reorder if fk_parent_line change
921  if (!empty($fk_parent_line) && !empty($staticline->fk_parent_line) && $fk_parent_line != $staticline->fk_parent_line) {
922  $rangmax = $this->line_max($fk_parent_line);
923  $this->line->rang = $rangmax + 1;
924  }
925 
926  $this->line->id = $rowid;
927  $this->line->label = $label;
928  $this->line->desc = $desc;
929  $this->line->qty = $qty;
930  $this->line->product_type = $type;
931  $this->line->vat_src_code = $vat_src_code;
932  $this->line->tva_tx = $txtva;
933  $this->line->localtax1_tx = $txlocaltax1;
934  $this->line->localtax2_tx = $txlocaltax2;
935  $this->line->localtax1_type = empty($localtaxes_type[0]) ? '' : $localtaxes_type[0];
936  $this->line->localtax2_type = empty($localtaxes_type[2]) ? '' : $localtaxes_type[2];
937  $this->line->remise_percent = $remise_percent;
938  $this->line->subprice = $pu_ht;
939  $this->line->info_bits = $info_bits;
940 
941  $this->line->total_ht = $total_ht;
942  $this->line->total_tva = $total_tva;
943  $this->line->total_localtax1 = $total_localtax1;
944  $this->line->total_localtax2 = $total_localtax2;
945  $this->line->total_ttc = $total_ttc;
946  $this->line->special_code = $special_code;
947  $this->line->fk_parent_line = $fk_parent_line;
948  $this->line->skip_update_total = $skip_update_total;
949  $this->line->fk_unit = $fk_unit;
950 
951  $this->line->fk_fournprice = $fk_fournprice;
952  $this->line->pa_ht = $pa_ht;
953 
954  $this->line->date_start = $date_start;
955  $this->line->date_end = $date_end;
956 
957  if (is_array($array_options) && count($array_options) > 0) {
958  // We replace values in this->line->array_options only for entries defined into $array_options
959  foreach ($array_options as $key => $value) {
960  $this->line->array_options[$key] = $array_options[$key];
961  }
962  }
963 
964  // Multicurrency
965  $this->line->multicurrency_subprice = $pu_ht_devise;
966  $this->line->multicurrency_total_ht = $multicurrency_total_ht;
967  $this->line->multicurrency_total_tva = $multicurrency_total_tva;
968  $this->line->multicurrency_total_ttc = $multicurrency_total_ttc;
969 
970  $result = $this->line->update($notrigger);
971  if ($result > 0) {
972  // Reorder if child line
973  if (!empty($fk_parent_line)) {
974  $this->line_order(true, 'DESC');
975  }
976 
977  $this->update_price(1, 'auto');
978 
979  // $this is Propal
980  // $this->fk_propal = $this->id;
981  // $this->rowid = $rowid;
982 
983  $this->db->commit();
984  return $result;
985  } else {
986  $this->error = $this->line->error;
987  $this->errors = $this->line->errors;
988  $this->db->rollback();
989  return -1;
990  }
991  } else {
992  dol_syslog(get_class($this)."::updateline Erreur -2 Propal en mode incompatible pour cette action");
993  return -2;
994  }
995  }
996 
997 
1005  public function deleteLine($lineid, $id = 0)
1006  {
1007  global $user;
1008 
1009  if ($this->statut == self::STATUS_DRAFT) {
1010  $this->db->begin();
1011 
1012  $line = new PropaleLigne($this->db);
1013 
1014  $line->context = $this->context;
1015 
1016  // Load data
1017  $line->fetch($lineid);
1018 
1019  if ($id > 0 && $line->fk_propal != $id) {
1020  $this->error = 'ErrorLineIDDoesNotMatchWithObjectID';
1021  return -1;
1022  }
1023 
1024  // Memorize previous line for triggers
1025  $staticline = clone $line;
1026  $line->oldline = $staticline;
1027 
1028  if ($line->delete($user) > 0) {
1029  $this->update_price(1);
1030 
1031  $this->db->commit();
1032  return 1;
1033  } else {
1034  $this->error = $line->error;
1035  $this->errors = $line->errors;
1036  $this->db->rollback();
1037  return -1;
1038  }
1039  } else {
1040  $this->error = 'ErrorDeleteLineNotAllowedByObjectStatus';
1041  return -2;
1042  }
1043  }
1044 
1045 
1054  public function create($user, $notrigger = 0)
1055  {
1056  global $conf, $hookmanager, $mysoc;
1057  $error = 0;
1058 
1059  $now = dol_now();
1060 
1061  // Clean parameters
1062  if (empty($this->date)) {
1063  $this->date = $this->datep;
1064  }
1065  $this->fin_validite = $this->date + ($this->duree_validite * 24 * 3600);
1066  if (empty($this->availability_id)) {
1067  $this->availability_id = 0;
1068  }
1069  if (empty($this->demand_reason_id)) {
1070  $this->demand_reason_id = 0;
1071  }
1072 
1073  // Multicurrency (test on $this->multicurrency_tx because we should take the default rate only if not using origin rate)
1074  if (!empty($this->multicurrency_code) && empty($this->multicurrency_tx)) {
1075  list($this->fk_multicurrency, $this->multicurrency_tx) = MultiCurrency::getIdAndTxFromCode($this->db, $this->multicurrency_code, $this->date);
1076  } else {
1077  $this->fk_multicurrency = MultiCurrency::getIdFromCode($this->db, $this->multicurrency_code);
1078  }
1079  if (empty($this->fk_multicurrency)) {
1080  $this->multicurrency_code = $conf->currency;
1081  $this->fk_multicurrency = 0;
1082  $this->multicurrency_tx = 1;
1083  }
1084 
1085  // Set tmp vars
1086  $delivery_date = $this->delivery_date;
1087 
1088  dol_syslog(get_class($this)."::create");
1089 
1090  // Check parameters
1091  $result = $this->fetch_thirdparty();
1092  if ($result < 0) {
1093  $this->error = "Failed to fetch company";
1094  dol_syslog(get_class($this)."::create ".$this->error, LOG_ERR);
1095  return -3;
1096  }
1097 
1098  // Check parameters
1099  if (!empty($this->ref)) { // We check that ref is not already used
1100  $result = self::isExistingObject($this->element, 0, $this->ref); // Check ref is not yet used
1101  if ($result > 0) {
1102  $this->error = 'ErrorRefAlreadyExists';
1103  dol_syslog(get_class($this)."::create ".$this->error, LOG_WARNING);
1104  $this->db->rollback();
1105  return -1;
1106  }
1107  }
1108 
1109  if (empty($this->date)) {
1110  $this->error = "Date of proposal is required";
1111  dol_syslog(get_class($this)."::create ".$this->error, LOG_ERR);
1112  return -4;
1113  }
1114 
1115 
1116  $this->db->begin();
1117 
1118  // Insert into database
1119  $sql = "INSERT INTO ".MAIN_DB_PREFIX."propal (";
1120  $sql .= "fk_soc";
1121  $sql .= ", price";
1122  $sql .= ", total_tva";
1123  $sql .= ", total_ttc";
1124  $sql .= ", datep";
1125  $sql .= ", datec";
1126  $sql .= ", ref";
1127  $sql .= ", fk_user_author";
1128  $sql .= ", note_private";
1129  $sql .= ", note_public";
1130  $sql .= ", model_pdf";
1131  $sql .= ", fin_validite";
1132  $sql .= ", fk_cond_reglement";
1133  $sql .= ", deposit_percent";
1134  $sql .= ", fk_mode_reglement";
1135  $sql .= ", fk_account";
1136  $sql .= ", ref_client";
1137  $sql .= ", ref_ext";
1138  $sql .= ", date_livraison";
1139  $sql .= ", fk_shipping_method";
1140  $sql .= ", fk_warehouse";
1141  $sql .= ", fk_availability";
1142  $sql .= ", fk_input_reason";
1143  $sql .= ", fk_projet";
1144  $sql .= ", fk_incoterms";
1145  $sql .= ", location_incoterms";
1146  $sql .= ", entity";
1147  $sql .= ", fk_multicurrency";
1148  $sql .= ", multicurrency_code";
1149  $sql .= ", multicurrency_tx";
1150  $sql .= ") ";
1151  $sql .= " VALUES (";
1152  $sql .= $this->socid;
1153  $sql .= ", 0";
1154  $sql .= ", 0";
1155  $sql .= ", 0";
1156  $sql .= ", '".$this->db->idate($this->date)."'";
1157  $sql .= ", '".$this->db->idate($now)."'";
1158  $sql .= ", '(PROV)'";
1159  $sql .= ", ".($user->id > 0 ? ((int) $user->id) : "NULL");
1160  $sql .= ", '".$this->db->escape($this->note_private)."'";
1161  $sql .= ", '".$this->db->escape($this->note_public)."'";
1162  $sql .= ", '".$this->db->escape($this->model_pdf)."'";
1163  $sql .= ", ".($this->fin_validite != '' ? "'".$this->db->idate($this->fin_validite)."'" : "NULL");
1164  $sql .= ", ".($this->cond_reglement_id > 0 ? ((int) $this->cond_reglement_id) : 'NULL');
1165  $sql .= ", ".(!empty($this->deposit_percent) ? "'".$this->db->escape($this->deposit_percent)."'" : 'NULL');
1166  $sql .= ", ".($this->mode_reglement_id > 0 ? ((int) $this->mode_reglement_id) : 'NULL');
1167  $sql .= ", ".($this->fk_account > 0 ? ((int) $this->fk_account) : 'NULL');
1168  $sql .= ", '".$this->db->escape($this->ref_client)."'";
1169  $sql .= ", '".$this->db->escape($this->ref_ext)."'";
1170  $sql .= ", ".(empty($delivery_date) ? "NULL" : "'".$this->db->idate($delivery_date)."'");
1171  $sql .= ", ".($this->shipping_method_id > 0 ? $this->shipping_method_id : 'NULL');
1172  $sql .= ", ".($this->warehouse_id > 0 ? $this->warehouse_id : 'NULL');
1173  $sql .= ", ".$this->availability_id;
1174  $sql .= ", ".$this->demand_reason_id;
1175  $sql .= ", ".($this->fk_project ? $this->fk_project : "null");
1176  $sql .= ", ".(int) $this->fk_incoterms;
1177  $sql .= ", '".$this->db->escape($this->location_incoterms)."'";
1178  $sql .= ", ".setEntity($this);
1179  $sql .= ", ".(int) $this->fk_multicurrency;
1180  $sql .= ", '".$this->db->escape($this->multicurrency_code)."'";
1181  $sql .= ", ".(float) $this->multicurrency_tx;
1182  $sql .= ")";
1183 
1184  dol_syslog(get_class($this)."::create", LOG_DEBUG);
1185  $resql = $this->db->query($sql);
1186  if ($resql) {
1187  $this->id = $this->db->last_insert_id(MAIN_DB_PREFIX."propal");
1188 
1189  if ($this->id) {
1190  $this->ref = '(PROV'.$this->id.')';
1191  $sql = 'UPDATE '.MAIN_DB_PREFIX."propal SET ref='".$this->db->escape($this->ref)."' WHERE rowid=".((int) $this->id);
1192 
1193  dol_syslog(get_class($this)."::create", LOG_DEBUG);
1194  $resql = $this->db->query($sql);
1195  if (!$resql) {
1196  $error++;
1197  }
1198 
1199  if (!empty($this->linkedObjectsIds) && empty($this->linked_objects)) { // To use new linkedObjectsIds instead of old linked_objects
1200  $this->linked_objects = $this->linkedObjectsIds; // TODO Replace linked_objects with linkedObjectsIds
1201  }
1202 
1203  // Add object linked
1204  if (!$error && $this->id && !empty($this->linked_objects) && is_array($this->linked_objects)) {
1205  foreach ($this->linked_objects as $origin => $tmp_origin_id) {
1206  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, ...))
1207  foreach ($tmp_origin_id as $origin_id) {
1208  $ret = $this->add_object_linked($origin, $origin_id);
1209  if (!$ret) {
1210  $this->error = $this->db->lasterror();
1211  $error++;
1212  }
1213  }
1214  } else { // Old behaviour, if linked_object has only one link per type, so is something like array('contract'=>id1))
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 (getDolGlobalString('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 = 0;
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 = 0; // 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 && getDolGlobalString('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 (getDolGlobalString('PRODUIT_MULTIPRICES') && $objsoc->price_level > 0) {
1444  $pu_ht = $prod->multiprices[$objsoc->price_level];
1445  if (getDolGlobalString('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 (getDolGlobalString('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_creation_id = $user->id;
1486  $object->user_validation_id = 0;
1487  $object->date = $now;
1488  $object->datep = $now; // deprecated
1489  $object->fin_validite = $object->date + ($object->duree_validite * 24 * 3600);
1490  if (!getDolGlobalString('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 && !getDolGlobalInt('MAIN_IGNORE_CONTACTS_ON_CLONING')) {
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.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  // Don'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->total = $obj->total_ttc; // TODO deprecated
1622  $this->total_ttc = $obj->total_ttc;
1623  $this->total_ht = $obj->total_ht;
1624  $this->total_tva = $obj->total_tva;
1625  $this->total_localtax1 = $obj->localtax1;
1626  $this->total_localtax2 = $obj->localtax2;
1627 
1628  $this->socid = $obj->fk_soc;
1629  $this->thirdparty = null; // Clear if another value was already set by fetch_thirdparty
1630 
1631  $this->fk_project = $obj->fk_project;
1632  $this->project = null; // Clear if another value was already set by fetch_projet
1633 
1634  $this->model_pdf = $obj->model_pdf;
1635  $this->last_main_doc = $obj->last_main_doc;
1636  $this->note = $obj->note_private; // TODO deprecated
1637  $this->note_private = $obj->note_private;
1638  $this->note_public = $obj->note_public;
1639 
1640  $this->status = (int) $obj->fk_statut;
1641  $this->statut = $this->status; // deprecated
1642 
1643  $this->datec = $this->db->jdate($obj->datec); // TODO deprecated
1644  $this->datev = $this->db->jdate($obj->datev); // TODO deprecated
1645  $this->date_creation = $this->db->jdate($obj->datec); //Creation date
1646  $this->date_validation = $this->db->jdate($obj->datev); //Validation date
1647  $this->date_modification = $this->db->jdate($obj->date_modification); // tms
1648  $this->date_signature = $this->db->jdate($obj->dates); // Signature date
1649  $this->date = $this->db->jdate($obj->dp); // Proposal date
1650  $this->datep = $this->db->jdate($obj->dp); // deprecated
1651  $this->fin_validite = $this->db->jdate($obj->dfv);
1652  $this->delivery_date = $this->db->jdate($obj->delivery_date);
1653  $this->shipping_method_id = ($obj->fk_shipping_method > 0) ? $obj->fk_shipping_method : null;
1654  $this->warehouse_id = ($obj->fk_warehouse > 0) ? $obj->fk_warehouse : null;
1655  $this->availability_id = $obj->fk_availability;
1656  $this->availability_code = $obj->availability_code;
1657  $this->availability = $obj->availability;
1658  $this->demand_reason_id = $obj->fk_input_reason;
1659  $this->demand_reason_code = $obj->demand_reason_code;
1660  $this->demand_reason = $obj->demand_reason;
1661  $this->fk_address = $obj->fk_delivery_address;
1662 
1663  $this->mode_reglement_id = $obj->fk_mode_reglement;
1664  $this->mode_reglement_code = $obj->mode_reglement_code;
1665  $this->mode_reglement = $obj->mode_reglement;
1666  $this->fk_account = ($obj->fk_account > 0) ? $obj->fk_account : null;
1667  $this->cond_reglement_id = $obj->fk_cond_reglement;
1668  $this->cond_reglement_code = $obj->cond_reglement_code;
1669  $this->cond_reglement = $obj->cond_reglement;
1670  $this->cond_reglement_doc = $obj->cond_reglement_libelle_doc;
1671  $this->deposit_percent = $obj->deposit_percent;
1672 
1673  $this->extraparams = !empty($obj->extraparams) ? (array) json_decode($obj->extraparams, true) : array();
1674 
1675  $this->user_author_id = $obj->fk_user_author;
1676  $this->user_validation_id = $obj->fk_user_valid;
1677  $this->user_closing_id = $obj->fk_user_cloture;
1678 
1679  //Incoterms
1680  $this->fk_incoterms = $obj->fk_incoterms;
1681  $this->location_incoterms = $obj->location_incoterms;
1682  $this->label_incoterms = $obj->label_incoterms;
1683 
1684  // Multicurrency
1685  $this->fk_multicurrency = $obj->fk_multicurrency;
1686  $this->multicurrency_code = $obj->multicurrency_code;
1687  $this->multicurrency_tx = $obj->multicurrency_tx;
1688  $this->multicurrency_total_ht = $obj->multicurrency_total_ht;
1689  $this->multicurrency_total_tva = $obj->multicurrency_total_tva;
1690  $this->multicurrency_total_ttc = $obj->multicurrency_total_ttc;
1691 
1692  // Retrieve all extrafield
1693  // fetch optionals attributes and labels
1694  $this->fetch_optionals();
1695 
1696  $this->db->free($resql);
1697 
1698  $this->lines = array();
1699 
1700  // Lines
1701  $result = $this->fetch_lines();
1702  if ($result < 0) {
1703  return -3;
1704  }
1705 
1706  return 1;
1707  }
1708 
1709  $this->error = "Record Not Found";
1710  return 0;
1711  } else {
1712  $this->error = $this->db->lasterror();
1713  return -1;
1714  }
1715  }
1716 
1724  public function update(User $user, $notrigger = 0)
1725  {
1726  global $conf;
1727 
1728  $error = 0;
1729 
1730  // Clean parameters
1731  if (isset($this->ref)) {
1732  $this->ref = trim($this->ref);
1733  }
1734  if (isset($this->ref_client)) {
1735  $this->ref_client = trim($this->ref_client);
1736  }
1737  if (isset($this->note) || isset($this->note_private)) {
1738  $this->note_private = (isset($this->note_private) ? trim($this->note_private) : trim($this->note));
1739  }
1740  if (isset($this->note_public)) {
1741  $this->note_public = trim($this->note_public);
1742  }
1743  if (isset($this->model_pdf)) {
1744  $this->model_pdf = trim($this->model_pdf);
1745  }
1746  if (isset($this->import_key)) {
1747  $this->import_key = trim($this->import_key);
1748  }
1749  if (!empty($this->duree_validite) && is_numeric($this->duree_validite)) {
1750  $this->fin_validite = $this->date + ($this->duree_validite * 24 * 3600);
1751  }
1752 
1753  // Check parameters
1754  // Put here code to add control on parameters values
1755 
1756  // Update request
1757  $sql = "UPDATE ".MAIN_DB_PREFIX."propal SET";
1758  $sql .= " ref=".(isset($this->ref) ? "'".$this->db->escape($this->ref)."'" : "null").",";
1759  $sql .= " ref_client=".(isset($this->ref_client) ? "'".$this->db->escape($this->ref_client)."'" : "null").",";
1760  $sql .= " ref_ext=".(isset($this->ref_ext) ? "'".$this->db->escape($this->ref_ext)."'" : "null").",";
1761  $sql .= " fk_soc=".(isset($this->socid) ? $this->socid : "null").",";
1762  $sql .= " datep=".(strval($this->date) != '' ? "'".$this->db->idate($this->date)."'" : 'null').",";
1763  if (!empty($this->fin_validite)) {
1764  $sql .= " fin_validite=".(strval($this->fin_validite) != '' ? "'".$this->db->idate($this->fin_validite)."'" : 'null').",";
1765  }
1766  $sql .= " date_valid=".(strval($this->date_validation) != '' ? "'".$this->db->idate($this->date_validation)."'" : 'null').",";
1767  $sql .= " total_tva=".(isset($this->total_tva) ? $this->total_tva : "null").",";
1768  $sql .= " localtax1=".(isset($this->total_localtax1) ? $this->total_localtax1 : "null").",";
1769  $sql .= " localtax2=".(isset($this->total_localtax2) ? $this->total_localtax2 : "null").",";
1770  $sql .= " total_ht=".(isset($this->total_ht) ? $this->total_ht : "null").",";
1771  $sql .= " total_ttc=".(isset($this->total_ttc) ? $this->total_ttc : "null").",";
1772  $sql .= " fk_statut=".(isset($this->statut) ? $this->statut : "null").",";
1773  $sql .= " fk_user_author=".(isset($this->user_author_id) ? $this->user_author_id : "null").",";
1774  $sql .= " fk_user_valid=".(isset($this->user_validation_id) ? $this->user_validation_id : "null").",";
1775  $sql .= " fk_projet=".(isset($this->fk_project) ? $this->fk_project : "null").",";
1776  $sql .= " fk_cond_reglement=".(isset($this->cond_reglement_id) ? $this->cond_reglement_id : "null").",";
1777  $sql .= " deposit_percent=".(!empty($this->deposit_percent) ? "'".$this->db->escape($this->deposit_percent)."'" : "null").",";
1778  $sql .= " fk_mode_reglement=".(isset($this->mode_reglement_id) ? $this->mode_reglement_id : "null").",";
1779  $sql .= " fk_input_reason=".(isset($this->demand_reason_id) ? $this->demand_reason_id : "null").",";
1780  $sql .= " note_private=".(isset($this->note_private) ? "'".$this->db->escape($this->note_private)."'" : "null").",";
1781  $sql .= " note_public=".(isset($this->note_public) ? "'".$this->db->escape($this->note_public)."'" : "null").",";
1782  $sql .= " model_pdf=".(isset($this->model_pdf) ? "'".$this->db->escape($this->model_pdf)."'" : "null").",";
1783  $sql .= " import_key=".(isset($this->import_key) ? "'".$this->db->escape($this->import_key)."'" : "null");
1784  $sql .= " WHERE rowid=".((int) $this->id);
1785 
1786  $this->db->begin();
1787 
1788  dol_syslog(get_class($this)."::update", LOG_DEBUG);
1789  $resql = $this->db->query($sql);
1790  if (!$resql) {
1791  $error++;
1792  $this->errors[] = "Error ".$this->db->lasterror();
1793  }
1794 
1795  if (!$error) {
1796  $result = $this->insertExtraFields();
1797  if ($result < 0) {
1798  $error++;
1799  }
1800  }
1801 
1802  if (!$error && !$notrigger) {
1803  // Call trigger
1804  $result = $this->call_trigger('PROPAL_MODIFY', $user);
1805  if ($result < 0) {
1806  $error++;
1807  }
1808  // End call triggers
1809  }
1810 
1811  // Commit or rollback
1812  if ($error) {
1813  foreach ($this->errors as $errmsg) {
1814  dol_syslog(get_class($this)."::update ".$errmsg, LOG_ERR);
1815  $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
1816  }
1817  $this->db->rollback();
1818  return -1 * $error;
1819  } else {
1820  $this->db->commit();
1821  return 1;
1822  }
1823  }
1824 
1825 
1826  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1835  public function fetch_lines($only_product = 0, $loadalsotranslation = 0, $sqlforgedfilters = '')
1836  {
1837  // phpcs:enable
1838  $this->lines = array();
1839 
1840  $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,';
1841  $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,';
1842  $sql .= ' d.fk_unit,';
1843  $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,';
1844  $sql .= ' p.weight, p.weight_units, p.volume, p.volume_units,';
1845  $sql .= ' d.date_start, d.date_end,';
1846  $sql .= ' d.fk_multicurrency, d.multicurrency_code, d.multicurrency_subprice, d.multicurrency_total_ht, d.multicurrency_total_tva, d.multicurrency_total_ttc';
1847  $sql .= ' FROM '.MAIN_DB_PREFIX.'propaldet as d';
1848  $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'product as p ON (d.fk_product = p.rowid)';
1849  $sql .= ' WHERE d.fk_propal = '.((int) $this->id);
1850  if ($only_product) {
1851  $sql .= ' AND p.fk_product_type = 0';
1852  }
1853  if ($sqlforgedfilters) {
1854  $sql .= $sqlforgedfilters;
1855  }
1856  $sql .= ' ORDER by d.rang';
1857 
1858  dol_syslog(get_class($this)."::fetch_lines", LOG_DEBUG);
1859  $result = $this->db->query($sql);
1860  if ($result) {
1861  require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php';
1862 
1863  $num = $this->db->num_rows($result);
1864 
1865  $i = 0;
1866  while ($i < $num) {
1867  $objp = $this->db->fetch_object($result);
1868 
1869  $line = new PropaleLigne($this->db);
1870 
1871  $line->rowid = $objp->rowid; //Deprecated
1872  $line->id = $objp->rowid;
1873  $line->fk_propal = $objp->fk_propal;
1874  $line->fk_parent_line = $objp->fk_parent_line;
1875  $line->product_type = $objp->product_type;
1876  $line->label = $objp->custom_label;
1877  $line->desc = $objp->description; // Description ligne
1878  $line->description = $objp->description; // Description ligne
1879  $line->qty = $objp->qty;
1880  $line->vat_src_code = $objp->vat_src_code;
1881  $line->tva_tx = $objp->tva_tx;
1882  $line->localtax1_tx = $objp->localtax1_tx;
1883  $line->localtax2_tx = $objp->localtax2_tx;
1884  $line->localtax1_type = $objp->localtax1_type;
1885  $line->localtax2_type = $objp->localtax2_type;
1886  $line->subprice = $objp->subprice;
1887  $line->fk_remise_except = $objp->fk_remise_except;
1888  $line->remise_percent = $objp->remise_percent;
1889  $line->price = $objp->price; // TODO deprecated
1890 
1891  $line->info_bits = $objp->info_bits;
1892  $line->total_ht = $objp->total_ht;
1893  $line->total_tva = $objp->total_tva;
1894  $line->total_localtax1 = $objp->total_localtax1;
1895  $line->total_localtax2 = $objp->total_localtax2;
1896  $line->total_ttc = $objp->total_ttc;
1897  $line->fk_fournprice = $objp->fk_fournprice;
1898  $marginInfos = getMarginInfos($objp->subprice, $objp->remise_percent, $objp->tva_tx, $objp->localtax1_tx, $objp->localtax2_tx, $line->fk_fournprice, $objp->pa_ht);
1899  $line->pa_ht = $marginInfos[0];
1900  $line->marge_tx = $marginInfos[1];
1901  $line->marque_tx = $marginInfos[2];
1902  $line->special_code = $objp->special_code;
1903  $line->rang = $objp->rang;
1904 
1905  $line->fk_product = $objp->fk_product;
1906 
1907  $line->ref = $objp->product_ref; // deprecated
1908  $line->libelle = $objp->product_label; // deprecated
1909 
1910  $line->product_ref = $objp->product_ref;
1911  $line->product_label = $objp->product_label;
1912  $line->product_desc = $objp->product_desc; // Description produit
1913  $line->product_tobatch = $objp->product_tobatch;
1914  $line->product_barcode = $objp->product_barcode;
1915 
1916  $line->fk_product_type = $objp->fk_product_type; // deprecated
1917  $line->fk_unit = $objp->fk_unit;
1918  $line->weight = $objp->weight;
1919  $line->weight_units = $objp->weight_units;
1920  $line->volume = $objp->volume;
1921  $line->volume_units = $objp->volume_units;
1922 
1923  $line->date_start = $this->db->jdate($objp->date_start);
1924  $line->date_end = $this->db->jdate($objp->date_end);
1925 
1926  // Multicurrency
1927  $line->fk_multicurrency = $objp->fk_multicurrency;
1928  $line->multicurrency_code = $objp->multicurrency_code;
1929  $line->multicurrency_subprice = $objp->multicurrency_subprice;
1930  $line->multicurrency_total_ht = $objp->multicurrency_total_ht;
1931  $line->multicurrency_total_tva = $objp->multicurrency_total_tva;
1932  $line->multicurrency_total_ttc = $objp->multicurrency_total_ttc;
1933 
1934  $line->fetch_optionals();
1935 
1936  // multilangs
1937  if (getDolGlobalInt('MAIN_MULTILANGS') && !empty($objp->fk_product) && !empty($loadalsotranslation)) {
1938  $tmpproduct = new Product($this->db);
1939  $tmpproduct->fetch($objp->fk_product);
1940  $tmpproduct->getMultiLangs();
1941 
1942  $line->multilangs = $tmpproduct->multilangs;
1943  }
1944 
1945  $this->lines[$i] = $line;
1946 
1947  $i++;
1948  }
1949 
1950  $this->db->free($result);
1951 
1952  return $num;
1953  } else {
1954  $this->error = $this->db->lasterror();
1955  return -3;
1956  }
1957  }
1958 
1966  public function valid($user, $notrigger = 0)
1967  {
1968  global $conf;
1969 
1970  require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
1971 
1972  $error = 0;
1973 
1974  // Protection
1975  if ($this->statut == self::STATUS_VALIDATED) {
1976  dol_syslog(get_class($this)."::valid action abandoned: already validated", LOG_WARNING);
1977  return 0;
1978  }
1979 
1980  if (!((!getDolGlobalString('MAIN_USE_ADVANCED_PERMS') && $user->hasRight('propal', 'creer'))
1981  || (getDolGlobalString('MAIN_USE_ADVANCED_PERMS') && $user->hasRight('propal', 'propal_advance', 'validate')))) {
1982  $this->error = 'ErrorPermissionDenied';
1983  dol_syslog(get_class($this)."::valid ".$this->error, LOG_ERR);
1984  return -1;
1985  }
1986 
1987  $now = dol_now();
1988 
1989  $this->db->begin();
1990 
1991  // Numbering module definition
1992  $soc = new Societe($this->db);
1993  $soc->fetch($this->socid);
1994 
1995  // Define new ref
1996  if (!$error && (preg_match('/^[\‍(]?PROV/i', $this->ref) || empty($this->ref))) { // empty should not happened, but when it occurs, the test save life
1997  $num = $this->getNextNumRef($soc);
1998  } else {
1999  $num = $this->ref;
2000  }
2001  $this->newref = dol_sanitizeFileName($num);
2002 
2003  $sql = "UPDATE ".MAIN_DB_PREFIX."propal";
2004  $sql .= " SET ref = '".$this->db->escape($num)."',";
2005  $sql .= " fk_statut = ".self::STATUS_VALIDATED.", date_valid='".$this->db->idate($now)."', fk_user_valid=".((int) $user->id);
2006  $sql .= " WHERE rowid = ".((int) $this->id)." AND fk_statut = ".self::STATUS_DRAFT;
2007 
2008  dol_syslog(get_class($this)."::valid", LOG_DEBUG);
2009  $resql = $this->db->query($sql);
2010  if (!$resql) {
2011  dol_print_error($this->db);
2012  $error++;
2013  }
2014 
2015  // Trigger calls
2016  if (!$error && !$notrigger) {
2017  // Call trigger
2018  $result = $this->call_trigger('PROPAL_VALIDATE', $user);
2019  if ($result < 0) {
2020  $error++;
2021  }
2022  // End call triggers
2023  }
2024 
2025  if (!$error) {
2026  $this->oldref = $this->ref;
2027 
2028  // Rename directory if dir was a temporary ref
2029  if (preg_match('/^[\‍(]?PROV/i', $this->ref)) {
2030  // Now we rename also files into index
2031  $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)."'";
2032  $sql .= " WHERE filename LIKE '".$this->db->escape($this->ref)."%' AND filepath = 'propale/".$this->db->escape($this->ref)."' and entity = ".((int) $conf->entity);
2033  $resql = $this->db->query($sql);
2034  if (!$resql) {
2035  $error++;
2036  $this->error = $this->db->lasterror();
2037  }
2038  $sql = 'UPDATE '.MAIN_DB_PREFIX."ecm_files set filepath = 'propale/".$this->db->escape($this->newref)."'";
2039  $sql .= " WHERE filepath = 'propale/".$this->db->escape($this->ref)."' and entity = ".$conf->entity;
2040  $resql = $this->db->query($sql);
2041  if (!$resql) {
2042  $error++;
2043  $this->error = $this->db->lasterror();
2044  }
2045 
2046  // We rename directory ($this->ref = old ref, $num = new ref) in order not to lose the attachments
2047  $oldref = dol_sanitizeFileName($this->ref);
2048  $newref = dol_sanitizeFileName($num);
2049  $dirsource = $conf->propal->multidir_output[$this->entity].'/'.$oldref;
2050  $dirdest = $conf->propal->multidir_output[$this->entity].'/'.$newref;
2051  if (!$error && file_exists($dirsource)) {
2052  dol_syslog(get_class($this)."::validate rename dir ".$dirsource." into ".$dirdest);
2053  if (@rename($dirsource, $dirdest)) {
2054  dol_syslog("Rename ok");
2055  // Rename docs starting with $oldref with $newref
2056  $listoffiles = dol_dir_list($dirdest, 'files', 1, '^'.preg_quote($oldref, '/'));
2057  foreach ($listoffiles as $fileentry) {
2058  $dirsource = $fileentry['name'];
2059  $dirdest = preg_replace('/^'.preg_quote($oldref, '/').'/', $newref, $dirsource);
2060  $dirsource = $fileentry['path'].'/'.$dirsource;
2061  $dirdest = $fileentry['path'].'/'.$dirdest;
2062  @rename($dirsource, $dirdest);
2063  }
2064  }
2065  }
2066  }
2067 
2068  $this->ref = $num;
2069  $this->statut = self::STATUS_VALIDATED;
2070  $this->status = self::STATUS_VALIDATED;
2071  $this->user_validation_id = $user->id;
2072  $this->datev = $now;
2073  $this->date_validation = $now;
2074 
2075  $this->db->commit();
2076  return 1;
2077  } else {
2078  $this->db->rollback();
2079  return -1;
2080  }
2081  }
2082 
2083 
2084  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2093  public function set_date($user, $date, $notrigger = 0)
2094  {
2095  // phpcs:enable
2096  if (empty($date)) {
2097  $this->error = 'ErrorBadParameter';
2098  dol_syslog(get_class($this)."::set_date ".$this->error, LOG_ERR);
2099  return -1;
2100  }
2101 
2102  if ($user->hasRight('propal', 'creer')) {
2103  $error = 0;
2104 
2105  $this->db->begin();
2106 
2107  $sql = "UPDATE ".MAIN_DB_PREFIX."propal SET datep = '".$this->db->idate($date)."'";
2108  $sql .= " WHERE rowid = ".((int) $this->id);
2109 
2110  dol_syslog(__METHOD__, LOG_DEBUG);
2111  $resql = $this->db->query($sql);
2112  if (!$resql) {
2113  $this->errors[] = $this->db->error();
2114  $error++;
2115  }
2116 
2117  if (!$error) {
2118  $this->oldcopy = clone $this;
2119  $this->date = $date;
2120  $this->datep = $date; // deprecated
2121  }
2122 
2123  if (!$notrigger && empty($error)) {
2124  // Call trigger
2125  $result = $this->call_trigger('PROPAL_MODIFY', $user);
2126  if ($result < 0) {
2127  $error++;
2128  }
2129  // End call triggers
2130  }
2131 
2132  if (!$error) {
2133  $this->db->commit();
2134  return 1;
2135  } else {
2136  foreach ($this->errors as $errmsg) {
2137  dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2138  $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2139  }
2140  $this->db->rollback();
2141  return -1 * $error;
2142  }
2143  }
2144 
2145  return -1;
2146  }
2147 
2148  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2157  public function set_echeance($user, $date_end_validity, $notrigger = 0)
2158  {
2159  // phpcs:enable
2160  if ($user->hasRight('propal', 'creer')) {
2161  $error = 0;
2162 
2163  $this->db->begin();
2164 
2165  $sql = "UPDATE ".MAIN_DB_PREFIX."propal SET fin_validite = ".($date_end_validity != '' ? "'".$this->db->idate($date_end_validity)."'" : 'null');
2166  $sql .= " WHERE rowid = ".((int) $this->id);
2167 
2168  dol_syslog(__METHOD__, LOG_DEBUG);
2169 
2170  $resql = $this->db->query($sql);
2171  if (!$resql) {
2172  $this->errors[] = $this->db->error();
2173  $error++;
2174  }
2175 
2176 
2177  if (!$error) {
2178  $this->oldcopy = clone $this;
2179  $this->fin_validite = $date_end_validity;
2180  }
2181 
2182  if (!$notrigger && empty($error)) {
2183  // Call trigger
2184  $result = $this->call_trigger('PROPAL_MODIFY', $user);
2185  if ($result < 0) {
2186  $error++;
2187  }
2188  // End call triggers
2189  }
2190 
2191  if (!$error) {
2192  $this->db->commit();
2193  return 1;
2194  } else {
2195  foreach ($this->errors as $errmsg) {
2196  dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2197  $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2198  }
2199  $this->db->rollback();
2200  return -1 * $error;
2201  }
2202  }
2203 
2204  return -1;
2205  }
2206 
2207  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2217  public function set_date_livraison($user, $delivery_date, $notrigger = 0)
2218  {
2219  // phpcs:enable
2220  return $this->setDeliveryDate($user, $delivery_date, $notrigger);
2221  }
2222 
2231  public function setDeliveryDate($user, $delivery_date, $notrigger = 0)
2232  {
2233  if ($user->hasRight('propal', 'creer')) {
2234  $error = 0;
2235 
2236  $this->db->begin();
2237 
2238  $sql = "UPDATE ".MAIN_DB_PREFIX."propal ";
2239  $sql .= " SET date_livraison = ".($delivery_date != '' ? "'".$this->db->idate($delivery_date)."'" : 'null');
2240  $sql .= " WHERE rowid = ".((int) $this->id);
2241 
2242  dol_syslog(__METHOD__, LOG_DEBUG);
2243  $resql = $this->db->query($sql);
2244  if (!$resql) {
2245  $this->errors[] = $this->db->error();
2246  $error++;
2247  }
2248 
2249  if (!$error) {
2250  $this->oldcopy = clone $this;
2251  $this->delivery_date = $delivery_date;
2252  }
2253 
2254  if (!$notrigger && empty($error)) {
2255  // Call trigger
2256  $result = $this->call_trigger('PROPAL_MODIFY', $user);
2257  if ($result < 0) {
2258  $error++;
2259  }
2260  // End call triggers
2261  }
2262 
2263  if (!$error) {
2264  $this->db->commit();
2265  return 1;
2266  } else {
2267  foreach ($this->errors as $errmsg) {
2268  dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2269  $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2270  }
2271  $this->db->rollback();
2272  return -1 * $error;
2273  }
2274  }
2275 
2276  return -1;
2277  }
2278 
2279  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2288  public function set_availability($user, $id, $notrigger = 0)
2289  {
2290  // phpcs:enable
2291  if ($user->hasRight('propal', 'creer') && $this->statut >= self::STATUS_DRAFT) {
2292  $error = 0;
2293 
2294  $this->db->begin();
2295 
2296  $sql = "UPDATE ".MAIN_DB_PREFIX."propal";
2297  $sql .= " SET fk_availability = ".((int) $id);
2298  $sql .= " WHERE rowid = ".((int) $this->id);
2299 
2300  dol_syslog(__METHOD__.' availability('.$id.')', LOG_DEBUG);
2301  $resql = $this->db->query($sql);
2302  if (!$resql) {
2303  $this->errors[] = $this->db->error();
2304  $error++;
2305  }
2306 
2307  if (!$error) {
2308  $this->oldcopy = clone $this;
2309  $this->fk_availability = $id;
2310  $this->availability_id = $id;
2311  }
2312 
2313  if (!$notrigger && empty($error)) {
2314  // Call trigger
2315  $result = $this->call_trigger('PROPAL_MODIFY', $user);
2316  if ($result < 0) {
2317  $error++;
2318  }
2319  // End call triggers
2320  }
2321 
2322  if (!$error) {
2323  $this->db->commit();
2324  return 1;
2325  } else {
2326  foreach ($this->errors as $errmsg) {
2327  dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2328  $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2329  }
2330  $this->db->rollback();
2331  return -1 * $error;
2332  }
2333  } else {
2334  $error_str = 'Propal status do not meet requirement '.$this->statut;
2335  dol_syslog(__METHOD__.$error_str, LOG_ERR);
2336  $this->error = $error_str;
2337  $this->errors[] = $this->error;
2338  return -2;
2339  }
2340  }
2341 
2342  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2351  public function set_demand_reason($user, $id, $notrigger = 0)
2352  {
2353  // phpcs:enable
2354  if ($user->hasRight('propal', 'creer') && $this->statut >= self::STATUS_DRAFT) {
2355  $error = 0;
2356 
2357  $this->db->begin();
2358 
2359  $sql = "UPDATE ".MAIN_DB_PREFIX."propal ";
2360  $sql .= " SET fk_input_reason = ".((int) $id);
2361  $sql .= " WHERE rowid = ".((int) $this->id);
2362 
2363  dol_syslog(__METHOD__, LOG_DEBUG);
2364  $resql = $this->db->query($sql);
2365  if (!$resql) {
2366  $this->errors[] = $this->db->error();
2367  $error++;
2368  }
2369 
2370 
2371  if (!$error) {
2372  $this->oldcopy = clone $this;
2373 
2374  $this->demand_reason_id = $id;
2375  }
2376 
2377 
2378  if (!$notrigger && empty($error)) {
2379  // Call trigger
2380  $result = $this->call_trigger('PROPAL_MODIFY', $user);
2381  if ($result < 0) {
2382  $error++;
2383  }
2384  // End call triggers
2385  }
2386 
2387  if (!$error) {
2388  $this->db->commit();
2389  return 1;
2390  } else {
2391  foreach ($this->errors as $errmsg) {
2392  dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2393  $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2394  }
2395  $this->db->rollback();
2396  return -1 * $error;
2397  }
2398  } else {
2399  $error_str = 'Propal status do not meet requirement '.$this->statut;
2400  dol_syslog(__METHOD__.$error_str, LOG_ERR);
2401  $this->error = $error_str;
2402  $this->errors[] = $this->error;
2403  return -2;
2404  }
2405  }
2406 
2407  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2416  public function set_ref_client($user, $ref_client, $notrigger = 0)
2417  {
2418  // phpcs:enable
2419  if ($user->hasRight('propal', 'creer')) {
2420  $error = 0;
2421 
2422  $this->db->begin();
2423 
2424  $sql = "UPDATE ".MAIN_DB_PREFIX."propal SET ref_client = ".(empty($ref_client) ? 'NULL' : "'".$this->db->escape($ref_client)."'");
2425  $sql .= " WHERE rowid = ".((int) $this->id);
2426 
2427  dol_syslog(__METHOD__.' $this->id='.$this->id.', ref_client='.$ref_client, LOG_DEBUG);
2428  $resql = $this->db->query($sql);
2429  if (!$resql) {
2430  $this->errors[] = $this->db->error();
2431  $error++;
2432  }
2433 
2434  if (!$error) {
2435  $this->oldcopy = clone $this;
2436  $this->ref_client = $ref_client;
2437  }
2438 
2439  if (!$notrigger && empty($error)) {
2440  // Call trigger
2441  $result = $this->call_trigger('PROPAL_MODIFY', $user);
2442  if ($result < 0) {
2443  $error++;
2444  }
2445  // End call triggers
2446  }
2447 
2448  if (!$error) {
2449  $this->db->commit();
2450  return 1;
2451  } else {
2452  foreach ($this->errors as $errmsg) {
2453  dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2454  $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2455  }
2456  $this->db->rollback();
2457  return -1 * $error;
2458  }
2459  }
2460 
2461  return -1;
2462  }
2463 
2464 
2474  public function reopen($user, $status, $note = '', $notrigger = 0)
2475  {
2476  $error = 0;
2477 
2478  $sql = "UPDATE ".MAIN_DB_PREFIX."propal";
2479  $sql .= " SET fk_statut = ".((int) $status).",";
2480  if (!empty($note)) {
2481  $sql .= " note_private = '".$this->db->escape($note)."',";
2482  }
2483  $sql .= " date_cloture=NULL, fk_user_cloture=NULL";
2484  $sql .= " WHERE rowid = ".((int) $this->id);
2485 
2486  $this->db->begin();
2487 
2488  dol_syslog(get_class($this)."::reopen", LOG_DEBUG);
2489  $resql = $this->db->query($sql);
2490  if (!$resql) {
2491  $error++;
2492  $this->errors[] = "Error ".$this->db->lasterror();
2493  }
2494  if (!$error) {
2495  if (!$notrigger) {
2496  // Call trigger
2497  $result = $this->call_trigger('PROPAL_REOPEN', $user);
2498  if ($result < 0) {
2499  $error++;
2500  }
2501  // End call triggers
2502  }
2503  }
2504 
2505  // Commit or rollback
2506  if ($error) {
2507  if (!empty($this->errors)) {
2508  foreach ($this->errors as $errmsg) {
2509  dol_syslog(get_class($this)."::update ".$errmsg, LOG_ERR);
2510  $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2511  }
2512  }
2513  $this->db->rollback();
2514  return -1 * $error;
2515  } else {
2516  $this->statut = $status;
2517  $this->status = $status;
2518 
2519  $this->db->commit();
2520  return 1;
2521  }
2522  }
2523 
2533  public function closeProposal($user, $status, $note = '', $notrigger = 0)
2534  {
2535  global $langs,$conf;
2536 
2537  $error = 0;
2538  $now = dol_now();
2539 
2540  $this->db->begin();
2541 
2542  $newprivatenote = dol_concatdesc($this->note_private, $note);
2543 
2544  if (!getDolGlobalString('PROPALE_KEEP_OLD_SIGNATURE_INFO')) {
2545  $date_signature = $now;
2546  $fk_user_signature = $user->id;
2547  } else {
2548  $this->info($this->id);
2549  if (!isset($this->date_signature) || $this->date_signature == '') {
2550  $date_signature = $now;
2551  $fk_user_signature = $user->id;
2552  } else {
2553  $date_signature = $this->date_signature;
2554  $fk_user_signature = $this->user_signature->id;
2555  }
2556  }
2557 
2558  $sql = "UPDATE ".MAIN_DB_PREFIX."propal";
2559  $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;
2560  $sql .= " WHERE rowid = ".((int) $this->id);
2561 
2562  $resql = $this->db->query($sql);
2563  if ($resql) {
2564  // Status self::STATUS_REFUSED by default
2565  $modelpdf = getDolGlobalString('PROPALE_ADDON_PDF_ODT_CLOSED', $this->model_pdf);
2566  $trigger_name = 'PROPAL_CLOSE_REFUSED'; // used later in call_trigger()
2567 
2568  if ($status == self::STATUS_SIGNED) { // Status self::STATUS_SIGNED
2569  $trigger_name = 'PROPAL_CLOSE_SIGNED'; // used later in call_trigger()
2570  $modelpdf = getDolGlobalString('PROPALE_ADDON_PDF_ODT_TOBILL') ? $conf->global->PROPALE_ADDON_PDF_ODT_TOBILL : $this->model_pdf;
2571 
2572  // The connected company is classified as a client
2573  $soc = new Societe($this->db);
2574  $soc->id = $this->socid;
2575  $result = $soc->setAsCustomer();
2576 
2577  if ($result < 0) {
2578  $this->error = $this->db->lasterror();
2579  $this->db->rollback();
2580  return -2;
2581  }
2582  }
2583 
2584  if (!getDolGlobalString('MAIN_DISABLE_PDF_AUTOUPDATE') && !getDolGlobalInt('PROPAL_DISABLE_AUTOUPDATE_ON_CLOSE')) {
2585  // Define output language
2586  $outputlangs = $langs;
2587  if (getDolGlobalInt('MAIN_MULTILANGS')) {
2588  $outputlangs = new Translate("", $conf);
2589  $newlang = (GETPOST('lang_id', 'aZ09') ? GETPOST('lang_id', 'aZ09') : $this->thirdparty->default_lang);
2590  $outputlangs->setDefaultLang($newlang);
2591  }
2592 
2593  // PDF
2594  $hidedetails = (getDolGlobalString('MAIN_GENERATE_DOCUMENTS_HIDE_DETAILS') ? 1 : 0);
2595  $hidedesc = (getDolGlobalString('MAIN_GENERATE_DOCUMENTS_HIDE_DESC') ? 1 : 0);
2596  $hideref = (getDolGlobalString('MAIN_GENERATE_DOCUMENTS_HIDE_REF') ? 1 : 0);
2597 
2598  //$ret=$object->fetch($id); // Reload to get new records
2599  $this->generateDocument($modelpdf, $outputlangs, $hidedetails, $hidedesc, $hideref);
2600  }
2601 
2602  if (!$error) {
2603  $this->oldcopy = clone $this;
2604  $this->statut = $status;
2605  $this->status = $status;
2606  $this->date_signature = $date_signature;
2607  $this->note_private = $newprivatenote;
2608  }
2609 
2610  if (!$notrigger && empty($error)) {
2611  // Call trigger
2612  $result = $this->call_trigger($trigger_name, $user);
2613  if ($result < 0) {
2614  $error++;
2615  }
2616  // End call triggers
2617  }
2618 
2619  if (!$error) {
2620  $this->db->commit();
2621  return 1;
2622  } else {
2623  $this->statut = $this->oldcopy->statut;
2624  $this->status = $this->oldcopy->statut;
2625  $this->date_signature = $this->oldcopy->date_signature;
2626  $this->note_private = $this->oldcopy->note_private;
2627 
2628  $this->db->rollback();
2629  return -1;
2630  }
2631  } else {
2632  $this->error = $this->db->lasterror();
2633  $this->db->rollback();
2634  return -1;
2635  }
2636  }
2637 
2646  public function classifyBilled(User $user, $notrigger = 0, $note = '')
2647  {
2648  global $conf, $langs;
2649 
2650  $error = 0;
2651 
2652  $now = dol_now();
2653  $num = 0;
2654 
2655  $triggerName = 'PROPAL_CLASSIFY_BILLED';
2656 
2657  $this->db->begin();
2658 
2659  $newprivatenote = dol_concatdesc($this->note_private, $note);
2660 
2661  $sql = 'UPDATE '.MAIN_DB_PREFIX.'propal SET fk_statut = '.self::STATUS_BILLED.", ";
2662  $sql .= " note_private = '".$this->db->escape($newprivatenote)."', date_cloture='".$this->db->idate($now)."', fk_user_cloture=".((int) $user->id);
2663  $sql .= ' WHERE rowid = '.((int) $this->id).' AND fk_statut = '.((int) self::STATUS_SIGNED);
2664 
2665  dol_syslog(__METHOD__, LOG_DEBUG);
2666  $resql = $this->db->query($sql);
2667  if (!$resql) {
2668  $this->errors[] = $this->db->error();
2669  $error++;
2670  } else {
2671  $num = $this->db->affected_rows($resql);
2672  }
2673 
2674  if (!$error) {
2675  $modelpdf = getDolGlobalString('PROPALE_ADDON_PDF_ODT_CLOSED', $this->model_pdf);
2676 
2677  if (!getDolGlobalString('MAIN_DISABLE_PDF_AUTOUPDATE')) {
2678  // Define output language
2679  $outputlangs = $langs;
2680  if (getDolGlobalInt('MAIN_MULTILANGS')) {
2681  $outputlangs = new Translate("", $conf);
2682  $newlang = (GETPOST('lang_id', 'aZ09') ? GETPOST('lang_id', 'aZ09') : $this->thirdparty->default_lang);
2683  $outputlangs->setDefaultLang($newlang);
2684  }
2685 
2686  // PDF
2687  $hidedetails = (getDolGlobalString('MAIN_GENERATE_DOCUMENTS_HIDE_DETAILS') ? 1 : 0);
2688  $hidedesc = (getDolGlobalString('MAIN_GENERATE_DOCUMENTS_HIDE_DESC') ? 1 : 0);
2689  $hideref = (getDolGlobalString('MAIN_GENERATE_DOCUMENTS_HIDE_REF') ? 1 : 0);
2690 
2691  //$ret=$object->fetch($id); // Reload to get new records
2692  $this->generateDocument($modelpdf, $outputlangs, $hidedetails, $hidedesc, $hideref);
2693  }
2694 
2695  $this->oldcopy = clone $this;
2696  $this->statut = self::STATUS_BILLED;
2697  $this->date_cloture = $now;
2698  $this->note_private = $newprivatenote;
2699  }
2700 
2701  if (!$notrigger && empty($error)) {
2702  // Call trigger
2703  $result = $this->call_trigger($triggerName, $user);
2704  if ($result < 0) {
2705  $error++;
2706  }
2707  // End call triggers
2708  }
2709 
2710  if (!$error) {
2711  $this->db->commit();
2712  return $num;
2713  } else {
2714  foreach ($this->errors as $errmsg) {
2715  dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2716  $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2717  }
2718  $this->db->rollback();
2719  return -1 * $error;
2720  }
2721  }
2722 
2729  public function setCancel(User $user)
2730  {
2731  $error = 0;
2732 
2733  $this->db->begin();
2734 
2735  $sql = "UPDATE ". MAIN_DB_PREFIX . "propal";
2736  $sql .= " SET fk_statut = " . self::STATUS_CANCELED . ",";
2737  $sql .= " fk_user_modif = " . ((int) $user->id);
2738  $sql .= " WHERE rowid = " . ((int) $this->id);
2739 
2740  dol_syslog(get_class($this)."::cancel", LOG_DEBUG);
2741  if ($this->db->query($sql)) {
2742  if (!$error) {
2743  // Call trigger
2744  $result = $this->call_trigger('PROPAL_CANCEL', $user);
2745  if ($result < 0) {
2746  $error++;
2747  }
2748  // End call triggers
2749  }
2750 
2751  if (!$error) {
2752  $this->statut = self::STATUS_CANCELED;
2753  $this->db->commit();
2754  return 1;
2755  } else {
2756  foreach ($this->errors as $errmsg) {
2757  dol_syslog(get_class($this)."::cancel ".$errmsg, LOG_ERR);
2758  $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2759  }
2760  $this->db->rollback();
2761  return -1;
2762  }
2763  } else {
2764  $this->error = $this->db->error();
2765  $this->db->rollback();
2766  return -1;
2767  }
2768  }
2769 
2770  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2778  public function setDraft($user, $notrigger = 0)
2779  {
2780  // phpcs:enable
2781  $error = 0;
2782 
2783  // Protection
2784  if ($this->statut <= self::STATUS_DRAFT) {
2785  return 0;
2786  }
2787 
2788  dol_syslog(get_class($this)."::setDraft", LOG_DEBUG);
2789 
2790  $this->db->begin();
2791 
2792  $sql = "UPDATE ".MAIN_DB_PREFIX."propal";
2793  $sql .= " SET fk_statut = ".self::STATUS_DRAFT;
2794  $sql .= ", online_sign_ip = NULL , online_sign_name = NULL";
2795  $sql .= " WHERE rowid = ".((int) $this->id);
2796 
2797  $resql = $this->db->query($sql);
2798  if (!$resql) {
2799  $this->errors[] = $this->db->error();
2800  $error++;
2801  }
2802 
2803  if (!$error) {
2804  $this->oldcopy = clone $this;
2805  }
2806 
2807  if (!$notrigger && empty($error)) {
2808  // Call trigger
2809  $result = $this->call_trigger('PROPAL_MODIFY', $user);
2810  if ($result < 0) {
2811  $error++;
2812  }
2813  // End call triggers
2814  }
2815 
2816  if (!$error) {
2817  $this->statut = self::STATUS_DRAFT;
2818  $this->status = self::STATUS_DRAFT;
2819 
2820  $this->db->commit();
2821  return 1;
2822  } else {
2823  foreach ($this->errors as $errmsg) {
2824  dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2825  $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2826  }
2827  $this->db->rollback();
2828  return -1 * $error;
2829  }
2830  }
2831 
2832 
2833  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2847  public function liste_array($shortlist = 0, $draft = 0, $notcurrentuser = 0, $socid = 0, $limit = 0, $offset = 0, $sortfield = 'p.datep', $sortorder = 'DESC')
2848  {
2849  // phpcs:enable
2850  global $user;
2851 
2852  $ga = array();
2853 
2854  $sql = "SELECT s.rowid, s.nom as name, s.client,";
2855  $sql .= " p.rowid as propalid, p.fk_statut, p.total_ht, p.ref, p.remise, ";
2856  $sql .= " p.datep as dp, p.fin_validite as datelimite";
2857  $sql .= " FROM ".MAIN_DB_PREFIX."societe as s, ".MAIN_DB_PREFIX."propal as p, ".MAIN_DB_PREFIX."c_propalst as c";
2858  $sql .= " WHERE p.entity IN (".getEntity('propal').")";
2859  $sql .= " AND p.fk_soc = s.rowid";
2860  $sql .= " AND p.fk_statut = c.id";
2861 
2862  // If the internal user must only see his customers, force searching by him
2863  $search_sale = 0;
2864  if (!$user->hasRight('societe', 'client', 'voir')) {
2865  $search_sale = $user->id;
2866  }
2867  // Search on sale representative
2868  if ($search_sale && $search_sale != '-1') {
2869  if ($search_sale == -2) {
2870  $sql .= " AND NOT EXISTS (SELECT sc.fk_soc FROM ".MAIN_DB_PREFIX."societe_commerciaux as sc WHERE sc.fk_soc = p.fk_soc)";
2871  } elseif ($search_sale > 0) {
2872  $sql .= " AND EXISTS (SELECT sc.fk_soc FROM ".MAIN_DB_PREFIX."societe_commerciaux as sc WHERE sc.fk_soc = p.fk_soc AND sc.fk_user = ".((int) $search_sale).")";
2873  }
2874  }
2875  // Search on socid
2876  if ($socid) {
2877  $sql .= " AND p.fk_soc = ".((int) $socid);
2878  }
2879  if ($draft) {
2880  $sql .= " AND p.fk_statut = ".((int) self::STATUS_DRAFT);
2881  }
2882  if ($notcurrentuser > 0) {
2883  $sql .= " AND p.fk_user_author <> ".((int) $user->id);
2884  }
2885  $sql .= $this->db->order($sortfield, $sortorder);
2886  $sql .= $this->db->plimit($limit, $offset);
2887 
2888  $result = $this->db->query($sql);
2889  if ($result) {
2890  $num = $this->db->num_rows($result);
2891  if ($num) {
2892  $i = 0;
2893  while ($i < $num) {
2894  $obj = $this->db->fetch_object($result);
2895 
2896  if ($shortlist == 1) {
2897  $ga[$obj->propalid] = $obj->ref;
2898  } elseif ($shortlist == 2) {
2899  $ga[$obj->propalid] = $obj->ref.' ('.$obj->name.')';
2900  } else {
2901  $ga[$i]['id'] = $obj->propalid;
2902  $ga[$i]['ref'] = $obj->ref;
2903  $ga[$i]['name'] = $obj->name;
2904  }
2905 
2906  $i++;
2907  }
2908  }
2909  return $ga;
2910  } else {
2911  dol_print_error($this->db);
2912  return -1;
2913  }
2914  }
2915 
2921  public function getInvoiceArrayList()
2922  {
2923  return $this->InvoiceArrayList($this->id);
2924  }
2925 
2926  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2933  public function InvoiceArrayList($id)
2934  {
2935  // phpcs:enable
2936  $ga = array();
2937  $linkedInvoices = array();
2938 
2939  $this->fetchObjectLinked($id, $this->element);
2940  foreach ($this->linkedObjectsIds as $objecttype => $objectid) {
2941  // Nouveau système du common object renvoi des rowid et non un id linéaire de 1 à n
2942  // On parcourt donc une liste d'objets en tant qu'objet unique
2943  foreach ($objectid as $key => $object) {
2944  // Cas des factures liees directement
2945  if ($objecttype == 'facture') {
2946  $linkedInvoices[] = $object;
2947  } else {
2948  // Cas des factures liees par un autre object (ex: commande)
2949  $this->fetchObjectLinked($object, $objecttype);
2950  foreach ($this->linkedObjectsIds as $subobjecttype => $subobjectid) {
2951  foreach ($subobjectid as $subkey => $subobject) {
2952  if ($subobjecttype == 'facture') {
2953  $linkedInvoices[] = $subobject;
2954  }
2955  }
2956  }
2957  }
2958  }
2959  }
2960 
2961  if (count($linkedInvoices) > 0) {
2962  $sql = "SELECT rowid as facid, ref, total_ht as total, datef as df, fk_user_author, fk_statut, paye";
2963  $sql .= " FROM ".MAIN_DB_PREFIX."facture";
2964  $sql .= " WHERE rowid IN (".$this->db->sanitize(implode(',', $linkedInvoices)).")";
2965 
2966  dol_syslog(get_class($this)."::InvoiceArrayList", LOG_DEBUG);
2967  $resql = $this->db->query($sql);
2968 
2969  if ($resql) {
2970  $tab_sqlobj = array();
2971  $nump = $this->db->num_rows($resql);
2972  for ($i = 0; $i < $nump; $i++) {
2973  $sqlobj = $this->db->fetch_object($resql);
2974  $tab_sqlobj[] = $sqlobj;
2975  }
2976  $this->db->free($resql);
2977 
2978  $nump = count($tab_sqlobj);
2979 
2980  if ($nump) {
2981  $i = 0;
2982  while ($i < $nump) {
2983  $obj = array_shift($tab_sqlobj);
2984 
2985  $ga[$i] = $obj;
2986 
2987  $i++;
2988  }
2989  }
2990  return $ga;
2991  } else {
2992  return -1;
2993  }
2994  } else {
2995  return $ga;
2996  }
2997  }
2998 
3006  public function delete($user, $notrigger = 0)
3007  {
3008  global $conf;
3009  require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
3010 
3011  $error = 0;
3012 
3013  $this->db->begin();
3014 
3015  if (!$notrigger) {
3016  // Call trigger
3017  $result = $this->call_trigger('PROPAL_DELETE', $user);
3018  if ($result < 0) {
3019  $error++;
3020  }
3021  // End call triggers
3022  }
3023 
3024  // Delete extrafields of lines and lines
3025  if (!$error && !empty($this->table_element_line)) {
3026  $tabletodelete = $this->table_element_line;
3027  $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).")";
3028  $sql = "DELETE FROM ".MAIN_DB_PREFIX.$tabletodelete." WHERE ".$this->fk_element." = ".((int) $this->id);
3029  if (!$this->db->query($sqlef) || !$this->db->query($sql)) {
3030  $error++;
3031  $this->error = $this->db->lasterror();
3032  $this->errors[] = $this->error;
3033  dol_syslog(get_class($this)."::delete error ".$this->error, LOG_ERR);
3034  }
3035  }
3036 
3037  if (!$error) {
3038  // Delete linked object
3039  $res = $this->deleteObjectLinked();
3040  if ($res < 0) {
3041  $error++;
3042  }
3043  }
3044 
3045  if (!$error) {
3046  // Delete linked contacts
3047  $res = $this->delete_linked_contact();
3048  if ($res < 0) {
3049  $error++;
3050  }
3051  }
3052 
3053  // Removed extrafields of object
3054  if (!$error) {
3055  $result = $this->deleteExtraFields();
3056  if ($result < 0) {
3057  $error++;
3058  dol_syslog(get_class($this)."::delete error ".$this->error, LOG_ERR);
3059  }
3060  }
3061 
3062  // Delete main record
3063  if (!$error) {
3064  $sql = "DELETE FROM ".MAIN_DB_PREFIX.$this->table_element." WHERE rowid = ".((int) $this->id);
3065  $res = $this->db->query($sql);
3066  if (!$res) {
3067  $error++;
3068  $this->error = $this->db->lasterror();
3069  $this->errors[] = $this->error;
3070  dol_syslog(get_class($this)."::delete error ".$this->error, LOG_ERR);
3071  }
3072  }
3073 
3074  // Delete record into ECM index and physically
3075  if (!$error) {
3076  $res = $this->deleteEcmFiles(0); // Deleting files physically is done later with the dol_delete_dir_recursive
3077  $res = $this->deleteEcmFiles(1); // Deleting files physically is done later with the dol_delete_dir_recursive
3078  if (!$res) {
3079  $error++;
3080  }
3081  }
3082 
3083  if (!$error) {
3084  // We remove directory
3085  $ref = dol_sanitizeFileName($this->ref);
3086  if ($conf->propal->multidir_output[$this->entity] && !empty($this->ref)) {
3087  $dir = $conf->propal->multidir_output[$this->entity]."/".$ref;
3088  $file = $dir."/".$ref.".pdf";
3089  if (file_exists($file)) {
3090  dol_delete_preview($this);
3091 
3092  if (!dol_delete_file($file, 0, 0, 0, $this)) {
3093  $this->error = 'ErrorFailToDeleteFile';
3094  $this->errors[] = $this->error;
3095  $this->db->rollback();
3096  return 0;
3097  }
3098  }
3099  if (file_exists($dir)) {
3100  $res = @dol_delete_dir_recursive($dir); // delete files physically + into ecm tables
3101  if (!$res) {
3102  $this->error = 'ErrorFailToDeleteDir';
3103  $this->errors[] = $this->error;
3104  $this->db->rollback();
3105  return 0;
3106  }
3107  }
3108  }
3109  }
3110 
3111  if (!$error) {
3112  dol_syslog(get_class($this)."::delete ".$this->id." by ".$user->id, LOG_DEBUG);
3113  $this->db->commit();
3114  return 1;
3115  } else {
3116  $this->db->rollback();
3117  return -1;
3118  }
3119  }
3120 
3129  public function availability($availability_id, $notrigger = 0)
3130  {
3131  global $user;
3132 
3133  if ($this->statut >= self::STATUS_DRAFT) {
3134  $error = 0;
3135 
3136  $this->db->begin();
3137 
3138  $sql = 'UPDATE '.MAIN_DB_PREFIX.'propal';
3139  $sql .= ' SET fk_availability = '.((int) $availability_id);
3140  $sql .= ' WHERE rowid='.((int) $this->id);
3141 
3142  dol_syslog(__METHOD__.' availability('.$availability_id.')', LOG_DEBUG);
3143  $resql = $this->db->query($sql);
3144  if (!$resql) {
3145  $this->errors[] = $this->db->error();
3146  $error++;
3147  }
3148 
3149  if (!$error) {
3150  $this->oldcopy = clone $this;
3151  $this->availability_id = $availability_id;
3152  }
3153 
3154  if (!$notrigger && empty($error)) {
3155  // Call trigger
3156  $result = $this->call_trigger('PROPAL_MODIFY', $user);
3157  if ($result < 0) {
3158  $error++;
3159  }
3160  // End call triggers
3161  }
3162 
3163  if (!$error) {
3164  $this->db->commit();
3165  return 1;
3166  } else {
3167  foreach ($this->errors as $errmsg) {
3168  dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
3169  $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
3170  }
3171  $this->db->rollback();
3172  return -1 * $error;
3173  }
3174  } else {
3175  $error_str = 'Propal status do not meet requirement '.$this->statut;
3176  dol_syslog(__METHOD__.$error_str, LOG_ERR);
3177  $this->error = $error_str;
3178  $this->errors[] = $this->error;
3179  return -2;
3180  }
3181  }
3182 
3183  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3192  public function demand_reason($demand_reason_id, $notrigger = 0)
3193  {
3194  // phpcs:enable
3195  global $user;
3196 
3197  if ($this->status >= self::STATUS_DRAFT) {
3198  $error = 0;
3199 
3200  $this->db->begin();
3201 
3202  $sql = 'UPDATE '.MAIN_DB_PREFIX.'propal';
3203  $sql .= ' SET fk_input_reason = '.((int) $demand_reason_id);
3204  $sql .= ' WHERE rowid='.((int) $this->id);
3205 
3206  dol_syslog(__METHOD__.' demand_reason('.$demand_reason_id.')', LOG_DEBUG);
3207  $resql = $this->db->query($sql);
3208  if (!$resql) {
3209  $this->errors[] = $this->db->error();
3210  $error++;
3211  }
3212 
3213  if (!$error) {
3214  $this->oldcopy = clone $this;
3215  $this->demand_reason_id = $demand_reason_id;
3216  }
3217 
3218  if (!$notrigger && empty($error)) {
3219  // Call trigger
3220  $result = $this->call_trigger('PROPAL_MODIFY', $user);
3221  if ($result < 0) {
3222  $error++;
3223  }
3224  // End call triggers
3225  }
3226 
3227  if (!$error) {
3228  $this->db->commit();
3229  return 1;
3230  } else {
3231  foreach ($this->errors as $errmsg) {
3232  dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
3233  $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
3234  }
3235  $this->db->rollback();
3236  return -1 * $error;
3237  }
3238  } else {
3239  $error_str = 'Propal status do not meet requirement '.$this->statut;
3240  dol_syslog(__METHOD__.$error_str, LOG_ERR);
3241  $this->error = $error_str;
3242  $this->errors[] = $this->error;
3243  return -2;
3244  }
3245  }
3246 
3247 
3254  public function info($id)
3255  {
3256  $sql = "SELECT c.rowid, ";
3257  $sql .= " c.datec, c.date_valid as datev, c.date_signature, c.date_cloture,";
3258  $sql .= " c.fk_user_author, c.fk_user_valid, c.fk_user_signature, c.fk_user_cloture";
3259  $sql .= " FROM ".MAIN_DB_PREFIX."propal as c";
3260  $sql .= " WHERE c.rowid = ".((int) $id);
3261 
3262  $result = $this->db->query($sql);
3263 
3264  if ($result) {
3265  if ($this->db->num_rows($result)) {
3266  $obj = $this->db->fetch_object($result);
3267 
3268  $this->id = $obj->rowid;
3269 
3270  $this->date_creation = $this->db->jdate($obj->datec);
3271  $this->date_validation = $this->db->jdate($obj->datev);
3272  $this->date_signature = $this->db->jdate($obj->date_signature);
3273  $this->date_cloture = $this->db->jdate($obj->date_cloture);
3274 
3275  $this->user_creation_id = $obj->fk_user_author;
3276  $this->user_validation_id = $obj->fk_user_valid;
3277 
3278  if ($obj->fk_user_signature) {
3279  $user_signature = new User($this->db);
3280  $user_signature->fetch($obj->fk_user_signature);
3281  $this->user_signature = $user_signature;
3282  }
3283 
3284  $this->user_closing_id = $obj->fk_user_cloture;
3285  }
3286  $this->db->free($result);
3287  } else {
3288  dol_print_error($this->db);
3289  }
3290  }
3291 
3292 
3299  public function getLibStatut($mode = 0)
3300  {
3301  return $this->LibStatut($this->statut, $mode);
3302  }
3303 
3304  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3312  public function LibStatut($status, $mode = 1)
3313  {
3314  // phpcs:enable
3315  global $conf, $hookmanager;
3316 
3317  // Init/load array of translation of status
3318  if (empty($this->labelStatus) || empty($this->labelStatusShort)) {
3319  global $langs;
3320  $langs->load("propal");
3321  $this->labelStatus[-1] = $langs->transnoentitiesnoconv("PropalStatusCanceled");
3322  $this->labelStatus[0] = $langs->transnoentitiesnoconv("PropalStatusDraft");
3323  $this->labelStatus[1] = $langs->transnoentitiesnoconv("PropalStatusValidated");
3324  $this->labelStatus[2] = $langs->transnoentitiesnoconv("PropalStatusSigned");
3325  $this->labelStatus[3] = $langs->transnoentitiesnoconv("PropalStatusNotSigned");
3326  $this->labelStatus[4] = $langs->transnoentitiesnoconv("PropalStatusBilled");
3327  $this->labelStatusShort[-1] = $langs->transnoentitiesnoconv("PropalStatusCanceledShort");
3328  $this->labelStatusShort[0] = $langs->transnoentitiesnoconv("PropalStatusDraftShort");
3329  $this->labelStatusShort[1] = $langs->transnoentitiesnoconv("PropalStatusValidatedShort");
3330  $this->labelStatusShort[2] = $langs->transnoentitiesnoconv("PropalStatusSignedShort");
3331  $this->labelStatusShort[3] = $langs->transnoentitiesnoconv("PropalStatusNotSignedShort");
3332  $this->labelStatusShort[4] = $langs->transnoentitiesnoconv("PropalStatusBilledShort");
3333  }
3334 
3335  $statusType = '';
3336  if ($status == self::STATUS_CANCELED) {
3337  $statusType = 'status9';
3338  } elseif ($status == self::STATUS_DRAFT) {
3339  $statusType = 'status0';
3340  } elseif ($status == self::STATUS_VALIDATED) {
3341  $statusType = 'status1';
3342  } elseif ($status == self::STATUS_SIGNED) {
3343  $statusType = 'status4';
3344  } elseif ($status == self::STATUS_NOTSIGNED) {
3345  $statusType = 'status9';
3346  } elseif ($status == self::STATUS_BILLED) {
3347  $statusType = 'status6';
3348  }
3349 
3350 
3351  $parameters = array('status' => $status, 'mode' => $mode);
3352  $reshook = $hookmanager->executeHooks('LibStatut', $parameters, $this); // Note that $action and $object may have been modified by hook
3353 
3354  if ($reshook > 0) {
3355  return $hookmanager->resPrint;
3356  }
3357 
3358  return dolGetStatus($this->labelStatus[$status], $this->labelStatusShort[$status], '', $statusType, $mode);
3359  }
3360 
3361 
3362  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3370  public function load_board($user, $mode)
3371  {
3372  // phpcs:enable
3373  global $conf, $langs;
3374 
3375  $clause = " WHERE";
3376 
3377  $sql = "SELECT p.rowid, p.ref, p.datec as datec, p.fin_validite as datefin, p.total_ht";
3378  $sql .= " FROM ".MAIN_DB_PREFIX."propal as p";
3379  $sql .= $clause." p.entity IN (".getEntity('propal').")";
3380  if ($mode == 'opened') {
3381  $sql .= " AND p.fk_statut = ".self::STATUS_VALIDATED;
3382  }
3383  if ($mode == 'signed') {
3384  $sql .= " AND p.fk_statut = ".self::STATUS_SIGNED;
3385  }
3386  // If the internal user must only see his customers, force searching by him
3387  $search_sale = 0;
3388  if (!$user->hasRight('societe', 'client', 'voir')) {
3389  $search_sale = $user->id;
3390  }
3391  // Search on sale representative
3392  if ($search_sale && $search_sale != '-1') {
3393  if ($search_sale == -2) {
3394  $sql .= " AND NOT EXISTS (SELECT sc.fk_soc FROM ".MAIN_DB_PREFIX."societe_commerciaux as sc WHERE sc.fk_soc = p.fk_soc)";
3395  } elseif ($search_sale > 0) {
3396  $sql .= " AND EXISTS (SELECT sc.fk_soc FROM ".MAIN_DB_PREFIX."societe_commerciaux as sc WHERE sc.fk_soc = p.fk_soc AND sc.fk_user = ".((int) $search_sale).")";
3397  }
3398  }
3399 
3400  $resql = $this->db->query($sql);
3401  if ($resql) {
3402  $langs->load("propal");
3403  $now = dol_now();
3404 
3405  $delay_warning = 0;
3406  $status = 0;
3407  $label = $labelShort = '';
3408  if ($mode == 'opened') {
3409  $delay_warning = $conf->propal->cloture->warning_delay;
3410  $status = self::STATUS_VALIDATED;
3411  $label = $langs->transnoentitiesnoconv("PropalsToClose");
3412  $labelShort = $langs->transnoentitiesnoconv("ToAcceptRefuse");
3413  }
3414  if ($mode == 'signed') {
3415  $delay_warning = $conf->propal->facturation->warning_delay;
3416  $status = self::STATUS_SIGNED;
3417  $label = $langs->trans("PropalsToBill"); // We set here bill but may be billed or ordered
3418  $labelShort = $langs->trans("ToBill");
3419  }
3420 
3421  $response = new WorkboardResponse();
3422  $response->warning_delay = $delay_warning / 60 / 60 / 24;
3423  $response->label = $label;
3424  $response->labelShort = $labelShort;
3425  $response->url = DOL_URL_ROOT.'/comm/propal/list.php?search_status='.$status.'&mainmenu=commercial&leftmenu=propals';
3426  $response->url_late = DOL_URL_ROOT.'/comm/propal/list.php?search_status='.$status.'&mainmenu=commercial&leftmenu=propals&sortfield=p.datep&sortorder=asc';
3427  $response->img = img_object('', "propal");
3428 
3429  // This assignment in condition is not a bug. It allows walking the results.
3430  while ($obj = $this->db->fetch_object($resql)) {
3431  $response->nbtodo++;
3432  $response->total += $obj->total_ht;
3433 
3434  if ($mode == 'opened') {
3435  $datelimit = $this->db->jdate($obj->datefin);
3436  if ($datelimit < ($now - $delay_warning)) {
3437  $response->nbtodolate++;
3438  }
3439  }
3440  // TODO Definir regle des propales a facturer en retard
3441  // if ($mode == 'signed' && ! count($this->FactureListeArray($obj->rowid))) $this->nbtodolate++;
3442  }
3443 
3444  return $response;
3445  } else {
3446  $this->error = $this->db->error();
3447  return -1;
3448  }
3449  }
3450 
3451 
3459  public function initAsSpecimen()
3460  {
3461  global $conf, $langs;
3462 
3463  // Load array of products prodids
3464  $num_prods = 0;
3465  $prodids = array();
3466  $sql = "SELECT rowid";
3467  $sql .= " FROM ".MAIN_DB_PREFIX."product";
3468  $sql .= " WHERE entity IN (".getEntity('product').")";
3469  $sql .= $this->db->plimit(100);
3470 
3471  $resql = $this->db->query($sql);
3472  if ($resql) {
3473  $num_prods = $this->db->num_rows($resql);
3474  $i = 0;
3475  while ($i < $num_prods) {
3476  $i++;
3477  $row = $this->db->fetch_row($resql);
3478  $prodids[$i] = $row[0];
3479  }
3480  }
3481 
3482  // Initialise parameters
3483  $this->id = 0;
3484  $this->ref = 'SPECIMEN';
3485  $this->ref_client = 'NEMICEPS';
3486  $this->specimen = 1;
3487  $this->socid = 1;
3488  $this->date = time();
3489  $this->fin_validite = $this->date + 3600 * 24 * 30;
3490  $this->cond_reglement_id = 1;
3491  $this->cond_reglement_code = 'RECEP';
3492  $this->mode_reglement_id = 7;
3493  $this->mode_reglement_code = 'CHQ';
3494  $this->availability_id = 1;
3495  $this->availability_code = 'AV_NOW';
3496  $this->demand_reason_id = 1;
3497  $this->demand_reason_code = 'SRC_00';
3498  $this->note_public = 'This is a comment (public)';
3499  $this->note_private = 'This is a comment (private)';
3500 
3501  $this->multicurrency_tx = 1;
3502  $this->multicurrency_code = $conf->currency;
3503 
3504  // Lines
3505  $nbp = 5;
3506  $xnbp = 0;
3507  while ($xnbp < $nbp) {
3508  $line = new PropaleLigne($this->db);
3509  $line->desc = $langs->trans("Description")." ".$xnbp;
3510  $line->qty = 1;
3511  $line->subprice = 100;
3512  $line->price = 100;
3513  $line->tva_tx = 20;
3514  $line->localtax1_tx = 0;
3515  $line->localtax2_tx = 0;
3516  if ($xnbp == 2) {
3517  $line->total_ht = 50;
3518  $line->total_ttc = 60;
3519  $line->total_tva = 10;
3520  $line->remise_percent = 50;
3521  } else {
3522  $line->total_ht = 100;
3523  $line->total_ttc = 120;
3524  $line->total_tva = 20;
3525  $line->remise_percent = 00;
3526  }
3527 
3528  if ($num_prods > 0) {
3529  $prodid = mt_rand(1, $num_prods);
3530  $line->fk_product = $prodids[$prodid];
3531  $line->product_ref = 'SPECIMEN';
3532  }
3533 
3534  $this->lines[$xnbp] = $line;
3535 
3536  $this->total_ht += $line->total_ht;
3537  $this->total_tva += $line->total_tva;
3538  $this->total_ttc += $line->total_ttc;
3539 
3540  $xnbp++;
3541  }
3542 
3543  return 1;
3544  }
3545 
3551  public function loadStateBoard()
3552  {
3553  global $user;
3554 
3555  $this->nb = array();
3556  $clause = "WHERE";
3557 
3558  $sql = "SELECT count(p.rowid) as nb";
3559  $sql .= " FROM ".MAIN_DB_PREFIX."propal as p";
3560  $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe as s ON p.fk_soc = s.rowid";
3561  $sql .= " ".$clause." p.entity IN (".getEntity('propal').")";
3562 
3563  // If the internal user must only see his customers, force searching by him
3564  $search_sale = 0;
3565  if (!$user->hasRight('societe', 'client', 'voir')) {
3566  $search_sale = $user->id;
3567  }
3568  // Search on sale representative
3569  if ($search_sale && $search_sale != '-1') {
3570  if ($search_sale == -2) {
3571  $sql .= " AND NOT EXISTS (SELECT sc.fk_soc FROM ".MAIN_DB_PREFIX."societe_commerciaux as sc WHERE sc.fk_soc = p.fk_soc)";
3572  } elseif ($search_sale > 0) {
3573  $sql .= " AND EXISTS (SELECT sc.fk_soc FROM ".MAIN_DB_PREFIX."societe_commerciaux as sc WHERE sc.fk_soc = p.fk_soc AND sc.fk_user = ".((int) $search_sale).")";
3574  }
3575  }
3576 
3577  $resql = $this->db->query($sql);
3578  if ($resql) {
3579  // This assignment in condition is not a bug. It allows walking the results.
3580  while ($obj = $this->db->fetch_object($resql)) {
3581  $this->nb["proposals"] = $obj->nb;
3582  }
3583  $this->db->free($resql);
3584  return 1;
3585  } else {
3586  dol_print_error($this->db);
3587  $this->error = $this->db->error();
3588  return -1;
3589  }
3590  }
3591 
3592 
3600  public function getNextNumRef($soc)
3601  {
3602  global $conf, $langs;
3603  $langs->load("propal");
3604 
3605  $classname = getDolGlobalString('PROPALE_ADDON');
3606 
3607  if (!empty($classname)) {
3608  $mybool = false;
3609 
3610  $file = $classname.".php";
3611 
3612  // Include file with class
3613  $dirmodels = array_merge(array('/'), (array) $conf->modules_parts['models']);
3614  foreach ($dirmodels as $reldir) {
3615  $dir = dol_buildpath($reldir."core/modules/propale/");
3616 
3617  // Load file with numbering class (if found)
3618  $mybool = ((bool) @include_once $dir.$file) || $mybool;
3619  }
3620 
3621  if (!$mybool) {
3622  dol_print_error(null, "Failed to include file ".$file);
3623  return '';
3624  }
3625 
3626  $obj = new $classname();
3627  $numref = "";
3628  $numref = $obj->getNextValue($soc, $this);
3629 
3630  if ($numref != "") {
3631  return $numref;
3632  } else {
3633  $this->error = $obj->error;
3634  //dol_print_error($db,"Propale::getNextNumRef ".$obj->error);
3635  return "";
3636  }
3637  } else {
3638  $langs->load("errors");
3639  print $langs->trans("Error")." ".$langs->trans("ErrorModuleSetupNotComplete", $langs->transnoentitiesnoconv("Proposal"));
3640  return "";
3641  }
3642  }
3643 
3650  public function getTooltipContentArray($params)
3651  {
3652  global $conf, $langs, $user;
3653 
3654  $langs->load('propal');
3655  $datas = [];
3656  $nofetch = !empty($params['nofetch']);
3657 
3658  if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
3659  return ['optimize' => $langs->trans("Proposal")];
3660  }
3661  if ($user->hasRight('propal', 'lire')) {
3662  $datas['picto'] = img_picto('', $this->picto).' <u class="paddingrightonly">'.$langs->trans("Proposal").'</u>';
3663  if (isset($this->statut)) {
3664  $datas['status'] = ' '.$this->getLibStatut(5);
3665  }
3666  if (!empty($this->ref)) {
3667  $datas['ref'] = '<br><b>'.$langs->trans('Ref').':</b> '.$this->ref;
3668  }
3669  if (!$nofetch) {
3670  $langs->load('companies');
3671  if (empty($this->thirdparty)) {
3672  $this->fetch_thirdparty();
3673  }
3674  $datas['customer'] = '<br><b>'.$langs->trans('Customer').':</b> '.$this->thirdparty->getNomUrl(1, '', 0, 1);
3675  }
3676  if (!empty($this->ref_client)) {
3677  $datas['refcustomer'] = '<br><b>'.$langs->trans('RefCustomer').':</b> '.$this->ref_client;
3678  }
3679  if (!$nofetch) {
3680  $langs->load('project');
3681  if (empty($this->project)) {
3682  $res = $this->fetch_project();
3683  if ($res > 0 && $this->project instanceof Project) {
3684  $datas['project'] = '<br><b>'.$langs->trans('Project').':</b> '.$this->project->getNomUrl(1, '', 0, 1);
3685  }
3686  }
3687  }
3688  if (!empty($this->total_ht)) {
3689  $datas['amountht'] = '<br><b>'.$langs->trans('AmountHT').':</b> '.price($this->total_ht, 0, $langs, 0, -1, -1, $conf->currency);
3690  }
3691  if (!empty($this->total_tva)) {
3692  $datas['vat'] = '<br><b>'.$langs->trans('VAT').':</b> '.price($this->total_tva, 0, $langs, 0, -1, -1, $conf->currency);
3693  }
3694  if (!empty($this->total_ttc)) {
3695  $datas['amountttc'] = '<br><b>'.$langs->trans('AmountTTC').':</b> '.price($this->total_ttc, 0, $langs, 0, -1, -1, $conf->currency);
3696  }
3697  if (!empty($this->date)) {
3698  $datas['date'] = '<br><b>'.$langs->trans('Date').':</b> '.dol_print_date($this->date, 'day');
3699  }
3700  if (!empty($this->delivery_date)) {
3701  $datas['deliverydate'] = '<br><b>'.$langs->trans('DeliveryDate').':</b> '.dol_print_date($this->delivery_date, 'dayhour');
3702  }
3703  }
3704 
3705  return $datas;
3706  }
3707 
3719  public function getNomUrl($withpicto = 0, $option = '', $get_params = '', $notooltip = 0, $save_lastsearch_value = -1, $addlinktonotes = -1)
3720  {
3721  global $langs, $conf, $user, $hookmanager;
3722 
3723  if (!empty($conf->dol_no_mouse_hover)) {
3724  $notooltip = 1; // Force disable tooltips
3725  }
3726 
3727  $result = '';
3728  $params = [
3729  'id' => $this->id,
3730  'objecttype' => $this->element,
3731  'option' => $option,
3732  'nofetch' => 1,
3733  ];
3734  $classfortooltip = 'classfortooltip';
3735  $dataparams = '';
3736  if (getDolGlobalInt('MAIN_ENABLE_AJAX_TOOLTIP')) {
3737  $classfortooltip = 'classforajaxtooltip';
3738  $dataparams = ' data-params="'.dol_escape_htmltag(json_encode($params)).'"';
3739  $label = '';
3740  } else {
3741  $label = implode($this->getTooltipContentArray($params));
3742  }
3743 
3744  $url = '';
3745  if ($user->hasRight('propal', 'lire')) {
3746  if ($option == '') {
3747  $url = DOL_URL_ROOT.'/comm/propal/card.php?id='.$this->id.$get_params;
3748  } elseif ($option == 'compta') { // deprecated
3749  $url = DOL_URL_ROOT.'/comm/propal/card.php?id='.$this->id.$get_params;
3750  } elseif ($option == 'expedition') {
3751  $url = DOL_URL_ROOT.'/expedition/propal.php?id='.$this->id.$get_params;
3752  } elseif ($option == 'document') {
3753  $url = DOL_URL_ROOT.'/comm/propal/document.php?id='.$this->id.$get_params;
3754  }
3755 
3756  if ($option != 'nolink') {
3757  // Add param to save lastsearch_values or not
3758  $add_save_lastsearch_values = ($save_lastsearch_value == 1 ? 1 : 0);
3759  if ($save_lastsearch_value == -1 && isset($_SERVER["PHP_SELF"]) && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) {
3760  $add_save_lastsearch_values = 1;
3761  }
3762  if ($add_save_lastsearch_values) {
3763  $url .= '&save_lastsearch_values=1';
3764  }
3765  }
3766  }
3767 
3768  $linkclose = '';
3769  if (empty($notooltip) && $user->hasRight('propal', 'lire')) {
3770  if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
3771  $label = $langs->trans("Proposal");
3772  $linkclose .= ' alt="'.dol_escape_htmltag($label, 1).'"';
3773  }
3774  $linkclose .= ($label ? ' title="'.dol_escape_htmltag($label, 1).'"' : ' title="tocomplete"');
3775  $linkclose .= $dataparams.' class="'.$classfortooltip.'"';
3776  }
3777 
3778  $linkstart = '<a href="'.$url.'"';
3779  $linkstart .= $linkclose.'>';
3780  $linkend = '</a>';
3781 
3782  $result .= $linkstart;
3783  if ($withpicto) {
3784  $result .= img_object(($notooltip ? '' : $label), ($this->picto ? $this->picto : 'generic'), (($withpicto != 2) ? 'class="paddingright"' : ''), 0, 0, $notooltip ? 0 : 1);
3785  }
3786  if ($withpicto != 2) {
3787  $result .= $this->ref;
3788  }
3789  $result .= $linkend;
3790 
3791  if ($addlinktonotes >= 0) {
3792  $txttoshow = '';
3793 
3794  if ($addlinktonotes == 0) {
3795  if (!empty($this->note_private) || !empty($this->note_public)) {
3796  $txttoshow = $langs->trans('ViewPrivateNote');
3797  }
3798  } elseif ($addlinktonotes == 1) {
3799  if (!empty($this->note_private)) {
3800  $txttoshow .= ($user->socid > 0 ? '' : dol_string_nohtmltag($this->note_private, 1));
3801  }
3802  } elseif ($addlinktonotes == 2) {
3803  if (!empty($this->note_public)) {
3804  $txttoshow .= dol_string_nohtmltag($this->note_public, 1);
3805  }
3806  } elseif ($addlinktonotes == 3) {
3807  if ($user->socid > 0) {
3808  if (!empty($this->note_public)) {
3809  $txttoshow .= dol_string_nohtmltag($this->note_public, 1);
3810  }
3811  } else {
3812  if (!empty($this->note_public)) {
3813  $txttoshow .= dol_string_nohtmltag($this->note_public, 1);
3814  }
3815  if (!empty($this->note_private)) {
3816  if (!empty($txttoshow)) {
3817  $txttoshow .= '<br><br>';
3818  }
3819  $txttoshow .= dol_string_nohtmltag($this->note_private, 1);
3820  }
3821  }
3822  }
3823 
3824  if ($txttoshow) {
3825  $result .= ' <span class="note inline-block">';
3826  $result .= '<a href="'.DOL_URL_ROOT.'/comm/propal/note.php?id='.$this->id.'" class="classfortooltip" title="'.dol_escape_htmltag($txttoshow).'">';
3827  $result .= img_picto('', 'note');
3828  $result .= '</a>';
3829  $result .= '</span>';
3830  }
3831  }
3832 
3833  global $action;
3834  $hookmanager->initHooks(array($this->element . 'dao'));
3835  $parameters = array('id' => $this->id, 'getnomurl' => &$result);
3836  $reshook = $hookmanager->executeHooks('getNomUrl', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
3837  if ($reshook > 0) {
3838  $result = $hookmanager->resPrint;
3839  } else {
3840  $result .= $hookmanager->resPrint;
3841  }
3842  return $result;
3843  }
3844 
3851  public function getLinesArray($sqlforgedfilters = '')
3852  {
3853  return $this->fetch_lines(0, 0, $sqlforgedfilters);
3854  }
3855 
3867  public function generateDocument($modele, $outputlangs, $hidedetails = 0, $hidedesc = 0, $hideref = 0, $moreparams = null)
3868  {
3869  global $conf, $langs;
3870 
3871  $langs->load("propale");
3872  $outputlangs->load("products");
3873 
3874  if (!dol_strlen($modele)) {
3875  $modele = 'azur';
3876 
3877  if ($this->model_pdf) {
3878  $modele = $this->model_pdf;
3879  } elseif (getDolGlobalString('PROPALE_ADDON_PDF')) {
3880  $modele = getDolGlobalString('PROPALE_ADDON_PDF');
3881  }
3882  }
3883 
3884  $modelpath = "core/modules/propale/doc/";
3885 
3886  return $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams);
3887  }
3888 
3897  public static function replaceThirdparty(DoliDB $dbs, $origin_id, $dest_id)
3898  {
3899  $tables = array(
3900  'propal'
3901  );
3902 
3903  return CommonObject::commonReplaceThirdparty($dbs, $origin_id, $dest_id, $tables);
3904  }
3905 
3914  public static function replaceProduct(DoliDB $db, $origin_id, $dest_id)
3915  {
3916  $tables = array(
3917  'propaldet'
3918  );
3919 
3920  return CommonObject::commonReplaceProduct($db, $origin_id, $dest_id, $tables);
3921  }
3922 
3930  public function getKanbanView($option = '', $arraydata = null)
3931  {
3932  global $langs;
3933 
3934  $selected = (empty($arraydata['selected']) ? 0 : $arraydata['selected']);
3935 
3936  $return = '<div class="box-flex-item box-flex-grow-zero">';
3937  $return .= '<div class="info-box info-box-sm">';
3938  $return .= '<span class="info-box-icon bg-infobox-action">';
3939  $return .= img_picto('', $this->picto);
3940  $return .= '</span>';
3941  $return .= '<div class="info-box-content">';
3942  $return .= '<span class="info-box-ref inline-block tdoverflowmax150 valignmiddle">'.(method_exists($this, 'getNomUrl') ? $this->getNomUrl() : $this->ref).'</span>';
3943  if ($selected >= 0) {
3944  $return .= '<input id="cb'.$this->id.'" class="flat checkforselect fright" type="checkbox" name="toselect[]" value="'.$this->id.'"'.($selected ? ' checked="checked"' : '').'>';
3945  }
3946  if (!empty($arraydata['projectlink'])) {
3947  $return .= '<span class="info-box-ref"> | '.$arraydata['projectlink'].'</span>';
3948  }
3949  if (!empty($arraydata['authorlink'])) {
3950  $return .= '<br><span class="info-box-label">'.$arraydata['authorlink'].'</span>';
3951  }
3952  if (property_exists($this, 'total_ht')) {
3953  $return .= '<br><span class="info-box-label amount" title="'.$langs->trans("AmountHT").'">'.price($this->total_ht).'</span>';
3954  }
3955  if (method_exists($this, 'getLibStatut')) {
3956  $return .= '<br><div class="info-box-status">'.$this->getLibStatut(3).'</div>';
3957  }
3958  $return .= '</div>';
3959  $return .= '</div>';
3960  $return .= '</div>';
3961  return $return;
3962  }
3963 }
3964 
3969 {
3973  public $element = 'propaldet';
3974 
3978  public $table_element = 'propaldet';
3979 
3980  public $oldline;
3981 
3982  // From llx_propaldet
3983  public $fk_propal;
3984  public $fk_parent_line;
3985  public $desc; // Description ligne
3986  public $fk_product; // Id produit predefini
3997  public $product_type = Product::TYPE_PRODUCT;
3998 
3999  public $qty;
4000 
4001  public $tva_tx;
4002  public $vat_src_code;
4003 
4004  public $subprice;
4005  public $remise_percent;
4006  public $fk_remise_except;
4007 
4008  public $rang = 0;
4009 
4010  public $fk_fournprice;
4011  public $pa_ht;
4012  public $marge_tx;
4013  public $marque_tx;
4014 
4021  public $special_code; // Tag for special lines (exclusive tags)
4022 
4023  public $info_bits = 0; // Some other info:
4024  // Bit 0: 0 si TVA normal - 1 if TVA NPR
4025  // Bit 1: 0 ligne normal - 1 if line with fixed discount
4026 
4027  public $total_ht; // Total HT de la ligne toute quantite et incluant la remise ligne
4028  public $total_tva; // Total TVA de la ligne toute quantite et incluant la remise ligne
4029  public $total_ttc; // Total TTC de la ligne toute quantite et incluant la remise ligne
4030 
4035  public $remise;
4040  public $price;
4041 
4042  // From llx_product
4047  public $ref;
4052  public $product_ref;
4057  public $libelle;
4062  public $label;
4067  public $product_label;
4072  public $product_desc;
4073 
4078  public $product_tobatch;
4079 
4084  public $product_barcode;
4085 
4086  public $localtax1_tx; // Local tax 1
4087  public $localtax2_tx; // Local tax 2
4088  public $localtax1_type; // Local tax 1 type
4089  public $localtax2_type; // Local tax 2 type
4090  public $total_localtax1; // Line total local tax 1
4091  public $total_localtax2; // Line total local tax 2
4092 
4093  public $date_start;
4094  public $date_end;
4095 
4096  public $skip_update_total; // Skip update price total for special lines
4097 
4098  // Multicurrency
4099  public $fk_multicurrency;
4100  public $multicurrency_code;
4101  public $multicurrency_subprice;
4102  public $multicurrency_total_ht;
4103  public $multicurrency_total_tva;
4104  public $multicurrency_total_ttc;
4105 
4106 
4112  public function __construct($db)
4113  {
4114  $this->db = $db;
4115  }
4116 
4123  public function fetch($rowid)
4124  {
4125  $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,';
4126  $sql .= ' pd.remise, pd.remise_percent, pd.fk_remise_except, pd.subprice,';
4127  $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,';
4128  $sql .= ' pd.fk_unit,';
4129  $sql .= ' pd.localtax1_tx, pd.localtax2_tx, pd.total_localtax1, pd.total_localtax2,';
4130  $sql .= ' pd.fk_multicurrency, pd.multicurrency_code, pd.multicurrency_subprice, pd.multicurrency_total_ht, pd.multicurrency_total_tva, pd.multicurrency_total_ttc,';
4131  $sql .= ' p.ref as product_ref, p.label as product_label, p.description as product_desc,';
4132  $sql .= ' pd.date_start, pd.date_end, pd.product_type';
4133  $sql .= ' FROM '.MAIN_DB_PREFIX.'propaldet as pd';
4134  $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'product as p ON pd.fk_product = p.rowid';
4135  $sql .= ' WHERE pd.rowid = '.((int) $rowid);
4136 
4137  $result = $this->db->query($sql);
4138  if ($result) {
4139  $objp = $this->db->fetch_object($result);
4140 
4141  if ($objp) {
4142  $this->id = $objp->rowid;
4143  $this->rowid = $objp->rowid; // deprecated
4144  $this->fk_propal = $objp->fk_propal;
4145  $this->fk_parent_line = $objp->fk_parent_line;
4146  $this->label = $objp->custom_label;
4147  $this->desc = $objp->description;
4148  $this->qty = $objp->qty;
4149  $this->price = $objp->price; // deprecated
4150  $this->subprice = $objp->subprice;
4151  $this->vat_src_code = $objp->vat_src_code;
4152  $this->tva_tx = $objp->tva_tx;
4153  $this->remise = $objp->remise; // deprecated
4154  $this->remise_percent = $objp->remise_percent;
4155  $this->fk_remise_except = $objp->fk_remise_except;
4156  $this->fk_product = $objp->fk_product;
4157  $this->info_bits = $objp->info_bits;
4158 
4159  $this->total_ht = $objp->total_ht;
4160  $this->total_tva = $objp->total_tva;
4161  $this->total_ttc = $objp->total_ttc;
4162 
4163  $this->fk_fournprice = $objp->fk_fournprice;
4164 
4165  $marginInfos = getMarginInfos($objp->subprice, $objp->remise_percent, $objp->tva_tx, $objp->localtax1_tx, $objp->localtax2_tx, $this->fk_fournprice, $objp->pa_ht);
4166  $this->pa_ht = $marginInfos[0];
4167  $this->marge_tx = $marginInfos[1];
4168  $this->marque_tx = $marginInfos[2];
4169 
4170  $this->special_code = $objp->special_code;
4171  $this->product_type = $objp->product_type;
4172  $this->rang = $objp->rang;
4173 
4174  $this->ref = $objp->product_ref; // deprecated
4175  $this->product_ref = $objp->product_ref;
4176  $this->libelle = $objp->product_label; // deprecated
4177  $this->product_label = $objp->product_label;
4178  $this->product_desc = $objp->product_desc;
4179  $this->fk_unit = $objp->fk_unit;
4180 
4181  $this->date_start = $this->db->jdate($objp->date_start);
4182  $this->date_end = $this->db->jdate($objp->date_end);
4183 
4184  // Multicurrency
4185  $this->fk_multicurrency = $objp->fk_multicurrency;
4186  $this->multicurrency_code = $objp->multicurrency_code;
4187  $this->multicurrency_subprice = $objp->multicurrency_subprice;
4188  $this->multicurrency_total_ht = $objp->multicurrency_total_ht;
4189  $this->multicurrency_total_tva = $objp->multicurrency_total_tva;
4190  $this->multicurrency_total_ttc = $objp->multicurrency_total_ttc;
4191 
4192  $this->fetch_optionals();
4193 
4194  $this->db->free($result);
4195 
4196  return 1;
4197  } else {
4198  return 0;
4199  }
4200  } else {
4201  return -1;
4202  }
4203  }
4204 
4211  public function insert($notrigger = 0)
4212  {
4213  global $conf, $user;
4214 
4215  $error = 0;
4216 
4217  dol_syslog(get_class($this)."::insert rang=".$this->rang);
4218 
4219  $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'.
4220 
4221  // Clean parameters
4222  if (empty($this->tva_tx)) {
4223  $this->tva_tx = 0;
4224  }
4225  if (empty($this->localtax1_tx)) {
4226  $this->localtax1_tx = 0;
4227  }
4228  if (empty($this->localtax2_tx)) {
4229  $this->localtax2_tx = 0;
4230  }
4231  if (empty($this->localtax1_type)) {
4232  $this->localtax1_type = 0;
4233  }
4234  if (empty($this->localtax2_type)) {
4235  $this->localtax2_type = 0;
4236  }
4237  if (empty($this->total_localtax1)) {
4238  $this->total_localtax1 = 0;
4239  }
4240  if (empty($this->total_localtax2)) {
4241  $this->total_localtax2 = 0;
4242  }
4243  if (empty($this->rang)) {
4244  $this->rang = 0;
4245  }
4246  if (empty($this->remise_percent) || !is_numeric($this->remise_percent)) {
4247  $this->remise_percent = 0;
4248  }
4249  if (empty($this->info_bits)) {
4250  $this->info_bits = 0;
4251  }
4252  if (empty($this->special_code)) {
4253  $this->special_code = 0;
4254  }
4255  if (empty($this->fk_parent_line)) {
4256  $this->fk_parent_line = 0;
4257  }
4258  if (empty($this->fk_fournprice)) {
4259  $this->fk_fournprice = 0;
4260  }
4261  if (!is_numeric($this->qty)) {
4262  $this->qty = 0;
4263  }
4264  if (empty($this->pa_ht)) {
4265  $this->pa_ht = 0;
4266  }
4267  if (empty($this->multicurrency_subprice)) {
4268  $this->multicurrency_subprice = 0;
4269  }
4270  if (empty($this->multicurrency_total_ht)) {
4271  $this->multicurrency_total_ht = 0;
4272  }
4273  if (empty($this->multicurrency_total_tva)) {
4274  $this->multicurrency_total_tva = 0;
4275  }
4276  if (empty($this->multicurrency_total_ttc)) {
4277  $this->multicurrency_total_ttc = 0;
4278  }
4279 
4280  // if buy price not defined, define buyprice as configured in margin admin
4281  if ($this->pa_ht == 0 && $pa_ht_isemptystring) {
4282  if (($result = $this->defineBuyPrice($this->subprice, $this->remise_percent, $this->fk_product)) < 0) {
4283  return $result;
4284  } else {
4285  $this->pa_ht = $result;
4286  }
4287  }
4288 
4289  // Check parameters
4290  if ($this->product_type < 0) {
4291  return -1;
4292  }
4293 
4294  $this->db->begin();
4295 
4296  // Insert line into database
4297  $sql = 'INSERT INTO '.MAIN_DB_PREFIX.'propaldet';
4298  $sql .= ' (fk_propal, fk_parent_line, label, description, fk_product, product_type,';
4299  $sql .= ' fk_remise_except, qty, vat_src_code, tva_tx, localtax1_tx, localtax2_tx, localtax1_type, localtax2_type,';
4300  $sql .= ' subprice, remise_percent, ';
4301  $sql .= ' info_bits, ';
4302  $sql .= ' total_ht, total_tva, total_localtax1, total_localtax2, total_ttc, fk_product_fournisseur_price, buy_price_ht, special_code, rang,';
4303  $sql .= ' fk_unit,';
4304  $sql .= ' date_start, date_end';
4305  $sql .= ', fk_multicurrency, multicurrency_code, multicurrency_subprice, multicurrency_total_ht, multicurrency_total_tva, multicurrency_total_ttc)';
4306  $sql .= " VALUES (".$this->fk_propal.",";
4307  $sql .= " ".($this->fk_parent_line > 0 ? "'".$this->db->escape($this->fk_parent_line)."'" : "null").",";
4308  $sql .= " ".(!empty($this->label) ? "'".$this->db->escape($this->label)."'" : "null").",";
4309  $sql .= " '".$this->db->escape($this->desc)."',";
4310  $sql .= " ".($this->fk_product ? "'".$this->db->escape($this->fk_product)."'" : "null").",";
4311  $sql .= " '".$this->db->escape($this->product_type)."',";
4312  $sql .= " ".($this->fk_remise_except ? "'".$this->db->escape($this->fk_remise_except)."'" : "null").",";
4313  $sql .= " ".price2num($this->qty, 'MS').",";
4314  $sql .= " ".(empty($this->vat_src_code) ? "''" : "'".$this->db->escape($this->vat_src_code)."'").",";
4315  $sql .= " ".price2num($this->tva_tx).",";
4316  $sql .= " ".price2num($this->localtax1_tx).",";
4317  $sql .= " ".price2num($this->localtax2_tx).",";
4318  $sql .= " '".$this->db->escape($this->localtax1_type)."',";
4319  $sql .= " '".$this->db->escape($this->localtax2_type)."',";
4320  $sql .= " ".(price2num($this->subprice) !== '' ? price2num($this->subprice, 'MU') : "null").",";
4321  $sql .= " ".price2num($this->remise_percent, 3).",";
4322  $sql .= " ".(isset($this->info_bits) ? ((int) $this->info_bits) : "null").",";
4323  $sql .= " ".price2num($this->total_ht, 'MT').",";
4324  $sql .= " ".price2num($this->total_tva, 'MT').",";
4325  $sql .= " ".price2num($this->total_localtax1, 'MT').",";
4326  $sql .= " ".price2num($this->total_localtax2, 'MT').",";
4327  $sql .= " ".price2num($this->total_ttc, 'MT').",";
4328  $sql .= " ".(!empty($this->fk_fournprice) ? "'".$this->db->escape($this->fk_fournprice)."'" : "null").",";
4329  $sql .= " ".(isset($this->pa_ht) ? "'".price2num($this->pa_ht)."'" : "null").",";
4330  $sql .= ' '.((int) $this->special_code).',';
4331  $sql .= ' '.((int) $this->rang).',';
4332  $sql .= ' '.(empty($this->fk_unit) ? 'NULL' : ((int) $this->fk_unit)).',';
4333  $sql .= " ".(!empty($this->date_start) ? "'".$this->db->idate($this->date_start)."'" : "null").',';
4334  $sql .= " ".(!empty($this->date_end) ? "'".$this->db->idate($this->date_end)."'" : "null");
4335  $sql .= ", ".($this->fk_multicurrency > 0 ? ((int) $this->fk_multicurrency) : 'null');
4336  $sql .= ", '".$this->db->escape($this->multicurrency_code)."'";
4337  $sql .= ", ".price2num($this->multicurrency_subprice, 'CU');
4338  $sql .= ", ".price2num($this->multicurrency_total_ht, 'CT');
4339  $sql .= ", ".price2num($this->multicurrency_total_tva, 'CT');
4340  $sql .= ", ".price2num($this->multicurrency_total_ttc, 'CT');
4341  $sql .= ')';
4342 
4343  dol_syslog(get_class($this).'::insert', LOG_DEBUG);
4344  $resql = $this->db->query($sql);
4345  if ($resql) {
4346  $this->rowid = $this->db->last_insert_id(MAIN_DB_PREFIX.'propaldet');
4347 
4348  if (!$error) {
4349  $this->id = $this->rowid;
4350  $result = $this->insertExtraFields();
4351  if ($result < 0) {
4352  $error++;
4353  }
4354  }
4355 
4356  if (!$error && !$notrigger) {
4357  // Call trigger
4358  $result = $this->call_trigger('LINEPROPAL_INSERT', $user);
4359  if ($result < 0) {
4360  $this->db->rollback();
4361  return -1;
4362  }
4363  // End call triggers
4364  }
4365 
4366  $this->db->commit();
4367  return 1;
4368  } else {
4369  $this->error = $this->db->error()." sql=".$sql;
4370  $this->db->rollback();
4371  return -1;
4372  }
4373  }
4374 
4382  public function delete(User $user, $notrigger = 0)
4383  {
4384  global $conf;
4385 
4386  $error = 0;
4387  $this->db->begin();
4388 
4389  if (!$notrigger) {
4390  // Call trigger
4391  $result = $this->call_trigger('LINEPROPAL_DELETE', $user);
4392  if ($result < 0) {
4393  $error++;
4394  }
4395  }
4396  // End call triggers
4397 
4398  if (!$error) {
4399  $sql = "DELETE FROM " . MAIN_DB_PREFIX . "propaldet WHERE rowid = " . ((int) $this->rowid);
4400  dol_syslog("PropaleLigne::delete", LOG_DEBUG);
4401  if ($this->db->query($sql)) {
4402  // Remove extrafields
4403  if (!$error) {
4404  $this->id = $this->rowid;
4405  $result = $this->deleteExtraFields();
4406  if ($result < 0) {
4407  $error++;
4408  dol_syslog(get_class($this) . "::delete error -4 " . $this->error, LOG_ERR);
4409  }
4410  }
4411  } else {
4412  $this->error = $this->db->error() . " sql=" . $sql;
4413  $error++;
4414  }
4415  }
4416 
4417  if ($error) {
4418  $this->db->rollback();
4419  return -1;
4420  } else {
4421  $this->db->commit();
4422  return 1;
4423  }
4424  }
4425 
4432  public function update($notrigger = 0)
4433  {
4434  global $conf, $user;
4435 
4436  $error = 0;
4437 
4438  $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'.
4439 
4440  if (empty($this->id) && !empty($this->rowid)) {
4441  $this->id = $this->rowid;
4442  }
4443 
4444  // Clean parameters
4445  if (empty($this->tva_tx)) {
4446  $this->tva_tx = 0;
4447  }
4448  if (empty($this->localtax1_tx)) {
4449  $this->localtax1_tx = 0;
4450  }
4451  if (empty($this->localtax2_tx)) {
4452  $this->localtax2_tx = 0;
4453  }
4454  if (empty($this->total_localtax1)) {
4455  $this->total_localtax1 = 0;
4456  }
4457  if (empty($this->total_localtax2)) {
4458  $this->total_localtax2 = 0;
4459  }
4460  if (empty($this->localtax1_type)) {
4461  $this->localtax1_type = 0;
4462  }
4463  if (empty($this->localtax2_type)) {
4464  $this->localtax2_type = 0;
4465  }
4466  if (empty($this->marque_tx)) {
4467  $this->marque_tx = 0;
4468  }
4469  if (empty($this->marge_tx)) {
4470  $this->marge_tx = 0;
4471  }
4472  if (empty($this->price)) {
4473  $this->price = 0; // TODO A virer
4474  }
4475  if (empty($this->remise_percent)) {
4476  $this->remise_percent = 0;
4477  }
4478  if (empty($this->info_bits)) {
4479  $this->info_bits = 0;
4480  }
4481  if (empty($this->special_code)) {
4482  $this->special_code = 0;
4483  }
4484  if (empty($this->fk_parent_line)) {
4485  $this->fk_parent_line = 0;
4486  }
4487  if (empty($this->fk_fournprice)) {
4488  $this->fk_fournprice = 0;
4489  }
4490  if (empty($this->subprice)) {
4491  $this->subprice = 0;
4492  }
4493  if (empty($this->pa_ht)) {
4494  $this->pa_ht = 0;
4495  }
4496 
4497  // if buy price not defined, define buyprice as configured in margin admin
4498  if ($this->pa_ht == 0 && $pa_ht_isemptystring) {
4499  if (($result = $this->defineBuyPrice($this->subprice, $this->remise_percent, $this->fk_product)) < 0) {
4500  return $result;
4501  } else {
4502  $this->pa_ht = $result;
4503  }
4504  }
4505 
4506  $this->db->begin();
4507 
4508  // Mise a jour ligne en base
4509  $sql = "UPDATE ".MAIN_DB_PREFIX."propaldet SET";
4510  $sql .= " description='".$this->db->escape($this->desc)."'";
4511  $sql .= ", label=".(!empty($this->label) ? "'".$this->db->escape($this->label)."'" : "null");
4512  $sql .= ", product_type=".$this->product_type;
4513  $sql .= ", vat_src_code = '".(empty($this->vat_src_code) ? '' : $this->vat_src_code)."'";
4514  $sql .= ", tva_tx='".price2num($this->tva_tx)."'";
4515  $sql .= ", localtax1_tx=".price2num($this->localtax1_tx);
4516  $sql .= ", localtax2_tx=".price2num($this->localtax2_tx);
4517  $sql .= ", localtax1_type='".$this->db->escape($this->localtax1_type)."'";
4518  $sql .= ", localtax2_type='".$this->db->escape($this->localtax2_type)."'";
4519  $sql .= ", qty='".price2num($this->qty)."'";
4520  $sql .= ", subprice=".price2num($this->subprice);
4521  $sql .= ", remise_percent=".price2num($this->remise_percent);
4522  $sql .= ", price=".(float) price2num($this->price); // TODO A virer
4523  $sql .= ", remise=".(float) price2num($this->remise); // TODO A virer
4524  $sql .= ", info_bits='".$this->db->escape($this->info_bits)."'";
4525  if (empty($this->skip_update_total)) {
4526  $sql .= ", total_ht=".price2num($this->total_ht);
4527  $sql .= ", total_tva=".price2num($this->total_tva);
4528  $sql .= ", total_ttc=".price2num($this->total_ttc);
4529  $sql .= ", total_localtax1=".price2num($this->total_localtax1);
4530  $sql .= ", total_localtax2=".price2num($this->total_localtax2);
4531  }
4532  $sql .= ", fk_product_fournisseur_price=".(!empty($this->fk_fournprice) ? "'".$this->db->escape($this->fk_fournprice)."'" : "null");
4533  $sql .= ", buy_price_ht=".price2num($this->pa_ht);
4534  $sql .= ", special_code=".((int) $this->special_code);
4535  $sql .= ", fk_parent_line=".($this->fk_parent_line > 0 ? (int) $this->fk_parent_line : "null");
4536  if (!empty($this->rang)) {
4537  $sql .= ", rang=".((int) $this->rang);
4538  }
4539  $sql .= ", date_start=".(!empty($this->date_start) ? "'".$this->db->idate($this->date_start)."'" : "null");
4540  $sql .= ", date_end=".(!empty($this->date_end) ? "'".$this->db->idate($this->date_end)."'" : "null");
4541  $sql .= ", fk_unit=".(!$this->fk_unit ? 'NULL' : $this->fk_unit);
4542 
4543  // Multicurrency
4544  $sql .= ", multicurrency_subprice=".price2num($this->multicurrency_subprice);
4545  $sql .= ", multicurrency_total_ht=".price2num($this->multicurrency_total_ht);
4546  $sql .= ", multicurrency_total_tva=".price2num($this->multicurrency_total_tva);
4547  $sql .= ", multicurrency_total_ttc=".price2num($this->multicurrency_total_ttc);
4548 
4549  $sql .= " WHERE rowid = ".((int) $this->id);
4550 
4551  dol_syslog(get_class($this)."::update", LOG_DEBUG);
4552  $resql = $this->db->query($sql);
4553  if ($resql) {
4554  if (!$error) {
4555  $result = $this->insertExtraFields();
4556  if ($result < 0) {
4557  $error++;
4558  }
4559  }
4560 
4561  if (!$error && !$notrigger) {
4562  // Call trigger
4563  $result = $this->call_trigger('LINEPROPAL_MODIFY', $user);
4564  if ($result < 0) {
4565  $this->db->rollback();
4566  return -1;
4567  }
4568  // End call triggers
4569  }
4570 
4571  $this->db->commit();
4572  return 1;
4573  } else {
4574  $this->error = $this->db->error();
4575  $this->db->rollback();
4576  return -2;
4577  }
4578  }
4579 
4580  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4587  public function update_total()
4588  {
4589  // phpcs:enable
4590  $this->db->begin();
4591 
4592  // Mise a jour ligne en base
4593  $sql = "UPDATE ".MAIN_DB_PREFIX."propaldet SET";
4594  $sql .= " total_ht=".price2num($this->total_ht, 'MT');
4595  $sql .= ",total_tva=".price2num($this->total_tva, 'MT');
4596  $sql .= ",total_ttc=".price2num($this->total_ttc, 'MT');
4597  $sql .= " WHERE rowid = ".((int) $this->rowid);
4598 
4599  dol_syslog("PropaleLigne::update_total", LOG_DEBUG);
4600 
4601  $resql = $this->db->query($sql);
4602  if ($resql) {
4603  $this->db->commit();
4604  return 1;
4605  } else {
4606  $this->error = $this->db->error();
4607  $this->db->rollback();
4608  return -2;
4609  }
4610  }
4611 }
if($user->socid > 0) if(! $user->hasRight('accounting', 'chartofaccount')) $object
Definition: card.php:58
print $langs trans("AuditedSecurityEvents").'</strong >< span class="opacitymedium"></span >< br > status
Or an array listing all the potential status of the object: array: int of the status => translated la...
Definition: security.php:604
$object ref
Definition: info.php:79
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.
update_price($exclspec=0, $roundingadjust='auto', $nodatabaseupdate=0, $seller=null)
Update total_ht, total_ttc, total_vat, total_localtax1, total_localtax2 for an object (sum of lines).
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.
deleteObjectLinked($sourceid=null, $sourcetype='', $targetid=null, $targettype='', $rowid=0, $f_user=null, $notrigger=0)
Delete all links between an object $this.
setErrorsFromObject($object)
setErrorsFromObject
static isExistingObject($element, $id, $ref='', $ref_ext='')
Check if an object id or ref exists If you don't need or want to instantiate the object and just need...
updateRangOfLine($rowid, $rang)
Update position of line (rang)
fetch_project()
Load the project with id $this->fk_project into this->project.
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.
File of class to manage predefined price products or services by customer.
Class to manage products or services.
const TYPE_PRODUCT
Regular product.
Class to manage projects.
Class to manage proposals.
getTooltipContentArray($params)
getTooltipContentArray
const STATUS_DRAFT
Draft status.
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.
classifyBilled(User $user, $notrigger=0, $note='')
Classify the proposal to status Billed.
getLinesArray($sqlforgedfilters='')
Retrieve an array of proposal lines.
fetch($rowid, $ref='', $ref_ext='', $forceentity=0)
Load a proposal from database.
set_availability($user, $id, $notrigger=0)
Set delivery.
fetch_lines($only_product=0, $loadalsotranslation=0, $sqlforgedfilters='')
Load array lines.
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=array(), $fk_unit=null, $pu_ht_devise=0, $notrigger=0, $rang=0)
Update a proposal line.
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, ...)
const STATUS_CANCELED
Canceled status.
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.
generateDocument($modele, $outputlangs, $hidedetails=0, $hidedesc=0, $hideref=0, $moreparams=null)
Create a document onto disk according to template module.
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.
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.
deleteLine($lineid, $id=0)
Delete detail line.
__construct($db, $socid=0, $propalid=0)
Constructor.
load_board($user, $mode)
Load indicators for dashboard (this->nbtodo and this->nbtodolate)
setCancel(User $user)
Cancel the proposal.
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=array(), $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...
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.
loadStateBoard()
Load the indicators this->nb for the state board.
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 Constructor.
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:50
trait CommonIncoterm
Superclass for incoterm classes.
if(isModEnabled('invoice') && $user->hasRight('facture', 'lire')) if((isModEnabled('fournisseur') &&!getDolGlobalString('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') && $user->hasRight('tax', 'charges', 'lire')) if(isModEnabled('invoice') &&isModEnabled('order') && $user->hasRight("commande", "lire") &&!getDolGlobalString('WORKFLOW_DISABLE_CREATE_INVOICE_FROM_ORDER')) $sql
Social contributions to pay.
Definition: index.php:744
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:1589
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:1438
dol_dir_list($utf8_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:63
dol_delete_preview($object)
Delete all preview files linked to object instance.
Definition: files.lib.php:1641
img_object($titlealt, $picto, $moreatt='', $pictoisfullpath=0, $srconly=0, $notitle=0)
Show a picto called object_picto (generic function)
img_picto($titlealt, $picto, $moreatt='', $pictoisfullpath=0, $srconly=0, $notitle=0, $alt='', $morecss='', $marginleftonlyshort=2)
Show picto whatever it's its name (generic function)
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_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.
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 a Dolibarr global constant int value.
getLocalTaxesFromRate($vatrate, $local, $buyer, $seller, $firstparamisid=0)
Get type and rate of localtaxes for a particular vat rate/country of a thirdparty.
dol_print_date($time, $format='', $tzoutput='auto', $outputlangs=null, $encodetooutput=false)
Output date in a string format according to outputlangs (or langs if not defined).
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.
dol_sanitizeFileName($str, $newstr='_', $unaccent=1)
Clean a string to use it as a file name.
dol_print_error($db=null, $error='', $errors=null)
Displays error message system with all the information to facilitate the diagnosis and the escalation...
getDolGlobalString($key, $default='')
Return dolibarr global constant string value.
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...
get_localtax($vatrate, $local, $thirdparty_buyer=null, $thirdparty_seller=null, $vatnpr=0)
Return localtax rate for a particular vat, when selling a product with vat $vatrate,...
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($pv_ht, $remise_percent, $tva_tx, $localtax1_tx, $localtax2_tx, $fk_pa, $pa_ht)
Return an array with margins information of a line.
div float
Buy price without taxes.
Definition: style.css.php:959
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:88