dolibarr 21.0.4
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
44require_once DOL_DOCUMENT_ROOT.'/core/class/commonobject.class.php';
45require_once DOL_DOCUMENT_ROOT.'/comm/propal/class/propaleligne.class.php';
46require_once DOL_DOCUMENT_ROOT.'/product/class/product.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{
56 use CommonIncoterm;
57
61 public $code = "";
62
66 public $element = 'propal';
67
71 public $table_element = 'propal';
72
76 public $table_element_line = 'propaldet';
77
81 public $fk_element = 'fk_propal';
82
86 public $picto = 'propal';
87
92 public $restrictiononfksoc = 1;
93
97 protected $table_ref_field = 'ref';
98
103 public $socid;
104
109 public $contactid;
110
117 public $ref_client;
118
123 public $ref_customer;
124
131 public $statut;
132
138 public $status;
139
145 public $datec;
146
152 public $datev;
153
157 public $date_validation;
158
162 public $date_signature;
163
167 public $user_signature;
168
172 public $date;
173
179 public $datep;
180
184 public $delivery_date; // Date expected of shipment (date starting shipment, not the reception that occurs some days after)
185
186
190 public $fin_validite;
191
195 public $user_author_id;
196
200 public $author;
201
207 public $price;
208
214 public $tva;
220 public $total;
221
225 public $cond_reglement_code;
226
230 public $cond_reglement;
231
235 public $cond_reglement_doc;
236
240 public $mode_reglement_code;
241
245 public $mode_reglement;
246
252 public $deposit_percent;
253
258 public $fk_address;
259
264 public $address_type;
265
270 public $address;
271
275 public $availability_id;
276
282 public $fk_availability;
283
287 public $availability_code;
288
292 public $availability;
293
297 public $duree_validite;
298
302 public $demand_reason_id;
303
307 public $demand_reason_code;
308
312 public $demand_reason;
313
317 public $warehouse_id;
318
322 public $lines = array();
323
327 public $line;
328
329 public $labelStatus = array();
330 public $labelStatusShort = array();
331
332
357 // BEGIN MODULEBUILDER PROPERTIES
361 public $fields = array(
362 'rowid' => array('type' => 'integer', 'label' => 'TechnicalID', 'enabled' => 1, 'visible' => -1, 'notnull' => 1, 'position' => 10),
363 'entity' => array('type' => 'integer', 'label' => 'Entity', 'default' => '1', 'enabled' => 1, 'visible' => -2, 'notnull' => 1, 'position' => 15, 'index' => 1),
364 'ref' => array('type' => 'varchar(30)', 'label' => 'Ref', 'enabled' => 1, 'visible' => -1, 'notnull' => 1, 'showoncombobox' => 1, 'position' => 20),
365 'ref_client' => array('type' => 'varchar(255)', 'label' => 'RefCustomer', 'enabled' => 1, 'visible' => -1, 'position' => 22),
366 'ref_ext' => array('type' => 'varchar(255)', 'label' => 'RefExt', 'enabled' => 1, 'visible' => 0, 'position' => 40),
367 'fk_soc' => array('type' => 'integer:Societe:societe/class/societe.class.php', 'label' => 'ThirdParty', 'enabled' => 'isModEnabled("societe")', 'visible' => -1, 'position' => 23),
368 '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),
369 'tms' => array('type' => 'timestamp', 'label' => 'DateModification', 'enabled' => 1, 'visible' => -1, 'notnull' => 1, 'position' => 25),
370 'datec' => array('type' => 'datetime', 'label' => 'DateCreation', 'enabled' => 1, 'visible' => -1, 'position' => 55),
371 'datep' => array('type' => 'date', 'label' => 'Date', 'enabled' => 1, 'visible' => -1, 'position' => 60),
372 'fin_validite' => array('type' => 'datetime', 'label' => 'DateEnd', 'enabled' => 1, 'visible' => -1, 'position' => 65),
373 'date_valid' => array('type' => 'datetime', 'label' => 'DateValidation', 'enabled' => 1, 'visible' => -1, 'position' => 70),
374 'date_cloture' => array('type' => 'datetime', 'label' => 'DateClosing', 'enabled' => 1, 'visible' => -1, 'position' => 75),
375 'fk_user_author' => array('type' => 'integer:User:user/class/user.class.php', 'label' => 'Fk user author', 'enabled' => 1, 'visible' => -1, 'position' => 80),
376 'fk_user_modif' => array('type' => 'integer:User:user/class/user.class.php', 'label' => 'UserModif', 'enabled' => 1, 'visible' => -2, 'notnull' => -1, 'position' => 85),
377 'fk_user_valid' => array('type' => 'integer:User:user/class/user.class.php', 'label' => 'UserValidation', 'enabled' => 1, 'visible' => -1, 'position' => 90),
378 'fk_user_cloture' => array('type' => 'integer:User:user/class/user.class.php', 'label' => 'Fk user cloture', 'enabled' => 1, 'visible' => -1, 'position' => 95),
379 'price' => array('type' => 'double', 'label' => 'Price', 'enabled' => 1, 'visible' => -1, 'position' => 105),
380 'total_ht' => array('type' => 'double(24,8)', 'label' => 'TotalHT', 'enabled' => 1, 'visible' => -1, 'position' => 125, 'isameasure' => 1),
381 'total_tva' => array('type' => 'double(24,8)', 'label' => 'VAT', 'enabled' => 1, 'visible' => -1, 'position' => 130, 'isameasure' => 1),
382 'localtax1' => array('type' => 'double(24,8)', 'label' => 'LocalTax1', 'enabled' => 1, 'visible' => -1, 'position' => 135, 'isameasure' => 1),
383 'localtax2' => array('type' => 'double(24,8)', 'label' => 'LocalTax2', 'enabled' => 1, 'visible' => -1, 'position' => 140, 'isameasure' => 1),
384 'total_ttc' => array('type' => 'double(24,8)', 'label' => 'TotalTTC', 'enabled' => 1, 'visible' => -1, 'position' => 145, 'isameasure' => 1),
385 'fk_account' => array('type' => 'integer', 'label' => 'BankAccount', 'enabled' => 'isModEnabled("bank")', 'visible' => -1, 'position' => 150),
386 'fk_currency' => array('type' => 'varchar(3)', 'label' => 'Currency', 'enabled' => 1, 'visible' => -1, 'position' => 155),
387 'fk_cond_reglement' => array('type' => 'integer', 'label' => 'PaymentTerm', 'enabled' => 1, 'visible' => -1, 'position' => 160),
388 'deposit_percent' => array('type' => 'varchar(63)', 'label' => 'DepositPercent', 'enabled' => 1, 'visible' => -1, 'position' => 161),
389 'fk_mode_reglement' => array('type' => 'integer', 'label' => 'PaymentMode', 'enabled' => 1, 'visible' => -1, 'position' => 165),
390 'note_private' => array('type' => 'html', 'label' => 'NotePrivate', 'enabled' => 1, 'visible' => 0, 'position' => 170),
391 'note_public' => array('type' => 'html', 'label' => 'NotePublic', 'enabled' => 1, 'visible' => 0, 'position' => 175),
392 'model_pdf' => array('type' => 'varchar(255)', 'label' => 'PDFTemplate', 'enabled' => 1, 'visible' => 0, 'position' => 180),
393 'date_livraison' => array('type' => 'date', 'label' => 'DateDeliveryPlanned', 'enabled' => 1, 'visible' => -1, 'position' => 185),
394 'fk_shipping_method' => array('type' => 'integer', 'label' => 'ShippingMethod', 'enabled' => 1, 'visible' => -1, 'position' => 190),
395 'fk_warehouse' => array('type' => 'integer:Entrepot:product/stock/class/entrepot.class.php', 'label' => 'Fk warehouse', 'enabled' => 'isModEnabled("stock")', 'visible' => -1, 'position' => 191),
396 'fk_availability' => array('type' => 'integer', 'label' => 'Availability', 'enabled' => 1, 'visible' => -1, 'position' => 195),
397 'fk_delivery_address' => array('type' => 'integer', 'label' => 'DeliveryAddress', 'enabled' => 1, 'visible' => 0, 'position' => 200), // deprecated
398 'fk_input_reason' => array('type' => 'integer', 'label' => 'InputReason', 'enabled' => 1, 'visible' => -1, 'position' => 205),
399 'extraparams' => array('type' => 'varchar(255)', 'label' => 'Extraparams', 'enabled' => 1, 'visible' => -1, 'position' => 215),
400 'fk_incoterms' => array('type' => 'integer', 'label' => 'IncotermCode', 'enabled' => 'isModEnabled("incoterm")', 'visible' => -1, 'position' => 220),
401 'location_incoterms' => array('type' => 'varchar(255)', 'label' => 'IncotermLabel', 'enabled' => 'isModEnabled("incoterm")', 'visible' => -1, 'position' => 225),
402 'fk_multicurrency' => array('type' => 'integer', 'label' => 'MulticurrencyID', 'enabled' => 1, 'visible' => -1, 'position' => 230),
403 'multicurrency_code' => array('type' => 'varchar(255)', 'label' => 'MulticurrencyCurrency', 'enabled' => 'isModEnabled("multicurrency")', 'visible' => -1, 'position' => 235),
404 'multicurrency_tx' => array('type' => 'double(24,8)', 'label' => 'MulticurrencyRate', 'enabled' => 'isModEnabled("multicurrency")', 'visible' => -1, 'position' => 240, 'isameasure' => 1),
405 'multicurrency_total_ht' => array('type' => 'double(24,8)', 'label' => 'MulticurrencyAmountHT', 'enabled' => 'isModEnabled("multicurrency")', 'visible' => -1, 'position' => 245, 'isameasure' => 1),
406 'multicurrency_total_tva' => array('type' => 'double(24,8)', 'label' => 'MulticurrencyAmountVAT', 'enabled' => 'isModEnabled("multicurrency")', 'visible' => -1, 'position' => 250, 'isameasure' => 1),
407 'multicurrency_total_ttc' => array('type' => 'double(24,8)', 'label' => 'MulticurrencyAmountTTC', 'enabled' => 'isModEnabled("multicurrency")', 'visible' => -1, 'position' => 255, 'isameasure' => 1),
408 'last_main_doc' => array('type' => 'varchar(255)', 'label' => 'LastMainDoc', 'enabled' => 1, 'visible' => -1, 'position' => 260),
409 'fk_statut' => array('type' => 'smallint(6)', 'label' => 'Status', 'enabled' => 1, 'visible' => -1, 'notnull' => 1, 'position' => 500),
410 'import_key' => array('type' => 'varchar(14)', 'label' => 'ImportId', 'enabled' => 1, 'visible' => -2, 'position' => 900),
411 );
412 // END MODULEBUILDER PROPERTIES
413
417 const STATUS_CANCELED = -1;
421 const STATUS_DRAFT = 0;
429 const STATUS_SIGNED = 2;
437 const STATUS_BILLED = 4; // Todo rename into STATUS_CLOSE ?
438
439
447 public function __construct($db, $socid = 0, $propalid = 0)
448 {
449 $this->db = $db;
450
451 $this->ismultientitymanaged = 1;
452 $this->socid = $socid;
453 $this->id = $propalid;
454
455 $this->duree_validite = getDolGlobalInt('PROPALE_VALIDITY_DURATION');
456
457 $this->fields['ref_ext']['visible'] = getDolGlobalInt('MAIN_LIST_SHOW_REF_EXT');
458 }
459
460
461 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
473 public function add_product($idproduct, $qty, $remise_percent = 0)
474 {
475 // phpcs:enable
476 global $conf, $mysoc;
477
478 if (!$qty) {
479 $qty = 1;
480 }
481
482 dol_syslog(get_class($this)."::add_product $idproduct, $qty, $remise_percent");
483 if ($idproduct > 0) {
484 $prod = new Product($this->db);
485 $prod->fetch($idproduct);
486
487 $productdesc = $prod->description;
488
489 $tva_tx = (string) get_default_tva($mysoc, $this->thirdparty, $prod->id);
490 $tva_npr = get_default_npr($mysoc, $this->thirdparty, $prod->id);
491 if (empty($tva_tx)) {
492 $tva_npr = 0;
493 }
494 $vat_src_code = ''; // May be defined into tva_tx
495
496 $localtax1_tx = get_localtax($tva_tx, 1, $mysoc, $this->thirdparty, $tva_npr);
497 $localtax2_tx = get_localtax($tva_tx, 2, $mysoc, $this->thirdparty, $tva_npr);
498
499 // multiprices
500 if (getDolGlobalString('PRODUIT_MULTIPRICES') && $this->thirdparty->price_level) {
501 $price = $prod->multiprices[$this->thirdparty->price_level];
502 } else {
503 $price = $prod->price;
504 }
505
506 $line = new PropaleLigne($this->db);
507
508 $line->fk_product = $idproduct;
509 $line->desc = $productdesc;
510 $line->qty = $qty;
511 $line->subprice = $price;
512 $line->remise_percent = $remise_percent;
513 $line->vat_src_code = $vat_src_code;
514 $line->tva_tx = $tva_tx;
515 $line->fk_unit = $prod->fk_unit;
516 if ($tva_npr) {
517 $line->info_bits = 1;
518 }
519
520 $this->lines[] = $line;
521 }
522
523 return 1;
524 }
525
526 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
533 public function insert_discount($idremise)
534 {
535 // phpcs:enable
536 global $langs;
537
538 include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
539 include_once DOL_DOCUMENT_ROOT.'/core/class/discount.class.php';
540
541 $this->db->begin();
542
543 $remise = new DiscountAbsolute($this->db);
544 $result = $remise->fetch($idremise);
545
546 if ($result > 0) {
547 if ($remise->fk_facture) { // Protection against multiple submission
548 $this->error = $langs->trans("ErrorDiscountAlreadyUsed");
549 $this->db->rollback();
550 return -5;
551 }
552
553 $line = new PropaleLigne($this->db);
554
555 $line->context = $this->context;
556
557 $line->fk_propal = $this->id;
558 $line->fk_remise_except = $remise->id;
559 $line->desc = $remise->description; // Description ligne
560 $line->vat_src_code = $remise->vat_src_code;
561 $line->tva_tx = $remise->tva_tx;
562 $line->subprice = -(float) $remise->amount_ht;
563 $line->fk_product = 0; // Id produit predefined
564 $line->qty = 1;
565 $line->remise_percent = 0;
566 $line->rang = -1;
567 $line->info_bits = 2;
568
569 // TODO deprecated
570 $line->price = -(float) $remise->amount_ht;
571
572 $line->total_ht = -(float) $remise->amount_ht;
573 $line->total_tva = -(float) $remise->amount_tva;
574 $line->total_ttc = -(float) $remise->amount_ttc;
575
576 $result = $line->insert();
577 if ($result > 0) {
578 $result = $this->update_price(1);
579 if ($result > 0) {
580 $this->db->commit();
581 return 1;
582 } else {
583 $this->db->rollback();
584 return -1;
585 }
586 } else {
587 $this->error = $line->error;
588 $this->errors = $line->errors;
589 $this->db->rollback();
590 return -2;
591 }
592 } else {
593 $this->db->rollback();
594 return -2;
595 }
596 }
597
635 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)
636 {
637 global $mysoc, $conf, $langs;
638
639 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);
640
641 if ($this->statut == self::STATUS_DRAFT) {
642 include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
643
644 // Clean parameters
645 if (empty($remise_percent)) {
646 $remise_percent = 0;
647 }
648 if (empty($qty)) {
649 $qty = 0;
650 }
651 if (empty($info_bits)) {
652 $info_bits = 0;
653 }
654 if (empty($rang)) {
655 $rang = 0;
656 }
657 if (empty($fk_parent_line) || $fk_parent_line < 0) {
658 $fk_parent_line = 0;
659 }
660
661 $remise_percent = price2num($remise_percent);
662 $qty = (float) price2num($qty);
663 $pu_ht = price2num($pu_ht);
664 $pu_ht_devise = price2num($pu_ht_devise);
665 $pu_ttc = price2num($pu_ttc);
666 if (!preg_match('/\‍((.*)\‍)/', (string) $txtva)) {
667 $txtva = price2num($txtva); // $txtva can have format '5,1' or '5.1' or '5.1(XXX)', we must clean only if '5,1'
668 }
669 $txlocaltax1 = price2num($txlocaltax1);
670 $txlocaltax2 = price2num($txlocaltax2);
671 $pa_ht = price2num($pa_ht); // do not convert to float here, it breaks the functioning of $pa_ht_isemptystring
672 if ($price_base_type == 'HT') {
673 $pu = $pu_ht;
674 } else {
675 $pu = $pu_ttc;
676 }
677
678 // Check parameters
679 if ($type < 0) {
680 return -1;
681 }
682
683 if ($date_start && $date_end && $date_start > $date_end) {
684 $langs->load("errors");
685 $this->error = $langs->trans('ErrorStartDateGreaterEnd');
686 return -1;
687 }
688
689 $this->db->begin();
690
691 $product_type = $type;
692 if (!empty($fk_product) && $fk_product > 0) {
693 $product = new Product($this->db);
694 $result = $product->fetch($fk_product);
695 $product_type = $product->type;
696
697 if (getDolGlobalString('STOCK_MUST_BE_ENOUGH_FOR_PROPOSAL') && $product_type == 0 && $product->stock_reel < $qty) {
698 $langs->load("errors");
699 $this->error = $langs->trans('ErrorStockIsNotEnoughToAddProductOnProposal', $product->ref);
700 $this->db->rollback();
701 return -3;
702 }
703 }
704
705 // Calcul du total TTC et de la TVA pour la ligne a partir de
706 // qty, pu, remise_percent et txtva
707 // TRES IMPORTANT: C'est au moment de l'insertion ligne qu'on doit stocker
708 // la part ht, tva et ttc, et ce au niveau de la ligne qui a son propre taux tva.
709
710 $localtaxes_type = getLocalTaxesFromRate($txtva, 0, $this->thirdparty, $mysoc);
711
712 // Clean vat code
713 $reg = array();
714 $vat_src_code = '';
715 if (preg_match('/\‍((.*)\‍)/', $txtva, $reg)) {
716 $vat_src_code = $reg[1];
717 $txtva = preg_replace('/\s*\‍(.*\‍)/', '', $txtva); // Remove code into vatrate.
718 }
719
720 $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);
721
722 $total_ht = $tabprice[0];
723 $total_tva = $tabprice[1];
724 $total_ttc = $tabprice[2];
725 $total_localtax1 = $tabprice[9];
726 $total_localtax2 = $tabprice[10];
727 $pu_ht = $tabprice[3];
728 $pu_tva = $tabprice[4];
729 $pu_ttc = $tabprice[5];
730
731 // MultiCurrency
732 $multicurrency_total_ht = $tabprice[16];
733 $multicurrency_total_tva = $tabprice[17];
734 $multicurrency_total_ttc = $tabprice[18];
735 $pu_ht_devise = $tabprice[19];
736
737 // Rang to use
738 $ranktouse = $rang;
739 if ($ranktouse == -1) {
740 $rangmax = $this->line_max($fk_parent_line);
741 $ranktouse = $rangmax + 1;
742 }
743
744 // TODO A virer
745 // Anciens indicateurs: $price, $remise (a ne plus utiliser)
746 $price = $pu;
747 $remise = 0;
748 if ((float) $remise_percent > 0) {
749 $remise = round(((float) $pu * (float) $remise_percent / 100), 2);
750 $price = (float) $pu - $remise;
751 }
752
753 // Insert line
754 $this->line = new PropaleLigne($this->db);
755
756 $this->line->context = $this->context;
757
758 $this->line->fk_propal = $this->id;
759 $this->line->label = $label;
760 $this->line->desc = $desc;
761 $this->line->qty = $qty;
762
763 $this->line->vat_src_code = $vat_src_code;
764 $this->line->tva_tx = $txtva;
765 $this->line->localtax1_tx = ($total_localtax1 ? $localtaxes_type[1] : 0);
766 $this->line->localtax2_tx = ($total_localtax2 ? $localtaxes_type[3] : 0);
767 $this->line->localtax1_type = empty($localtaxes_type[0]) ? '' : $localtaxes_type[0];
768 $this->line->localtax2_type = empty($localtaxes_type[2]) ? '' : $localtaxes_type[2];
769 $this->line->fk_product = $fk_product;
770 $this->line->product_type = $type;
771 $this->line->fk_remise_except = $fk_remise_except;
772 $this->line->remise_percent = $remise_percent;
773 $this->line->subprice = (float) $pu_ht;
774 $this->line->rang = $ranktouse;
775 $this->line->info_bits = $info_bits;
776 $this->line->total_ht = (float) $total_ht;
777 $this->line->total_tva = (float) $total_tva;
778 $this->line->total_localtax1 = (float) $total_localtax1;
779 $this->line->total_localtax2 = (float) $total_localtax2;
780 $this->line->total_ttc = (float) $total_ttc;
781 $this->line->special_code = $special_code;
782 $this->line->fk_parent_line = $fk_parent_line;
783 $this->line->fk_unit = $fk_unit;
784
785 $this->line->date_start = $date_start;
786 $this->line->date_end = $date_end;
787
788 $this->line->fk_fournprice = $fk_fournprice;
789 $this->line->pa_ht = $pa_ht;
790
791 $this->line->origin_id = $origin_id;
792 $this->line->origin = $origin;
793
794 // Multicurrency
795 $this->line->fk_multicurrency = $this->fk_multicurrency;
796 $this->line->multicurrency_code = $this->multicurrency_code;
797 $this->line->multicurrency_subprice = (float) $pu_ht_devise;
798 $this->line->multicurrency_total_ht = (float) $multicurrency_total_ht;
799 $this->line->multicurrency_total_tva = (float) $multicurrency_total_tva;
800 $this->line->multicurrency_total_ttc = (float) $multicurrency_total_ttc;
801
802 // Mise en option de la ligne
803 if (empty($qty) && empty($special_code)) {
804 $this->line->special_code = 3;
805 }
806
807 // TODO deprecated
808 $this->line->price = $price;
809
810 if (is_array($array_options) && count($array_options) > 0) {
811 $this->line->array_options = $array_options;
812 }
813
814 $result = $this->line->insert();
815 if ($result > 0) {
816 if (!isset($this->context['createfromclone'])) {
817 if (!empty($fk_parent_line)) {
818 // Always reorder if child line
819 $this->line_order(true, 'DESC');
820 } elseif ($ranktouse > 0 && $ranktouse <= count($this->lines)) {
821 // Update all rank of all other lines starting from the same $ranktouse
822 $linecount = count($this->lines);
823 for ($ii = $ranktouse; $ii <= $linecount; $ii++) {
824 $this->updateRangOfLine($this->lines[$ii - 1]->id, $ii + 1);
825 }
826 }
827
828 $this->lines[] = $this->line;
829 }
830
831 // Update denormalized fields at the order level
832 if (empty($noupdateafterinsertline)) {
833 $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.
834 }
835
836 if ($result > 0) {
837 $this->db->commit();
838 return $this->line->id;
839 } else {
840 $this->error = $this->db->error();
841 $this->db->rollback();
842 return -1;
843 }
844 } else {
845 $this->error = $this->line->error;
846 $this->errors = $this->line->errors;
847 $this->db->rollback();
848 return -2;
849 }
850 } else {
851 dol_syslog(get_class($this)."::addline status of proposal must be Draft to allow use of ->addline()", LOG_ERR);
852 return -3;
853 }
854 }
855
856
886 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)
887 {
888 global $mysoc, $langs;
889
890 dol_syslog(get_class($this)."::updateLine rowid=$rowid, pu=$pu, qty=$qty, remise_percent=$remise_percent,
891 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");
892 include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
893
894 // Clean parameters
895 $remise_percent = price2num($remise_percent);
896 $qty = (float) price2num($qty);
897 $pu = price2num($pu);
898 $pu_ht_devise = price2num($pu_ht_devise);
899 if (!preg_match('/\‍((.*)\‍)/', (string) $txtva)) {
900 $txtva = price2num($txtva); // $txtva can have format '5.0(XXX)' or '5'
901 }
902 $txlocaltax1 = price2num($txlocaltax1);
903 $txlocaltax2 = price2num($txlocaltax2);
904 $pa_ht = price2num($pa_ht); // do not convert to float here, it breaks the functioning of $pa_ht_isemptystring
905 if (empty($qty) && empty($special_code)) {
906 $special_code = 3; // Set option tag
907 }
908 if (!empty($qty) && $special_code == 3) {
909 $special_code = 0; // Remove option tag
910 }
911 if (empty($type)) {
912 $type = 0;
913 }
914
915 if ($date_start && $date_end && $date_start > $date_end) {
916 $langs->load("errors");
917 $this->error = $langs->trans('ErrorStartDateGreaterEnd');
918 return -1;
919 }
920
921 if ($this->status == self::STATUS_DRAFT) {
922 $this->db->begin();
923
924 // Calcul du total TTC et de la TVA pour la ligne a partir de
925 // qty, pu, remise_percent et txtva
926 // TRES IMPORTANT: C'est au moment de l'insertion ligne qu'on doit stocker
927 // la part ht, tva et ttc, et ce au niveau de la ligne qui a son propre taux tva.
928
929 $localtaxes_type = getLocalTaxesFromRate($txtva, 0, $this->thirdparty, $mysoc);
930
931 // Clean vat code
932 $reg = array();
933 $vat_src_code = '';
934 if (preg_match('/\‍((.*)\‍)/', $txtva, $reg)) {
935 $vat_src_code = $reg[1];
936 $txtva = preg_replace('/\s*\‍(.*\‍)/', '', $txtva); // Remove code into vatrate.
937 }
938
939 // TODO Implement if (getDolGlobalInt('MAIN_UNIT_PRICE_WITH_TAX_IS_FOR_ALL_TAXES')) ?
940
941 $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);
942 $total_ht = $tabprice[0];
943 $total_tva = $tabprice[1];
944 $total_ttc = $tabprice[2];
945 $total_localtax1 = $tabprice[9];
946 $total_localtax2 = $tabprice[10];
947 $pu_ht = $tabprice[3];
948 $pu_tva = $tabprice[4];
949 $pu_ttc = $tabprice[5];
950
951 // MultiCurrency
952 $multicurrency_total_ht = $tabprice[16];
953 $multicurrency_total_tva = $tabprice[17];
954 $multicurrency_total_ttc = $tabprice[18];
955 $pu_ht_devise = $tabprice[19];
956
957 // Anciens indicateurs: $price, $remise (a ne plus utiliser)
958 $price = $pu;
959 $remise = 0;
960 if ((float) $remise_percent > 0) {
961 $remise = round(((float) $pu * (float) $remise_percent / 100), 2);
962 $price = (float) $pu - $remise;
963 }
964
965 // Fetch current line from the database and then clone the object and set it in $oldline property
966 $line = new PropaleLigne($this->db);
967 $line->fetch($rowid);
968
969 $staticline = clone $line;
970
971 $line->oldline = $staticline;
972 $this->line = $line;
973 $this->line->context = $this->context;
974 $this->line->rang = $rang;
975
976 // Reorder if fk_parent_line change
977 if (!empty($fk_parent_line) && !empty($staticline->fk_parent_line) && $fk_parent_line != $staticline->fk_parent_line) {
978 $rangmax = $this->line_max($fk_parent_line);
979 $this->line->rang = $rangmax + 1;
980 }
981
982 $this->line->id = $rowid;
983 $this->line->label = $label;
984 $this->line->desc = $desc;
985 $this->line->qty = $qty;
986 $this->line->product_type = $type;
987 $this->line->vat_src_code = $vat_src_code;
988 $this->line->tva_tx = $txtva;
989 $this->line->localtax1_tx = $txlocaltax1;
990 $this->line->localtax2_tx = $txlocaltax2;
991 $this->line->localtax1_type = empty($localtaxes_type[0]) ? '' : $localtaxes_type[0];
992 $this->line->localtax2_type = empty($localtaxes_type[2]) ? '' : $localtaxes_type[2];
993 $this->line->remise_percent = $remise_percent;
994 $this->line->subprice = (float) $pu_ht;
995 $this->line->info_bits = $info_bits;
996
997 $this->line->total_ht = (float) $total_ht;
998 $this->line->total_tva = (float) $total_tva;
999 $this->line->total_localtax1 = (float) $total_localtax1;
1000 $this->line->total_localtax2 = (float) $total_localtax2;
1001 $this->line->total_ttc = (float) $total_ttc;
1002 $this->line->special_code = $special_code;
1003 $this->line->fk_parent_line = $fk_parent_line;
1004 $this->line->skip_update_total = $skip_update_total;
1005 $this->line->fk_unit = $fk_unit;
1006
1007 $this->line->fk_fournprice = $fk_fournprice;
1008 $this->line->pa_ht = $pa_ht;
1009
1010 $this->line->date_start = $date_start;
1011 $this->line->date_end = $date_end;
1012
1013 if (is_array($array_options) && count($array_options) > 0) {
1014 // We replace values in this->line->array_options only for entries defined into $array_options
1015 foreach ($array_options as $key => $value) {
1016 $this->line->array_options[$key] = $array_options[$key];
1017 }
1018 }
1019
1020 // Multicurrency
1021 $this->line->multicurrency_subprice = (float) $pu_ht_devise;
1022 $this->line->multicurrency_total_ht = (float) $multicurrency_total_ht;
1023 $this->line->multicurrency_total_tva = (float) $multicurrency_total_tva;
1024 $this->line->multicurrency_total_ttc = (float) $multicurrency_total_ttc;
1025
1026 $result = $this->line->update($notrigger);
1027 if ($result > 0) {
1028 // Reorder if child line
1029 if (!empty($fk_parent_line)) {
1030 $this->line_order(true, 'DESC');
1031 }
1032
1033 $this->update_price(1, 'auto');
1034
1035 // $this is Propal
1036 // $this->fk_propal = $this->id;
1037 // $this->rowid = $rowid;
1038
1039 $this->db->commit();
1040 return $result;
1041 } else {
1042 $this->error = $this->line->error;
1043 $this->errors = $this->line->errors;
1044 $this->db->rollback();
1045 return -1;
1046 }
1047 } else {
1048 dol_syslog(get_class($this)."::updateline Erreur -2 Propal en mode incompatible pour cette action");
1049 return -2;
1050 }
1051 }
1052
1053
1061 public function deleteLine($lineid, $id = 0)
1062 {
1063 global $user;
1064
1065 if ($this->status == self::STATUS_DRAFT) {
1066 $this->db->begin();
1067
1068 $line = new PropaleLigne($this->db);
1069
1070 $line->context = $this->context;
1071
1072 // Load data
1073 $line->fetch($lineid);
1074
1075 if ($id > 0 && $line->fk_propal != $id) {
1076 $this->error = 'ErrorLineIDDoesNotMatchWithObjectID';
1077 return -1;
1078 }
1079
1080 // Memorize previous line for triggers
1081 $staticline = clone $line;
1082 $line->oldline = $staticline;
1083
1084 if ($line->delete($user) > 0) {
1085 $this->update_price(1);
1086
1087 $this->db->commit();
1088 return 1;
1089 } else {
1090 $this->error = $line->error;
1091 $this->errors = $line->errors;
1092 $this->db->rollback();
1093 return -1;
1094 }
1095 } else {
1096 $this->error = 'ErrorDeleteLineNotAllowedByObjectStatus';
1097 return -2;
1098 }
1099 }
1100
1101
1110 public function create($user, $notrigger = 0)
1111 {
1112 global $conf, $hookmanager, $mysoc;
1113 $error = 0;
1114
1115 $now = dol_now();
1116
1117 // Clean parameters
1118 if (empty($this->date)) {
1119 $this->date = $this->datep;
1120 }
1121 $this->fin_validite = $this->date + ($this->duree_validite * 24 * 3600);
1122 if (empty($this->availability_id)) {
1123 $this->availability_id = 0;
1124 }
1125 if (empty($this->demand_reason_id)) {
1126 $this->demand_reason_id = 0;
1127 }
1128
1129 // Multicurrency (test on $this->multicurrency_tx because we should take the default rate only if not using origin rate)
1130 if (!empty($this->multicurrency_code) && empty($this->multicurrency_tx)) {
1131 list($this->fk_multicurrency, $this->multicurrency_tx) = MultiCurrency::getIdAndTxFromCode($this->db, $this->multicurrency_code, $this->date);
1132 } else {
1133 $this->fk_multicurrency = MultiCurrency::getIdFromCode($this->db, $this->multicurrency_code);
1134 }
1135 if (empty($this->fk_multicurrency)) {
1136 $this->multicurrency_code = $conf->currency;
1137 $this->fk_multicurrency = 0;
1138 $this->multicurrency_tx = 1;
1139 }
1140 // setEntity will set entity with the right value if empty or change it for the right value if multicompany module is active
1141 $this->entity = setEntity($this);
1142
1143 // Set tmp vars
1144 $delivery_date = $this->delivery_date;
1145
1146 dol_syslog(get_class($this)."::create");
1147
1148 // Check parameters
1149 $result = $this->fetch_thirdparty();
1150 if ($result < 0) {
1151 $this->error = "Failed to fetch company";
1152 dol_syslog(get_class($this)."::create ".$this->error, LOG_ERR);
1153 return -3;
1154 }
1155
1156 // Check parameters
1157 if (!empty($this->ref)) { // We check that ref is not already used
1158 $result = self::isExistingObject($this->element, 0, $this->ref); // Check ref is not yet used
1159 if ($result > 0) {
1160 $this->error = 'ErrorRefAlreadyExists';
1161 dol_syslog(get_class($this)."::create ".$this->error, LOG_WARNING);
1162 $this->db->rollback();
1163 return -1;
1164 }
1165 }
1166
1167 if (empty($this->date)) {
1168 $this->error = "Date of proposal is required";
1169 dol_syslog(get_class($this)."::create ".$this->error, LOG_ERR);
1170 return -4;
1171 }
1172
1173
1174 $this->db->begin();
1175
1176 // Insert into database
1177 $sql = "INSERT INTO ".MAIN_DB_PREFIX."propal (";
1178 $sql .= "fk_soc";
1179 $sql .= ", price";
1180 $sql .= ", total_tva";
1181 $sql .= ", total_ttc";
1182 $sql .= ", datep";
1183 $sql .= ", datec";
1184 $sql .= ", ref";
1185 $sql .= ", fk_user_author";
1186 $sql .= ", note_private";
1187 $sql .= ", note_public";
1188 $sql .= ", model_pdf";
1189 $sql .= ", fin_validite";
1190 $sql .= ", fk_cond_reglement";
1191 $sql .= ", deposit_percent";
1192 $sql .= ", fk_mode_reglement";
1193 $sql .= ", fk_account";
1194 $sql .= ", ref_client";
1195 $sql .= ", ref_ext";
1196 $sql .= ", date_livraison";
1197 $sql .= ", fk_shipping_method";
1198 $sql .= ", fk_warehouse";
1199 $sql .= ", fk_availability";
1200 $sql .= ", fk_input_reason";
1201 $sql .= ", fk_projet";
1202 $sql .= ", fk_incoterms";
1203 $sql .= ", location_incoterms";
1204 $sql .= ", entity";
1205 $sql .= ", fk_multicurrency";
1206 $sql .= ", multicurrency_code";
1207 $sql .= ", multicurrency_tx";
1208 $sql .= ") ";
1209 $sql .= " VALUES (";
1210 $sql .= $this->socid;
1211 $sql .= ", 0";
1212 $sql .= ", 0";
1213 $sql .= ", 0";
1214 $sql .= ", '".$this->db->idate($this->date)."'";
1215 $sql .= ", '".$this->db->idate($now)."'";
1216 $sql .= ", '(PROV)'";
1217 $sql .= ", ".($user->id > 0 ? ((int) $user->id) : "NULL");
1218 $sql .= ", '".$this->db->escape($this->note_private)."'";
1219 $sql .= ", '".$this->db->escape($this->note_public)."'";
1220 $sql .= ", '".$this->db->escape($this->model_pdf)."'";
1221 $sql .= ", ".($this->fin_validite != '' ? "'".$this->db->idate($this->fin_validite)."'" : "NULL");
1222 $sql .= ", ".($this->cond_reglement_id > 0 ? ((int) $this->cond_reglement_id) : 'NULL');
1223 $sql .= ", ".(!empty($this->deposit_percent) ? "'".$this->db->escape($this->deposit_percent)."'" : 'NULL');
1224 $sql .= ", ".($this->mode_reglement_id > 0 ? ((int) $this->mode_reglement_id) : 'NULL');
1225 $sql .= ", ".($this->fk_account > 0 ? ((int) $this->fk_account) : 'NULL');
1226 $sql .= ", '".$this->db->escape($this->ref_client)."'";
1227 $sql .= ", '".$this->db->escape($this->ref_ext)."'";
1228 $sql .= ", ".(!isDolTms($delivery_date) ? "NULL" : "'".$this->db->idate($delivery_date)."'");
1229 $sql .= ", ".($this->shipping_method_id > 0 ? $this->shipping_method_id : 'NULL');
1230 $sql .= ", ".($this->warehouse_id > 0 ? $this->warehouse_id : 'NULL');
1231 $sql .= ", ".$this->availability_id;
1232 $sql .= ", ".$this->demand_reason_id;
1233 $sql .= ", ".($this->fk_project ? $this->fk_project : "null");
1234 $sql .= ", ".(int) $this->fk_incoterms;
1235 $sql .= ", '".$this->db->escape($this->location_incoterms)."'";
1236 $sql .= ", ".(int) $this->entity;
1237 $sql .= ", ".(int) $this->fk_multicurrency;
1238 $sql .= ", '".$this->db->escape($this->multicurrency_code)."'";
1239 $sql .= ", ".(float) $this->multicurrency_tx;
1240 $sql .= ")";
1241
1242 dol_syslog(get_class($this)."::create", LOG_DEBUG);
1243 $resql = $this->db->query($sql);
1244 if ($resql) {
1245 $this->id = $this->db->last_insert_id(MAIN_DB_PREFIX."propal");
1246
1247 if ($this->id) {
1248 $this->ref = '(PROV'.$this->id.')';
1249 $sql = 'UPDATE '.MAIN_DB_PREFIX."propal SET ref='".$this->db->escape($this->ref)."' WHERE rowid=".((int) $this->id);
1250
1251 dol_syslog(get_class($this)."::create", LOG_DEBUG);
1252 $resql = $this->db->query($sql);
1253 if (!$resql) {
1254 $error++;
1255 }
1256
1257 if (!empty($this->linkedObjectsIds) && empty($this->linked_objects)) { // To use new linkedObjectsIds instead of old linked_objects
1258 $this->linked_objects = $this->linkedObjectsIds; // TODO Replace linked_objects with linkedObjectsIds
1259 }
1260
1261 // Add object linked
1262 if (!$error && $this->id && !empty($this->linked_objects) && is_array($this->linked_objects)) {
1263 foreach ($this->linked_objects as $origin => $tmp_origin_id) {
1264 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, ...))
1265 foreach ($tmp_origin_id as $origin_id) {
1266 $ret = $this->add_object_linked($origin, $origin_id);
1267 if (!$ret) {
1268 $this->error = $this->db->lasterror();
1269 $error++;
1270 }
1271 }
1272 } else { // Old behaviour, if linked_object has only one link per type, so is something like array('contract'=>id1))
1273 $origin_id = $tmp_origin_id;
1274 $ret = $this->add_object_linked($origin, $origin_id);
1275 if (!$ret) {
1276 $this->error = $this->db->lasterror();
1277 $error++;
1278 }
1279 }
1280 }
1281 }
1282
1283 /*
1284 * Insertion du detail des produits dans la base
1285 * Insert products detail in database
1286 */
1287 if (!$error) {
1288 $fk_parent_line = 0;
1289 $num = count($this->lines);
1290
1291 for ($i = 0; $i < $num; $i++) {
1292 if (!is_object($this->lines[$i])) { // If this->lines is not array of objects, coming from REST API
1293 // Convert into object this->lines[$i].
1294 $line = (object) $this->lines[$i];
1295 } else {
1296 $line = $this->lines[$i];
1297 }
1298 // Reset fk_parent_line for line that are not child lines or special product
1299 if (($line->product_type != 9 && empty($line->fk_parent_line)) || $line->product_type == 9) {
1300 $fk_parent_line = 0;
1301 }
1302 // Complete vat rate with code
1303 $vatrate = $line->tva_tx;
1304 if ($line->vat_src_code && !preg_match('/\‍(.*\‍)/', $vatrate)) {
1305 $vatrate .= ' ('.$line->vat_src_code.')';
1306 }
1307
1308 if (getDolGlobalString('MAIN_CREATEFROM_KEEP_LINE_ORIGIN_INFORMATION')) {
1309 $originid = $line->origin_id;
1310 $origintype = $line->origin;
1311 } else {
1312 $originid = $line->id;
1313 $origintype = $this->element;
1314 }
1315
1316 $result = $this->addline(
1317 $line->desc,
1318 $line->subprice,
1319 $line->qty,
1320 $vatrate,
1321 $line->localtax1_tx,
1322 $line->localtax2_tx,
1323 $line->fk_product,
1324 $line->remise_percent,
1325 'HT',
1326 0,
1327 $line->info_bits,
1328 $line->product_type,
1329 $line->rang,
1330 $line->special_code,
1331 $fk_parent_line,
1332 $line->fk_fournprice,
1333 $line->pa_ht,
1334 $line->label,
1335 $line->date_start,
1336 $line->date_end,
1337 $line->array_options,
1338 $line->fk_unit,
1339 $origintype,
1340 $originid,
1341 0,
1342 0,
1343 1
1344 );
1345
1346 if ($result < 0) {
1347 $error++;
1348 $this->error = $this->db->error;
1349 dol_print_error($this->db);
1350 break;
1351 }
1352
1353 // Set the id on created row
1354 $line->id = $result;
1355
1356 // Defined the new fk_parent_line
1357 if ($result > 0 && $line->product_type == 9) {
1358 $fk_parent_line = $result;
1359 }
1360 }
1361 }
1362
1363 // Set delivery address
1364 /*if (! $error && $this->fk_delivery_address)
1365 {
1366 $sql = "UPDATE ".MAIN_DB_PREFIX."propal";
1367 $sql.= " SET fk_delivery_address = ".((int) $this->fk_delivery_address);
1368 $sql.= " WHERE ref = '".$this->db->escape($this->ref)."'";
1369 $sql.= " AND entity = ".setEntity($this);
1370
1371 $result=$this->db->query($sql);
1372 }*/
1373
1374 if (!$error) {
1375 // Mise a jour infos denormalisees
1376 $resql = $this->update_price(1, 'auto', 0, $mysoc);
1377 if ($resql) {
1378 $action = 'update';
1379
1380 // Actions on extra fields
1381 if (!$error) {
1382 $result = $this->insertExtraFields();
1383 if ($result < 0) {
1384 $error++;
1385 }
1386 }
1387
1388 if (!$error && !$notrigger) {
1389 // Call trigger
1390 $result = $this->call_trigger('PROPAL_CREATE', $user);
1391 if ($result < 0) {
1392 $error++;
1393 }
1394 // End call triggers
1395 }
1396 } else {
1397 $this->error = $this->db->lasterror();
1398 $error++;
1399 }
1400 }
1401 } else {
1402 $this->error = $this->db->lasterror();
1403 $error++;
1404 }
1405
1406 if (!$error) {
1407 $this->db->commit();
1408 dol_syslog(get_class($this)."::create done id=".$this->id);
1409 return $this->id;
1410 } else {
1411 $this->db->rollback();
1412 return -2;
1413 }
1414 } else {
1415 $this->error = $this->db->lasterror();
1416 $this->db->rollback();
1417 return -1;
1418 }
1419 }
1420
1431 public function createFromClone(User $user, $socid = 0, $forceentity = null, $update_prices = false, $update_desc = false)
1432 {
1433 global $conf, $hookmanager, $mysoc;
1434
1435 dol_include_once('/projet/class/project.class.php');
1436
1437 $error = 0;
1438 $now = dol_now();
1439
1440 dol_syslog(__METHOD__, LOG_DEBUG);
1441
1442 $object = new self($this->db);
1443
1444 $this->db->begin();
1445
1446 // Load source object
1447 $object->fetch($this->id);
1448
1449 $objsoc = new Societe($this->db);
1450
1451 // Change socid if needed
1452 if (!empty($socid) && $socid != $object->socid) {
1453 if ($objsoc->fetch($socid) > 0) {
1454 $object->socid = $objsoc->id;
1455 $object->cond_reglement_id = (!empty($objsoc->cond_reglement_id) ? $objsoc->cond_reglement_id : 0);
1456 $object->deposit_percent = (!empty($objsoc->deposit_percent) ? $objsoc->deposit_percent : null);
1457 $object->mode_reglement_id = (!empty($objsoc->mode_reglement_id) ? $objsoc->mode_reglement_id : 0);
1458 $object->fk_delivery_address = 0;
1459
1460 /*if (isModEnabled('project'))
1461 {
1462 $project = new Project($db);
1463 if ($this->fk_project > 0 && $project->fetch($this->fk_project)) {
1464 if ($project->socid <= 0) $clonedObj->fk_project = $this->fk_project;
1465 else $clonedObj->fk_project = '';
1466 } else {
1467 $clonedObj->fk_project = '';
1468 }
1469 }*/
1470 $object->fk_project = 0; // A cloned proposal is set by default to no project.
1471 }
1472
1473 // reset ref_client
1474 if (!getDolGlobalString('MAIN_KEEP_REF_CUSTOMER_ON_CLONING')) {
1475 $object->ref_client = '';
1476 $object->ref_customer = '';
1477 }
1478
1479 // TODO Change product price if multi-prices
1480 } else {
1481 $objsoc->fetch($object->socid);
1482 }
1483
1484 // update prices
1485 if ($update_prices === true || $update_desc === true) {
1486 if ($objsoc->id > 0 && !empty($object->lines)) {
1487 if ($update_prices === true && getDolGlobalString('PRODUIT_CUSTOMER_PRICES')) {
1488 // If price per customer
1489 require_once DOL_DOCUMENT_ROOT . '/product/class/productcustomerprice.class.php';
1490 }
1491
1492 foreach ($object->lines as $line) {
1493 $line->id = 0;
1494
1495 if ($line->fk_product > 0) {
1496 $prod = new Product($this->db);
1497 $res = $prod->fetch($line->fk_product);
1498 if ($res > 0) {
1499 if ($update_prices === true) {
1500 $pu_ht = $prod->price;
1501 $tva_tx = (string) get_default_tva($mysoc, $objsoc, $prod->id);
1502 $remise_percent = $objsoc->remise_percent;
1503
1504 if (getDolGlobalString('PRODUIT_MULTIPRICES') && $objsoc->price_level > 0) {
1505 $pu_ht = $prod->multiprices[$objsoc->price_level];
1506 if (getDolGlobalString('PRODUIT_MULTIPRICES_USE_VAT_PER_LEVEL')) { // using this option is a bug. kept for backward compatibility
1507 if (isset($prod->multiprices_tva_tx[$objsoc->price_level])) {
1508 $tva_tx = (string) $prod->multiprices_tva_tx[$objsoc->price_level];
1509 }
1510 }
1511 } elseif (getDolGlobalString('PRODUIT_CUSTOMER_PRICES')) {
1512 $prodcustprice = new ProductCustomerPrice($this->db);
1513 $filter = array('t.fk_product' => $prod->id, 't.fk_soc' => $objsoc->id);
1514 $result = $prodcustprice->fetchAll('', '', 0, 0, $filter);
1515 if ($result) {
1516 // If there is some prices specific to the customer
1517 if (count($prodcustprice->lines) > 0) {
1518 $pu_ht = price($prodcustprice->lines[0]->price);
1519 $tva_tx = ($prodcustprice->lines[0]->default_vat_code ? $prodcustprice->lines[0]->tva_tx.' ('.$prodcustprice->lines[0]->default_vat_code.' )' : $prodcustprice->lines[0]->tva_tx);
1520 if ($prodcustprice->lines[0]->default_vat_code && !preg_match('/\‍(.*\‍)/', $tva_tx)) {
1521 $tva_tx .= ' ('.$prodcustprice->lines[0]->default_vat_code.')';
1522 }
1523 }
1524 }
1525 }
1526
1527 $line->subprice = $pu_ht;
1528 $line->tva_tx = $tva_tx;
1529 $line->remise_percent = $remise_percent;
1530 }
1531 if ($update_desc === true) {
1532 $line->desc = $prod->description;
1533 }
1534 }
1535 }
1536 }
1537 }
1538 }
1539
1540 $object->id = 0;
1541 $object->ref = '';
1542 $object->entity = (!empty($forceentity) ? $forceentity : $object->entity);
1543 $object->statut = self::STATUS_DRAFT;
1544
1545 // Clear fields
1546 $object->user_creation_id = $user->id;
1547 $object->user_validation_id = 0;
1548 $object->date = $now;
1549 $object->datep = $now; // deprecated
1550 $object->fin_validite = $object->date + ($object->duree_validite * 24 * 3600);
1551 if (!getDolGlobalString('MAIN_KEEP_REF_CUSTOMER_ON_CLONING')) {
1552 $object->ref_client = '';
1553 $object->ref_customer = '';
1554 }
1555 if (getDolGlobalInt('MAIN_DONT_KEEP_NOTE_ON_CLONING') == 1) {
1556 $object->note_private = '';
1557 $object->note_public = '';
1558 }
1559 // Create clone
1560 $object->context['createfromclone'] = 'createfromclone';
1561 $result = $object->create($user);
1562 if ($result < 0) {
1563 $this->error = $object->error;
1564 $this->errors = array_merge($this->errors, $object->errors);
1565 $error++;
1566 }
1567
1568 if (!$error && !getDolGlobalInt('MAIN_IGNORE_CONTACTS_ON_CLONING')) {
1569 // copy internal contacts
1570 if ($object->copy_linked_contact($this, 'internal') < 0) {
1571 $error++;
1572 }
1573 }
1574
1575 if (!$error) {
1576 // copy external contacts if same company
1577 if ($this->socid == $object->socid) {
1578 if ($object->copy_linked_contact($this, 'external') < 0) {
1579 $error++;
1580 }
1581 }
1582 }
1583
1584 if (!$error) {
1585 // Hook of thirdparty module
1586 if (is_object($hookmanager)) {
1587 $parameters = array('objFrom' => $this, 'clonedObj' => $object);
1588 $action = '';
1589 $reshook = $hookmanager->executeHooks('createFrom', $parameters, $object, $action); // Note that $action and $object may have been modified by some hooks
1590 if ($reshook < 0) {
1591 $this->setErrorsFromObject($hookmanager);
1592 $error++;
1593 }
1594 }
1595 }
1596
1597 unset($object->context['createfromclone']);
1598
1599 // End
1600 if (!$error) {
1601 $this->db->commit();
1602 return $object->id;
1603 } else {
1604 $this->db->rollback();
1605 return -1;
1606 }
1607 }
1608
1618 public function fetch($rowid, $ref = '', $ref_ext = '', $forceentity = 0)
1619 {
1620 $sql = "SELECT p.rowid, p.ref, p.entity, p.fk_soc";
1621 $sql .= ", p.total_ttc, p.total_tva, p.localtax1, p.localtax2, p.total_ht";
1622 $sql .= ", p.datec";
1623 $sql .= ", p.date_signature as dates";
1624 $sql .= ", p.date_valid as datev";
1625 $sql .= ", p.datep as dp";
1626 $sql .= ", p.fin_validite as dfv";
1627 $sql .= ", p.date_livraison as delivery_date";
1628 $sql .= ", p.model_pdf, p.last_main_doc, p.ref_client, ref_ext, p.extraparams";
1629 $sql .= ", p.note_private, p.note_public";
1630 $sql .= ", p.fk_projet as fk_project, p.fk_statut";
1631 $sql .= ", p.fk_user_author, p.fk_user_valid, p.fk_user_cloture";
1632 $sql .= ", p.fk_delivery_address";
1633 $sql .= ", p.fk_availability";
1634 $sql .= ", p.fk_input_reason";
1635 $sql .= ", p.fk_cond_reglement";
1636 $sql .= ", p.fk_mode_reglement";
1637 $sql .= ', p.fk_account';
1638 $sql .= ", p.fk_shipping_method";
1639 $sql .= ", p.fk_warehouse";
1640 $sql .= ", p.fk_incoterms, p.location_incoterms";
1641 $sql .= ", p.fk_multicurrency, p.multicurrency_code, p.multicurrency_tx, p.multicurrency_total_ht, p.multicurrency_total_tva, p.multicurrency_total_ttc";
1642 $sql .= ", p.tms as date_modification";
1643 $sql .= ", i.libelle as label_incoterms";
1644 $sql .= ", c.label as statut_label";
1645 $sql .= ", ca.code as availability_code, ca.label as availability";
1646 $sql .= ", dr.code as demand_reason_code, dr.label as demand_reason";
1647 $sql .= ", cr.code as cond_reglement_code, cr.libelle as cond_reglement, cr.libelle_facture as cond_reglement_libelle_doc, p.deposit_percent";
1648 $sql .= ", cp.code as mode_reglement_code, cp.libelle as mode_reglement";
1649 $sql .= " FROM ".MAIN_DB_PREFIX."propal as p";
1650 $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_propalst as c ON p.fk_statut = c.id';
1651 $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_paiement as cp ON p.fk_mode_reglement = cp.id AND cp.entity IN ('.getEntity('c_paiement').')';
1652 $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').')';
1653 $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_availability as ca ON p.fk_availability = ca.rowid';
1654 $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_input_reason as dr ON p.fk_input_reason = dr.rowid';
1655 $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_incoterms as i ON p.fk_incoterms = i.rowid';
1656
1657 if (!empty($ref)) {
1658 if (!empty($forceentity)) {
1659 $sql .= " WHERE p.entity = ".(int) $forceentity; // Check only the current entity because we may have the same reference in several entities
1660 } else {
1661 $sql .= " WHERE p.entity IN (".getEntity('propal').")";
1662 }
1663 $sql .= " AND p.ref='".$this->db->escape($ref)."'";
1664 } else {
1665 // Don't use entity if you use rowid
1666 $sql .= " WHERE p.rowid = ".((int) $rowid);
1667 }
1668
1669 dol_syslog(get_class($this)."::fetch", LOG_DEBUG);
1670 $resql = $this->db->query($sql);
1671 if ($resql) {
1672 if ($this->db->num_rows($resql)) {
1673 $obj = $this->db->fetch_object($resql);
1674
1675 $this->id = $obj->rowid;
1676 $this->entity = $obj->entity;
1677
1678 $this->ref = $obj->ref;
1679 $this->ref_client = $obj->ref_client;
1680 $this->ref_customer = $obj->ref_client;
1681 $this->ref_ext = $obj->ref_ext;
1682
1683 $this->total = $obj->total_ttc; // TODO deprecated
1684 $this->total_ttc = $obj->total_ttc;
1685 $this->total_ht = $obj->total_ht;
1686 $this->total_tva = $obj->total_tva;
1687 $this->total_localtax1 = $obj->localtax1;
1688 $this->total_localtax2 = $obj->localtax2;
1689
1690 $this->socid = $obj->fk_soc;
1691 $this->thirdparty = null; // Clear if another value was already set by fetch_thirdparty
1692
1693 $this->fk_project = $obj->fk_project;
1694 $this->project = null; // Clear if another value was already set by fetchProject
1695
1696 $this->model_pdf = $obj->model_pdf;
1697 $this->last_main_doc = $obj->last_main_doc;
1698 $this->note = $obj->note_private; // TODO deprecated
1699 $this->note_private = $obj->note_private;
1700 $this->note_public = $obj->note_public;
1701
1702 $this->status = (int) $obj->fk_statut;
1703 $this->statut = $this->status; // deprecated
1704
1705 $this->datec = $this->db->jdate($obj->datec); // TODO deprecated
1706 $this->datev = $this->db->jdate($obj->datev); // TODO deprecated
1707 $this->date_creation = $this->db->jdate($obj->datec); //Creation date
1708 $this->date_validation = $this->db->jdate($obj->datev); //Validation date
1709 $this->date_modification = $this->db->jdate($obj->date_modification); // tms
1710 $this->date_signature = $this->db->jdate($obj->dates); // Signature date
1711 $this->date = $this->db->jdate($obj->dp); // Proposal date
1712 $this->datep = $this->db->jdate($obj->dp); // deprecated
1713 $this->fin_validite = $this->db->jdate($obj->dfv);
1714 $this->delivery_date = $this->db->jdate($obj->delivery_date);
1715 $this->shipping_method_id = ($obj->fk_shipping_method > 0) ? $obj->fk_shipping_method : null;
1716 $this->warehouse_id = ($obj->fk_warehouse > 0) ? $obj->fk_warehouse : null;
1717 $this->availability_id = $obj->fk_availability;
1718 $this->availability_code = $obj->availability_code;
1719 $this->availability = $obj->availability;
1720 $this->demand_reason_id = $obj->fk_input_reason;
1721 $this->demand_reason_code = $obj->demand_reason_code;
1722 $this->demand_reason = $obj->demand_reason;
1723 $this->fk_address = $obj->fk_delivery_address;
1724
1725 $this->mode_reglement_id = $obj->fk_mode_reglement;
1726 $this->mode_reglement_code = $obj->mode_reglement_code;
1727 $this->mode_reglement = $obj->mode_reglement;
1728 $this->fk_account = ($obj->fk_account > 0) ? $obj->fk_account : null;
1729 $this->cond_reglement_id = $obj->fk_cond_reglement;
1730 $this->cond_reglement_code = $obj->cond_reglement_code;
1731 $this->cond_reglement = $obj->cond_reglement;
1732 $this->cond_reglement_doc = $obj->cond_reglement_libelle_doc;
1733 $this->deposit_percent = $obj->deposit_percent;
1734
1735 $this->extraparams = !empty($obj->extraparams) ? (array) json_decode($obj->extraparams, true) : array();
1736
1737 $this->user_author_id = $obj->fk_user_author;
1738 $this->user_validation_id = $obj->fk_user_valid;
1739 $this->user_closing_id = $obj->fk_user_cloture;
1740
1741 //Incoterms
1742 $this->fk_incoterms = $obj->fk_incoterms;
1743 $this->location_incoterms = $obj->location_incoterms;
1744 $this->label_incoterms = $obj->label_incoterms;
1745
1746 // Multicurrency
1747 $this->fk_multicurrency = $obj->fk_multicurrency;
1748 $this->multicurrency_code = $obj->multicurrency_code;
1749 $this->multicurrency_tx = $obj->multicurrency_tx;
1750 $this->multicurrency_total_ht = $obj->multicurrency_total_ht;
1751 $this->multicurrency_total_tva = $obj->multicurrency_total_tva;
1752 $this->multicurrency_total_ttc = $obj->multicurrency_total_ttc;
1753
1754 // Retrieve all extrafield
1755 // fetch optionals attributes and labels
1756 $this->fetch_optionals();
1757
1758 $this->db->free($resql);
1759
1760 $this->lines = array();
1761
1762 // Lines
1763 $result = $this->fetch_lines();
1764 if ($result < 0) {
1765 return -3;
1766 }
1767
1768 return 1;
1769 }
1770
1771 $this->error = "Record Not Found";
1772 return 0;
1773 } else {
1774 $this->error = $this->db->lasterror();
1775 return -1;
1776 }
1777 }
1778
1786 public function update(User $user, $notrigger = 0)
1787 {
1788 global $conf;
1789
1790 $error = 0;
1791
1792 // Clean parameters
1793 if (isset($this->ref)) {
1794 $this->ref = trim($this->ref);
1795 }
1796 if (isset($this->ref_client)) {
1797 $this->ref_client = trim($this->ref_client);
1798 }
1799 if (isset($this->note) || isset($this->note_private)) {
1800 $this->note_private = (isset($this->note_private) ? trim($this->note_private) : trim($this->note));
1801 }
1802 if (isset($this->note_public)) {
1803 $this->note_public = trim($this->note_public);
1804 }
1805 if (isset($this->model_pdf)) {
1806 $this->model_pdf = trim($this->model_pdf);
1807 }
1808 if (isset($this->import_key)) {
1809 $this->import_key = trim($this->import_key);
1810 }
1811 if (!empty($this->duree_validite) && is_numeric($this->duree_validite)) {
1812 $this->fin_validite = $this->date + ($this->duree_validite * 24 * 3600);
1813 }
1814
1815 // Check parameters
1816 // Put here code to add control on parameters values
1817
1818 // Update request
1819 $sql = "UPDATE ".MAIN_DB_PREFIX."propal SET";
1820 $sql .= " ref = ".(isset($this->ref) ? "'".$this->db->escape($this->ref)."'" : "null").",";
1821 $sql .= " ref_client = ".(isset($this->ref_client) ? "'".$this->db->escape($this->ref_client)."'" : "null").",";
1822 $sql .= " ref_ext = ".(isset($this->ref_ext) ? "'".$this->db->escape($this->ref_ext)."'" : "null").",";
1823 $sql .= " fk_soc = ".(!empty($this->socid) ? (int) $this->socid : "null").",";
1824 $sql .= " datep = ".(strval($this->date) != '' ? "'".$this->db->idate($this->date)."'" : 'null').",";
1825 if (!empty($this->fin_validite)) {
1826 $sql .= " fin_validite = ".(strval($this->fin_validite) != '' ? "'".$this->db->idate($this->fin_validite)."'" : 'null').",";
1827 }
1828 $sql .= " date_valid = ".(strval($this->date_validation) != '' ? "'".$this->db->idate($this->date_validation)."'" : 'null').",";
1829 $sql .= " total_tva = ".(isset($this->total_tva) ? (float) $this->total_tva : "null").",";
1830 $sql .= " localtax1 = ".(isset($this->total_localtax1) ? (float) $this->total_localtax1 : "null").",";
1831 $sql .= " localtax2 = ".(isset($this->total_localtax2) ? (float) $this->total_localtax2 : "null").",";
1832 $sql .= " total_ht = ".(isset($this->total_ht) ? (float) $this->total_ht : "null").",";
1833 $sql .= " total_ttc = ".(isset($this->total_ttc) ? (float) $this->total_ttc : "null").",";
1834 $sql .= " fk_statut = ".(isset($this->status) ? (int) $this->status : "null").",";
1835 $sql .= " fk_user_author = ".(!empty($this->user_author_id) ? (int) $this->user_author_id : "null").",";
1836 $sql .= " fk_user_valid = ".(!empty($this->user_validation_id) ? (int) $this->user_validation_id : "null").",";
1837
1838 $sql .= " fk_projet = ".(!empty($this->fk_project) ? (int) $this->fk_project : "null").",";
1839 $sql .= " fk_cond_reglement = ".(!empty($this->cond_reglement_id) ? (int) $this->cond_reglement_id : "null").",";
1840 $sql .= " deposit_percent = ".(!empty($this->deposit_percent) ? "'".$this->db->escape($this->deposit_percent)."'" : "null").",";
1841 $sql .= " fk_mode_reglement = ".(!empty($this->mode_reglement_id) ? (int) $this->mode_reglement_id : "null").",";
1842 $sql .= " fk_input_reason = ".(!empty($this->demand_reason_id) ? (int) $this->demand_reason_id : "null").",";
1843 $sql .= " fk_shipping_method=".(isset($this->shipping_method_id) ? (int) $this->shipping_method_id : "null").",";
1844 $sql .= " fk_availability=".(isset($this->availability_id) ? (int) $this->availability_id : "null").",";
1845 $sql .= " note_private = ".(isset($this->note_private) ? "'".$this->db->escape($this->note_private)."'" : "null").",";
1846 $sql .= " note_public = ".(isset($this->note_public) ? "'".$this->db->escape($this->note_public)."'" : "null").",";
1847 $sql .= " model_pdf = ".(isset($this->model_pdf) ? "'".$this->db->escape($this->model_pdf)."'" : "null").",";
1848 $sql .= " import_key = ".(isset($this->import_key) ? "'".$this->db->escape($this->import_key)."'" : "null");
1849 $sql .= " WHERE rowid = ".((int) $this->id);
1850
1851 $this->db->begin();
1852
1853 dol_syslog(get_class($this)."::update", LOG_DEBUG);
1854 $resql = $this->db->query($sql);
1855 if (!$resql) {
1856 $error++;
1857 $this->errors[] = "Error ".$this->db->lasterror();
1858 }
1859
1860 if (!$error) {
1861 $result = $this->insertExtraFields();
1862 if ($result < 0) {
1863 $error++;
1864 }
1865 }
1866
1867 if (!$error && !$notrigger) {
1868 // Call trigger
1869 $result = $this->call_trigger('PROPAL_MODIFY', $user);
1870 if ($result < 0) {
1871 $error++;
1872 }
1873 // End call triggers
1874 }
1875
1876 // Commit or rollback
1877 if ($error) {
1878 foreach ($this->errors as $errmsg) {
1879 dol_syslog(get_class($this)."::update ".$errmsg, LOG_ERR);
1880 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
1881 }
1882 $this->db->rollback();
1883 return -1 * $error;
1884 } else {
1885 $this->db->commit();
1886 return 1;
1887 }
1888 }
1889
1890
1891 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1900 public function fetch_lines($only_product = 0, $loadalsotranslation = 0, $sqlforgedfilters = '')
1901 {
1902 // phpcs:enable
1903 $this->lines = array();
1904
1905 $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,';
1906 $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,';
1907 $sql .= ' d.fk_unit,';
1908 $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,';
1909 $sql .= ' p.weight, p.weight_units, p.volume, p.volume_units,';
1910 $sql .= ' d.date_start, d.date_end,';
1911 $sql .= ' d.fk_multicurrency, d.multicurrency_code, d.multicurrency_subprice, d.multicurrency_total_ht, d.multicurrency_total_tva, d.multicurrency_total_ttc';
1912 $sql .= ' FROM '.MAIN_DB_PREFIX.'propaldet as d';
1913 $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'product as p ON (d.fk_product = p.rowid)';
1914 $sql .= ' WHERE d.fk_propal = '.((int) $this->id);
1915 if ($only_product) {
1916 $sql .= ' AND p.fk_product_type = 0';
1917 }
1918 if ($sqlforgedfilters) {
1919 $sql .= $sqlforgedfilters;
1920 }
1921 $sql .= ' ORDER by d.rang';
1922
1923 dol_syslog(get_class($this)."::fetch_lines", LOG_DEBUG);
1924 $result = $this->db->query($sql);
1925 if ($result) {
1926 require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php';
1927
1928 $num = $this->db->num_rows($result);
1929
1930 $i = 0;
1931 while ($i < $num) {
1932 $objp = $this->db->fetch_object($result);
1933
1934 $line = new PropaleLigne($this->db);
1935
1936 $line->rowid = $objp->rowid; //Deprecated
1937 $line->id = $objp->rowid;
1938 $line->fk_propal = $objp->fk_propal;
1939 $line->fk_parent_line = $objp->fk_parent_line;
1940 $line->product_type = $objp->product_type;
1941 $line->label = $objp->custom_label;
1942 $line->desc = $objp->description; // Description ligne
1943 $line->description = $objp->description; // Description ligne
1944 $line->qty = $objp->qty;
1945 $line->vat_src_code = $objp->vat_src_code;
1946 $line->tva_tx = $objp->tva_tx;
1947 $line->localtax1_tx = $objp->localtax1_tx;
1948 $line->localtax2_tx = $objp->localtax2_tx;
1949 $line->localtax1_type = $objp->localtax1_type;
1950 $line->localtax2_type = $objp->localtax2_type;
1951 $line->subprice = $objp->subprice;
1952 $line->fk_remise_except = $objp->fk_remise_except;
1953 $line->remise_percent = $objp->remise_percent;
1954 $line->price = $objp->price; // TODO deprecated
1955
1956 $line->info_bits = $objp->info_bits;
1957 $line->total_ht = $objp->total_ht;
1958 $line->total_tva = $objp->total_tva;
1959 $line->total_localtax1 = $objp->total_localtax1;
1960 $line->total_localtax2 = $objp->total_localtax2;
1961 $line->total_ttc = $objp->total_ttc;
1962 $line->fk_fournprice = $objp->fk_fournprice;
1963 $marginInfos = getMarginInfos($objp->subprice, $objp->remise_percent, $objp->tva_tx, $objp->localtax1_tx, $objp->localtax2_tx, $line->fk_fournprice, $objp->pa_ht);
1964 $line->pa_ht = $marginInfos[0];
1965 $line->marge_tx = $marginInfos[1];
1966 $line->marque_tx = $marginInfos[2];
1967 $line->special_code = $objp->special_code;
1968 $line->rang = $objp->rang;
1969
1970 $line->fk_product = $objp->fk_product;
1971
1972 $line->ref = $objp->product_ref; // deprecated
1973 $line->libelle = $objp->product_label; // deprecated
1974
1975 $line->product_ref = $objp->product_ref;
1976 $line->product_label = $objp->product_label;
1977 $line->product_desc = $objp->product_desc; // Description produit
1978 $line->product_tobatch = $objp->product_tobatch;
1979 $line->product_barcode = $objp->product_barcode;
1980
1981 $line->fk_product_type = $objp->fk_product_type; // deprecated
1982 $line->fk_unit = $objp->fk_unit;
1983 $line->weight = $objp->weight;
1984 $line->weight_units = $objp->weight_units;
1985 $line->volume = $objp->volume;
1986 $line->volume_units = $objp->volume_units;
1987
1988 $line->date_start = $this->db->jdate($objp->date_start);
1989 $line->date_end = $this->db->jdate($objp->date_end);
1990
1991 // Multicurrency
1992 $line->fk_multicurrency = $objp->fk_multicurrency;
1993 $line->multicurrency_code = $objp->multicurrency_code;
1994 $line->multicurrency_subprice = $objp->multicurrency_subprice;
1995 $line->multicurrency_total_ht = $objp->multicurrency_total_ht;
1996 $line->multicurrency_total_tva = $objp->multicurrency_total_tva;
1997 $line->multicurrency_total_ttc = $objp->multicurrency_total_ttc;
1998
1999 $line->fetch_optionals();
2000
2001 // multilangs
2002 if (getDolGlobalInt('MAIN_MULTILANGS') && !empty($objp->fk_product) && !empty($loadalsotranslation)) {
2003 $tmpproduct = new Product($this->db);
2004 $tmpproduct->fetch($objp->fk_product);
2005 $tmpproduct->getMultiLangs();
2006
2007 $line->multilangs = $tmpproduct->multilangs;
2008 }
2009
2010 $this->lines[$i] = $line;
2011
2012 $i++;
2013 }
2014
2015 $this->db->free($result);
2016
2017 return $num;
2018 } else {
2019 $this->error = $this->db->lasterror();
2020 return -3;
2021 }
2022 }
2023
2031 public function valid($user, $notrigger = 0)
2032 {
2033 global $conf;
2034
2035 require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
2036
2037 $error = 0;
2038
2039 // Protection
2040 if ($this->status == self::STATUS_VALIDATED) {
2041 dol_syslog(get_class($this)."::valid action abandoned: already validated", LOG_WARNING);
2042 return 0;
2043 }
2044
2045 if (!((!getDolGlobalString('MAIN_USE_ADVANCED_PERMS') && $user->hasRight('propal', 'creer'))
2046 || (getDolGlobalString('MAIN_USE_ADVANCED_PERMS') && $user->hasRight('propal', 'propal_advance', 'validate')))) {
2047 $this->error = 'ErrorPermissionDenied';
2048 dol_syslog(get_class($this)."::valid ".$this->error, LOG_ERR);
2049 return -1;
2050 }
2051
2052 $now = dol_now();
2053
2054 $this->db->begin();
2055
2056 // Numbering module definition
2057 $soc = new Societe($this->db);
2058 $soc->fetch($this->socid);
2059
2060 // Define new ref
2061 if (!$error && (preg_match('/^[\‍(]?PROV/i', $this->ref) || empty($this->ref))) { // empty should not happened, but when it occurs, the test save life
2062 $num = $this->getNextNumRef($soc);
2063 } else {
2064 $num = $this->ref;
2065 }
2066 $this->newref = dol_sanitizeFileName($num);
2067
2068 $sql = "UPDATE ".MAIN_DB_PREFIX."propal";
2069 $sql .= " SET ref = '".$this->db->escape($num)."',";
2070 $sql .= " fk_statut = ".self::STATUS_VALIDATED.", date_valid='".$this->db->idate($now)."', fk_user_valid=".((int) $user->id);
2071 $sql .= " WHERE rowid = ".((int) $this->id)." AND fk_statut = ".self::STATUS_DRAFT;
2072
2073 dol_syslog(get_class($this)."::valid", LOG_DEBUG);
2074 $resql = $this->db->query($sql);
2075 if (!$resql) {
2076 dol_print_error($this->db);
2077 $error++;
2078 }
2079
2080 // Trigger calls
2081 if (!$error && !$notrigger) {
2082 // Call trigger
2083 $result = $this->call_trigger('PROPAL_VALIDATE', $user);
2084 if ($result < 0) {
2085 $error++;
2086 }
2087 // End call triggers
2088 }
2089
2090 if (!$error) {
2091 $this->oldref = $this->ref;
2092
2093 // Rename directory if dir was a temporary ref
2094 if (preg_match('/^[\‍(]?PROV/i', $this->ref)) {
2095 // Now we rename also files into index
2096 $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)."'";
2097 $sql .= " WHERE filename LIKE '".$this->db->escape($this->ref)."%' AND filepath = 'propale/".$this->db->escape($this->ref)."' and entity = ".((int) $conf->entity);
2098 $resql = $this->db->query($sql);
2099 if (!$resql) {
2100 $error++;
2101 $this->error = $this->db->lasterror();
2102 }
2103 $sql = 'UPDATE '.MAIN_DB_PREFIX."ecm_files set filepath = 'propale/".$this->db->escape($this->newref)."'";
2104 $sql .= " WHERE filepath = 'propale/".$this->db->escape($this->ref)."' and entity = ".$conf->entity;
2105 $resql = $this->db->query($sql);
2106 if (!$resql) {
2107 $error++;
2108 $this->error = $this->db->lasterror();
2109 }
2110
2111 // We rename directory ($this->ref = old ref, $num = new ref) in order not to lose the attachments
2112 $oldref = dol_sanitizeFileName($this->ref);
2113 $newref = dol_sanitizeFileName($num);
2114 $dirsource = $conf->propal->multidir_output[$this->entity].'/'.$oldref;
2115 $dirdest = $conf->propal->multidir_output[$this->entity].'/'.$newref;
2116 if (!$error && file_exists($dirsource)) {
2117 dol_syslog(get_class($this)."::validate rename dir ".$dirsource." into ".$dirdest);
2118 if (@rename($dirsource, $dirdest)) {
2119 dol_syslog("Rename ok");
2120 // Rename docs starting with $oldref with $newref
2121 $listoffiles = dol_dir_list($dirdest, 'files', 1, '^'.preg_quote($oldref, '/'));
2122 foreach ($listoffiles as $fileentry) {
2123 $dirsource = $fileentry['name'];
2124 $dirdest = preg_replace('/^'.preg_quote($oldref, '/').'/', $newref, $dirsource);
2125 $dirsource = $fileentry['path'].'/'.$dirsource;
2126 $dirdest = $fileentry['path'].'/'.$dirdest;
2127 @rename($dirsource, $dirdest);
2128 }
2129 }
2130 }
2131 }
2132
2133 $this->ref = $num;
2134 $this->statut = self::STATUS_VALIDATED;
2135 $this->user_validation_id = $user->id;
2136 $this->datev = $now;
2137 $this->date_validation = $now;
2138
2139 $this->db->commit();
2140 return 1;
2141 } else {
2142 $this->db->rollback();
2143 return -1;
2144 }
2145 }
2146
2147
2148 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2157 public function set_date($user, $date, $notrigger = 0)
2158 {
2159 // phpcs:enable
2160 if (empty($date)) {
2161 $this->error = 'ErrorBadParameter';
2162 dol_syslog(get_class($this)."::set_date ".$this->error, LOG_ERR);
2163 return -1;
2164 }
2165
2166 if ($user->hasRight('propal', 'creer')) {
2167 $error = 0;
2168
2169 $this->db->begin();
2170
2171 $sql = "UPDATE ".MAIN_DB_PREFIX."propal SET datep = '".$this->db->idate($date)."'";
2172 $sql .= " WHERE rowid = ".((int) $this->id);
2173
2174 dol_syslog(__METHOD__, LOG_DEBUG);
2175 $resql = $this->db->query($sql);
2176 if (!$resql) {
2177 $this->errors[] = $this->db->error();
2178 $error++;
2179 }
2180
2181 if (!$error) {
2182 $this->oldcopy = clone $this;
2183 $this->date = $date;
2184 $this->datep = $date; // deprecated
2185 }
2186
2187 if (!$notrigger && empty($error)) {
2188 // Call trigger
2189 $result = $this->call_trigger('PROPAL_MODIFY', $user);
2190 if ($result < 0) {
2191 $error++;
2192 }
2193 // End call triggers
2194 }
2195
2196 if (!$error) {
2197 $this->db->commit();
2198 return 1;
2199 } else {
2200 foreach ($this->errors as $errmsg) {
2201 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2202 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2203 }
2204 $this->db->rollback();
2205 return -1 * $error;
2206 }
2207 }
2208
2209 return -1;
2210 }
2211
2212 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2221 public function set_echeance($user, $date_end_validity, $notrigger = 0)
2222 {
2223 // phpcs:enable
2224 if ($user->hasRight('propal', 'creer')) {
2225 $error = 0;
2226
2227 $this->db->begin();
2228
2229 $sql = "UPDATE ".MAIN_DB_PREFIX."propal SET fin_validite = ".($date_end_validity != '' ? "'".$this->db->idate($date_end_validity)."'" : 'null');
2230 $sql .= " WHERE rowid = ".((int) $this->id);
2231
2232 dol_syslog(__METHOD__, LOG_DEBUG);
2233
2234 $resql = $this->db->query($sql);
2235 if (!$resql) {
2236 $this->errors[] = $this->db->error();
2237 $error++;
2238 }
2239
2240
2241 if (!$error) {
2242 $this->oldcopy = clone $this;
2243 $this->fin_validite = $date_end_validity;
2244 }
2245
2246 if (!$notrigger && empty($error)) {
2247 // Call trigger
2248 $result = $this->call_trigger('PROPAL_MODIFY', $user);
2249 if ($result < 0) {
2250 $error++;
2251 }
2252 // End call triggers
2253 }
2254
2255 if (!$error) {
2256 $this->db->commit();
2257 return 1;
2258 } else {
2259 foreach ($this->errors as $errmsg) {
2260 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2261 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2262 }
2263 $this->db->rollback();
2264 return -1 * $error;
2265 }
2266 }
2267
2268 return -1;
2269 }
2270
2271 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2281 public function set_date_livraison($user, $delivery_date, $notrigger = 0)
2282 {
2283 // phpcs:enable
2284 return $this->setDeliveryDate($user, $delivery_date, $notrigger);
2285 }
2286
2295 public function setDeliveryDate($user, $delivery_date, $notrigger = 0)
2296 {
2297 if ($user->hasRight('propal', 'creer')) {
2298 $error = 0;
2299
2300 $this->db->begin();
2301
2302 $sql = "UPDATE ".MAIN_DB_PREFIX."propal ";
2303 $sql .= " SET date_livraison = ".(isDolTms($delivery_date) ? "'".$this->db->idate($delivery_date)."'" : 'null');
2304 $sql .= " WHERE rowid = ".((int) $this->id);
2305
2306 dol_syslog(__METHOD__, LOG_DEBUG);
2307 $resql = $this->db->query($sql);
2308 if (!$resql) {
2309 $this->errors[] = $this->db->error();
2310 $error++;
2311 }
2312
2313 if (!$error) {
2314 $this->oldcopy = clone $this;
2315 $this->delivery_date = $delivery_date;
2316 }
2317
2318 if (!$notrigger && empty($error)) {
2319 // Call trigger
2320 $result = $this->call_trigger('PROPAL_MODIFY', $user);
2321 if ($result < 0) {
2322 $error++;
2323 }
2324 // End call triggers
2325 }
2326
2327 if (!$error) {
2328 $this->db->commit();
2329 return 1;
2330 } else {
2331 foreach ($this->errors as $errmsg) {
2332 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2333 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2334 }
2335 $this->db->rollback();
2336 return -1 * $error;
2337 }
2338 }
2339
2340 return -1;
2341 }
2342
2343 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2352 public function set_availability($user, $id, $notrigger = 0)
2353 {
2354 // phpcs:enable
2355 if ($user->hasRight('propal', 'creer') && $this->status >= self::STATUS_DRAFT) {
2356 $error = 0;
2357
2358 $this->db->begin();
2359
2360 $sql = "UPDATE ".MAIN_DB_PREFIX."propal";
2361 $sql .= " SET fk_availability = ".((int) $id);
2362 $sql .= " WHERE rowid = ".((int) $this->id);
2363
2364 dol_syslog(__METHOD__.' availability('.$id.')', LOG_DEBUG);
2365 $resql = $this->db->query($sql);
2366 if (!$resql) {
2367 $this->errors[] = $this->db->error();
2368 $error++;
2369 }
2370
2371 if (!$error) {
2372 $this->oldcopy = clone $this;
2373 $this->fk_availability = $id;
2374 $this->availability_id = $id;
2375 }
2376
2377 if (!$notrigger && empty($error)) {
2378 // Call trigger
2379 $result = $this->call_trigger('PROPAL_MODIFY', $user);
2380 if ($result < 0) {
2381 $error++;
2382 }
2383 // End call triggers
2384 }
2385
2386 if (!$error) {
2387 $this->db->commit();
2388 return 1;
2389 } else {
2390 foreach ($this->errors as $errmsg) {
2391 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2392 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2393 }
2394 $this->db->rollback();
2395 return -1 * $error;
2396 }
2397 } else {
2398 $error_str = 'Propal status do not meet requirement '.$this->status;
2399 dol_syslog(__METHOD__.$error_str, LOG_ERR);
2400 $this->error = $error_str;
2401 $this->errors[] = $this->error;
2402 return -2;
2403 }
2404 }
2405
2406 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2415 public function set_demand_reason($user, $id, $notrigger = 0)
2416 {
2417 // phpcs:enable
2418 if ($user->hasRight('propal', 'creer') && $this->status >= self::STATUS_DRAFT) {
2419 $error = 0;
2420
2421 $this->db->begin();
2422
2423 $sql = "UPDATE ".MAIN_DB_PREFIX."propal ";
2424 $sql .= " SET fk_input_reason = ".((int) $id);
2425 $sql .= " WHERE rowid = ".((int) $this->id);
2426
2427 dol_syslog(__METHOD__, LOG_DEBUG);
2428 $resql = $this->db->query($sql);
2429 if (!$resql) {
2430 $this->errors[] = $this->db->error();
2431 $error++;
2432 }
2433
2434
2435 if (!$error) {
2436 $this->oldcopy = clone $this;
2437
2438 $this->demand_reason_id = $id;
2439 }
2440
2441
2442 if (!$notrigger && empty($error)) {
2443 // Call trigger
2444 $result = $this->call_trigger('PROPAL_MODIFY', $user);
2445 if ($result < 0) {
2446 $error++;
2447 }
2448 // End call triggers
2449 }
2450
2451 if (!$error) {
2452 $this->db->commit();
2453 return 1;
2454 } else {
2455 foreach ($this->errors as $errmsg) {
2456 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2457 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2458 }
2459 $this->db->rollback();
2460 return -1 * $error;
2461 }
2462 } else {
2463 $error_str = 'Propal status do not meet requirement '.$this->status;
2464 dol_syslog(__METHOD__.$error_str, LOG_ERR);
2465 $this->error = $error_str;
2466 $this->errors[] = $this->error;
2467 return -2;
2468 }
2469 }
2470
2471 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2480 public function set_ref_client($user, $ref_client, $notrigger = 0)
2481 {
2482 // phpcs:enable
2483 if ($user->hasRight('propal', 'creer')) {
2484 $error = 0;
2485
2486 $this->db->begin();
2487
2488 $sql = "UPDATE ".MAIN_DB_PREFIX."propal SET ref_client = ".(empty($ref_client) ? 'NULL' : "'".$this->db->escape($ref_client)."'");
2489 $sql .= " WHERE rowid = ".((int) $this->id);
2490
2491 dol_syslog(__METHOD__.' $this->id='.$this->id.', ref_client='.$ref_client, LOG_DEBUG);
2492 $resql = $this->db->query($sql);
2493 if (!$resql) {
2494 $this->errors[] = $this->db->error();
2495 $error++;
2496 }
2497
2498 if (!$error) {
2499 $this->oldcopy = clone $this;
2500 $this->ref_client = $ref_client;
2501 }
2502
2503 if (!$notrigger && empty($error)) {
2504 // Call trigger
2505 $result = $this->call_trigger('PROPAL_MODIFY', $user);
2506 if ($result < 0) {
2507 $error++;
2508 }
2509 // End call triggers
2510 }
2511
2512 if (!$error) {
2513 $this->db->commit();
2514 return 1;
2515 } else {
2516 foreach ($this->errors as $errmsg) {
2517 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2518 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2519 }
2520 $this->db->rollback();
2521 return -1 * $error;
2522 }
2523 }
2524
2525 return -1;
2526 }
2527
2528
2538 public function reopen($user, $status, $note = '', $notrigger = 0)
2539 {
2540 $error = 0;
2541
2542 $sql = "UPDATE ".MAIN_DB_PREFIX."propal";
2543 $sql .= " SET fk_statut = ".((int) $status).",";
2544 if (!empty($note)) {
2545 $sql .= " note_private = '".$this->db->escape($note)."',";
2546 }
2547 $sql .= " date_cloture=NULL, fk_user_cloture=NULL";
2548 $sql .= " WHERE rowid = ".((int) $this->id);
2549
2550 $this->db->begin();
2551
2552 dol_syslog(get_class($this)."::reopen", LOG_DEBUG);
2553 $resql = $this->db->query($sql);
2554 if (!$resql) {
2555 $error++;
2556 $this->errors[] = "Error ".$this->db->lasterror();
2557 }
2558 if (!$error) {
2559 if (!$notrigger) {
2560 // Call trigger
2561 $result = $this->call_trigger('PROPAL_REOPEN', $user);
2562 if ($result < 0) {
2563 $error++;
2564 }
2565 // End call triggers
2566 }
2567 }
2568
2569 // Commit or rollback
2570 if ($error) {
2571 if (!empty($this->errors)) {
2572 foreach ($this->errors as $errmsg) {
2573 dol_syslog(get_class($this)."::update ".$errmsg, LOG_ERR);
2574 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2575 }
2576 }
2577 $this->db->rollback();
2578 return -1 * $error;
2579 } else {
2580 $this->statut = $status;
2581 $this->status = $status;
2582
2583 $this->db->commit();
2584 return 1;
2585 }
2586 }
2587
2598 public function closeProposal($user, $status, $note_private = '', $notrigger = 0, $note_public = '')
2599 {
2600 global $langs,$conf;
2601
2602 $error = 0;
2603 $now = dol_now();
2604
2605 $this->db->begin();
2606
2607 $newprivatenote = dol_concatdesc($this->note_private, $note_private);
2608 $newpublicnote = dol_concatdesc($this->note_public, $note_public);
2609
2610 if (!getDolGlobalString('PROPALE_KEEP_OLD_SIGNATURE_INFO')) {
2611 $date_signature = $now;
2612 $fk_user_signature = $user->id;
2613 } else {
2614 $this->info($this->id);
2615 if (!isset($this->date_signature) || $this->date_signature == '') {
2616 $date_signature = $now;
2617 $fk_user_signature = $user->id;
2618 } else {
2619 $date_signature = $this->date_signature;
2620 $fk_user_signature = $this->user_signature->id;
2621 }
2622 }
2623
2624 $sql = "UPDATE ".MAIN_DB_PREFIX."propal";
2625 $sql .= " SET fk_statut = ".((int) $status).", note_private = '".$this->db->escape($newprivatenote)."', note_public = '".$this->db->escape($newpublicnote)."'";
2626 if ($status == self::STATUS_SIGNED) {
2627 $sql .= ", date_signature='".$this->db->idate($now)."', fk_user_signature = ".($fk_user_signature);
2628 }
2629 $sql .= " WHERE rowid = ".((int) $this->id);
2630
2631 $resql = $this->db->query($sql);
2632 if ($resql) {
2633 // Status self::STATUS_REFUSED by default
2634 $modelpdf = getDolGlobalString('PROPALE_ADDON_PDF_ODT_CLOSED', $this->model_pdf);
2635 $trigger_name = 'PROPAL_CLOSE_REFUSED'; // used later in call_trigger()
2636
2637 if ($status == self::STATUS_SIGNED) { // Status self::STATUS_SIGNED
2638 $trigger_name = 'PROPAL_CLOSE_SIGNED'; // used later in call_trigger()
2639 $modelpdf = getDolGlobalString('PROPALE_ADDON_PDF_ODT_TOBILL') ? $conf->global->PROPALE_ADDON_PDF_ODT_TOBILL : $this->model_pdf;
2640
2641 // The connected company is classified as a client
2642 $soc = new Societe($this->db);
2643 $soc->id = $this->socid;
2644 $result = $soc->setAsCustomer();
2645
2646 if ($result < 0) {
2647 $this->error = $this->db->lasterror();
2648 $this->db->rollback();
2649 return -2;
2650 }
2651 }
2652
2653 if (!getDolGlobalString('MAIN_DISABLE_PDF_AUTOUPDATE') && !getDolGlobalInt('PROPAL_DISABLE_AUTOUPDATE_ON_CLOSE')) {
2654 // Define output language
2655 $outputlangs = $langs;
2656 if (getDolGlobalInt('MAIN_MULTILANGS')) {
2657 $outputlangs = new Translate("", $conf);
2658 $newlang = (GETPOST('lang_id', 'aZ09') ? GETPOST('lang_id', 'aZ09') : $this->thirdparty->default_lang);
2659 $outputlangs->setDefaultLang($newlang);
2660 }
2661
2662 // PDF
2663 $hidedetails = (getDolGlobalString('MAIN_GENERATE_DOCUMENTS_HIDE_DETAILS') ? 1 : 0);
2664 $hidedesc = (getDolGlobalString('MAIN_GENERATE_DOCUMENTS_HIDE_DESC') ? 1 : 0);
2665 $hideref = (getDolGlobalString('MAIN_GENERATE_DOCUMENTS_HIDE_REF') ? 1 : 0);
2666
2667 //$ret=$object->fetch($id); // Reload to get new records
2668 $this->generateDocument($modelpdf, $outputlangs, $hidedetails, $hidedesc, $hideref);
2669 }
2670
2671 if (!$error) {
2672 $this->oldcopy = clone $this;
2673 $this->statut = $status;
2674 $this->status = $status;
2675 $this->date_signature = $date_signature;
2676 $this->note_private = $newprivatenote;
2677 }
2678
2679 if (!$notrigger && empty($error)) {
2680 // Call trigger
2681 $result = $this->call_trigger($trigger_name, $user);
2682 if ($result < 0) {
2683 $error++;
2684 }
2685 // End call triggers
2686 }
2687
2688 if (!$error) {
2689 $this->db->commit();
2690 return 1;
2691 } else {
2692 foreach ($this->errors as $errmsg) {
2693 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2694 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2695 }
2696 if (!empty($this->oldcopy)) {
2697 $this->statut = $this->oldcopy->status;
2698 $this->status = $this->oldcopy->status;
2699 $this->date_signature = $this->oldcopy->date_signature;
2700 $this->note_private = $this->oldcopy->note_private;
2701 }
2702
2703 $this->db->rollback();
2704 return -1;
2705 }
2706 } else {
2707 $this->error = $this->db->lasterror();
2708 $this->db->rollback();
2709 return -1;
2710 }
2711 }
2712
2721 public function classifyBilled(User $user, $notrigger = 0, $note = '')
2722 {
2723 global $conf, $langs;
2724
2725 $error = 0;
2726
2727 $now = dol_now();
2728 $num = 0;
2729
2730 $triggerName = 'PROPAL_CLASSIFY_BILLED';
2731
2732 $this->db->begin();
2733
2734 $newprivatenote = dol_concatdesc($this->note_private, $note);
2735
2736 $sql = 'UPDATE '.MAIN_DB_PREFIX.'propal SET fk_statut = '.self::STATUS_BILLED.", ";
2737 $sql .= " note_private = '".$this->db->escape($newprivatenote)."', date_cloture='".$this->db->idate($now)."', fk_user_cloture=".((int) $user->id);
2738 $sql .= ' WHERE rowid = '.((int) $this->id).' AND fk_statut = '.((int) self::STATUS_SIGNED);
2739
2740 dol_syslog(__METHOD__, LOG_DEBUG);
2741 $resql = $this->db->query($sql);
2742 if (!$resql) {
2743 $this->errors[] = $this->db->error();
2744 $error++;
2745 } else {
2746 $num = $this->db->affected_rows($resql);
2747 }
2748
2749 if (!$error) {
2750 $modelpdf = getDolGlobalString('PROPALE_ADDON_PDF_ODT_CLOSED', $this->model_pdf);
2751
2752 if (!getDolGlobalString('MAIN_DISABLE_PDF_AUTOUPDATE')) {
2753 // Define output language
2754 $outputlangs = $langs;
2755 if (getDolGlobalInt('MAIN_MULTILANGS')) {
2756 $outputlangs = new Translate("", $conf);
2757 $newlang = (GETPOST('lang_id', 'aZ09') ? GETPOST('lang_id', 'aZ09') : $this->thirdparty->default_lang);
2758 $outputlangs->setDefaultLang($newlang);
2759 }
2760
2761 // PDF
2762 $hidedetails = (getDolGlobalString('MAIN_GENERATE_DOCUMENTS_HIDE_DETAILS') ? 1 : 0);
2763 $hidedesc = (getDolGlobalString('MAIN_GENERATE_DOCUMENTS_HIDE_DESC') ? 1 : 0);
2764 $hideref = (getDolGlobalString('MAIN_GENERATE_DOCUMENTS_HIDE_REF') ? 1 : 0);
2765
2766 //$ret=$object->fetch($id); // Reload to get new records
2767 $this->generateDocument($modelpdf, $outputlangs, $hidedetails, $hidedesc, $hideref);
2768 }
2769
2770 $this->oldcopy = clone $this;
2771 $this->statut = self::STATUS_BILLED;
2772 $this->status = self::STATUS_BILLED;
2773 $this->date_cloture = $now;
2774 $this->note_private = $newprivatenote;
2775 }
2776
2777 if (!$notrigger && empty($error)) {
2778 // Call trigger
2779 $result = $this->call_trigger($triggerName, $user);
2780 if ($result < 0) {
2781 $error++;
2782 }
2783 // End call triggers
2784 }
2785
2786 if (!$error) {
2787 $this->db->commit();
2788 return $num;
2789 } else {
2790 foreach ($this->errors as $errmsg) {
2791 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2792 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2793 }
2794 $this->db->rollback();
2795 return -1 * $error;
2796 }
2797 }
2798
2805 public function setCancel(User $user)
2806 {
2807 $error = 0;
2808
2809 $this->db->begin();
2810
2811 $sql = "UPDATE ". MAIN_DB_PREFIX . "propal";
2812 $sql .= " SET fk_statut = " . self::STATUS_CANCELED . ",";
2813 $sql .= " fk_user_modif = " . ((int) $user->id);
2814 $sql .= " WHERE rowid = " . ((int) $this->id);
2815
2816 dol_syslog(get_class($this)."::cancel", LOG_DEBUG);
2817 if ($this->db->query($sql)) {
2818 if (!$error) {
2819 // Call trigger
2820 $result = $this->call_trigger('PROPAL_CANCEL', $user);
2821 if ($result < 0) {
2822 $error++;
2823 }
2824 // End call triggers
2825 }
2826
2827 if (!$error) {
2828 $this->statut = self::STATUS_CANCELED;
2830 $this->db->commit();
2831 return 1;
2832 } else {
2833 foreach ($this->errors as $errmsg) {
2834 dol_syslog(get_class($this)."::cancel ".$errmsg, LOG_ERR);
2835 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2836 }
2837 $this->db->rollback();
2838 return -1;
2839 }
2840 } else {
2841 $this->error = $this->db->error();
2842 $this->db->rollback();
2843 return -1;
2844 }
2845 }
2846
2847 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2855 public function setDraft($user, $notrigger = 0)
2856 {
2857 // phpcs:enable
2858 $error = 0;
2859
2860 // Protection
2861 if ($this->status <= self::STATUS_DRAFT) {
2862 return 0;
2863 }
2864
2865 dol_syslog(get_class($this)."::setDraft", LOG_DEBUG);
2866
2867 $this->db->begin();
2868
2869 $sql = "UPDATE ".MAIN_DB_PREFIX."propal";
2870 $sql .= " SET fk_statut = ".self::STATUS_DRAFT;
2871 $sql .= ", online_sign_ip = NULL , online_sign_name = NULL";
2872 $sql .= " WHERE rowid = ".((int) $this->id);
2873
2874 $resql = $this->db->query($sql);
2875 if (!$resql) {
2876 $this->errors[] = $this->db->error();
2877 $error++;
2878 }
2879
2880 if (!$error) {
2881 $this->oldcopy = clone $this;
2882 }
2883
2884 if (!$notrigger && empty($error)) {
2885 // Call trigger
2886 $result = $this->call_trigger('PROPAL_MODIFY', $user);
2887 if ($result < 0) {
2888 $error++;
2889 }
2890 // End call triggers
2891 }
2892
2893 if (!$error) {
2894 $this->statut = self::STATUS_DRAFT;
2895 $this->status = self::STATUS_DRAFT;
2896
2897 $this->db->commit();
2898 return 1;
2899 } else {
2900 foreach ($this->errors as $errmsg) {
2901 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2902 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2903 }
2904 $this->db->rollback();
2905 return -1 * $error;
2906 }
2907 }
2908
2909
2910 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2924 public function liste_array($shortlist = 0, $draft = 0, $notcurrentuser = 0, $socid = 0, $limit = 0, $offset = 0, $sortfield = 'p.datep', $sortorder = 'DESC')
2925 {
2926 // phpcs:enable
2927 global $user;
2928
2929 $ga = array();
2930
2931 $sql = "SELECT s.rowid, s.nom as name, s.client,";
2932 $sql .= " p.rowid as propalid, p.fk_statut, p.total_ht, p.ref, p.remise, ";
2933 $sql .= " p.datep as dp, p.fin_validite as datelimite";
2934 $sql .= " FROM ".MAIN_DB_PREFIX."societe as s, ".MAIN_DB_PREFIX."propal as p, ".MAIN_DB_PREFIX."c_propalst as c";
2935 $sql .= " WHERE p.entity IN (".getEntity('propal').")";
2936 $sql .= " AND p.fk_soc = s.rowid";
2937 $sql .= " AND p.fk_statut = c.id";
2938
2939 // If the internal user must only see his customers, force searching by him
2940 $search_sale = 0;
2941 if (empty($user->socid) && !$user->hasRight('societe', 'client', 'voir')) {
2942 $search_sale = $user->id;
2943 }
2944 // Search on sale representative
2945 if ($search_sale && $search_sale != '-1') {
2946 if ($search_sale == -2) {
2947 $sql .= " AND NOT EXISTS (SELECT sc.fk_soc FROM ".MAIN_DB_PREFIX."societe_commerciaux as sc WHERE sc.fk_soc = p.fk_soc)";
2948 } elseif ($search_sale > 0) {
2949 $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).")";
2950 }
2951 }
2952 // Search on socid
2953 if ($socid) {
2954 $sql .= " AND p.fk_soc = ".((int) $socid);
2955 }
2956 if ($draft) {
2957 $sql .= " AND p.fk_statut = ".((int) self::STATUS_DRAFT);
2958 }
2959 if ($notcurrentuser > 0) {
2960 $sql .= " AND p.fk_user_author <> ".((int) $user->id);
2961 }
2962 $sql .= $this->db->order($sortfield, $sortorder);
2963 $sql .= $this->db->plimit($limit, $offset);
2964
2965 $result = $this->db->query($sql);
2966 if ($result) {
2967 $num = $this->db->num_rows($result);
2968 if ($num) {
2969 $i = 0;
2970 while ($i < $num) {
2971 $obj = $this->db->fetch_object($result);
2972
2973 if ($shortlist == 1) {
2974 $ga[$obj->propalid] = $obj->ref;
2975 } elseif ($shortlist == 2) {
2976 $ga[$obj->propalid] = $obj->ref.' ('.$obj->name.')';
2977 } else {
2978 $ga[$i]['id'] = $obj->propalid;
2979 $ga[$i]['ref'] = $obj->ref;
2980 $ga[$i]['name'] = $obj->name;
2981 }
2982
2983 $i++;
2984 }
2985 }
2986 return $ga;
2987 } else {
2988 dol_print_error($this->db);
2989 return -1;
2990 }
2991 }
2992
2998 public function getInvoiceArrayList()
2999 {
3000 return $this->InvoiceArrayList($this->id);
3001 }
3002
3003 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3010 public function InvoiceArrayList($id)
3011 {
3012 // phpcs:enable
3013 $ga = array();
3014 $linkedInvoices = array();
3015
3016 $this->fetchObjectLinked($id, $this->element);
3017 foreach ($this->linkedObjectsIds as $objecttype => $objectid) {
3018 // Nouveau système du common object renvoi des rowid et non un id linéaire de 1 à n
3019 // On parcourt donc une liste d'objets en tant qu'objet unique
3020 foreach ($objectid as $key => $object) {
3021 // Cas des factures liees directement
3022 if ($objecttype == 'facture') {
3023 $linkedInvoices[] = $object;
3024 } else {
3025 // Cas des factures liees par un autre object (ex: commande)
3026 $this->fetchObjectLinked($object, $objecttype);
3027 foreach ($this->linkedObjectsIds as $subobjecttype => $subobjectid) {
3028 foreach ($subobjectid as $subkey => $subobject) {
3029 if ($subobjecttype == 'facture') {
3030 $linkedInvoices[] = $subobject;
3031 }
3032 }
3033 }
3034 }
3035 }
3036 }
3037
3038 if (count($linkedInvoices) > 0) {
3039 $sql = "SELECT rowid as facid, ref, total_ht as total, datef as df, fk_user_author, fk_statut, paye";
3040 $sql .= " FROM ".MAIN_DB_PREFIX."facture";
3041 $sql .= " WHERE rowid IN (".$this->db->sanitize(implode(',', $linkedInvoices)).")";
3042
3043 dol_syslog(get_class($this)."::InvoiceArrayList", LOG_DEBUG);
3044 $resql = $this->db->query($sql);
3045
3046 if ($resql) {
3047 $tab_sqlobj = array();
3048 $nump = $this->db->num_rows($resql);
3049 for ($i = 0; $i < $nump; $i++) {
3050 $sqlobj = $this->db->fetch_object($resql);
3051 $tab_sqlobj[] = $sqlobj;
3052 }
3053 $this->db->free($resql);
3054
3055 $nump = count($tab_sqlobj);
3056
3057 if ($nump) {
3058 $i = 0;
3059 while ($i < $nump) {
3060 $obj = array_shift($tab_sqlobj);
3061
3062 $ga[$i] = $obj;
3063
3064 $i++;
3065 }
3066 }
3067 return $ga;
3068 } else {
3069 return -1;
3070 }
3071 } else {
3072 return $ga;
3073 }
3074 }
3075
3083 public function delete($user, $notrigger = 0)
3084 {
3085 global $conf;
3086 require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
3087
3088 $error = 0;
3089
3090 $this->db->begin();
3091
3092 if (!$notrigger) {
3093 // Call trigger
3094 $result = $this->call_trigger('PROPAL_DELETE', $user);
3095 if ($result < 0) {
3096 $error++;
3097 }
3098 // End call triggers
3099 }
3100
3101 // Delete extrafields of lines and lines
3102 if (!$error && !empty($this->table_element_line)) {
3103 $tabletodelete = $this->table_element_line;
3104 $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).")";
3105 $sql = "DELETE FROM ".MAIN_DB_PREFIX.$tabletodelete." WHERE ".$this->fk_element." = ".((int) $this->id);
3106 if (!$this->db->query($sqlef) || !$this->db->query($sql)) {
3107 $error++;
3108 $this->error = $this->db->lasterror();
3109 $this->errors[] = $this->error;
3110 dol_syslog(get_class($this)."::delete error ".$this->error, LOG_ERR);
3111 }
3112 }
3113
3114 if (!$error) {
3115 // Delete linked object
3116 $res = $this->deleteObjectLinked();
3117 if ($res < 0) {
3118 $error++;
3119 }
3120 }
3121
3122 if (!$error) {
3123 // Delete linked contacts
3124 $res = $this->delete_linked_contact();
3125 if ($res < 0) {
3126 $error++;
3127 }
3128 }
3129
3130 // Removed extrafields of object
3131 if (!$error) {
3132 $result = $this->deleteExtraFields();
3133 if ($result < 0) {
3134 $error++;
3135 dol_syslog(get_class($this)."::delete error ".$this->error, LOG_ERR);
3136 }
3137 }
3138
3139 // Delete main record
3140 if (!$error) {
3141 $sql = "DELETE FROM ".MAIN_DB_PREFIX.$this->table_element." WHERE rowid = ".((int) $this->id);
3142 $res = $this->db->query($sql);
3143 if (!$res) {
3144 $error++;
3145 $this->error = $this->db->lasterror();
3146 $this->errors[] = $this->error;
3147 dol_syslog(get_class($this)."::delete error ".$this->error, LOG_ERR);
3148 }
3149 }
3150
3151 // Delete record into ECM index and physically
3152 if (!$error) {
3153 $res = $this->deleteEcmFiles(0); // Deleting files physically is done later with the dol_delete_dir_recursive
3154 $res = $this->deleteEcmFiles(1); // Deleting files physically is done later with the dol_delete_dir_recursive
3155 if (!$res) {
3156 $error++;
3157 }
3158 }
3159
3160 if (!$error) {
3161 // We remove directory
3162 $ref = dol_sanitizeFileName($this->ref);
3163 if ($conf->propal->multidir_output[$this->entity] && !empty($this->ref)) {
3164 $dir = $conf->propal->multidir_output[$this->entity]."/".$ref;
3165 $file = $dir."/".$ref.".pdf";
3166 if (file_exists($file)) {
3167 dol_delete_preview($this);
3168
3169 if (!dol_delete_file($file, 0, 0, 0, $this)) {
3170 $this->error = 'ErrorFailToDeleteFile';
3171 $this->errors[] = $this->error;
3172 $this->db->rollback();
3173 return 0;
3174 }
3175 }
3176 if (file_exists($dir)) {
3177 $res = @dol_delete_dir_recursive($dir); // delete files physically + into ecm tables
3178 if (!$res) {
3179 $this->error = 'ErrorFailToDeleteDir';
3180 $this->errors[] = $this->error;
3181 $this->db->rollback();
3182 return 0;
3183 }
3184 }
3185 }
3186 }
3187
3188 if (!$error) {
3189 dol_syslog(get_class($this)."::delete ".$this->id." by ".$user->id, LOG_DEBUG);
3190 $this->db->commit();
3191 return 1;
3192 } else {
3193 $this->db->rollback();
3194 return -1;
3195 }
3196 }
3197
3206 public function availability($availability_id, $notrigger = 0)
3207 {
3208 global $user;
3209
3210 if ($this->status >= self::STATUS_DRAFT) {
3211 $error = 0;
3212
3213 $this->db->begin();
3214
3215 $sql = 'UPDATE '.MAIN_DB_PREFIX.'propal';
3216 $sql .= ' SET fk_availability = '.((int) $availability_id);
3217 $sql .= ' WHERE rowid='.((int) $this->id);
3218
3219 dol_syslog(__METHOD__.' availability('.$availability_id.')', LOG_DEBUG);
3220 $resql = $this->db->query($sql);
3221 if (!$resql) {
3222 $this->errors[] = $this->db->error();
3223 $error++;
3224 }
3225
3226 if (!$error) {
3227 $this->oldcopy = clone $this;
3228 $this->availability_id = $availability_id;
3229 }
3230
3231 if (!$notrigger && empty($error)) {
3232 // Call trigger
3233 $result = $this->call_trigger('PROPAL_MODIFY', $user);
3234 if ($result < 0) {
3235 $error++;
3236 }
3237 // End call triggers
3238 }
3239
3240 if (!$error) {
3241 $this->db->commit();
3242 return 1;
3243 } else {
3244 foreach ($this->errors as $errmsg) {
3245 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
3246 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
3247 }
3248 $this->db->rollback();
3249 return -1 * $error;
3250 }
3251 } else {
3252 $error_str = 'Propal status do not meet requirement '.$this->status;
3253 dol_syslog(__METHOD__.$error_str, LOG_ERR);
3254 $this->error = $error_str;
3255 $this->errors[] = $this->error;
3256 return -2;
3257 }
3258 }
3259
3260 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3269 public function demand_reason($demand_reason_id, $notrigger = 0)
3270 {
3271 // phpcs:enable
3272 global $user;
3273
3274 if ($this->status >= self::STATUS_DRAFT) {
3275 $error = 0;
3276
3277 $this->db->begin();
3278
3279 $sql = 'UPDATE '.MAIN_DB_PREFIX.'propal';
3280 $sql .= ' SET fk_input_reason = '.((int) $demand_reason_id);
3281 $sql .= ' WHERE rowid='.((int) $this->id);
3282
3283 dol_syslog(__METHOD__.' demand_reason('.$demand_reason_id.')', LOG_DEBUG);
3284 $resql = $this->db->query($sql);
3285 if (!$resql) {
3286 $this->errors[] = $this->db->error();
3287 $error++;
3288 }
3289
3290 if (!$error) {
3291 $this->oldcopy = clone $this;
3292 $this->demand_reason_id = $demand_reason_id;
3293 }
3294
3295 if (!$notrigger && empty($error)) {
3296 // Call trigger
3297 $result = $this->call_trigger('PROPAL_MODIFY', $user);
3298 if ($result < 0) {
3299 $error++;
3300 }
3301 // End call triggers
3302 }
3303
3304 if (!$error) {
3305 $this->db->commit();
3306 return 1;
3307 } else {
3308 foreach ($this->errors as $errmsg) {
3309 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
3310 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
3311 }
3312 $this->db->rollback();
3313 return -1 * $error;
3314 }
3315 } else {
3316 $error_str = 'Propal status do not meet requirement '.$this->status;
3317 dol_syslog(__METHOD__.$error_str, LOG_ERR);
3318 $this->error = $error_str;
3319 $this->errors[] = $this->error;
3320 return -2;
3321 }
3322 }
3323
3324
3331 public function info($id)
3332 {
3333 $sql = "SELECT c.rowid, ";
3334 $sql .= " c.datec, c.date_valid as datev, c.date_signature, c.date_cloture,";
3335 $sql .= " c.fk_user_author, c.fk_user_valid, c.fk_user_signature, c.fk_user_cloture";
3336 $sql .= " FROM ".MAIN_DB_PREFIX."propal as c";
3337 $sql .= " WHERE c.rowid = ".((int) $id);
3338
3339 $result = $this->db->query($sql);
3340
3341 if ($result) {
3342 if ($this->db->num_rows($result)) {
3343 $obj = $this->db->fetch_object($result);
3344
3345 $this->id = $obj->rowid;
3346
3347 $this->date_creation = $this->db->jdate($obj->datec);
3348 $this->date_validation = $this->db->jdate($obj->datev);
3349 $this->date_signature = $this->db->jdate($obj->date_signature);
3350 $this->date_cloture = $this->db->jdate($obj->date_cloture);
3351
3352 $this->user_creation_id = $obj->fk_user_author;
3353 $this->user_validation_id = $obj->fk_user_valid;
3354
3355 if ($obj->fk_user_signature) {
3356 $user_signature = new User($this->db);
3357 $user_signature->fetch($obj->fk_user_signature);
3358 $this->user_signature = $user_signature;
3359 }
3360
3361 $this->user_closing_id = $obj->fk_user_cloture;
3362 }
3363 $this->db->free($result);
3364 } else {
3365 dol_print_error($this->db);
3366 }
3367 }
3368
3369
3376 public function getLibStatut($mode = 0)
3377 {
3378 return $this->LibStatut($this->status, $mode);
3379 }
3380
3381 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3389 public function LibStatut($status, $mode = 1)
3390 {
3391 // phpcs:enable
3392 global $hookmanager;
3393
3394 // Init/load array of translation of status
3395 if (empty($this->labelStatus) || empty($this->labelStatusShort)) {
3396 global $langs;
3397 $langs->load("propal");
3398 $this->labelStatus[-1] = $langs->transnoentitiesnoconv("PropalStatusCanceled");
3399 $this->labelStatus[0] = $langs->transnoentitiesnoconv("PropalStatusDraft");
3400 $this->labelStatus[1] = $langs->transnoentitiesnoconv("PropalStatusValidated");
3401 $this->labelStatus[2] = $langs->transnoentitiesnoconv("PropalStatusSigned");
3402 $this->labelStatus[3] = $langs->transnoentitiesnoconv("PropalStatusNotSigned");
3403 $this->labelStatus[4] = $langs->transnoentitiesnoconv("PropalStatusBilled");
3404 $this->labelStatusShort[-1] = $langs->transnoentitiesnoconv("PropalStatusCanceledShort");
3405 $this->labelStatusShort[0] = $langs->transnoentitiesnoconv("PropalStatusDraftShort");
3406 $this->labelStatusShort[1] = $langs->transnoentitiesnoconv("PropalStatusValidatedShort");
3407 $this->labelStatusShort[2] = $langs->transnoentitiesnoconv("PropalStatusSignedShort");
3408 $this->labelStatusShort[3] = $langs->transnoentitiesnoconv("PropalStatusNotSignedShort");
3409 $this->labelStatusShort[4] = $langs->transnoentitiesnoconv("PropalStatusBilledShort");
3410 }
3411
3412 $statusType = '';
3413 if ($status == self::STATUS_CANCELED) {
3414 $statusType = 'status9';
3415 } elseif ($status == self::STATUS_DRAFT) {
3416 $statusType = 'status0';
3417 } elseif ($status == self::STATUS_VALIDATED) {
3418 $statusType = 'status1';
3419 } elseif ($status == self::STATUS_SIGNED) {
3420 $statusType = 'status4';
3421 } elseif ($status == self::STATUS_NOTSIGNED) {
3422 $statusType = 'status9';
3423 } elseif ($status == self::STATUS_BILLED) {
3424 $statusType = 'status6';
3425 }
3426
3427 $parameters = array('status' => $status, 'mode' => $mode);
3428 $reshook = $hookmanager->executeHooks('LibStatut', $parameters, $this); // Note that $action and $object may have been modified by hook
3429
3430 if ($reshook > 0) {
3431 return $hookmanager->resPrint;
3432 }
3433
3434 return dolGetStatus($this->labelStatus[$status], $this->labelStatusShort[$status], '', $statusType, $mode);
3435 }
3436
3437
3438 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3446 public function load_board($user, $mode)
3447 {
3448 // phpcs:enable
3449 global $conf, $langs;
3450
3451 $clause = " WHERE";
3452
3453 $sql = "SELECT p.rowid, p.ref, p.datec as datec, p.fin_validite as datefin, p.total_ht";
3454 $sql .= " FROM ".MAIN_DB_PREFIX."propal as p";
3455 $sql .= $clause." p.entity IN (".getEntity('propal').")";
3456 if ($mode == 'opened') {
3457 $sql .= " AND p.fk_statut = ".self::STATUS_VALIDATED;
3458 }
3459 if ($mode == 'signed') {
3460 $sql .= " AND p.fk_statut = ".self::STATUS_SIGNED;
3461 }
3462 // If the internal user must only see his customers, force searching by him
3463 $search_sale = 0;
3464 if (empty($user->socid) && !$user->hasRight('societe', 'client', 'voir')) {
3465 $search_sale = $user->id;
3466 }
3467 // Search on sale representative
3468 if ($search_sale && $search_sale != '-1') {
3469 if ($search_sale == -2) {
3470 $sql .= " AND NOT EXISTS (SELECT sc.fk_soc FROM ".MAIN_DB_PREFIX."societe_commerciaux as sc WHERE sc.fk_soc = p.fk_soc)";
3471 } elseif ($search_sale > 0) {
3472 $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).")";
3473 }
3474 }
3475
3476 $resql = $this->db->query($sql);
3477 if ($resql) {
3478 $langs->load("propal");
3479 $now = dol_now();
3480
3481 $delay_warning = 0;
3482 $status = 0;
3483 $label = $labelShort = '';
3484 if ($mode == 'opened') {
3485 $delay_warning = $conf->propal->cloture->warning_delay;
3486 $status = self::STATUS_VALIDATED;
3487 $label = $langs->transnoentitiesnoconv("PropalsToClose");
3488 $labelShort = $langs->transnoentitiesnoconv("ToAcceptRefuse");
3489 }
3490 if ($mode == 'signed') {
3491 $delay_warning = $conf->propal->facturation->warning_delay;
3492 $status = self::STATUS_SIGNED;
3493 $label = $langs->trans("PropalsToBill"); // We set here bill but may be billed or ordered
3494 $labelShort = $langs->trans("ToBill");
3495 }
3496
3497 $response = new WorkboardResponse();
3498 $response->warning_delay = $delay_warning / 60 / 60 / 24;
3499 $response->label = $label;
3500 $response->labelShort = $labelShort;
3501 $response->url = DOL_URL_ROOT.'/comm/propal/list.php?search_status='.$status.'&mainmenu=commercial&leftmenu=propals';
3502 $response->url_late = DOL_URL_ROOT.'/comm/propal/list.php?search_option=late&mainmenu=commercial&leftmenu=propals&sortfield=p.datep&sortorder=asc';
3503 $response->img = img_object('', "propal");
3504
3505 // This assignment in condition is not a bug. It allows walking the results.
3506 while ($obj = $this->db->fetch_object($resql)) {
3507 $response->nbtodo++;
3508 $response->total += $obj->total_ht;
3509
3510 if ($mode == 'opened') {
3511 $datelimit = $this->db->jdate($obj->datefin);
3512 if ($datelimit < ($now - $delay_warning)) {
3513 $response->nbtodolate++;
3514 }
3515 }
3516 // TODO Definir regle des propales a facturer en retard
3517 // if ($mode == 'signed' && ! count($this->FactureListeArray($obj->rowid))) $this->nbtodolate++;
3518 }
3519
3520 return $response;
3521 } else {
3522 $this->error = $this->db->error();
3523 return -1;
3524 }
3525 }
3526
3527
3535 public function initAsSpecimen()
3536 {
3537 global $conf, $langs;
3538
3539 // Load array of products prodids
3540 $num_prods = 0;
3541 $prodids = array();
3542 $sql = "SELECT rowid";
3543 $sql .= " FROM ".MAIN_DB_PREFIX."product";
3544 $sql .= " WHERE entity IN (".getEntity('product').")";
3545 $sql .= $this->db->plimit(100);
3546
3547 $resql = $this->db->query($sql);
3548 if ($resql) {
3549 $num_prods = $this->db->num_rows($resql);
3550 $i = 0;
3551 while ($i < $num_prods) {
3552 $i++;
3553 $row = $this->db->fetch_row($resql);
3554 $prodids[$i] = $row[0];
3555 }
3556 }
3557
3558 // Initialise parameters
3559 $this->id = 0;
3560 $this->ref = 'SPECIMEN';
3561 $this->ref_client = 'NEMICEPS';
3562 $this->specimen = 1;
3563 $this->socid = 1;
3564 $this->date = time();
3565 $this->fin_validite = $this->date + 3600 * 24 * 30;
3566 $this->cond_reglement_id = 1;
3567 $this->cond_reglement_code = 'RECEP';
3568 $this->mode_reglement_id = 7;
3569 $this->mode_reglement_code = 'CHQ';
3570 $this->availability_id = 1;
3571 $this->availability_code = 'AV_NOW';
3572 $this->demand_reason_id = 1;
3573 $this->demand_reason_code = 'SRC_00';
3574 $this->note_public = 'This is a comment (public)';
3575 $this->note_private = 'This is a comment (private)';
3576
3577 $this->multicurrency_tx = 1;
3578 $this->multicurrency_code = $conf->currency;
3579
3580 // Lines
3581 $nbp = min(1000, GETPOSTINT('nblines') ? GETPOSTINT('nblines') : 5); // We can force the nb of lines to test from command line (but not more than 1000)
3582 $xnbp = 0;
3583 while ($xnbp < $nbp) {
3584 $line = new PropaleLigne($this->db);
3585 $line->desc = $langs->trans("Description")." ".$xnbp;
3586 $line->qty = 1;
3587 $line->subprice = 100;
3588 $line->price = 100;
3589 $line->tva_tx = 20;
3590 $line->localtax1_tx = 0;
3591 $line->localtax2_tx = 0;
3592 if ($xnbp == 2) {
3593 $line->total_ht = 50;
3594 $line->total_ttc = 60;
3595 $line->total_tva = 10;
3596 $line->remise_percent = 50;
3597 } else {
3598 $line->total_ht = 100;
3599 $line->total_ttc = 120;
3600 $line->total_tva = 20;
3601 $line->remise_percent = 00;
3602 }
3603
3604 if ($num_prods > 0) {
3605 $prodid = mt_rand(1, $num_prods);
3606 $line->fk_product = $prodids[$prodid];
3607 $line->product_ref = 'SPECIMEN';
3608 }
3609
3610 $this->lines[$xnbp] = $line;
3611
3612 $this->total_ht += $line->total_ht;
3613 $this->total_tva += $line->total_tva;
3614 $this->total_ttc += $line->total_ttc;
3615
3616 $xnbp++;
3617 }
3618
3619 return 1;
3620 }
3621
3627 public function loadStateBoard()
3628 {
3629 global $user;
3630
3631 $this->nb = array();
3632 $clause = "WHERE";
3633
3634 $sql = "SELECT count(p.rowid) as nb";
3635 $sql .= " FROM ".MAIN_DB_PREFIX."propal as p";
3636 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe as s ON p.fk_soc = s.rowid";
3637 $sql .= " ".$clause." p.entity IN (".getEntity('propal').")";
3638
3639 // If the internal user must only see his customers, force searching by him
3640 $search_sale = 0;
3641 if (empty($user->socid) && !$user->hasRight('societe', 'client', 'voir')) {
3642 $search_sale = $user->id;
3643 }
3644 // Search on sale representative
3645 if ($search_sale && $search_sale != '-1') {
3646 if ($search_sale == -2) {
3647 $sql .= " AND NOT EXISTS (SELECT sc.fk_soc FROM ".MAIN_DB_PREFIX."societe_commerciaux as sc WHERE sc.fk_soc = p.fk_soc)";
3648 } elseif ($search_sale > 0) {
3649 $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).")";
3650 }
3651 }
3652
3653 $resql = $this->db->query($sql);
3654 if ($resql) {
3655 // This assignment in condition is not a bug. It allows walking the results.
3656 while ($obj = $this->db->fetch_object($resql)) {
3657 $this->nb["proposals"] = $obj->nb;
3658 }
3659 $this->db->free($resql);
3660 return 1;
3661 } else {
3662 dol_print_error($this->db);
3663 $this->error = $this->db->error();
3664 return -1;
3665 }
3666 }
3667
3668
3676 public function getNextNumRef($soc)
3677 {
3678 global $conf, $langs;
3679 $langs->load("propal");
3680
3681 $classname = getDolGlobalString('PROPALE_ADDON');
3682
3683 if (!empty($classname)) {
3684 $mybool = false;
3685
3686 $file = $classname.".php";
3687
3688 // Include file with class
3689 $dirmodels = array_merge(array('/'), (array) $conf->modules_parts['models']);
3690 foreach ($dirmodels as $reldir) {
3691 $dir = dol_buildpath($reldir."core/modules/propale/");
3692
3693 // Load file with numbering class (if found)
3694 $mybool = ((bool) @include_once $dir.$file) || $mybool;
3695 }
3696
3697 if (!$mybool) {
3698 dol_print_error(null, "Failed to include file ".$file);
3699 return '';
3700 }
3701
3702 $obj = new $classname();
3703 '@phan-var-force ModeleNumRefPropales $obj';
3704
3705 $numref = $obj->getNextValue($soc, $this);
3706
3707 if ($numref != "") {
3708 return $numref;
3709 } else {
3710 $this->error = $obj->error;
3711 //dol_print_error($db,"Propale::getNextNumRef ".$obj->error);
3712 return "";
3713 }
3714 } else {
3715 $langs->load("errors");
3716 print $langs->trans("Error")." ".$langs->trans("ErrorModuleSetupNotComplete", $langs->transnoentitiesnoconv("Proposal"));
3717 return "";
3718 }
3719 }
3720
3727 public function getTooltipContentArray($params)
3728 {
3729 global $conf, $langs, $user;
3730
3731 $langs->load('propal');
3732 $datas = [];
3733 $nofetch = !empty($params['nofetch']);
3734
3735 if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
3736 return ['optimize' => $langs->trans("Proposal")];
3737 }
3738 if ($user->hasRight('propal', 'lire')) {
3739 $datas['picto'] = img_picto('', $this->picto, '', 0, 0, 0, '', 'paddingrightonly').'<u>'.$langs->trans("Proposal").'</u>';
3740 if (isset($this->status)) {
3741 $datas['status'] = ' '.$this->getLibStatut(5);
3742 }
3743 if (!empty($this->ref)) {
3744 $datas['ref'] = '<br><b>'.$langs->trans('Ref').':</b> '.$this->ref;
3745 }
3746 if (!$nofetch) {
3747 $langs->load('companies');
3748 if (empty($this->thirdparty)) {
3749 $this->fetch_thirdparty();
3750 }
3751 $datas['customer'] = '<br><b>'.$langs->trans('Customer').':</b> '.$this->thirdparty->getNomUrl(1, '', 0, 1);
3752 }
3753 if (!empty($this->ref_customer)) {
3754 $datas['refcustomer'] = '<br><b>'.$langs->trans('RefCustomer').':</b> '.$this->ref_customer;
3755 }
3756 if (!$nofetch) {
3757 $langs->load('project');
3758 if (is_null($this->project) || (is_object($this->project) && $this->project->isEmpty())) {
3759 $res = $this->fetchProject();
3760 if ($res > 0 && $this->project instanceof Project) {
3761 $datas['project'] = '<br><b>'.$langs->trans('Project').':</b> '.$this->project->getNomUrl(1, '', 0, 1);
3762 }
3763 }
3764 }
3765 if (!empty($this->total_ht)) {
3766 $datas['amountht'] = '<br><b>'.$langs->trans('AmountHT').':</b> '.price($this->total_ht, 0, $langs, 0, -1, -1, $conf->currency);
3767 }
3768 if (!empty($this->total_tva)) {
3769 $datas['vat'] = '<br><b>'.$langs->trans('VAT').':</b> '.price($this->total_tva, 0, $langs, 0, -1, -1, $conf->currency);
3770 }
3771 if (!empty($this->total_ttc)) {
3772 $datas['amountttc'] = '<br><b>'.$langs->trans('AmountTTC').':</b> '.price($this->total_ttc, 0, $langs, 0, -1, -1, $conf->currency);
3773 }
3774 if (!empty($this->date)) {
3775 $datas['date'] = '<br><b>'.$langs->trans('Date').':</b> '.dol_print_date($this->date, 'day');
3776 }
3777 if (!empty($this->delivery_date)) {
3778 $datas['deliverydate'] = '<br><b>'.$langs->trans('DeliveryDate').':</b> '.dol_print_date($this->delivery_date, 'dayhour');
3779 }
3780 }
3781
3782 return $datas;
3783 }
3784
3796 public function getNomUrl($withpicto = 0, $option = '', $get_params = '', $notooltip = 0, $save_lastsearch_value = -1, $addlinktonotes = -1)
3797 {
3798 global $langs, $conf, $user, $hookmanager;
3799
3800 if (!empty($conf->dol_no_mouse_hover)) {
3801 $notooltip = 1; // Force disable tooltips
3802 }
3803
3804 $result = '';
3805 $params = [
3806 'id' => $this->id,
3807 'objecttype' => $this->element,
3808 'option' => $option,
3809 'nofetch' => 1,
3810 ];
3811 $classfortooltip = 'classfortooltip';
3812 $dataparams = '';
3813 if (getDolGlobalInt('MAIN_ENABLE_AJAX_TOOLTIP')) {
3814 $classfortooltip = 'classforajaxtooltip';
3815 $dataparams = ' data-params="'.dol_escape_htmltag(json_encode($params)).'"';
3816 $label = '';
3817 } else {
3818 $label = implode($this->getTooltipContentArray($params));
3819 }
3820
3821 $url = '';
3822 if ($user->hasRight('propal', 'lire')) {
3823 if ($option == '') {
3824 $url = DOL_URL_ROOT.'/comm/propal/card.php?id='.$this->id.$get_params;
3825 } elseif ($option == 'compta') { // deprecated
3826 $url = DOL_URL_ROOT.'/comm/propal/card.php?id='.$this->id.$get_params;
3827 } elseif ($option == 'expedition') {
3828 $url = DOL_URL_ROOT.'/expedition/propal.php?id='.$this->id.$get_params;
3829 } elseif ($option == 'document') {
3830 $url = DOL_URL_ROOT.'/comm/propal/document.php?id='.$this->id.$get_params;
3831 }
3832
3833 if ($option != 'nolink') {
3834 // Add param to save lastsearch_values or not
3835 $add_save_lastsearch_values = ($save_lastsearch_value == 1 ? 1 : 0);
3836 if ($save_lastsearch_value == -1 && isset($_SERVER["PHP_SELF"]) && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) {
3837 $add_save_lastsearch_values = 1;
3838 }
3839 if ($add_save_lastsearch_values) {
3840 $url .= '&save_lastsearch_values=1';
3841 }
3842 }
3843 }
3844
3845 $linkclose = '';
3846 if (empty($notooltip) && $user->hasRight('propal', 'lire')) {
3847 if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
3848 $label = $langs->trans("Proposal");
3849 $linkclose .= ' alt="'.dolPrintHTMLForAttribute($label).'"';
3850 }
3851 $linkclose .= ($label ? ' title="'.dolPrintHTMLForAttribute($label).'"' : ' title="tocomplete"');
3852 $linkclose .= $dataparams.' class="'.$classfortooltip.'"';
3853 }
3854
3855 $linkstart = '<a href="'.$url.'"';
3856 $linkstart .= $linkclose.'>';
3857 $linkend = '</a>';
3858
3859 $result .= $linkstart;
3860 if ($withpicto) {
3861 $result .= img_object(($notooltip ? '' : $label), ($this->picto ? $this->picto : 'generic'), (($withpicto != 2) ? 'class="paddingright"' : ''), 0, 0, $notooltip ? 0 : 1);
3862 }
3863 if ($withpicto != 2) {
3864 $result .= $this->ref;
3865 }
3866 $result .= $linkend;
3867
3868 if ($addlinktonotes >= 0) {
3869 $txttoshow = '';
3870
3871 if ($addlinktonotes == 0) {
3872 if (!empty($this->note_private) || !empty($this->note_public)) {
3873 $txttoshow = $langs->trans('ViewPrivateNote');
3874 }
3875 } elseif ($addlinktonotes == 1) {
3876 if (!empty($this->note_private)) {
3877 $txttoshow .= ($user->socid > 0 ? '' : dol_string_nohtmltag($this->note_private, 1));
3878 }
3879 } elseif ($addlinktonotes == 2) {
3880 if (!empty($this->note_public)) {
3881 $txttoshow .= dol_string_nohtmltag($this->note_public, 1);
3882 }
3883 } elseif ($addlinktonotes == 3) {
3884 if ($user->socid > 0) {
3885 if (!empty($this->note_public)) {
3886 $txttoshow .= dol_string_nohtmltag($this->note_public, 1);
3887 }
3888 } else {
3889 if (!empty($this->note_public)) {
3890 $txttoshow .= dol_string_nohtmltag($this->note_public, 1);
3891 }
3892 if (!empty($this->note_private)) {
3893 if (!empty($txttoshow)) {
3894 $txttoshow .= '<br><br>';
3895 }
3896 $txttoshow .= dol_string_nohtmltag($this->note_private, 1);
3897 }
3898 }
3899 }
3900
3901 if ($txttoshow) {
3902 $result .= ' <span class="note inline-block">';
3903 $result .= '<a href="'.DOL_URL_ROOT.'/comm/propal/note.php?id='.$this->id.'" class="classfortooltip" title="'.dol_escape_htmltag($txttoshow).'">';
3904 $result .= img_picto('', 'note');
3905 $result .= '</a>';
3906 $result .= '</span>';
3907 }
3908 }
3909
3910 global $action;
3911 $hookmanager->initHooks(array($this->element . 'dao'));
3912 $parameters = array('id' => $this->id, 'getnomurl' => &$result);
3913 $reshook = $hookmanager->executeHooks('getNomUrl', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
3914 if ($reshook > 0) {
3915 $result = $hookmanager->resPrint;
3916 } else {
3917 $result .= $hookmanager->resPrint;
3918 }
3919 return $result;
3920 }
3921
3928 public function getLinesArray($sqlforgedfilters = '')
3929 {
3930 return $this->fetch_lines(0, 0, $sqlforgedfilters);
3931 }
3932
3944 public function generateDocument($modele, $outputlangs, $hidedetails = 0, $hidedesc = 0, $hideref = 0, $moreparams = null)
3945 {
3946 global $conf, $langs;
3947
3948 $langs->load("propale");
3949 $outputlangs->load("products");
3950
3951 if (!dol_strlen($modele)) {
3952 $modele = 'azur';
3953
3954 if ($this->model_pdf) {
3955 $modele = $this->model_pdf;
3956 } elseif (getDolGlobalString('PROPALE_ADDON_PDF')) {
3957 $modele = getDolGlobalString('PROPALE_ADDON_PDF');
3958 }
3959 }
3960
3961 $modelpath = "core/modules/propale/doc/";
3962
3963 return $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams);
3964 }
3965
3974 public static function replaceThirdparty(DoliDB $dbs, $origin_id, $dest_id)
3975 {
3976 $tables = array(
3977 'propal'
3978 );
3979
3980 return CommonObject::commonReplaceThirdparty($dbs, $origin_id, $dest_id, $tables);
3981 }
3982
3991 public static function replaceProduct(DoliDB $db, $origin_id, $dest_id)
3992 {
3993 $tables = array(
3994 'propaldet'
3995 );
3996
3997 return CommonObject::commonReplaceProduct($db, $origin_id, $dest_id, $tables);
3998 }
3999
4007 public function getKanbanView($option = '', $arraydata = null)
4008 {
4009 global $langs;
4010
4011 $selected = (empty($arraydata['selected']) ? 0 : $arraydata['selected']);
4012
4013 $return = '<div class="box-flex-item box-flex-grow-zero">';
4014 $return .= '<div class="info-box info-box-sm">';
4015 $return .= '<div class="info-box-icon bg-infobox-action">';
4016 $return .= img_picto('', $this->picto);
4017 $return .= '</div>';
4018 $return .= '<div class="info-box-content">';
4019 $return .= '<span class="info-box-ref inline-block tdoverflowmax150 valignmiddle">'.(method_exists($this, 'getNomUrl') ? $this->getNomUrl() : $this->ref).'</span>';
4020 if ($selected >= 0) {
4021 $return .= '<input id="cb'.$this->id.'" class="flat checkforselect fright" type="checkbox" name="toselect[]" value="'.$this->id.'"'.($selected ? ' checked="checked"' : '').'>';
4022 }
4023 if (!empty($arraydata['projectlink'])) {
4024 $return .= '<span class="info-box-ref"> | '.$arraydata['projectlink'].'</span>';
4025 }
4026 $return .= '<br>';
4027 if (property_exists($this, 'thirdparty') && is_object($this->thirdparty)) {
4028 $return .= '<div class="info-box-ref tdoverflowmax150">'.$this->thirdparty->getNomUrl(1).'</div>';
4029 }
4030 if (property_exists($this, 'total_ht')) {
4031 $return .= '<span class="info-box-label amount" title="'.$langs->trans("AmountHT").'">'.price($this->total_ht).'</span>';
4032 }
4033 if (!empty($arraydata['authorlink'])) {
4034 $return .= ' &nbsp; <span class="info-box-label">'.$arraydata['authorlink'].'</span>';
4035 }
4036 if (method_exists($this, 'getLibStatut')) {
4037 $return .= '<br><div class="info-box-status">'.$this->getLibStatut(3).'</div>';
4038 }
4039 $return .= '</div>';
4040 $return .= '</div>';
4041 $return .= '</div>';
4042 return $return;
4043 }
4044}
if( $user->socid > 0) if(! $user->hasRight('accounting', 'chartofaccount')) $object
Definition card.php:66
$object ref
Definition info.php:89
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.
commonGenerateDocument($modelspath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams=null)
Common function for all objects extending CommonObject for generating documents.
fetchProject()
Load the project with id $this->fk_project into this->project.
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)
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.
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.
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 clickable link of object (with eventually picto)
getKanbanView($option='', $arraydata=null)
Return clickable 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.
closeProposal($user, $status, $note_private='', $notrigger=0, $note_public='')
Close/set the commercial proposal to status signed or refused (fill also date signature)
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)
Add a discount line into an proposal (as a proposal line) using an existing absolute discount (Consum...
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.
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.
Class to manage third parties objects (customers, suppliers, prospects...)
Class to manage translations.
Class to manage Dolibarr users.
print $langs trans("Ref").' m titre as m m statut as status
Or an array listing all the potential status of the object: array: int of the status => translated la...
Definition index.php:171
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_delete_dir_recursive($dir, $count=0, $nophperrors=0, $onlysub=0, &$countdeleted=0, $indexdatabase=1, $nolog=0, $level=0)
Remove a directory $dir and its subdirectories (or only files and subdirectories)
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.
setEntity($currentobject)
Set entity id to use when to create an object.
img_picto($titlealt, $picto, $moreatt='', $pictoisfullpath=0, $srconly=0, $notitle=0, $alt='', $morecss='', $marginleftonlyshort=2, $allowothertags=array())
Show picto whatever it's its name (generic function)
GETPOSTINT($paramname, $method=0)
Return the value of a $_GET or $_POST supervariable, converted into integer.
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 '.
isDolTms($timestamp)
isDolTms check if a timestamp is valid.
img_object($titlealt, $picto, $moreatt='', $pictoisfullpath=0, $srconly=0, $notitle=0, $allowothertags=array())
Show a picto called object_picto (generic function)
dol_strlen($string, $stringencoding='UTF-8')
Make a strlen call.
price($amount, $form=0, $outlangs='', $trunc=1, $rounding=-1, $forcerounding=-1, $currency_code='')
Function to format a value into an amount for visual output Function used into PDF and HTML pages.
dol_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...
dolGetStatus($statusLabel='', $statusLabelShort='', $html='', $statusType='status0', $displayMode=0, $url='', $params=array())
Output the badge of a status.
GETPOST($paramname, $check='alphanohtml', $method=0, $filter=null, $options=null, $noreplace=0)
Return value of a param into GET or POST supervariable.
dol_buildpath($path, $type=0, $returnemptyifnotfound=0)
Return path of url or filesystem.
dol_print_error($db=null, $error='', $errors=null)
Displays error message system with all the information to facilitate the diagnosis and the escalation...
dol_sanitizeFileName($str, $newstr='_', $unaccent=1, $includequotes=0)
Clean a string to use it as a file name.
getDolGlobalString($key, $default='')
Return a 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.
global $conf
The following vars must be defined: $type2label $form $conf, $lang, The following vars may also be de...
Definition member.php:79
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:90