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 $this->statut = $this->oldcopy->status;
2693 $this->status = $this->oldcopy->status;
2694 $this->date_signature = $this->oldcopy->date_signature;
2695 $this->note_private = $this->oldcopy->note_private;
2696
2697 $this->db->rollback();
2698 return -1;
2699 }
2700 } else {
2701 $this->error = $this->db->lasterror();
2702 $this->db->rollback();
2703 return -1;
2704 }
2705 }
2706
2715 public function classifyBilled(User $user, $notrigger = 0, $note = '')
2716 {
2717 global $conf, $langs;
2718
2719 $error = 0;
2720
2721 $now = dol_now();
2722 $num = 0;
2723
2724 $triggerName = 'PROPAL_CLASSIFY_BILLED';
2725
2726 $this->db->begin();
2727
2728 $newprivatenote = dol_concatdesc($this->note_private, $note);
2729
2730 $sql = 'UPDATE '.MAIN_DB_PREFIX.'propal SET fk_statut = '.self::STATUS_BILLED.", ";
2731 $sql .= " note_private = '".$this->db->escape($newprivatenote)."', date_cloture='".$this->db->idate($now)."', fk_user_cloture=".((int) $user->id);
2732 $sql .= ' WHERE rowid = '.((int) $this->id).' AND fk_statut = '.((int) self::STATUS_SIGNED);
2733
2734 dol_syslog(__METHOD__, LOG_DEBUG);
2735 $resql = $this->db->query($sql);
2736 if (!$resql) {
2737 $this->errors[] = $this->db->error();
2738 $error++;
2739 } else {
2740 $num = $this->db->affected_rows($resql);
2741 }
2742
2743 if (!$error) {
2744 $modelpdf = getDolGlobalString('PROPALE_ADDON_PDF_ODT_CLOSED', $this->model_pdf);
2745
2746 if (!getDolGlobalString('MAIN_DISABLE_PDF_AUTOUPDATE')) {
2747 // Define output language
2748 $outputlangs = $langs;
2749 if (getDolGlobalInt('MAIN_MULTILANGS')) {
2750 $outputlangs = new Translate("", $conf);
2751 $newlang = (GETPOST('lang_id', 'aZ09') ? GETPOST('lang_id', 'aZ09') : $this->thirdparty->default_lang);
2752 $outputlangs->setDefaultLang($newlang);
2753 }
2754
2755 // PDF
2756 $hidedetails = (getDolGlobalString('MAIN_GENERATE_DOCUMENTS_HIDE_DETAILS') ? 1 : 0);
2757 $hidedesc = (getDolGlobalString('MAIN_GENERATE_DOCUMENTS_HIDE_DESC') ? 1 : 0);
2758 $hideref = (getDolGlobalString('MAIN_GENERATE_DOCUMENTS_HIDE_REF') ? 1 : 0);
2759
2760 //$ret=$object->fetch($id); // Reload to get new records
2761 $this->generateDocument($modelpdf, $outputlangs, $hidedetails, $hidedesc, $hideref);
2762 }
2763
2764 $this->oldcopy = clone $this;
2765 $this->statut = self::STATUS_BILLED;
2766 $this->status = self::STATUS_BILLED;
2767 $this->date_cloture = $now;
2768 $this->note_private = $newprivatenote;
2769 }
2770
2771 if (!$notrigger && empty($error)) {
2772 // Call trigger
2773 $result = $this->call_trigger($triggerName, $user);
2774 if ($result < 0) {
2775 $error++;
2776 }
2777 // End call triggers
2778 }
2779
2780 if (!$error) {
2781 $this->db->commit();
2782 return $num;
2783 } else {
2784 foreach ($this->errors as $errmsg) {
2785 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2786 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2787 }
2788 $this->db->rollback();
2789 return -1 * $error;
2790 }
2791 }
2792
2799 public function setCancel(User $user)
2800 {
2801 $error = 0;
2802
2803 $this->db->begin();
2804
2805 $sql = "UPDATE ". MAIN_DB_PREFIX . "propal";
2806 $sql .= " SET fk_statut = " . self::STATUS_CANCELED . ",";
2807 $sql .= " fk_user_modif = " . ((int) $user->id);
2808 $sql .= " WHERE rowid = " . ((int) $this->id);
2809
2810 dol_syslog(get_class($this)."::cancel", LOG_DEBUG);
2811 if ($this->db->query($sql)) {
2812 if (!$error) {
2813 // Call trigger
2814 $result = $this->call_trigger('PROPAL_CANCEL', $user);
2815 if ($result < 0) {
2816 $error++;
2817 }
2818 // End call triggers
2819 }
2820
2821 if (!$error) {
2822 $this->statut = self::STATUS_CANCELED;
2824 $this->db->commit();
2825 return 1;
2826 } else {
2827 foreach ($this->errors as $errmsg) {
2828 dol_syslog(get_class($this)."::cancel ".$errmsg, LOG_ERR);
2829 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2830 }
2831 $this->db->rollback();
2832 return -1;
2833 }
2834 } else {
2835 $this->error = $this->db->error();
2836 $this->db->rollback();
2837 return -1;
2838 }
2839 }
2840
2841 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2849 public function setDraft($user, $notrigger = 0)
2850 {
2851 // phpcs:enable
2852 $error = 0;
2853
2854 // Protection
2855 if ($this->status <= self::STATUS_DRAFT) {
2856 return 0;
2857 }
2858
2859 dol_syslog(get_class($this)."::setDraft", LOG_DEBUG);
2860
2861 $this->db->begin();
2862
2863 $sql = "UPDATE ".MAIN_DB_PREFIX."propal";
2864 $sql .= " SET fk_statut = ".self::STATUS_DRAFT;
2865 $sql .= ", online_sign_ip = NULL , online_sign_name = NULL";
2866 $sql .= " WHERE rowid = ".((int) $this->id);
2867
2868 $resql = $this->db->query($sql);
2869 if (!$resql) {
2870 $this->errors[] = $this->db->error();
2871 $error++;
2872 }
2873
2874 if (!$error) {
2875 $this->oldcopy = clone $this;
2876 }
2877
2878 if (!$notrigger && empty($error)) {
2879 // Call trigger
2880 $result = $this->call_trigger('PROPAL_MODIFY', $user);
2881 if ($result < 0) {
2882 $error++;
2883 }
2884 // End call triggers
2885 }
2886
2887 if (!$error) {
2888 $this->statut = self::STATUS_DRAFT;
2889 $this->status = self::STATUS_DRAFT;
2890
2891 $this->db->commit();
2892 return 1;
2893 } else {
2894 foreach ($this->errors as $errmsg) {
2895 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2896 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2897 }
2898 $this->db->rollback();
2899 return -1 * $error;
2900 }
2901 }
2902
2903
2904 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2918 public function liste_array($shortlist = 0, $draft = 0, $notcurrentuser = 0, $socid = 0, $limit = 0, $offset = 0, $sortfield = 'p.datep', $sortorder = 'DESC')
2919 {
2920 // phpcs:enable
2921 global $user;
2922
2923 $ga = array();
2924
2925 $sql = "SELECT s.rowid, s.nom as name, s.client,";
2926 $sql .= " p.rowid as propalid, p.fk_statut, p.total_ht, p.ref, p.remise, ";
2927 $sql .= " p.datep as dp, p.fin_validite as datelimite";
2928 $sql .= " FROM ".MAIN_DB_PREFIX."societe as s, ".MAIN_DB_PREFIX."propal as p, ".MAIN_DB_PREFIX."c_propalst as c";
2929 $sql .= " WHERE p.entity IN (".getEntity('propal').")";
2930 $sql .= " AND p.fk_soc = s.rowid";
2931 $sql .= " AND p.fk_statut = c.id";
2932
2933 // If the internal user must only see his customers, force searching by him
2934 $search_sale = 0;
2935 if (empty($user->socid) && !$user->hasRight('societe', 'client', 'voir')) {
2936 $search_sale = $user->id;
2937 }
2938 // Search on sale representative
2939 if ($search_sale && $search_sale != '-1') {
2940 if ($search_sale == -2) {
2941 $sql .= " AND NOT EXISTS (SELECT sc.fk_soc FROM ".MAIN_DB_PREFIX."societe_commerciaux as sc WHERE sc.fk_soc = p.fk_soc)";
2942 } elseif ($search_sale > 0) {
2943 $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).")";
2944 }
2945 }
2946 // Search on socid
2947 if ($socid) {
2948 $sql .= " AND p.fk_soc = ".((int) $socid);
2949 }
2950 if ($draft) {
2951 $sql .= " AND p.fk_statut = ".((int) self::STATUS_DRAFT);
2952 }
2953 if ($notcurrentuser > 0) {
2954 $sql .= " AND p.fk_user_author <> ".((int) $user->id);
2955 }
2956 $sql .= $this->db->order($sortfield, $sortorder);
2957 $sql .= $this->db->plimit($limit, $offset);
2958
2959 $result = $this->db->query($sql);
2960 if ($result) {
2961 $num = $this->db->num_rows($result);
2962 if ($num) {
2963 $i = 0;
2964 while ($i < $num) {
2965 $obj = $this->db->fetch_object($result);
2966
2967 if ($shortlist == 1) {
2968 $ga[$obj->propalid] = $obj->ref;
2969 } elseif ($shortlist == 2) {
2970 $ga[$obj->propalid] = $obj->ref.' ('.$obj->name.')';
2971 } else {
2972 $ga[$i]['id'] = $obj->propalid;
2973 $ga[$i]['ref'] = $obj->ref;
2974 $ga[$i]['name'] = $obj->name;
2975 }
2976
2977 $i++;
2978 }
2979 }
2980 return $ga;
2981 } else {
2982 dol_print_error($this->db);
2983 return -1;
2984 }
2985 }
2986
2992 public function getInvoiceArrayList()
2993 {
2994 return $this->InvoiceArrayList($this->id);
2995 }
2996
2997 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3004 public function InvoiceArrayList($id)
3005 {
3006 // phpcs:enable
3007 $ga = array();
3008 $linkedInvoices = array();
3009
3010 $this->fetchObjectLinked($id, $this->element);
3011 foreach ($this->linkedObjectsIds as $objecttype => $objectid) {
3012 // Nouveau système du common object renvoi des rowid et non un id linéaire de 1 à n
3013 // On parcourt donc une liste d'objets en tant qu'objet unique
3014 foreach ($objectid as $key => $object) {
3015 // Cas des factures liees directement
3016 if ($objecttype == 'facture') {
3017 $linkedInvoices[] = $object;
3018 } else {
3019 // Cas des factures liees par un autre object (ex: commande)
3020 $this->fetchObjectLinked($object, $objecttype);
3021 foreach ($this->linkedObjectsIds as $subobjecttype => $subobjectid) {
3022 foreach ($subobjectid as $subkey => $subobject) {
3023 if ($subobjecttype == 'facture') {
3024 $linkedInvoices[] = $subobject;
3025 }
3026 }
3027 }
3028 }
3029 }
3030 }
3031
3032 if (count($linkedInvoices) > 0) {
3033 $sql = "SELECT rowid as facid, ref, total_ht as total, datef as df, fk_user_author, fk_statut, paye";
3034 $sql .= " FROM ".MAIN_DB_PREFIX."facture";
3035 $sql .= " WHERE rowid IN (".$this->db->sanitize(implode(',', $linkedInvoices)).")";
3036
3037 dol_syslog(get_class($this)."::InvoiceArrayList", LOG_DEBUG);
3038 $resql = $this->db->query($sql);
3039
3040 if ($resql) {
3041 $tab_sqlobj = array();
3042 $nump = $this->db->num_rows($resql);
3043 for ($i = 0; $i < $nump; $i++) {
3044 $sqlobj = $this->db->fetch_object($resql);
3045 $tab_sqlobj[] = $sqlobj;
3046 }
3047 $this->db->free($resql);
3048
3049 $nump = count($tab_sqlobj);
3050
3051 if ($nump) {
3052 $i = 0;
3053 while ($i < $nump) {
3054 $obj = array_shift($tab_sqlobj);
3055
3056 $ga[$i] = $obj;
3057
3058 $i++;
3059 }
3060 }
3061 return $ga;
3062 } else {
3063 return -1;
3064 }
3065 } else {
3066 return $ga;
3067 }
3068 }
3069
3077 public function delete($user, $notrigger = 0)
3078 {
3079 global $conf;
3080 require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
3081
3082 $error = 0;
3083
3084 $this->db->begin();
3085
3086 if (!$notrigger) {
3087 // Call trigger
3088 $result = $this->call_trigger('PROPAL_DELETE', $user);
3089 if ($result < 0) {
3090 $error++;
3091 }
3092 // End call triggers
3093 }
3094
3095 // Delete extrafields of lines and lines
3096 if (!$error && !empty($this->table_element_line)) {
3097 $tabletodelete = $this->table_element_line;
3098 $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).")";
3099 $sql = "DELETE FROM ".MAIN_DB_PREFIX.$tabletodelete." WHERE ".$this->fk_element." = ".((int) $this->id);
3100 if (!$this->db->query($sqlef) || !$this->db->query($sql)) {
3101 $error++;
3102 $this->error = $this->db->lasterror();
3103 $this->errors[] = $this->error;
3104 dol_syslog(get_class($this)."::delete error ".$this->error, LOG_ERR);
3105 }
3106 }
3107
3108 if (!$error) {
3109 // Delete linked object
3110 $res = $this->deleteObjectLinked();
3111 if ($res < 0) {
3112 $error++;
3113 }
3114 }
3115
3116 if (!$error) {
3117 // Delete linked contacts
3118 $res = $this->delete_linked_contact();
3119 if ($res < 0) {
3120 $error++;
3121 }
3122 }
3123
3124 // Removed extrafields of object
3125 if (!$error) {
3126 $result = $this->deleteExtraFields();
3127 if ($result < 0) {
3128 $error++;
3129 dol_syslog(get_class($this)."::delete error ".$this->error, LOG_ERR);
3130 }
3131 }
3132
3133 // Delete main record
3134 if (!$error) {
3135 $sql = "DELETE FROM ".MAIN_DB_PREFIX.$this->table_element." WHERE rowid = ".((int) $this->id);
3136 $res = $this->db->query($sql);
3137 if (!$res) {
3138 $error++;
3139 $this->error = $this->db->lasterror();
3140 $this->errors[] = $this->error;
3141 dol_syslog(get_class($this)."::delete error ".$this->error, LOG_ERR);
3142 }
3143 }
3144
3145 // Delete record into ECM index and physically
3146 if (!$error) {
3147 $res = $this->deleteEcmFiles(0); // Deleting files physically is done later with the dol_delete_dir_recursive
3148 $res = $this->deleteEcmFiles(1); // Deleting files physically is done later with the dol_delete_dir_recursive
3149 if (!$res) {
3150 $error++;
3151 }
3152 }
3153
3154 if (!$error) {
3155 // We remove directory
3156 $ref = dol_sanitizeFileName($this->ref);
3157 if ($conf->propal->multidir_output[$this->entity] && !empty($this->ref)) {
3158 $dir = $conf->propal->multidir_output[$this->entity]."/".$ref;
3159 $file = $dir."/".$ref.".pdf";
3160 if (file_exists($file)) {
3161 dol_delete_preview($this);
3162
3163 if (!dol_delete_file($file, 0, 0, 0, $this)) {
3164 $this->error = 'ErrorFailToDeleteFile';
3165 $this->errors[] = $this->error;
3166 $this->db->rollback();
3167 return 0;
3168 }
3169 }
3170 if (file_exists($dir)) {
3171 $res = @dol_delete_dir_recursive($dir); // delete files physically + into ecm tables
3172 if (!$res) {
3173 $this->error = 'ErrorFailToDeleteDir';
3174 $this->errors[] = $this->error;
3175 $this->db->rollback();
3176 return 0;
3177 }
3178 }
3179 }
3180 }
3181
3182 if (!$error) {
3183 dol_syslog(get_class($this)."::delete ".$this->id." by ".$user->id, LOG_DEBUG);
3184 $this->db->commit();
3185 return 1;
3186 } else {
3187 $this->db->rollback();
3188 return -1;
3189 }
3190 }
3191
3200 public function availability($availability_id, $notrigger = 0)
3201 {
3202 global $user;
3203
3204 if ($this->status >= self::STATUS_DRAFT) {
3205 $error = 0;
3206
3207 $this->db->begin();
3208
3209 $sql = 'UPDATE '.MAIN_DB_PREFIX.'propal';
3210 $sql .= ' SET fk_availability = '.((int) $availability_id);
3211 $sql .= ' WHERE rowid='.((int) $this->id);
3212
3213 dol_syslog(__METHOD__.' availability('.$availability_id.')', LOG_DEBUG);
3214 $resql = $this->db->query($sql);
3215 if (!$resql) {
3216 $this->errors[] = $this->db->error();
3217 $error++;
3218 }
3219
3220 if (!$error) {
3221 $this->oldcopy = clone $this;
3222 $this->availability_id = $availability_id;
3223 }
3224
3225 if (!$notrigger && empty($error)) {
3226 // Call trigger
3227 $result = $this->call_trigger('PROPAL_MODIFY', $user);
3228 if ($result < 0) {
3229 $error++;
3230 }
3231 // End call triggers
3232 }
3233
3234 if (!$error) {
3235 $this->db->commit();
3236 return 1;
3237 } else {
3238 foreach ($this->errors as $errmsg) {
3239 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
3240 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
3241 }
3242 $this->db->rollback();
3243 return -1 * $error;
3244 }
3245 } else {
3246 $error_str = 'Propal status do not meet requirement '.$this->status;
3247 dol_syslog(__METHOD__.$error_str, LOG_ERR);
3248 $this->error = $error_str;
3249 $this->errors[] = $this->error;
3250 return -2;
3251 }
3252 }
3253
3254 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3263 public function demand_reason($demand_reason_id, $notrigger = 0)
3264 {
3265 // phpcs:enable
3266 global $user;
3267
3268 if ($this->status >= self::STATUS_DRAFT) {
3269 $error = 0;
3270
3271 $this->db->begin();
3272
3273 $sql = 'UPDATE '.MAIN_DB_PREFIX.'propal';
3274 $sql .= ' SET fk_input_reason = '.((int) $demand_reason_id);
3275 $sql .= ' WHERE rowid='.((int) $this->id);
3276
3277 dol_syslog(__METHOD__.' demand_reason('.$demand_reason_id.')', LOG_DEBUG);
3278 $resql = $this->db->query($sql);
3279 if (!$resql) {
3280 $this->errors[] = $this->db->error();
3281 $error++;
3282 }
3283
3284 if (!$error) {
3285 $this->oldcopy = clone $this;
3286 $this->demand_reason_id = $demand_reason_id;
3287 }
3288
3289 if (!$notrigger && empty($error)) {
3290 // Call trigger
3291 $result = $this->call_trigger('PROPAL_MODIFY', $user);
3292 if ($result < 0) {
3293 $error++;
3294 }
3295 // End call triggers
3296 }
3297
3298 if (!$error) {
3299 $this->db->commit();
3300 return 1;
3301 } else {
3302 foreach ($this->errors as $errmsg) {
3303 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
3304 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
3305 }
3306 $this->db->rollback();
3307 return -1 * $error;
3308 }
3309 } else {
3310 $error_str = 'Propal status do not meet requirement '.$this->status;
3311 dol_syslog(__METHOD__.$error_str, LOG_ERR);
3312 $this->error = $error_str;
3313 $this->errors[] = $this->error;
3314 return -2;
3315 }
3316 }
3317
3318
3325 public function info($id)
3326 {
3327 $sql = "SELECT c.rowid, ";
3328 $sql .= " c.datec, c.date_valid as datev, c.date_signature, c.date_cloture,";
3329 $sql .= " c.fk_user_author, c.fk_user_valid, c.fk_user_signature, c.fk_user_cloture";
3330 $sql .= " FROM ".MAIN_DB_PREFIX."propal as c";
3331 $sql .= " WHERE c.rowid = ".((int) $id);
3332
3333 $result = $this->db->query($sql);
3334
3335 if ($result) {
3336 if ($this->db->num_rows($result)) {
3337 $obj = $this->db->fetch_object($result);
3338
3339 $this->id = $obj->rowid;
3340
3341 $this->date_creation = $this->db->jdate($obj->datec);
3342 $this->date_validation = $this->db->jdate($obj->datev);
3343 $this->date_signature = $this->db->jdate($obj->date_signature);
3344 $this->date_cloture = $this->db->jdate($obj->date_cloture);
3345
3346 $this->user_creation_id = $obj->fk_user_author;
3347 $this->user_validation_id = $obj->fk_user_valid;
3348
3349 if ($obj->fk_user_signature) {
3350 $user_signature = new User($this->db);
3351 $user_signature->fetch($obj->fk_user_signature);
3352 $this->user_signature = $user_signature;
3353 }
3354
3355 $this->user_closing_id = $obj->fk_user_cloture;
3356 }
3357 $this->db->free($result);
3358 } else {
3359 dol_print_error($this->db);
3360 }
3361 }
3362
3363
3370 public function getLibStatut($mode = 0)
3371 {
3372 return $this->LibStatut($this->status, $mode);
3373 }
3374
3375 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3383 public function LibStatut($status, $mode = 1)
3384 {
3385 // phpcs:enable
3386 global $hookmanager;
3387
3388 // Init/load array of translation of status
3389 if (empty($this->labelStatus) || empty($this->labelStatusShort)) {
3390 global $langs;
3391 $langs->load("propal");
3392 $this->labelStatus[-1] = $langs->transnoentitiesnoconv("PropalStatusCanceled");
3393 $this->labelStatus[0] = $langs->transnoentitiesnoconv("PropalStatusDraft");
3394 $this->labelStatus[1] = $langs->transnoentitiesnoconv("PropalStatusValidated");
3395 $this->labelStatus[2] = $langs->transnoentitiesnoconv("PropalStatusSigned");
3396 $this->labelStatus[3] = $langs->transnoentitiesnoconv("PropalStatusNotSigned");
3397 $this->labelStatus[4] = $langs->transnoentitiesnoconv("PropalStatusBilled");
3398 $this->labelStatusShort[-1] = $langs->transnoentitiesnoconv("PropalStatusCanceledShort");
3399 $this->labelStatusShort[0] = $langs->transnoentitiesnoconv("PropalStatusDraftShort");
3400 $this->labelStatusShort[1] = $langs->transnoentitiesnoconv("PropalStatusValidatedShort");
3401 $this->labelStatusShort[2] = $langs->transnoentitiesnoconv("PropalStatusSignedShort");
3402 $this->labelStatusShort[3] = $langs->transnoentitiesnoconv("PropalStatusNotSignedShort");
3403 $this->labelStatusShort[4] = $langs->transnoentitiesnoconv("PropalStatusBilledShort");
3404 }
3405
3406 $statusType = '';
3407 if ($status == self::STATUS_CANCELED) {
3408 $statusType = 'status9';
3409 } elseif ($status == self::STATUS_DRAFT) {
3410 $statusType = 'status0';
3411 } elseif ($status == self::STATUS_VALIDATED) {
3412 $statusType = 'status1';
3413 } elseif ($status == self::STATUS_SIGNED) {
3414 $statusType = 'status4';
3415 } elseif ($status == self::STATUS_NOTSIGNED) {
3416 $statusType = 'status9';
3417 } elseif ($status == self::STATUS_BILLED) {
3418 $statusType = 'status6';
3419 }
3420
3421 $parameters = array('status' => $status, 'mode' => $mode);
3422 $reshook = $hookmanager->executeHooks('LibStatut', $parameters, $this); // Note that $action and $object may have been modified by hook
3423
3424 if ($reshook > 0) {
3425 return $hookmanager->resPrint;
3426 }
3427
3428 return dolGetStatus($this->labelStatus[$status], $this->labelStatusShort[$status], '', $statusType, $mode);
3429 }
3430
3431
3432 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3440 public function load_board($user, $mode)
3441 {
3442 // phpcs:enable
3443 global $conf, $langs;
3444
3445 $clause = " WHERE";
3446
3447 $sql = "SELECT p.rowid, p.ref, p.datec as datec, p.fin_validite as datefin, p.total_ht";
3448 $sql .= " FROM ".MAIN_DB_PREFIX."propal as p";
3449 $sql .= $clause." p.entity IN (".getEntity('propal').")";
3450 if ($mode == 'opened') {
3451 $sql .= " AND p.fk_statut = ".self::STATUS_VALIDATED;
3452 }
3453 if ($mode == 'signed') {
3454 $sql .= " AND p.fk_statut = ".self::STATUS_SIGNED;
3455 }
3456 // If the internal user must only see his customers, force searching by him
3457 $search_sale = 0;
3458 if (empty($user->socid) && !$user->hasRight('societe', 'client', 'voir')) {
3459 $search_sale = $user->id;
3460 }
3461 // Search on sale representative
3462 if ($search_sale && $search_sale != '-1') {
3463 if ($search_sale == -2) {
3464 $sql .= " AND NOT EXISTS (SELECT sc.fk_soc FROM ".MAIN_DB_PREFIX."societe_commerciaux as sc WHERE sc.fk_soc = p.fk_soc)";
3465 } elseif ($search_sale > 0) {
3466 $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).")";
3467 }
3468 }
3469
3470 $resql = $this->db->query($sql);
3471 if ($resql) {
3472 $langs->load("propal");
3473 $now = dol_now();
3474
3475 $delay_warning = 0;
3476 $status = 0;
3477 $label = $labelShort = '';
3478 if ($mode == 'opened') {
3479 $delay_warning = $conf->propal->cloture->warning_delay;
3480 $status = self::STATUS_VALIDATED;
3481 $label = $langs->transnoentitiesnoconv("PropalsToClose");
3482 $labelShort = $langs->transnoentitiesnoconv("ToAcceptRefuse");
3483 }
3484 if ($mode == 'signed') {
3485 $delay_warning = $conf->propal->facturation->warning_delay;
3486 $status = self::STATUS_SIGNED;
3487 $label = $langs->trans("PropalsToBill"); // We set here bill but may be billed or ordered
3488 $labelShort = $langs->trans("ToBill");
3489 }
3490
3491 $response = new WorkboardResponse();
3492 $response->warning_delay = $delay_warning / 60 / 60 / 24;
3493 $response->label = $label;
3494 $response->labelShort = $labelShort;
3495 $response->url = DOL_URL_ROOT.'/comm/propal/list.php?search_status='.$status.'&mainmenu=commercial&leftmenu=propals';
3496 $response->url_late = DOL_URL_ROOT.'/comm/propal/list.php?search_option=late&mainmenu=commercial&leftmenu=propals&sortfield=p.datep&sortorder=asc';
3497 $response->img = img_object('', "propal");
3498
3499 // This assignment in condition is not a bug. It allows walking the results.
3500 while ($obj = $this->db->fetch_object($resql)) {
3501 $response->nbtodo++;
3502 $response->total += $obj->total_ht;
3503
3504 if ($mode == 'opened') {
3505 $datelimit = $this->db->jdate($obj->datefin);
3506 if ($datelimit < ($now - $delay_warning)) {
3507 $response->nbtodolate++;
3508 }
3509 }
3510 // TODO Definir regle des propales a facturer en retard
3511 // if ($mode == 'signed' && ! count($this->FactureListeArray($obj->rowid))) $this->nbtodolate++;
3512 }
3513
3514 return $response;
3515 } else {
3516 $this->error = $this->db->error();
3517 return -1;
3518 }
3519 }
3520
3521
3529 public function initAsSpecimen()
3530 {
3531 global $conf, $langs;
3532
3533 // Load array of products prodids
3534 $num_prods = 0;
3535 $prodids = array();
3536 $sql = "SELECT rowid";
3537 $sql .= " FROM ".MAIN_DB_PREFIX."product";
3538 $sql .= " WHERE entity IN (".getEntity('product').")";
3539 $sql .= $this->db->plimit(100);
3540
3541 $resql = $this->db->query($sql);
3542 if ($resql) {
3543 $num_prods = $this->db->num_rows($resql);
3544 $i = 0;
3545 while ($i < $num_prods) {
3546 $i++;
3547 $row = $this->db->fetch_row($resql);
3548 $prodids[$i] = $row[0];
3549 }
3550 }
3551
3552 // Initialise parameters
3553 $this->id = 0;
3554 $this->ref = 'SPECIMEN';
3555 $this->ref_client = 'NEMICEPS';
3556 $this->specimen = 1;
3557 $this->socid = 1;
3558 $this->date = time();
3559 $this->fin_validite = $this->date + 3600 * 24 * 30;
3560 $this->cond_reglement_id = 1;
3561 $this->cond_reglement_code = 'RECEP';
3562 $this->mode_reglement_id = 7;
3563 $this->mode_reglement_code = 'CHQ';
3564 $this->availability_id = 1;
3565 $this->availability_code = 'AV_NOW';
3566 $this->demand_reason_id = 1;
3567 $this->demand_reason_code = 'SRC_00';
3568 $this->note_public = 'This is a comment (public)';
3569 $this->note_private = 'This is a comment (private)';
3570
3571 $this->multicurrency_tx = 1;
3572 $this->multicurrency_code = $conf->currency;
3573
3574 // Lines
3575 $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)
3576 $xnbp = 0;
3577 while ($xnbp < $nbp) {
3578 $line = new PropaleLigne($this->db);
3579 $line->desc = $langs->trans("Description")." ".$xnbp;
3580 $line->qty = 1;
3581 $line->subprice = 100;
3582 $line->price = 100;
3583 $line->tva_tx = 20;
3584 $line->localtax1_tx = 0;
3585 $line->localtax2_tx = 0;
3586 if ($xnbp == 2) {
3587 $line->total_ht = 50;
3588 $line->total_ttc = 60;
3589 $line->total_tva = 10;
3590 $line->remise_percent = 50;
3591 } else {
3592 $line->total_ht = 100;
3593 $line->total_ttc = 120;
3594 $line->total_tva = 20;
3595 $line->remise_percent = 00;
3596 }
3597
3598 if ($num_prods > 0) {
3599 $prodid = mt_rand(1, $num_prods);
3600 $line->fk_product = $prodids[$prodid];
3601 $line->product_ref = 'SPECIMEN';
3602 }
3603
3604 $this->lines[$xnbp] = $line;
3605
3606 $this->total_ht += $line->total_ht;
3607 $this->total_tva += $line->total_tva;
3608 $this->total_ttc += $line->total_ttc;
3609
3610 $xnbp++;
3611 }
3612
3613 return 1;
3614 }
3615
3621 public function loadStateBoard()
3622 {
3623 global $user;
3624
3625 $this->nb = array();
3626 $clause = "WHERE";
3627
3628 $sql = "SELECT count(p.rowid) as nb";
3629 $sql .= " FROM ".MAIN_DB_PREFIX."propal as p";
3630 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe as s ON p.fk_soc = s.rowid";
3631 $sql .= " ".$clause." p.entity IN (".getEntity('propal').")";
3632
3633 // If the internal user must only see his customers, force searching by him
3634 $search_sale = 0;
3635 if (empty($user->socid) && !$user->hasRight('societe', 'client', 'voir')) {
3636 $search_sale = $user->id;
3637 }
3638 // Search on sale representative
3639 if ($search_sale && $search_sale != '-1') {
3640 if ($search_sale == -2) {
3641 $sql .= " AND NOT EXISTS (SELECT sc.fk_soc FROM ".MAIN_DB_PREFIX."societe_commerciaux as sc WHERE sc.fk_soc = p.fk_soc)";
3642 } elseif ($search_sale > 0) {
3643 $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).")";
3644 }
3645 }
3646
3647 $resql = $this->db->query($sql);
3648 if ($resql) {
3649 // This assignment in condition is not a bug. It allows walking the results.
3650 while ($obj = $this->db->fetch_object($resql)) {
3651 $this->nb["proposals"] = $obj->nb;
3652 }
3653 $this->db->free($resql);
3654 return 1;
3655 } else {
3656 dol_print_error($this->db);
3657 $this->error = $this->db->error();
3658 return -1;
3659 }
3660 }
3661
3662
3670 public function getNextNumRef($soc)
3671 {
3672 global $conf, $langs;
3673 $langs->load("propal");
3674
3675 $classname = getDolGlobalString('PROPALE_ADDON');
3676
3677 if (!empty($classname)) {
3678 $mybool = false;
3679
3680 $file = $classname.".php";
3681
3682 // Include file with class
3683 $dirmodels = array_merge(array('/'), (array) $conf->modules_parts['models']);
3684 foreach ($dirmodels as $reldir) {
3685 $dir = dol_buildpath($reldir."core/modules/propale/");
3686
3687 // Load file with numbering class (if found)
3688 $mybool = ((bool) @include_once $dir.$file) || $mybool;
3689 }
3690
3691 if (!$mybool) {
3692 dol_print_error(null, "Failed to include file ".$file);
3693 return '';
3694 }
3695
3696 $obj = new $classname();
3697 '@phan-var-force ModeleNumRefPropales $obj';
3698
3699 $numref = $obj->getNextValue($soc, $this);
3700
3701 if ($numref != "") {
3702 return $numref;
3703 } else {
3704 $this->error = $obj->error;
3705 //dol_print_error($db,"Propale::getNextNumRef ".$obj->error);
3706 return "";
3707 }
3708 } else {
3709 $langs->load("errors");
3710 print $langs->trans("Error")." ".$langs->trans("ErrorModuleSetupNotComplete", $langs->transnoentitiesnoconv("Proposal"));
3711 return "";
3712 }
3713 }
3714
3721 public function getTooltipContentArray($params)
3722 {
3723 global $conf, $langs, $user;
3724
3725 $langs->load('propal');
3726 $datas = [];
3727 $nofetch = !empty($params['nofetch']);
3728
3729 if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
3730 return ['optimize' => $langs->trans("Proposal")];
3731 }
3732 if ($user->hasRight('propal', 'lire')) {
3733 $datas['picto'] = img_picto('', $this->picto, '', 0, 0, 0, '', 'paddingrightonly').'<u>'.$langs->trans("Proposal").'</u>';
3734 if (isset($this->status)) {
3735 $datas['status'] = ' '.$this->getLibStatut(5);
3736 }
3737 if (!empty($this->ref)) {
3738 $datas['ref'] = '<br><b>'.$langs->trans('Ref').':</b> '.$this->ref;
3739 }
3740 if (!$nofetch) {
3741 $langs->load('companies');
3742 if (empty($this->thirdparty)) {
3743 $this->fetch_thirdparty();
3744 }
3745 $datas['customer'] = '<br><b>'.$langs->trans('Customer').':</b> '.$this->thirdparty->getNomUrl(1, '', 0, 1);
3746 }
3747 if (!empty($this->ref_customer)) {
3748 $datas['refcustomer'] = '<br><b>'.$langs->trans('RefCustomer').':</b> '.$this->ref_customer;
3749 }
3750 if (!$nofetch) {
3751 $langs->load('project');
3752 if (is_null($this->project) || (is_object($this->project) && $this->project->isEmpty())) {
3753 $res = $this->fetchProject();
3754 if ($res > 0 && $this->project instanceof Project) {
3755 $datas['project'] = '<br><b>'.$langs->trans('Project').':</b> '.$this->project->getNomUrl(1, '', 0, 1);
3756 }
3757 }
3758 }
3759 if (!empty($this->total_ht)) {
3760 $datas['amountht'] = '<br><b>'.$langs->trans('AmountHT').':</b> '.price($this->total_ht, 0, $langs, 0, -1, -1, $conf->currency);
3761 }
3762 if (!empty($this->total_tva)) {
3763 $datas['vat'] = '<br><b>'.$langs->trans('VAT').':</b> '.price($this->total_tva, 0, $langs, 0, -1, -1, $conf->currency);
3764 }
3765 if (!empty($this->total_ttc)) {
3766 $datas['amountttc'] = '<br><b>'.$langs->trans('AmountTTC').':</b> '.price($this->total_ttc, 0, $langs, 0, -1, -1, $conf->currency);
3767 }
3768 if (!empty($this->date)) {
3769 $datas['date'] = '<br><b>'.$langs->trans('Date').':</b> '.dol_print_date($this->date, 'day');
3770 }
3771 if (!empty($this->delivery_date)) {
3772 $datas['deliverydate'] = '<br><b>'.$langs->trans('DeliveryDate').':</b> '.dol_print_date($this->delivery_date, 'dayhour');
3773 }
3774 }
3775
3776 return $datas;
3777 }
3778
3790 public function getNomUrl($withpicto = 0, $option = '', $get_params = '', $notooltip = 0, $save_lastsearch_value = -1, $addlinktonotes = -1)
3791 {
3792 global $langs, $conf, $user, $hookmanager;
3793
3794 if (!empty($conf->dol_no_mouse_hover)) {
3795 $notooltip = 1; // Force disable tooltips
3796 }
3797
3798 $result = '';
3799 $params = [
3800 'id' => $this->id,
3801 'objecttype' => $this->element,
3802 'option' => $option,
3803 'nofetch' => 1,
3804 ];
3805 $classfortooltip = 'classfortooltip';
3806 $dataparams = '';
3807 if (getDolGlobalInt('MAIN_ENABLE_AJAX_TOOLTIP')) {
3808 $classfortooltip = 'classforajaxtooltip';
3809 $dataparams = ' data-params="'.dol_escape_htmltag(json_encode($params)).'"';
3810 $label = '';
3811 } else {
3812 $label = implode($this->getTooltipContentArray($params));
3813 }
3814
3815 $url = '';
3816 if ($user->hasRight('propal', 'lire')) {
3817 if ($option == '') {
3818 $url = DOL_URL_ROOT.'/comm/propal/card.php?id='.$this->id.$get_params;
3819 } elseif ($option == 'compta') { // deprecated
3820 $url = DOL_URL_ROOT.'/comm/propal/card.php?id='.$this->id.$get_params;
3821 } elseif ($option == 'expedition') {
3822 $url = DOL_URL_ROOT.'/expedition/propal.php?id='.$this->id.$get_params;
3823 } elseif ($option == 'document') {
3824 $url = DOL_URL_ROOT.'/comm/propal/document.php?id='.$this->id.$get_params;
3825 }
3826
3827 if ($option != 'nolink') {
3828 // Add param to save lastsearch_values or not
3829 $add_save_lastsearch_values = ($save_lastsearch_value == 1 ? 1 : 0);
3830 if ($save_lastsearch_value == -1 && isset($_SERVER["PHP_SELF"]) && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) {
3831 $add_save_lastsearch_values = 1;
3832 }
3833 if ($add_save_lastsearch_values) {
3834 $url .= '&save_lastsearch_values=1';
3835 }
3836 }
3837 }
3838
3839 $linkclose = '';
3840 if (empty($notooltip) && $user->hasRight('propal', 'lire')) {
3841 if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
3842 $label = $langs->trans("Proposal");
3843 $linkclose .= ' alt="'.dolPrintHTMLForAttribute($label).'"';
3844 }
3845 $linkclose .= ($label ? ' title="'.dolPrintHTMLForAttribute($label).'"' : ' title="tocomplete"');
3846 $linkclose .= $dataparams.' class="'.$classfortooltip.'"';
3847 }
3848
3849 $linkstart = '<a href="'.$url.'"';
3850 $linkstart .= $linkclose.'>';
3851 $linkend = '</a>';
3852
3853 $result .= $linkstart;
3854 if ($withpicto) {
3855 $result .= img_object(($notooltip ? '' : $label), ($this->picto ? $this->picto : 'generic'), (($withpicto != 2) ? 'class="paddingright"' : ''), 0, 0, $notooltip ? 0 : 1);
3856 }
3857 if ($withpicto != 2) {
3858 $result .= $this->ref;
3859 }
3860 $result .= $linkend;
3861
3862 if ($addlinktonotes >= 0) {
3863 $txttoshow = '';
3864
3865 if ($addlinktonotes == 0) {
3866 if (!empty($this->note_private) || !empty($this->note_public)) {
3867 $txttoshow = $langs->trans('ViewPrivateNote');
3868 }
3869 } elseif ($addlinktonotes == 1) {
3870 if (!empty($this->note_private)) {
3871 $txttoshow .= ($user->socid > 0 ? '' : dol_string_nohtmltag($this->note_private, 1));
3872 }
3873 } elseif ($addlinktonotes == 2) {
3874 if (!empty($this->note_public)) {
3875 $txttoshow .= dol_string_nohtmltag($this->note_public, 1);
3876 }
3877 } elseif ($addlinktonotes == 3) {
3878 if ($user->socid > 0) {
3879 if (!empty($this->note_public)) {
3880 $txttoshow .= dol_string_nohtmltag($this->note_public, 1);
3881 }
3882 } else {
3883 if (!empty($this->note_public)) {
3884 $txttoshow .= dol_string_nohtmltag($this->note_public, 1);
3885 }
3886 if (!empty($this->note_private)) {
3887 if (!empty($txttoshow)) {
3888 $txttoshow .= '<br><br>';
3889 }
3890 $txttoshow .= dol_string_nohtmltag($this->note_private, 1);
3891 }
3892 }
3893 }
3894
3895 if ($txttoshow) {
3896 $result .= ' <span class="note inline-block">';
3897 $result .= '<a href="'.DOL_URL_ROOT.'/comm/propal/note.php?id='.$this->id.'" class="classfortooltip" title="'.dol_escape_htmltag($txttoshow).'">';
3898 $result .= img_picto('', 'note');
3899 $result .= '</a>';
3900 $result .= '</span>';
3901 }
3902 }
3903
3904 global $action;
3905 $hookmanager->initHooks(array($this->element . 'dao'));
3906 $parameters = array('id' => $this->id, 'getnomurl' => &$result);
3907 $reshook = $hookmanager->executeHooks('getNomUrl', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
3908 if ($reshook > 0) {
3909 $result = $hookmanager->resPrint;
3910 } else {
3911 $result .= $hookmanager->resPrint;
3912 }
3913 return $result;
3914 }
3915
3922 public function getLinesArray($sqlforgedfilters = '')
3923 {
3924 return $this->fetch_lines(0, 0, $sqlforgedfilters);
3925 }
3926
3938 public function generateDocument($modele, $outputlangs, $hidedetails = 0, $hidedesc = 0, $hideref = 0, $moreparams = null)
3939 {
3940 global $conf, $langs;
3941
3942 $langs->load("propale");
3943 $outputlangs->load("products");
3944
3945 if (!dol_strlen($modele)) {
3946 $modele = 'azur';
3947
3948 if ($this->model_pdf) {
3949 $modele = $this->model_pdf;
3950 } elseif (getDolGlobalString('PROPALE_ADDON_PDF')) {
3951 $modele = getDolGlobalString('PROPALE_ADDON_PDF');
3952 }
3953 }
3954
3955 $modelpath = "core/modules/propale/doc/";
3956
3957 return $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams);
3958 }
3959
3968 public static function replaceThirdparty(DoliDB $dbs, $origin_id, $dest_id)
3969 {
3970 $tables = array(
3971 'propal'
3972 );
3973
3974 return CommonObject::commonReplaceThirdparty($dbs, $origin_id, $dest_id, $tables);
3975 }
3976
3985 public static function replaceProduct(DoliDB $db, $origin_id, $dest_id)
3986 {
3987 $tables = array(
3988 'propaldet'
3989 );
3990
3991 return CommonObject::commonReplaceProduct($db, $origin_id, $dest_id, $tables);
3992 }
3993
4001 public function getKanbanView($option = '', $arraydata = null)
4002 {
4003 global $langs;
4004
4005 $selected = (empty($arraydata['selected']) ? 0 : $arraydata['selected']);
4006
4007 $return = '<div class="box-flex-item box-flex-grow-zero">';
4008 $return .= '<div class="info-box info-box-sm">';
4009 $return .= '<div class="info-box-icon bg-infobox-action">';
4010 $return .= img_picto('', $this->picto);
4011 $return .= '</div>';
4012 $return .= '<div class="info-box-content">';
4013 $return .= '<span class="info-box-ref inline-block tdoverflowmax150 valignmiddle">'.(method_exists($this, 'getNomUrl') ? $this->getNomUrl() : $this->ref).'</span>';
4014 if ($selected >= 0) {
4015 $return .= '<input id="cb'.$this->id.'" class="flat checkforselect fright" type="checkbox" name="toselect[]" value="'.$this->id.'"'.($selected ? ' checked="checked"' : '').'>';
4016 }
4017 if (!empty($arraydata['projectlink'])) {
4018 $return .= '<span class="info-box-ref"> | '.$arraydata['projectlink'].'</span>';
4019 }
4020 $return .= '<br>';
4021 if (property_exists($this, 'thirdparty') && is_object($this->thirdparty)) {
4022 $return .= '<div class="info-box-ref tdoverflowmax150">'.$this->thirdparty->getNomUrl(1).'</div>';
4023 }
4024 if (property_exists($this, 'total_ht')) {
4025 $return .= '<span class="info-box-label amount" title="'.$langs->trans("AmountHT").'">'.price($this->total_ht).'</span>';
4026 }
4027 if (!empty($arraydata['authorlink'])) {
4028 $return .= ' &nbsp; <span class="info-box-label">'.$arraydata['authorlink'].'</span>';
4029 }
4030 if (method_exists($this, 'getLibStatut')) {
4031 $return .= '<br><div class="info-box-status">'.$this->getLibStatut(3).'</div>';
4032 }
4033 $return .= '</div>';
4034 $return .= '</div>';
4035 $return .= '</div>';
4036 return $return;
4037 }
4038}
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