dolibarr 21.0.3
propal.class.php
Go to the documentation of this file.
1<?php
2/* Copyright (C) 2002-2004 Rodolphe Quiedeville <rodolphe@quiedeville.org>
3 * Copyright (C) 2004 Eric Seigne <eric.seigne@ryxeo.com>
4 * Copyright (C) 2004-2011 Laurent Destailleur <eldy@users.sourceforge.net>
5 * Copyright (C) 2005 Marc Barilley <marc@ocebo.com>
6 * Copyright (C) 2005-2013 Regis Houssin <regis.houssin@inodbox.com>
7 * Copyright (C) 2006 Andre Cianfarani <acianfa@free.fr>
8 * Copyright (C) 2008 Raphael Bertrand <raphael.bertrand@resultic.fr>
9 * Copyright (C) 2010-2020 Juanjo Menent <jmenent@2byte.es>
10 * Copyright (C) 2010-2022 Philippe Grand <philippe.grand@atoo-net.com>
11 * Copyright (C) 2012-2014 Christophe Battarel <christophe.battarel@altairis.fr>
12 * Copyright (C) 2012 Cedric Salvador <csalvador@gpcsolutions.fr>
13 * Copyright (C) 2013 Florian Henry <florian.henry@open-concept.pro>
14 * Copyright (C) 2014-2015 Marcos García <marcosgdf@gmail.com>
15 * Copyright (C) 2018 Nicolas ZABOURI <info@inovea-conseil.com>
16 * Copyright (C) 2018-2024 Frédéric France <frederic.france@free.fr>
17 * Copyright (C) 2018 Ferran Marcet <fmarcet@2byte.es>
18 * Copyright (C) 2022 ATM Consulting <contact@atm-consulting.fr>
19 * Copyright (C) 2022 OpenDSI <support@open-dsi.fr>
20 * Copyright (C) 2022 Gauthier VERDOL <gauthier.verdol@atm-consulting.fr>
21 * Copyright (C) 2023 William Mead <william.mead@manchenumerique.fr>
22 * Copyright (C) 2024 MDW <mdeweerd@users.noreply.github.com>
23 *
24 * This program is free software; you can redistribute it and/or modify
25 * it under the terms of the GNU General Public License as published by
26 * the Free Software Foundation; either version 3 of the License, or
27 * (at your option) any later version.
28 *
29 * This program is distributed in the hope that it will be useful,
30 * but WITHOUT ANY WARRANTY; without even the implied warranty of
31 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
32 * GNU General Public License for more details.
33 *
34 * You should have received a copy of the GNU General Public License
35 * along with this program. If not, see <https://www.gnu.org/licenses/>.
36 */
37
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 $sql .= " fk_projet = ".(!empty($this->fk_project) ? (int) $this->fk_project : "null").",";
1838 $sql .= " fk_cond_reglement = ".(!empty($this->cond_reglement_id) ? (int) $this->cond_reglement_id : "null").",";
1839 $sql .= " deposit_percent = ".(!empty($this->deposit_percent) ? "'".$this->db->escape($this->deposit_percent)."'" : "null").",";
1840 $sql .= " fk_mode_reglement = ".(!empty($this->mode_reglement_id) ? (int) $this->mode_reglement_id : "null").",";
1841 $sql .= " fk_input_reason = ".(!empty($this->demand_reason_id) ? (int) $this->demand_reason_id : "null").",";
1842 $sql .= " note_private = ".(isset($this->note_private) ? "'".$this->db->escape($this->note_private)."'" : "null").",";
1843 $sql .= " note_public = ".(isset($this->note_public) ? "'".$this->db->escape($this->note_public)."'" : "null").",";
1844 $sql .= " model_pdf = ".(isset($this->model_pdf) ? "'".$this->db->escape($this->model_pdf)."'" : "null").",";
1845 $sql .= " import_key = ".(isset($this->import_key) ? "'".$this->db->escape($this->import_key)."'" : "null");
1846 $sql .= " WHERE rowid = ".((int) $this->id);
1847
1848 $this->db->begin();
1849
1850 dol_syslog(get_class($this)."::update", LOG_DEBUG);
1851 $resql = $this->db->query($sql);
1852 if (!$resql) {
1853 $error++;
1854 $this->errors[] = "Error ".$this->db->lasterror();
1855 }
1856
1857 if (!$error) {
1858 $result = $this->insertExtraFields();
1859 if ($result < 0) {
1860 $error++;
1861 }
1862 }
1863
1864 if (!$error && !$notrigger) {
1865 // Call trigger
1866 $result = $this->call_trigger('PROPAL_MODIFY', $user);
1867 if ($result < 0) {
1868 $error++;
1869 }
1870 // End call triggers
1871 }
1872
1873 // Commit or rollback
1874 if ($error) {
1875 foreach ($this->errors as $errmsg) {
1876 dol_syslog(get_class($this)."::update ".$errmsg, LOG_ERR);
1877 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
1878 }
1879 $this->db->rollback();
1880 return -1 * $error;
1881 } else {
1882 $this->db->commit();
1883 return 1;
1884 }
1885 }
1886
1887
1888 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1897 public function fetch_lines($only_product = 0, $loadalsotranslation = 0, $sqlforgedfilters = '')
1898 {
1899 // phpcs:enable
1900 $this->lines = array();
1901
1902 $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,';
1903 $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,';
1904 $sql .= ' d.fk_unit,';
1905 $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,';
1906 $sql .= ' p.weight, p.weight_units, p.volume, p.volume_units,';
1907 $sql .= ' d.date_start, d.date_end,';
1908 $sql .= ' d.fk_multicurrency, d.multicurrency_code, d.multicurrency_subprice, d.multicurrency_total_ht, d.multicurrency_total_tva, d.multicurrency_total_ttc';
1909 $sql .= ' FROM '.MAIN_DB_PREFIX.'propaldet as d';
1910 $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'product as p ON (d.fk_product = p.rowid)';
1911 $sql .= ' WHERE d.fk_propal = '.((int) $this->id);
1912 if ($only_product) {
1913 $sql .= ' AND p.fk_product_type = 0';
1914 }
1915 if ($sqlforgedfilters) {
1916 $sql .= $sqlforgedfilters;
1917 }
1918 $sql .= ' ORDER by d.rang';
1919
1920 dol_syslog(get_class($this)."::fetch_lines", LOG_DEBUG);
1921 $result = $this->db->query($sql);
1922 if ($result) {
1923 require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php';
1924
1925 $num = $this->db->num_rows($result);
1926
1927 $i = 0;
1928 while ($i < $num) {
1929 $objp = $this->db->fetch_object($result);
1930
1931 $line = new PropaleLigne($this->db);
1932
1933 $line->rowid = $objp->rowid; //Deprecated
1934 $line->id = $objp->rowid;
1935 $line->fk_propal = $objp->fk_propal;
1936 $line->fk_parent_line = $objp->fk_parent_line;
1937 $line->product_type = $objp->product_type;
1938 $line->label = $objp->custom_label;
1939 $line->desc = $objp->description; // Description ligne
1940 $line->description = $objp->description; // Description ligne
1941 $line->qty = $objp->qty;
1942 $line->vat_src_code = $objp->vat_src_code;
1943 $line->tva_tx = $objp->tva_tx;
1944 $line->localtax1_tx = $objp->localtax1_tx;
1945 $line->localtax2_tx = $objp->localtax2_tx;
1946 $line->localtax1_type = $objp->localtax1_type;
1947 $line->localtax2_type = $objp->localtax2_type;
1948 $line->subprice = $objp->subprice;
1949 $line->fk_remise_except = $objp->fk_remise_except;
1950 $line->remise_percent = $objp->remise_percent;
1951 $line->price = $objp->price; // TODO deprecated
1952
1953 $line->info_bits = $objp->info_bits;
1954 $line->total_ht = $objp->total_ht;
1955 $line->total_tva = $objp->total_tva;
1956 $line->total_localtax1 = $objp->total_localtax1;
1957 $line->total_localtax2 = $objp->total_localtax2;
1958 $line->total_ttc = $objp->total_ttc;
1959 $line->fk_fournprice = $objp->fk_fournprice;
1960 $marginInfos = getMarginInfos($objp->subprice, $objp->remise_percent, $objp->tva_tx, $objp->localtax1_tx, $objp->localtax2_tx, $line->fk_fournprice, $objp->pa_ht);
1961 $line->pa_ht = $marginInfos[0];
1962 $line->marge_tx = $marginInfos[1];
1963 $line->marque_tx = $marginInfos[2];
1964 $line->special_code = $objp->special_code;
1965 $line->rang = $objp->rang;
1966
1967 $line->fk_product = $objp->fk_product;
1968
1969 $line->ref = $objp->product_ref; // deprecated
1970 $line->libelle = $objp->product_label; // deprecated
1971
1972 $line->product_ref = $objp->product_ref;
1973 $line->product_label = $objp->product_label;
1974 $line->product_desc = $objp->product_desc; // Description produit
1975 $line->product_tobatch = $objp->product_tobatch;
1976 $line->product_barcode = $objp->product_barcode;
1977
1978 $line->fk_product_type = $objp->fk_product_type; // deprecated
1979 $line->fk_unit = $objp->fk_unit;
1980 $line->weight = $objp->weight;
1981 $line->weight_units = $objp->weight_units;
1982 $line->volume = $objp->volume;
1983 $line->volume_units = $objp->volume_units;
1984
1985 $line->date_start = $this->db->jdate($objp->date_start);
1986 $line->date_end = $this->db->jdate($objp->date_end);
1987
1988 // Multicurrency
1989 $line->fk_multicurrency = $objp->fk_multicurrency;
1990 $line->multicurrency_code = $objp->multicurrency_code;
1991 $line->multicurrency_subprice = $objp->multicurrency_subprice;
1992 $line->multicurrency_total_ht = $objp->multicurrency_total_ht;
1993 $line->multicurrency_total_tva = $objp->multicurrency_total_tva;
1994 $line->multicurrency_total_ttc = $objp->multicurrency_total_ttc;
1995
1996 $line->fetch_optionals();
1997
1998 // multilangs
1999 if (getDolGlobalInt('MAIN_MULTILANGS') && !empty($objp->fk_product) && !empty($loadalsotranslation)) {
2000 $tmpproduct = new Product($this->db);
2001 $tmpproduct->fetch($objp->fk_product);
2002 $tmpproduct->getMultiLangs();
2003
2004 $line->multilangs = $tmpproduct->multilangs;
2005 }
2006
2007 $this->lines[$i] = $line;
2008
2009 $i++;
2010 }
2011
2012 $this->db->free($result);
2013
2014 return $num;
2015 } else {
2016 $this->error = $this->db->lasterror();
2017 return -3;
2018 }
2019 }
2020
2028 public function valid($user, $notrigger = 0)
2029 {
2030 global $conf;
2031
2032 require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
2033
2034 $error = 0;
2035
2036 // Protection
2037 if ($this->status == self::STATUS_VALIDATED) {
2038 dol_syslog(get_class($this)."::valid action abandoned: already validated", LOG_WARNING);
2039 return 0;
2040 }
2041
2042 if (!((!getDolGlobalString('MAIN_USE_ADVANCED_PERMS') && $user->hasRight('propal', 'creer'))
2043 || (getDolGlobalString('MAIN_USE_ADVANCED_PERMS') && $user->hasRight('propal', 'propal_advance', 'validate')))) {
2044 $this->error = 'ErrorPermissionDenied';
2045 dol_syslog(get_class($this)."::valid ".$this->error, LOG_ERR);
2046 return -1;
2047 }
2048
2049 $now = dol_now();
2050
2051 $this->db->begin();
2052
2053 // Numbering module definition
2054 $soc = new Societe($this->db);
2055 $soc->fetch($this->socid);
2056
2057 // Define new ref
2058 if (!$error && (preg_match('/^[\‍(]?PROV/i', $this->ref) || empty($this->ref))) { // empty should not happened, but when it occurs, the test save life
2059 $num = $this->getNextNumRef($soc);
2060 } else {
2061 $num = $this->ref;
2062 }
2063 $this->newref = dol_sanitizeFileName($num);
2064
2065 $sql = "UPDATE ".MAIN_DB_PREFIX."propal";
2066 $sql .= " SET ref = '".$this->db->escape($num)."',";
2067 $sql .= " fk_statut = ".self::STATUS_VALIDATED.", date_valid='".$this->db->idate($now)."', fk_user_valid=".((int) $user->id);
2068 $sql .= " WHERE rowid = ".((int) $this->id)." AND fk_statut = ".self::STATUS_DRAFT;
2069
2070 dol_syslog(get_class($this)."::valid", LOG_DEBUG);
2071 $resql = $this->db->query($sql);
2072 if (!$resql) {
2073 dol_print_error($this->db);
2074 $error++;
2075 }
2076
2077 // Trigger calls
2078 if (!$error && !$notrigger) {
2079 // Call trigger
2080 $result = $this->call_trigger('PROPAL_VALIDATE', $user);
2081 if ($result < 0) {
2082 $error++;
2083 }
2084 // End call triggers
2085 }
2086
2087 if (!$error) {
2088 $this->oldref = $this->ref;
2089
2090 // Rename directory if dir was a temporary ref
2091 if (preg_match('/^[\‍(]?PROV/i', $this->ref)) {
2092 // Now we rename also files into index
2093 $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)."'";
2094 $sql .= " WHERE filename LIKE '".$this->db->escape($this->ref)."%' AND filepath = 'propale/".$this->db->escape($this->ref)."' and entity = ".((int) $conf->entity);
2095 $resql = $this->db->query($sql);
2096 if (!$resql) {
2097 $error++;
2098 $this->error = $this->db->lasterror();
2099 }
2100 $sql = 'UPDATE '.MAIN_DB_PREFIX."ecm_files set filepath = 'propale/".$this->db->escape($this->newref)."'";
2101 $sql .= " WHERE filepath = 'propale/".$this->db->escape($this->ref)."' and entity = ".$conf->entity;
2102 $resql = $this->db->query($sql);
2103 if (!$resql) {
2104 $error++;
2105 $this->error = $this->db->lasterror();
2106 }
2107
2108 // We rename directory ($this->ref = old ref, $num = new ref) in order not to lose the attachments
2109 $oldref = dol_sanitizeFileName($this->ref);
2110 $newref = dol_sanitizeFileName($num);
2111 $dirsource = $conf->propal->multidir_output[$this->entity].'/'.$oldref;
2112 $dirdest = $conf->propal->multidir_output[$this->entity].'/'.$newref;
2113 if (!$error && file_exists($dirsource)) {
2114 dol_syslog(get_class($this)."::validate rename dir ".$dirsource." into ".$dirdest);
2115 if (@rename($dirsource, $dirdest)) {
2116 dol_syslog("Rename ok");
2117 // Rename docs starting with $oldref with $newref
2118 $listoffiles = dol_dir_list($dirdest, 'files', 1, '^'.preg_quote($oldref, '/'));
2119 foreach ($listoffiles as $fileentry) {
2120 $dirsource = $fileentry['name'];
2121 $dirdest = preg_replace('/^'.preg_quote($oldref, '/').'/', $newref, $dirsource);
2122 $dirsource = $fileentry['path'].'/'.$dirsource;
2123 $dirdest = $fileentry['path'].'/'.$dirdest;
2124 @rename($dirsource, $dirdest);
2125 }
2126 }
2127 }
2128 }
2129
2130 $this->ref = $num;
2131 $this->statut = self::STATUS_VALIDATED;
2133 $this->user_validation_id = $user->id;
2134 $this->datev = $now;
2135 $this->date_validation = $now;
2136
2137 $this->db->commit();
2138 return 1;
2139 } else {
2140 $this->db->rollback();
2141 return -1;
2142 }
2143 }
2144
2145
2146 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2155 public function set_date($user, $date, $notrigger = 0)
2156 {
2157 // phpcs:enable
2158 if (empty($date)) {
2159 $this->error = 'ErrorBadParameter';
2160 dol_syslog(get_class($this)."::set_date ".$this->error, LOG_ERR);
2161 return -1;
2162 }
2163
2164 if ($user->hasRight('propal', 'creer')) {
2165 $error = 0;
2166
2167 $this->db->begin();
2168
2169 $sql = "UPDATE ".MAIN_DB_PREFIX."propal SET datep = '".$this->db->idate($date)."'";
2170 $sql .= " WHERE rowid = ".((int) $this->id);
2171
2172 dol_syslog(__METHOD__, LOG_DEBUG);
2173 $resql = $this->db->query($sql);
2174 if (!$resql) {
2175 $this->errors[] = $this->db->error();
2176 $error++;
2177 }
2178
2179 if (!$error) {
2180 $this->oldcopy = clone $this;
2181 $this->date = $date;
2182 $this->datep = $date; // deprecated
2183 }
2184
2185 if (!$notrigger && empty($error)) {
2186 // Call trigger
2187 $result = $this->call_trigger('PROPAL_MODIFY', $user);
2188 if ($result < 0) {
2189 $error++;
2190 }
2191 // End call triggers
2192 }
2193
2194 if (!$error) {
2195 $this->db->commit();
2196 return 1;
2197 } else {
2198 foreach ($this->errors as $errmsg) {
2199 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2200 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2201 }
2202 $this->db->rollback();
2203 return -1 * $error;
2204 }
2205 }
2206
2207 return -1;
2208 }
2209
2210 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2219 public function set_echeance($user, $date_end_validity, $notrigger = 0)
2220 {
2221 // phpcs:enable
2222 if ($user->hasRight('propal', 'creer')) {
2223 $error = 0;
2224
2225 $this->db->begin();
2226
2227 $sql = "UPDATE ".MAIN_DB_PREFIX."propal SET fin_validite = ".($date_end_validity != '' ? "'".$this->db->idate($date_end_validity)."'" : 'null');
2228 $sql .= " WHERE rowid = ".((int) $this->id);
2229
2230 dol_syslog(__METHOD__, LOG_DEBUG);
2231
2232 $resql = $this->db->query($sql);
2233 if (!$resql) {
2234 $this->errors[] = $this->db->error();
2235 $error++;
2236 }
2237
2238
2239 if (!$error) {
2240 $this->oldcopy = clone $this;
2241 $this->fin_validite = $date_end_validity;
2242 }
2243
2244 if (!$notrigger && empty($error)) {
2245 // Call trigger
2246 $result = $this->call_trigger('PROPAL_MODIFY', $user);
2247 if ($result < 0) {
2248 $error++;
2249 }
2250 // End call triggers
2251 }
2252
2253 if (!$error) {
2254 $this->db->commit();
2255 return 1;
2256 } else {
2257 foreach ($this->errors as $errmsg) {
2258 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2259 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2260 }
2261 $this->db->rollback();
2262 return -1 * $error;
2263 }
2264 }
2265
2266 return -1;
2267 }
2268
2269 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2279 public function set_date_livraison($user, $delivery_date, $notrigger = 0)
2280 {
2281 // phpcs:enable
2282 return $this->setDeliveryDate($user, $delivery_date, $notrigger);
2283 }
2284
2293 public function setDeliveryDate($user, $delivery_date, $notrigger = 0)
2294 {
2295 if ($user->hasRight('propal', 'creer')) {
2296 $error = 0;
2297
2298 $this->db->begin();
2299
2300 $sql = "UPDATE ".MAIN_DB_PREFIX."propal ";
2301 $sql .= " SET date_livraison = ".(isDolTms($delivery_date) ? "'".$this->db->idate($delivery_date)."'" : 'null');
2302 $sql .= " WHERE rowid = ".((int) $this->id);
2303
2304 dol_syslog(__METHOD__, LOG_DEBUG);
2305 $resql = $this->db->query($sql);
2306 if (!$resql) {
2307 $this->errors[] = $this->db->error();
2308 $error++;
2309 }
2310
2311 if (!$error) {
2312 $this->oldcopy = clone $this;
2313 $this->delivery_date = $delivery_date;
2314 }
2315
2316 if (!$notrigger && empty($error)) {
2317 // Call trigger
2318 $result = $this->call_trigger('PROPAL_MODIFY', $user);
2319 if ($result < 0) {
2320 $error++;
2321 }
2322 // End call triggers
2323 }
2324
2325 if (!$error) {
2326 $this->db->commit();
2327 return 1;
2328 } else {
2329 foreach ($this->errors as $errmsg) {
2330 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2331 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2332 }
2333 $this->db->rollback();
2334 return -1 * $error;
2335 }
2336 }
2337
2338 return -1;
2339 }
2340
2341 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2350 public function set_availability($user, $id, $notrigger = 0)
2351 {
2352 // phpcs:enable
2353 if ($user->hasRight('propal', 'creer') && $this->status >= self::STATUS_DRAFT) {
2354 $error = 0;
2355
2356 $this->db->begin();
2357
2358 $sql = "UPDATE ".MAIN_DB_PREFIX."propal";
2359 $sql .= " SET fk_availability = ".((int) $id);
2360 $sql .= " WHERE rowid = ".((int) $this->id);
2361
2362 dol_syslog(__METHOD__.' availability('.$id.')', LOG_DEBUG);
2363 $resql = $this->db->query($sql);
2364 if (!$resql) {
2365 $this->errors[] = $this->db->error();
2366 $error++;
2367 }
2368
2369 if (!$error) {
2370 $this->oldcopy = clone $this;
2371 $this->fk_availability = $id;
2372 $this->availability_id = $id;
2373 }
2374
2375 if (!$notrigger && empty($error)) {
2376 // Call trigger
2377 $result = $this->call_trigger('PROPAL_MODIFY', $user);
2378 if ($result < 0) {
2379 $error++;
2380 }
2381 // End call triggers
2382 }
2383
2384 if (!$error) {
2385 $this->db->commit();
2386 return 1;
2387 } else {
2388 foreach ($this->errors as $errmsg) {
2389 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2390 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2391 }
2392 $this->db->rollback();
2393 return -1 * $error;
2394 }
2395 } else {
2396 $error_str = 'Propal status do not meet requirement '.$this->status;
2397 dol_syslog(__METHOD__.$error_str, LOG_ERR);
2398 $this->error = $error_str;
2399 $this->errors[] = $this->error;
2400 return -2;
2401 }
2402 }
2403
2404 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2413 public function set_demand_reason($user, $id, $notrigger = 0)
2414 {
2415 // phpcs:enable
2416 if ($user->hasRight('propal', 'creer') && $this->status >= self::STATUS_DRAFT) {
2417 $error = 0;
2418
2419 $this->db->begin();
2420
2421 $sql = "UPDATE ".MAIN_DB_PREFIX."propal ";
2422 $sql .= " SET fk_input_reason = ".((int) $id);
2423 $sql .= " WHERE rowid = ".((int) $this->id);
2424
2425 dol_syslog(__METHOD__, LOG_DEBUG);
2426 $resql = $this->db->query($sql);
2427 if (!$resql) {
2428 $this->errors[] = $this->db->error();
2429 $error++;
2430 }
2431
2432
2433 if (!$error) {
2434 $this->oldcopy = clone $this;
2435
2436 $this->demand_reason_id = $id;
2437 }
2438
2439
2440 if (!$notrigger && empty($error)) {
2441 // Call trigger
2442 $result = $this->call_trigger('PROPAL_MODIFY', $user);
2443 if ($result < 0) {
2444 $error++;
2445 }
2446 // End call triggers
2447 }
2448
2449 if (!$error) {
2450 $this->db->commit();
2451 return 1;
2452 } else {
2453 foreach ($this->errors as $errmsg) {
2454 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2455 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2456 }
2457 $this->db->rollback();
2458 return -1 * $error;
2459 }
2460 } else {
2461 $error_str = 'Propal status do not meet requirement '.$this->status;
2462 dol_syslog(__METHOD__.$error_str, LOG_ERR);
2463 $this->error = $error_str;
2464 $this->errors[] = $this->error;
2465 return -2;
2466 }
2467 }
2468
2469 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2478 public function set_ref_client($user, $ref_client, $notrigger = 0)
2479 {
2480 // phpcs:enable
2481 if ($user->hasRight('propal', 'creer')) {
2482 $error = 0;
2483
2484 $this->db->begin();
2485
2486 $sql = "UPDATE ".MAIN_DB_PREFIX."propal SET ref_client = ".(empty($ref_client) ? 'NULL' : "'".$this->db->escape($ref_client)."'");
2487 $sql .= " WHERE rowid = ".((int) $this->id);
2488
2489 dol_syslog(__METHOD__.' $this->id='.$this->id.', ref_client='.$ref_client, LOG_DEBUG);
2490 $resql = $this->db->query($sql);
2491 if (!$resql) {
2492 $this->errors[] = $this->db->error();
2493 $error++;
2494 }
2495
2496 if (!$error) {
2497 $this->oldcopy = clone $this;
2498 $this->ref_client = $ref_client;
2499 }
2500
2501 if (!$notrigger && empty($error)) {
2502 // Call trigger
2503 $result = $this->call_trigger('PROPAL_MODIFY', $user);
2504 if ($result < 0) {
2505 $error++;
2506 }
2507 // End call triggers
2508 }
2509
2510 if (!$error) {
2511 $this->db->commit();
2512 return 1;
2513 } else {
2514 foreach ($this->errors as $errmsg) {
2515 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2516 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2517 }
2518 $this->db->rollback();
2519 return -1 * $error;
2520 }
2521 }
2522
2523 return -1;
2524 }
2525
2526
2536 public function reopen($user, $status, $note = '', $notrigger = 0)
2537 {
2538 $error = 0;
2539
2540 $sql = "UPDATE ".MAIN_DB_PREFIX."propal";
2541 $sql .= " SET fk_statut = ".((int) $status).",";
2542 if (!empty($note)) {
2543 $sql .= " note_private = '".$this->db->escape($note)."',";
2544 }
2545 $sql .= " date_cloture=NULL, fk_user_cloture=NULL";
2546 $sql .= " WHERE rowid = ".((int) $this->id);
2547
2548 $this->db->begin();
2549
2550 dol_syslog(get_class($this)."::reopen", LOG_DEBUG);
2551 $resql = $this->db->query($sql);
2552 if (!$resql) {
2553 $error++;
2554 $this->errors[] = "Error ".$this->db->lasterror();
2555 }
2556 if (!$error) {
2557 if (!$notrigger) {
2558 // Call trigger
2559 $result = $this->call_trigger('PROPAL_REOPEN', $user);
2560 if ($result < 0) {
2561 $error++;
2562 }
2563 // End call triggers
2564 }
2565 }
2566
2567 // Commit or rollback
2568 if ($error) {
2569 if (!empty($this->errors)) {
2570 foreach ($this->errors as $errmsg) {
2571 dol_syslog(get_class($this)."::update ".$errmsg, LOG_ERR);
2572 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2573 }
2574 }
2575 $this->db->rollback();
2576 return -1 * $error;
2577 } else {
2578 $this->statut = $status;
2579 $this->status = $status;
2580
2581 $this->db->commit();
2582 return 1;
2583 }
2584 }
2585
2596 public function closeProposal($user, $status, $note_private = '', $notrigger = 0, $note_public = '')
2597 {
2598 global $langs,$conf;
2599
2600 $error = 0;
2601 $now = dol_now();
2602
2603 $this->db->begin();
2604
2605 $newprivatenote = dol_concatdesc($this->note_private, $note_private);
2606 $newpublicnote = dol_concatdesc($this->note_public, $note_public);
2607
2608 if (!getDolGlobalString('PROPALE_KEEP_OLD_SIGNATURE_INFO')) {
2609 $date_signature = $now;
2610 $fk_user_signature = $user->id;
2611 } else {
2612 $this->info($this->id);
2613 if (!isset($this->date_signature) || $this->date_signature == '') {
2614 $date_signature = $now;
2615 $fk_user_signature = $user->id;
2616 } else {
2617 $date_signature = $this->date_signature;
2618 $fk_user_signature = $this->user_signature->id;
2619 }
2620 }
2621
2622 $sql = "UPDATE ".MAIN_DB_PREFIX."propal";
2623 $sql .= " SET fk_statut = ".((int) $status).", note_private = '".$this->db->escape($newprivatenote)."', note_public = '".$this->db->escape($newpublicnote)."'";
2624 if ($status == self::STATUS_SIGNED) {
2625 $sql .= ", date_signature='".$this->db->idate($now)."', fk_user_signature = ".($fk_user_signature);
2626 }
2627 $sql .= " WHERE rowid = ".((int) $this->id);
2628
2629 $resql = $this->db->query($sql);
2630 if ($resql) {
2631 // Status self::STATUS_REFUSED by default
2632 $modelpdf = getDolGlobalString('PROPALE_ADDON_PDF_ODT_CLOSED', $this->model_pdf);
2633 $trigger_name = 'PROPAL_CLOSE_REFUSED'; // used later in call_trigger()
2634
2635 if ($status == self::STATUS_SIGNED) { // Status self::STATUS_SIGNED
2636 $trigger_name = 'PROPAL_CLOSE_SIGNED'; // used later in call_trigger()
2637 $modelpdf = getDolGlobalString('PROPALE_ADDON_PDF_ODT_TOBILL') ? $conf->global->PROPALE_ADDON_PDF_ODT_TOBILL : $this->model_pdf;
2638
2639 // The connected company is classified as a client
2640 $soc = new Societe($this->db);
2641 $soc->id = $this->socid;
2642 $result = $soc->setAsCustomer();
2643
2644 if ($result < 0) {
2645 $this->error = $this->db->lasterror();
2646 $this->db->rollback();
2647 return -2;
2648 }
2649 }
2650
2651 if (!getDolGlobalString('MAIN_DISABLE_PDF_AUTOUPDATE') && !getDolGlobalInt('PROPAL_DISABLE_AUTOUPDATE_ON_CLOSE')) {
2652 // Define output language
2653 $outputlangs = $langs;
2654 if (getDolGlobalInt('MAIN_MULTILANGS')) {
2655 $outputlangs = new Translate("", $conf);
2656 $newlang = (GETPOST('lang_id', 'aZ09') ? GETPOST('lang_id', 'aZ09') : $this->thirdparty->default_lang);
2657 $outputlangs->setDefaultLang($newlang);
2658 }
2659
2660 // PDF
2661 $hidedetails = (getDolGlobalString('MAIN_GENERATE_DOCUMENTS_HIDE_DETAILS') ? 1 : 0);
2662 $hidedesc = (getDolGlobalString('MAIN_GENERATE_DOCUMENTS_HIDE_DESC') ? 1 : 0);
2663 $hideref = (getDolGlobalString('MAIN_GENERATE_DOCUMENTS_HIDE_REF') ? 1 : 0);
2664
2665 //$ret=$object->fetch($id); // Reload to get new records
2666 $this->generateDocument($modelpdf, $outputlangs, $hidedetails, $hidedesc, $hideref);
2667 }
2668
2669 if (!$error) {
2670 $this->oldcopy = clone $this;
2671 $this->statut = $status;
2672 $this->status = $status;
2673 $this->date_signature = $date_signature;
2674 $this->note_private = $newprivatenote;
2675 }
2676
2677 if (!$notrigger && empty($error)) {
2678 // Call trigger
2679 $result = $this->call_trigger($trigger_name, $user);
2680 if ($result < 0) {
2681 $error++;
2682 }
2683 // End call triggers
2684 }
2685
2686 if (!$error) {
2687 $this->db->commit();
2688 return 1;
2689 } else {
2690 $this->statut = $this->oldcopy->status;
2691 $this->status = $this->oldcopy->status;
2692 $this->date_signature = $this->oldcopy->date_signature;
2693 $this->note_private = $this->oldcopy->note_private;
2694
2695 $this->db->rollback();
2696 return -1;
2697 }
2698 } else {
2699 $this->error = $this->db->lasterror();
2700 $this->db->rollback();
2701 return -1;
2702 }
2703 }
2704
2713 public function classifyBilled(User $user, $notrigger = 0, $note = '')
2714 {
2715 global $conf, $langs;
2716
2717 $error = 0;
2718
2719 $now = dol_now();
2720 $num = 0;
2721
2722 $triggerName = 'PROPAL_CLASSIFY_BILLED';
2723
2724 $this->db->begin();
2725
2726 $newprivatenote = dol_concatdesc($this->note_private, $note);
2727
2728 $sql = 'UPDATE '.MAIN_DB_PREFIX.'propal SET fk_statut = '.self::STATUS_BILLED.", ";
2729 $sql .= " note_private = '".$this->db->escape($newprivatenote)."', date_cloture='".$this->db->idate($now)."', fk_user_cloture=".((int) $user->id);
2730 $sql .= ' WHERE rowid = '.((int) $this->id).' AND fk_statut = '.((int) self::STATUS_SIGNED);
2731
2732 dol_syslog(__METHOD__, LOG_DEBUG);
2733 $resql = $this->db->query($sql);
2734 if (!$resql) {
2735 $this->errors[] = $this->db->error();
2736 $error++;
2737 } else {
2738 $num = $this->db->affected_rows($resql);
2739 }
2740
2741 if (!$error) {
2742 $modelpdf = getDolGlobalString('PROPALE_ADDON_PDF_ODT_CLOSED', $this->model_pdf);
2743
2744 if (!getDolGlobalString('MAIN_DISABLE_PDF_AUTOUPDATE')) {
2745 // Define output language
2746 $outputlangs = $langs;
2747 if (getDolGlobalInt('MAIN_MULTILANGS')) {
2748 $outputlangs = new Translate("", $conf);
2749 $newlang = (GETPOST('lang_id', 'aZ09') ? GETPOST('lang_id', 'aZ09') : $this->thirdparty->default_lang);
2750 $outputlangs->setDefaultLang($newlang);
2751 }
2752
2753 // PDF
2754 $hidedetails = (getDolGlobalString('MAIN_GENERATE_DOCUMENTS_HIDE_DETAILS') ? 1 : 0);
2755 $hidedesc = (getDolGlobalString('MAIN_GENERATE_DOCUMENTS_HIDE_DESC') ? 1 : 0);
2756 $hideref = (getDolGlobalString('MAIN_GENERATE_DOCUMENTS_HIDE_REF') ? 1 : 0);
2757
2758 //$ret=$object->fetch($id); // Reload to get new records
2759 $this->generateDocument($modelpdf, $outputlangs, $hidedetails, $hidedesc, $hideref);
2760 }
2761
2762 $this->oldcopy = clone $this;
2763 $this->statut = self::STATUS_BILLED;
2764 $this->status = self::STATUS_BILLED;
2765 $this->date_cloture = $now;
2766 $this->note_private = $newprivatenote;
2767 }
2768
2769 if (!$notrigger && empty($error)) {
2770 // Call trigger
2771 $result = $this->call_trigger($triggerName, $user);
2772 if ($result < 0) {
2773 $error++;
2774 }
2775 // End call triggers
2776 }
2777
2778 if (!$error) {
2779 $this->db->commit();
2780 return $num;
2781 } else {
2782 foreach ($this->errors as $errmsg) {
2783 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2784 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2785 }
2786 $this->db->rollback();
2787 return -1 * $error;
2788 }
2789 }
2790
2797 public function setCancel(User $user)
2798 {
2799 $error = 0;
2800
2801 $this->db->begin();
2802
2803 $sql = "UPDATE ". MAIN_DB_PREFIX . "propal";
2804 $sql .= " SET fk_statut = " . self::STATUS_CANCELED . ",";
2805 $sql .= " fk_user_modif = " . ((int) $user->id);
2806 $sql .= " WHERE rowid = " . ((int) $this->id);
2807
2808 dol_syslog(get_class($this)."::cancel", LOG_DEBUG);
2809 if ($this->db->query($sql)) {
2810 if (!$error) {
2811 // Call trigger
2812 $result = $this->call_trigger('PROPAL_CANCEL', $user);
2813 if ($result < 0) {
2814 $error++;
2815 }
2816 // End call triggers
2817 }
2818
2819 if (!$error) {
2820 $this->statut = self::STATUS_CANCELED;
2822 $this->db->commit();
2823 return 1;
2824 } else {
2825 foreach ($this->errors as $errmsg) {
2826 dol_syslog(get_class($this)."::cancel ".$errmsg, LOG_ERR);
2827 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2828 }
2829 $this->db->rollback();
2830 return -1;
2831 }
2832 } else {
2833 $this->error = $this->db->error();
2834 $this->db->rollback();
2835 return -1;
2836 }
2837 }
2838
2839 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2847 public function setDraft($user, $notrigger = 0)
2848 {
2849 // phpcs:enable
2850 $error = 0;
2851
2852 // Protection
2853 if ($this->status <= self::STATUS_DRAFT) {
2854 return 0;
2855 }
2856
2857 dol_syslog(get_class($this)."::setDraft", LOG_DEBUG);
2858
2859 $this->db->begin();
2860
2861 $sql = "UPDATE ".MAIN_DB_PREFIX."propal";
2862 $sql .= " SET fk_statut = ".self::STATUS_DRAFT;
2863 $sql .= ", online_sign_ip = NULL , online_sign_name = NULL";
2864 $sql .= " WHERE rowid = ".((int) $this->id);
2865
2866 $resql = $this->db->query($sql);
2867 if (!$resql) {
2868 $this->errors[] = $this->db->error();
2869 $error++;
2870 }
2871
2872 if (!$error) {
2873 $this->oldcopy = clone $this;
2874 }
2875
2876 if (!$notrigger && empty($error)) {
2877 // Call trigger
2878 $result = $this->call_trigger('PROPAL_MODIFY', $user);
2879 if ($result < 0) {
2880 $error++;
2881 }
2882 // End call triggers
2883 }
2884
2885 if (!$error) {
2886 $this->statut = self::STATUS_DRAFT;
2887 $this->status = self::STATUS_DRAFT;
2888
2889 $this->db->commit();
2890 return 1;
2891 } else {
2892 foreach ($this->errors as $errmsg) {
2893 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2894 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2895 }
2896 $this->db->rollback();
2897 return -1 * $error;
2898 }
2899 }
2900
2901
2902 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2916 public function liste_array($shortlist = 0, $draft = 0, $notcurrentuser = 0, $socid = 0, $limit = 0, $offset = 0, $sortfield = 'p.datep', $sortorder = 'DESC')
2917 {
2918 // phpcs:enable
2919 global $user;
2920
2921 $ga = array();
2922
2923 $sql = "SELECT s.rowid, s.nom as name, s.client,";
2924 $sql .= " p.rowid as propalid, p.fk_statut, p.total_ht, p.ref, p.remise, ";
2925 $sql .= " p.datep as dp, p.fin_validite as datelimite";
2926 $sql .= " FROM ".MAIN_DB_PREFIX."societe as s, ".MAIN_DB_PREFIX."propal as p, ".MAIN_DB_PREFIX."c_propalst as c";
2927 $sql .= " WHERE p.entity IN (".getEntity('propal').")";
2928 $sql .= " AND p.fk_soc = s.rowid";
2929 $sql .= " AND p.fk_statut = c.id";
2930
2931 // If the internal user must only see his customers, force searching by him
2932 $search_sale = 0;
2933 if (empty($user->socid) && !$user->hasRight('societe', 'client', 'voir')) {
2934 $search_sale = $user->id;
2935 }
2936 // Search on sale representative
2937 if ($search_sale && $search_sale != '-1') {
2938 if ($search_sale == -2) {
2939 $sql .= " AND NOT EXISTS (SELECT sc.fk_soc FROM ".MAIN_DB_PREFIX."societe_commerciaux as sc WHERE sc.fk_soc = p.fk_soc)";
2940 } elseif ($search_sale > 0) {
2941 $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).")";
2942 }
2943 }
2944 // Search on socid
2945 if ($socid) {
2946 $sql .= " AND p.fk_soc = ".((int) $socid);
2947 }
2948 if ($draft) {
2949 $sql .= " AND p.fk_statut = ".((int) self::STATUS_DRAFT);
2950 }
2951 if ($notcurrentuser > 0) {
2952 $sql .= " AND p.fk_user_author <> ".((int) $user->id);
2953 }
2954 $sql .= $this->db->order($sortfield, $sortorder);
2955 $sql .= $this->db->plimit($limit, $offset);
2956
2957 $result = $this->db->query($sql);
2958 if ($result) {
2959 $num = $this->db->num_rows($result);
2960 if ($num) {
2961 $i = 0;
2962 while ($i < $num) {
2963 $obj = $this->db->fetch_object($result);
2964
2965 if ($shortlist == 1) {
2966 $ga[$obj->propalid] = $obj->ref;
2967 } elseif ($shortlist == 2) {
2968 $ga[$obj->propalid] = $obj->ref.' ('.$obj->name.')';
2969 } else {
2970 $ga[$i]['id'] = $obj->propalid;
2971 $ga[$i]['ref'] = $obj->ref;
2972 $ga[$i]['name'] = $obj->name;
2973 }
2974
2975 $i++;
2976 }
2977 }
2978 return $ga;
2979 } else {
2980 dol_print_error($this->db);
2981 return -1;
2982 }
2983 }
2984
2990 public function getInvoiceArrayList()
2991 {
2992 return $this->InvoiceArrayList($this->id);
2993 }
2994
2995 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3002 public function InvoiceArrayList($id)
3003 {
3004 // phpcs:enable
3005 $ga = array();
3006 $linkedInvoices = array();
3007
3008 $this->fetchObjectLinked($id, $this->element);
3009 foreach ($this->linkedObjectsIds as $objecttype => $objectid) {
3010 // Nouveau système du common object renvoi des rowid et non un id linéaire de 1 à n
3011 // On parcourt donc une liste d'objets en tant qu'objet unique
3012 foreach ($objectid as $key => $object) {
3013 // Cas des factures liees directement
3014 if ($objecttype == 'facture') {
3015 $linkedInvoices[] = $object;
3016 } else {
3017 // Cas des factures liees par un autre object (ex: commande)
3018 $this->fetchObjectLinked($object, $objecttype);
3019 foreach ($this->linkedObjectsIds as $subobjecttype => $subobjectid) {
3020 foreach ($subobjectid as $subkey => $subobject) {
3021 if ($subobjecttype == 'facture') {
3022 $linkedInvoices[] = $subobject;
3023 }
3024 }
3025 }
3026 }
3027 }
3028 }
3029
3030 if (count($linkedInvoices) > 0) {
3031 $sql = "SELECT rowid as facid, ref, total_ht as total, datef as df, fk_user_author, fk_statut, paye";
3032 $sql .= " FROM ".MAIN_DB_PREFIX."facture";
3033 $sql .= " WHERE rowid IN (".$this->db->sanitize(implode(',', $linkedInvoices)).")";
3034
3035 dol_syslog(get_class($this)."::InvoiceArrayList", LOG_DEBUG);
3036 $resql = $this->db->query($sql);
3037
3038 if ($resql) {
3039 $tab_sqlobj = array();
3040 $nump = $this->db->num_rows($resql);
3041 for ($i = 0; $i < $nump; $i++) {
3042 $sqlobj = $this->db->fetch_object($resql);
3043 $tab_sqlobj[] = $sqlobj;
3044 }
3045 $this->db->free($resql);
3046
3047 $nump = count($tab_sqlobj);
3048
3049 if ($nump) {
3050 $i = 0;
3051 while ($i < $nump) {
3052 $obj = array_shift($tab_sqlobj);
3053
3054 $ga[$i] = $obj;
3055
3056 $i++;
3057 }
3058 }
3059 return $ga;
3060 } else {
3061 return -1;
3062 }
3063 } else {
3064 return $ga;
3065 }
3066 }
3067
3075 public function delete($user, $notrigger = 0)
3076 {
3077 global $conf;
3078 require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
3079
3080 $error = 0;
3081
3082 $this->db->begin();
3083
3084 if (!$notrigger) {
3085 // Call trigger
3086 $result = $this->call_trigger('PROPAL_DELETE', $user);
3087 if ($result < 0) {
3088 $error++;
3089 }
3090 // End call triggers
3091 }
3092
3093 // Delete extrafields of lines and lines
3094 if (!$error && !empty($this->table_element_line)) {
3095 $tabletodelete = $this->table_element_line;
3096 $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).")";
3097 $sql = "DELETE FROM ".MAIN_DB_PREFIX.$tabletodelete." WHERE ".$this->fk_element." = ".((int) $this->id);
3098 if (!$this->db->query($sqlef) || !$this->db->query($sql)) {
3099 $error++;
3100 $this->error = $this->db->lasterror();
3101 $this->errors[] = $this->error;
3102 dol_syslog(get_class($this)."::delete error ".$this->error, LOG_ERR);
3103 }
3104 }
3105
3106 if (!$error) {
3107 // Delete linked object
3108 $res = $this->deleteObjectLinked();
3109 if ($res < 0) {
3110 $error++;
3111 }
3112 }
3113
3114 if (!$error) {
3115 // Delete linked contacts
3116 $res = $this->delete_linked_contact();
3117 if ($res < 0) {
3118 $error++;
3119 }
3120 }
3121
3122 // Removed extrafields of object
3123 if (!$error) {
3124 $result = $this->deleteExtraFields();
3125 if ($result < 0) {
3126 $error++;
3127 dol_syslog(get_class($this)."::delete error ".$this->error, LOG_ERR);
3128 }
3129 }
3130
3131 // Delete main record
3132 if (!$error) {
3133 $sql = "DELETE FROM ".MAIN_DB_PREFIX.$this->table_element." WHERE rowid = ".((int) $this->id);
3134 $res = $this->db->query($sql);
3135 if (!$res) {
3136 $error++;
3137 $this->error = $this->db->lasterror();
3138 $this->errors[] = $this->error;
3139 dol_syslog(get_class($this)."::delete error ".$this->error, LOG_ERR);
3140 }
3141 }
3142
3143 // Delete record into ECM index and physically
3144 if (!$error) {
3145 $res = $this->deleteEcmFiles(0); // Deleting files physically is done later with the dol_delete_dir_recursive
3146 $res = $this->deleteEcmFiles(1); // Deleting files physically is done later with the dol_delete_dir_recursive
3147 if (!$res) {
3148 $error++;
3149 }
3150 }
3151
3152 if (!$error) {
3153 // We remove directory
3154 $ref = dol_sanitizeFileName($this->ref);
3155 if ($conf->propal->multidir_output[$this->entity] && !empty($this->ref)) {
3156 $dir = $conf->propal->multidir_output[$this->entity]."/".$ref;
3157 $file = $dir."/".$ref.".pdf";
3158 if (file_exists($file)) {
3159 dol_delete_preview($this);
3160
3161 if (!dol_delete_file($file, 0, 0, 0, $this)) {
3162 $this->error = 'ErrorFailToDeleteFile';
3163 $this->errors[] = $this->error;
3164 $this->db->rollback();
3165 return 0;
3166 }
3167 }
3168 if (file_exists($dir)) {
3169 $res = @dol_delete_dir_recursive($dir); // delete files physically + into ecm tables
3170 if (!$res) {
3171 $this->error = 'ErrorFailToDeleteDir';
3172 $this->errors[] = $this->error;
3173 $this->db->rollback();
3174 return 0;
3175 }
3176 }
3177 }
3178 }
3179
3180 if (!$error) {
3181 dol_syslog(get_class($this)."::delete ".$this->id." by ".$user->id, LOG_DEBUG);
3182 $this->db->commit();
3183 return 1;
3184 } else {
3185 $this->db->rollback();
3186 return -1;
3187 }
3188 }
3189
3198 public function availability($availability_id, $notrigger = 0)
3199 {
3200 global $user;
3201
3202 if ($this->status >= self::STATUS_DRAFT) {
3203 $error = 0;
3204
3205 $this->db->begin();
3206
3207 $sql = 'UPDATE '.MAIN_DB_PREFIX.'propal';
3208 $sql .= ' SET fk_availability = '.((int) $availability_id);
3209 $sql .= ' WHERE rowid='.((int) $this->id);
3210
3211 dol_syslog(__METHOD__.' availability('.$availability_id.')', LOG_DEBUG);
3212 $resql = $this->db->query($sql);
3213 if (!$resql) {
3214 $this->errors[] = $this->db->error();
3215 $error++;
3216 }
3217
3218 if (!$error) {
3219 $this->oldcopy = clone $this;
3220 $this->availability_id = $availability_id;
3221 }
3222
3223 if (!$notrigger && empty($error)) {
3224 // Call trigger
3225 $result = $this->call_trigger('PROPAL_MODIFY', $user);
3226 if ($result < 0) {
3227 $error++;
3228 }
3229 // End call triggers
3230 }
3231
3232 if (!$error) {
3233 $this->db->commit();
3234 return 1;
3235 } else {
3236 foreach ($this->errors as $errmsg) {
3237 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
3238 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
3239 }
3240 $this->db->rollback();
3241 return -1 * $error;
3242 }
3243 } else {
3244 $error_str = 'Propal status do not meet requirement '.$this->status;
3245 dol_syslog(__METHOD__.$error_str, LOG_ERR);
3246 $this->error = $error_str;
3247 $this->errors[] = $this->error;
3248 return -2;
3249 }
3250 }
3251
3252 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3261 public function demand_reason($demand_reason_id, $notrigger = 0)
3262 {
3263 // phpcs:enable
3264 global $user;
3265
3266 if ($this->status >= self::STATUS_DRAFT) {
3267 $error = 0;
3268
3269 $this->db->begin();
3270
3271 $sql = 'UPDATE '.MAIN_DB_PREFIX.'propal';
3272 $sql .= ' SET fk_input_reason = '.((int) $demand_reason_id);
3273 $sql .= ' WHERE rowid='.((int) $this->id);
3274
3275 dol_syslog(__METHOD__.' demand_reason('.$demand_reason_id.')', LOG_DEBUG);
3276 $resql = $this->db->query($sql);
3277 if (!$resql) {
3278 $this->errors[] = $this->db->error();
3279 $error++;
3280 }
3281
3282 if (!$error) {
3283 $this->oldcopy = clone $this;
3284 $this->demand_reason_id = $demand_reason_id;
3285 }
3286
3287 if (!$notrigger && empty($error)) {
3288 // Call trigger
3289 $result = $this->call_trigger('PROPAL_MODIFY', $user);
3290 if ($result < 0) {
3291 $error++;
3292 }
3293 // End call triggers
3294 }
3295
3296 if (!$error) {
3297 $this->db->commit();
3298 return 1;
3299 } else {
3300 foreach ($this->errors as $errmsg) {
3301 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
3302 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
3303 }
3304 $this->db->rollback();
3305 return -1 * $error;
3306 }
3307 } else {
3308 $error_str = 'Propal status do not meet requirement '.$this->status;
3309 dol_syslog(__METHOD__.$error_str, LOG_ERR);
3310 $this->error = $error_str;
3311 $this->errors[] = $this->error;
3312 return -2;
3313 }
3314 }
3315
3316
3323 public function info($id)
3324 {
3325 $sql = "SELECT c.rowid, ";
3326 $sql .= " c.datec, c.date_valid as datev, c.date_signature, c.date_cloture,";
3327 $sql .= " c.fk_user_author, c.fk_user_valid, c.fk_user_signature, c.fk_user_cloture";
3328 $sql .= " FROM ".MAIN_DB_PREFIX."propal as c";
3329 $sql .= " WHERE c.rowid = ".((int) $id);
3330
3331 $result = $this->db->query($sql);
3332
3333 if ($result) {
3334 if ($this->db->num_rows($result)) {
3335 $obj = $this->db->fetch_object($result);
3336
3337 $this->id = $obj->rowid;
3338
3339 $this->date_creation = $this->db->jdate($obj->datec);
3340 $this->date_validation = $this->db->jdate($obj->datev);
3341 $this->date_signature = $this->db->jdate($obj->date_signature);
3342 $this->date_cloture = $this->db->jdate($obj->date_cloture);
3343
3344 $this->user_creation_id = $obj->fk_user_author;
3345 $this->user_validation_id = $obj->fk_user_valid;
3346
3347 if ($obj->fk_user_signature) {
3348 $user_signature = new User($this->db);
3349 $user_signature->fetch($obj->fk_user_signature);
3350 $this->user_signature = $user_signature;
3351 }
3352
3353 $this->user_closing_id = $obj->fk_user_cloture;
3354 }
3355 $this->db->free($result);
3356 } else {
3357 dol_print_error($this->db);
3358 }
3359 }
3360
3361
3368 public function getLibStatut($mode = 0)
3369 {
3370 return $this->LibStatut($this->status, $mode);
3371 }
3372
3373 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3381 public function LibStatut($status, $mode = 1)
3382 {
3383 // phpcs:enable
3384 global $hookmanager;
3385
3386 // Init/load array of translation of status
3387 if (empty($this->labelStatus) || empty($this->labelStatusShort)) {
3388 global $langs;
3389 $langs->load("propal");
3390 $this->labelStatus[-1] = $langs->transnoentitiesnoconv("PropalStatusCanceled");
3391 $this->labelStatus[0] = $langs->transnoentitiesnoconv("PropalStatusDraft");
3392 $this->labelStatus[1] = $langs->transnoentitiesnoconv("PropalStatusValidated");
3393 $this->labelStatus[2] = $langs->transnoentitiesnoconv("PropalStatusSigned");
3394 $this->labelStatus[3] = $langs->transnoentitiesnoconv("PropalStatusNotSigned");
3395 $this->labelStatus[4] = $langs->transnoentitiesnoconv("PropalStatusBilled");
3396 $this->labelStatusShort[-1] = $langs->transnoentitiesnoconv("PropalStatusCanceledShort");
3397 $this->labelStatusShort[0] = $langs->transnoentitiesnoconv("PropalStatusDraftShort");
3398 $this->labelStatusShort[1] = $langs->transnoentitiesnoconv("PropalStatusValidatedShort");
3399 $this->labelStatusShort[2] = $langs->transnoentitiesnoconv("PropalStatusSignedShort");
3400 $this->labelStatusShort[3] = $langs->transnoentitiesnoconv("PropalStatusNotSignedShort");
3401 $this->labelStatusShort[4] = $langs->transnoentitiesnoconv("PropalStatusBilledShort");
3402 }
3403
3404 $statusType = '';
3405 if ($status == self::STATUS_CANCELED) {
3406 $statusType = 'status9';
3407 } elseif ($status == self::STATUS_DRAFT) {
3408 $statusType = 'status0';
3409 } elseif ($status == self::STATUS_VALIDATED) {
3410 $statusType = 'status1';
3411 } elseif ($status == self::STATUS_SIGNED) {
3412 $statusType = 'status4';
3413 } elseif ($status == self::STATUS_NOTSIGNED) {
3414 $statusType = 'status9';
3415 } elseif ($status == self::STATUS_BILLED) {
3416 $statusType = 'status6';
3417 }
3418
3419 $parameters = array('status' => $status, 'mode' => $mode);
3420 $reshook = $hookmanager->executeHooks('LibStatut', $parameters, $this); // Note that $action and $object may have been modified by hook
3421
3422 if ($reshook > 0) {
3423 return $hookmanager->resPrint;
3424 }
3425
3426 return dolGetStatus($this->labelStatus[$status], $this->labelStatusShort[$status], '', $statusType, $mode);
3427 }
3428
3429
3430 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3438 public function load_board($user, $mode)
3439 {
3440 // phpcs:enable
3441 global $conf, $langs;
3442
3443 $clause = " WHERE";
3444
3445 $sql = "SELECT p.rowid, p.ref, p.datec as datec, p.fin_validite as datefin, p.total_ht";
3446 $sql .= " FROM ".MAIN_DB_PREFIX."propal as p";
3447 $sql .= $clause." p.entity IN (".getEntity('propal').")";
3448 if ($mode == 'opened') {
3449 $sql .= " AND p.fk_statut = ".self::STATUS_VALIDATED;
3450 }
3451 if ($mode == 'signed') {
3452 $sql .= " AND p.fk_statut = ".self::STATUS_SIGNED;
3453 }
3454 // If the internal user must only see his customers, force searching by him
3455 $search_sale = 0;
3456 if (empty($user->socid) && !$user->hasRight('societe', 'client', 'voir')) {
3457 $search_sale = $user->id;
3458 }
3459 // Search on sale representative
3460 if ($search_sale && $search_sale != '-1') {
3461 if ($search_sale == -2) {
3462 $sql .= " AND NOT EXISTS (SELECT sc.fk_soc FROM ".MAIN_DB_PREFIX."societe_commerciaux as sc WHERE sc.fk_soc = p.fk_soc)";
3463 } elseif ($search_sale > 0) {
3464 $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).")";
3465 }
3466 }
3467
3468 $resql = $this->db->query($sql);
3469 if ($resql) {
3470 $langs->load("propal");
3471 $now = dol_now();
3472
3473 $delay_warning = 0;
3474 $status = 0;
3475 $label = $labelShort = '';
3476 if ($mode == 'opened') {
3477 $delay_warning = $conf->propal->cloture->warning_delay;
3478 $status = self::STATUS_VALIDATED;
3479 $label = $langs->transnoentitiesnoconv("PropalsToClose");
3480 $labelShort = $langs->transnoentitiesnoconv("ToAcceptRefuse");
3481 }
3482 if ($mode == 'signed') {
3483 $delay_warning = $conf->propal->facturation->warning_delay;
3484 $status = self::STATUS_SIGNED;
3485 $label = $langs->trans("PropalsToBill"); // We set here bill but may be billed or ordered
3486 $labelShort = $langs->trans("ToBill");
3487 }
3488
3489 $response = new WorkboardResponse();
3490 $response->warning_delay = $delay_warning / 60 / 60 / 24;
3491 $response->label = $label;
3492 $response->labelShort = $labelShort;
3493 $response->url = DOL_URL_ROOT.'/comm/propal/list.php?search_status='.$status.'&mainmenu=commercial&leftmenu=propals';
3494 $response->url_late = DOL_URL_ROOT.'/comm/propal/list.php?search_option=late&mainmenu=commercial&leftmenu=propals&sortfield=p.datep&sortorder=asc';
3495 $response->img = img_object('', "propal");
3496
3497 // This assignment in condition is not a bug. It allows walking the results.
3498 while ($obj = $this->db->fetch_object($resql)) {
3499 $response->nbtodo++;
3500 $response->total += $obj->total_ht;
3501
3502 if ($mode == 'opened') {
3503 $datelimit = $this->db->jdate($obj->datefin);
3504 if ($datelimit < ($now - $delay_warning)) {
3505 $response->nbtodolate++;
3506 }
3507 }
3508 // TODO Definir regle des propales a facturer en retard
3509 // if ($mode == 'signed' && ! count($this->FactureListeArray($obj->rowid))) $this->nbtodolate++;
3510 }
3511
3512 return $response;
3513 } else {
3514 $this->error = $this->db->error();
3515 return -1;
3516 }
3517 }
3518
3519
3527 public function initAsSpecimen()
3528 {
3529 global $conf, $langs;
3530
3531 // Load array of products prodids
3532 $num_prods = 0;
3533 $prodids = array();
3534 $sql = "SELECT rowid";
3535 $sql .= " FROM ".MAIN_DB_PREFIX."product";
3536 $sql .= " WHERE entity IN (".getEntity('product').")";
3537 $sql .= $this->db->plimit(100);
3538
3539 $resql = $this->db->query($sql);
3540 if ($resql) {
3541 $num_prods = $this->db->num_rows($resql);
3542 $i = 0;
3543 while ($i < $num_prods) {
3544 $i++;
3545 $row = $this->db->fetch_row($resql);
3546 $prodids[$i] = $row[0];
3547 }
3548 }
3549
3550 // Initialise parameters
3551 $this->id = 0;
3552 $this->ref = 'SPECIMEN';
3553 $this->ref_client = 'NEMICEPS';
3554 $this->specimen = 1;
3555 $this->socid = 1;
3556 $this->date = time();
3557 $this->fin_validite = $this->date + 3600 * 24 * 30;
3558 $this->cond_reglement_id = 1;
3559 $this->cond_reglement_code = 'RECEP';
3560 $this->mode_reglement_id = 7;
3561 $this->mode_reglement_code = 'CHQ';
3562 $this->availability_id = 1;
3563 $this->availability_code = 'AV_NOW';
3564 $this->demand_reason_id = 1;
3565 $this->demand_reason_code = 'SRC_00';
3566 $this->note_public = 'This is a comment (public)';
3567 $this->note_private = 'This is a comment (private)';
3568
3569 $this->multicurrency_tx = 1;
3570 $this->multicurrency_code = $conf->currency;
3571
3572 // Lines
3573 $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)
3574 $xnbp = 0;
3575 while ($xnbp < $nbp) {
3576 $line = new PropaleLigne($this->db);
3577 $line->desc = $langs->trans("Description")." ".$xnbp;
3578 $line->qty = 1;
3579 $line->subprice = 100;
3580 $line->price = 100;
3581 $line->tva_tx = 20;
3582 $line->localtax1_tx = 0;
3583 $line->localtax2_tx = 0;
3584 if ($xnbp == 2) {
3585 $line->total_ht = 50;
3586 $line->total_ttc = 60;
3587 $line->total_tva = 10;
3588 $line->remise_percent = 50;
3589 } else {
3590 $line->total_ht = 100;
3591 $line->total_ttc = 120;
3592 $line->total_tva = 20;
3593 $line->remise_percent = 00;
3594 }
3595
3596 if ($num_prods > 0) {
3597 $prodid = mt_rand(1, $num_prods);
3598 $line->fk_product = $prodids[$prodid];
3599 $line->product_ref = 'SPECIMEN';
3600 }
3601
3602 $this->lines[$xnbp] = $line;
3603
3604 $this->total_ht += $line->total_ht;
3605 $this->total_tva += $line->total_tva;
3606 $this->total_ttc += $line->total_ttc;
3607
3608 $xnbp++;
3609 }
3610
3611 return 1;
3612 }
3613
3619 public function loadStateBoard()
3620 {
3621 global $user;
3622
3623 $this->nb = array();
3624 $clause = "WHERE";
3625
3626 $sql = "SELECT count(p.rowid) as nb";
3627 $sql .= " FROM ".MAIN_DB_PREFIX."propal as p";
3628 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe as s ON p.fk_soc = s.rowid";
3629 $sql .= " ".$clause." p.entity IN (".getEntity('propal').")";
3630
3631 // If the internal user must only see his customers, force searching by him
3632 $search_sale = 0;
3633 if (empty($user->socid) && !$user->hasRight('societe', 'client', 'voir')) {
3634 $search_sale = $user->id;
3635 }
3636 // Search on sale representative
3637 if ($search_sale && $search_sale != '-1') {
3638 if ($search_sale == -2) {
3639 $sql .= " AND NOT EXISTS (SELECT sc.fk_soc FROM ".MAIN_DB_PREFIX."societe_commerciaux as sc WHERE sc.fk_soc = p.fk_soc)";
3640 } elseif ($search_sale > 0) {
3641 $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).")";
3642 }
3643 }
3644
3645 $resql = $this->db->query($sql);
3646 if ($resql) {
3647 // This assignment in condition is not a bug. It allows walking the results.
3648 while ($obj = $this->db->fetch_object($resql)) {
3649 $this->nb["proposals"] = $obj->nb;
3650 }
3651 $this->db->free($resql);
3652 return 1;
3653 } else {
3654 dol_print_error($this->db);
3655 $this->error = $this->db->error();
3656 return -1;
3657 }
3658 }
3659
3660
3668 public function getNextNumRef($soc)
3669 {
3670 global $conf, $langs;
3671 $langs->load("propal");
3672
3673 $classname = getDolGlobalString('PROPALE_ADDON');
3674
3675 if (!empty($classname)) {
3676 $mybool = false;
3677
3678 $file = $classname.".php";
3679
3680 // Include file with class
3681 $dirmodels = array_merge(array('/'), (array) $conf->modules_parts['models']);
3682 foreach ($dirmodels as $reldir) {
3683 $dir = dol_buildpath($reldir."core/modules/propale/");
3684
3685 // Load file with numbering class (if found)
3686 $mybool = ((bool) @include_once $dir.$file) || $mybool;
3687 }
3688
3689 if (!$mybool) {
3690 dol_print_error(null, "Failed to include file ".$file);
3691 return '';
3692 }
3693
3694 $obj = new $classname();
3695 '@phan-var-force ModeleNumRefPropales $obj';
3696
3697 $numref = $obj->getNextValue($soc, $this);
3698
3699 if ($numref != "") {
3700 return $numref;
3701 } else {
3702 $this->error = $obj->error;
3703 //dol_print_error($db,"Propale::getNextNumRef ".$obj->error);
3704 return "";
3705 }
3706 } else {
3707 $langs->load("errors");
3708 print $langs->trans("Error")." ".$langs->trans("ErrorModuleSetupNotComplete", $langs->transnoentitiesnoconv("Proposal"));
3709 return "";
3710 }
3711 }
3712
3719 public function getTooltipContentArray($params)
3720 {
3721 global $conf, $langs, $user;
3722
3723 $langs->load('propal');
3724 $datas = [];
3725 $nofetch = !empty($params['nofetch']);
3726
3727 if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
3728 return ['optimize' => $langs->trans("Proposal")];
3729 }
3730 if ($user->hasRight('propal', 'lire')) {
3731 $datas['picto'] = img_picto('', $this->picto, '', 0, 0, 0, '', 'paddingrightonly').'<u>'.$langs->trans("Proposal").'</u>';
3732 if (isset($this->status)) {
3733 $datas['status'] = ' '.$this->getLibStatut(5);
3734 }
3735 if (!empty($this->ref)) {
3736 $datas['ref'] = '<br><b>'.$langs->trans('Ref').':</b> '.$this->ref;
3737 }
3738 if (!$nofetch) {
3739 $langs->load('companies');
3740 if (empty($this->thirdparty)) {
3741 $this->fetch_thirdparty();
3742 }
3743 $datas['customer'] = '<br><b>'.$langs->trans('Customer').':</b> '.$this->thirdparty->getNomUrl(1, '', 0, 1);
3744 }
3745 if (!empty($this->ref_customer)) {
3746 $datas['refcustomer'] = '<br><b>'.$langs->trans('RefCustomer').':</b> '.$this->ref_customer;
3747 }
3748 if (!$nofetch) {
3749 $langs->load('project');
3750 if (is_null($this->project) || (is_object($this->project) && $this->project->isEmpty())) {
3751 $res = $this->fetchProject();
3752 if ($res > 0 && $this->project instanceof Project) {
3753 $datas['project'] = '<br><b>'.$langs->trans('Project').':</b> '.$this->project->getNomUrl(1, '', 0, 1);
3754 }
3755 }
3756 }
3757 if (!empty($this->total_ht)) {
3758 $datas['amountht'] = '<br><b>'.$langs->trans('AmountHT').':</b> '.price($this->total_ht, 0, $langs, 0, -1, -1, $conf->currency);
3759 }
3760 if (!empty($this->total_tva)) {
3761 $datas['vat'] = '<br><b>'.$langs->trans('VAT').':</b> '.price($this->total_tva, 0, $langs, 0, -1, -1, $conf->currency);
3762 }
3763 if (!empty($this->total_ttc)) {
3764 $datas['amountttc'] = '<br><b>'.$langs->trans('AmountTTC').':</b> '.price($this->total_ttc, 0, $langs, 0, -1, -1, $conf->currency);
3765 }
3766 if (!empty($this->date)) {
3767 $datas['date'] = '<br><b>'.$langs->trans('Date').':</b> '.dol_print_date($this->date, 'day');
3768 }
3769 if (!empty($this->delivery_date)) {
3770 $datas['deliverydate'] = '<br><b>'.$langs->trans('DeliveryDate').':</b> '.dol_print_date($this->delivery_date, 'dayhour');
3771 }
3772 }
3773
3774 return $datas;
3775 }
3776
3788 public function getNomUrl($withpicto = 0, $option = '', $get_params = '', $notooltip = 0, $save_lastsearch_value = -1, $addlinktonotes = -1)
3789 {
3790 global $langs, $conf, $user, $hookmanager;
3791
3792 if (!empty($conf->dol_no_mouse_hover)) {
3793 $notooltip = 1; // Force disable tooltips
3794 }
3795
3796 $result = '';
3797 $params = [
3798 'id' => $this->id,
3799 'objecttype' => $this->element,
3800 'option' => $option,
3801 'nofetch' => 1,
3802 ];
3803 $classfortooltip = 'classfortooltip';
3804 $dataparams = '';
3805 if (getDolGlobalInt('MAIN_ENABLE_AJAX_TOOLTIP')) {
3806 $classfortooltip = 'classforajaxtooltip';
3807 $dataparams = ' data-params="'.dol_escape_htmltag(json_encode($params)).'"';
3808 $label = '';
3809 } else {
3810 $label = implode($this->getTooltipContentArray($params));
3811 }
3812
3813 $url = '';
3814 if ($user->hasRight('propal', 'lire')) {
3815 if ($option == '') {
3816 $url = DOL_URL_ROOT.'/comm/propal/card.php?id='.$this->id.$get_params;
3817 } elseif ($option == 'compta') { // deprecated
3818 $url = DOL_URL_ROOT.'/comm/propal/card.php?id='.$this->id.$get_params;
3819 } elseif ($option == 'expedition') {
3820 $url = DOL_URL_ROOT.'/expedition/propal.php?id='.$this->id.$get_params;
3821 } elseif ($option == 'document') {
3822 $url = DOL_URL_ROOT.'/comm/propal/document.php?id='.$this->id.$get_params;
3823 }
3824
3825 if ($option != 'nolink') {
3826 // Add param to save lastsearch_values or not
3827 $add_save_lastsearch_values = ($save_lastsearch_value == 1 ? 1 : 0);
3828 if ($save_lastsearch_value == -1 && isset($_SERVER["PHP_SELF"]) && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) {
3829 $add_save_lastsearch_values = 1;
3830 }
3831 if ($add_save_lastsearch_values) {
3832 $url .= '&save_lastsearch_values=1';
3833 }
3834 }
3835 }
3836
3837 $linkclose = '';
3838 if (empty($notooltip) && $user->hasRight('propal', 'lire')) {
3839 if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
3840 $label = $langs->trans("Proposal");
3841 $linkclose .= ' alt="'.dolPrintHTMLForAttribute($label).'"';
3842 }
3843 $linkclose .= ($label ? ' title="'.dolPrintHTMLForAttribute($label).'"' : ' title="tocomplete"');
3844 $linkclose .= $dataparams.' class="'.$classfortooltip.'"';
3845 }
3846
3847 $linkstart = '<a href="'.$url.'"';
3848 $linkstart .= $linkclose.'>';
3849 $linkend = '</a>';
3850
3851 $result .= $linkstart;
3852 if ($withpicto) {
3853 $result .= img_object(($notooltip ? '' : $label), ($this->picto ? $this->picto : 'generic'), (($withpicto != 2) ? 'class="paddingright"' : ''), 0, 0, $notooltip ? 0 : 1);
3854 }
3855 if ($withpicto != 2) {
3856 $result .= $this->ref;
3857 }
3858 $result .= $linkend;
3859
3860 if ($addlinktonotes >= 0) {
3861 $txttoshow = '';
3862
3863 if ($addlinktonotes == 0) {
3864 if (!empty($this->note_private) || !empty($this->note_public)) {
3865 $txttoshow = $langs->trans('ViewPrivateNote');
3866 }
3867 } elseif ($addlinktonotes == 1) {
3868 if (!empty($this->note_private)) {
3869 $txttoshow .= ($user->socid > 0 ? '' : dol_string_nohtmltag($this->note_private, 1));
3870 }
3871 } elseif ($addlinktonotes == 2) {
3872 if (!empty($this->note_public)) {
3873 $txttoshow .= dol_string_nohtmltag($this->note_public, 1);
3874 }
3875 } elseif ($addlinktonotes == 3) {
3876 if ($user->socid > 0) {
3877 if (!empty($this->note_public)) {
3878 $txttoshow .= dol_string_nohtmltag($this->note_public, 1);
3879 }
3880 } else {
3881 if (!empty($this->note_public)) {
3882 $txttoshow .= dol_string_nohtmltag($this->note_public, 1);
3883 }
3884 if (!empty($this->note_private)) {
3885 if (!empty($txttoshow)) {
3886 $txttoshow .= '<br><br>';
3887 }
3888 $txttoshow .= dol_string_nohtmltag($this->note_private, 1);
3889 }
3890 }
3891 }
3892
3893 if ($txttoshow) {
3894 $result .= ' <span class="note inline-block">';
3895 $result .= '<a href="'.DOL_URL_ROOT.'/comm/propal/note.php?id='.$this->id.'" class="classfortooltip" title="'.dol_escape_htmltag($txttoshow).'">';
3896 $result .= img_picto('', 'note');
3897 $result .= '</a>';
3898 $result .= '</span>';
3899 }
3900 }
3901
3902 global $action;
3903 $hookmanager->initHooks(array($this->element . 'dao'));
3904 $parameters = array('id' => $this->id, 'getnomurl' => &$result);
3905 $reshook = $hookmanager->executeHooks('getNomUrl', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
3906 if ($reshook > 0) {
3907 $result = $hookmanager->resPrint;
3908 } else {
3909 $result .= $hookmanager->resPrint;
3910 }
3911 return $result;
3912 }
3913
3920 public function getLinesArray($sqlforgedfilters = '')
3921 {
3922 return $this->fetch_lines(0, 0, $sqlforgedfilters);
3923 }
3924
3936 public function generateDocument($modele, $outputlangs, $hidedetails = 0, $hidedesc = 0, $hideref = 0, $moreparams = null)
3937 {
3938 global $conf, $langs;
3939
3940 $langs->load("propale");
3941 $outputlangs->load("products");
3942
3943 if (!dol_strlen($modele)) {
3944 $modele = 'azur';
3945
3946 if ($this->model_pdf) {
3947 $modele = $this->model_pdf;
3948 } elseif (getDolGlobalString('PROPALE_ADDON_PDF')) {
3949 $modele = getDolGlobalString('PROPALE_ADDON_PDF');
3950 }
3951 }
3952
3953 $modelpath = "core/modules/propale/doc/";
3954
3955 return $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams);
3956 }
3957
3966 public static function replaceThirdparty(DoliDB $dbs, $origin_id, $dest_id)
3967 {
3968 $tables = array(
3969 'propal'
3970 );
3971
3972 return CommonObject::commonReplaceThirdparty($dbs, $origin_id, $dest_id, $tables);
3973 }
3974
3983 public static function replaceProduct(DoliDB $db, $origin_id, $dest_id)
3984 {
3985 $tables = array(
3986 'propaldet'
3987 );
3988
3989 return CommonObject::commonReplaceProduct($db, $origin_id, $dest_id, $tables);
3990 }
3991
3999 public function getKanbanView($option = '', $arraydata = null)
4000 {
4001 global $langs;
4002
4003 $selected = (empty($arraydata['selected']) ? 0 : $arraydata['selected']);
4004
4005 $return = '<div class="box-flex-item box-flex-grow-zero">';
4006 $return .= '<div class="info-box info-box-sm">';
4007 $return .= '<div class="info-box-icon bg-infobox-action">';
4008 $return .= img_picto('', $this->picto);
4009 $return .= '</div>';
4010 $return .= '<div class="info-box-content">';
4011 $return .= '<span class="info-box-ref inline-block tdoverflowmax150 valignmiddle">'.(method_exists($this, 'getNomUrl') ? $this->getNomUrl() : $this->ref).'</span>';
4012 if ($selected >= 0) {
4013 $return .= '<input id="cb'.$this->id.'" class="flat checkforselect fright" type="checkbox" name="toselect[]" value="'.$this->id.'"'.($selected ? ' checked="checked"' : '').'>';
4014 }
4015 if (!empty($arraydata['projectlink'])) {
4016 $return .= '<span class="info-box-ref"> | '.$arraydata['projectlink'].'</span>';
4017 }
4018 $return .= '<br>';
4019 if (property_exists($this, 'thirdparty') && is_object($this->thirdparty)) {
4020 $return .= '<div class="info-box-ref tdoverflowmax150">'.$this->thirdparty->getNomUrl(1).'</div>';
4021 }
4022 if (property_exists($this, 'total_ht')) {
4023 $return .= '<span class="info-box-label amount" title="'.$langs->trans("AmountHT").'">'.price($this->total_ht).'</span>';
4024 }
4025 if (!empty($arraydata['authorlink'])) {
4026 $return .= ' &nbsp; <span class="info-box-label">'.$arraydata['authorlink'].'</span>';
4027 }
4028 if (method_exists($this, 'getLibStatut')) {
4029 $return .= '<br><div class="info-box-status">'.$this->getLibStatut(3).'</div>';
4030 }
4031 $return .= '</div>';
4032 $return .= '</div>';
4033 $return .= '</div>';
4034 return $return;
4035 }
4036}
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