dolibarr 23.0.4
propal.class.php
Go to the documentation of this file.
1<?php
2/* Copyright (C) 2002-2004 Rodolphe Quiedeville <rodolphe@quiedeville.org>
3 * Copyright (C) 2004 Eric Seigne <eric.seigne@ryxeo.com>
4 * Copyright (C) 2004-2011 Laurent Destailleur <eldy@users.sourceforge.net>
5 * Copyright (C) 2005 Marc Barilley <marc@ocebo.com>
6 * Copyright (C) 2005-2013 Regis Houssin <regis.houssin@inodbox.com>
7 * Copyright (C) 2006 Andre Cianfarani <acianfa@free.fr>
8 * Copyright (C) 2008 Raphael Bertrand <raphael.bertrand@resultic.fr>
9 * Copyright (C) 2010-2020 Juanjo Menent <jmenent@2byte.es>
10 * Copyright (C) 2010-2022 Philippe Grand <philippe.grand@atoo-net.com>
11 * Copyright (C) 2012-2014 Christophe Battarel <christophe.battarel@altairis.fr>
12 * Copyright (C) 2012 Cedric Salvador <csalvador@gpcsolutions.fr>
13 * Copyright (C) 2013 Florian Henry <florian.henry@open-concept.pro>
14 * Copyright (C) 2014-2015 Marcos García <marcosgdf@gmail.com>
15 * Copyright (C) 2018 Nicolas ZABOURI <info@inovea-conseil.com>
16 * Copyright (C) 2018-2025 Frédéric France <frederic.france@free.fr>
17 * Copyright (C) 2018 Ferran Marcet <fmarcet@2byte.es>
18 * Copyright (C) 2022 ATM Consulting <contact@atm-consulting.fr>
19 * Copyright (C) 2022 OpenDSI <support@open-dsi.fr>
20 * Copyright (C) 2022 Gauthier VERDOL <gauthier.verdol@atm-consulting.fr>
21 * Copyright (C) 2024-2025 MDW <mdeweerd@users.noreply.github.com>
22 * Copyright (C) 2025 William Mead <william@m34d.com>
23 *
24 * This program is free software; you can redistribute it and/or modify
25 * it under the terms of the GNU General Public License as published by
26 * the Free Software Foundation; either version 3 of the License, or
27 * (at your option) any later version.
28 *
29 * This program is distributed in the hope that it will be useful,
30 * but WITHOUT ANY WARRANTY; without even the implied warranty of
31 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
32 * GNU General Public License for more details.
33 *
34 * You should have received a copy of the GNU General Public License
35 * along with this program. If not, see <https://www.gnu.org/licenses/>.
36 */
37
44require_once DOL_DOCUMENT_ROOT.'/core/class/commonobject.class.php';
45require_once DOL_DOCUMENT_ROOT.'/comm/propal/class/propaleligne.class.php';
46require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
47require_once DOL_DOCUMENT_ROOT.'/margin/lib/margins.lib.php';
48require_once DOL_DOCUMENT_ROOT.'/multicurrency/class/multicurrency.class.php';
49require_once DOL_DOCUMENT_ROOT.'/core/class/commonincoterm.class.php';
50require_once DOL_DOCUMENT_ROOT.'/subtotals/class/commonsubtotal.class.php';
51
55class Propal extends CommonObject
56{
57 use CommonIncoterm;
58 use CommonSubtotal;
59
63 public $code = "";
64
68 public $element = 'propal';
69
73 public $table_element = 'propal';
74
78 public $table_element_line = 'propaldet';
79
83 public $fk_element = 'fk_propal';
84
88 public $picto = 'propal';
89
94 public $TRIGGER_PREFIX = 'PROPAL';
95
100 public $restrictiononfksoc = 1;
101
105 protected $table_ref_field = 'ref';
106
111 public $socid;
112
117 public $contactid;
118
125 public $ref_client;
126
131 public $ref_customer;
132
139 public $statut;
140
146 public $status;
147
153 public $datec;
154
160 public $datev;
161
165 public $date_validation;
166
170 public $date_signature;
171
175 public $user_signature;
176
180 public $date;
181
187 public $datep;
188
192 public $delivery_date; // Date expected of shipment (date starting shipment, not the reception that occurs some days after)
193
194
198 public $fin_validite;
199
203 public $user_author_id;
204
209 public $author;
210
216 public $price;
217
223 public $tva;
229 public $total;
230
234 public $cond_reglement_code;
235
239 public $cond_reglement;
240
244 public $cond_reglement_doc;
245
249 public $mode_reglement_code;
250
254 public $mode_reglement;
255
261 public $deposit_percent;
262
267 public $fk_address;
268
273 public $address_type;
274
279 public $address;
280
284 public $availability_id;
285
291 public $fk_availability;
292
296 public $availability_code;
297
301 public $availability;
302
306 public $duree_validite;
307
311 public $demand_reason_id;
312
316 public $demand_reason_code;
317
321 public $demand_reason;
322
326 public $warehouse_id;
327
331 public $lines = array();
332
336 public $line;
337
338 public $labelStatus = array();
339 public $labelStatusShort = array();
340
341
366 // BEGIN MODULEBUILDER PROPERTIES
370 public $fields = array(
371 'rowid' => array('type' => 'integer', 'label' => 'TechnicalID', 'enabled' => 1, 'visible' => -1, 'notnull' => 1, 'position' => 10),
372 'entity' => array('type' => 'integer', 'label' => 'Entity', 'default' => '1', 'enabled' => 1, 'visible' => -2, 'notnull' => 1, 'position' => 15, 'index' => 1),
373 'ref' => array('type' => 'varchar(30)', 'label' => 'Ref', 'enabled' => 1, 'visible' => -1, 'notnull' => 1, 'showoncombobox' => 1, 'position' => 20),
374 'ref_client' => array('type' => 'varchar(255)', 'label' => 'RefCustomer', 'enabled' => 1, 'visible' => -1, 'position' => 22),
375 'ref_ext' => array('type' => 'varchar(255)', 'label' => 'RefExt', 'enabled' => 1, 'visible' => 0, 'position' => 40),
376 'fk_soc' => array('type' => 'integer:Societe:societe/class/societe.class.php', 'label' => 'ThirdParty', 'enabled' => 'isModEnabled("societe")', 'visible' => -1, 'position' => 23),
377 '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),
378 'tms' => array('type' => 'timestamp', 'label' => 'DateModification', 'enabled' => 1, 'visible' => -1, 'notnull' => 1, 'position' => 25),
379 'datec' => array('type' => 'datetime', 'label' => 'DateCreation', 'enabled' => 1, 'visible' => -1, 'position' => 55),
380 'datep' => array('type' => 'date', 'label' => 'Date', 'enabled' => 1, 'visible' => -1, 'position' => 60),
381 'fin_validite' => array('type' => 'datetime', 'label' => 'DateEnd', 'enabled' => 1, 'visible' => -1, 'position' => 65),
382 'date_valid' => array('type' => 'datetime', 'label' => 'DateValidation', 'enabled' => 1, 'visible' => -1, 'position' => 70),
383 'date_cloture' => array('type' => 'datetime', 'label' => 'DateClosing', 'enabled' => 1, 'visible' => -1, 'position' => 75),
384 'fk_user_author' => array('type' => 'integer:User:user/class/user.class.php', 'label' => 'UserAuthor', 'enabled' => 1, 'visible' => -1, 'position' => 80),
385 'fk_user_modif' => array('type' => 'integer:User:user/class/user.class.php', 'label' => 'UserModif', 'enabled' => 1, 'visible' => -2, 'notnull' => -1, 'position' => 85),
386 'fk_user_valid' => array('type' => 'integer:User:user/class/user.class.php', 'label' => 'UserValidation', 'enabled' => 1, 'visible' => -1, 'position' => 90),
387 'fk_user_cloture' => array('type' => 'integer:User:user/class/user.class.php', 'label' => 'UserClosing', 'enabled' => 1, 'visible' => -1, 'position' => 95),
388 'price' => array('type' => 'double', 'label' => 'Price', 'enabled' => 1, 'visible' => -1, 'position' => 105),
389 'total_ht' => array('type' => 'double(24,8)', 'label' => 'TotalHT', 'enabled' => 1, 'visible' => -1, 'position' => 125, 'isameasure' => 1),
390 'total_tva' => array('type' => 'double(24,8)', 'label' => 'VAT', 'enabled' => 1, 'visible' => -1, 'position' => 130, 'isameasure' => 1),
391 'localtax1' => array('type' => 'double(24,8)', 'label' => 'LocalTax1', 'enabled' => 1, 'visible' => -1, 'position' => 135, 'isameasure' => 1),
392 'localtax2' => array('type' => 'double(24,8)', 'label' => 'LocalTax2', 'enabled' => 1, 'visible' => -1, 'position' => 140, 'isameasure' => 1),
393 'total_ttc' => array('type' => 'double(24,8)', 'label' => 'TotalTTC', 'enabled' => 1, 'visible' => -1, 'position' => 145, 'isameasure' => 1),
394 'fk_account' => array('type' => 'integer', 'label' => 'BankAccount', 'enabled' => 'isModEnabled("bank")', 'visible' => -1, 'position' => 150),
395 'fk_currency' => array('type' => 'varchar(3)', 'label' => 'Currency', 'enabled' => 1, 'visible' => -1, 'position' => 155),
396 'fk_cond_reglement' => array('type' => 'integer', 'label' => 'PaymentTerm', 'enabled' => 1, 'visible' => -1, 'position' => 160),
397 'deposit_percent' => array('type' => 'varchar(63)', 'label' => 'DepositPercent', 'enabled' => 1, 'visible' => -1, 'position' => 161),
398 'fk_mode_reglement' => array('type' => 'integer', 'label' => 'PaymentMode', 'enabled' => 1, 'visible' => -1, 'position' => 165),
399 'note_private' => array('type' => 'html', 'label' => 'NotePrivate', 'enabled' => 1, 'visible' => 0, 'position' => 170),
400 'note_public' => array('type' => 'html', 'label' => 'NotePublic', 'enabled' => 1, 'visible' => 0, 'position' => 175),
401 'model_pdf' => array('type' => 'varchar(255)', 'label' => 'PDFTemplate', 'enabled' => 1, 'visible' => 0, 'position' => 180),
402 'date_livraison' => array('type' => 'date', 'label' => 'DateDeliveryPlanned', 'enabled' => 1, 'visible' => -1, 'position' => 185),
403 'fk_shipping_method' => array('type' => 'integer', 'label' => 'ShippingMethod', 'enabled' => 1, 'visible' => -1, 'position' => 190),
404 'fk_warehouse' => array('type' => 'integer:Entrepot:product/stock/class/entrepot.class.php', 'label' => 'Fk warehouse', 'enabled' => 'isModEnabled("stock")', 'visible' => -1, 'position' => 191),
405 'fk_availability' => array('type' => 'integer', 'label' => 'Availability', 'enabled' => 1, 'visible' => -1, 'position' => 195),
406 'fk_delivery_address' => array('type' => 'integer', 'label' => 'DeliveryAddress', 'enabled' => 1, 'visible' => 0, 'position' => 200), // deprecated
407 'fk_input_reason' => array('type' => 'integer', 'label' => 'InputReason', 'enabled' => 1, 'visible' => -1, 'position' => 205),
408 'extraparams' => array('type' => 'varchar(255)', 'label' => 'Extraparams', 'enabled' => 1, 'visible' => -1, 'position' => 215),
409 'fk_incoterms' => array('type' => 'integer', 'label' => 'IncotermCode', 'enabled' => 'isModEnabled("incoterm")', 'visible' => -1, 'position' => 220),
410 'location_incoterms' => array('type' => 'varchar(255)', 'label' => 'IncotermLabel', 'enabled' => 'isModEnabled("incoterm")', 'visible' => -1, 'position' => 225),
411 'fk_multicurrency' => array('type' => 'integer', 'label' => 'MulticurrencyID', 'enabled' => 1, 'visible' => -1, 'position' => 230),
412 'multicurrency_code' => array('type' => 'varchar(255)', 'label' => 'MulticurrencyCurrency', 'enabled' => 'isModEnabled("multicurrency")', 'visible' => -1, 'position' => 235),
413 'multicurrency_tx' => array('type' => 'double(24,8)', 'label' => 'MulticurrencyRate', 'enabled' => 'isModEnabled("multicurrency")', 'visible' => -1, 'position' => 240, 'isameasure' => 1),
414 'multicurrency_total_ht' => array('type' => 'double(24,8)', 'label' => 'MulticurrencyAmountHT', 'enabled' => 'isModEnabled("multicurrency")', 'visible' => -1, 'position' => 245, 'isameasure' => 1),
415 'multicurrency_total_tva' => array('type' => 'double(24,8)', 'label' => 'MulticurrencyAmountVAT', 'enabled' => 'isModEnabled("multicurrency")', 'visible' => -1, 'position' => 250, 'isameasure' => 1),
416 'multicurrency_total_ttc' => array('type' => 'double(24,8)', 'label' => 'MulticurrencyAmountTTC', 'enabled' => 'isModEnabled("multicurrency")', 'visible' => -1, 'position' => 255, 'isameasure' => 1),
417 'last_main_doc' => array('type' => 'varchar(255)', 'label' => 'LastMainDoc', 'enabled' => 1, 'visible' => -1, 'position' => 260),
418 'fk_statut' => array('type' => 'smallint(6)', 'label' => 'Status', 'enabled' => 1, 'visible' => -1, 'notnull' => 1, 'position' => 500),
419 'import_key' => array('type' => 'varchar(14)', 'label' => 'ImportId', 'enabled' => 1, 'visible' => -2, 'position' => 900),
420 );
421 // END MODULEBUILDER PROPERTIES
422
426 const STATUS_CANCELED = -1;
430 const STATUS_DRAFT = 0;
438 const STATUS_SIGNED = 2;
446 const STATUS_BILLED = 4; // Todo rename into STATUS_CLOSE ?
447
448
456 public function __construct($db, $socid = 0, $propalid = 0)
457 {
458 $this->db = $db;
459
460 $this->ismultientitymanaged = 1;
461 $this->socid = $socid;
462 $this->id = $propalid;
463
464 $this->duree_validite = getDolGlobalInt('PROPALE_VALIDITY_DURATION');
465
466 $this->fields['ref_ext']['visible'] = getDolGlobalInt('MAIN_LIST_SHOW_REF_EXT');
467 }
468
469
470 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
482 public function add_product($idproduct, $qty, $remise_percent = 0)
483 {
484 // phpcs:enable
485 global $mysoc;
486
487 if (!$qty) {
488 $qty = 1;
489 }
490
491 dol_syslog(get_class($this)."::add_product $idproduct, $qty, $remise_percent");
492 if ($idproduct > 0) {
493 $prod = new Product($this->db);
494 $prod->fetch($idproduct);
495
496 $productdesc = $prod->description;
497
498 $tva_tx = (string) get_default_tva($mysoc, $this->thirdparty, $prod->id);
499 $tva_npr = get_default_npr($mysoc, $this->thirdparty, $prod->id);
500 if (empty($tva_tx)) {
501 $tva_npr = 0;
502 }
503 $vat_src_code = ''; // May be defined into tva_tx
504
505 $localtax1_tx = get_localtax($tva_tx, 1, $mysoc, $this->thirdparty, $tva_npr);
506 $localtax2_tx = get_localtax($tva_tx, 2, $mysoc, $this->thirdparty, $tva_npr);
507
508 // multiprices
509 if (getDolGlobalString('PRODUIT_MULTIPRICES') && $this->thirdparty->price_level) {
510 $price = $prod->multiprices[$this->thirdparty->price_level];
511 } else {
512 $price = $prod->price;
513 }
514
515 $line = new PropaleLigne($this->db);
516
517 $line->fk_product = $idproduct;
518 $line->desc = $productdesc;
519 $line->qty = $qty;
520 $line->subprice = $price;
521 $line->remise_percent = $remise_percent;
522 $line->vat_src_code = $vat_src_code;
523 $line->tva_tx = $tva_tx;
524 $line->fk_unit = $prod->fk_unit;
525 if ($tva_npr) {
526 $line->info_bits = 1;
527 }
528
529 $this->lines[] = $line;
530 }
531
532 return 1;
533 }
534
535 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
542 public function insert_discount($idremise)
543 {
544 // phpcs:enable
545 global $langs;
546
547 include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
548 include_once DOL_DOCUMENT_ROOT.'/core/class/discount.class.php';
549
550 $this->db->begin();
551
552 $remise = new DiscountAbsolute($this->db);
553 $result = $remise->fetch($idremise);
554
555 if ($result > 0) {
556 if ($remise->fk_facture) { // Protection against multiple submission
557 $this->error = $langs->trans("ErrorDiscountAlreadyUsed");
558 $this->db->rollback();
559 return -5;
560 }
561
562 $line = new PropaleLigne($this->db);
563
564 $line->context = $this->context;
565
566 $line->fk_propal = $this->id;
567 $line->fk_remise_except = $remise->id;
568 $line->desc = $remise->description; // Description ligne
569 $line->vat_src_code = $remise->vat_src_code;
570 $line->tva_tx = $remise->tva_tx;
571 $line->subprice = -(float) $remise->amount_ht;
572 $line->fk_product = 0; // Id produit predefined
573 $line->qty = 1;
574 $line->remise_percent = 0;
575 $line->rang = -1;
576 $line->info_bits = 2;
577
578 $line->total_ht = -(float) $remise->amount_ht;
579 $line->total_tva = -(float) $remise->amount_tva;
580 $line->total_ttc = -(float) $remise->amount_ttc;
581
582 $result = $line->insert();
583 if ($result > 0) {
584 $result = $this->update_price(1);
585 if ($result > 0) {
586 $this->db->commit();
587 return 1;
588 } else {
589 $this->db->rollback();
590 return -1;
591 }
592 } else {
593 $this->setErrorsFromObject($line);
594 $this->db->rollback();
595 return -2;
596 }
597 } else {
598 $this->db->rollback();
599 return -2;
600 }
601 }
602
640 public function addline(
641 $desc,
642 $pu_ht,
643 $qty,
644 $txtva,
645 $txlocaltax1 = 0.0,
646 $txlocaltax2 = 0.0,
647 $fk_product = 0,
648 $remise_percent = 0.0,
649 $price_base_type = 'HT',
650 $pu_ttc = 0.0,
651 $info_bits = 0,
652 $type = 0,
653 $rang = -1,
654 $special_code = 0,
655 $fk_parent_line = 0,
656 $fk_fournprice = 0,
657 $pa_ht = 0,
658 $label = '',
659 $date_start = '',
660 $date_end = '',
661 $array_options = array(),
662 $fk_unit = null,
663 $origin = '',
664 $origin_id = 0,
665 $pu_ht_devise = 0,
666 $fk_remise_except = 0,
667 $noupdateafterinsertline = 0
668 ) {
669 global $mysoc, $langs;
670
671 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);
672
673 if ($this->status == self::STATUS_DRAFT) {
674 include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
675
676 // Clean parameters
677 if (empty($remise_percent)) {
678 $remise_percent = 0;
679 }
680 if (empty($qty)) {
681 $qty = 0;
682 }
683 if (empty($info_bits)) {
684 $info_bits = 0;
685 }
686 if (empty($rang)) {
687 $rang = 0;
688 }
689 if (empty($fk_parent_line) || $fk_parent_line < 0) {
690 $fk_parent_line = 0;
691 }
692
693 $remise_percent = price2num($remise_percent);
694 $qty = (float) price2num($qty, 'MS');
695 $pu_ht = price2num($pu_ht);
696 $pu_ht_devise = price2num($pu_ht_devise);
697 $pu_ttc = price2num($pu_ttc);
698 if (!preg_match('/\‍((.*)\‍)/', (string) $txtva)) {
699 $txtva = price2num($txtva); // $txtva can have format '5,1' or '5.1' or '5.1(XXX)', we must clean only if '5,1'
700 }
701 $txlocaltax1 = price2num($txlocaltax1);
702 $txlocaltax2 = price2num($txlocaltax2);
703 $pa_ht = price2num($pa_ht); // do not convert to float here, it breaks the functioning of $pa_ht is empty string
704 if ($price_base_type == 'HT') {
705 $pu = $pu_ht;
706 } else {
707 $pu = $pu_ttc;
708 }
709
710 // Check parameters
711 if ($type < 0) {
712 return -1;
713 }
714
715 if ($date_start && $date_end && $date_start > $date_end) {
716 $langs->load("errors");
717 $this->error = $langs->trans('ErrorStartDateGreaterEnd');
718 return -1;
719 }
720
721 $this->db->begin();
722
723 $product_type = $type;
724 if (!empty($fk_product) && $fk_product > 0) {
725 $product = new Product($this->db);
726 $result = $product->fetch($fk_product);
727 $product_type = $product->type;
728
729 if ($product->isStockManaged() && $product->stock_reel < $qty && getDolGlobalString('STOCK_MUST_BE_ENOUGH_FOR_PROPOSAL')) {
730 $langs->load("errors");
731 $this->error = $langs->trans('ErrorStockIsNotEnoughToAddProductOnProposal', $product->ref);
732 $this->db->rollback();
733 return -3;
734 }
735 }
736
737 // Calcul du total TTC et de la TVA pour la ligne a partir de
738 // qty, pu, remise_percent et txtva
739 // TRES IMPORTANT: C'est au moment de l'insertion ligne qu'on doit stocker
740 // la part ht, tva et ttc, et ce au niveau de la ligne qui a son propre taux tva.
741
742 $localtaxes_type = getLocalTaxesFromRate($txtva, 0, $this->thirdparty, $mysoc);
743
744 if (getDolGlobalString('PRODUCT_USE_CUSTOMER_PACKAGING')) {
745 $tmpproduct = new Product($this->db);
746 $result = $tmpproduct->fetch($fk_product);
747 $newqty = $this->roundQtyToPackaging($qty, $tmpproduct->packaging);
748 if ($newqty != $qty) {
749 $qty = $newqty;
750 setEventMessages($langs->trans('QtyRecalculatedWithPackaging'), null, 'warnings');
751 }
752 }
753
754 // Clean vat code
755 $reg = array();
756 $vat_src_code = '';
757 if (preg_match('/\‍((.*)\‍)/', $txtva, $reg)) {
758 $vat_src_code = $reg[1];
759 $txtva = preg_replace('/\s*\‍(.*\‍)/', '', $txtva); // Remove code into vatrate.
760 }
761
762 $tabprice = calcul_price_total($qty, (float) $pu, (float) $remise_percent, $txtva, (float) $txlocaltax1, (float) $txlocaltax2, 0, $price_base_type, $info_bits, $product_type, $mysoc, $localtaxes_type, 100, $this->multicurrency_tx, (float) $pu_ht_devise);
763
764 $total_ht = $tabprice[0];
765 $total_tva = $tabprice[1];
766 $total_ttc = $tabprice[2];
767 $total_localtax1 = $tabprice[9];
768 $total_localtax2 = $tabprice[10];
769 $pu_ht = $tabprice[3];
770 $pu_tva = $tabprice[4];
771 $pu_ttc = $tabprice[5];
772
773 // MultiCurrency
774 $multicurrency_total_ht = $tabprice[16];
775 $multicurrency_total_tva = $tabprice[17];
776 $multicurrency_total_ttc = $tabprice[18];
777 $pu_ht_devise = $tabprice[19];
778
779 // Rang to use
780 $ranktouse = $rang;
781 if (empty($ranktouse) || $ranktouse == -1) {
782 $rangmax = $this->line_max($fk_parent_line);
783 $ranktouse = $rangmax + 1;
784 }
785
786 // Insert line
787 $this->line = new PropaleLigne($this->db);
788
789 $this->line->context = $this->context;
790
791 $this->line->fk_propal = $this->id;
792 $this->line->label = $label;
793 $this->line->desc = $desc;
794 $this->line->qty = (float) $qty;
795
796 $this->line->vat_src_code = $vat_src_code;
797 $this->line->tva_tx = $txtva;
798 $this->line->localtax1_tx = ($total_localtax1 ? $localtaxes_type[1] : 0);
799 $this->line->localtax2_tx = ($total_localtax2 ? $localtaxes_type[3] : 0);
800 $this->line->localtax1_type = empty($localtaxes_type[0]) ? '' : $localtaxes_type[0];
801 $this->line->localtax2_type = empty($localtaxes_type[2]) ? '' : $localtaxes_type[2];
802 $this->line->fk_product = $fk_product;
803 $this->line->product_type = $type;
804 $this->line->fk_remise_except = $fk_remise_except;
805 $this->line->remise_percent = $remise_percent;
806 $this->line->subprice = (float) $pu_ht;
807 $this->line->rang = $ranktouse;
808 $this->line->info_bits = $info_bits;
809 $this->line->total_ht = (float) $total_ht;
810 $this->line->total_tva = (float) $total_tva;
811 $this->line->total_localtax1 = (float) $total_localtax1;
812 $this->line->total_localtax2 = (float) $total_localtax2;
813 $this->line->total_ttc = (float) $total_ttc;
814 $this->line->special_code = $special_code;
815 $this->line->fk_parent_line = $fk_parent_line;
816 $this->line->fk_unit = $fk_unit;
817
818 $this->line->date_start = $date_start;
819 $this->line->date_end = $date_end;
820
821 $this->line->fk_fournprice = $fk_fournprice;
822 $this->line->pa_ht = $pa_ht;
823
824 $this->line->origin_id = $origin_id;
825 $this->line->origin = $origin;
826
827 // Multicurrency
828 $this->line->fk_multicurrency = $this->fk_multicurrency;
829 $this->line->multicurrency_code = $this->multicurrency_code;
830 $this->line->multicurrency_subprice = (float) $pu_ht_devise;
831 $this->line->multicurrency_total_ht = (float) $multicurrency_total_ht;
832 $this->line->multicurrency_total_tva = (float) $multicurrency_total_tva;
833 $this->line->multicurrency_total_ttc = (float) $multicurrency_total_ttc;
834
835 // Mise en option de la ligne
836 if (empty($qty) && empty($special_code)) {
837 $this->line->special_code = 3;
838 }
839
840 if (is_array($array_options) && count($array_options) > 0) {
841 $this->line->array_options = $array_options;
842 }
843
844 $result = $this->line->insert();
845 if ($result > 0) {
846 if (!isset($this->context['createfromclone'])) {
847 if (!empty($fk_parent_line)) {
848 // Always reorder if child line
849 $this->line_order(true, 'DESC');
850 } elseif ($ranktouse > 0 && $ranktouse <= count($this->lines)) {
851 // Update all rank of all other lines starting from the same $ranktouse
852 $linecount = count($this->lines);
853 for ($ii = $ranktouse; $ii <= $linecount; $ii++) {
854 $this->updateRangOfLine($this->lines[$ii - 1]->id, $ii + 1);
855 }
856 }
857
858 $this->lines[] = $this->line;
859 } else {
860 foreach ($this->lines as $line) {
861 if ($line->id == $origin_id) {
862 $this->line->extraparams = $line->extraparams;
863 $this->line->setExtraParameters();
864 }
865 }
866 }
867
868 // Update denormalized fields at the order level
869 if (empty($noupdateafterinsertline)) {
870 $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.
871 }
872
873 if ($result > 0) {
874 $this->db->commit();
875 return $this->line->id;
876 } else {
877 $this->error = $this->db->error();
878 $this->db->rollback();
879 return -1;
880 }
881 } else {
882 $this->setErrorsFromObject($this->line);
883 $this->db->rollback();
884 return -2;
885 }
886 } else {
887 dol_syslog(get_class($this)."::addline status of proposal must be Draft to allow use of ->addline()", LOG_ERR);
888 return -3;
889 }
890 }
891
892
922 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)
923 {
924 global $mysoc, $langs;
925
926 if (!$this->isLineOfObject($rowid)) {
927 $this->error = 'ErrorLineIDDoesNotMatchWithObjectID';
928 return -1;
929 }
930
931 dol_syslog(get_class($this)."::updateLine rowid=$rowid, pu=$pu, qty=$qty, remise_percent=$remise_percent,
932 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");
933 include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
934
935 // Clean parameters
936 $remise_percent = price2num($remise_percent);
937 $qty = (float) price2num($qty);
938 $pu = price2num($pu);
939 $pu_ht_devise = price2num($pu_ht_devise);
940 if (!preg_match('/\‍((.*)\‍)/', (string) $txtva)) {
941 $txtva = price2num($txtva); // $txtva can have format '5.0(XXX)' or '5'
942 }
943 $txlocaltax1 = price2num($txlocaltax1);
944 $txlocaltax2 = price2num($txlocaltax2);
945 $pa_ht = price2num($pa_ht); // do not convert to float here, it breaks the functioning of $pa_ht_isemptystring
946 if (empty($qty) && empty($special_code)) {
947 $special_code = 3; // Set option tag
948 }
949 if (!empty($qty) && $special_code == 3) {
950 $special_code = 0; // Remove option tag
951 }
952 if (empty($type)) {
953 $type = 0;
954 }
955
956 if ($date_start && $date_end && $date_start > $date_end) {
957 $langs->load("errors");
958 $this->error = $langs->trans('ErrorStartDateGreaterEnd');
959 return -1;
960 }
961
962 if ($this->status == self::STATUS_DRAFT) {
963 $this->db->begin();
964
965 // Calcul du total TTC et de la TVA pour la ligne a partir de
966 // qty, pu, remise_percent et txtva
967 // TRES IMPORTANT: C'est au moment de l'insertion ligne qu'on doit stocker
968 // la part ht, tva et ttc, et ce au niveau de la ligne qui a son propre taux tva.
969
970 $localtaxes_type = getLocalTaxesFromRate($txtva, 0, $this->thirdparty, $mysoc);
971
972 // Clean vat code
973 $reg = array();
974 $vat_src_code = '';
975 if (preg_match('/\‍((.*)\‍)/', $txtva, $reg)) {
976 $vat_src_code = $reg[1];
977 $txtva = preg_replace('/\s*\‍(.*\‍)/', '', $txtva); // Remove code into vatrate.
978 }
979
980 // TODO Implement if (getDolGlobalInt('MAIN_UNIT_PRICE_WITH_TAX_IS_FOR_ALL_TAXES')) ?
981
982 // Round the quantity to the packaging before computing the amounts of the line (and checking the stock),
983 // else the line is saved with the rounded quantity but with the amounts of the quantity before rounding
984 if (getDolGlobalString('PRODUCT_USE_CUSTOMER_PACKAGING')) {
985 $tmpline = new PropaleLigne($this->db);
986 if ($tmpline->fetch($rowid) > 0) {
987 $newqty = $this->roundQtyToPackaging($qty, $tmpline->packaging);
988 if ($newqty != $qty) {
989 $qty = $newqty;
990 setEventMessages($langs->trans('QtyRecalculatedWithPackaging'), null, 'warnings');
991 }
992 }
993 }
994
995 $tabprice = calcul_price_total($qty, (float) $pu, (float) $remise_percent, $txtva, (float) $txlocaltax1, (float) $txlocaltax2, 0, $price_base_type, $info_bits, $type, $mysoc, $localtaxes_type, 100, $this->multicurrency_tx, (float) $pu_ht_devise);
996 $total_ht = $tabprice[0];
997 $total_tva = $tabprice[1];
998 $total_ttc = $tabprice[2];
999 $total_localtax1 = $tabprice[9];
1000 $total_localtax2 = $tabprice[10];
1001 $pu_ht = $tabprice[3];
1002 $pu_tva = $tabprice[4];
1003 $pu_ttc = $tabprice[5];
1004
1005 // MultiCurrency
1006 $multicurrency_total_ht = $tabprice[16];
1007 $multicurrency_total_tva = $tabprice[17];
1008 $multicurrency_total_ttc = $tabprice[18];
1009 $pu_ht_devise = $tabprice[19];
1010
1011 // Fetch current line from the database and then clone the object and set it in $oldline property
1012 $line = new PropaleLigne($this->db);
1013 $line->fetch($rowid);
1014
1015 $staticline = clone $line;
1016
1017 $line->oldline = $staticline;
1018 $this->line = $line;
1019 $this->line->context = $this->context;
1020 $this->line->rang = $rang;
1021
1022 // Reorder if fk_parent_line change
1023 if (!empty($fk_parent_line) && !empty($staticline->fk_parent_line) && $fk_parent_line != $staticline->fk_parent_line) {
1024 $rangmax = $this->line_max($fk_parent_line);
1025 $this->line->rang = $rangmax + 1;
1026 }
1027
1028
1029 $this->line->id = $rowid;
1030 $this->line->label = $label;
1031 $this->line->desc = $desc;
1032 $this->line->qty = $qty;
1033 $this->line->product_type = $type;
1034 $this->line->vat_src_code = $vat_src_code;
1035 $this->line->tva_tx = $txtva;
1036 $this->line->localtax1_tx = $txlocaltax1;
1037 $this->line->localtax2_tx = $txlocaltax2;
1038 $this->line->localtax1_type = empty($localtaxes_type[0]) ? '' : $localtaxes_type[0];
1039 $this->line->localtax2_type = empty($localtaxes_type[2]) ? '' : $localtaxes_type[2];
1040 $this->line->remise_percent = $remise_percent;
1041 $this->line->subprice = (float) $pu_ht;
1042 $this->line->info_bits = $info_bits;
1043
1044 $this->line->total_ht = (float) $total_ht;
1045 $this->line->total_tva = (float) $total_tva;
1046 $this->line->total_localtax1 = (float) $total_localtax1;
1047 $this->line->total_localtax2 = (float) $total_localtax2;
1048 $this->line->total_ttc = (float) $total_ttc;
1049 $this->line->special_code = $special_code;
1050 $this->line->fk_parent_line = $fk_parent_line;
1051 $this->line->skip_update_total = $skip_update_total;
1052 $this->line->fk_unit = $fk_unit;
1053
1054 $this->line->fk_fournprice = $fk_fournprice;
1055 $this->line->pa_ht = $pa_ht;
1056
1057 $this->line->date_start = $date_start;
1058 $this->line->date_end = $date_end;
1059
1060 if (is_array($array_options) && count($array_options) > 0) {
1061 // We replace values in this->line->array_options only for entries defined into $array_options
1062 foreach ($array_options as $key => $value) {
1063 $this->line->array_options[$key] = $array_options[$key];
1064 }
1065 }
1066
1067 // Multicurrency
1068 $this->line->multicurrency_subprice = (float) $pu_ht_devise;
1069 $this->line->multicurrency_total_ht = (float) $multicurrency_total_ht;
1070 $this->line->multicurrency_total_tva = (float) $multicurrency_total_tva;
1071 $this->line->multicurrency_total_ttc = (float) $multicurrency_total_ttc;
1072
1073 $result = $this->line->update($notrigger);
1074 if ($result > 0) {
1075 // Reorder if child line
1076 if (!empty($fk_parent_line)) {
1077 $this->line_order(true, 'DESC');
1078 }
1079
1080 $this->update_price(1, 'auto');
1081
1082 // $this is Propal
1083 // $this->fk_propal = $this->id;
1084 // $this->rowid = $rowid;
1085
1086 $this->db->commit();
1087 return $result;
1088 } else {
1089 $this->setErrorsFromObject($this->line);
1090 $this->db->rollback();
1091 return -1;
1092 }
1093 } else {
1094 dol_syslog(get_class($this)."::updateline Erreur -2 Propal en mode incompatible pour cette action");
1095 return -2;
1096 }
1097 }
1098
1099
1107 public function deleteLine($lineid, $id = 0)
1108 {
1109 global $user;
1110
1111 if ($this->status == self::STATUS_DRAFT) {
1112 $this->db->begin();
1113
1114 $line = new PropaleLigne($this->db);
1115
1116 $line->context = $this->context;
1117
1118 // Load data
1119 $line->fetch($lineid);
1120
1121 if ($id <= 0) {
1122 $id = $this->id;
1123 }
1124 if ($id > 0 && (int) $line->fk_propal !== (int) $id) {
1125 $this->db->rollback();
1126 $this->error = 'ErrorLineIDDoesNotMatchWithObjectID';
1127 return -1;
1128 }
1129
1130 // Memorize previous line for triggers
1131 $staticline = clone $line;
1132 $line->oldline = $staticline;
1133
1134 if ($line->delete($user) > 0) {
1135 $this->update_price(1);
1136
1137 $this->db->commit();
1138 return 1;
1139 } else {
1140 $this->setErrorsFromObject($line);
1141 $this->db->rollback();
1142 return -1;
1143 }
1144 } else {
1145 $this->error = 'ErrorDeleteLineNotAllowedByObjectStatus';
1146 return -2;
1147 }
1148 }
1149
1150
1159 public function create($user, $notrigger = 0)
1160 {
1161 global $mysoc;
1162
1163 $error = 0;
1164
1165 $now = dol_now();
1166
1167 // Clean parameters
1168 if (empty($this->date)) {
1169 $this->date = $this->datep;
1170 }
1171 $this->fin_validite = $this->date + ($this->duree_validite * 24 * 3600);
1172 if (empty($this->availability_id)) {
1173 $this->availability_id = 0;
1174 }
1175 if (empty($this->demand_reason_id)) {
1176 $this->demand_reason_id = 0;
1177 }
1178
1179 // Multicurrency (test on $this->multicurrency_tx because we should take the default rate only if not using origin rate)
1180 if (!empty($this->multicurrency_code) && empty($this->multicurrency_tx)) {
1181 list($this->fk_multicurrency, $this->multicurrency_tx) = MultiCurrency::getIdAndTxFromCode($this->db, $this->multicurrency_code, $this->date);
1182 } else {
1183 $this->fk_multicurrency = MultiCurrency::getIdFromCode($this->db, $this->multicurrency_code);
1184 }
1185 if (empty($this->fk_multicurrency)) {
1186 $this->multicurrency_code = getDolCurrency();
1187 $this->fk_multicurrency = 0;
1188 $this->multicurrency_tx = 1;
1189 }
1190 // setEntity will set entity with the right value if empty or change it for the right value if multicompany module is active
1191 $this->entity = setEntity($this);
1192
1193 // Set tmp vars
1194 $delivery_date = $this->delivery_date;
1195
1196 dol_syslog(get_class($this)."::create");
1197
1198 // Check parameters
1199 $result = $this->fetch_thirdparty();
1200 if ($result < 0) {
1201 $this->error = "Failed to fetch company";
1202 dol_syslog(get_class($this)."::create ".$this->error, LOG_ERR);
1203 return -3;
1204 }
1205
1206 // Check parameters
1207 if (!empty($this->ref)) { // We check that ref is not already used
1208 $result = self::isExistingObject($this->element, 0, $this->ref); // Check ref is not yet used
1209 if ($result > 0) {
1210 $this->error = 'ErrorRefAlreadyExists';
1211 dol_syslog(get_class($this)."::create ".$this->error, LOG_WARNING);
1212 $this->db->rollback();
1213 return -1;
1214 }
1215 }
1216
1217 if (empty($this->date)) {
1218 $this->error = "Date of proposal is required";
1219 dol_syslog(get_class($this)."::create ".$this->error, LOG_ERR);
1220 return -4;
1221 }
1222
1223
1224 $this->db->begin();
1225
1226 // Insert into database
1227 $sql = "INSERT INTO ".MAIN_DB_PREFIX.$this->table_element." (";
1228 $sql .= "fk_soc";
1229 $sql .= ", price";
1230 $sql .= ", total_tva";
1231 $sql .= ", total_ttc";
1232 $sql .= ", datep";
1233 $sql .= ", datec";
1234 $sql .= ", ref";
1235 $sql .= ", fk_user_author";
1236 $sql .= ", note_private";
1237 $sql .= ", note_public";
1238 $sql .= ", model_pdf";
1239 $sql .= ", fin_validite";
1240 $sql .= ", fk_cond_reglement";
1241 $sql .= ", deposit_percent";
1242 $sql .= ", fk_mode_reglement";
1243 $sql .= ", fk_account";
1244 $sql .= ", ref_client";
1245 $sql .= ", ref_ext";
1246 $sql .= ", date_livraison";
1247 $sql .= ", fk_shipping_method";
1248 $sql .= ", fk_warehouse";
1249 $sql .= ", fk_availability";
1250 $sql .= ", fk_input_reason";
1251 $sql .= ", fk_projet";
1252 $sql .= ", fk_incoterms";
1253 $sql .= ", location_incoterms";
1254 $sql .= ", entity";
1255 $sql .= ", fk_multicurrency";
1256 $sql .= ", multicurrency_code";
1257 $sql .= ", multicurrency_tx";
1258 $sql .= ") ";
1259 $sql .= " VALUES (";
1260 $sql .= $this->socid;
1261 $sql .= ", 0";
1262 $sql .= ", 0";
1263 $sql .= ", 0";
1264 $sql .= ", '".$this->db->idate($this->date)."'";
1265 $sql .= ", '".$this->db->idate($now)."'";
1266 $sql .= ", '(PROV)'";
1267 $sql .= ", ".($user->id > 0 ? ((int) $user->id) : "NULL");
1268 $sql .= ", '".$this->db->escape($this->note_private)."'";
1269 $sql .= ", '".$this->db->escape($this->note_public)."'";
1270 $sql .= ", '".$this->db->escape($this->model_pdf)."'";
1271 $sql .= ", ".($this->fin_validite != '' ? "'".$this->db->idate($this->fin_validite)."'" : "NULL");
1272 $sql .= ", ".($this->cond_reglement_id > 0 ? ((int) $this->cond_reglement_id) : 'NULL');
1273 $sql .= ", ".(!empty($this->deposit_percent) ? "'".$this->db->escape($this->deposit_percent)."'" : 'NULL');
1274 $sql .= ", ".($this->mode_reglement_id > 0 ? ((int) $this->mode_reglement_id) : 'NULL');
1275 $sql .= ", ".($this->fk_account > 0 ? ((int) $this->fk_account) : 'NULL');
1276 $sql .= ", '".$this->db->escape((string) $this->ref_client)."'";
1277 $sql .= ", '".$this->db->escape((string) $this->ref_ext)."'";
1278 $sql .= ", ".(!isDolTms($delivery_date) ? "NULL" : "'".$this->db->idate($delivery_date)."'");
1279 $sql .= ", ".($this->shipping_method_id > 0 ? $this->shipping_method_id : 'NULL');
1280 $sql .= ", ".($this->warehouse_id > 0 ? $this->warehouse_id : 'NULL');
1281 $sql .= ", ".((int) $this->availability_id);
1282 $sql .= ", ".((int) $this->demand_reason_id);
1283 $sql .= ", ".($this->fk_project ? $this->fk_project : "null");
1284 $sql .= ", ".(int) $this->fk_incoterms;
1285 $sql .= ", '".$this->db->escape($this->location_incoterms)."'";
1286 $sql .= ", ".(int) $this->entity;
1287 $sql .= ", ".(int) $this->fk_multicurrency;
1288 $sql .= ", '".$this->db->escape($this->multicurrency_code)."'";
1289 $sql .= ", ".(float) $this->multicurrency_tx;
1290 $sql .= ")";
1291
1292 dol_syslog(get_class($this)."::create", LOG_DEBUG);
1293 $resql = $this->db->query($sql);
1294 if ($resql) {
1295 $this->id = $this->db->last_insert_id(MAIN_DB_PREFIX.$this->table_element);
1296
1297 if ($this->id) {
1298 $this->ref = '(PROV'.$this->id.')';
1299 $sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element." SET ref='".$this->db->escape($this->ref)."' WHERE rowid=".((int) $this->id);
1300
1301 dol_syslog(get_class($this)."::create", LOG_DEBUG);
1302 $resql = $this->db->query($sql);
1303 if (!$resql) {
1304 $error++;
1305 }
1306
1307 if (!empty($this->linkedObjectsIds) && empty($this->linked_objects)) { // To use new linkedObjectsIds instead of old linked_objects
1308 $this->linked_objects = $this->linkedObjectsIds; // TODO Replace linked_objects with linkedObjectsIds
1309 }
1310
1311 // Add object linked
1312 if (!$error && !empty($this->linked_objects) && is_array($this->linked_objects)) {
1313 foreach ($this->linked_objects as $origin => $tmp_origin_id) {
1314 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, ...))
1315 foreach ($tmp_origin_id as $origin_id) {
1316 $ret = $this->add_object_linked($origin, $origin_id);
1317 if (!$ret) {
1318 $this->error = $this->db->lasterror();
1319 $error++;
1320 }
1321 }
1322 } else { // Old behaviour, if linked_object has only one link per type, so is something like array('contract'=>id1))
1323 $origin_id = $tmp_origin_id;
1324 $ret = $this->add_object_linked($origin, $origin_id);
1325 if (!$ret) {
1326 $this->error = $this->db->lasterror();
1327 $error++;
1328 }
1329 }
1330 }
1331 }
1332
1333 /*
1334 * Insertion du detail des produits dans la base
1335 * Insert products detail in database
1336 */
1337 if (!$error) {
1338 $fk_parent_line = 0;
1339 $num = count($this->lines);
1340
1341 for ($i = 0; $i < $num; $i++) {
1342 if (!is_object($this->lines[$i])) { // If this->lines is not array of objects, coming from REST API
1343 // Convert into object this->lines[$i].
1344 $line = (object) $this->lines[$i];
1345 } else {
1346 $line = $this->lines[$i];
1347 }
1348 // Reset fk_parent_line for line that are not child lines or special product
1349 if (($line->product_type != 9 && empty($line->fk_parent_line)) || $line->product_type == 9) {
1350 $fk_parent_line = 0;
1351 }
1352 // Complete vat rate with code
1353 $vatrate = $line->tva_tx;
1354 if ($line->vat_src_code && !preg_match('/\‍(.*\‍)/', $vatrate)) {
1355 $vatrate .= ' ('.$line->vat_src_code.')';
1356 }
1357
1358 if (getDolGlobalString('MAIN_CREATEFROM_KEEP_LINE_ORIGIN_INFORMATION')) {
1359 $originid = $line->origin_id;
1360 $origintype = $line->origin;
1361 } else {
1362 $originid = $line->id;
1363 $origintype = $this->element;
1364 }
1365
1366 $result = $this->addline(
1367 $line->desc,
1368 $line->subprice,
1369 $line->qty,
1370 $vatrate,
1371 $line->localtax1_tx,
1372 $line->localtax2_tx,
1373 $line->fk_product,
1374 $line->remise_percent,
1375 'HT',
1376 0,
1377 $line->info_bits,
1378 $line->product_type,
1379 $line->rang,
1380 $line->special_code,
1381 $fk_parent_line,
1382 $line->fk_fournprice,
1383 $line->pa_ht,
1384 $line->label,
1385 $line->date_start,
1386 $line->date_end,
1387 $line->array_options,
1388 $line->fk_unit,
1389 (string) $origintype,
1390 $originid,
1391 0,
1392 0,
1393 1
1394 );
1395
1396 if ($result < 0) {
1397 $error++;
1398 $this->error = $this->db->error;
1399 dol_print_error($this->db);
1400 break;
1401 }
1402
1403 // Set the id on created row
1404 $line->id = $result;
1405
1406 // Defined the new fk_parent_line
1407 if ($result > 0 && $line->product_type == 9) {
1408 $fk_parent_line = $result;
1409 }
1410 }
1411 }
1412
1413 if (!$error) {
1414 // Update denormalized data
1415 $resql = $this->update_price(1, 'auto', 0, $mysoc);
1416 if ($resql) {
1417 // Actions on extra fields
1418 $result = $this->insertExtraFields();
1419 if ($result < 0) {
1420 $error++;
1421 }
1422
1423 if (!$error && !$notrigger) {
1424 // Call trigger
1425 $result = $this->call_trigger('PROPAL_CREATE', $user);
1426 if ($result < 0) {
1427 $error++;
1428 }
1429 // End call triggers
1430 }
1431 } else {
1432 $this->error = $this->db->lasterror();
1433 $error++;
1434 }
1435 }
1436 } else {
1437 $this->error = $this->db->lasterror();
1438 $error++;
1439 }
1440
1441 if (!$error) {
1442 $this->db->commit();
1443 dol_syslog(get_class($this)."::create done id=".$this->id);
1444 return $this->id;
1445 } else {
1446 $this->db->rollback();
1447 return -2;
1448 }
1449 } else {
1450 $this->error = $this->db->lasterror();
1451 $this->db->rollback();
1452 return -1;
1453 }
1454 }
1455
1466 public function createFromClone(User $user, $socid = 0, $forceentity = null, $update_prices = false, $update_desc = false)
1467 {
1468 global $conf, $hookmanager, $mysoc;
1469
1470 dol_include_once('/projet/class/project.class.php');
1471
1472 $error = 0;
1473 $now = dol_now();
1474
1475 dol_syslog(__METHOD__, LOG_DEBUG);
1476
1477 $object = new self($this->db);
1478
1479 $this->db->begin();
1480
1481 // Load source object
1482 $object->fetch($this->id);
1483
1484 $objsoc = new Societe($this->db);
1485
1486 // Change socid if needed
1487 if (!empty($socid) && $socid != $object->socid) {
1488 if ($objsoc->fetch($socid) > 0) {
1489 $object->socid = $objsoc->id;
1490 $object->cond_reglement_id = (!empty($objsoc->cond_reglement_id) ? $objsoc->cond_reglement_id : 0);
1491 $object->deposit_percent = (!empty($objsoc->deposit_percent) ? $objsoc->deposit_percent : null);
1492 $object->mode_reglement_id = (!empty($objsoc->mode_reglement_id) ? $objsoc->mode_reglement_id : 0);
1493 $object->fk_delivery_address = 0;
1494
1495 /*if (isModEnabled('project'))
1496 {
1497 $project = new Project($db);
1498 if ($this->fk_project > 0 && $project->fetch($this->fk_project)) {
1499 if ($project->socid <= 0) $clonedObj->fk_project = $this->fk_project;
1500 else $clonedObj->fk_project = '';
1501 } else {
1502 $clonedObj->fk_project = '';
1503 }
1504 }*/
1505 $object->fk_project = 0; // A cloned proposal is set by default to no project.
1506 }
1507
1508 // reset ref_client
1509 if (!getDolGlobalString('MAIN_KEEP_REF_CUSTOMER_ON_CLONING')) {
1510 $object->ref_client = '';
1511 $object->ref_customer = '';
1512 }
1513
1514 // TODO Change product price if multi-prices
1515 } else {
1516 $objsoc->fetch($object->socid);
1517 }
1518
1519 // update prices
1520 if ($update_prices === true || $update_desc === true) {
1521 if ($objsoc->id > 0 && !empty($object->lines)) {
1522 if ($update_prices === true && getDolGlobalString('PRODUIT_CUSTOMER_PRICES')) {
1523 // If price per customer
1524 require_once DOL_DOCUMENT_ROOT . '/product/class/productcustomerprice.class.php';
1525 }
1526
1527 foreach ($object->lines as $line) {
1528 $line->id = 0;
1529
1530 if ($line->fk_product > 0) {
1531 $prod = new Product($this->db);
1532 $res = $prod->fetch($line->fk_product);
1533 if ($res > 0) {
1534 if ($update_prices === true) {
1535 $pu_ht = $prod->price;
1536 $tva_tx = (string) get_default_tva($mysoc, $objsoc, $prod->id);
1537 $remise_percent = $objsoc->remise_percent;
1538
1539 if (getDolGlobalString('PRODUIT_MULTIPRICES') && $objsoc->price_level > 0) {
1540 $pu_ht = $prod->multiprices[$objsoc->price_level];
1541 if (getDolGlobalString('PRODUIT_MULTIPRICES_USE_VAT_PER_LEVEL')) { // using this option is a bug. kept for backward compatibility
1542 if (isset($prod->multiprices_tva_tx[$objsoc->price_level])) {
1543 $tva_tx = (string) $prod->multiprices_tva_tx[$objsoc->price_level];
1544 }
1545 }
1546 } elseif (getDolGlobalString('PRODUIT_CUSTOMER_PRICES')) {
1547 $prodcustprice = new ProductCustomerPrice($this->db);
1548 $filter = array('t.fk_product' => (string) $prod->id, 't.fk_soc' => (string) $objsoc->id);
1549 $result = $prodcustprice->fetchAll('', '', 0, 0, $filter);
1550 if ($result) {
1551 // If there is some prices specific to the customer
1552 if (count($prodcustprice->lines) > 0) {
1553 $date_now = (int) floor(dol_now() / 86400) * 86400; // date without hours
1554 foreach ($prodcustprice->lines as $k => $custprice_line) {
1555 if ($custprice_line->date_begin <= $date_now && (empty($custprice_line->date_end) || $date_now <= $custprice_line->date_end)) {
1556 $pu_ht = price($custprice_line->price);
1557 $tva_tx = ($custprice_line->default_vat_code ? $custprice_line->tva_tx . ' (' . $custprice_line->default_vat_code . ' )' : $custprice_line->tva_tx);
1558 if ($custprice_line->default_vat_code && !preg_match('/\‍(.*\‍)/', $tva_tx)) {
1559 $tva_tx .= ' (' . $custprice_line->default_vat_code . ')';
1560 }
1561 $remise_percent = $custprice_line->discount_percent;
1562 break;
1563 }
1564 }
1565 }
1566 }
1567 }
1568
1569 $line->subprice = $pu_ht;
1570 $line->tva_tx = $tva_tx;
1571 $line->remise_percent = $remise_percent;
1572 // Refresh the buying price too: current supplier price if one was selected on the line, otherwise let PropaleLigne::insert() recompute it (defineBuyPrice(), according to MARGIN_TYPE)
1573 $line->pa_ht = '';
1574 if ($line->fk_fournprice > 0) {
1575 require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.product.class.php';
1576 $prodfourn = new ProductFournisseur($this->db);
1577 if ($prodfourn->fetch_product_fournisseur_price($line->fk_fournprice) > 0) {
1578 $line->pa_ht = price2num($prodfourn->fourn_unitprice * (1 - $prodfourn->fourn_remise_percent / 100), 'MU');
1579 } else {
1580 $line->fk_fournprice = 0;
1581 }
1582 }
1583 }
1584 if ($update_desc === true) {
1585 $line->desc = $prod->description;
1586 }
1587 }
1588 }
1589 }
1590 }
1591 }
1592
1593 $object->id = 0;
1594 $object->ref = '';
1595 $object->entity = (!empty($forceentity) ? $forceentity : $object->entity);
1596 $object->statut = self::STATUS_DRAFT;
1597 $object->status = self::STATUS_DRAFT;
1598
1599 // Clear fields
1600 $object->user_creation_id = $user->id;
1601 $object->user_validation_id = 0;
1602 $object->date = $now;
1603 $object->datep = $now; // deprecated
1604 $object->fin_validite = $object->date + ($object->duree_validite * 24 * 3600);
1605 if (!getDolGlobalString('MAIN_KEEP_REF_CUSTOMER_ON_CLONING')) {
1606 $object->ref_client = '';
1607 $object->ref_customer = '';
1608 }
1609 if (getDolGlobalInt('MAIN_DONT_KEEP_NOTE_ON_CLONING') == 1) {
1610 $object->note_private = '';
1611 $object->note_public = '';
1612 }
1613 // Create clone
1614 $object->context['createfromclone'] = 'createfromclone';
1615 $result = $object->create($user);
1616 if ($result < 0) {
1618 $error++;
1619 }
1620
1621 if (!$error && !getDolGlobalInt('MAIN_IGNORE_CONTACTS_ON_CLONING')) {
1622 // copy internal contacts
1623 if ($object->copy_linked_contact($this, 'internal') < 0) {
1624 $error++;
1625 }
1626 }
1627
1628 if (!$error) {
1629 // copy external contacts if same company
1630 if ($this->socid == $object->socid) {
1631 if ($object->copy_linked_contact($this, 'external') < 0) {
1632 $error++;
1633 }
1634 }
1635 }
1636
1637 if (!$error) {
1638 // Hook of thirdparty module
1639 if (is_object($hookmanager)) {
1640 $parameters = array('objFrom' => $this, 'clonedObj' => $object);
1641 $action = '';
1642 $reshook = $hookmanager->executeHooks('createFrom', $parameters, $object, $action); // Note that $action and $object may have been modified by some hooks
1643 if ($reshook < 0) {
1644 $this->setErrorsFromObject($hookmanager);
1645 $error++;
1646 }
1647 }
1648 }
1649
1650 unset($object->context['createfromclone']);
1651
1652 // End
1653 if (!$error) {
1654 $this->db->commit();
1655 return $object->id;
1656 } else {
1657 $this->db->rollback();
1658 return -1;
1659 }
1660 }
1661
1671 public function fetch($rowid, $ref = '', $ref_ext = '', $forceentity = 0)
1672 {
1673 $sql = "SELECT p.rowid, p.ref, p.entity, p.fk_soc";
1674 $sql .= ", p.total_ttc, p.total_tva, p.localtax1, p.localtax2, p.total_ht";
1675 $sql .= ", p.datec";
1676 $sql .= ", p.date_signature as dates";
1677 $sql .= ", p.date_valid as datev";
1678 $sql .= ", p.datep as dp";
1679 $sql .= ", p.fin_validite as dfv";
1680 $sql .= ", p.date_livraison as delivery_date";
1681 $sql .= ", p.model_pdf, p.last_main_doc, p.ref_client, ref_ext, p.extraparams";
1682 $sql .= ", p.note_private, p.note_public";
1683 $sql .= ", p.fk_projet as fk_project, p.fk_statut";
1684 $sql .= ", p.fk_user_author, p.fk_user_valid, p.fk_user_cloture";
1685 $sql .= ", p.fk_delivery_address";
1686 $sql .= ", p.fk_availability";
1687 $sql .= ", p.fk_input_reason";
1688 $sql .= ", p.fk_cond_reglement";
1689 $sql .= ", p.fk_mode_reglement";
1690 $sql .= ', p.fk_account';
1691 $sql .= ", p.fk_shipping_method";
1692 $sql .= ", p.fk_warehouse";
1693 $sql .= ", p.fk_incoterms, p.location_incoterms";
1694 $sql .= ", p.fk_multicurrency, p.multicurrency_code, p.multicurrency_tx, p.multicurrency_total_ht, p.multicurrency_total_tva, p.multicurrency_total_ttc";
1695 $sql .= ", p.tms as date_modification";
1696 $sql .= ", i.libelle as label_incoterms";
1697 $sql .= ", c.label as statut_label";
1698 $sql .= ", ca.code as availability_code, ca.label as availability";
1699 $sql .= ", dr.code as demand_reason_code, dr.label as demand_reason";
1700 $sql .= ", cr.code as cond_reglement_code, cr.libelle as cond_reglement, cr.libelle_facture as cond_reglement_libelle_doc, p.deposit_percent";
1701 $sql .= ", cp.code as mode_reglement_code, cp.libelle as mode_reglement";
1702 $sql .= " FROM ".MAIN_DB_PREFIX.$this->table_element." as p";
1703 $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_propalst as c ON p.fk_statut = c.id';
1704 $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_paiement as cp ON p.fk_mode_reglement = cp.id AND cp.entity IN ('.getEntity('c_paiement').')';
1705 $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').')';
1706 $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_availability as ca ON p.fk_availability = ca.rowid';
1707 $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_input_reason as dr ON p.fk_input_reason = dr.rowid';
1708 $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_incoterms as i ON p.fk_incoterms = i.rowid';
1709
1710 if (!empty($ref)) {
1711 if (!empty($forceentity)) {
1712 $sql .= " WHERE p.entity = ".(int) $forceentity; // Check only the current entity because we may have the same reference in several entities
1713 } else {
1714 $sql .= " WHERE p.entity IN (".getEntity('propal').")";
1715 }
1716 $sql .= " AND p.ref='".$this->db->escape($ref)."'";
1717 } else {
1718 // Don't use entity if you use rowid
1719 $sql .= " WHERE p.rowid = ".((int) $rowid);
1720 }
1721
1722 dol_syslog(get_class($this)."::fetch", LOG_DEBUG);
1723 $resql = $this->db->query($sql);
1724 if ($resql) {
1725 if ($this->db->num_rows($resql)) {
1726 $obj = $this->db->fetch_object($resql);
1727
1728 $this->id = $obj->rowid;
1729 $this->entity = $obj->entity;
1730
1731 $this->ref = $obj->ref;
1732 $this->ref_client = $obj->ref_client;
1733 $this->ref_customer = $obj->ref_client;
1734 $this->ref_ext = $obj->ref_ext;
1735
1736 $this->total = $obj->total_ttc; // TODO deprecated
1737 $this->total_ttc = $obj->total_ttc;
1738 $this->total_ht = $obj->total_ht;
1739 $this->total_tva = $obj->total_tva;
1740 $this->total_localtax1 = $obj->localtax1;
1741 $this->total_localtax2 = $obj->localtax2;
1742
1743 $this->socid = $obj->fk_soc;
1744 $this->thirdparty = null; // Clear if another value was already set by fetch_thirdparty
1745
1746 $this->fk_project = $obj->fk_project;
1747 $this->project = null; // Clear if another value was already set by fetchProject
1748
1749 $this->model_pdf = $obj->model_pdf;
1750 $this->last_main_doc = $obj->last_main_doc;
1751 $this->note = $obj->note_private; // TODO deprecated
1752 $this->note_private = $obj->note_private;
1753 $this->note_public = $obj->note_public;
1754
1755 $this->status = (int) $obj->fk_statut;
1756 $this->statut = $this->status; // deprecated
1757
1758 $this->datec = $this->db->jdate($obj->datec); // TODO deprecated
1759 $this->datev = $this->db->jdate($obj->datev); // TODO deprecated
1760 $this->date_creation = $this->db->jdate($obj->datec); //Creation date
1761 $this->date_validation = $this->db->jdate($obj->datev); //Validation date
1762 $this->date_modification = $this->db->jdate($obj->date_modification); // tms
1763 $this->date_signature = $this->db->jdate($obj->dates); // Signature date
1764 $this->date = $this->db->jdate($obj->dp); // Proposal date
1765 $this->datep = $this->db->jdate($obj->dp); // deprecated
1766 $this->fin_validite = $this->db->jdate($obj->dfv);
1767 $this->delivery_date = $this->db->jdate($obj->delivery_date);
1768 $this->shipping_method_id = ($obj->fk_shipping_method > 0) ? $obj->fk_shipping_method : null;
1769 $this->warehouse_id = ($obj->fk_warehouse > 0) ? $obj->fk_warehouse : null;
1770 $this->availability_id = $obj->fk_availability;
1771 $this->availability_code = $obj->availability_code;
1772 $this->availability = $obj->availability;
1773 $this->demand_reason_id = $obj->fk_input_reason;
1774 $this->demand_reason_code = $obj->demand_reason_code;
1775 $this->demand_reason = $obj->demand_reason;
1776 $this->fk_address = $obj->fk_delivery_address;
1777
1778 $this->mode_reglement_id = $obj->fk_mode_reglement;
1779 $this->mode_reglement_code = $obj->mode_reglement_code;
1780 $this->mode_reglement = $obj->mode_reglement;
1781 $this->fk_account = ($obj->fk_account > 0) ? $obj->fk_account : null;
1782 $this->cond_reglement_id = $obj->fk_cond_reglement;
1783 $this->cond_reglement_code = $obj->cond_reglement_code;
1784 $this->cond_reglement = $obj->cond_reglement;
1785 $this->cond_reglement_doc = $obj->cond_reglement_libelle_doc;
1786 $this->deposit_percent = $obj->deposit_percent;
1787
1788 $this->extraparams = !empty($obj->extraparams) ? (array) json_decode($obj->extraparams, true) : array();
1789
1790 $this->user_author_id = $obj->fk_user_author;
1791 $this->user_validation_id = $obj->fk_user_valid;
1792 $this->user_closing_id = $obj->fk_user_cloture;
1793
1794 //Incoterms
1795 $this->fk_incoterms = $obj->fk_incoterms;
1796 $this->location_incoterms = $obj->location_incoterms;
1797 $this->label_incoterms = $obj->label_incoterms;
1798
1799 // Multicurrency
1800 $this->fk_multicurrency = $obj->fk_multicurrency;
1801 $this->multicurrency_code = $obj->multicurrency_code;
1802 $this->multicurrency_tx = $obj->multicurrency_tx;
1803 $this->multicurrency_total_ht = $obj->multicurrency_total_ht;
1804 $this->multicurrency_total_tva = $obj->multicurrency_total_tva;
1805 $this->multicurrency_total_ttc = $obj->multicurrency_total_ttc;
1806
1807 // Retrieve all extrafield
1808 // fetch optionals attributes and labels
1809 $this->fetch_optionals();
1810
1811 $this->db->free($resql);
1812
1813 $this->lines = array();
1814
1815 // Lines
1816 $result = $this->fetch_lines();
1817 if ($result < 0) {
1818 return -3;
1819 }
1820
1821 return 1;
1822 }
1823
1824 $this->error = "Record Not Found";
1825 return 0;
1826 } else {
1827 $this->error = $this->db->lasterror();
1828 return -1;
1829 }
1830 }
1831
1839 public function update(User $user, $notrigger = 0)
1840 {
1841 global $conf;
1842
1843 $error = 0;
1844
1845 // Clean parameters
1846 if (isset($this->ref)) {
1847 $this->ref = trim($this->ref);
1848 }
1849 if (isset($this->ref_client)) {
1850 $this->ref_client = trim($this->ref_client);
1851 }
1852 if (isset($this->note) || isset($this->note_private)) {
1853 $this->note_private = (isset($this->note_private) ? trim($this->note_private) : trim($this->note));
1854 }
1855 if (isset($this->note_public)) {
1856 $this->note_public = trim($this->note_public);
1857 }
1858 if (isset($this->model_pdf)) {
1859 $this->model_pdf = trim($this->model_pdf);
1860 }
1861 if (isset($this->import_key)) {
1862 $this->import_key = trim($this->import_key);
1863 }
1864 if (!empty($this->duree_validite) && is_numeric($this->duree_validite)) {
1865 $this->fin_validite = $this->date + ($this->duree_validite * 24 * 3600);
1866 }
1867
1868 // Check parameters
1869 // Put here code to add control on parameters values
1870
1871 // Update request
1872 $sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element." SET";
1873 $sql = "UPDATE ".MAIN_DB_PREFIX."propal SET";
1874 $sql .= " ref = ".(isset($this->ref) ? "'".$this->db->escape($this->ref)."'" : "null").",";
1875 $sql .= " ref_client = ".(isset($this->ref_client) ? "'".$this->db->escape($this->ref_client)."'" : "null").",";
1876 $sql .= " ref_ext = ".(isset($this->ref_ext) ? "'".$this->db->escape($this->ref_ext)."'" : "null").",";
1877 $sql .= " fk_soc = ".(!empty($this->socid) ? (int) $this->socid : "null").",";
1878 $sql .= " datep = ".(strval($this->date) != '' ? "'".$this->db->idate($this->date)."'" : 'null').",";
1879 if (!empty($this->fin_validite)) {
1880 $sql .= " fin_validite = ".(strval($this->fin_validite) != '' ? "'".$this->db->idate($this->fin_validite)."'" : 'null').",";
1881 }
1882 $sql .= " date_valid = ".(strval($this->date_validation) != '' ? "'".$this->db->idate($this->date_validation)."'" : 'null').",";
1883 $sql .= " total_tva = ".(isset($this->total_tva) ? (float) $this->total_tva : "null").",";
1884 $sql .= " localtax1 = ".(isset($this->total_localtax1) ? (float) $this->total_localtax1 : "null").",";
1885 $sql .= " localtax2 = ".(isset($this->total_localtax2) ? (float) $this->total_localtax2 : "null").",";
1886 $sql .= " total_ht = ".(isset($this->total_ht) ? (float) $this->total_ht : "null").",";
1887 $sql .= " total_ttc = ".(isset($this->total_ttc) ? (float) $this->total_ttc : "null").",";
1888 $sql .= " fk_statut = ".(isset($this->status) ? (int) $this->status : "null").",";
1889 $sql .= " fk_user_author = ".(!empty($this->user_author_id) ? (int) $this->user_author_id : "null").",";
1890 $sql .= " fk_user_valid = ".(!empty($this->user_validation_id) ? (int) $this->user_validation_id : "null").",";
1891
1892 $sql .= " fk_projet = ".(!empty($this->fk_project) ? (int) $this->fk_project : "null").",";
1893 $sql .= " fk_cond_reglement = ".(!empty($this->cond_reglement_id) ? (int) $this->cond_reglement_id : "null").",";
1894 $sql .= " deposit_percent = ".(!empty($this->deposit_percent) ? "'".$this->db->escape($this->deposit_percent)."'" : "null").",";
1895 $sql .= " fk_mode_reglement = ".(!empty($this->mode_reglement_id) ? (int) $this->mode_reglement_id : "null").",";
1896 $sql .= " fk_input_reason = ".(!empty($this->demand_reason_id) ? (int) $this->demand_reason_id : "null").",";
1897 $sql .= " fk_shipping_method = ".(isset($this->shipping_method_id) ? (int) $this->shipping_method_id : "null").",";
1898 $sql .= " fk_availability = ".(!empty($this->availability_id) ? (int) $this->availability_id : "null").",";
1899 $sql .= " note_private = ".(isset($this->note_private) ? "'".$this->db->escape($this->note_private)."'" : "null").",";
1900 $sql .= " note_public = ".(isset($this->note_public) ? "'".$this->db->escape($this->note_public)."'" : "null").",";
1901 $sql .= " model_pdf = ".(isset($this->model_pdf) ? "'".$this->db->escape($this->model_pdf)."'" : "null").",";
1902 $sql .= " import_key = ".(isset($this->import_key) ? "'".$this->db->escape($this->import_key)."'" : "null");
1903 $sql .= " WHERE rowid = ".((int) $this->id);
1904
1905 $this->db->begin();
1906
1907 dol_syslog(get_class($this)."::update", LOG_DEBUG);
1908 $resql = $this->db->query($sql);
1909 if (!$resql) {
1910 $error++;
1911 $this->errors[] = "Error ".$this->db->lasterror();
1912 }
1913
1914 if (!$error) {
1915 $result = $this->insertExtraFields();
1916 if ($result < 0) {
1917 $error++;
1918 }
1919 }
1920
1921 if (!$error && !$notrigger) {
1922 // Call trigger
1923 $result = $this->call_trigger('PROPAL_MODIFY', $user);
1924 if ($result < 0) {
1925 $error++;
1926 }
1927 // End call triggers
1928 }
1929
1930 // Commit or rollback
1931 if ($error) {
1932 foreach ($this->errors as $errmsg) {
1933 dol_syslog(get_class($this)."::update ".$errmsg, LOG_ERR);
1934 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
1935 }
1936 $this->db->rollback();
1937 return -1 * $error;
1938 } else {
1939 $this->db->commit();
1940 return 1;
1941 }
1942 }
1943
1944
1945 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1954 public function fetch_lines($only_product = 0, $loadalsotranslation = 0, $sqlforgedfilters = '')
1955 {
1956 // phpcs:enable
1957 $this->lines = array();
1958
1959 $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,';
1960 $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,';
1961 $sql .= ' d.fk_unit,';
1962 $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,';
1963 $sql .= ' p.weight, p.weight_units, p.volume, p.volume_units,';
1964 $sql .= ' p.customcode, p.fk_country as country_id, c.code as country_code,';
1965 $sql .= ' d.date_start, d.date_end, d.extraparams,';
1966 $sql .= ' d.fk_multicurrency, d.multicurrency_code, d.multicurrency_subprice, d.multicurrency_total_ht, d.multicurrency_total_tva, d.multicurrency_total_ttc';
1967 $sql .= ' FROM '.MAIN_DB_PREFIX.$this->table_element_line.' as d';
1968 $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'product as p ON (d.fk_product = p.rowid)';
1969 $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_country as c ON c.rowid = p.fk_country';
1970 $sql .= ' WHERE d.fk_propal = '.((int) $this->id);
1971 if ($only_product) {
1972 $sql .= ' AND p.fk_product_type = 0';
1973 }
1974 if ($sqlforgedfilters) {
1975 $sql .= $sqlforgedfilters;
1976 }
1977 $sql .= ' ORDER BY d.rang, d.rowid';
1978
1979 dol_syslog(get_class($this)."::fetch_lines", LOG_DEBUG);
1980 $result = $this->db->query($sql);
1981 if ($result) {
1982 require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php';
1983
1984 $num = $this->db->num_rows($result);
1985
1986 $i = 0;
1987 while ($i < $num) {
1988 $objp = $this->db->fetch_object($result);
1989
1990 $line = new PropaleLigne($this->db);
1991
1992 $line->rowid = $objp->rowid; //Deprecated
1993 $line->id = $objp->rowid;
1994 $line->fk_propal = $objp->fk_propal;
1995 $line->fk_parent_line = $objp->fk_parent_line;
1996 $line->label = $objp->custom_label;
1997 $line->desc = $objp->description; // Description ligne
1998 $line->description = $objp->description; // Description ligne
1999 $line->qty = $objp->qty;
2000 $line->vat_src_code = $objp->vat_src_code;
2001 $line->tva_tx = $objp->tva_tx;
2002 $line->localtax1_tx = $objp->localtax1_tx;
2003 $line->localtax2_tx = $objp->localtax2_tx;
2004 $line->localtax1_type = $objp->localtax1_type;
2005 $line->localtax2_type = $objp->localtax2_type;
2006 $line->subprice = $objp->subprice;
2007 $line->fk_remise_except = $objp->fk_remise_except;
2008 $line->remise_percent = $objp->remise_percent;
2009
2010 $line->info_bits = $objp->info_bits;
2011 $line->total_ht = $objp->total_ht;
2012 $line->total_tva = $objp->total_tva;
2013 $line->total_localtax1 = $objp->total_localtax1;
2014 $line->total_localtax2 = $objp->total_localtax2;
2015 $line->total_ttc = $objp->total_ttc;
2016 $line->fk_fournprice = $objp->fk_fournprice;
2017 $marginInfos = getMarginInfos($objp->subprice, $objp->remise_percent, $objp->tva_tx, $objp->localtax1_tx, $objp->localtax2_tx, $line->fk_fournprice, $objp->pa_ht);
2018 $line->pa_ht = $marginInfos[0];
2019 $line->marge_tx = $marginInfos[1];
2020 $line->marque_tx = $marginInfos[2];
2021 $line->special_code = $objp->special_code;
2022 $line->rang = $objp->rang;
2023
2024 $line->fk_product = $objp->fk_product;
2025
2026 $line->ref = $objp->product_ref; // deprecated
2027 $line->libelle = $objp->product_label; // deprecated
2028
2029 $line->product_ref = $objp->product_ref;
2030 $line->product_label = $objp->product_label;
2031 $line->product_desc = $objp->product_desc; // Description produit
2032 $line->product_tobatch = $objp->product_tobatch;
2033 $line->product_barcode = $objp->product_barcode;
2034 $line->product_custom_code = $objp->customcode;
2035 $line->product_custom_country_id = $objp->country_id;
2036 $line->product_custom_country_code = $objp->country_code;
2037 $line->product_type = $objp->product_type;
2038 $line->fk_product_type = $objp->fk_product_type; // deprecated
2039
2040 $line->fk_unit = $objp->fk_unit;
2041 $line->weight = $objp->weight;
2042 $line->weight_units = $objp->weight_units;
2043 $line->volume = $objp->volume;
2044 $line->volume_units = $objp->volume_units;
2045
2046 $line->date_start = $this->db->jdate($objp->date_start);
2047 $line->date_end = $this->db->jdate($objp->date_end);
2048
2049 $line->extraparams = !empty($objp->extraparams) ? (array) json_decode($objp->extraparams, true) : array();
2050
2051 // Multicurrency
2052 $line->fk_multicurrency = $objp->fk_multicurrency;
2053 $line->multicurrency_code = $objp->multicurrency_code;
2054 $line->multicurrency_subprice = $objp->multicurrency_subprice;
2055 $line->multicurrency_total_ht = $objp->multicurrency_total_ht;
2056 $line->multicurrency_total_tva = $objp->multicurrency_total_tva;
2057 $line->multicurrency_total_ttc = $objp->multicurrency_total_ttc;
2058
2059 $line->fetch_optionals();
2060
2061 // multilangs
2062 if (getDolGlobalInt('MAIN_MULTILANGS') && !empty($objp->fk_product) && !empty($loadalsotranslation)) {
2063 $tmpproduct = new Product($this->db);
2064 $tmpproduct->fetch($objp->fk_product);
2065 $tmpproduct->getMultiLangs();
2066
2067 $line->multilangs = $tmpproduct->multilangs;
2068 }
2069
2070 $this->lines[$i] = $line;
2071
2072 $i++;
2073 }
2074
2075 $this->db->free($result);
2076
2077 return $num;
2078 } else {
2079 $this->error = $this->db->lasterror();
2080 return -3;
2081 }
2082 }
2083
2091 public function valid($user, $notrigger = 0)
2092 {
2093 global $conf;
2094
2095 require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
2096
2097 $error = 0;
2098
2099 // Protection
2100 if ($this->status == self::STATUS_VALIDATED) {
2101 dol_syslog(get_class($this)."::valid action abandoned: already validated", LOG_WARNING);
2102 return 0;
2103 }
2104
2105 if (!((!getDolGlobalString('MAIN_USE_ADVANCED_PERMS') && $user->hasRight('propal', 'creer'))
2106 || (getDolGlobalString('MAIN_USE_ADVANCED_PERMS') && $user->hasRight('propal', 'propal_advance', 'validate')))) {
2107 $this->error = 'ErrorPermissionDenied';
2108 dol_syslog(get_class($this)."::valid ".$this->error, LOG_ERR);
2109 return -1;
2110 }
2111
2112 if (!getDolGlobalBool('PROPALE_NOCHECK_ONSALE_PRODUCTS_ONVALID') && !$this->checkActiveProductInLines()) {
2113 dol_syslog(get_class($this)."::valid checkActiveProductInLines ".$this->error, LOG_INFO);
2114 return -1;
2115 }
2116
2117 $now = dol_now();
2118
2119 $this->db->begin();
2120
2121 // Numbering module definition
2122 $soc = new Societe($this->db);
2123 $soc->fetch($this->socid);
2124
2125 // Define new ref
2126 if (preg_match('/^[\‍(]?PROV/i', $this->ref) || empty($this->ref)) { // empty should not happened, but when it occurs, the test save life
2127 $num = $this->getNextNumRef($soc);
2128 } else {
2129 $num = (string) $this->ref;
2130 }
2131 $this->newref = dol_sanitizeFileName($num);
2132
2133 $sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element;
2134 $sql .= " SET ref = '".$this->db->escape($num)."',";
2135 $sql .= " fk_statut = ".self::STATUS_VALIDATED.", date_valid='".$this->db->idate($now)."', fk_user_valid=".((int) $user->id);
2136 $sql .= " WHERE rowid = ".((int) $this->id)." AND fk_statut = ".self::STATUS_DRAFT;
2137
2138 dol_syslog(get_class($this)."::valid", LOG_DEBUG);
2139 $resql = $this->db->query($sql);
2140 if (!$resql) {
2141 dol_print_error($this->db);
2142 $error++;
2143 }
2144
2145 // Trigger calls
2146 if (!$error && !$notrigger) {
2147 // Call trigger
2148 $result = $this->call_trigger('PROPAL_VALIDATE', $user);
2149 if ($result < 0) {
2150 $error++;
2151 }
2152 // End call triggers
2153 }
2154
2155 if (!$error) {
2156 $this->oldref = $this->ref;
2157
2158 // Rename directory if dir was a temporary ref
2159 if (preg_match('/^[\‍(]?PROV/i', $this->ref)) {
2160 // Now we rename also files into index
2161 $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)."'";
2162 $sql .= " WHERE filename LIKE '".$this->db->escape($this->ref)."%' AND filepath = 'propale/".$this->db->escape($this->ref)."' and entity = ".((int) $conf->entity);
2163 $resql = $this->db->query($sql);
2164 if (!$resql) {
2165 $error++;
2166 $this->error = $this->db->lasterror();
2167 }
2168 $sql = 'UPDATE '.MAIN_DB_PREFIX."ecm_files set filepath = 'propale/".$this->db->escape($this->newref)."'";
2169 $sql .= " WHERE filepath = 'propale/".$this->db->escape($this->ref)."' and entity = ".$conf->entity;
2170 $resql = $this->db->query($sql);
2171 if (!$resql) {
2172 $error++;
2173 $this->error = $this->db->lasterror();
2174 }
2175
2176 // We rename directory ($this->ref = old ref, $num = new ref) in order not to lose the attachments
2177 $oldref = dol_sanitizeFileName($this->ref);
2178 $newref = dol_sanitizeFileName($num);
2179 $dirsource = $conf->propal->multidir_output[$this->entity].'/'.$oldref;
2180 $dirdest = $conf->propal->multidir_output[$this->entity].'/'.$newref;
2181 if (!$error && file_exists($dirsource)) {
2182 dol_syslog(get_class($this)."::validate rename dir ".$dirsource." into ".$dirdest);
2183 if (@rename($dirsource, $dirdest)) {
2184 dol_syslog("Rename ok");
2185 // Rename docs starting with $oldref with $newref
2186 $listoffiles = dol_dir_list($dirdest, 'files', 1, '^'.preg_quote($oldref, '/'));
2187 foreach ($listoffiles as $fileentry) {
2188 $dirsource = $fileentry['name'];
2189 $dirdest = preg_replace('/^'.preg_quote($oldref, '/').'/', $newref, $dirsource);
2190 $dirsource = $fileentry['path'].'/'.$dirsource;
2191 $dirdest = $fileentry['path'].'/'.$dirdest;
2192 @rename($dirsource, $dirdest);
2193 }
2194 }
2195 }
2196 }
2197
2198 $this->ref = $num;
2199 $this->statut = self::STATUS_VALIDATED;
2200 $this->user_validation_id = $user->id;
2201 $this->datev = $now;
2202 $this->date_validation = $now;
2203
2204 $this->db->commit();
2205 return 1;
2206 } else {
2207 $this->db->rollback();
2208 return -1;
2209 }
2210 }
2211
2212
2213 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2222 public function set_date($user, $date, $notrigger = 0)
2223 {
2224 // phpcs:enable
2225 if (empty($date)) {
2226 $this->error = 'ErrorBadParameter';
2227 dol_syslog(get_class($this)."::set_date ".$this->error, LOG_ERR);
2228 return -1;
2229 }
2230
2231 if ($user->hasRight('propal', 'creer')) {
2232 $error = 0;
2233
2234 $this->db->begin();
2235
2236 $sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element;
2237 $sql .= " SET datep = '".$this->db->idate($date)."'";
2238 $sql .= " WHERE rowid = ".((int) $this->id);
2239
2240 dol_syslog(__METHOD__, LOG_DEBUG);
2241 $resql = $this->db->query($sql);
2242 if (!$resql) {
2243 $this->errors[] = $this->db->error();
2244 $error++;
2245 }
2246
2247 if (!$error) {
2248 $this->oldcopy = clone $this;
2249 $this->date = $date;
2250 $this->datep = $date; // deprecated
2251 }
2252
2253 if (!$notrigger && empty($error)) {
2254 // Call trigger
2255 $result = $this->call_trigger('PROPAL_MODIFY', $user);
2256 if ($result < 0) {
2257 $error++;
2258 }
2259 // End call triggers
2260 }
2261
2262 if (!$error) {
2263 $this->db->commit();
2264 return 1;
2265 } else {
2266 foreach ($this->errors as $errmsg) {
2267 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2268 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2269 }
2270 $this->db->rollback();
2271 return -1 * $error;
2272 }
2273 }
2274
2275 return -1;
2276 }
2277
2278 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2287 public function set_echeance($user, $date_end_validity, $notrigger = 0)
2288 {
2289 // phpcs:enable
2290 if ($user->hasRight('propal', 'creer')) {
2291 $error = 0;
2292
2293 $this->db->begin();
2294
2295 $sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element;
2296 $sql .= " SET fin_validite = ".($date_end_validity != '' ? "'".$this->db->idate($date_end_validity)."'" : 'null');
2297 $sql .= " WHERE rowid = ".((int) $this->id);
2298
2299 dol_syslog(__METHOD__, LOG_DEBUG);
2300
2301 $resql = $this->db->query($sql);
2302 if (!$resql) {
2303 $this->errors[] = $this->db->error();
2304 $error++;
2305 }
2306
2307
2308 if (!$error) {
2309 $this->oldcopy = clone $this;
2310 $this->fin_validite = $date_end_validity;
2311 }
2312
2313 if (!$notrigger && empty($error)) {
2314 // Call trigger
2315 $result = $this->call_trigger('PROPAL_MODIFY', $user);
2316 if ($result < 0) {
2317 $error++;
2318 }
2319 // End call triggers
2320 }
2321
2322 if (!$error) {
2323 $this->db->commit();
2324 return 1;
2325 } else {
2326 foreach ($this->errors as $errmsg) {
2327 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2328 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2329 }
2330 $this->db->rollback();
2331 return -1 * $error;
2332 }
2333 }
2334
2335 return -1;
2336 }
2337
2338 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2348 public function set_date_livraison($user, $delivery_date, $notrigger = 0)
2349 {
2350 // phpcs:enable
2351 return $this->setDeliveryDate($user, $delivery_date, $notrigger);
2352 }
2353
2362 public function setDeliveryDate($user, $delivery_date, $notrigger = 0)
2363 {
2364 if ($user->hasRight('propal', 'creer')) {
2365 $error = 0;
2366
2367 $this->db->begin();
2368
2369 $sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element;
2370 $sql .= " SET date_livraison = ".(isDolTms($delivery_date) ? "'".$this->db->idate($delivery_date)."'" : 'null');
2371 $sql .= " WHERE rowid = ".((int) $this->id);
2372
2373 dol_syslog(__METHOD__, LOG_DEBUG);
2374 $resql = $this->db->query($sql);
2375 if (!$resql) {
2376 $this->errors[] = $this->db->error();
2377 $error++;
2378 }
2379
2380 if (!$error) {
2381 $this->oldcopy = clone $this;
2382 $this->delivery_date = $delivery_date;
2383 }
2384
2385 if (!$notrigger && empty($error)) {
2386 // Call trigger
2387 $result = $this->call_trigger('PROPAL_MODIFY', $user);
2388 if ($result < 0) {
2389 $error++;
2390 }
2391 // End call triggers
2392 }
2393
2394 if (!$error) {
2395 $this->db->commit();
2396 return 1;
2397 } else {
2398 foreach ($this->errors as $errmsg) {
2399 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2400 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2401 }
2402 $this->db->rollback();
2403 return -1 * $error;
2404 }
2405 }
2406
2407 return -1;
2408 }
2409
2410 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2419 public function set_availability($user, $id, $notrigger = 0)
2420 {
2421 // phpcs:enable
2422 if ($user->hasRight('propal', 'creer') && $this->status >= self::STATUS_DRAFT) {
2423 $error = 0;
2424
2425 $this->db->begin();
2426
2427 $sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element;
2428 $sql .= " SET fk_availability = ".((int) $id);
2429 $sql .= " WHERE rowid = ".((int) $this->id);
2430
2431 dol_syslog(__METHOD__.' availability('.$id.')', LOG_DEBUG);
2432 $resql = $this->db->query($sql);
2433 if (!$resql) {
2434 $this->errors[] = $this->db->error();
2435 $error++;
2436 }
2437
2438 if (!$error) {
2439 $this->oldcopy = clone $this;
2440 $this->fk_availability = $id;
2441 $this->availability_id = $id;
2442 }
2443
2444 if (!$notrigger && empty($error)) {
2445 // Call trigger
2446 $result = $this->call_trigger('PROPAL_MODIFY', $user);
2447 if ($result < 0) {
2448 $error++;
2449 }
2450 // End call triggers
2451 }
2452
2453 if (!$error) {
2454 $this->db->commit();
2455 return 1;
2456 } else {
2457 foreach ($this->errors as $errmsg) {
2458 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2459 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2460 }
2461 $this->db->rollback();
2462 return -1 * $error;
2463 }
2464 } else {
2465 $error_str = 'Propal status do not meet requirement '.$this->status;
2466 dol_syslog(__METHOD__.$error_str, LOG_ERR);
2467 $this->error = $error_str;
2468 $this->errors[] = $this->error;
2469 return -2;
2470 }
2471 }
2472
2473 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2482 public function set_demand_reason($user, $id, $notrigger = 0)
2483 {
2484 // phpcs:enable
2485 if ($user->hasRight('propal', 'creer') && $this->status >= self::STATUS_DRAFT) {
2486 $error = 0;
2487
2488 $this->db->begin();
2489
2490 $sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element;
2491 $sql .= " SET fk_input_reason = ".((int) $id);
2492 $sql .= " WHERE rowid = ".((int) $this->id);
2493
2494 dol_syslog(__METHOD__, LOG_DEBUG);
2495 $resql = $this->db->query($sql);
2496 if (!$resql) {
2497 $this->errors[] = $this->db->error();
2498 $error++;
2499 }
2500
2501
2502 if (!$error) {
2503 $this->oldcopy = clone $this;
2504
2505 $this->demand_reason_id = $id;
2506 }
2507
2508
2509 if (!$notrigger && empty($error)) {
2510 // Call trigger
2511 $result = $this->call_trigger('PROPAL_MODIFY', $user);
2512 if ($result < 0) {
2513 $error++;
2514 }
2515 // End call triggers
2516 }
2517
2518 if (!$error) {
2519 $this->db->commit();
2520 return 1;
2521 } else {
2522 foreach ($this->errors as $errmsg) {
2523 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2524 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2525 }
2526 $this->db->rollback();
2527 return -1 * $error;
2528 }
2529 } else {
2530 $error_str = 'Propal status do not meet requirement '.$this->status;
2531 dol_syslog(__METHOD__.$error_str, LOG_ERR);
2532 $this->error = $error_str;
2533 $this->errors[] = $this->error;
2534 return -2;
2535 }
2536 }
2537
2538 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2547 public function set_ref_client($user, $ref_client, $notrigger = 0)
2548 {
2549 // phpcs:enable
2550 if ($user->hasRight('propal', 'creer')) {
2551 $error = 0;
2552
2553 $this->db->begin();
2554
2555 $sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element;
2556 $sql .= " SET ref_client = ".(empty($ref_client) ? 'NULL' : "'".$this->db->escape($ref_client)."'");
2557 $sql .= " WHERE rowid = ".((int) $this->id);
2558
2559 dol_syslog(__METHOD__.' $this->id='.$this->id.', ref_client='.$ref_client, LOG_DEBUG);
2560 $resql = $this->db->query($sql);
2561 if (!$resql) {
2562 $this->errors[] = $this->db->error();
2563 $error++;
2564 }
2565
2566 if (!$error) {
2567 $this->oldcopy = clone $this;
2568 $this->ref_customer = $ref_client;
2569 $this->ref_client = $ref_client;
2570 }
2571
2572 if (!$notrigger && empty($error)) {
2573 // Call trigger
2574 $result = $this->call_trigger('PROPAL_MODIFY', $user);
2575 if ($result < 0) {
2576 $error++;
2577 }
2578 // End call triggers
2579 }
2580
2581 if (!$error) {
2582 $this->db->commit();
2583 return 1;
2584 } else {
2585 foreach ($this->errors as $errmsg) {
2586 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2587 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2588 }
2589 $this->db->rollback();
2590 return -1 * $error;
2591 }
2592 }
2593
2594 return -1;
2595 }
2596
2597
2607 public function reopen($user, $status, $note = '', $notrigger = 0)
2608 {
2609 $error = 0;
2610
2611 $sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element;
2612 $sql .= " SET fk_statut = ".((int) $status).",";
2613 if (!empty($note)) {
2614 $sql .= " note_private = '".$this->db->escape($note)."',";
2615 }
2616 $sql .= " date_cloture=NULL, fk_user_cloture=NULL";
2617 $sql .= " WHERE rowid = ".((int) $this->id);
2618
2619 $this->db->begin();
2620
2621 dol_syslog(get_class($this)."::reopen", LOG_DEBUG);
2622 $resql = $this->db->query($sql);
2623 if (!$resql) {
2624 $error++;
2625 $this->errors[] = "Error ".$this->db->lasterror();
2626 }
2627 if (!$error) {
2628 if (!$notrigger) {
2629 // Call trigger
2630 $result = $this->call_trigger('PROPAL_REOPEN', $user);
2631 if ($result < 0) {
2632 $error++;
2633 }
2634 // End call triggers
2635 }
2636 }
2637
2638 // Commit or rollback
2639 if ($error) {
2640 if (!empty($this->errors)) {
2641 foreach ($this->errors as $errmsg) {
2642 dol_syslog(get_class($this)."::update ".$errmsg, LOG_ERR);
2643 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2644 }
2645 }
2646 $this->db->rollback();
2647 return -1 * $error;
2648 } else {
2649 $this->statut = $status;
2650 $this->status = $status;
2651
2652 $this->db->commit();
2653 return 1;
2654 }
2655 }
2656
2667 public function closeProposal($user, $status, $note_private = '', $notrigger = 0, $note_public = '')
2668 {
2669 global $langs,$conf;
2670
2671 $error = 0;
2672 $now = dol_now();
2673
2674 $this->db->begin();
2675
2676 $newprivatenote = dol_concatdesc($this->note_private, $note_private);
2677 $newpublicnote = dol_concatdesc($this->note_public, $note_public);
2678
2679 if (!getDolGlobalString('PROPALE_KEEP_OLD_SIGNATURE_INFO')) {
2680 $date_signature = $now;
2681 $fk_user_signature = $user->id;
2682 } else {
2683 $this->info($this->id);
2684 if (!isset($this->date_signature) || $this->date_signature == '') {
2685 $date_signature = $now;
2686 $fk_user_signature = $user->id;
2687 } else {
2688 $date_signature = $this->date_signature;
2689 $fk_user_signature = $this->user_signature->id;
2690 }
2691 }
2692
2693 $sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element;
2694 $sql .= " SET fk_statut = ".((int) $status).", note_private = '".$this->db->escape($newprivatenote)."', note_public = '".$this->db->escape($newpublicnote)."'";
2695 if ($status == self::STATUS_SIGNED) {
2696 $sql .= ", date_signature='".$this->db->idate($now)."', fk_user_signature = ".($fk_user_signature);
2697 }
2698 $sql .= " WHERE rowid = ".((int) $this->id);
2699
2700 $resql = $this->db->query($sql);
2701 if ($resql) {
2702 // Status self::STATUS_REFUSED by default
2703 $modelpdf = getDolGlobalString('PROPALE_ADDON_PDF_ODT_CLOSED', $this->model_pdf);
2704 $triggerName = 'PROPAL_CLOSE_REFUSED'; // used later in call_trigger()
2705
2706 if ($status == self::STATUS_SIGNED) { // Status self::STATUS_SIGNED
2707 $triggerName = 'PROPAL_CLOSE_SIGNED'; // used later in call_trigger()
2708 $modelpdf = getDolGlobalString('PROPALE_ADDON_PDF_ODT_TOBILL', $this->model_pdf);
2709
2710 // The connected company is classified as a client
2711 $soc = new Societe($this->db);
2712 $soc->id = $this->socid;
2713 $result = $soc->setAsCustomer();
2714
2715 if ($result < 0) {
2716 $this->error = $this->db->lasterror();
2717 $this->db->rollback();
2718 return -2;
2719 }
2720 }
2721
2722 if (!getDolGlobalString('MAIN_DISABLE_PDF_AUTOUPDATE') && !getDolGlobalInt('PROPAL_DISABLE_AUTOUPDATE_ON_CLOSE')) {
2723 // Define output language
2724 $outputlangs = $langs;
2725 if (getDolGlobalInt('MAIN_MULTILANGS')) {
2726 $outputlangs = new Translate("", $conf);
2727 $newlang = (GETPOST('lang_id', 'aZ09') ? GETPOST('lang_id', 'aZ09') : $this->thirdparty->default_lang);
2728 $outputlangs->setDefaultLang($newlang);
2729 }
2730
2731 // PDF
2732 $hidedetails = (getDolGlobalString('MAIN_GENERATE_DOCUMENTS_HIDE_DETAILS') ? 1 : 0);
2733 $hidedesc = (getDolGlobalString('MAIN_GENERATE_DOCUMENTS_HIDE_DESC') ? 1 : 0);
2734 $hideref = (getDolGlobalString('MAIN_GENERATE_DOCUMENTS_HIDE_REF') ? 1 : 0);
2735
2736 //$ret=$object->fetch($id); // Reload to get new records
2737 $this->generateDocument($modelpdf, $outputlangs, $hidedetails, $hidedesc, $hideref);
2738 }
2739
2740 $this->oldcopy = clone $this;
2741 $this->statut = $status; // deprecated
2742 $this->status = $status;
2743 $this->date_signature = $date_signature;
2744 $this->note_private = $newprivatenote;
2745
2746 if (!$notrigger && empty($error)) {
2747 // Call trigger
2748 $result = $this->call_trigger($triggerName, $user);
2749 if ($result < 0) {
2750 $error++;
2751 }
2752 // End call triggers
2753 }
2754
2755 if (!$error) {
2756 $this->db->commit();
2757 return 1;
2758 } else {
2759 foreach ($this->errors as $errmsg) {
2760 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2761 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2762 }
2763 if (!empty($this->oldcopy)) {
2764 $this->statut = $this->oldcopy->status; // deprecated
2765 $this->status = $this->oldcopy->status;
2766 $this->date_signature = $this->oldcopy->date_signature;
2767 $this->note_private = $this->oldcopy->note_private;
2768 }
2769
2770 $this->db->rollback();
2771 return -1;
2772 }
2773 } else {
2774 $this->error = $this->db->lasterror();
2775 $this->db->rollback();
2776 return -1;
2777 }
2778 }
2779
2788 public function classifyBilled(User $user, $notrigger = 0, $note = '')
2789 {
2790 global $conf, $langs;
2791
2792 $error = 0;
2793
2794 $now = dol_now();
2795 $num = 0;
2796
2797 $triggerName = 'PROPAL_CLASSIFY_BILLED';
2798
2799 $this->db->begin();
2800
2801 $newprivatenote = dol_concatdesc($this->note_private, $note);
2802
2803 $sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element;
2804 $sql .= " SET fk_statut = ".self::STATUS_BILLED.", ";
2805 $sql .= " note_private = '".$this->db->escape($newprivatenote)."', date_cloture='".$this->db->idate($now)."', fk_user_cloture=".((int) $user->id);
2806 $sql .= ' WHERE rowid = '.((int) $this->id).' AND fk_statut = '.((int) self::STATUS_SIGNED);
2807
2808 dol_syslog(__METHOD__, LOG_DEBUG);
2809 $resql = $this->db->query($sql);
2810 if (!$resql) {
2811 $this->errors[] = $this->db->error();
2812 $error++;
2813 } else {
2814 $num = $this->db->affected_rows($resql);
2815 }
2816
2817 if (!$error) {
2818 $modelpdf = getDolGlobalString('PROPALE_ADDON_PDF_ODT_CLOSED', $this->model_pdf);
2819
2820 if (!getDolGlobalString('MAIN_DISABLE_PDF_AUTOUPDATE')) {
2821 // Define output language
2822 $outputlangs = $langs;
2823 if (getDolGlobalInt('MAIN_MULTILANGS')) {
2824 $outputlangs = new Translate("", $conf);
2825 $newlang = (GETPOST('lang_id', 'aZ09') ? GETPOST('lang_id', 'aZ09') : $this->thirdparty->default_lang);
2826 $outputlangs->setDefaultLang($newlang);
2827 }
2828
2829 // PDF
2830 $hidedetails = (getDolGlobalString('MAIN_GENERATE_DOCUMENTS_HIDE_DETAILS') ? 1 : 0);
2831 $hidedesc = (getDolGlobalString('MAIN_GENERATE_DOCUMENTS_HIDE_DESC') ? 1 : 0);
2832 $hideref = (getDolGlobalString('MAIN_GENERATE_DOCUMENTS_HIDE_REF') ? 1 : 0);
2833
2834 //$ret=$object->fetch($id); // Reload to get new records
2835 $this->generateDocument($modelpdf, $outputlangs, $hidedetails, $hidedesc, $hideref);
2836 }
2837
2838 $this->oldcopy = clone $this;
2839 $this->statut = self::STATUS_BILLED;
2840 $this->status = self::STATUS_BILLED;
2841 $this->date_cloture = $now;
2842 $this->note_private = $newprivatenote;
2843 }
2844
2845 if (!$notrigger && empty($error)) {
2846 // Call trigger
2847 $result = $this->call_trigger($triggerName, $user);
2848 if ($result < 0) {
2849 $error++;
2850 }
2851 // End call triggers
2852 }
2853
2854 if (!$error) {
2855 $this->db->commit();
2856 return $num;
2857 } else {
2858 foreach ($this->errors as $errmsg) {
2859 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2860 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2861 }
2862 $this->db->rollback();
2863 return -1 * $error;
2864 }
2865 }
2866
2873 public function setCancel(User $user)
2874 {
2875 $error = 0;
2876
2877 $this->db->begin();
2878
2879 $sql = "UPDATE ". MAIN_DB_PREFIX . $this->table_element;
2880 $sql .= " SET fk_statut = " . self::STATUS_CANCELED . ",";
2881 $sql .= " fk_user_modif = " . ((int) $user->id);
2882 $sql .= " WHERE rowid = " . ((int) $this->id);
2883
2884 dol_syslog(get_class($this)."::cancel", LOG_DEBUG);
2885 if ($this->db->query($sql)) {
2886 // Call trigger
2887 $result = $this->call_trigger('PROPAL_CANCEL', $user);
2888 if ($result < 0) {
2889 $error++;
2890 }
2891 // End call triggers
2892
2893 if (!$error) {
2894 $this->statut = self::STATUS_CANCELED; // deprecated
2896 $this->db->commit();
2897 return 1;
2898 } else {
2899 foreach ($this->errors as $errmsg) {
2900 dol_syslog(get_class($this)."::cancel ".$errmsg, LOG_ERR);
2901 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2902 }
2903 $this->db->rollback();
2904 return -1;
2905 }
2906 } else {
2907 $this->error = $this->db->error();
2908 $this->db->rollback();
2909 return -1;
2910 }
2911 }
2912
2913 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2921 public function setDraft($user, $notrigger = 0)
2922 {
2923 // phpcs:enable
2924 $error = 0;
2925
2926 // Protection
2927 if ($this->status <= self::STATUS_DRAFT) {
2928 return 0;
2929 }
2930
2931 dol_syslog(get_class($this)."::setDraft", LOG_DEBUG);
2932
2933 $this->db->begin();
2934
2935 $sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element;
2936 $sql .= " SET fk_statut = ".self::STATUS_DRAFT;
2937 $sql .= ", online_sign_ip = NULL , online_sign_name = NULL";
2938 $sql .= " WHERE rowid = ".((int) $this->id);
2939
2940 $resql = $this->db->query($sql);
2941 if (!$resql) {
2942 $this->errors[] = $this->db->error();
2943 $error++;
2944 }
2945
2946 if (!$error) {
2947 $this->oldcopy = clone $this;
2948 }
2949
2950 if (!$notrigger && empty($error)) {
2951 // Call trigger
2952 $result = $this->call_trigger('PROPAL_MODIFY', $user);
2953 if ($result < 0) {
2954 $error++;
2955 }
2956 // End call triggers
2957 }
2958
2959 if (!$error) {
2960 $this->statut = self::STATUS_DRAFT; // deprecated
2961 $this->status = self::STATUS_DRAFT;
2962
2963 $this->db->commit();
2964 return 1;
2965 } else {
2966 foreach ($this->errors as $errmsg) {
2967 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2968 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2969 }
2970 $this->db->rollback();
2971 return -1 * $error;
2972 }
2973 }
2974
2975
2976 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2990 public function liste_array($shortlist = 0, $draft = 0, $notcurrentuser = 0, $socid = 0, $limit = 0, $offset = 0, $sortfield = 'p.datep', $sortorder = 'DESC')
2991 {
2992 // phpcs:enable
2993 global $user;
2994
2995 $ga = array();
2996
2997 $sql = "SELECT s.rowid, s.nom as name, s.client,";
2998 $sql .= " p.rowid as propalid, p.fk_statut, p.total_ht, p.ref, p.remise, ";
2999 $sql .= " p.datep as dp, p.fin_validite as datelimite";
3000 $sql .= " FROM ".MAIN_DB_PREFIX."societe as s, ".MAIN_DB_PREFIX.$this->table_element." as p, ".MAIN_DB_PREFIX."c_propalst as c";
3001 $sql .= " WHERE p.entity IN (".getEntity('propal').")";
3002 $sql .= " AND p.fk_soc = s.rowid";
3003 $sql .= " AND p.fk_statut = c.id";
3004
3005 // If the internal user must only see his customers, force searching by him
3006 $search_sale = 0;
3007 if (empty($user->socid) && !$user->hasRight('societe', 'client', 'voir')) {
3008 $search_sale = $user->id;
3009 }
3010 // Search on sale representative
3011 if ($search_sale && $search_sale != '-1') {
3012 if ($search_sale == -2) {
3013 $sql .= " AND NOT EXISTS (SELECT sc.fk_soc FROM ".MAIN_DB_PREFIX."societe_commerciaux as sc WHERE sc.fk_soc = p.fk_soc)";
3014 } elseif ($search_sale > 0) {
3015 $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).")";
3016 }
3017 }
3018 // Search on socid
3019 if ($socid) {
3020 $sql .= " AND p.fk_soc = ".((int) $socid);
3021 }
3022 if ($draft) {
3023 $sql .= " AND p.fk_statut = ".((int) self::STATUS_DRAFT);
3024 }
3025 if ($notcurrentuser > 0) {
3026 $sql .= " AND p.fk_user_author <> ".((int) $user->id);
3027 }
3028 $sql .= $this->db->order($sortfield, $sortorder);
3029 $sql .= $this->db->plimit($limit, $offset);
3030
3031 $result = $this->db->query($sql);
3032 if ($result) {
3033 $num = $this->db->num_rows($result);
3034 if ($num) {
3035 $i = 0;
3036 while ($i < $num) {
3037 $obj = $this->db->fetch_object($result);
3038
3039 if ($shortlist == 1) {
3040 $ga[$obj->propalid] = $obj->ref;
3041 } elseif ($shortlist == 2) {
3042 $ga[$obj->propalid] = $obj->ref.' ('.$obj->name.')';
3043 } else {
3044 $ga[$i]['id'] = $obj->propalid;
3045 $ga[$i]['ref'] = $obj->ref;
3046 $ga[$i]['name'] = $obj->name;
3047 }
3048
3049 $i++;
3050 }
3051 }
3052 return $ga;
3053 } else {
3054 dol_print_error($this->db);
3055 return -1;
3056 }
3057 }
3058
3064 public function getInvoiceArrayList()
3065 {
3066 return $this->InvoiceArrayList($this->id);
3067 }
3068
3069 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3076 public function InvoiceArrayList($id)
3077 {
3078 // phpcs:enable
3079 $ga = array();
3080 $linkedInvoices = array();
3081
3082 $this->fetchObjectLinked($id, $this->element);
3083 foreach ($this->linkedObjectsIds as $objecttype => $objectid) {
3084 // Nouveau système du common object renvoi des rowid et non un id linéaire de 1 à n
3085 // On parcourt donc une liste d'objets en tant qu'objet unique
3086 foreach ($objectid as $key => $object) {
3087 // Cas des factures liees directement
3088 if ($objecttype == 'facture') {
3089 $linkedInvoices[] = $object;
3090 } else {
3091 // Cas des factures liees par un autre object (ex: commande)
3092 $this->fetchObjectLinked($object, $objecttype);
3093 foreach ($this->linkedObjectsIds as $subobjecttype => $subobjectid) {
3094 foreach ($subobjectid as $subkey => $subobject) {
3095 if ($subobjecttype == 'facture') {
3096 $linkedInvoices[] = $subobject;
3097 }
3098 }
3099 }
3100 }
3101 }
3102 }
3103
3104 if (count($linkedInvoices) > 0) {
3105 $sql = "SELECT rowid as facid, ref, total_ht as total, datef as df, fk_user_author, fk_statut, paye";
3106 $sql .= " FROM ".MAIN_DB_PREFIX."facture";
3107 $sql .= " WHERE rowid IN (".$this->db->sanitize(implode(',', $linkedInvoices)).")";
3108
3109 dol_syslog(get_class($this)."::InvoiceArrayList", LOG_DEBUG);
3110 $resql = $this->db->query($sql);
3111
3112 if ($resql) {
3113 $tab_sqlobj = array();
3114 $nump = $this->db->num_rows($resql);
3115 for ($i = 0; $i < $nump; $i++) {
3116 $sqlobj = $this->db->fetch_object($resql);
3117 $tab_sqlobj[] = $sqlobj;
3118 }
3119 $this->db->free($resql);
3120
3121 $nump = count($tab_sqlobj);
3122
3123 if ($nump) {
3124 $i = 0;
3125 while ($i < $nump) {
3126 $obj = array_shift($tab_sqlobj);
3127
3128 $ga[$i] = $obj;
3129
3130 $i++;
3131 }
3132 }
3133 return $ga;
3134 } else {
3135 return -1;
3136 }
3137 } else {
3138 return $ga;
3139 }
3140 }
3141
3149 public function delete($user, $notrigger = 0)
3150 {
3151 global $conf;
3152 require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
3153
3154 $error = 0;
3155
3156 $this->db->begin();
3157
3158 if (!$notrigger) {
3159 // Call trigger
3160 $result = $this->call_trigger('PROPAL_DELETE', $user);
3161 if ($result < 0) {
3162 $error++;
3163 }
3164 // End call triggers
3165 }
3166
3167 // Remove linked categories.
3168 if (!$error) {
3169 $sql = "DELETE FROM ".MAIN_DB_PREFIX."categorie_propal";
3170 $sql .= " WHERE fk_propal = ".((int) $this->id);
3171
3172 $result = $this->db->query($sql);
3173 if (!$result) {
3174 $error++;
3175 $this->error = $this->db->lasterror();
3176 $this->errors[] = $this->error;
3177 }
3178 }
3179
3180 // Delete extrafields of lines and lines
3181 if (!$error && !empty($this->table_element_line)) {
3182 $tabletodelete = $this->table_element_line;
3183 $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).")";
3184 $sql = "DELETE FROM ".MAIN_DB_PREFIX.$tabletodelete." WHERE ".$this->fk_element." = ".((int) $this->id);
3185 if (!$this->db->query($sqlef) || !$this->db->query($sql)) {
3186 $error++;
3187 $this->error = $this->db->lasterror();
3188 $this->errors[] = $this->error;
3189 dol_syslog(get_class($this)."::delete error ".$this->error, LOG_ERR);
3190 }
3191 }
3192
3193 if (!$error) {
3194 // Delete linked object
3195 $res = $this->deleteObjectLinked();
3196 if ($res < 0) {
3197 $error++;
3198 }
3199 }
3200
3201 if (!$error) {
3202 // Delete linked contacts
3203 $res = $this->delete_linked_contact();
3204 if ($res < 0) {
3205 $error++;
3206 }
3207 }
3208
3209 // Removed extrafields of object
3210 if (!$error) {
3211 $result = $this->deleteExtraFields();
3212 if ($result < 0) {
3213 $error++;
3214 dol_syslog(get_class($this)."::delete error ".$this->error, LOG_ERR);
3215 }
3216 }
3217
3218 // Delete main record
3219 if (!$error) {
3220 $sql = "DELETE FROM ".MAIN_DB_PREFIX.$this->table_element." WHERE rowid = ".((int) $this->id);
3221 $res = $this->db->query($sql);
3222 if (!$res) {
3223 $error++;
3224 $this->error = $this->db->lasterror();
3225 $this->errors[] = $this->error;
3226 dol_syslog(get_class($this)."::delete error ".$this->error, LOG_ERR);
3227 }
3228 }
3229
3230 // Delete record into ECM index and physically
3231 if (!$error) {
3232 $res = $this->deleteEcmFiles(0); // Deleting files physically is done later with the dol_delete_dir_recursive
3233 $res = $this->deleteEcmFiles(1); // Deleting files physically is done later with the dol_delete_dir_recursive
3234 if (!$res) {
3235 $error++;
3236 }
3237 }
3238
3239 if (!$error) {
3240 // We remove directory
3241 $ref = dol_sanitizeFileName($this->ref);
3242 if ($conf->propal->multidir_output[$this->entity] && !empty($this->ref)) {
3243 $dir = $conf->propal->multidir_output[$this->entity]."/".$ref;
3244 $file = $dir."/".$ref.".pdf";
3245 if (file_exists($file)) {
3246 dol_delete_preview($this);
3247
3248 if (!dol_delete_file($file, 0, 0, 0, $this)) {
3249 $this->error = 'ErrorFailToDeleteFile';
3250 $this->errors[] = $this->error;
3251 $this->db->rollback();
3252 return 0;
3253 }
3254 }
3255 if (file_exists($dir)) {
3256 $res = @dol_delete_dir_recursive($dir); // delete files physically + into ecm tables
3257 if (!$res) {
3258 $this->error = 'ErrorFailToDeleteDir';
3259 $this->errors[] = $this->error;
3260 $this->db->rollback();
3261 return 0;
3262 }
3263 }
3264 }
3265 }
3266
3267 if (!$error) {
3268 dol_syslog(get_class($this)."::delete ".$this->id." by ".$user->id, LOG_DEBUG);
3269 $this->db->commit();
3270 return 1;
3271 } else {
3272 $this->db->rollback();
3273 return -1;
3274 }
3275 }
3276
3285 public function availability($availability_id, $notrigger = 0)
3286 {
3287 global $user;
3288
3289 if ($this->status >= self::STATUS_DRAFT) {
3290 $error = 0;
3291
3292 $this->db->begin();
3293
3294 $sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element;
3295 $sql .= ' SET fk_availability = '.((int) $availability_id);
3296 $sql .= ' WHERE rowid='.((int) $this->id);
3297
3298 dol_syslog(__METHOD__.' availability('.$availability_id.')', LOG_DEBUG);
3299 $resql = $this->db->query($sql);
3300 if (!$resql) {
3301 $this->errors[] = $this->db->error();
3302 $error++;
3303 }
3304
3305 if (!$error) {
3306 $this->oldcopy = clone $this;
3307 $this->availability_id = $availability_id;
3308 }
3309
3310 if (!$notrigger && empty($error)) {
3311 // Call trigger
3312 $result = $this->call_trigger('PROPAL_MODIFY', $user);
3313 if ($result < 0) {
3314 $error++;
3315 }
3316 // End call triggers
3317 }
3318
3319 if (!$error) {
3320 $this->db->commit();
3321 return 1;
3322 } else {
3323 foreach ($this->errors as $errmsg) {
3324 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
3325 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
3326 }
3327 $this->db->rollback();
3328 return -1 * $error;
3329 }
3330 } else {
3331 $error_str = 'Propal status do not meet requirement '.$this->status;
3332 dol_syslog(__METHOD__.$error_str, LOG_ERR);
3333 $this->error = $error_str;
3334 $this->errors[] = $this->error;
3335 return -2;
3336 }
3337 }
3338
3339 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3348 public function demand_reason($demand_reason_id, $notrigger = 0)
3349 {
3350 // phpcs:enable
3351 global $user;
3352
3353 if ($this->status >= self::STATUS_DRAFT) {
3354 $error = 0;
3355
3356 $this->db->begin();
3357
3358 $sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element;
3359 $sql .= ' SET fk_input_reason = '.((int) $demand_reason_id);
3360 $sql .= ' WHERE rowid='.((int) $this->id);
3361
3362 dol_syslog(__METHOD__.' demand_reason('.$demand_reason_id.')', LOG_DEBUG);
3363 $resql = $this->db->query($sql);
3364 if (!$resql) {
3365 $this->errors[] = $this->db->error();
3366 $error++;
3367 }
3368
3369 if (!$error) {
3370 $this->oldcopy = clone $this;
3371 $this->demand_reason_id = $demand_reason_id;
3372 }
3373
3374 if (!$notrigger && empty($error)) {
3375 // Call trigger
3376 $result = $this->call_trigger('PROPAL_MODIFY', $user);
3377 if ($result < 0) {
3378 $error++;
3379 }
3380 // End call triggers
3381 }
3382
3383 if (!$error) {
3384 $this->db->commit();
3385 return 1;
3386 } else {
3387 foreach ($this->errors as $errmsg) {
3388 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
3389 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
3390 }
3391 $this->db->rollback();
3392 return -1 * $error;
3393 }
3394 } else {
3395 $error_str = 'Propal status do not meet requirement '.$this->status;
3396 dol_syslog(__METHOD__.$error_str, LOG_ERR);
3397 $this->error = $error_str;
3398 $this->errors[] = $this->error;
3399 return -2;
3400 }
3401 }
3402
3403
3410 public function info($id)
3411 {
3412 $sql = "SELECT c.rowid, ";
3413 $sql .= " c.datec, c.date_valid as datev, c.date_signature, c.date_cloture,";
3414 $sql .= " c.fk_user_author, c.fk_user_valid, c.fk_user_signature, c.fk_user_cloture";
3415 $sql .= " FROM ".MAIN_DB_PREFIX.$this->table_element." as c";
3416 $sql .= " WHERE c.rowid = ".((int) $id);
3417
3418 $result = $this->db->query($sql);
3419
3420 if ($result) {
3421 if ($this->db->num_rows($result)) {
3422 $obj = $this->db->fetch_object($result);
3423
3424 $this->id = $obj->rowid;
3425
3426 $this->date_creation = $this->db->jdate($obj->datec);
3427 $this->date_validation = $this->db->jdate($obj->datev);
3428 $this->date_signature = $this->db->jdate($obj->date_signature);
3429 $this->date_cloture = $this->db->jdate($obj->date_cloture);
3430
3431 $this->user_creation_id = $obj->fk_user_author;
3432 $this->user_validation_id = $obj->fk_user_valid;
3433
3434 if ($obj->fk_user_signature) {
3435 $user_signature = new User($this->db);
3436 $user_signature->fetch($obj->fk_user_signature);
3437 $this->user_signature = $user_signature;
3438 }
3439
3440 $this->user_closing_id = $obj->fk_user_cloture;
3441 }
3442 $this->db->free($result);
3443 } else {
3444 dol_print_error($this->db);
3445 }
3446 }
3447
3448
3455 public function getLibStatut($mode = 0)
3456 {
3457 return $this->LibStatut($this->status, $mode);
3458 }
3459
3460 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3468 public function LibStatut($status, $mode = 1)
3469 {
3470 // phpcs:enable
3471 global $hookmanager;
3472
3473 // Init/load array of translation of status
3474 if (empty($this->labelStatus) || empty($this->labelStatusShort)) {
3475 global $langs;
3476 $langs->load("propal");
3477 $this->labelStatus[-1] = $langs->transnoentitiesnoconv("PropalStatusCanceled");
3478 $this->labelStatus[0] = $langs->transnoentitiesnoconv("PropalStatusDraft");
3479 $this->labelStatus[1] = $langs->transnoentitiesnoconv("PropalStatusValidated");
3480 $this->labelStatus[2] = $langs->transnoentitiesnoconv("PropalStatusSigned");
3481 $this->labelStatus[3] = $langs->transnoentitiesnoconv("PropalStatusNotSigned");
3482 $this->labelStatus[4] = $langs->transnoentitiesnoconv("PropalStatusBilled");
3483 $this->labelStatusShort[-1] = $langs->transnoentitiesnoconv("PropalStatusCanceledShort");
3484 $this->labelStatusShort[0] = $langs->transnoentitiesnoconv("PropalStatusDraftShort");
3485 $this->labelStatusShort[1] = $langs->transnoentitiesnoconv("PropalStatusValidatedShort");
3486 $this->labelStatusShort[2] = $langs->transnoentitiesnoconv("PropalStatusSignedShort");
3487 $this->labelStatusShort[3] = $langs->transnoentitiesnoconv("PropalStatusNotSignedShort");
3488 $this->labelStatusShort[4] = $langs->transnoentitiesnoconv("PropalStatusBilledShort");
3489 }
3490
3491 $statusType = '';
3492 if ($status == self::STATUS_CANCELED) {
3493 $statusType = 'status9';
3494 } elseif ($status == self::STATUS_DRAFT) {
3495 $statusType = 'status0';
3496 } elseif ($status == self::STATUS_VALIDATED) {
3497 $statusType = 'status1';
3498 } elseif ($status == self::STATUS_SIGNED) {
3499 $statusType = 'status4';
3500 } elseif ($status == self::STATUS_NOTSIGNED) {
3501 $statusType = 'status9';
3502 } elseif ($status == self::STATUS_BILLED) {
3503 $statusType = 'status6';
3504 }
3505
3506 $parameters = array('status' => $status, 'mode' => $mode);
3507 $reshook = $hookmanager->executeHooks('LibStatut', $parameters, $this); // Note that $action and $object may have been modified by hook
3508
3509 if ($reshook > 0) {
3510 return $hookmanager->resPrint;
3511 }
3512
3513 return dolGetStatus($this->labelStatus[$status], $this->labelStatusShort[$status], '', $statusType, $mode);
3514 }
3515
3516
3517 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3525 public function load_board($user, $mode)
3526 {
3527 // phpcs:enable
3528 global $conf, $langs, $hookmanager;
3529
3530 $sql = "SELECT p.rowid, p.ref, p.datec as datec, p.fin_validite as datefin, p.total_ht";
3531 $sql .= " FROM ".MAIN_DB_PREFIX.$this->table_element." as p";
3532 $sql .= " WHERE p.entity IN (".getEntity('propal').")";
3533 if ($mode == 'opened') {
3534 $sql .= " AND p.fk_statut = ".self::STATUS_VALIDATED;
3535 }
3536 if ($mode == 'signed') {
3537 $sql .= " AND p.fk_statut = ".self::STATUS_SIGNED;
3538 }
3539 // If the internal user must only see his customers, force searching by him
3540 $search_sale = 0;
3541 if (empty($user->socid) && !$user->hasRight('societe', 'client', 'voir')) {
3542 $search_sale = $user->id;
3543 }
3544 // Search on sale representative
3545 if ($search_sale && $search_sale != '-1') {
3546 if ($search_sale == -2) {
3547 $sql .= " AND NOT EXISTS (SELECT sc.fk_soc FROM ".MAIN_DB_PREFIX."societe_commerciaux as sc WHERE sc.fk_soc = p.fk_soc)";
3548 } elseif ($search_sale > 0) {
3549 $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).")";
3550 }
3551 }
3552 if ($user->socid) {
3553 $sql .= " AND p.fk_soc = ".((int) $user->socid);
3554 }
3555 // Add where from hooks
3556 $parameters = array('socid' => $user->socid);
3557 $reshook = $hookmanager->executeHooks('printFieldListWhere', $parameters, $this); // Note that $action and $object may have been modified by hook
3558 $sql .= $hookmanager->resPrint;
3559
3560 $resql = $this->db->query($sql);
3561 if ($resql) {
3562 $langs->load("propal");
3563 $now = dol_now();
3564
3565 $delay_warning = 0;
3566 $status = 0;
3567 $label = $labelShort = '';
3568 if ($mode == 'opened') {
3569 $delay_warning = getWarningDelay('propal', 'cloture');
3570 $status = self::STATUS_VALIDATED;
3571 $label = $langs->transnoentitiesnoconv("PropalsToClose");
3572 $labelShort = $langs->transnoentitiesnoconv("ToAcceptRefuse");
3573 }
3574 if ($mode == 'signed') {
3575 $delay_warning = getWarningDelay('propal', 'facturation');
3576 $status = self::STATUS_SIGNED;
3577 $label = $langs->trans("PropalsToBill"); // We set here bill but may be billed or ordered
3578 $labelShort = $langs->trans("ToBill");
3579 }
3580
3581 $response = new WorkboardResponse();
3582 $response->warning_delay = $delay_warning / 60 / 60 / 24;
3583 $response->label = $label;
3584 $response->labelShort = $labelShort;
3585 $response->url = DOL_URL_ROOT.'/comm/propal/list.php?search_status='.$status.'&mainmenu=commercial&leftmenu=propals';
3586 $response->url_late = DOL_URL_ROOT.'/comm/propal/list.php?search_option=late&mainmenu=commercial&leftmenu=propals&sortfield=p.datep&sortorder=asc';
3587 $response->img = img_object('', "propal");
3588
3589 // This assignment in condition is not a bug. It allows walking the results.
3590 while ($obj = $this->db->fetch_object($resql)) {
3591 $response->nbtodo++;
3592 $response->total += $obj->total_ht;
3593
3594 if ($mode == 'opened') {
3595 $datelimit = $this->db->jdate($obj->datefin);
3596 if ($datelimit < ($now - $delay_warning)) {
3597 $response->nbtodolate++;
3598 }
3599 }
3600 // TODO Definir regle des propales a facturer en retard
3601 // if ($mode == 'signed' && ! count($this->FactureListeArray($obj->rowid))) $this->nbtodolate++;
3602 }
3603
3604 return $response;
3605 } else {
3606 $this->error = $this->db->error();
3607 return -1;
3608 }
3609 }
3610
3611
3620 public function initAsSpecimen($param = array())
3621 {
3622 global $conf, $langs;
3623
3624 // Load array of products prodids
3625 $num_prods = 0;
3626 $prodids = array();
3627 $sql = "SELECT rowid";
3628 $sql .= " FROM ".MAIN_DB_PREFIX."product";
3629 $sql .= " WHERE entity IN (".getEntity('product').")";
3630 if (array_key_exists('tosell', $param)) {
3631 $sql .= " AND tosell = ".((int) $param['tosell']);
3632 }
3633 $sql .= $this->db->plimit(100);
3634
3635 $resql = $this->db->query($sql);
3636 if ($resql) {
3637 $num_prods = $this->db->num_rows($resql);
3638 $i = 0;
3639 while ($i < $num_prods) {
3640 $i++;
3641 $row = $this->db->fetch_row($resql);
3642 $prodids[$i] = $row[0];
3643 }
3644 }
3645
3646 // Initialise parameters
3647 $this->id = 0;
3648 $this->ref = 'SPECIMEN';
3649 $this->ref_customer = 'NEMICEPS';
3650 $this->ref_client = 'NEMICEPS';
3651 $this->specimen = 1;
3652 $this->socid = 1;
3653 $this->date = time();
3654 $this->fin_validite = $this->date + 3600 * 24 * 30;
3655 $this->cond_reglement_id = 1;
3656 $this->cond_reglement_code = 'RECEP';
3657 $this->mode_reglement_id = 7;
3658 $this->mode_reglement_code = 'CHQ';
3659 $this->availability_id = 1;
3660 $this->availability_code = 'AV_NOW';
3661 $this->demand_reason_id = 1;
3662 $this->demand_reason_code = 'SRC_00';
3663 $this->note_public = 'This is a comment (public)';
3664 $this->note_private = 'This is a comment (private)';
3665
3666 $this->multicurrency_tx = 1;
3667 $this->multicurrency_code = getDolCurrency();
3668
3669 // Lines
3670 $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)
3671 $xnbp = 0;
3672 while ($xnbp < $nbp) {
3673 $line = new PropaleLigne($this->db);
3674 $line->desc = $langs->trans("Description")." ".$xnbp;
3675 $line->qty = 1;
3676 $line->subprice = 100;
3677 $line->tva_tx = 20;
3678 $line->localtax1_tx = 0;
3679 $line->localtax2_tx = 0;
3680 if ($xnbp == 2) {
3681 $line->total_ht = 50;
3682 $line->total_ttc = 60;
3683 $line->total_tva = 10;
3684 $line->remise_percent = 50;
3685 } else {
3686 $line->total_ht = 100;
3687 $line->total_ttc = 120;
3688 $line->total_tva = 20;
3689 $line->remise_percent = 00;
3690 }
3691
3692 if ($num_prods > 0) {
3693 $prodid = mt_rand(1, $num_prods);
3694 $line->fk_product = $prodids[$prodid];
3695 $line->product_ref = 'SPECIMEN';
3696 }
3697
3698 $this->lines[$xnbp] = $line;
3699
3700 $this->total_ht += $line->total_ht;
3701 $this->total_tva += $line->total_tva;
3702 $this->total_ttc += $line->total_ttc;
3703
3704 $xnbp++;
3705 }
3706
3707 return 1;
3708 }
3709
3715 public function loadStateBoard()
3716 {
3717 global $user, $hookmanager;
3718
3719 $this->nb = array();
3720
3721 $sql = "SELECT count(p.rowid) as nb";
3722 $sql .= " FROM ".MAIN_DB_PREFIX.$this->table_element." as p";
3723 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe as s ON p.fk_soc = s.rowid";
3724 $sql .= " WHERE p.entity IN (".getEntity('propal').")";
3725
3726 // If the internal user must only see his customers, force searching by him
3727 $search_sale = 0;
3728 if (empty($user->socid) && !$user->hasRight('societe', 'client', 'voir')) {
3729 $search_sale = $user->id;
3730 }
3731 // Search on sale representative
3732 if ($search_sale && $search_sale != '-1') {
3733 if ($search_sale == -2) {
3734 $sql .= " AND NOT EXISTS (SELECT sc.fk_soc FROM ".MAIN_DB_PREFIX."societe_commerciaux as sc WHERE sc.fk_soc = p.fk_soc)";
3735 } elseif ($search_sale > 0) {
3736 $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).")";
3737 }
3738 }
3739
3740 // Add where from hooks
3741 $parameters = array();
3742 $hookmanager->executeHooks('printFieldListWhere', $parameters, $this); // Note that $action and $object may have been modified by hook
3743 $sql .= $hookmanager->resPrint;
3744 $resql = $this->db->query($sql);
3745 if ($resql) {
3746 // This assignment in condition is not a bug. It allows walking the results.
3747 while ($obj = $this->db->fetch_object($resql)) {
3748 $this->nb["proposals"] = $obj->nb;
3749 }
3750 $this->db->free($resql);
3751 return 1;
3752 } else {
3753 dol_print_error($this->db);
3754 $this->error = $this->db->error();
3755 return -1;
3756 }
3757 }
3758
3759
3767 public function getNextNumRef($soc)
3768 {
3769 global $conf, $langs;
3770 $langs->load("propal");
3771
3772 $classname = getDolGlobalString('PROPALE_ADDON');
3773
3774 if (!empty($classname)) {
3775 $mybool = false;
3776
3777 $file = $classname.".php";
3778
3779 // Include file with class
3780 $dirmodels = array_merge(array('/'), (array) $conf->modules_parts['models']);
3781 foreach ($dirmodels as $reldir) {
3782 $dir = dol_buildpath($reldir."core/modules/propale/");
3783
3784 // Load file with numbering class (if found)
3785 $mybool = ((bool) @include_once $dir.$file) || $mybool;
3786 }
3787
3788 if (!$mybool) {
3789 dol_print_error(null, "Failed to include file ".$file);
3790 return '';
3791 }
3792
3793 $obj = new $classname();
3794 '@phan-var-force ModeleNumRefPropales $obj';
3797 $numref = $obj->getNextValue($soc, $this);
3798
3799 if ($numref != "") {
3800 return $numref;
3801 } else {
3802 $this->error = $obj->error;
3803 //dol_print_error($db,"Propale::getNextNumRef ".$obj->error);
3804 return "";
3805 }
3806 } else {
3807 $langs->load("errors");
3808 print $langs->trans("Error")." ".$langs->trans("ErrorModuleSetupNotComplete", $langs->transnoentitiesnoconv("Proposal"));
3809 return "";
3810 }
3811 }
3812
3819 public function getTooltipContentArray($params)
3820 {
3821 global $conf, $langs, $user;
3822
3823 $langs->load('propal');
3824 $datas = [];
3825 $nofetch = !empty($params['nofetch']);
3826
3827 if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
3828 return ['optimize' => $langs->trans("Proposal")];
3829 }
3830 if ($user->hasRight('propal', 'lire')) {
3831 $datas['picto'] = img_picto('', $this->picto, '', 0, 0, 0, '', 'paddingrightonly').'<u>'.$langs->trans("Proposal").'</u>';
3832 if (isset($this->status)) {
3833 $datas['status'] = ' '.$this->getLibStatut(5);
3834 }
3835 if (!empty($this->ref)) {
3836 $datas['ref'] = '<br><b>'.$langs->trans('Ref').':</b> '.$this->ref;
3837 }
3838 if (!$nofetch) {
3839 $langs->load('companies');
3840 if (empty($this->thirdparty)) {
3841 $this->fetch_thirdparty();
3842 }
3843 $datas['customer'] = '<br><b>'.$langs->trans('Customer').':</b> '.$this->thirdparty->getNomUrl(1, '', 0, 1);
3844 }
3845 if (!empty($this->ref_customer)) {
3846 $datas['refcustomer'] = '<br><b>'.$langs->trans('RefCustomer').':</b> '.$this->ref_customer;
3847 }
3848 if (!$nofetch) {
3849 $langs->load('project');
3850 if (is_null($this->project) || (is_object($this->project) && $this->project->isEmpty())) {
3851 $res = $this->fetchProject();
3852 if ($res > 0 && $this->project instanceof Project) {
3853 $datas['project'] = '<br><b>'.$langs->trans('Project').':</b> '.$this->project->getNomUrl(1, '', 0, '1');
3854 }
3855 }
3856 }
3857 if (!empty($this->total_ht)) {
3858 $datas['amountht'] = '<br><b>'.$langs->trans('AmountHT').':</b> '.price($this->total_ht, 0, $langs, 0, -1, -1, $conf->currency);
3859 }
3860 if (!empty($this->total_tva)) {
3861 $datas['vat'] = '<br><b>'.$langs->trans('VAT').':</b> '.price($this->total_tva, 0, $langs, 0, -1, -1, $conf->currency);
3862 }
3863 if (!empty($this->total_ttc)) {
3864 $datas['amountttc'] = '<br><b>'.$langs->trans('AmountTTC').':</b> '.price($this->total_ttc, 0, $langs, 0, -1, -1, $conf->currency);
3865 }
3866 if (!empty($this->date)) {
3867 $datas['date'] = '<br><b>'.$langs->trans('Date').':</b> '.dol_print_date($this->date, 'day');
3868 }
3869 if (!empty($this->date_signature)) {
3870 $datas['datesignature'] = '<br><b>'.$langs->trans('DateSigning').':</b> '.dol_print_date($this->date_signature, 'day');
3871 }
3872 if (!empty($this->delivery_date)) {
3873 $datas['deliverydate'] = '<br><b>'.$langs->trans('DeliveryDate').':</b> '.dol_print_date($this->delivery_date, 'dayhour');
3874 }
3875 }
3876
3877 return $datas;
3878 }
3879
3891 public function getNomUrl($withpicto = 0, $option = '', $get_params = '', $notooltip = 0, $save_lastsearch_value = -1, $addlinktonotes = -1)
3892 {
3893 global $langs, $conf, $user, $hookmanager;
3894
3895 if (!empty($conf->dol_no_mouse_hover)) {
3896 $notooltip = 1; // Force disable tooltips
3897 }
3898
3899 $result = '';
3900 $params = [
3901 'id' => $this->id,
3902 'objecttype' => $this->element,
3903 'option' => $option,
3904 'nofetch' => 1,
3905 ];
3906 $classfortooltip = 'classfortooltip';
3907 $dataparams = '';
3908 if (getDolGlobalInt('MAIN_ENABLE_AJAX_TOOLTIP')) {
3909 $classfortooltip = 'classforajaxtooltip';
3910 $dataparams = ' data-params="'.dol_escape_htmltag(json_encode($params)).'"';
3911 $label = '';
3912 } else {
3913 $label = implode($this->getTooltipContentArray($params));
3914 }
3915
3916 $baseurl = '';
3917 $query = [];
3918 if ($user->hasRight('propal', 'lire')) {
3919 parse_str($get_params, $query);
3920 $query = array_merge($query, ['id' => $this->id]);
3921 if ($option == '') {
3922 $baseurl = DOL_URL_ROOT . '/comm/propal/card.php';
3923 } elseif ($option == 'compta') { // deprecated
3924 $baseurl = DOL_URL_ROOT . '/comm/propal/card.php';
3925 } elseif ($option == 'expedition') {
3926 $baseurl = DOL_URL_ROOT . '/expedition/propal.php';
3927 } elseif ($option == 'document') {
3928 $baseurl = DOL_URL_ROOT . '/comm/propal/document.php';
3929 }
3930
3931 if ($option != 'nolink') {
3932 // Add param to save lastsearch_values or not
3933 $add_save_lastsearch_values = ($save_lastsearch_value == 1 ? 1 : 0);
3934 if ($save_lastsearch_value == -1 && isset($_SERVER["PHP_SELF"]) && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) {
3935 $add_save_lastsearch_values = 1;
3936 }
3937 if ($add_save_lastsearch_values) {
3938 $query = array_merge($query, ['save_lastsearch_values' => 1]);
3939 }
3940 }
3941 }
3942 $url = dolBuildUrl($baseurl, $query);
3943
3944 $linkclose = '';
3945 if (empty($notooltip) && $user->hasRight('propal', 'lire')) {
3946 if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
3947 $label = $langs->trans("Proposal");
3948 $linkclose .= ' alt="'.dolPrintHTMLForAttribute($label).'"';
3949 }
3950 $linkclose .= ($label ? ' title="'.dolPrintHTMLForAttribute($label).'"' : ' title="tocomplete"');
3951 $linkclose .= $dataparams.' class="'.$classfortooltip.'"';
3952 }
3953
3954 $linkstart = '<a href="'.$url.'"';
3955 $linkstart .= $linkclose.'>';
3956 $linkend = '</a>';
3957
3958 $result .= $linkstart;
3959 if ($withpicto) {
3960 $result .= img_object(($notooltip ? '' : $label), ($this->picto ? $this->picto : 'generic'), (($withpicto != 2) ? 'class="paddingright"' : ''), 0, 0, $notooltip ? 0 : 1);
3961 }
3962 if ($withpicto != 2) {
3963 $result .= $this->ref;
3964 }
3965 $result .= $linkend;
3966
3967 if ($addlinktonotes >= 0) {
3968 $txttoshow = '';
3969
3970 if ($addlinktonotes == 0) {
3971 if (!empty($this->note_private) || !empty($this->note_public)) {
3972 $txttoshow = $langs->trans('ViewPrivateNote');
3973 }
3974 } elseif ($addlinktonotes == 1) {
3975 if (!empty($this->note_private)) {
3976 $txttoshow .= ($user->socid > 0 ? '' : dol_string_nohtmltag($this->note_private, 1));
3977 }
3978 } elseif ($addlinktonotes == 2) {
3979 if (!empty($this->note_public)) {
3980 $txttoshow .= dol_string_nohtmltag($this->note_public, 1);
3981 }
3982 } elseif ($addlinktonotes == 3) {
3983 if ($user->socid > 0) {
3984 if (!empty($this->note_public)) {
3985 $txttoshow .= dol_string_nohtmltag($this->note_public, 1);
3986 }
3987 } else {
3988 if (!empty($this->note_public)) {
3989 $txttoshow .= dol_string_nohtmltag($this->note_public, 1);
3990 }
3991 if (!empty($this->note_private)) {
3992 if (!empty($txttoshow)) {
3993 $txttoshow .= '<br><br>';
3994 }
3995 $txttoshow .= dol_string_nohtmltag($this->note_private, 1);
3996 }
3997 }
3998 }
3999
4000 if ($txttoshow) {
4001 $result .= ' <span class="note inline-block">';
4002 $result .= '<a href="'.DOL_URL_ROOT.'/comm/propal/note.php?id='.$this->id.'" class="classfortooltip" title="'.dol_escape_htmltag($txttoshow).'">';
4003 $result .= img_picto('', 'note');
4004 $result .= '</a>';
4005 $result .= '</span>';
4006 }
4007 }
4008
4009 global $action;
4010 $hookmanager->initHooks(array($this->element . 'dao'));
4011 $parameters = array('id' => $this->id, 'getnomurl' => &$result);
4012 $reshook = $hookmanager->executeHooks('getNomUrl', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
4013 if ($reshook > 0) {
4014 $result = $hookmanager->resPrint;
4015 } else {
4016 $result .= $hookmanager->resPrint;
4017 }
4018 return $result;
4019 }
4020
4027 public function getLinesArray($sqlforgedfilters = '')
4028 {
4029 return $this->fetch_lines(0, 0, $sqlforgedfilters);
4030 }
4031
4043 public function generateDocument($modele, $outputlangs, $hidedetails = 0, $hidedesc = 0, $hideref = 0, $moreparams = null)
4044 {
4045 $outputlangs->loadLangs(array("propale", "products"));
4046
4047 if (!dol_strlen($modele)) {
4048 $modele = 'azur';
4049
4050 if ($this->model_pdf) {
4051 $modele = $this->model_pdf;
4052 } elseif (getDolGlobalString('PROPALE_ADDON_PDF')) {
4053 $modele = getDolGlobalString('PROPALE_ADDON_PDF');
4054 }
4055 }
4056
4057 $modelpath = "core/modules/propale/doc/";
4058
4059 return $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams);
4060 }
4061
4070 public static function replaceThirdparty(DoliDB $dbs, $origin_id, $dest_id)
4071 {
4072 $tables = array(
4073 'propal'
4074 );
4075
4076 return CommonObject::commonReplaceThirdparty($dbs, $origin_id, $dest_id, $tables);
4077 }
4078
4087 public static function replaceProduct(DoliDB $db, $origin_id, $dest_id)
4088 {
4089 $tables = array(
4090 'propaldet'
4091 );
4092
4093 return CommonObject::commonReplaceProduct($db, $origin_id, $dest_id, $tables);
4094 }
4095
4103 public function getKanbanView($option = '', $arraydata = null)
4104 {
4105 global $langs;
4106
4107 $selected = (empty($arraydata['selected']) ? 0 : $arraydata['selected']);
4108
4109 $return = '<div class="box-flex-item box-flex-grow-zero">';
4110 $return .= '<div class="info-box info-box-sm">';
4111 $return .= '<div class="info-box-icon bg-infobox-action">';
4112 $return .= img_picto('', $this->picto);
4113 $return .= '</div>';
4114 $return .= '<div class="info-box-content">';
4115 $return .= '<span class="info-box-ref inline-block tdoverflowmax150 valignmiddle">' . $this->getNomUrl() . '</span>';
4116 if ($selected >= 0) {
4117 $return .= '<input id="cb'.$this->id.'" class="flat checkforselect fright" type="checkbox" name="toselect[]" value="'.$this->id.'"'.($selected ? ' checked="checked"' : '').'>';
4118 }
4119 if (!empty($arraydata['projectlink'])) {
4120 $return .= '<span class="info-box-ref"> | '.$arraydata['projectlink'].'</span>';
4121 }
4122 $return .= '<br>';
4123 if (is_object($this->thirdparty)) {
4124 $return .= '<div class="info-box-ref tdoverflowmax150">'.$this->thirdparty->getNomUrl(1).'</div>';
4125 }
4126 $return .= '<span class="info-box-label amount" title="'.$langs->trans("AmountHT").'">'.price($this->total_ht).'</span>';
4127 if (!empty($arraydata['authorlink'])) {
4128 $return .= ' &nbsp; <span class="info-box-label">'.$arraydata['authorlink'].'</span>';
4129 }
4130 $return .= '<br><div class="info-box-status">'.$this->getLibStatut(3).'</div>';
4131 $return .= '</div>';
4132 $return .= '</div>';
4133 $return .= '</div>';
4134 return $return;
4135 }
4136
4147 public function setCategories($categories)
4148 {
4149 require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php';
4150 return parent::setCategoriesCommon($categories, Categorie::TYPE_PROPOSAL);
4151 }
4152}
if(! $sortfield) if(! $sortorder) $object
Definition account.php:100
$object ref
Definition info.php:90
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.
roundQtyToPackaging($qty, $packaging)
Round a quantity up to the next multiple of a packaging quantity (options PRODUCT_USE_CUSTOMER_PACKAG...
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
isLineOfObject($lineid)
Check that a line belongs to this object, using $this->table_element_line and $this->fk_element.
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)
checkActiveProductInLines($status='onsale')
Check if all products have the right status (on sale, on buy) called during validation of propal,...
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.
Class to manage absolute discounts.
Class to manage Dolibarr database access.
static getIdAndTxFromCode($dbs, $code, $date_document=0)
Get id and rate of currency from code.
static getIdFromCode($dbs, $code)
Get id of currency from code.
File of class to manage predefined price products or services by customer.
Class to manage predefined suppliers products.
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.
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...
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.
initAsSpecimen($param=array())
Initialise an instance with random values.
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...
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.
setCategories($categories)
Sets object to supplied categories.
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.
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.
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
global $mysoc
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:64
dol_delete_preview($object)
Delete all preview files linked to object instance.
dol_now($mode='gmt')
Return date for now.
setEventMessages($mesg, $mesgs, $style='mesgs', $messagekey='', $noduplicate=0, $attop=0)
Set event messages in dol_events session object.
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.
dolBuildUrl($url, $params=[], $addtoken=false)
Return path of url.
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_sanitizeFileName($str, $newstr='_', $unaccent=1, $includequotes=0, $allowdash=0)
Clean a string to use it as a file name.
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.
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.
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...
getDolCurrency()
Return the main currency ('EUR', 'USD', ...)
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_date($time, $format='', $tzoutput='auto', $outputlangs=null, $encodetooutput=false, $decorate=0)
Output date in a string format according to outputlangs (or langs if not defined).
getDolGlobalBool($key, $default=false)
Return a Dolibarr global constant boolean value.
dol_print_error($db=null, $error='', $errors=null)
Displays error message system with all the information to facilitate the diagnosis and the escalation...
getDolGlobalString($key, $default='')
Return a Dolibarr global constant string value.
getWarningDelay($module, $parmlevel1, $parmlevel2='')
Return a warning delay You can use it like this: if (getWarningDelay('module', 'paramlevel1')) It rep...
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 rate, when selling a product with vat $vatrate,...
dol_syslog($message, $level=LOG_INFO, $ident=0, $suffixinfilename='', $restricttologhandler='', $logcontext=null)
Write log message into outputs.
getEntity($element, $shared=1, $currentobject=null)
Get list of entity id to use.
dol_escape_htmltag($stringtoescape, $keepb=0, $keepn=0, $noescapetags='', $escapeonlyhtmltags=0, $cleanalsojavascript=0)
Returns text escaped for inclusion in HTML alt or title or value tags, or into values of HTML input f...
getMarginInfos($pv_ht, $remise_percent, $tva_tx, $localtax1_tx, $localtax2_tx, $fk_pa, $pa_ht)
Return an array with margins information of a line.
calcul_price_total($qty, $pu, $remise_percent_ligne, $txtva, $uselocaltax1_rate, $uselocaltax2_rate, $remise_percent_global, $price_base_type, $info_bits, $type, $seller=null, $localtaxes_array=[], $progress=100, $multicurrency_tx=1, $pu_devise=0, $multicurrency_code='')
Calculate totals (net, vat, ...) of a line.
Definition price.lib.php:90
if(getDolGlobalString( 'TAKEPOS_SHOW_CUSTOMER')) print $langs trans('Date')." left Label right Qty right Price right TotalHT right TotalTTC right right right right right right right right right centpercent right TotalHT right n right VAT right n right TotalVAT right n No sujeto a RE IRPF right TotalLT1 right n right TotalLT2 right n right TotalTTC right n takeposcustomercurrency takeposcustomercurrency takeposcustomercurrency takeposcustomercurrency right TotalTTC takeposcustomercurrency right takeposcustomercurrency n right PaymentTypeShortLIQ right SELECT p pos_change as p datep as date
Definition receipt.php:469