dolibarr 20.0.5
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) 2024 MDW <mdeweerd@users.noreply.github.com>
22 * Copyright (C) 2025 William Mead <william@m34d.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
43require_once DOL_DOCUMENT_ROOT.'/core/class/commonobject.class.php';
44require_once DOL_DOCUMENT_ROOT."/core/class/commonobjectline.class.php";
45require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
46require_once DOL_DOCUMENT_ROOT.'/contact/class/contact.class.php';
47require_once DOL_DOCUMENT_ROOT.'/margin/lib/margins.lib.php';
48require_once DOL_DOCUMENT_ROOT.'/multicurrency/class/multicurrency.class.php';
49require_once DOL_DOCUMENT_ROOT.'/core/class/commonincoterm.class.php';
50
54class Propal extends CommonObject
55{
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 $restrictiononfksoc = 1;
93
97 protected $table_ref_field = 'ref';
98
103 public $socid;
104
109 public $contactid;
110 public $author;
111
118 public $ref_client;
119
124 public $ref_customer;
125
129 public $oldcopy;
130
137 public $statut;
138
144 public $status;
145
150 public $datec;
151
155 public $date_creation;
156
161 public $datev;
162
166 public $date_validation;
167
171 public $date_signature;
172
176 public $user_signature;
177
181 public $date;
182
187 public $datep;
188
192 public $delivery_date; // Date expected of shipment (date starting shipment, not the reception that occurs some days after)
193
194
195 public $fin_validite;
196
197 public $user_author_id;
198
203 public $price;
208 public $tva;
213 public $total;
214
215 public $cond_reglement_code; // code
216 public $cond_reglement; // label
217 public $cond_reglement_doc; // label doc
218
219 public $mode_reglement_code; // code
220 public $mode_reglement; // label
221
222 public $deposit_percent;
223
228 public $fk_address;
229
230 public $address_type;
231 public $address;
232
236 public $availability_id;
237
243 public $fk_availability;
244
248 public $availability_code;
249
253 public $availability;
254
255 public $duree_validite;
256
257 public $demand_reason_id; // id
258 public $demand_reason_code; // code
259 public $demand_reason; // label
260
261 public $warehouse_id;
262
263 public $extraparams = array();
264
268 public $lines = array();
269
273 public $line;
274
275 public $labelStatus = array();
276 public $labelStatusShort = array();
277
278
303 // BEGIN MODULEBUILDER PROPERTIES
307 public $fields = array(
308 'rowid' => array('type' => 'integer', 'label' => 'TechnicalID', 'enabled' => 1, 'visible' => -1, 'notnull' => 1, 'position' => 10),
309 'entity' => array('type' => 'integer', 'label' => 'Entity', 'default' => '1', 'enabled' => 1, 'visible' => -2, 'notnull' => 1, 'position' => 15, 'index' => 1),
310 'ref' => array('type' => 'varchar(30)', 'label' => 'Ref', 'enabled' => 1, 'visible' => -1, 'notnull' => 1, 'showoncombobox' => 1, 'position' => 20),
311 'ref_client' => array('type' => 'varchar(255)', 'label' => 'RefCustomer', 'enabled' => 1, 'visible' => -1, 'position' => 22),
312 'ref_ext' => array('type' => 'varchar(255)', 'label' => 'RefExt', 'enabled' => 1, 'visible' => 0, 'position' => 40),
313 'fk_soc' => array('type' => 'integer:Societe:societe/class/societe.class.php', 'label' => 'ThirdParty', 'enabled' => 'isModEnabled("societe")', 'visible' => -1, 'position' => 23),
314 'fk_projet' => array('type' => 'integer:Project:projet/class/project.class.php:1:(fk_statut:=:1)', 'label' => 'Fk projet', 'enabled' => "isModEnabled('project')", 'visible' => -1, 'position' => 24),
315 'tms' => array('type' => 'timestamp', 'label' => 'DateModification', 'enabled' => 1, 'visible' => -1, 'notnull' => 1, 'position' => 25),
316 'datec' => array('type' => 'datetime', 'label' => 'DateCreation', 'enabled' => 1, 'visible' => -1, 'position' => 55),
317 'datep' => array('type' => 'date', 'label' => 'Date', 'enabled' => 1, 'visible' => -1, 'position' => 60),
318 'fin_validite' => array('type' => 'datetime', 'label' => 'DateEnd', 'enabled' => 1, 'visible' => -1, 'position' => 65),
319 'date_valid' => array('type' => 'datetime', 'label' => 'DateValidation', 'enabled' => 1, 'visible' => -1, 'position' => 70),
320 'date_cloture' => array('type' => 'datetime', 'label' => 'DateClosing', 'enabled' => 1, 'visible' => -1, 'position' => 75),
321 'fk_user_author' => array('type' => 'integer:User:user/class/user.class.php', 'label' => 'Fk user author', 'enabled' => 1, 'visible' => -1, 'position' => 80),
322 'fk_user_modif' => array('type' => 'integer:User:user/class/user.class.php', 'label' => 'UserModif', 'enabled' => 1, 'visible' => -2, 'notnull' => -1, 'position' => 85),
323 'fk_user_valid' => array('type' => 'integer:User:user/class/user.class.php', 'label' => 'UserValidation', 'enabled' => 1, 'visible' => -1, 'position' => 90),
324 'fk_user_cloture' => array('type' => 'integer:User:user/class/user.class.php', 'label' => 'Fk user cloture', 'enabled' => 1, 'visible' => -1, 'position' => 95),
325 'price' => array('type' => 'double', 'label' => 'Price', 'enabled' => 1, 'visible' => -1, 'position' => 105),
326 'total_ht' => array('type' => 'double(24,8)', 'label' => 'TotalHT', 'enabled' => 1, 'visible' => -1, 'position' => 125, 'isameasure' => 1),
327 'total_tva' => array('type' => 'double(24,8)', 'label' => 'VAT', 'enabled' => 1, 'visible' => -1, 'position' => 130, 'isameasure' => 1),
328 'localtax1' => array('type' => 'double(24,8)', 'label' => 'LocalTax1', 'enabled' => 1, 'visible' => -1, 'position' => 135, 'isameasure' => 1),
329 'localtax2' => array('type' => 'double(24,8)', 'label' => 'LocalTax2', 'enabled' => 1, 'visible' => -1, 'position' => 140, 'isameasure' => 1),
330 'total_ttc' => array('type' => 'double(24,8)', 'label' => 'TotalTTC', 'enabled' => 1, 'visible' => -1, 'position' => 145, 'isameasure' => 1),
331 'fk_account' => array('type' => 'integer', 'label' => 'BankAccount', 'enabled' => 'isModEnabled("bank")', 'visible' => -1, 'position' => 150),
332 'fk_currency' => array('type' => 'varchar(3)', 'label' => 'Currency', 'enabled' => 1, 'visible' => -1, 'position' => 155),
333 'fk_cond_reglement' => array('type' => 'integer', 'label' => 'PaymentTerm', 'enabled' => 1, 'visible' => -1, 'position' => 160),
334 'deposit_percent' => array('type' => 'varchar(63)', 'label' => 'DepositPercent', 'enabled' => 1, 'visible' => -1, 'position' => 161),
335 'fk_mode_reglement' => array('type' => 'integer', 'label' => 'PaymentMode', 'enabled' => 1, 'visible' => -1, 'position' => 165),
336 'note_private' => array('type' => 'html', 'label' => 'NotePrivate', 'enabled' => 1, 'visible' => 0, 'position' => 170),
337 'note_public' => array('type' => 'html', 'label' => 'NotePublic', 'enabled' => 1, 'visible' => 0, 'position' => 175),
338 'model_pdf' => array('type' => 'varchar(255)', 'label' => 'PDFTemplate', 'enabled' => 1, 'visible' => 0, 'position' => 180),
339 'date_livraison' => array('type' => 'date', 'label' => 'DateDeliveryPlanned', 'enabled' => 1, 'visible' => -1, 'position' => 185),
340 'fk_shipping_method' => array('type' => 'integer', 'label' => 'ShippingMethod', 'enabled' => 1, 'visible' => -1, 'position' => 190),
341 'fk_warehouse' => array('type' => 'integer:Entrepot:product/stock/class/entrepot.class.php', 'label' => 'Fk warehouse', 'enabled' => 'isModEnabled("stock")', 'visible' => -1, 'position' => 191),
342 'fk_availability' => array('type' => 'integer', 'label' => 'Availability', 'enabled' => 1, 'visible' => -1, 'position' => 195),
343 'fk_delivery_address' => array('type' => 'integer', 'label' => 'DeliveryAddress', 'enabled' => 1, 'visible' => 0, 'position' => 200), // deprecated
344 'fk_input_reason' => array('type' => 'integer', 'label' => 'InputReason', 'enabled' => 1, 'visible' => -1, 'position' => 205),
345 'extraparams' => array('type' => 'varchar(255)', 'label' => 'Extraparams', 'enabled' => 1, 'visible' => -1, 'position' => 215),
346 'fk_incoterms' => array('type' => 'integer', 'label' => 'IncotermCode', 'enabled' => 'isModEnabled("incoterm")', 'visible' => -1, 'position' => 220),
347 'location_incoterms' => array('type' => 'varchar(255)', 'label' => 'IncotermLabel', 'enabled' => 'isModEnabled("incoterm")', 'visible' => -1, 'position' => 225),
348 'fk_multicurrency' => array('type' => 'integer', 'label' => 'MulticurrencyID', 'enabled' => 1, 'visible' => -1, 'position' => 230),
349 'multicurrency_code' => array('type' => 'varchar(255)', 'label' => 'MulticurrencyCurrency', 'enabled' => 'isModEnabled("multicurrency")', 'visible' => -1, 'position' => 235),
350 'multicurrency_tx' => array('type' => 'double(24,8)', 'label' => 'MulticurrencyRate', 'enabled' => 'isModEnabled("multicurrency")', 'visible' => -1, 'position' => 240, 'isameasure' => 1),
351 'multicurrency_total_ht' => array('type' => 'double(24,8)', 'label' => 'MulticurrencyAmountHT', 'enabled' => 'isModEnabled("multicurrency")', 'visible' => -1, 'position' => 245, 'isameasure' => 1),
352 'multicurrency_total_tva' => array('type' => 'double(24,8)', 'label' => 'MulticurrencyAmountVAT', 'enabled' => 'isModEnabled("multicurrency")', 'visible' => -1, 'position' => 250, 'isameasure' => 1),
353 'multicurrency_total_ttc' => array('type' => 'double(24,8)', 'label' => 'MulticurrencyAmountTTC', 'enabled' => 'isModEnabled("multicurrency")', 'visible' => -1, 'position' => 255, 'isameasure' => 1),
354 'last_main_doc' => array('type' => 'varchar(255)', 'label' => 'LastMainDoc', 'enabled' => 1, 'visible' => -1, 'position' => 260),
355 'fk_statut' => array('type' => 'smallint(6)', 'label' => 'Status', 'enabled' => 1, 'visible' => -1, 'notnull' => 1, 'position' => 500),
356 'import_key' => array('type' => 'varchar(14)', 'label' => 'ImportId', 'enabled' => 1, 'visible' => -2, 'position' => 900),
357 );
358 // END MODULEBUILDER PROPERTIES
359
363 const STATUS_CANCELED = -1;
367 const STATUS_DRAFT = 0;
375 const STATUS_SIGNED = 2;
383 const STATUS_BILLED = 4; // Todo rename into STATUS_CLOSE ?
384
385
393 public function __construct($db, $socid = 0, $propalid = 0)
394 {
395 $this->db = $db;
396
397 $this->ismultientitymanaged = 1;
398 $this->socid = $socid;
399 $this->id = $propalid;
400
401 $this->duree_validite = getDolGlobalInt('PROPALE_VALIDITY_DURATION', 0);
402 }
403
404
405 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
417 public function add_product($idproduct, $qty, $remise_percent = 0)
418 {
419 // phpcs:enable
420 global $conf, $mysoc;
421
422 if (!$qty) {
423 $qty = 1;
424 }
425
426 dol_syslog(get_class($this)."::add_product $idproduct, $qty, $remise_percent");
427 if ($idproduct > 0) {
428 $prod = new Product($this->db);
429 $prod->fetch($idproduct);
430
431 $productdesc = $prod->description;
432
433 $tva_tx = get_default_tva($mysoc, $this->thirdparty, $prod->id);
434 $tva_npr = get_default_npr($mysoc, $this->thirdparty, $prod->id);
435 if (empty($tva_tx)) {
436 $tva_npr = 0;
437 }
438 $vat_src_code = ''; // May be defined into tva_tx
439
440 $localtax1_tx = get_localtax($tva_tx, 1, $mysoc, $this->thirdparty, $tva_npr);
441 $localtax2_tx = get_localtax($tva_tx, 2, $mysoc, $this->thirdparty, $tva_npr);
442
443 // multiprices
444 if ($conf->global->PRODUIT_MULTIPRICES && $this->thirdparty->price_level) {
445 $price = $prod->multiprices[$this->thirdparty->price_level];
446 } else {
447 $price = $prod->price;
448 }
449
450 $line = new PropaleLigne($this->db);
451
452 $line->fk_product = $idproduct;
453 $line->desc = $productdesc;
454 $line->qty = $qty;
455 $line->subprice = $price;
456 $line->remise_percent = $remise_percent;
457 $line->vat_src_code = $vat_src_code;
458 $line->tva_tx = $tva_tx;
459 $line->fk_unit = $prod->fk_unit;
460 if ($tva_npr) {
461 $line->info_bits = 1;
462 }
463
464 $this->lines[] = $line;
465 }
466
467 return 1;
468 }
469
470 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
477 public function insert_discount($idremise)
478 {
479 // phpcs:enable
480 global $langs;
481
482 include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
483 include_once DOL_DOCUMENT_ROOT.'/core/class/discount.class.php';
484
485 $this->db->begin();
486
487 $remise = new DiscountAbsolute($this->db);
488 $result = $remise->fetch($idremise);
489
490 if ($result > 0) {
491 if ($remise->fk_facture) { // Protection against multiple submission
492 $this->error = $langs->trans("ErrorDiscountAlreadyUsed");
493 $this->db->rollback();
494 return -5;
495 }
496
497 $line = new PropaleLigne($this->db);
498
499 $line->context = $this->context;
500
501 $line->fk_propal = $this->id;
502 $line->fk_remise_except = $remise->id;
503 $line->desc = $remise->description; // Description ligne
504 $line->vat_src_code = $remise->vat_src_code;
505 $line->tva_tx = $remise->tva_tx;
506 $line->subprice = -$remise->amount_ht;
507 $line->fk_product = 0; // Id produit predefined
508 $line->qty = 1;
509 $line->remise_percent = 0;
510 $line->rang = -1;
511 $line->info_bits = 2;
512
513 // TODO deprecated
514 $line->price = -$remise->amount_ht;
515
516 $line->total_ht = -$remise->amount_ht;
517 $line->total_tva = -$remise->amount_tva;
518 $line->total_ttc = -$remise->amount_ttc;
519
520 $result = $line->insert();
521 if ($result > 0) {
522 $result = $this->update_price(1);
523 if ($result > 0) {
524 $this->db->commit();
525 return 1;
526 } else {
527 $this->db->rollback();
528 return -1;
529 }
530 } else {
531 $this->error = $line->error;
532 $this->errors = $line->errors;
533 $this->db->rollback();
534 return -2;
535 }
536 } else {
537 $this->db->rollback();
538 return -2;
539 }
540 }
541
579 public function addline($desc, $pu_ht, $qty, $txtva, $txlocaltax1 = 0.0, $txlocaltax2 = 0.0, $fk_product = 0, $remise_percent = 0.0, $price_base_type = 'HT', $pu_ttc = 0.0, $info_bits = 0, $type = 0, $rang = -1, $special_code = 0, $fk_parent_line = 0, $fk_fournprice = 0, $pa_ht = 0, $label = '', $date_start = '', $date_end = '', $array_options = array(), $fk_unit = null, $origin = '', $origin_id = 0, $pu_ht_devise = 0, $fk_remise_except = 0, $noupdateafterinsertline = 0)
580 {
581 global $mysoc, $conf, $langs;
582
583 dol_syslog(get_class($this)."::addline propalid=$this->id, desc=$desc, pu_ht=$pu_ht, qty=$qty, txtva=$txtva, fk_product=$fk_product, remise_except=$remise_percent, price_base_type=$price_base_type, pu_ttc=$pu_ttc, info_bits=$info_bits, type=$type, fk_remise_except=".$fk_remise_except);
584
585 if ($this->statut == self::STATUS_DRAFT) {
586 include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
587
588 // Clean parameters
589 if (empty($remise_percent)) {
590 $remise_percent = 0;
591 }
592 if (empty($qty)) {
593 $qty = 0;
594 }
595 if (empty($info_bits)) {
596 $info_bits = 0;
597 }
598 if (empty($rang)) {
599 $rang = 0;
600 }
601 if (empty($fk_parent_line) || $fk_parent_line < 0) {
602 $fk_parent_line = 0;
603 }
604
605 $remise_percent = price2num($remise_percent);
606 $qty = (float) price2num($qty);
607 $pu_ht = price2num($pu_ht);
608 $pu_ht_devise = price2num($pu_ht_devise);
609 $pu_ttc = price2num($pu_ttc);
610 if (!preg_match('/\‍((.*)\‍)/', (string) $txtva)) {
611 $txtva = price2num($txtva); // $txtva can have format '5,1' or '5.1' or '5.1(XXX)', we must clean only if '5,1'
612 }
613 $txlocaltax1 = price2num($txlocaltax1);
614 $txlocaltax2 = price2num($txlocaltax2);
615 $pa_ht = price2num($pa_ht); // do not convert to float here, it breaks the functioning of $pa_ht_isemptystring
616 if ($price_base_type == 'HT') {
617 $pu = $pu_ht;
618 } else {
619 $pu = $pu_ttc;
620 }
621
622 // Check parameters
623 if ($type < 0) {
624 return -1;
625 }
626
627 if ($date_start && $date_end && $date_start > $date_end) {
628 $langs->load("errors");
629 $this->error = $langs->trans('ErrorStartDateGreaterEnd');
630 return -1;
631 }
632
633 $this->db->begin();
634
635 $product_type = $type;
636 if (!empty($fk_product) && $fk_product > 0) {
637 $product = new Product($this->db);
638 $result = $product->fetch($fk_product);
639 $product_type = $product->type;
640
641 if (getDolGlobalString('STOCK_MUST_BE_ENOUGH_FOR_PROPOSAL') && $product_type == 0 && $product->stock_reel < $qty) {
642 $langs->load("errors");
643 $this->error = $langs->trans('ErrorStockIsNotEnoughToAddProductOnProposal', $product->ref);
644 $this->db->rollback();
645 return -3;
646 }
647 }
648
649 // Calcul du total TTC et de la TVA pour la ligne a partir de
650 // qty, pu, remise_percent et txtva
651 // TRES IMPORTANT: C'est au moment de l'insertion ligne qu'on doit stocker
652 // la part ht, tva et ttc, et ce au niveau de la ligne qui a son propre taux tva.
653
654 $localtaxes_type = getLocalTaxesFromRate($txtva, 0, $this->thirdparty, $mysoc);
655
656 // Clean vat code
657 $reg = array();
658 $vat_src_code = '';
659 if (preg_match('/\‍((.*)\‍)/', $txtva, $reg)) {
660 $vat_src_code = $reg[1];
661 $txtva = preg_replace('/\s*\‍(.*\‍)/', '', $txtva); // Remove code into vatrate.
662 }
663
664 $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);
665
666 $total_ht = $tabprice[0];
667 $total_tva = $tabprice[1];
668 $total_ttc = $tabprice[2];
669 $total_localtax1 = $tabprice[9];
670 $total_localtax2 = $tabprice[10];
671 $pu_ht = $tabprice[3];
672 $pu_tva = $tabprice[4];
673 $pu_ttc = $tabprice[5];
674
675 // MultiCurrency
676 $multicurrency_total_ht = $tabprice[16];
677 $multicurrency_total_tva = $tabprice[17];
678 $multicurrency_total_ttc = $tabprice[18];
679 $pu_ht_devise = $tabprice[19];
680
681 // Rang to use
682 $ranktouse = $rang;
683 if ($ranktouse == -1) {
684 $rangmax = $this->line_max($fk_parent_line);
685 $ranktouse = $rangmax + 1;
686 }
687
688 // TODO A virer
689 // Anciens indicateurs: $price, $remise (a ne plus utiliser)
690 $price = $pu;
691 $remise = 0;
692 if ((float) $remise_percent > 0) {
693 $remise = round(((float) $pu * (float) $remise_percent / 100), 2);
694 $price = (float) $pu - $remise;
695 }
696
697 // Insert line
698 $this->line = new PropaleLigne($this->db);
699
700 $this->line->context = $this->context;
701
702 $this->line->fk_propal = $this->id;
703 $this->line->label = $label;
704 $this->line->desc = $desc;
705 $this->line->qty = $qty;
706
707 $this->line->vat_src_code = $vat_src_code;
708 $this->line->tva_tx = $txtva;
709 $this->line->localtax1_tx = ($total_localtax1 ? $localtaxes_type[1] : 0);
710 $this->line->localtax2_tx = ($total_localtax2 ? $localtaxes_type[3] : 0);
711 $this->line->localtax1_type = empty($localtaxes_type[0]) ? '' : $localtaxes_type[0];
712 $this->line->localtax2_type = empty($localtaxes_type[2]) ? '' : $localtaxes_type[2];
713 $this->line->fk_product = $fk_product;
714 $this->line->product_type = $type;
715 $this->line->fk_remise_except = $fk_remise_except;
716 $this->line->remise_percent = $remise_percent;
717 $this->line->subprice = $pu_ht;
718 $this->line->rang = $ranktouse;
719 $this->line->info_bits = $info_bits;
720 $this->line->total_ht = $total_ht;
721 $this->line->total_tva = $total_tva;
722 $this->line->total_localtax1 = $total_localtax1;
723 $this->line->total_localtax2 = $total_localtax2;
724 $this->line->total_ttc = $total_ttc;
725 $this->line->special_code = $special_code;
726 $this->line->fk_parent_line = $fk_parent_line;
727 $this->line->fk_unit = $fk_unit;
728
729 $this->line->date_start = $date_start;
730 $this->line->date_end = $date_end;
731
732 $this->line->fk_fournprice = $fk_fournprice;
733 $this->line->pa_ht = $pa_ht;
734
735 $this->line->origin_id = $origin_id;
736 $this->line->origin = $origin;
737
738 // Multicurrency
739 $this->line->fk_multicurrency = $this->fk_multicurrency;
740 $this->line->multicurrency_code = $this->multicurrency_code;
741 $this->line->multicurrency_subprice = $pu_ht_devise;
742 $this->line->multicurrency_total_ht = $multicurrency_total_ht;
743 $this->line->multicurrency_total_tva = $multicurrency_total_tva;
744 $this->line->multicurrency_total_ttc = $multicurrency_total_ttc;
745
746 // Mise en option de la ligne
747 if (empty($qty) && empty($special_code)) {
748 $this->line->special_code = 3;
749 }
750
751 // TODO deprecated
752 $this->line->price = $price;
753
754 if (is_array($array_options) && count($array_options) > 0) {
755 $this->line->array_options = $array_options;
756 }
757
758 $result = $this->line->insert();
759 if ($result > 0) {
760 // Reorder if child line
761 if (!empty($fk_parent_line)) {
762 $this->line_order(true, 'DESC');
763 } elseif ($ranktouse > 0 && $ranktouse <= count($this->lines)) { // Update all rank of all other lines
764 $linecount = count($this->lines);
765 for ($ii = $ranktouse; $ii <= $linecount; $ii++) {
766 $this->updateRangOfLine($this->lines[$ii - 1]->id, $ii + 1);
767 }
768 }
769
770 // Mise a jour information denormalisees au niveau de la propale meme
771 if (empty($noupdateafterinsertline)) {
772 $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.
773 }
774
775 if ($result > 0) {
776 $this->db->commit();
777 return $this->line->id;
778 } else {
779 $this->error = $this->db->error();
780 $this->db->rollback();
781 return -1;
782 }
783 } else {
784 $this->error = $this->line->error;
785 $this->errors = $this->line->errors;
786 $this->db->rollback();
787 return -2;
788 }
789 } else {
790 dol_syslog(get_class($this)."::addline status of proposal must be Draft to allow use of ->addline()", LOG_ERR);
791 return -3;
792 }
793 }
794
795
825 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)
826 {
827 global $mysoc, $langs;
828
829 dol_syslog(get_class($this)."::updateLine rowid=$rowid, pu=$pu, qty=$qty, remise_percent=$remise_percent,
830 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");
831 include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
832
833 // Clean parameters
834 $remise_percent = price2num($remise_percent);
835 $qty = (float) price2num($qty);
836 $pu = price2num($pu);
837 $pu_ht_devise = price2num($pu_ht_devise);
838 if (!preg_match('/\‍((.*)\‍)/', (string) $txtva)) {
839 $txtva = price2num($txtva); // $txtva can have format '5.0(XXX)' or '5'
840 }
841 $txlocaltax1 = price2num($txlocaltax1);
842 $txlocaltax2 = price2num($txlocaltax2);
843 $pa_ht = price2num($pa_ht); // do not convert to float here, it breaks the functioning of $pa_ht_isemptystring
844 if (empty($qty) && empty($special_code)) {
845 $special_code = 3; // Set option tag
846 }
847 if (!empty($qty) && $special_code == 3) {
848 $special_code = 0; // Remove option tag
849 }
850 if (empty($type)) {
851 $type = 0;
852 }
853
854 if ($date_start && $date_end && $date_start > $date_end) {
855 $langs->load("errors");
856 $this->error = $langs->trans('ErrorStartDateGreaterEnd');
857 return -1;
858 }
859
860 if ($this->status == self::STATUS_DRAFT) {
861 $this->db->begin();
862
863 // Calcul du total TTC et de la TVA pour la ligne a partir de
864 // qty, pu, remise_percent et txtva
865 // TRES IMPORTANT: C'est au moment de l'insertion ligne qu'on doit stocker
866 // la part ht, tva et ttc, et ce au niveau de la ligne qui a son propre taux tva.
867
868 $localtaxes_type = getLocalTaxesFromRate($txtva, 0, $this->thirdparty, $mysoc);
869
870 // Clean vat code
871 $reg = array();
872 $vat_src_code = '';
873 if (preg_match('/\‍((.*)\‍)/', $txtva, $reg)) {
874 $vat_src_code = $reg[1];
875 $txtva = preg_replace('/\s*\‍(.*\‍)/', '', $txtva); // Remove code into vatrate.
876 }
877
878 // TODO Implement if (getDolGlobalInt('MAIN_UNIT_PRICE_WITH_TAX_IS_FOR_ALL_TAXES')) ?
879
880 $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);
881 $total_ht = $tabprice[0];
882 $total_tva = $tabprice[1];
883 $total_ttc = $tabprice[2];
884 $total_localtax1 = $tabprice[9];
885 $total_localtax2 = $tabprice[10];
886 $pu_ht = $tabprice[3];
887 $pu_tva = $tabprice[4];
888 $pu_ttc = $tabprice[5];
889
890 // MultiCurrency
891 $multicurrency_total_ht = $tabprice[16];
892 $multicurrency_total_tva = $tabprice[17];
893 $multicurrency_total_ttc = $tabprice[18];
894 $pu_ht_devise = $tabprice[19];
895
896 // Anciens indicateurs: $price, $remise (a ne plus utiliser)
897 $price = $pu;
898 $remise = 0;
899 if ((float) $remise_percent > 0) {
900 $remise = round(((float) $pu * (float) $remise_percent / 100), 2);
901 $price = (float) $pu - $remise;
902 }
903
904 //Fetch current line from the database and then clone the object and set it in $oldline property
905 $line = new PropaleLigne($this->db);
906 $line->fetch($rowid);
907
908 $staticline = clone $line;
909
910 $line->oldline = $staticline;
911 $this->line = $line;
912 $this->line->context = $this->context;
913 $this->line->rang = $rang;
914
915 // Reorder if fk_parent_line change
916 if (!empty($fk_parent_line) && !empty($staticline->fk_parent_line) && $fk_parent_line != $staticline->fk_parent_line) {
917 $rangmax = $this->line_max($fk_parent_line);
918 $this->line->rang = $rangmax + 1;
919 }
920
921 $this->line->id = $rowid;
922 $this->line->label = $label;
923 $this->line->desc = $desc;
924 $this->line->qty = $qty;
925 $this->line->product_type = $type;
926 $this->line->vat_src_code = $vat_src_code;
927 $this->line->tva_tx = $txtva;
928 $this->line->localtax1_tx = $txlocaltax1;
929 $this->line->localtax2_tx = $txlocaltax2;
930 $this->line->localtax1_type = empty($localtaxes_type[0]) ? '' : $localtaxes_type[0];
931 $this->line->localtax2_type = empty($localtaxes_type[2]) ? '' : $localtaxes_type[2];
932 $this->line->remise_percent = $remise_percent;
933 $this->line->subprice = $pu_ht;
934 $this->line->info_bits = $info_bits;
935
936 $this->line->total_ht = $total_ht;
937 $this->line->total_tva = $total_tva;
938 $this->line->total_localtax1 = $total_localtax1;
939 $this->line->total_localtax2 = $total_localtax2;
940 $this->line->total_ttc = $total_ttc;
941 $this->line->special_code = $special_code;
942 $this->line->fk_parent_line = $fk_parent_line;
943 $this->line->skip_update_total = $skip_update_total;
944 $this->line->fk_unit = $fk_unit;
945
946 $this->line->fk_fournprice = $fk_fournprice;
947 $this->line->pa_ht = $pa_ht;
948
949 $this->line->date_start = $date_start;
950 $this->line->date_end = $date_end;
951
952 if (is_array($array_options) && count($array_options) > 0) {
953 // We replace values in this->line->array_options only for entries defined into $array_options
954 foreach ($array_options as $key => $value) {
955 $this->line->array_options[$key] = $array_options[$key];
956 }
957 }
958
959 // Multicurrency
960 $this->line->multicurrency_subprice = $pu_ht_devise;
961 $this->line->multicurrency_total_ht = $multicurrency_total_ht;
962 $this->line->multicurrency_total_tva = $multicurrency_total_tva;
963 $this->line->multicurrency_total_ttc = $multicurrency_total_ttc;
964
965 $result = $this->line->update($notrigger);
966 if ($result > 0) {
967 // Reorder if child line
968 if (!empty($fk_parent_line)) {
969 $this->line_order(true, 'DESC');
970 }
971
972 $this->update_price(1, 'auto');
973
974 // $this is Propal
975 // $this->fk_propal = $this->id;
976 // $this->rowid = $rowid;
977
978 $this->db->commit();
979 return $result;
980 } else {
981 $this->error = $this->line->error;
982 $this->errors = $this->line->errors;
983 $this->db->rollback();
984 return -1;
985 }
986 } else {
987 dol_syslog(get_class($this)."::updateline Erreur -2 Propal en mode incompatible pour cette action");
988 return -2;
989 }
990 }
991
992
1000 public function deleteLine($lineid, $id = 0)
1001 {
1002 global $user;
1003
1004 if ($this->statut == self::STATUS_DRAFT) {
1005 $this->db->begin();
1006
1007 $line = new PropaleLigne($this->db);
1008
1009 $line->context = $this->context;
1010
1011 // Load data
1012 $line->fetch($lineid);
1013
1014 if ($id > 0 && $line->fk_propal != $id) {
1015 $this->error = 'ErrorLineIDDoesNotMatchWithObjectID';
1016 return -1;
1017 }
1018
1019 // Memorize previous line for triggers
1020 $staticline = clone $line;
1021 $line->oldline = $staticline;
1022
1023 if ($line->delete($user) > 0) {
1024 $this->update_price(1);
1025
1026 $this->db->commit();
1027 return 1;
1028 } else {
1029 $this->error = $line->error;
1030 $this->errors = $line->errors;
1031 $this->db->rollback();
1032 return -1;
1033 }
1034 } else {
1035 $this->error = 'ErrorDeleteLineNotAllowedByObjectStatus';
1036 return -2;
1037 }
1038 }
1039
1040
1049 public function create($user, $notrigger = 0)
1050 {
1051 global $conf, $hookmanager, $mysoc;
1052 $error = 0;
1053
1054 $now = dol_now();
1055
1056 // Clean parameters
1057 if (empty($this->date)) {
1058 $this->date = $this->datep;
1059 }
1060 $this->fin_validite = $this->date + ($this->duree_validite * 24 * 3600);
1061 if (empty($this->availability_id)) {
1062 $this->availability_id = 0;
1063 }
1064 if (empty($this->demand_reason_id)) {
1065 $this->demand_reason_id = 0;
1066 }
1067
1068 // Multicurrency (test on $this->multicurrency_tx because we should take the default rate only if not using origin rate)
1069 if (!empty($this->multicurrency_code) && empty($this->multicurrency_tx)) {
1070 list($this->fk_multicurrency, $this->multicurrency_tx) = MultiCurrency::getIdAndTxFromCode($this->db, $this->multicurrency_code, $this->date);
1071 } else {
1072 $this->fk_multicurrency = MultiCurrency::getIdFromCode($this->db, $this->multicurrency_code);
1073 }
1074 if (empty($this->fk_multicurrency)) {
1075 $this->multicurrency_code = $conf->currency;
1076 $this->fk_multicurrency = 0;
1077 $this->multicurrency_tx = 1;
1078 }
1079
1080 // Set tmp vars
1081 $delivery_date = $this->delivery_date;
1082
1083 dol_syslog(get_class($this)."::create");
1084
1085 // Check parameters
1086 $result = $this->fetch_thirdparty();
1087 if ($result < 0) {
1088 $this->error = "Failed to fetch company";
1089 dol_syslog(get_class($this)."::create ".$this->error, LOG_ERR);
1090 return -3;
1091 }
1092
1093 // Check parameters
1094 if (!empty($this->ref)) { // We check that ref is not already used
1095 $result = self::isExistingObject($this->element, 0, $this->ref); // Check ref is not yet used
1096 if ($result > 0) {
1097 $this->error = 'ErrorRefAlreadyExists';
1098 dol_syslog(get_class($this)."::create ".$this->error, LOG_WARNING);
1099 $this->db->rollback();
1100 return -1;
1101 }
1102 }
1103
1104 if (empty($this->date)) {
1105 $this->error = "Date of proposal is required";
1106 dol_syslog(get_class($this)."::create ".$this->error, LOG_ERR);
1107 return -4;
1108 }
1109
1110
1111 $this->db->begin();
1112
1113 // Insert into database
1114 $sql = "INSERT INTO ".MAIN_DB_PREFIX."propal (";
1115 $sql .= "fk_soc";
1116 $sql .= ", price";
1117 $sql .= ", total_tva";
1118 $sql .= ", total_ttc";
1119 $sql .= ", datep";
1120 $sql .= ", datec";
1121 $sql .= ", ref";
1122 $sql .= ", fk_user_author";
1123 $sql .= ", note_private";
1124 $sql .= ", note_public";
1125 $sql .= ", model_pdf";
1126 $sql .= ", fin_validite";
1127 $sql .= ", fk_cond_reglement";
1128 $sql .= ", deposit_percent";
1129 $sql .= ", fk_mode_reglement";
1130 $sql .= ", fk_account";
1131 $sql .= ", ref_client";
1132 $sql .= ", ref_ext";
1133 $sql .= ", date_livraison";
1134 $sql .= ", fk_shipping_method";
1135 $sql .= ", fk_warehouse";
1136 $sql .= ", fk_availability";
1137 $sql .= ", fk_input_reason";
1138 $sql .= ", fk_projet";
1139 $sql .= ", fk_incoterms";
1140 $sql .= ", location_incoterms";
1141 $sql .= ", entity";
1142 $sql .= ", fk_multicurrency";
1143 $sql .= ", multicurrency_code";
1144 $sql .= ", multicurrency_tx";
1145 $sql .= ") ";
1146 $sql .= " VALUES (";
1147 $sql .= $this->socid;
1148 $sql .= ", 0";
1149 $sql .= ", 0";
1150 $sql .= ", 0";
1151 $sql .= ", '".$this->db->idate($this->date)."'";
1152 $sql .= ", '".$this->db->idate($now)."'";
1153 $sql .= ", '(PROV)'";
1154 $sql .= ", ".($user->id > 0 ? ((int) $user->id) : "NULL");
1155 $sql .= ", '".$this->db->escape($this->note_private)."'";
1156 $sql .= ", '".$this->db->escape($this->note_public)."'";
1157 $sql .= ", '".$this->db->escape($this->model_pdf)."'";
1158 $sql .= ", ".($this->fin_validite != '' ? "'".$this->db->idate($this->fin_validite)."'" : "NULL");
1159 $sql .= ", ".($this->cond_reglement_id > 0 ? ((int) $this->cond_reglement_id) : 'NULL');
1160 $sql .= ", ".(!empty($this->deposit_percent) ? "'".$this->db->escape($this->deposit_percent)."'" : 'NULL');
1161 $sql .= ", ".($this->mode_reglement_id > 0 ? ((int) $this->mode_reglement_id) : 'NULL');
1162 $sql .= ", ".($this->fk_account > 0 ? ((int) $this->fk_account) : 'NULL');
1163 $sql .= ", '".$this->db->escape($this->ref_client)."'";
1164 $sql .= ", '".$this->db->escape($this->ref_ext)."'";
1165 $sql .= ", ".(empty($delivery_date) ? "NULL" : "'".$this->db->idate($delivery_date)."'");
1166 $sql .= ", ".($this->shipping_method_id > 0 ? $this->shipping_method_id : 'NULL');
1167 $sql .= ", ".($this->warehouse_id > 0 ? $this->warehouse_id : 'NULL');
1168 $sql .= ", ".$this->availability_id;
1169 $sql .= ", ".$this->demand_reason_id;
1170 $sql .= ", ".($this->fk_project ? $this->fk_project : "null");
1171 $sql .= ", ".(int) $this->fk_incoterms;
1172 $sql .= ", '".$this->db->escape($this->location_incoterms)."'";
1173 $sql .= ", ".setEntity($this);
1174 $sql .= ", ".(int) $this->fk_multicurrency;
1175 $sql .= ", '".$this->db->escape($this->multicurrency_code)."'";
1176 $sql .= ", ".(float) $this->multicurrency_tx;
1177 $sql .= ")";
1178
1179 dol_syslog(get_class($this)."::create", LOG_DEBUG);
1180 $resql = $this->db->query($sql);
1181 if ($resql) {
1182 $this->id = $this->db->last_insert_id(MAIN_DB_PREFIX."propal");
1183
1184 if ($this->id) {
1185 $this->ref = '(PROV'.$this->id.')';
1186 $sql = 'UPDATE '.MAIN_DB_PREFIX."propal SET ref='".$this->db->escape($this->ref)."' WHERE rowid=".((int) $this->id);
1187
1188 dol_syslog(get_class($this)."::create", LOG_DEBUG);
1189 $resql = $this->db->query($sql);
1190 if (!$resql) {
1191 $error++;
1192 }
1193
1194 if (!empty($this->linkedObjectsIds) && empty($this->linked_objects)) { // To use new linkedObjectsIds instead of old linked_objects
1195 $this->linked_objects = $this->linkedObjectsIds; // TODO Replace linked_objects with linkedObjectsIds
1196 }
1197
1198 // Add object linked
1199 if (!$error && $this->id && !empty($this->linked_objects) && is_array($this->linked_objects)) {
1200 foreach ($this->linked_objects as $origin => $tmp_origin_id) {
1201 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, ...))
1202 foreach ($tmp_origin_id as $origin_id) {
1203 $ret = $this->add_object_linked($origin, $origin_id);
1204 if (!$ret) {
1205 $this->error = $this->db->lasterror();
1206 $error++;
1207 }
1208 }
1209 } else { // Old behaviour, if linked_object has only one link per type, so is something like array('contract'=>id1))
1210 $origin_id = $tmp_origin_id;
1211 $ret = $this->add_object_linked($origin, $origin_id);
1212 if (!$ret) {
1213 $this->error = $this->db->lasterror();
1214 $error++;
1215 }
1216 }
1217 }
1218 }
1219
1220 /*
1221 * Insertion du detail des produits dans la base
1222 * Insert products detail in database
1223 */
1224 if (!$error) {
1225 $fk_parent_line = 0;
1226 $num = count($this->lines);
1227
1228 for ($i = 0; $i < $num; $i++) {
1229 if (!is_object($this->lines[$i])) { // If this->lines is not array of objects, coming from REST API
1230 // Convert into object this->lines[$i].
1231 $line = (object) $this->lines[$i];
1232 } else {
1233 $line = $this->lines[$i];
1234 }
1235 // Reset fk_parent_line for line that are not child lines or special product
1236 if (($line->product_type != 9 && empty($line->fk_parent_line)) || $line->product_type == 9) {
1237 $fk_parent_line = 0;
1238 }
1239 // Complete vat rate with code
1240 $vatrate = $line->tva_tx;
1241 if ($line->vat_src_code && !preg_match('/\‍(.*\‍)/', $vatrate)) {
1242 $vatrate .= ' ('.$line->vat_src_code.')';
1243 }
1244
1245 if (getDolGlobalString('MAIN_CREATEFROM_KEEP_LINE_ORIGIN_INFORMATION')) {
1246 $originid = $line->origin_id;
1247 $origintype = $line->origin;
1248 } else {
1249 $originid = $line->id;
1250 $origintype = $this->element;
1251 }
1252
1253 $result = $this->addline(
1254 $line->desc,
1255 $line->subprice,
1256 $line->qty,
1257 $vatrate,
1258 $line->localtax1_tx,
1259 $line->localtax2_tx,
1260 $line->fk_product,
1261 $line->remise_percent,
1262 'HT',
1263 0,
1264 $line->info_bits,
1265 $line->product_type,
1266 $line->rang,
1267 $line->special_code,
1268 $fk_parent_line,
1269 $line->fk_fournprice,
1270 $line->pa_ht,
1271 $line->label,
1272 $line->date_start,
1273 $line->date_end,
1274 $line->array_options,
1275 $line->fk_unit,
1276 $origintype,
1277 $originid,
1278 0,
1279 0,
1280 1
1281 );
1282
1283 if ($result < 0) {
1284 $error++;
1285 $this->error = $this->db->error;
1286 dol_print_error($this->db);
1287 break;
1288 }
1289
1290 // Set the id on created row
1291 $line->id = $result;
1292
1293 // Defined the new fk_parent_line
1294 if ($result > 0 && $line->product_type == 9) {
1295 $fk_parent_line = $result;
1296 }
1297 }
1298 }
1299
1300 // Set delivery address
1301 /*if (! $error && $this->fk_delivery_address)
1302 {
1303 $sql = "UPDATE ".MAIN_DB_PREFIX."propal";
1304 $sql.= " SET fk_delivery_address = ".((int) $this->fk_delivery_address);
1305 $sql.= " WHERE ref = '".$this->db->escape($this->ref)."'";
1306 $sql.= " AND entity = ".setEntity($this);
1307
1308 $result=$this->db->query($sql);
1309 }*/
1310
1311 if (!$error) {
1312 // Mise a jour infos denormalisees
1313 $resql = $this->update_price(1, 'auto', 0, $mysoc);
1314 if ($resql) {
1315 $action = 'update';
1316
1317 // Actions on extra fields
1318 if (!$error) {
1319 $result = $this->insertExtraFields();
1320 if ($result < 0) {
1321 $error++;
1322 }
1323 }
1324
1325 if (!$error && !$notrigger) {
1326 // Call trigger
1327 $result = $this->call_trigger('PROPAL_CREATE', $user);
1328 if ($result < 0) {
1329 $error++;
1330 }
1331 // End call triggers
1332 }
1333 } else {
1334 $this->error = $this->db->lasterror();
1335 $error++;
1336 }
1337 }
1338 } else {
1339 $this->error = $this->db->lasterror();
1340 $error++;
1341 }
1342
1343 if (!$error) {
1344 $this->db->commit();
1345 dol_syslog(get_class($this)."::create done id=".$this->id);
1346 return $this->id;
1347 } else {
1348 $this->db->rollback();
1349 return -2;
1350 }
1351 } else {
1352 $this->error = $this->db->lasterror();
1353 $this->db->rollback();
1354 return -1;
1355 }
1356 }
1357
1368 public function createFromClone(User $user, $socid = 0, $forceentity = null, $update_prices = false, $update_desc = false)
1369 {
1370 global $conf, $hookmanager, $mysoc;
1371
1372 dol_include_once('/projet/class/project.class.php');
1373
1374 $error = 0;
1375 $now = dol_now();
1376
1377 dol_syslog(__METHOD__, LOG_DEBUG);
1378
1379 $object = new self($this->db);
1380
1381 $this->db->begin();
1382
1383 // Load source object
1384 $object->fetch($this->id);
1385
1386 $objsoc = new Societe($this->db);
1387
1388 // Change socid if needed
1389 if (!empty($socid) && $socid != $object->socid) {
1390 if ($objsoc->fetch($socid) > 0) {
1391 $object->socid = $objsoc->id;
1392 $object->cond_reglement_id = (!empty($objsoc->cond_reglement_id) ? $objsoc->cond_reglement_id : 0);
1393 $object->deposit_percent = (!empty($objsoc->deposit_percent) ? $objsoc->deposit_percent : null);
1394 $object->mode_reglement_id = (!empty($objsoc->mode_reglement_id) ? $objsoc->mode_reglement_id : 0);
1395 $object->fk_delivery_address = 0;
1396
1397 /*if (isModEnabled('project'))
1398 {
1399 $project = new Project($db);
1400 if ($this->fk_project > 0 && $project->fetch($this->fk_project)) {
1401 if ($project->socid <= 0) $clonedObj->fk_project = $this->fk_project;
1402 else $clonedObj->fk_project = '';
1403 } else {
1404 $clonedObj->fk_project = '';
1405 }
1406 }*/
1407 $object->fk_project = 0; // A cloned proposal is set by default to no project.
1408 }
1409
1410 // reset ref_client
1411 if (!getDolGlobalString('MAIN_KEEP_REF_CUSTOMER_ON_CLONING')) {
1412 $object->ref_client = '';
1413 }
1414
1415 // TODO Change product price if multi-prices
1416 } else {
1417 $objsoc->fetch($object->socid);
1418 }
1419
1420 // update prices
1421 if ($update_prices === true || $update_desc === true) {
1422 if ($objsoc->id > 0 && !empty($object->lines)) {
1423 if ($update_prices === true && getDolGlobalString('PRODUIT_CUSTOMER_PRICES')) {
1424 // If price per customer
1425 require_once DOL_DOCUMENT_ROOT . '/product/class/productcustomerprice.class.php';
1426 }
1427
1428 foreach ($object->lines as $line) {
1429 $line->id = 0;
1430
1431 if ($line->fk_product > 0) {
1432 $prod = new Product($this->db);
1433 $res = $prod->fetch($line->fk_product);
1434 if ($res > 0) {
1435 if ($update_prices === true) {
1436 $pu_ht = $prod->price;
1437 $tva_tx = get_default_tva($mysoc, $objsoc, $prod->id);
1438 $remise_percent = $objsoc->remise_percent;
1439
1440 if (getDolGlobalString('PRODUIT_MULTIPRICES') && $objsoc->price_level > 0) {
1441 $pu_ht = $prod->multiprices[$objsoc->price_level];
1442 if (getDolGlobalString('PRODUIT_MULTIPRICES_USE_VAT_PER_LEVEL')) { // using this option is a bug. kept for backward compatibility
1443 if (isset($prod->multiprices_tva_tx[$objsoc->price_level])) {
1444 $tva_tx = $prod->multiprices_tva_tx[$objsoc->price_level];
1445 }
1446 }
1447 } elseif (getDolGlobalString('PRODUIT_CUSTOMER_PRICES')) {
1448 $prodcustprice = new ProductCustomerPrice($this->db);
1449 $filter = array('t.fk_product' => $prod->id, 't.fk_soc' => $objsoc->id);
1450 $result = $prodcustprice->fetchAll('', '', 0, 0, $filter);
1451 if ($result) {
1452 // If there is some prices specific to the customer
1453 if (count($prodcustprice->lines) > 0) {
1454 $pu_ht = price($prodcustprice->lines[0]->price);
1455 $tva_tx = ($prodcustprice->lines[0]->default_vat_code ? $prodcustprice->lines[0]->tva_tx.' ('.$prodcustprice->lines[0]->default_vat_code.' )' : $prodcustprice->lines[0]->tva_tx);
1456 if ($prodcustprice->lines[0]->default_vat_code && !preg_match('/\‍(.*\‍)/', $tva_tx)) {
1457 $tva_tx .= ' ('.$prodcustprice->lines[0]->default_vat_code.')';
1458 }
1459 }
1460 }
1461 }
1462
1463 $line->subprice = $pu_ht;
1464 $line->tva_tx = $tva_tx;
1465 $line->remise_percent = $remise_percent;
1466 }
1467 if ($update_desc === true) {
1468 $line->desc = $prod->description;
1469 }
1470 }
1471 }
1472 }
1473 }
1474 }
1475
1476 $object->id = 0;
1477 $object->ref = '';
1478 $object->entity = (!empty($forceentity) ? $forceentity : $object->entity);
1479 $object->statut = self::STATUS_DRAFT;
1480
1481 // Clear fields
1482 $object->user_creation_id = $user->id;
1483 $object->user_validation_id = 0;
1484 $object->date = $now;
1485 $object->datep = $now; // deprecated
1486 $object->fin_validite = $object->date + ($object->duree_validite * 24 * 3600);
1487 if (!getDolGlobalString('MAIN_KEEP_REF_CUSTOMER_ON_CLONING')) {
1488 $object->ref_client = '';
1489 }
1490 if (getDolGlobalInt('MAIN_DONT_KEEP_NOTE_ON_CLONING') == 1) {
1491 $object->note_private = '';
1492 $object->note_public = '';
1493 }
1494 // Create clone
1495 $object->context['createfromclone'] = 'createfromclone';
1496 $result = $object->create($user);
1497 if ($result < 0) {
1498 $this->error = $object->error;
1499 $this->errors = array_merge($this->errors, $object->errors);
1500 $error++;
1501 }
1502
1503 if (!$error && !getDolGlobalInt('MAIN_IGNORE_CONTACTS_ON_CLONING')) {
1504 // copy internal contacts
1505 if ($object->copy_linked_contact($this, 'internal') < 0) {
1506 $error++;
1507 }
1508 }
1509
1510 if (!$error) {
1511 // copy external contacts if same company
1512 if ($this->socid == $object->socid) {
1513 if ($object->copy_linked_contact($this, 'external') < 0) {
1514 $error++;
1515 }
1516 }
1517 }
1518
1519 if (!$error) {
1520 // Hook of thirdparty module
1521 if (is_object($hookmanager)) {
1522 $parameters = array('objFrom' => $this, 'clonedObj' => $object);
1523 $action = '';
1524 $reshook = $hookmanager->executeHooks('createFrom', $parameters, $object, $action); // Note that $action and $object may have been modified by some hooks
1525 if ($reshook < 0) {
1526 $this->setErrorsFromObject($hookmanager);
1527 $error++;
1528 }
1529 }
1530 }
1531
1532 unset($object->context['createfromclone']);
1533
1534 // End
1535 if (!$error) {
1536 $this->db->commit();
1537 return $object->id;
1538 } else {
1539 $this->db->rollback();
1540 return -1;
1541 }
1542 }
1543
1553 public function fetch($rowid, $ref = '', $ref_ext = '', $forceentity = 0)
1554 {
1555 $sql = "SELECT p.rowid, p.ref, p.entity, p.fk_soc";
1556 $sql .= ", p.total_ttc, p.total_tva, p.localtax1, p.localtax2, p.total_ht";
1557 $sql .= ", p.datec";
1558 $sql .= ", p.date_signature as dates";
1559 $sql .= ", p.date_valid as datev";
1560 $sql .= ", p.datep as dp";
1561 $sql .= ", p.fin_validite as dfv";
1562 $sql .= ", p.date_livraison as delivery_date";
1563 $sql .= ", p.model_pdf, p.last_main_doc, p.ref_client, ref_ext, p.extraparams";
1564 $sql .= ", p.note_private, p.note_public";
1565 $sql .= ", p.fk_projet as fk_project, p.fk_statut";
1566 $sql .= ", p.fk_user_author, p.fk_user_valid, p.fk_user_cloture";
1567 $sql .= ", p.fk_delivery_address";
1568 $sql .= ", p.fk_availability";
1569 $sql .= ", p.fk_input_reason";
1570 $sql .= ", p.fk_cond_reglement";
1571 $sql .= ", p.fk_mode_reglement";
1572 $sql .= ', p.fk_account';
1573 $sql .= ", p.fk_shipping_method";
1574 $sql .= ", p.fk_warehouse";
1575 $sql .= ", p.fk_incoterms, p.location_incoterms";
1576 $sql .= ", p.fk_multicurrency, p.multicurrency_code, p.multicurrency_tx, p.multicurrency_total_ht, p.multicurrency_total_tva, p.multicurrency_total_ttc";
1577 $sql .= ", p.tms as date_modification";
1578 $sql .= ", i.libelle as label_incoterms";
1579 $sql .= ", c.label as statut_label";
1580 $sql .= ", ca.code as availability_code, ca.label as availability";
1581 $sql .= ", dr.code as demand_reason_code, dr.label as demand_reason";
1582 $sql .= ", cr.code as cond_reglement_code, cr.libelle as cond_reglement, cr.libelle_facture as cond_reglement_libelle_doc, p.deposit_percent";
1583 $sql .= ", cp.code as mode_reglement_code, cp.libelle as mode_reglement";
1584 $sql .= " FROM ".MAIN_DB_PREFIX."propal as p";
1585 $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_propalst as c ON p.fk_statut = c.id';
1586 $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_paiement as cp ON p.fk_mode_reglement = cp.id AND cp.entity IN ('.getEntity('c_paiement').')';
1587 $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').')';
1588 $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_availability as ca ON p.fk_availability = ca.rowid';
1589 $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_input_reason as dr ON p.fk_input_reason = dr.rowid';
1590 $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_incoterms as i ON p.fk_incoterms = i.rowid';
1591
1592 if (!empty($ref)) {
1593 if (!empty($forceentity)) {
1594 $sql .= " WHERE p.entity = ".(int) $forceentity; // Check only the current entity because we may have the same reference in several entities
1595 } else {
1596 $sql .= " WHERE p.entity IN (".getEntity('propal').")";
1597 }
1598 $sql .= " AND p.ref='".$this->db->escape($ref)."'";
1599 } else {
1600 // Don't use entity if you use rowid
1601 $sql .= " WHERE p.rowid = ".((int) $rowid);
1602 }
1603
1604 dol_syslog(get_class($this)."::fetch", LOG_DEBUG);
1605 $resql = $this->db->query($sql);
1606 if ($resql) {
1607 if ($this->db->num_rows($resql)) {
1608 $obj = $this->db->fetch_object($resql);
1609
1610 $this->id = $obj->rowid;
1611 $this->entity = $obj->entity;
1612
1613 $this->ref = $obj->ref;
1614 $this->ref_client = $obj->ref_client;
1615 $this->ref_customer = $obj->ref_client;
1616 $this->ref_ext = $obj->ref_ext;
1617
1618 $this->total = $obj->total_ttc; // TODO deprecated
1619 $this->total_ttc = $obj->total_ttc;
1620 $this->total_ht = $obj->total_ht;
1621 $this->total_tva = $obj->total_tva;
1622 $this->total_localtax1 = $obj->localtax1;
1623 $this->total_localtax2 = $obj->localtax2;
1624
1625 $this->socid = $obj->fk_soc;
1626 $this->thirdparty = null; // Clear if another value was already set by fetch_thirdparty
1627
1628 $this->fk_project = $obj->fk_project;
1629 $this->project = null; // Clear if another value was already set by fetch_projet
1630
1631 $this->model_pdf = $obj->model_pdf;
1632 $this->last_main_doc = $obj->last_main_doc;
1633 $this->note = $obj->note_private; // TODO deprecated
1634 $this->note_private = $obj->note_private;
1635 $this->note_public = $obj->note_public;
1636
1637 $this->status = (int) $obj->fk_statut;
1638 $this->statut = $this->status; // deprecated
1639
1640 $this->datec = $this->db->jdate($obj->datec); // TODO deprecated
1641 $this->datev = $this->db->jdate($obj->datev); // TODO deprecated
1642 $this->date_creation = $this->db->jdate($obj->datec); //Creation date
1643 $this->date_validation = $this->db->jdate($obj->datev); //Validation date
1644 $this->date_modification = $this->db->jdate($obj->date_modification); // tms
1645 $this->date_signature = $this->db->jdate($obj->dates); // Signature date
1646 $this->date = $this->db->jdate($obj->dp); // Proposal date
1647 $this->datep = $this->db->jdate($obj->dp); // deprecated
1648 $this->fin_validite = $this->db->jdate($obj->dfv);
1649 $this->delivery_date = $this->db->jdate($obj->delivery_date);
1650 $this->shipping_method_id = ($obj->fk_shipping_method > 0) ? $obj->fk_shipping_method : null;
1651 $this->warehouse_id = ($obj->fk_warehouse > 0) ? $obj->fk_warehouse : null;
1652 $this->availability_id = $obj->fk_availability;
1653 $this->availability_code = $obj->availability_code;
1654 $this->availability = $obj->availability;
1655 $this->demand_reason_id = $obj->fk_input_reason;
1656 $this->demand_reason_code = $obj->demand_reason_code;
1657 $this->demand_reason = $obj->demand_reason;
1658 $this->fk_address = $obj->fk_delivery_address;
1659
1660 $this->mode_reglement_id = $obj->fk_mode_reglement;
1661 $this->mode_reglement_code = $obj->mode_reglement_code;
1662 $this->mode_reglement = $obj->mode_reglement;
1663 $this->fk_account = ($obj->fk_account > 0) ? $obj->fk_account : null;
1664 $this->cond_reglement_id = $obj->fk_cond_reglement;
1665 $this->cond_reglement_code = $obj->cond_reglement_code;
1666 $this->cond_reglement = $obj->cond_reglement;
1667 $this->cond_reglement_doc = $obj->cond_reglement_libelle_doc;
1668 $this->deposit_percent = $obj->deposit_percent;
1669
1670 $this->extraparams = !empty($obj->extraparams) ? (array) json_decode($obj->extraparams, true) : array();
1671
1672 $this->user_author_id = $obj->fk_user_author;
1673 $this->user_validation_id = $obj->fk_user_valid;
1674 $this->user_closing_id = $obj->fk_user_cloture;
1675
1676 //Incoterms
1677 $this->fk_incoterms = $obj->fk_incoterms;
1678 $this->location_incoterms = $obj->location_incoterms;
1679 $this->label_incoterms = $obj->label_incoterms;
1680
1681 // Multicurrency
1682 $this->fk_multicurrency = $obj->fk_multicurrency;
1683 $this->multicurrency_code = $obj->multicurrency_code;
1684 $this->multicurrency_tx = $obj->multicurrency_tx;
1685 $this->multicurrency_total_ht = $obj->multicurrency_total_ht;
1686 $this->multicurrency_total_tva = $obj->multicurrency_total_tva;
1687 $this->multicurrency_total_ttc = $obj->multicurrency_total_ttc;
1688
1689 // Retrieve all extrafield
1690 // fetch optionals attributes and labels
1691 $this->fetch_optionals();
1692
1693 $this->db->free($resql);
1694
1695 $this->lines = array();
1696
1697 // Lines
1698 $result = $this->fetch_lines();
1699 if ($result < 0) {
1700 return -3;
1701 }
1702
1703 return 1;
1704 }
1705
1706 $this->error = "Record Not Found";
1707 return 0;
1708 } else {
1709 $this->error = $this->db->lasterror();
1710 return -1;
1711 }
1712 }
1713
1721 public function update(User $user, $notrigger = 0)
1722 {
1723 global $conf;
1724
1725 $error = 0;
1726
1727 // Clean parameters
1728 if (isset($this->ref)) {
1729 $this->ref = trim($this->ref);
1730 }
1731 if (isset($this->ref_client)) {
1732 $this->ref_client = trim($this->ref_client);
1733 }
1734 if (isset($this->note) || isset($this->note_private)) {
1735 $this->note_private = (isset($this->note_private) ? trim($this->note_private) : trim($this->note));
1736 }
1737 if (isset($this->note_public)) {
1738 $this->note_public = trim($this->note_public);
1739 }
1740 if (isset($this->model_pdf)) {
1741 $this->model_pdf = trim($this->model_pdf);
1742 }
1743 if (isset($this->import_key)) {
1744 $this->import_key = trim($this->import_key);
1745 }
1746 if (!empty($this->duree_validite) && is_numeric($this->duree_validite)) {
1747 $this->fin_validite = $this->date + ($this->duree_validite * 24 * 3600);
1748 }
1749
1750 // Check parameters
1751 // Put here code to add control on parameters values
1752
1753 // Update request
1754 $sql = "UPDATE ".MAIN_DB_PREFIX."propal SET";
1755 $sql .= " ref=".(isset($this->ref) ? "'".$this->db->escape($this->ref)."'" : "null").",";
1756 $sql .= " ref_client=".(isset($this->ref_client) ? "'".$this->db->escape($this->ref_client)."'" : "null").",";
1757 $sql .= " ref_ext=".(isset($this->ref_ext) ? "'".$this->db->escape($this->ref_ext)."'" : "null").",";
1758 $sql .= " fk_soc=".(isset($this->socid) ? $this->socid : "null").",";
1759 $sql .= " datep=".(strval($this->date) != '' ? "'".$this->db->idate($this->date)."'" : 'null').",";
1760 if (!empty($this->fin_validite)) {
1761 $sql .= " fin_validite=".(strval($this->fin_validite) != '' ? "'".$this->db->idate($this->fin_validite)."'" : 'null').",";
1762 }
1763 $sql .= " date_valid=".(strval($this->date_validation) != '' ? "'".$this->db->idate($this->date_validation)."'" : 'null').",";
1764 $sql .= " total_tva=".(isset($this->total_tva) ? $this->total_tva : "null").",";
1765 $sql .= " localtax1=".(isset($this->total_localtax1) ? $this->total_localtax1 : "null").",";
1766 $sql .= " localtax2=".(isset($this->total_localtax2) ? $this->total_localtax2 : "null").",";
1767 $sql .= " total_ht=".(isset($this->total_ht) ? $this->total_ht : "null").",";
1768 $sql .= " total_ttc=".(isset($this->total_ttc) ? $this->total_ttc : "null").",";
1769 $sql .= " fk_statut=".(isset($this->statut) ? $this->statut : "null").",";
1770 $sql .= " fk_user_author=".(isset($this->user_author_id) ? $this->user_author_id : "null").",";
1771 $sql .= " fk_user_valid = ".(!empty($this->user_validation_id) ? (int) $this->user_validation_id : "null").",";
1772 $sql .= " fk_projet=".(isset($this->fk_project) ? $this->fk_project : "null").",";
1773 $sql .= " fk_cond_reglement=".(isset($this->cond_reglement_id) ? $this->cond_reglement_id : "null").",";
1774 $sql .= " deposit_percent=".(!empty($this->deposit_percent) ? "'".$this->db->escape($this->deposit_percent)."'" : "null").",";
1775 $sql .= " fk_mode_reglement=".(isset($this->mode_reglement_id) ? $this->mode_reglement_id : "null").",";
1776 $sql .= " fk_input_reason=".(isset($this->demand_reason_id) ? $this->demand_reason_id : "null").",";
1777 $sql .= " fk_shipping_method=".(isset($this->shipping_method_id) ? (int) $this->shipping_method_id : "null").",";
1778 $sql .= " fk_availability=".(isset($this->availability_id) ? (int) $this->availability_id : "null").",";
1779 $sql .= " note_private=".(isset($this->note_private) ? "'".$this->db->escape($this->note_private)."'" : "null").",";
1780 $sql .= " note_public=".(isset($this->note_public) ? "'".$this->db->escape($this->note_public)."'" : "null").",";
1781 $sql .= " model_pdf=".(isset($this->model_pdf) ? "'".$this->db->escape($this->model_pdf)."'" : "null").",";
1782 $sql .= " import_key=".(isset($this->import_key) ? "'".$this->db->escape($this->import_key)."'" : "null");
1783 $sql .= " WHERE rowid=".((int) $this->id);
1784
1785 $this->db->begin();
1786
1787 dol_syslog(get_class($this)."::update", LOG_DEBUG);
1788 $resql = $this->db->query($sql);
1789 if (!$resql) {
1790 $error++;
1791 $this->errors[] = "Error ".$this->db->lasterror();
1792 }
1793
1794 if (!$error) {
1795 $result = $this->insertExtraFields();
1796 if ($result < 0) {
1797 $error++;
1798 }
1799 }
1800
1801 if (!$error && !$notrigger) {
1802 // Call trigger
1803 $result = $this->call_trigger('PROPAL_MODIFY', $user);
1804 if ($result < 0) {
1805 $error++;
1806 }
1807 // End call triggers
1808 }
1809
1810 // Commit or rollback
1811 if ($error) {
1812 foreach ($this->errors as $errmsg) {
1813 dol_syslog(get_class($this)."::update ".$errmsg, LOG_ERR);
1814 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
1815 }
1816 $this->db->rollback();
1817 return -1 * $error;
1818 } else {
1819 $this->db->commit();
1820 return 1;
1821 }
1822 }
1823
1824
1825 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1834 public function fetch_lines($only_product = 0, $loadalsotranslation = 0, $sqlforgedfilters = '')
1835 {
1836 // phpcs:enable
1837 $this->lines = array();
1838
1839 $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,';
1840 $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,';
1841 $sql .= ' d.fk_unit,';
1842 $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,';
1843 $sql .= ' p.weight, p.weight_units, p.volume, p.volume_units,';
1844 $sql .= ' d.date_start, d.date_end,';
1845 $sql .= ' d.fk_multicurrency, d.multicurrency_code, d.multicurrency_subprice, d.multicurrency_total_ht, d.multicurrency_total_tva, d.multicurrency_total_ttc';
1846 $sql .= ' FROM '.MAIN_DB_PREFIX.'propaldet as d';
1847 $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'product as p ON (d.fk_product = p.rowid)';
1848 $sql .= ' WHERE d.fk_propal = '.((int) $this->id);
1849 if ($only_product) {
1850 $sql .= ' AND p.fk_product_type = 0';
1851 }
1852 if ($sqlforgedfilters) {
1853 $sql .= $sqlforgedfilters;
1854 }
1855 $sql .= ' ORDER by d.rang';
1856
1857 dol_syslog(get_class($this)."::fetch_lines", LOG_DEBUG);
1858 $result = $this->db->query($sql);
1859 if ($result) {
1860 require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php';
1861
1862 $num = $this->db->num_rows($result);
1863
1864 $i = 0;
1865 while ($i < $num) {
1866 $objp = $this->db->fetch_object($result);
1867
1868 $line = new PropaleLigne($this->db);
1869
1870 $line->rowid = $objp->rowid; //Deprecated
1871 $line->id = $objp->rowid;
1872 $line->fk_propal = $objp->fk_propal;
1873 $line->fk_parent_line = $objp->fk_parent_line;
1874 $line->product_type = $objp->product_type;
1875 $line->label = $objp->custom_label;
1876 $line->desc = $objp->description; // Description ligne
1877 $line->description = $objp->description; // Description ligne
1878 $line->qty = $objp->qty;
1879 $line->vat_src_code = $objp->vat_src_code;
1880 $line->tva_tx = $objp->tva_tx;
1881 $line->localtax1_tx = $objp->localtax1_tx;
1882 $line->localtax2_tx = $objp->localtax2_tx;
1883 $line->localtax1_type = $objp->localtax1_type;
1884 $line->localtax2_type = $objp->localtax2_type;
1885 $line->subprice = $objp->subprice;
1886 $line->fk_remise_except = $objp->fk_remise_except;
1887 $line->remise_percent = $objp->remise_percent;
1888 $line->price = $objp->price; // TODO deprecated
1889
1890 $line->info_bits = $objp->info_bits;
1891 $line->total_ht = $objp->total_ht;
1892 $line->total_tva = $objp->total_tva;
1893 $line->total_localtax1 = $objp->total_localtax1;
1894 $line->total_localtax2 = $objp->total_localtax2;
1895 $line->total_ttc = $objp->total_ttc;
1896 $line->fk_fournprice = $objp->fk_fournprice;
1897 $marginInfos = getMarginInfos($objp->subprice, $objp->remise_percent, $objp->tva_tx, $objp->localtax1_tx, $objp->localtax2_tx, $line->fk_fournprice, $objp->pa_ht);
1898 $line->pa_ht = $marginInfos[0];
1899 $line->marge_tx = $marginInfos[1];
1900 $line->marque_tx = $marginInfos[2];
1901 $line->special_code = $objp->special_code;
1902 $line->rang = $objp->rang;
1903
1904 $line->fk_product = $objp->fk_product;
1905
1906 $line->ref = $objp->product_ref; // deprecated
1907 $line->libelle = $objp->product_label; // deprecated
1908
1909 $line->product_ref = $objp->product_ref;
1910 $line->product_label = $objp->product_label;
1911 $line->product_desc = $objp->product_desc; // Description produit
1912 $line->product_tobatch = $objp->product_tobatch;
1913 $line->product_barcode = $objp->product_barcode;
1914
1915 $line->fk_product_type = $objp->fk_product_type; // deprecated
1916 $line->fk_unit = $objp->fk_unit;
1917 $line->weight = $objp->weight;
1918 $line->weight_units = $objp->weight_units;
1919 $line->volume = $objp->volume;
1920 $line->volume_units = $objp->volume_units;
1921
1922 $line->date_start = $this->db->jdate($objp->date_start);
1923 $line->date_end = $this->db->jdate($objp->date_end);
1924
1925 // Multicurrency
1926 $line->fk_multicurrency = $objp->fk_multicurrency;
1927 $line->multicurrency_code = $objp->multicurrency_code;
1928 $line->multicurrency_subprice = $objp->multicurrency_subprice;
1929 $line->multicurrency_total_ht = $objp->multicurrency_total_ht;
1930 $line->multicurrency_total_tva = $objp->multicurrency_total_tva;
1931 $line->multicurrency_total_ttc = $objp->multicurrency_total_ttc;
1932
1933 $line->fetch_optionals();
1934
1935 // multilangs
1936 if (getDolGlobalInt('MAIN_MULTILANGS') && !empty($objp->fk_product) && !empty($loadalsotranslation)) {
1937 $tmpproduct = new Product($this->db);
1938 $tmpproduct->fetch($objp->fk_product);
1939 $tmpproduct->getMultiLangs();
1940
1941 $line->multilangs = $tmpproduct->multilangs;
1942 }
1943
1944 $this->lines[$i] = $line;
1945
1946 $i++;
1947 }
1948
1949 $this->db->free($result);
1950
1951 return $num;
1952 } else {
1953 $this->error = $this->db->lasterror();
1954 return -3;
1955 }
1956 }
1957
1965 public function valid($user, $notrigger = 0)
1966 {
1967 global $conf;
1968
1969 require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
1970
1971 $error = 0;
1972
1973 // Protection
1974 if ($this->statut == self::STATUS_VALIDATED) {
1975 dol_syslog(get_class($this)."::valid action abandoned: already validated", LOG_WARNING);
1976 return 0;
1977 }
1978
1979 if (!((!getDolGlobalString('MAIN_USE_ADVANCED_PERMS') && $user->hasRight('propal', 'creer'))
1980 || (getDolGlobalString('MAIN_USE_ADVANCED_PERMS') && $user->hasRight('propal', 'propal_advance', 'validate')))) {
1981 $this->error = 'ErrorPermissionDenied';
1982 dol_syslog(get_class($this)."::valid ".$this->error, LOG_ERR);
1983 return -1;
1984 }
1985
1986 $now = dol_now();
1987
1988 $this->db->begin();
1989
1990 // Numbering module definition
1991 $soc = new Societe($this->db);
1992 $soc->fetch($this->socid);
1993
1994 // Define new ref
1995 if (!$error && (preg_match('/^[\‍(]?PROV/i', $this->ref) || empty($this->ref))) { // empty should not happened, but when it occurs, the test save life
1996 $num = $this->getNextNumRef($soc);
1997 } else {
1998 $num = $this->ref;
1999 }
2000 $this->newref = dol_sanitizeFileName($num);
2001
2002 $sql = "UPDATE ".MAIN_DB_PREFIX."propal";
2003 $sql .= " SET ref = '".$this->db->escape($num)."',";
2004 $sql .= " fk_statut = ".self::STATUS_VALIDATED.", date_valid='".$this->db->idate($now)."', fk_user_valid=".((int) $user->id);
2005 $sql .= " WHERE rowid = ".((int) $this->id)." AND fk_statut = ".self::STATUS_DRAFT;
2006
2007 dol_syslog(get_class($this)."::valid", LOG_DEBUG);
2008 $resql = $this->db->query($sql);
2009 if (!$resql) {
2010 dol_print_error($this->db);
2011 $error++;
2012 }
2013
2014 // Trigger calls
2015 if (!$error && !$notrigger) {
2016 // Call trigger
2017 $result = $this->call_trigger('PROPAL_VALIDATE', $user);
2018 if ($result < 0) {
2019 $error++;
2020 }
2021 // End call triggers
2022 }
2023
2024 if (!$error) {
2025 $this->oldref = $this->ref;
2026
2027 // Rename directory if dir was a temporary ref
2028 if (preg_match('/^[\‍(]?PROV/i', $this->ref)) {
2029 // Now we rename also files into index
2030 $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)."'";
2031 $sql .= " WHERE filename LIKE '".$this->db->escape($this->ref)."%' AND filepath = 'propale/".$this->db->escape($this->ref)."' and entity = ".((int) $conf->entity);
2032 $resql = $this->db->query($sql);
2033 if (!$resql) {
2034 $error++;
2035 $this->error = $this->db->lasterror();
2036 }
2037 $sql = 'UPDATE '.MAIN_DB_PREFIX."ecm_files set filepath = 'propale/".$this->db->escape($this->newref)."'";
2038 $sql .= " WHERE filepath = 'propale/".$this->db->escape($this->ref)."' and entity = ".$conf->entity;
2039 $resql = $this->db->query($sql);
2040 if (!$resql) {
2041 $error++;
2042 $this->error = $this->db->lasterror();
2043 }
2044
2045 // We rename directory ($this->ref = old ref, $num = new ref) in order not to lose the attachments
2046 $oldref = dol_sanitizeFileName($this->ref);
2047 $newref = dol_sanitizeFileName($num);
2048 $dirsource = $conf->propal->multidir_output[$this->entity].'/'.$oldref;
2049 $dirdest = $conf->propal->multidir_output[$this->entity].'/'.$newref;
2050 if (!$error && file_exists($dirsource)) {
2051 dol_syslog(get_class($this)."::validate rename dir ".$dirsource." into ".$dirdest);
2052 if (@rename($dirsource, $dirdest)) {
2053 dol_syslog("Rename ok");
2054 // Rename docs starting with $oldref with $newref
2055 $listoffiles = dol_dir_list($dirdest, 'files', 1, '^'.preg_quote($oldref, '/'));
2056 foreach ($listoffiles as $fileentry) {
2057 $dirsource = $fileentry['name'];
2058 $dirdest = preg_replace('/^'.preg_quote($oldref, '/').'/', $newref, $dirsource);
2059 $dirsource = $fileentry['path'].'/'.$dirsource;
2060 $dirdest = $fileentry['path'].'/'.$dirdest;
2061 @rename($dirsource, $dirdest);
2062 }
2063 }
2064 }
2065 }
2066
2067 $this->ref = $num;
2069 $this->user_validation_id = $user->id;
2070 $this->datev = $now;
2071 $this->date_validation = $now;
2072
2073 $this->db->commit();
2074 return 1;
2075 } else {
2076 $this->db->rollback();
2077 return -1;
2078 }
2079 }
2080
2081
2082 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2091 public function set_date($user, $date, $notrigger = 0)
2092 {
2093 // phpcs:enable
2094 if (empty($date)) {
2095 $this->error = 'ErrorBadParameter';
2096 dol_syslog(get_class($this)."::set_date ".$this->error, LOG_ERR);
2097 return -1;
2098 }
2099
2100 if ($user->hasRight('propal', 'creer')) {
2101 $error = 0;
2102
2103 $this->db->begin();
2104
2105 $sql = "UPDATE ".MAIN_DB_PREFIX."propal SET datep = '".$this->db->idate($date)."'";
2106 $sql .= " WHERE rowid = ".((int) $this->id);
2107
2108 dol_syslog(__METHOD__, LOG_DEBUG);
2109 $resql = $this->db->query($sql);
2110 if (!$resql) {
2111 $this->errors[] = $this->db->error();
2112 $error++;
2113 }
2114
2115 if (!$error) {
2116 $this->oldcopy = clone $this;
2117 $this->date = $date;
2118 $this->datep = $date; // deprecated
2119 }
2120
2121 if (!$notrigger && empty($error)) {
2122 // Call trigger
2123 $result = $this->call_trigger('PROPAL_MODIFY', $user);
2124 if ($result < 0) {
2125 $error++;
2126 }
2127 // End call triggers
2128 }
2129
2130 if (!$error) {
2131 $this->db->commit();
2132 return 1;
2133 } else {
2134 foreach ($this->errors as $errmsg) {
2135 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2136 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2137 }
2138 $this->db->rollback();
2139 return -1 * $error;
2140 }
2141 }
2142
2143 return -1;
2144 }
2145
2146 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2155 public function set_echeance($user, $date_end_validity, $notrigger = 0)
2156 {
2157 // phpcs:enable
2158 if ($user->hasRight('propal', 'creer')) {
2159 $error = 0;
2160
2161 $this->db->begin();
2162
2163 $sql = "UPDATE ".MAIN_DB_PREFIX."propal SET fin_validite = ".($date_end_validity != '' ? "'".$this->db->idate($date_end_validity)."'" : 'null');
2164 $sql .= " WHERE rowid = ".((int) $this->id);
2165
2166 dol_syslog(__METHOD__, LOG_DEBUG);
2167
2168 $resql = $this->db->query($sql);
2169 if (!$resql) {
2170 $this->errors[] = $this->db->error();
2171 $error++;
2172 }
2173
2174
2175 if (!$error) {
2176 $this->oldcopy = clone $this;
2177 $this->fin_validite = $date_end_validity;
2178 }
2179
2180 if (!$notrigger && empty($error)) {
2181 // Call trigger
2182 $result = $this->call_trigger('PROPAL_MODIFY', $user);
2183 if ($result < 0) {
2184 $error++;
2185 }
2186 // End call triggers
2187 }
2188
2189 if (!$error) {
2190 $this->db->commit();
2191 return 1;
2192 } else {
2193 foreach ($this->errors as $errmsg) {
2194 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2195 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2196 }
2197 $this->db->rollback();
2198 return -1 * $error;
2199 }
2200 }
2201
2202 return -1;
2203 }
2204
2205 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2215 public function set_date_livraison($user, $delivery_date, $notrigger = 0)
2216 {
2217 // phpcs:enable
2218 return $this->setDeliveryDate($user, $delivery_date, $notrigger);
2219 }
2220
2229 public function setDeliveryDate($user, $delivery_date, $notrigger = 0)
2230 {
2231 if ($user->hasRight('propal', 'creer')) {
2232 $error = 0;
2233
2234 $this->db->begin();
2235
2236 $sql = "UPDATE ".MAIN_DB_PREFIX."propal ";
2237 $sql .= " SET date_livraison = ".($delivery_date != '' ? "'".$this->db->idate($delivery_date)."'" : 'null');
2238 $sql .= " WHERE rowid = ".((int) $this->id);
2239
2240 dol_syslog(__METHOD__, LOG_DEBUG);
2241 $resql = $this->db->query($sql);
2242 if (!$resql) {
2243 $this->errors[] = $this->db->error();
2244 $error++;
2245 }
2246
2247 if (!$error) {
2248 $this->oldcopy = clone $this;
2249 $this->delivery_date = $delivery_date;
2250 }
2251
2252 if (!$notrigger && empty($error)) {
2253 // Call trigger
2254 $result = $this->call_trigger('PROPAL_MODIFY', $user);
2255 if ($result < 0) {
2256 $error++;
2257 }
2258 // End call triggers
2259 }
2260
2261 if (!$error) {
2262 $this->db->commit();
2263 return 1;
2264 } else {
2265 foreach ($this->errors as $errmsg) {
2266 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2267 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2268 }
2269 $this->db->rollback();
2270 return -1 * $error;
2271 }
2272 }
2273
2274 return -1;
2275 }
2276
2277 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2286 public function set_availability($user, $id, $notrigger = 0)
2287 {
2288 // phpcs:enable
2289 if ($user->hasRight('propal', 'creer') && $this->statut >= self::STATUS_DRAFT) {
2290 $error = 0;
2291
2292 $this->db->begin();
2293
2294 $sql = "UPDATE ".MAIN_DB_PREFIX."propal";
2295 $sql .= " SET fk_availability = ".((int) $id);
2296 $sql .= " WHERE rowid = ".((int) $this->id);
2297
2298 dol_syslog(__METHOD__.' availability('.$id.')', LOG_DEBUG);
2299 $resql = $this->db->query($sql);
2300 if (!$resql) {
2301 $this->errors[] = $this->db->error();
2302 $error++;
2303 }
2304
2305 if (!$error) {
2306 $this->oldcopy = clone $this;
2307 $this->fk_availability = $id;
2308 $this->availability_id = $id;
2309 }
2310
2311 if (!$notrigger && empty($error)) {
2312 // Call trigger
2313 $result = $this->call_trigger('PROPAL_MODIFY', $user);
2314 if ($result < 0) {
2315 $error++;
2316 }
2317 // End call triggers
2318 }
2319
2320 if (!$error) {
2321 $this->db->commit();
2322 return 1;
2323 } else {
2324 foreach ($this->errors as $errmsg) {
2325 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2326 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2327 }
2328 $this->db->rollback();
2329 return -1 * $error;
2330 }
2331 } else {
2332 $error_str = 'Propal status do not meet requirement '.$this->statut;
2333 dol_syslog(__METHOD__.$error_str, LOG_ERR);
2334 $this->error = $error_str;
2335 $this->errors[] = $this->error;
2336 return -2;
2337 }
2338 }
2339
2340 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2349 public function set_demand_reason($user, $id, $notrigger = 0)
2350 {
2351 // phpcs:enable
2352 if ($user->hasRight('propal', 'creer') && $this->statut >= self::STATUS_DRAFT) {
2353 $error = 0;
2354
2355 $this->db->begin();
2356
2357 $sql = "UPDATE ".MAIN_DB_PREFIX."propal ";
2358 $sql .= " SET fk_input_reason = ".((int) $id);
2359 $sql .= " WHERE rowid = ".((int) $this->id);
2360
2361 dol_syslog(__METHOD__, LOG_DEBUG);
2362 $resql = $this->db->query($sql);
2363 if (!$resql) {
2364 $this->errors[] = $this->db->error();
2365 $error++;
2366 }
2367
2368
2369 if (!$error) {
2370 $this->oldcopy = clone $this;
2371
2372 $this->demand_reason_id = $id;
2373 }
2374
2375
2376 if (!$notrigger && empty($error)) {
2377 // Call trigger
2378 $result = $this->call_trigger('PROPAL_MODIFY', $user);
2379 if ($result < 0) {
2380 $error++;
2381 }
2382 // End call triggers
2383 }
2384
2385 if (!$error) {
2386 $this->db->commit();
2387 return 1;
2388 } else {
2389 foreach ($this->errors as $errmsg) {
2390 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2391 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2392 }
2393 $this->db->rollback();
2394 return -1 * $error;
2395 }
2396 } else {
2397 $error_str = 'Propal status do not meet requirement '.$this->statut;
2398 dol_syslog(__METHOD__.$error_str, LOG_ERR);
2399 $this->error = $error_str;
2400 $this->errors[] = $this->error;
2401 return -2;
2402 }
2403 }
2404
2405 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2414 public function set_ref_client($user, $ref_client, $notrigger = 0)
2415 {
2416 // phpcs:enable
2417 if ($user->hasRight('propal', 'creer')) {
2418 $error = 0;
2419
2420 $this->db->begin();
2421
2422 $sql = "UPDATE ".MAIN_DB_PREFIX."propal SET ref_client = ".(empty($ref_client) ? 'NULL' : "'".$this->db->escape($ref_client)."'");
2423 $sql .= " WHERE rowid = ".((int) $this->id);
2424
2425 dol_syslog(__METHOD__.' $this->id='.$this->id.', ref_client='.$ref_client, LOG_DEBUG);
2426 $resql = $this->db->query($sql);
2427 if (!$resql) {
2428 $this->errors[] = $this->db->error();
2429 $error++;
2430 }
2431
2432 if (!$error) {
2433 $this->oldcopy = clone $this;
2434 $this->ref_client = $ref_client;
2435 }
2436
2437 if (!$notrigger && empty($error)) {
2438 // Call trigger
2439 $result = $this->call_trigger('PROPAL_MODIFY', $user);
2440 if ($result < 0) {
2441 $error++;
2442 }
2443 // End call triggers
2444 }
2445
2446 if (!$error) {
2447 $this->db->commit();
2448 return 1;
2449 } else {
2450 foreach ($this->errors as $errmsg) {
2451 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2452 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2453 }
2454 $this->db->rollback();
2455 return -1 * $error;
2456 }
2457 }
2458
2459 return -1;
2460 }
2461
2462
2472 public function reopen($user, $status, $note = '', $notrigger = 0)
2473 {
2474 $error = 0;
2475
2476 $sql = "UPDATE ".MAIN_DB_PREFIX."propal";
2477 $sql .= " SET fk_statut = ".((int) $status).",";
2478 if (!empty($note)) {
2479 $sql .= " note_private = '".$this->db->escape($note)."',";
2480 }
2481 $sql .= " date_cloture=NULL, fk_user_cloture=NULL";
2482 $sql .= " WHERE rowid = ".((int) $this->id);
2483
2484 $this->db->begin();
2485
2486 dol_syslog(get_class($this)."::reopen", LOG_DEBUG);
2487 $resql = $this->db->query($sql);
2488 if (!$resql) {
2489 $error++;
2490 $this->errors[] = "Error ".$this->db->lasterror();
2491 }
2492 if (!$error) {
2493 if (!$notrigger) {
2494 // Call trigger
2495 $result = $this->call_trigger('PROPAL_REOPEN', $user);
2496 if ($result < 0) {
2497 $error++;
2498 }
2499 // End call triggers
2500 }
2501 }
2502
2503 // Commit or rollback
2504 if ($error) {
2505 if (!empty($this->errors)) {
2506 foreach ($this->errors as $errmsg) {
2507 dol_syslog(get_class($this)."::update ".$errmsg, LOG_ERR);
2508 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2509 }
2510 }
2511 $this->db->rollback();
2512 return -1 * $error;
2513 } else {
2514 $this->statut = $status;
2515 $this->status = $status;
2516
2517 $this->db->commit();
2518 return 1;
2519 }
2520 }
2521
2531 public function closeProposal($user, $status, $note = '', $notrigger = 0)
2532 {
2533 global $langs,$conf;
2534
2535 $error = 0;
2536 $now = dol_now();
2537
2538 $this->db->begin();
2539
2540 $newprivatenote = dol_concatdesc($this->note_private, $note);
2541
2542 if (!getDolGlobalString('PROPALE_KEEP_OLD_SIGNATURE_INFO')) {
2543 $date_signature = $now;
2544 $fk_user_signature = $user->id;
2545 } else {
2546 $this->info($this->id);
2547 if (!isset($this->date_signature) || $this->date_signature == '') {
2548 $date_signature = $now;
2549 $fk_user_signature = $user->id;
2550 } else {
2551 $date_signature = $this->date_signature;
2552 $fk_user_signature = $this->user_signature->id;
2553 }
2554 }
2555
2556 $sql = "UPDATE ".MAIN_DB_PREFIX."propal";
2557 $sql .= " SET fk_statut = ".((int) $status).", note_private = '".$this->db->escape($newprivatenote)."'";
2558 if ($status == self::STATUS_SIGNED) {
2559 $sql .= ", date_signature='".$this->db->idate($now)."', fk_user_signature = ".($fk_user_signature);
2560 }
2561 $sql .= " WHERE rowid = ".((int) $this->id);
2562
2563 $resql = $this->db->query($sql);
2564 if ($resql) {
2565 // Status self::STATUS_REFUSED by default
2566 $modelpdf = getDolGlobalString('PROPALE_ADDON_PDF_ODT_CLOSED', $this->model_pdf);
2567 $trigger_name = 'PROPAL_CLOSE_REFUSED'; // used later in call_trigger()
2568
2569 if ($status == self::STATUS_SIGNED) { // Status self::STATUS_SIGNED
2570 $trigger_name = 'PROPAL_CLOSE_SIGNED'; // used later in call_trigger()
2571 $modelpdf = getDolGlobalString('PROPALE_ADDON_PDF_ODT_TOBILL') ? $conf->global->PROPALE_ADDON_PDF_ODT_TOBILL : $this->model_pdf;
2572
2573 // The connected company is classified as a client
2574 $soc = new Societe($this->db);
2575 $soc->id = $this->socid;
2576 $result = $soc->setAsCustomer();
2577
2578 if ($result < 0) {
2579 $this->error = $this->db->lasterror();
2580 $this->db->rollback();
2581 return -2;
2582 }
2583 }
2584
2585 if (!getDolGlobalString('MAIN_DISABLE_PDF_AUTOUPDATE') && !getDolGlobalInt('PROPAL_DISABLE_AUTOUPDATE_ON_CLOSE')) {
2586 // Define output language
2587 $outputlangs = $langs;
2588 if (getDolGlobalInt('MAIN_MULTILANGS')) {
2589 $outputlangs = new Translate("", $conf);
2590 $newlang = (GETPOST('lang_id', 'aZ09') ? GETPOST('lang_id', 'aZ09') : $this->thirdparty->default_lang);
2591 $outputlangs->setDefaultLang($newlang);
2592 }
2593
2594 // PDF
2595 $hidedetails = (getDolGlobalString('MAIN_GENERATE_DOCUMENTS_HIDE_DETAILS') ? 1 : 0);
2596 $hidedesc = (getDolGlobalString('MAIN_GENERATE_DOCUMENTS_HIDE_DESC') ? 1 : 0);
2597 $hideref = (getDolGlobalString('MAIN_GENERATE_DOCUMENTS_HIDE_REF') ? 1 : 0);
2598
2599 //$ret=$object->fetch($id); // Reload to get new records
2600 $this->generateDocument($modelpdf, $outputlangs, $hidedetails, $hidedesc, $hideref);
2601 }
2602
2603 if (!$error) {
2604 $this->oldcopy = clone $this;
2605 $this->statut = $status;
2606 $this->status = $status;
2607 $this->date_signature = $date_signature;
2608 $this->note_private = $newprivatenote;
2609 }
2610
2611 if (!$notrigger && empty($error)) {
2612 // Call trigger
2613 $result = $this->call_trigger($trigger_name, $user);
2614 if ($result < 0) {
2615 $error++;
2616 }
2617 // End call triggers
2618 }
2619
2620 if (!$error) {
2621 $this->db->commit();
2622 return 1;
2623 } else {
2624 $this->statut = $this->oldcopy->statut;
2625 $this->status = $this->oldcopy->statut;
2626 $this->date_signature = $this->oldcopy->date_signature;
2627 $this->note_private = $this->oldcopy->note_private;
2628
2629 $this->db->rollback();
2630 return -1;
2631 }
2632 } else {
2633 $this->error = $this->db->lasterror();
2634 $this->db->rollback();
2635 return -1;
2636 }
2637 }
2638
2647 public function classifyBilled(User $user, $notrigger = 0, $note = '')
2648 {
2649 global $conf, $langs;
2650
2651 $error = 0;
2652
2653 $now = dol_now();
2654 $num = 0;
2655
2656 $triggerName = 'PROPAL_CLASSIFY_BILLED';
2657
2658 $this->db->begin();
2659
2660 $newprivatenote = dol_concatdesc($this->note_private, $note);
2661
2662 $sql = 'UPDATE '.MAIN_DB_PREFIX.'propal SET fk_statut = '.self::STATUS_BILLED.", ";
2663 $sql .= " note_private = '".$this->db->escape($newprivatenote)."', date_cloture='".$this->db->idate($now)."', fk_user_cloture=".((int) $user->id);
2664 $sql .= ' WHERE rowid = '.((int) $this->id).' AND fk_statut = '.((int) self::STATUS_SIGNED);
2665
2666 dol_syslog(__METHOD__, LOG_DEBUG);
2667 $resql = $this->db->query($sql);
2668 if (!$resql) {
2669 $this->errors[] = $this->db->error();
2670 $error++;
2671 } else {
2672 $num = $this->db->affected_rows($resql);
2673 }
2674
2675 if (!$error) {
2676 $modelpdf = getDolGlobalString('PROPALE_ADDON_PDF_ODT_CLOSED', $this->model_pdf);
2677
2678 if (!getDolGlobalString('MAIN_DISABLE_PDF_AUTOUPDATE')) {
2679 // Define output language
2680 $outputlangs = $langs;
2681 if (getDolGlobalInt('MAIN_MULTILANGS')) {
2682 $outputlangs = new Translate("", $conf);
2683 $newlang = (GETPOST('lang_id', 'aZ09') ? GETPOST('lang_id', 'aZ09') : $this->thirdparty->default_lang);
2684 $outputlangs->setDefaultLang($newlang);
2685 }
2686
2687 // PDF
2688 $hidedetails = (getDolGlobalString('MAIN_GENERATE_DOCUMENTS_HIDE_DETAILS') ? 1 : 0);
2689 $hidedesc = (getDolGlobalString('MAIN_GENERATE_DOCUMENTS_HIDE_DESC') ? 1 : 0);
2690 $hideref = (getDolGlobalString('MAIN_GENERATE_DOCUMENTS_HIDE_REF') ? 1 : 0);
2691
2692 //$ret=$object->fetch($id); // Reload to get new records
2693 $this->generateDocument($modelpdf, $outputlangs, $hidedetails, $hidedesc, $hideref);
2694 }
2695
2696 $this->oldcopy = clone $this;
2697 $this->statut = self::STATUS_BILLED;
2698 $this->date_cloture = $now;
2699 $this->note_private = $newprivatenote;
2700 }
2701
2702 if (!$notrigger && empty($error)) {
2703 // Call trigger
2704 $result = $this->call_trigger($triggerName, $user);
2705 if ($result < 0) {
2706 $error++;
2707 }
2708 // End call triggers
2709 }
2710
2711 if (!$error) {
2712 $this->db->commit();
2713 return $num;
2714 } else {
2715 foreach ($this->errors as $errmsg) {
2716 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2717 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2718 }
2719 $this->db->rollback();
2720 return -1 * $error;
2721 }
2722 }
2723
2730 public function setCancel(User $user)
2731 {
2732 $error = 0;
2733
2734 $this->db->begin();
2735
2736 $sql = "UPDATE ". MAIN_DB_PREFIX . "propal";
2737 $sql .= " SET fk_statut = " . self::STATUS_CANCELED . ",";
2738 $sql .= " fk_user_modif = " . ((int) $user->id);
2739 $sql .= " WHERE rowid = " . ((int) $this->id);
2740
2741 dol_syslog(get_class($this)."::cancel", LOG_DEBUG);
2742 if ($this->db->query($sql)) {
2743 if (!$error) {
2744 // Call trigger
2745 $result = $this->call_trigger('PROPAL_CANCEL', $user);
2746 if ($result < 0) {
2747 $error++;
2748 }
2749 // End call triggers
2750 }
2751
2752 if (!$error) {
2754 $this->db->commit();
2755 return 1;
2756 } else {
2757 foreach ($this->errors as $errmsg) {
2758 dol_syslog(get_class($this)."::cancel ".$errmsg, LOG_ERR);
2759 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2760 }
2761 $this->db->rollback();
2762 return -1;
2763 }
2764 } else {
2765 $this->error = $this->db->error();
2766 $this->db->rollback();
2767 return -1;
2768 }
2769 }
2770
2771 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2779 public function setDraft($user, $notrigger = 0)
2780 {
2781 // phpcs:enable
2782 $error = 0;
2783
2784 // Protection
2785 if ($this->statut <= self::STATUS_DRAFT) {
2786 return 0;
2787 }
2788
2789 dol_syslog(get_class($this)."::setDraft", LOG_DEBUG);
2790
2791 $this->db->begin();
2792
2793 $sql = "UPDATE ".MAIN_DB_PREFIX."propal";
2794 $sql .= " SET fk_statut = ".self::STATUS_DRAFT;
2795 $sql .= ", online_sign_ip = NULL , online_sign_name = NULL";
2796 $sql .= " WHERE rowid = ".((int) $this->id);
2797
2798 $resql = $this->db->query($sql);
2799 if (!$resql) {
2800 $this->errors[] = $this->db->error();
2801 $error++;
2802 }
2803
2804 if (!$error) {
2805 $this->oldcopy = clone $this;
2806 }
2807
2808 if (!$notrigger && empty($error)) {
2809 // Call trigger
2810 $result = $this->call_trigger('PROPAL_MODIFY', $user);
2811 if ($result < 0) {
2812 $error++;
2813 }
2814 // End call triggers
2815 }
2816
2817 if (!$error) {
2818 $this->statut = self::STATUS_DRAFT;
2819 $this->status = self::STATUS_DRAFT;
2820
2821 $this->db->commit();
2822 return 1;
2823 } else {
2824 foreach ($this->errors as $errmsg) {
2825 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2826 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2827 }
2828 $this->db->rollback();
2829 return -1 * $error;
2830 }
2831 }
2832
2833
2834 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2848 public function liste_array($shortlist = 0, $draft = 0, $notcurrentuser = 0, $socid = 0, $limit = 0, $offset = 0, $sortfield = 'p.datep', $sortorder = 'DESC')
2849 {
2850 // phpcs:enable
2851 global $user;
2852
2853 $ga = array();
2854
2855 $sql = "SELECT s.rowid, s.nom as name, s.client,";
2856 $sql .= " p.rowid as propalid, p.fk_statut, p.total_ht, p.ref, p.remise, ";
2857 $sql .= " p.datep as dp, p.fin_validite as datelimite";
2858 $sql .= " FROM ".MAIN_DB_PREFIX."societe as s, ".MAIN_DB_PREFIX."propal as p, ".MAIN_DB_PREFIX."c_propalst as c";
2859 $sql .= " WHERE p.entity IN (".getEntity('propal').")";
2860 $sql .= " AND p.fk_soc = s.rowid";
2861 $sql .= " AND p.fk_statut = c.id";
2862
2863 // If the internal user must only see his customers, force searching by him
2864 $search_sale = 0;
2865 if (empty($user->socid) && !$user->hasRight('societe', 'client', 'voir')) {
2866 $search_sale = $user->id;
2867 }
2868 // Search on sale representative
2869 if ($search_sale && $search_sale != '-1') {
2870 if ($search_sale == -2) {
2871 $sql .= " AND NOT EXISTS (SELECT sc.fk_soc FROM ".MAIN_DB_PREFIX."societe_commerciaux as sc WHERE sc.fk_soc = p.fk_soc)";
2872 } elseif ($search_sale > 0) {
2873 $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).")";
2874 }
2875 }
2876 // Search on socid
2877 if ($socid) {
2878 $sql .= " AND p.fk_soc = ".((int) $socid);
2879 }
2880 if ($draft) {
2881 $sql .= " AND p.fk_statut = ".((int) self::STATUS_DRAFT);
2882 }
2883 if ($notcurrentuser > 0) {
2884 $sql .= " AND p.fk_user_author <> ".((int) $user->id);
2885 }
2886 $sql .= $this->db->order($sortfield, $sortorder);
2887 $sql .= $this->db->plimit($limit, $offset);
2888
2889 $result = $this->db->query($sql);
2890 if ($result) {
2891 $num = $this->db->num_rows($result);
2892 if ($num) {
2893 $i = 0;
2894 while ($i < $num) {
2895 $obj = $this->db->fetch_object($result);
2896
2897 if ($shortlist == 1) {
2898 $ga[$obj->propalid] = $obj->ref;
2899 } elseif ($shortlist == 2) {
2900 $ga[$obj->propalid] = $obj->ref.' ('.$obj->name.')';
2901 } else {
2902 $ga[$i]['id'] = $obj->propalid;
2903 $ga[$i]['ref'] = $obj->ref;
2904 $ga[$i]['name'] = $obj->name;
2905 }
2906
2907 $i++;
2908 }
2909 }
2910 return $ga;
2911 } else {
2912 dol_print_error($this->db);
2913 return -1;
2914 }
2915 }
2916
2922 public function getInvoiceArrayList()
2923 {
2924 return $this->InvoiceArrayList($this->id);
2925 }
2926
2927 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2934 public function InvoiceArrayList($id)
2935 {
2936 // phpcs:enable
2937 $ga = array();
2938 $linkedInvoices = array();
2939
2940 $this->fetchObjectLinked($id, $this->element);
2941 foreach ($this->linkedObjectsIds as $objecttype => $objectid) {
2942 // Nouveau système du common object renvoi des rowid et non un id linéaire de 1 à n
2943 // On parcourt donc une liste d'objets en tant qu'objet unique
2944 foreach ($objectid as $key => $object) {
2945 // Cas des factures liees directement
2946 if ($objecttype == 'facture') {
2947 $linkedInvoices[] = $object;
2948 } else {
2949 // Cas des factures liees par un autre object (ex: commande)
2950 $this->fetchObjectLinked($object, $objecttype);
2951 foreach ($this->linkedObjectsIds as $subobjecttype => $subobjectid) {
2952 foreach ($subobjectid as $subkey => $subobject) {
2953 if ($subobjecttype == 'facture') {
2954 $linkedInvoices[] = $subobject;
2955 }
2956 }
2957 }
2958 }
2959 }
2960 }
2961
2962 if (count($linkedInvoices) > 0) {
2963 $sql = "SELECT rowid as facid, ref, total_ht as total, datef as df, fk_user_author, fk_statut, paye";
2964 $sql .= " FROM ".MAIN_DB_PREFIX."facture";
2965 $sql .= " WHERE rowid IN (".$this->db->sanitize(implode(',', $linkedInvoices)).")";
2966
2967 dol_syslog(get_class($this)."::InvoiceArrayList", LOG_DEBUG);
2968 $resql = $this->db->query($sql);
2969
2970 if ($resql) {
2971 $tab_sqlobj = array();
2972 $nump = $this->db->num_rows($resql);
2973 for ($i = 0; $i < $nump; $i++) {
2974 $sqlobj = $this->db->fetch_object($resql);
2975 $tab_sqlobj[] = $sqlobj;
2976 }
2977 $this->db->free($resql);
2978
2979 $nump = count($tab_sqlobj);
2980
2981 if ($nump) {
2982 $i = 0;
2983 while ($i < $nump) {
2984 $obj = array_shift($tab_sqlobj);
2985
2986 $ga[$i] = $obj;
2987
2988 $i++;
2989 }
2990 }
2991 return $ga;
2992 } else {
2993 return -1;
2994 }
2995 } else {
2996 return $ga;
2997 }
2998 }
2999
3007 public function delete($user, $notrigger = 0)
3008 {
3009 global $conf;
3010 require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
3011
3012 $error = 0;
3013
3014 $this->db->begin();
3015
3016 if (!$notrigger) {
3017 // Call trigger
3018 $result = $this->call_trigger('PROPAL_DELETE', $user);
3019 if ($result < 0) {
3020 $error++;
3021 }
3022 // End call triggers
3023 }
3024
3025 // Delete extrafields of lines and lines
3026 if (!$error && !empty($this->table_element_line)) {
3027 $tabletodelete = $this->table_element_line;
3028 $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).")";
3029 $sql = "DELETE FROM ".MAIN_DB_PREFIX.$tabletodelete." WHERE ".$this->fk_element." = ".((int) $this->id);
3030 if (!$this->db->query($sqlef) || !$this->db->query($sql)) {
3031 $error++;
3032 $this->error = $this->db->lasterror();
3033 $this->errors[] = $this->error;
3034 dol_syslog(get_class($this)."::delete error ".$this->error, LOG_ERR);
3035 }
3036 }
3037
3038 if (!$error) {
3039 // Delete linked object
3040 $res = $this->deleteObjectLinked();
3041 if ($res < 0) {
3042 $error++;
3043 }
3044 }
3045
3046 if (!$error) {
3047 // Delete linked contacts
3048 $res = $this->delete_linked_contact();
3049 if ($res < 0) {
3050 $error++;
3051 }
3052 }
3053
3054 // Removed extrafields of object
3055 if (!$error) {
3056 $result = $this->deleteExtraFields();
3057 if ($result < 0) {
3058 $error++;
3059 dol_syslog(get_class($this)."::delete error ".$this->error, LOG_ERR);
3060 }
3061 }
3062
3063 // Delete main record
3064 if (!$error) {
3065 $sql = "DELETE FROM ".MAIN_DB_PREFIX.$this->table_element." WHERE rowid = ".((int) $this->id);
3066 $res = $this->db->query($sql);
3067 if (!$res) {
3068 $error++;
3069 $this->error = $this->db->lasterror();
3070 $this->errors[] = $this->error;
3071 dol_syslog(get_class($this)."::delete error ".$this->error, LOG_ERR);
3072 }
3073 }
3074
3075 // Delete record into ECM index and physically
3076 if (!$error) {
3077 $res = $this->deleteEcmFiles(0); // Deleting files physically is done later with the dol_delete_dir_recursive
3078 $res = $this->deleteEcmFiles(1); // Deleting files physically is done later with the dol_delete_dir_recursive
3079 if (!$res) {
3080 $error++;
3081 }
3082 }
3083
3084 if (!$error) {
3085 // We remove directory
3086 $ref = dol_sanitizeFileName($this->ref);
3087 if ($conf->propal->multidir_output[$this->entity] && !empty($this->ref)) {
3088 $dir = $conf->propal->multidir_output[$this->entity]."/".$ref;
3089 $file = $dir."/".$ref.".pdf";
3090 if (file_exists($file)) {
3091 dol_delete_preview($this);
3092
3093 if (!dol_delete_file($file, 0, 0, 0, $this)) {
3094 $this->error = 'ErrorFailToDeleteFile';
3095 $this->errors[] = $this->error;
3096 $this->db->rollback();
3097 return 0;
3098 }
3099 }
3100 if (file_exists($dir)) {
3101 $res = @dol_delete_dir_recursive($dir); // delete files physically + into ecm tables
3102 if (!$res) {
3103 $this->error = 'ErrorFailToDeleteDir';
3104 $this->errors[] = $this->error;
3105 $this->db->rollback();
3106 return 0;
3107 }
3108 }
3109 }
3110 }
3111
3112 if (!$error) {
3113 dol_syslog(get_class($this)."::delete ".$this->id." by ".$user->id, LOG_DEBUG);
3114 $this->db->commit();
3115 return 1;
3116 } else {
3117 $this->db->rollback();
3118 return -1;
3119 }
3120 }
3121
3130 public function availability($availability_id, $notrigger = 0)
3131 {
3132 global $user;
3133
3134 if ($this->statut >= self::STATUS_DRAFT) {
3135 $error = 0;
3136
3137 $this->db->begin();
3138
3139 $sql = 'UPDATE '.MAIN_DB_PREFIX.'propal';
3140 $sql .= ' SET fk_availability = '.((int) $availability_id);
3141 $sql .= ' WHERE rowid='.((int) $this->id);
3142
3143 dol_syslog(__METHOD__.' availability('.$availability_id.')', LOG_DEBUG);
3144 $resql = $this->db->query($sql);
3145 if (!$resql) {
3146 $this->errors[] = $this->db->error();
3147 $error++;
3148 }
3149
3150 if (!$error) {
3151 $this->oldcopy = clone $this;
3152 $this->availability_id = $availability_id;
3153 }
3154
3155 if (!$notrigger && empty($error)) {
3156 // Call trigger
3157 $result = $this->call_trigger('PROPAL_MODIFY', $user);
3158 if ($result < 0) {
3159 $error++;
3160 }
3161 // End call triggers
3162 }
3163
3164 if (!$error) {
3165 $this->db->commit();
3166 return 1;
3167 } else {
3168 foreach ($this->errors as $errmsg) {
3169 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
3170 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
3171 }
3172 $this->db->rollback();
3173 return -1 * $error;
3174 }
3175 } else {
3176 $error_str = 'Propal status do not meet requirement '.$this->statut;
3177 dol_syslog(__METHOD__.$error_str, LOG_ERR);
3178 $this->error = $error_str;
3179 $this->errors[] = $this->error;
3180 return -2;
3181 }
3182 }
3183
3184 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3193 public function demand_reason($demand_reason_id, $notrigger = 0)
3194 {
3195 // phpcs:enable
3196 global $user;
3197
3198 if ($this->status >= self::STATUS_DRAFT) {
3199 $error = 0;
3200
3201 $this->db->begin();
3202
3203 $sql = 'UPDATE '.MAIN_DB_PREFIX.'propal';
3204 $sql .= ' SET fk_input_reason = '.((int) $demand_reason_id);
3205 $sql .= ' WHERE rowid='.((int) $this->id);
3206
3207 dol_syslog(__METHOD__.' demand_reason('.$demand_reason_id.')', LOG_DEBUG);
3208 $resql = $this->db->query($sql);
3209 if (!$resql) {
3210 $this->errors[] = $this->db->error();
3211 $error++;
3212 }
3213
3214 if (!$error) {
3215 $this->oldcopy = clone $this;
3216 $this->demand_reason_id = $demand_reason_id;
3217 }
3218
3219 if (!$notrigger && empty($error)) {
3220 // Call trigger
3221 $result = $this->call_trigger('PROPAL_MODIFY', $user);
3222 if ($result < 0) {
3223 $error++;
3224 }
3225 // End call triggers
3226 }
3227
3228 if (!$error) {
3229 $this->db->commit();
3230 return 1;
3231 } else {
3232 foreach ($this->errors as $errmsg) {
3233 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
3234 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
3235 }
3236 $this->db->rollback();
3237 return -1 * $error;
3238 }
3239 } else {
3240 $error_str = 'Propal status do not meet requirement '.$this->statut;
3241 dol_syslog(__METHOD__.$error_str, LOG_ERR);
3242 $this->error = $error_str;
3243 $this->errors[] = $this->error;
3244 return -2;
3245 }
3246 }
3247
3248
3255 public function info($id)
3256 {
3257 $sql = "SELECT c.rowid, ";
3258 $sql .= " c.datec, c.date_valid as datev, c.date_signature, c.date_cloture,";
3259 $sql .= " c.fk_user_author, c.fk_user_valid, c.fk_user_signature, c.fk_user_cloture";
3260 $sql .= " FROM ".MAIN_DB_PREFIX."propal as c";
3261 $sql .= " WHERE c.rowid = ".((int) $id);
3262
3263 $result = $this->db->query($sql);
3264
3265 if ($result) {
3266 if ($this->db->num_rows($result)) {
3267 $obj = $this->db->fetch_object($result);
3268
3269 $this->id = $obj->rowid;
3270
3271 $this->date_creation = $this->db->jdate($obj->datec);
3272 $this->date_validation = $this->db->jdate($obj->datev);
3273 $this->date_signature = $this->db->jdate($obj->date_signature);
3274 $this->date_cloture = $this->db->jdate($obj->date_cloture);
3275
3276 $this->user_creation_id = $obj->fk_user_author;
3277 $this->user_validation_id = $obj->fk_user_valid;
3278
3279 if ($obj->fk_user_signature) {
3280 $user_signature = new User($this->db);
3281 $user_signature->fetch($obj->fk_user_signature);
3282 $this->user_signature = $user_signature;
3283 }
3284
3285 $this->user_closing_id = $obj->fk_user_cloture;
3286 }
3287 $this->db->free($result);
3288 } else {
3289 dol_print_error($this->db);
3290 }
3291 }
3292
3293
3300 public function getLibStatut($mode = 0)
3301 {
3302 return $this->LibStatut($this->statut, $mode);
3303 }
3304
3305 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3313 public function LibStatut($status, $mode = 1)
3314 {
3315 // phpcs:enable
3316 global $hookmanager;
3317
3318 // Init/load array of translation of status
3319 if (empty($this->labelStatus) || empty($this->labelStatusShort)) {
3320 global $langs;
3321 $langs->load("propal");
3322 $this->labelStatus[-1] = $langs->transnoentitiesnoconv("PropalStatusCanceled");
3323 $this->labelStatus[0] = $langs->transnoentitiesnoconv("PropalStatusDraft");
3324 $this->labelStatus[1] = $langs->transnoentitiesnoconv("PropalStatusValidated");
3325 $this->labelStatus[2] = $langs->transnoentitiesnoconv("PropalStatusSigned");
3326 $this->labelStatus[3] = $langs->transnoentitiesnoconv("PropalStatusNotSigned");
3327 $this->labelStatus[4] = $langs->transnoentitiesnoconv("PropalStatusBilled");
3328 $this->labelStatusShort[-1] = $langs->transnoentitiesnoconv("PropalStatusCanceledShort");
3329 $this->labelStatusShort[0] = $langs->transnoentitiesnoconv("PropalStatusDraftShort");
3330 $this->labelStatusShort[1] = $langs->transnoentitiesnoconv("PropalStatusValidatedShort");
3331 $this->labelStatusShort[2] = $langs->transnoentitiesnoconv("PropalStatusSignedShort");
3332 $this->labelStatusShort[3] = $langs->transnoentitiesnoconv("PropalStatusNotSignedShort");
3333 $this->labelStatusShort[4] = $langs->transnoentitiesnoconv("PropalStatusBilledShort");
3334 }
3335
3336 $statusType = '';
3337 if ($status == self::STATUS_CANCELED) {
3338 $statusType = 'status9';
3339 } elseif ($status == self::STATUS_DRAFT) {
3340 $statusType = 'status0';
3341 } elseif ($status == self::STATUS_VALIDATED) {
3342 $statusType = 'status1';
3343 } elseif ($status == self::STATUS_SIGNED) {
3344 $statusType = 'status4';
3345 } elseif ($status == self::STATUS_NOTSIGNED) {
3346 $statusType = 'status9';
3347 } elseif ($status == self::STATUS_BILLED) {
3348 $statusType = 'status6';
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 (empty($user->socid) && !$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_option=late&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 (empty($user->socid) && !$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->status)) {
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_customer)) {
3677 $datas['refcustomer'] = '<br><b>'.$langs->trans('RefCustomer').':</b> '.$this->ref_customer;
3678 }
3679 if (!$nofetch) {
3680 $langs->load('project');
3681 if (is_null($this->project) || (is_object($this->project) && $this->project->isEmpty())) {
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 .= '<div class="info-box-icon bg-infobox-action">';
3939 $return .= img_picto('', $this->picto);
3940 $return .= '</div>';
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 $return .= '<br>';
3950 if (property_exists($this, 'thirdparty') && is_object($this->thirdparty)) {
3951 $return .= '<div class="info-box-ref tdoverflowmax150">'.$this->thirdparty->getNomUrl(1).'</div>';
3952 }
3953 if (property_exists($this, 'total_ht')) {
3954 $return .= '<span class="info-box-label amount" title="'.$langs->trans("AmountHT").'">'.price($this->total_ht).'</span>';
3955 }
3956 if (!empty($arraydata['authorlink'])) {
3957 $return .= ' &nbsp; <span class="info-box-label">'.$arraydata['authorlink'].'</span>';
3958 }
3959 if (method_exists($this, 'getLibStatut')) {
3960 $return .= '<br><div class="info-box-status">'.$this->getLibStatut(3).'</div>';
3961 }
3962 $return .= '</div>';
3963 $return .= '</div>';
3964 $return .= '</div>';
3965 return $return;
3966 }
3967}
3968
3973{
3977 public $element = 'propaldet';
3978
3982 public $table_element = 'propaldet';
3983
3987 public $parent_element = 'propal';
3988
3992 public $fk_parent_attribute = 'fk_propal';
3993
3994 public $oldline;
3995
3996 // From llx_propaldet
3997 public $fk_propal;
3998 public $fk_parent_line;
3999 public $desc; // Description ligne
4000 public $fk_product; // Id produit predefini
4011 public $product_type = Product::TYPE_PRODUCT;
4012
4013 public $qty;
4014
4015 public $tva_tx;
4016 public $vat_src_code;
4017
4022 public $subprice;
4023 public $remise_percent;
4024 public $fk_remise_except;
4025
4026 public $rang = 0;
4027
4028 public $fk_fournprice;
4029
4034 public $pa_ht;
4035 public $marge_tx;
4036 public $marque_tx;
4037
4044 public $special_code; // Tag for special lines (exclusive tags)
4045
4046 public $info_bits = 0; // Some other info:
4047 // Bit 0: 0 si TVA normal - 1 if TVA NPR
4048 // Bit 1: 0 ligne normal - 1 if line with fixed discount
4049
4050 public $total_ht; // Total HT de la ligne toute quantite et incluant la remise ligne
4051 public $total_tva; // Total TVA de la ligne toute quantite et incluant la remise ligne
4052 public $total_ttc; // Total TTC de la ligne toute quantite et incluant la remise ligne
4053
4058 public $remise;
4063 public $price;
4064
4065 // From llx_product
4070 public $ref;
4075 public $product_ref;
4080 public $libelle;
4085 public $label;
4090 public $product_label;
4095 public $product_desc;
4096
4101 public $product_tobatch;
4102
4107 public $product_barcode;
4108
4109 public $localtax1_tx; // Local tax 1
4110 public $localtax2_tx; // Local tax 2
4111 public $localtax1_type; // Local tax 1 type
4112 public $localtax2_type; // Local tax 2 type
4113 public $total_localtax1; // Line total local tax 1
4114 public $total_localtax2; // Line total local tax 2
4115
4116 public $date_start;
4117 public $date_end;
4118
4119 public $skip_update_total; // Skip update price total for special lines
4120
4121 // Multicurrency
4122 public $fk_multicurrency;
4123 public $multicurrency_code;
4124 public $multicurrency_subprice;
4125 public $multicurrency_total_ht;
4126 public $multicurrency_total_tva;
4127 public $multicurrency_total_ttc;
4128
4129
4135 public function __construct($db)
4136 {
4137 $this->db = $db;
4138 }
4139
4146 public function fetch($rowid)
4147 {
4148 $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,';
4149 $sql .= ' pd.remise, pd.remise_percent, pd.fk_remise_except, pd.subprice,';
4150 $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,';
4151 $sql .= ' pd.fk_unit,';
4152 $sql .= ' pd.localtax1_tx, pd.localtax2_tx, pd.total_localtax1, pd.total_localtax2,';
4153 $sql .= ' pd.fk_multicurrency, pd.multicurrency_code, pd.multicurrency_subprice, pd.multicurrency_total_ht, pd.multicurrency_total_tva, pd.multicurrency_total_ttc,';
4154 $sql .= ' p.ref as product_ref, p.label as product_label, p.description as product_desc,';
4155 $sql .= ' pd.date_start, pd.date_end, pd.product_type';
4156 $sql .= ' FROM '.MAIN_DB_PREFIX.'propaldet as pd';
4157 $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'product as p ON pd.fk_product = p.rowid';
4158 $sql .= ' WHERE pd.rowid = '.((int) $rowid);
4159
4160 $result = $this->db->query($sql);
4161 if ($result) {
4162 $objp = $this->db->fetch_object($result);
4163
4164 if ($objp) {
4165 $this->id = $objp->rowid;
4166 $this->rowid = $objp->rowid; // deprecated
4167 $this->fk_propal = $objp->fk_propal;
4168 $this->fk_parent_line = $objp->fk_parent_line;
4169 $this->label = $objp->custom_label;
4170 $this->desc = $objp->description;
4171 $this->qty = $objp->qty;
4172 $this->price = $objp->price; // deprecated
4173 $this->subprice = $objp->subprice;
4174 $this->vat_src_code = $objp->vat_src_code;
4175 $this->tva_tx = $objp->tva_tx;
4176 $this->remise = $objp->remise; // deprecated
4177 $this->remise_percent = $objp->remise_percent;
4178 $this->fk_remise_except = $objp->fk_remise_except;
4179 $this->fk_product = $objp->fk_product;
4180 $this->info_bits = $objp->info_bits;
4181
4182 $this->total_ht = $objp->total_ht;
4183 $this->total_tva = $objp->total_tva;
4184 $this->total_ttc = $objp->total_ttc;
4185
4186 $this->fk_fournprice = $objp->fk_fournprice;
4187
4188 $marginInfos = getMarginInfos($objp->subprice, $objp->remise_percent, $objp->tva_tx, $objp->localtax1_tx, $objp->localtax2_tx, $this->fk_fournprice, $objp->pa_ht);
4189 $this->pa_ht = $marginInfos[0];
4190 $this->marge_tx = $marginInfos[1];
4191 $this->marque_tx = $marginInfos[2];
4192
4193 $this->special_code = $objp->special_code;
4194 $this->product_type = $objp->product_type;
4195 $this->rang = $objp->rang;
4196
4197 $this->ref = $objp->product_ref; // deprecated
4198 $this->product_ref = $objp->product_ref;
4199 $this->libelle = $objp->product_label; // deprecated
4200 $this->product_label = $objp->product_label;
4201 $this->product_desc = $objp->product_desc;
4202 $this->fk_unit = $objp->fk_unit;
4203
4204 $this->date_start = $this->db->jdate($objp->date_start);
4205 $this->date_end = $this->db->jdate($objp->date_end);
4206
4207 // Multicurrency
4208 $this->fk_multicurrency = $objp->fk_multicurrency;
4209 $this->multicurrency_code = $objp->multicurrency_code;
4210 $this->multicurrency_subprice = $objp->multicurrency_subprice;
4211 $this->multicurrency_total_ht = $objp->multicurrency_total_ht;
4212 $this->multicurrency_total_tva = $objp->multicurrency_total_tva;
4213 $this->multicurrency_total_ttc = $objp->multicurrency_total_ttc;
4214
4215 $this->fetch_optionals();
4216
4217 $this->db->free($result);
4218
4219 return 1;
4220 } else {
4221 return 0;
4222 }
4223 } else {
4224 return -1;
4225 }
4226 }
4227
4234 public function insert($notrigger = 0)
4235 {
4236 global $conf, $user;
4237
4238 $error = 0;
4239
4240 dol_syslog(get_class($this)."::insert rang=".$this->rang);
4241
4242 $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'.
4243 $this->pa_ht = (float) $this->pa_ht; // convert to float after check if empty value
4244
4245 // Clean parameters
4246 if (empty($this->tva_tx)) {
4247 $this->tva_tx = 0;
4248 }
4249 if (empty($this->localtax1_tx)) {
4250 $this->localtax1_tx = 0;
4251 }
4252 if (empty($this->localtax2_tx)) {
4253 $this->localtax2_tx = 0;
4254 }
4255 if (empty($this->localtax1_type)) {
4256 $this->localtax1_type = 0;
4257 }
4258 if (empty($this->localtax2_type)) {
4259 $this->localtax2_type = 0;
4260 }
4261 if (empty($this->total_localtax1)) {
4262 $this->total_localtax1 = 0;
4263 }
4264 if (empty($this->total_localtax2)) {
4265 $this->total_localtax2 = 0;
4266 }
4267 if (empty($this->rang)) {
4268 $this->rang = 0;
4269 }
4270 if (empty($this->remise_percent) || !is_numeric($this->remise_percent)) {
4271 $this->remise_percent = 0;
4272 }
4273 if (empty($this->info_bits)) {
4274 $this->info_bits = 0;
4275 }
4276 if (empty($this->special_code)) {
4277 $this->special_code = 0;
4278 }
4279 if (empty($this->fk_parent_line)) {
4280 $this->fk_parent_line = 0;
4281 }
4282 if (empty($this->fk_fournprice)) {
4283 $this->fk_fournprice = 0;
4284 }
4285 if (!is_numeric($this->qty)) {
4286 $this->qty = 0;
4287 }
4288 if (empty($this->multicurrency_subprice)) {
4289 $this->multicurrency_subprice = 0;
4290 }
4291 if (empty($this->multicurrency_total_ht)) {
4292 $this->multicurrency_total_ht = 0;
4293 }
4294 if (empty($this->multicurrency_total_tva)) {
4295 $this->multicurrency_total_tva = 0;
4296 }
4297 if (empty($this->multicurrency_total_ttc)) {
4298 $this->multicurrency_total_ttc = 0;
4299 }
4300
4301 // if buy price not defined, define buyprice as configured in margin admin
4302 if ($this->pa_ht == 0 && $pa_ht_isemptystring) {
4303 if (($result = $this->defineBuyPrice($this->subprice, $this->remise_percent, $this->fk_product)) < 0) {
4304 return $result;
4305 } else {
4306 $this->pa_ht = $result;
4307 }
4308 }
4309
4310 // Check parameters
4311 if ($this->product_type < 0) {
4312 return -1;
4313 }
4314
4315 $this->db->begin();
4316
4317 // Insert line into database
4318 $sql = 'INSERT INTO '.MAIN_DB_PREFIX.'propaldet';
4319 $sql .= ' (fk_propal, fk_parent_line, label, description, fk_product, product_type,';
4320 $sql .= ' fk_remise_except, qty, vat_src_code, tva_tx, localtax1_tx, localtax2_tx, localtax1_type, localtax2_type,';
4321 $sql .= ' subprice, remise_percent, ';
4322 $sql .= ' info_bits, ';
4323 $sql .= ' total_ht, total_tva, total_localtax1, total_localtax2, total_ttc, fk_product_fournisseur_price, buy_price_ht, special_code, rang,';
4324 $sql .= ' fk_unit,';
4325 $sql .= ' date_start, date_end';
4326 $sql .= ', fk_multicurrency, multicurrency_code, multicurrency_subprice, multicurrency_total_ht, multicurrency_total_tva, multicurrency_total_ttc)';
4327 $sql .= " VALUES (".$this->fk_propal.",";
4328 $sql .= " ".($this->fk_parent_line > 0 ? "'".$this->db->escape($this->fk_parent_line)."'" : "null").",";
4329 $sql .= " ".(!empty($this->label) ? "'".$this->db->escape($this->label)."'" : "null").",";
4330 $sql .= " '".$this->db->escape($this->desc)."',";
4331 $sql .= " ".($this->fk_product ? "'".$this->db->escape($this->fk_product)."'" : "null").",";
4332 $sql .= " '".$this->db->escape($this->product_type)."',";
4333 $sql .= " ".($this->fk_remise_except ? "'".$this->db->escape($this->fk_remise_except)."'" : "null").",";
4334 $sql .= " ".price2num($this->qty, 'MS').",";
4335 $sql .= " ".(empty($this->vat_src_code) ? "''" : "'".$this->db->escape($this->vat_src_code)."'").",";
4336 $sql .= " ".price2num($this->tva_tx).",";
4337 $sql .= " ".price2num($this->localtax1_tx).",";
4338 $sql .= " ".price2num($this->localtax2_tx).",";
4339 $sql .= " '".$this->db->escape($this->localtax1_type)."',";
4340 $sql .= " '".$this->db->escape($this->localtax2_type)."',";
4341 $sql .= " ".(price2num($this->subprice) !== '' ? price2num($this->subprice, 'MU') : "null").",";
4342 $sql .= " ".price2num($this->remise_percent, 3).",";
4343 $sql .= " ".(isset($this->info_bits) ? ((int) $this->info_bits) : "null").",";
4344 $sql .= " ".price2num($this->total_ht, 'MT').",";
4345 $sql .= " ".price2num($this->total_tva, 'MT').",";
4346 $sql .= " ".price2num($this->total_localtax1, 'MT').",";
4347 $sql .= " ".price2num($this->total_localtax2, 'MT').",";
4348 $sql .= " ".price2num($this->total_ttc, 'MT').",";
4349 $sql .= " ".(!empty($this->fk_fournprice) ? "'".$this->db->escape($this->fk_fournprice)."'" : "null").",";
4350 $sql .= " ".(isset($this->pa_ht) ? "'".price2num($this->pa_ht)."'" : "null").",";
4351 $sql .= ' '.((int) $this->special_code).',';
4352 $sql .= ' '.((int) $this->rang).',';
4353 $sql .= ' '.(empty($this->fk_unit) ? 'NULL' : ((int) $this->fk_unit)).',';
4354 $sql .= " ".(!empty($this->date_start) ? "'".$this->db->idate($this->date_start)."'" : "null").',';
4355 $sql .= " ".(!empty($this->date_end) ? "'".$this->db->idate($this->date_end)."'" : "null");
4356 $sql .= ", ".($this->fk_multicurrency > 0 ? ((int) $this->fk_multicurrency) : 'null');
4357 $sql .= ", '".$this->db->escape($this->multicurrency_code)."'";
4358 $sql .= ", ".price2num($this->multicurrency_subprice, 'CU');
4359 $sql .= ", ".price2num($this->multicurrency_total_ht, 'CT');
4360 $sql .= ", ".price2num($this->multicurrency_total_tva, 'CT');
4361 $sql .= ", ".price2num($this->multicurrency_total_ttc, 'CT');
4362 $sql .= ')';
4363
4364 dol_syslog(get_class($this).'::insert', LOG_DEBUG);
4365 $resql = $this->db->query($sql);
4366 if ($resql) {
4367 $this->rowid = $this->db->last_insert_id(MAIN_DB_PREFIX.'propaldet');
4368
4369 if (!$error) {
4370 $this->id = $this->rowid;
4371 $result = $this->insertExtraFields();
4372 if ($result < 0) {
4373 $error++;
4374 }
4375 }
4376
4377 if (!$error && !$notrigger) {
4378 // Call trigger
4379 $result = $this->call_trigger('LINEPROPAL_INSERT', $user);
4380 if ($result < 0) {
4381 $this->db->rollback();
4382 return -1;
4383 }
4384 // End call triggers
4385 }
4386
4387 if (!$error) {
4388 $this->db->commit();
4389 return 1;
4390 }
4391
4392 foreach ($this->errors as $errmsg) {
4393 dol_syslog(get_class($this)."::insert ".$errmsg, LOG_ERR);
4394 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
4395 }
4396 $this->db->rollback();
4397 return -1 * $error;
4398 } else {
4399 $this->error = $this->db->error()." sql=".$sql;
4400 $this->db->rollback();
4401 return -1;
4402 }
4403 }
4404
4412 public function delete(User $user, $notrigger = 0)
4413 {
4414 global $conf;
4415
4416 $error = 0;
4417 $this->db->begin();
4418
4419 if (!$notrigger) {
4420 // Call trigger
4421 $result = $this->call_trigger('LINEPROPAL_DELETE', $user);
4422 if ($result < 0) {
4423 $error++;
4424 }
4425 }
4426 // End call triggers
4427
4428 if (!$error) {
4429 $sql = "DELETE FROM " . MAIN_DB_PREFIX . "propaldet WHERE rowid = " . ((int) $this->rowid);
4430 dol_syslog("PropaleLigne::delete", LOG_DEBUG);
4431 if ($this->db->query($sql)) {
4432 // Remove extrafields
4433 if (!$error) {
4434 $this->id = $this->rowid;
4435 $result = $this->deleteExtraFields();
4436 if ($result < 0) {
4437 $error++;
4438 dol_syslog(get_class($this) . "::delete error -4 " . $this->error, LOG_ERR);
4439 }
4440 }
4441 } else {
4442 $this->error = $this->db->error() . " sql=" . $sql;
4443 $error++;
4444 }
4445 }
4446
4447 if ($error) {
4448 $this->db->rollback();
4449 return -1;
4450 } else {
4451 $this->db->commit();
4452 return 1;
4453 }
4454 }
4455
4462 public function update($notrigger = 0)
4463 {
4464 global $conf, $user;
4465
4466 $error = 0;
4467
4468 $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'.
4469 $this->pa_ht = (float) $this->pa_ht; // convert to float after check if empty value
4470
4471 if (empty($this->id) && !empty($this->rowid)) {
4472 $this->id = $this->rowid;
4473 }
4474
4475 // Clean parameters
4476 if (empty($this->tva_tx)) {
4477 $this->tva_tx = 0;
4478 }
4479 if (empty($this->localtax1_tx)) {
4480 $this->localtax1_tx = 0;
4481 }
4482 if (empty($this->localtax2_tx)) {
4483 $this->localtax2_tx = 0;
4484 }
4485 if (empty($this->total_localtax1)) {
4486 $this->total_localtax1 = 0;
4487 }
4488 if (empty($this->total_localtax2)) {
4489 $this->total_localtax2 = 0;
4490 }
4491 if (empty($this->localtax1_type)) {
4492 $this->localtax1_type = 0;
4493 }
4494 if (empty($this->localtax2_type)) {
4495 $this->localtax2_type = 0;
4496 }
4497 if (empty($this->marque_tx)) {
4498 $this->marque_tx = 0;
4499 }
4500 if (empty($this->marge_tx)) {
4501 $this->marge_tx = 0;
4502 }
4503 if (empty($this->price)) {
4504 $this->price = 0; // TODO A virer
4505 }
4506 if (empty($this->remise_percent)) {
4507 $this->remise_percent = 0;
4508 }
4509 if (empty($this->info_bits)) {
4510 $this->info_bits = 0;
4511 }
4512 if (empty($this->special_code)) {
4513 $this->special_code = 0;
4514 }
4515 if (empty($this->fk_parent_line)) {
4516 $this->fk_parent_line = 0;
4517 }
4518 if (empty($this->fk_fournprice)) {
4519 $this->fk_fournprice = 0;
4520 }
4521 if (empty($this->subprice)) {
4522 $this->subprice = 0;
4523 }
4524
4525 // if buy price not defined, define buyprice as configured in margin admin
4526 if ($this->pa_ht == 0 && $pa_ht_isemptystring) {
4527 if (($result = $this->defineBuyPrice($this->subprice, $this->remise_percent, $this->fk_product)) < 0) {
4528 return $result;
4529 } else {
4530 $this->pa_ht = $result;
4531 }
4532 }
4533
4534 $this->db->begin();
4535
4536 // Mise a jour ligne en base
4537 $sql = "UPDATE ".MAIN_DB_PREFIX."propaldet SET";
4538 $sql .= " description='".$this->db->escape($this->desc)."'";
4539 $sql .= ", label=".(!empty($this->label) ? "'".$this->db->escape($this->label)."'" : "null");
4540 $sql .= ", product_type=".$this->product_type;
4541 $sql .= ", vat_src_code = '".(empty($this->vat_src_code) ? '' : $this->vat_src_code)."'";
4542 $sql .= ", tva_tx='".price2num($this->tva_tx)."'";
4543 $sql .= ", localtax1_tx=".price2num($this->localtax1_tx);
4544 $sql .= ", localtax2_tx=".price2num($this->localtax2_tx);
4545 $sql .= ", localtax1_type='".$this->db->escape($this->localtax1_type)."'";
4546 $sql .= ", localtax2_type='".$this->db->escape($this->localtax2_type)."'";
4547 $sql .= ", qty='".price2num($this->qty)."'";
4548 $sql .= ", subprice=".price2num($this->subprice);
4549 $sql .= ", remise_percent=".price2num($this->remise_percent);
4550 $sql .= ", price=".(float) price2num($this->price); // TODO A virer
4551 $sql .= ", remise=".(float) price2num($this->remise); // TODO A virer
4552 $sql .= ", info_bits='".$this->db->escape($this->info_bits)."'";
4553 if (empty($this->skip_update_total)) {
4554 $sql .= ", total_ht=".price2num($this->total_ht);
4555 $sql .= ", total_tva=".price2num($this->total_tva);
4556 $sql .= ", total_ttc=".price2num($this->total_ttc);
4557 $sql .= ", total_localtax1=".price2num($this->total_localtax1);
4558 $sql .= ", total_localtax2=".price2num($this->total_localtax2);
4559 }
4560 $sql .= ", fk_product_fournisseur_price=".(!empty($this->fk_fournprice) ? "'".$this->db->escape($this->fk_fournprice)."'" : "null");
4561 $sql .= ", buy_price_ht=".price2num($this->pa_ht);
4562 $sql .= ", special_code=".((int) $this->special_code);
4563 $sql .= ", fk_parent_line=".($this->fk_parent_line > 0 ? (int) $this->fk_parent_line : "null");
4564 if (!empty($this->rang)) {
4565 $sql .= ", rang=".((int) $this->rang);
4566 }
4567 $sql .= ", date_start=".(!empty($this->date_start) ? "'".$this->db->idate($this->date_start)."'" : "null");
4568 $sql .= ", date_end=".(!empty($this->date_end) ? "'".$this->db->idate($this->date_end)."'" : "null");
4569 $sql .= ", fk_unit=".(!$this->fk_unit ? 'NULL' : $this->fk_unit);
4570
4571 // Multicurrency
4572 $sql .= ", multicurrency_subprice=".price2num($this->multicurrency_subprice);
4573 $sql .= ", multicurrency_total_ht=".price2num($this->multicurrency_total_ht);
4574 $sql .= ", multicurrency_total_tva=".price2num($this->multicurrency_total_tva);
4575 $sql .= ", multicurrency_total_ttc=".price2num($this->multicurrency_total_ttc);
4576
4577 $sql .= " WHERE rowid = ".((int) $this->id);
4578
4579 dol_syslog(get_class($this)."::update", LOG_DEBUG);
4580 $resql = $this->db->query($sql);
4581 if ($resql) {
4582 if (!$error) {
4583 $result = $this->insertExtraFields();
4584 if ($result < 0) {
4585 $error++;
4586 }
4587 }
4588
4589 if (!$error && !$notrigger) {
4590 // Call trigger
4591 $result = $this->call_trigger('LINEPROPAL_MODIFY', $user);
4592 if ($result < 0) {
4593 $this->db->rollback();
4594 return -1;
4595 }
4596 // End call triggers
4597 }
4598
4599 if (!$error) {
4600 $this->db->commit();
4601 return 1;
4602 }
4603
4604 foreach ($this->errors as $errmsg) {
4605 dol_syslog(get_class($this)."::update ".$errmsg, LOG_ERR);
4606 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
4607 }
4608 $this->db->rollback();
4609 return -1 * $error;
4610 } else {
4611 $this->error = $this->db->error();
4612 $this->db->rollback();
4613 return -2;
4614 }
4615 }
4616
4617 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4624 public function update_total()
4625 {
4626 // phpcs:enable
4627 $this->db->begin();
4628
4629 // Mise a jour ligne en base
4630 $sql = "UPDATE ".MAIN_DB_PREFIX."propaldet SET";
4631 $sql .= " total_ht=".price2num($this->total_ht, 'MT');
4632 $sql .= ",total_tva=".price2num($this->total_tva, 'MT');
4633 $sql .= ",total_ttc=".price2num($this->total_ttc, 'MT');
4634 $sql .= " WHERE rowid = ".((int) $this->rowid);
4635
4636 dol_syslog("PropaleLigne::update_total", LOG_DEBUG);
4637
4638 $resql = $this->db->query($sql);
4639 if ($resql) {
4640 $this->db->commit();
4641 return 1;
4642 } else {
4643 $this->error = $this->db->error();
4644 $this->db->rollback();
4645 return -2;
4646 }
4647 }
4648}
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:637
$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.
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 commonReplaceThirdparty(DoliDB $dbs, $origin_id, $dest_id, array $tables, $ignoreerrors=0)
Function used to replace a thirdparty id with another one.
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.
trait CommonIncoterm
Superclass for incoterm classes.
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)
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.
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.
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.
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).
if(!function_exists( 'dol_getprefix')) dol_include_once($relpath, $classname='')
Make an include_once using default root and alternate root if it fails.
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.: VAT NPR in France)
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...
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.
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
publicphonebutton2 phonegreen basiclayout basiclayout TotalHT VATCode TotalVAT TotalLT1 TotalLT2 TotalTTC TotalHT clearboth nowraponall TAKEPOS_SHOW_SUBPRICE right right right takeposterminal SELECT e e e e e statut
Definition invoice.php:2043
publicphonebutton2 phonegreen basiclayout basiclayout TotalHT VATCode TotalVAT TotalLT1 TotalLT2 TotalTTC TotalHT clearboth nowraponall TAKEPOS_SHOW_SUBPRICE right right right takeposterminal SELECT e rowid
Definition invoice.php:2043