dolibarr 24.0.0-beta
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-2026 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-2026 MDW <mdeweerd@users.noreply.github.com>
22 * Copyright (C) 2025 William Mead <william@m34d.com>
23 * Copyright (C) 2026 Vincent de Grandpré <vincent@de-grandpre.quebec>
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; // Line Description
569 $line->vat_src_code = $remise->vat_src_code;
570 $line->tva_tx = $remise->tva_tx;
571 $line->localtax1_tx = $remise->localtax1_tx;
572 $line->localtax1_type = $remise->localtax1_type;
573 $line->localtax2_tx = $remise->localtax1_tx;
574 $line->localtax2_type = $remise->localtax1_type;
575 $line->subprice = -(float) $remise->amount_ht;
576 $line->fk_product = 0; // Product Id predefined
577 $line->qty = 1;
578 $line->remise_percent = 0;
579 $line->rang = -1;
580 $line->info_bits = 2;
581
582 $line->total_ht = -(float) $remise->amount_ht;
583 $line->total_tva = -(float) $remise->amount_tva;
584 $line->total_ttc = -(float) $remise->amount_ttc;
585 $line->total_localtax1 = -(float) $remise->total_localtax1;
586 $line->total_localtax2 = -(float) $remise->total_localtax2;
587
588 $result = $line->insert();
589 if ($result > 0) {
590 $result = $this->update_price(1);
591 if ($result > 0) {
592 $this->db->commit();
593 return 1;
594 } else {
595 $this->db->rollback();
596 return -1;
597 }
598 } else {
599 $this->setErrorsFromObject($line);
600 $this->db->rollback();
601 return -2;
602 }
603 } else {
604 $this->db->rollback();
605 return -2;
606 }
607 }
608
646 public function addline(
647 $desc,
648 $pu_ht,
649 $qty,
650 $txtva,
651 $txlocaltax1 = 0.0,
652 $txlocaltax2 = 0.0,
653 $fk_product = 0,
654 $remise_percent = 0.0,
655 $price_base_type = 'HT',
656 $pu_ttc = 0.0,
657 $info_bits = 0,
658 $type = 0,
659 $rang = -1,
660 $special_code = 0,
661 $fk_parent_line = 0,
662 $fk_fournprice = 0,
663 $pa_ht = 0,
664 $label = '',
665 $date_start = '',
666 $date_end = '',
667 $array_options = array(),
668 $fk_unit = null,
669 $origin = '',
670 $origin_id = 0,
671 $pu_ht_devise = 0,
672 $fk_remise_except = 0,
673 $noupdateafterinsertline = 0
674 ) {
675 global $mysoc, $langs;
676
677 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);
678
679 if ($this->status == self::STATUS_DRAFT) {
680 include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
681
682 // Clean parameters
683 if (empty($remise_percent)) {
684 $remise_percent = 0;
685 }
686 if (empty($qty)) {
687 $qty = 0;
688 }
689 if (empty($info_bits)) {
690 $info_bits = 0;
691 }
692 if (empty($rang)) {
693 $rang = 0;
694 }
695 if (empty($fk_parent_line) || $fk_parent_line < 0) {
696 $fk_parent_line = 0;
697 }
698
699 $remise_percent = price2num($remise_percent);
700 $qty = (float) price2num($qty, 'MS');
701 $pu_ht = price2num($pu_ht);
702 $pu_ht_devise = price2num($pu_ht_devise);
703 $pu_ttc = price2num($pu_ttc);
704 if (!preg_match('/\‍((.*)\‍)/', (string) $txtva)) {
705 $txtva = price2num($txtva); // $txtva can have format '5,1' or '5.1' or '5.1(XXX)', we must clean only if '5,1'
706 }
707 $txlocaltax1 = price2num($txlocaltax1);
708 $txlocaltax2 = price2num($txlocaltax2);
709 $pa_ht = price2num($pa_ht); // do not convert to float here, it breaks the functioning of $pa_ht is empty string
710 if ($price_base_type == 'HT') {
711 $pu = $pu_ht;
712 } else {
713 $pu = $pu_ttc;
714 }
715
716 // Check parameters
717 if ($type < 0) {
718 return -1;
719 }
720
721 if ($date_start && $date_end && $date_start > $date_end) {
722 $langs->load("errors");
723 $this->error = $langs->trans('ErrorStartDateGreaterEnd');
724 return -1;
725 }
726
727 $this->db->begin();
728
729 $product_type = $type;
730 if (!empty($fk_product) && $fk_product > 0) {
731 $product = new Product($this->db);
732 $result = $product->fetch($fk_product);
733 $product_type = $product->type;
734
735 if ($product->isStockManaged() && $product->stock_reel < $qty && getDolGlobalString('STOCK_MUST_BE_ENOUGH_FOR_PROPOSAL')) {
736 $langs->load("errors");
737 $this->error = $langs->trans('ErrorStockIsNotEnoughToAddProductOnProposal', $product->ref);
738 $this->db->rollback();
739 return -3;
740 }
741 }
742
743 // Calculation of the gross total (TTC) and VAT for the line from qty, pu, remise_percent and txtva
744 // VERY IMPORTANT: It's at the time of line insertion that we must store the net, VAT, and gross amounts,
745 // and this is done at the line level, which has its own VAT rate
746
747 $localtaxes_type = getLocalTaxesFromRate($txtva, 0, $this->thirdparty, $mysoc);
748
749 if (getDolGlobalString('PRODUCT_USE_CUSTOMER_PACKAGING')) {
750 $tmpproduct = new Product($this->db);
751 $result = $tmpproduct->fetch($fk_product);
752 if (abs((float) $qty) < $tmpproduct->packaging) {
753 $qty = (float) $tmpproduct->packaging;
754 setEventMessages($langs->trans('QtyRecalculatedWithPackaging'), null, 'warnings');
755 } else {
756 if (!empty($tmpproduct->packaging) && (float) price2num(fmod((float) $qty, (float) $tmpproduct->packaging), 'MS')) {
757 $coeff = intval(abs((float) $qty) / $tmpproduct->packaging) + 1;
758 $qty = price2num((float) $tmpproduct->packaging * $coeff, 'MS');
759 setEventMessages($langs->trans('QtyRecalculatedWithPackaging'), null, 'warnings');
760 }
761 }
762 }
763
764 // Clean vat code
765 $reg = array();
766 $vat_src_code = '';
767 if (preg_match('/\‍((.*)\‍)/', $txtva, $reg)) {
768 $vat_src_code = $reg[1];
769 $txtva = preg_replace('/\s*\‍(.*\‍)/', '', $txtva); // Remove code into vatrate.
770 }
771
772 $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);
773
774 $total_ht = $tabprice[0];
775 $total_tva = $tabprice[1];
776 $total_ttc = $tabprice[2];
777 $total_localtax1 = $tabprice[9];
778 $total_localtax2 = $tabprice[10];
779 $pu_ht = $tabprice[3];
780 $pu_tva = $tabprice[4];
781 $pu_ttc = $tabprice[5];
782
783 // MultiCurrency
784 $multicurrency_total_ht = $tabprice[16];
785 $multicurrency_total_tva = $tabprice[17];
786 $multicurrency_total_ttc = $tabprice[18];
787 $pu_ht_devise = $tabprice[19];
788
789 // Rang to use
790 $ranktouse = $rang;
791 if (empty($ranktouse) || $ranktouse == -1) {
792 $rangmax = $this->line_max($fk_parent_line);
793 $ranktouse = $rangmax + 1;
794 }
795
796 // Insert line
797 $this->line = new PropaleLigne($this->db);
798
799 $this->line->context = $this->context;
800
801 $this->line->fk_propal = $this->id;
802 $this->line->label = $label;
803 $this->line->desc = $desc;
804 $this->line->qty = (float) $qty;
805
806 $this->line->vat_src_code = $vat_src_code;
807 $this->line->tva_tx = $txtva;
808 $this->line->localtax1_tx = ($total_localtax1 ? $localtaxes_type[1] : 0);
809 $this->line->localtax2_tx = ($total_localtax2 ? $localtaxes_type[3] : 0);
810 $this->line->localtax1_type = empty($localtaxes_type[0]) ? '' : $localtaxes_type[0];
811 $this->line->localtax2_type = empty($localtaxes_type[2]) ? '' : $localtaxes_type[2];
812 $this->line->fk_product = $fk_product;
813 $this->line->product_type = $type;
814 $this->line->fk_remise_except = $fk_remise_except;
815 $this->line->remise_percent = $remise_percent;
816 $this->line->subprice = (float) $pu_ht;
817 // Persist the original entry mode of the line so updateline() can preserve it later.
818 $this->line->subprice_ttc = ($price_base_type === 'TTC') ? (float) $pu_ttc : 0;
819 $this->line->rang = $ranktouse;
820 $this->line->info_bits = $info_bits;
821 $this->line->total_ht = (float) $total_ht;
822 $this->line->total_tva = (float) $total_tva;
823 $this->line->total_localtax1 = (float) $total_localtax1;
824 $this->line->total_localtax2 = (float) $total_localtax2;
825 $this->line->total_ttc = (float) $total_ttc;
826 $this->line->special_code = $special_code;
827 $this->line->fk_parent_line = $fk_parent_line;
828 $this->line->fk_unit = $fk_unit;
829
830 $this->line->date_start = $date_start;
831 $this->line->date_end = $date_end;
832
833 $this->line->fk_fournprice = $fk_fournprice;
834 $this->line->pa_ht = $pa_ht;
835
836 $this->line->origin_id = $origin_id;
837 $this->line->origin = $origin;
838
839 // Multicurrency
840 $this->line->fk_multicurrency = $this->fk_multicurrency;
841 $this->line->multicurrency_code = $this->multicurrency_code;
842 $this->line->multicurrency_subprice = (float) $pu_ht_devise;
843 $this->line->multicurrency_total_ht = (float) $multicurrency_total_ht;
844 $this->line->multicurrency_total_tva = (float) $multicurrency_total_tva;
845 $this->line->multicurrency_total_ttc = (float) $multicurrency_total_ttc;
846
847 // Set the line as optional
848 if (empty($qty) && empty($special_code)) {
849 $this->line->special_code = 3;
850 }
851
852 if (is_array($array_options) && count($array_options) > 0) {
853 $this->line->array_options = $array_options;
854 }
855
856 $result = $this->line->insert();
857 if ($result > 0) {
858 if (!isset($this->context['createfromclone'])) {
859 if (!empty($fk_parent_line)) {
860 // Always reorder if child line
861 $this->line_order(true, 'DESC');
862 } elseif ($ranktouse > 0 && $ranktouse <= count($this->lines)) {
863 // Update all rank of all other lines starting from the same $ranktouse
864 $linecount = count($this->lines);
865 for ($ii = $ranktouse; $ii <= $linecount; $ii++) {
866 $this->updateRangOfLine($this->lines[$ii - 1]->id, $ii + 1);
867 }
868 }
869
870 $this->lines[] = $this->line;
871 } else {
872 // Loop on all lines of parent object
873 foreach ($this->lines as $tmpline) {
874 if ($tmpline->id == $origin_id && $tmpline->element == $origin) {
875 $this->line->extraparams = $tmpline->extraparams;
876 $this->line->setExtraParameters();
877 }
878 }
879 }
880
881 // Update denormalized fields at the order level
882 if (empty($noupdateafterinsertline)) {
883 $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.
884 }
885
886 if ($result > 0) {
887 $this->db->commit();
888 return $this->line->id;
889 } else {
890 $this->error = $this->db->error();
891 $this->db->rollback();
892 return -1;
893 }
894 } else {
895 $this->setErrorsFromObject($this->line);
896 $this->db->rollback();
897 return -2;
898 }
899 } else {
900 dol_syslog(get_class($this)."::addline status of proposal must be Draft to allow use of ->addline()", LOG_ERR);
901 return -3;
902 }
903 }
904
905
935 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)
936 {
937 global $mysoc, $langs;
938
939 dol_syslog(get_class($this)."::updateLine rowid=$rowid, pu=$pu, qty=$qty, remise_percent=$remise_percent,
940 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");
941 include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
942
943 // Clean parameters
944 $remise_percent = price2num($remise_percent);
945 $qty = (float) price2num($qty);
946 $pu = price2num($pu);
947 $pu_ht_devise = price2num($pu_ht_devise);
948 if (!preg_match('/\‍((.*)\‍)/', (string) $txtva)) {
949 $txtva = price2num($txtva); // $txtva can have format '5.0(XXX)' or '5'
950 }
951 $txlocaltax1 = price2num($txlocaltax1);
952 $txlocaltax2 = price2num($txlocaltax2);
953 $pa_ht = price2num($pa_ht); // do not convert to float here, it breaks the functioning of $pa_ht_isemptystring
954 if (empty($qty) && empty($special_code)) {
955 $special_code = 3; // Set option tag
956 }
957 if (!empty($qty) && $special_code == 3) {
958 $special_code = 0; // Remove option tag
959 }
960 if (empty($type)) {
961 $type = 0;
962 }
963
964 if ($date_start && $date_end && $date_start > $date_end) {
965 $langs->load("errors");
966 $this->error = $langs->trans('ErrorStartDateGreaterEnd');
967 return -1;
968 }
969
970 if ($this->status == self::STATUS_DRAFT) {
971 $this->db->begin();
972
973 // Calculation of the gross total (TTC) and VAT for the line from qty, pu, remise_percent and txtva
974 // VERY IMPORTANT: It's at the time of line insertion that we must store the net, VAT, and gross amounts,
975 // and this is done at the line level, which has its own VAT rate
976
977 $localtaxes_type = getLocalTaxesFromRate($txtva, 0, $this->thirdparty, $mysoc);
978
979 // Clean vat code
980 $reg = array();
981 $vat_src_code = '';
982 if (preg_match('/\‍((.*)\‍)/', $txtva, $reg)) {
983 $vat_src_code = $reg[1];
984 $txtva = preg_replace('/\s*\‍(.*\‍)/', '', $txtva); // Remove code into vatrate.
985 }
986
987 // TODO Implement if (getDolGlobalInt('MAIN_UNIT_PRICE_WITH_TAX_IS_FOR_ALL_TAXES')) ?
988
989 $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);
990 $total_ht = $tabprice[0];
991 $total_tva = $tabprice[1];
992 $total_ttc = $tabprice[2];
993 $total_localtax1 = $tabprice[9];
994 $total_localtax2 = $tabprice[10];
995 $pu_ht = $tabprice[3];
996 $pu_tva = $tabprice[4];
997 $pu_ttc = $tabprice[5];
998
999 // MultiCurrency
1000 $multicurrency_total_ht = $tabprice[16];
1001 $multicurrency_total_tva = $tabprice[17];
1002 $multicurrency_total_ttc = $tabprice[18];
1003 $pu_ht_devise = $tabprice[19];
1004
1005 // Fetch current line from the database and then clone the object and set it in $oldline property
1006 $line = new PropaleLigne($this->db);
1007 $line->fetch($rowid);
1008
1009 $staticline = clone $line;
1010
1011 $line->oldline = $staticline;
1012 $this->line = $line;
1013 $this->line->context = $this->context;
1014 $this->line->rang = $rang;
1015
1016 // Reorder if fk_parent_line change
1017 if (!empty($fk_parent_line) && !empty($staticline->fk_parent_line) && $fk_parent_line != $staticline->fk_parent_line) {
1018 $rangmax = $this->line_max($fk_parent_line);
1019 $this->line->rang = $rangmax + 1;
1020 }
1021
1022 if (getDolGlobalString('PRODUCT_USE_CUSTOMER_PACKAGING')) {
1023 if (abs((float) $qty) < $this->line->packaging) {
1024 $qty = $this->line->packaging;
1025 setEventMessage($langs->trans('QtyRecalculatedWithPackaging'), 'warnings');
1026 } else {
1027 if (!empty($this->line->packaging)
1028 && is_numeric($this->line->packaging)
1029 && (float) $this->line->packaging > 0
1030 && (float) price2num(fmod((float) $qty, (float) $this->line->packaging), 'MS')) {
1031 $coeff = intval(abs((float) $qty) / $this->line->packaging) + 1;
1032 $qty = $this->line->packaging * $coeff;
1033 setEventMessage($langs->trans('QtyRecalculatedWithPackaging'), 'warnings');
1034 }
1035 }
1036 }
1037
1038 $this->line->id = $rowid;
1039 $this->line->label = $label;
1040 $this->line->desc = $desc;
1041 $this->line->qty = $qty;
1042 $this->line->product_type = $type;
1043 $this->line->vat_src_code = $vat_src_code;
1044 $this->line->tva_tx = $txtva;
1045 $this->line->localtax1_tx = $txlocaltax1;
1046 $this->line->localtax2_tx = $txlocaltax2;
1047 $this->line->localtax1_type = empty($localtaxes_type[0]) ? '' : $localtaxes_type[0];
1048 $this->line->localtax2_type = empty($localtaxes_type[2]) ? '' : $localtaxes_type[2];
1049 $this->line->remise_percent = $remise_percent;
1050 $this->line->subprice = (float) $pu_ht;
1051 // Persist the original entry mode of the line so a no-op edit can preserve it later.
1052 $this->line->subprice_ttc = ($price_base_type === 'TTC') ? (float) $pu_ttc : 0;
1053 $this->line->info_bits = $info_bits;
1054
1055 $this->line->total_ht = (float) $total_ht;
1056 $this->line->total_tva = (float) $total_tva;
1057 $this->line->total_localtax1 = (float) $total_localtax1;
1058 $this->line->total_localtax2 = (float) $total_localtax2;
1059 $this->line->total_ttc = (float) $total_ttc;
1060 $this->line->special_code = $special_code;
1061 $this->line->fk_parent_line = $fk_parent_line;
1062 $this->line->skip_update_total = $skip_update_total;
1063 $this->line->fk_unit = $fk_unit;
1064
1065 $this->line->fk_fournprice = $fk_fournprice;
1066 $this->line->pa_ht = $pa_ht;
1067
1068 $this->line->date_start = $date_start;
1069 $this->line->date_end = $date_end;
1070
1071 if (is_array($array_options) && count($array_options) > 0) {
1072 // We replace values in this->line->array_options only for entries defined into $array_options
1073 foreach ($array_options as $key => $value) {
1074 $this->line->array_options[$key] = $array_options[$key];
1075 }
1076 }
1077
1078 // Multicurrency
1079 $this->line->multicurrency_subprice = (float) $pu_ht_devise;
1080 $this->line->multicurrency_total_ht = (float) $multicurrency_total_ht;
1081 $this->line->multicurrency_total_tva = (float) $multicurrency_total_tva;
1082 $this->line->multicurrency_total_ttc = (float) $multicurrency_total_ttc;
1083
1084 $result = $this->line->update($notrigger);
1085 if ($result > 0) {
1086 // Reorder if child line
1087 if (!empty($fk_parent_line)) {
1088 $this->line_order(true, 'DESC');
1089 }
1090
1091 $this->update_price(1, 'auto');
1092
1093 // $this is Propal
1094 // $this->fk_propal = $this->id;
1095 // $this->rowid = $rowid;
1096
1097 $this->db->commit();
1098 return $result;
1099 } else {
1100 $this->setErrorsFromObject($this->line);
1101 $this->db->rollback();
1102 return -1;
1103 }
1104 } else {
1105 dol_syslog(get_class($this)."::updateline Erreur -2 Propal en mode incompatible pour cette action");
1106 return -2;
1107 }
1108 }
1109
1110
1118 public function deleteLine($lineid, $id = 0)
1119 {
1120 global $user;
1121
1122 if ($this->status == self::STATUS_DRAFT) {
1123 $this->db->begin();
1124
1125 $line = new PropaleLigne($this->db);
1126
1127 $line->context = $this->context;
1128
1129 // Load data
1130 $line->fetch($lineid);
1131
1132 if ($id > 0 && $line->fk_propal != $id) {
1133 $this->error = 'ErrorLineIDDoesNotMatchWithObjectID';
1134 return -1;
1135 }
1136
1137 // Memorize previous line for triggers
1138 $staticline = clone $line;
1139 $line->oldline = $staticline;
1140
1141 if ($line->delete($user) > 0) {
1142 $this->update_price(1);
1143
1144 $this->db->commit();
1145 return 1;
1146 } else {
1147 $this->setErrorsFromObject($line);
1148 $this->db->rollback();
1149 return -1;
1150 }
1151 } else {
1152 $this->error = 'ErrorDeleteLineNotAllowedByObjectStatus';
1153 return -2;
1154 }
1155 }
1156
1157
1166 public function create($user, $notrigger = 0)
1167 {
1168 global $mysoc;
1169
1170 $error = 0;
1171
1172 $now = dol_now();
1173
1174 // Clean parameters
1175 if (empty($this->date)) {
1176 $this->date = $this->datep;
1177 }
1178 $this->fin_validite = $this->date + ($this->duree_validite * 24 * 3600);
1179 if (empty($this->availability_id)) {
1180 $this->availability_id = 0;
1181 }
1182 if (empty($this->demand_reason_id)) {
1183 $this->demand_reason_id = 0;
1184 }
1185
1186 // Multicurrency (test on $this->multicurrency_tx because we should take the default rate only if not using origin rate)
1187 if (!empty($this->multicurrency_code) && empty($this->multicurrency_tx)) {
1188 list($this->fk_multicurrency, $this->multicurrency_tx) = MultiCurrency::getIdAndTxFromCode($this->db, $this->multicurrency_code, $this->date);
1189 } else {
1190 $this->fk_multicurrency = MultiCurrency::getIdFromCode($this->db, $this->multicurrency_code);
1191 }
1192 if (empty($this->fk_multicurrency)) {
1193 $this->multicurrency_code = getDolCurrency();
1194 $this->fk_multicurrency = 0;
1195 $this->multicurrency_tx = 1;
1196 }
1197 // setEntity will set entity with the right value if empty or change it for the right value if multicompany module is active
1198 $this->entity = setEntity($this);
1199
1200 // Set tmp vars
1201 $delivery_date = $this->delivery_date;
1202
1203 dol_syslog(get_class($this)."::create");
1204
1205 // Check parameters
1206 $result = $this->fetch_thirdparty();
1207 if ($result < 0) {
1208 $this->error = "Failed to fetch company";
1209 dol_syslog(get_class($this)."::create ".$this->error, LOG_ERR);
1210 return -3;
1211 }
1212
1213 // Check parameters
1214 if (!empty($this->ref)) { // We check that ref is not already used
1215 $result = self::isExistingObject($this->element, 0, $this->ref); // Check ref is not yet used
1216 if ($result > 0) {
1217 $this->error = 'ErrorRefAlreadyExists';
1218 dol_syslog(get_class($this)."::create ".$this->error, LOG_WARNING);
1219 $this->db->rollback();
1220 return -1;
1221 }
1222 }
1223
1224 if (empty($this->date)) {
1225 $this->error = "Date of proposal is required";
1226 dol_syslog(get_class($this)."::create ".$this->error, LOG_ERR);
1227 return -4;
1228 }
1229
1230
1231 $this->db->begin();
1232
1233 // Insert into database
1234 $sql = "INSERT INTO ".MAIN_DB_PREFIX.$this->table_element." (";
1235 $sql .= "fk_soc";
1236 $sql .= ", price";
1237 $sql .= ", total_tva";
1238 $sql .= ", total_ttc";
1239 $sql .= ", datep";
1240 $sql .= ", datec";
1241 $sql .= ", ref";
1242 $sql .= ", fk_user_author";
1243 $sql .= ", note_private";
1244 $sql .= ", note_public";
1245 $sql .= ", model_pdf";
1246 $sql .= ", fin_validite";
1247 $sql .= ", fk_cond_reglement";
1248 $sql .= ", deposit_percent";
1249 $sql .= ", fk_mode_reglement";
1250 $sql .= ", fk_account";
1251 $sql .= ", ref_client";
1252 $sql .= ", ref_ext";
1253 $sql .= ", date_livraison";
1254 $sql .= ", fk_shipping_method";
1255 $sql .= ", fk_warehouse";
1256 $sql .= ", fk_availability";
1257 $sql .= ", fk_input_reason";
1258 $sql .= ", fk_projet";
1259 $sql .= ", fk_incoterms";
1260 $sql .= ", location_incoterms";
1261 $sql .= ", entity";
1262 $sql .= ", fk_multicurrency";
1263 $sql .= ", multicurrency_code";
1264 $sql .= ", multicurrency_tx";
1265 $sql .= ") ";
1266 $sql .= " VALUES (";
1267 $sql .= $this->socid;
1268 $sql .= ", 0";
1269 $sql .= ", 0";
1270 $sql .= ", 0";
1271 $sql .= ", '".$this->db->idate($this->date)."'";
1272 $sql .= ", '".$this->db->idate($now)."'";
1273 $sql .= ", '(PROV)'";
1274 $sql .= ", ".($user->id > 0 ? ((int) $user->id) : "NULL");
1275 $sql .= ", '".$this->db->escape($this->note_private)."'";
1276 $sql .= ", '".$this->db->escape($this->note_public)."'";
1277 $sql .= ", '".$this->db->escape($this->model_pdf)."'";
1278 $sql .= ", ".($this->fin_validite != '' ? "'".$this->db->idate($this->fin_validite)."'" : "NULL");
1279 $sql .= ", ".($this->cond_reglement_id > 0 ? ((int) $this->cond_reglement_id) : 'NULL');
1280 $sql .= ", ".(!empty($this->deposit_percent) ? "'".$this->db->escape($this->deposit_percent)."'" : 'NULL');
1281 $sql .= ", ".($this->mode_reglement_id > 0 ? ((int) $this->mode_reglement_id) : 'NULL');
1282 $sql .= ", ".($this->fk_account > 0 ? ((int) $this->fk_account) : 'NULL');
1283 $sql .= ", '".$this->db->escape((string) $this->ref_client)."'";
1284 $sql .= ", '".$this->db->escape((string) $this->ref_ext)."'";
1285 $sql .= ", ".(!isDolTms($delivery_date) ? "NULL" : "'".$this->db->idate($delivery_date)."'");
1286 $sql .= ", ".($this->shipping_method_id > 0 ? $this->shipping_method_id : 'NULL');
1287 $sql .= ", ".($this->warehouse_id > 0 ? $this->warehouse_id : 'NULL');
1288 $sql .= ", ".((int) $this->availability_id);
1289 $sql .= ", ".((int) $this->demand_reason_id);
1290 $sql .= ", ".($this->fk_project ? $this->fk_project : "null");
1291 $sql .= ", ".(int) $this->fk_incoterms;
1292 $sql .= ", '".$this->db->escape($this->location_incoterms)."'";
1293 $sql .= ", ".(int) $this->entity;
1294 $sql .= ", ".(int) $this->fk_multicurrency;
1295 $sql .= ", '".$this->db->escape($this->multicurrency_code)."'";
1296 $sql .= ", ".(float) $this->multicurrency_tx;
1297 $sql .= ")";
1298
1299 dol_syslog(get_class($this)."::create", LOG_DEBUG);
1300 $resql = $this->db->query($sql);
1301 if ($resql) {
1302 $this->id = $this->db->last_insert_id(MAIN_DB_PREFIX.$this->table_element);
1303
1304 if ($this->id) {
1305 $this->ref = '(PROV'.$this->id.')';
1306 $sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element." SET ref='".$this->db->escape($this->ref)."' WHERE rowid=".((int) $this->id);
1307
1308 dol_syslog(get_class($this)."::create", LOG_DEBUG);
1309 $resql = $this->db->query($sql);
1310 if (!$resql) {
1311 $error++;
1312 }
1313
1314 if (!empty($this->linkedObjectsIds) && empty($this->linked_objects)) { // To use new linkedObjectsIds instead of old linked_objects
1315 $this->linked_objects = $this->linkedObjectsIds; // TODO Replace linked_objects with linkedObjectsIds
1316 }
1317
1318 // Add object linked
1319 if (!$error && !empty($this->linked_objects) && is_array($this->linked_objects)) {
1320 foreach ($this->linked_objects as $origin => $tmp_origin_id) {
1321 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, ...))
1322 foreach ($tmp_origin_id as $origin_id) {
1323 $ret = $this->add_object_linked($origin, $origin_id);
1324 if (!$ret) {
1325 $this->error = $this->db->lasterror();
1326 $error++;
1327 }
1328 }
1329 } else { // Old behaviour, if linked_object has only one link per type, so is something like array('contract'=>id1))
1330 $origin_id = $tmp_origin_id;
1331 $ret = $this->add_object_linked($origin, $origin_id);
1332 if (!$ret) {
1333 $this->error = $this->db->lasterror();
1334 $error++;
1335 }
1336 }
1337 }
1338 }
1339
1340 /*
1341 * Insert products detail in database
1342 */
1343 if (!$error) {
1344 $fk_parent_line = 0;
1345 $num = count($this->lines);
1346
1347 for ($i = 0; $i < $num; $i++) {
1348 if (!is_object($this->lines[$i])) { // If this->lines is not array of objects, coming from REST API
1349 // Convert into object this->lines[$i].
1350 $line = (object) $this->lines[$i];
1351 } else {
1352 $line = $this->lines[$i];
1353 }
1354 // Reset fk_parent_line for line that are not child lines or special product
1355 if (($line->product_type != 9 && empty($line->fk_parent_line)) || $line->product_type == 9) {
1356 $fk_parent_line = 0;
1357 }
1358 // Complete vat rate with code
1359 $vatrate = $line->tva_tx;
1360 if ($line->vat_src_code && !preg_match('/\‍(.*\‍)/', $vatrate)) {
1361 $vatrate .= ' ('.$line->vat_src_code.')';
1362 }
1363
1364 if (getDolGlobalString('MAIN_CREATEFROM_KEEP_LINE_ORIGIN_INFORMATION')) {
1365 $originid = $line->origin_id;
1366 $origintype = empty($line->origin_type) ? $line->origin : $line->origin_type;
1367 } else { // old but bugged version (we store id of line and type of parent object)
1368 $originid = $line->id;
1369 $origintype = $this->element;
1370 }
1371
1372 $result = $this->addline(
1373 $line->desc,
1374 $line->subprice,
1375 $line->qty,
1376 $vatrate,
1377 $line->localtax1_tx,
1378 $line->localtax2_tx,
1379 $line->fk_product,
1380 $line->remise_percent,
1381 'HT',
1382 0,
1383 $line->info_bits,
1384 $line->product_type,
1385 $line->rang,
1386 $line->special_code,
1387 $fk_parent_line,
1388 $line->fk_fournprice,
1389 $line->pa_ht,
1390 $line->label,
1391 $line->date_start,
1392 $line->date_end,
1393 $line->array_options,
1394 $line->fk_unit,
1395 (string) $origintype,
1396 $originid,
1397 0,
1398 0,
1399 1
1400 );
1401
1402 if ($result < 0) {
1403 $error++;
1404 $this->error = $this->db->error;
1405 dol_print_error($this->db);
1406 break;
1407 }
1408
1409 // Set the id on created row
1410 $line->id = $result;
1411
1412 // Defined the new fk_parent_line
1413 if ($result > 0 && $line->product_type == 9) {
1414 $fk_parent_line = $result;
1415 }
1416 }
1417 }
1418
1419 if (!$error) {
1420 // Update denormalized data
1421 $resql = $this->update_price(1, 'auto', 0, $mysoc);
1422 if ($resql) {
1423 // Actions on extra fields
1424 $result = $this->insertExtraFields();
1425 if ($result < 0) {
1426 $error++;
1427 }
1428
1429 if (!$error && !$notrigger) {
1430 // Call trigger
1431 $result = $this->call_trigger('PROPAL_CREATE', $user);
1432 if ($result < 0) {
1433 $error++;
1434 }
1435 // End call triggers
1436 }
1437 } else {
1438 $this->error = $this->db->lasterror();
1439 $error++;
1440 }
1441 }
1442 } else {
1443 $this->error = $this->db->lasterror();
1444 $error++;
1445 }
1446
1447 if (!$error) {
1448 $this->db->commit();
1449 dol_syslog(get_class($this)."::create done id=".$this->id);
1450 return $this->id;
1451 } else {
1452 $this->db->rollback();
1453 return -2;
1454 }
1455 } else {
1456 $this->error = $this->db->lasterror();
1457 $this->db->rollback();
1458 return -1;
1459 }
1460 }
1461
1472 public function createFromClone(User $user, $socid = 0, $forceentity = null, $update_prices = false, $update_desc = false)
1473 {
1474 global $conf, $hookmanager, $mysoc;
1475
1476 dol_include_once('/projet/class/project.class.php');
1477
1478 $error = 0;
1479 $now = dol_now();
1480
1481 dol_syslog(__METHOD__, LOG_DEBUG);
1482
1483 $object = new self($this->db);
1484
1485 $this->db->begin();
1486
1487 // Load source object
1488 $object->fetch($this->id);
1489
1490 $objsoc = new Societe($this->db);
1491
1492 // Change socid if needed
1493 if (!empty($socid) && $socid != $object->socid) {
1494 if ($objsoc->fetch($socid) > 0) {
1495 $object->socid = $objsoc->id;
1496 $object->cond_reglement_id = (!empty($objsoc->cond_reglement_id) ? $objsoc->cond_reglement_id : 0);
1497 $object->deposit_percent = (!empty($objsoc->deposit_percent) ? $objsoc->deposit_percent : null);
1498 $object->mode_reglement_id = (!empty($objsoc->mode_reglement_id) ? $objsoc->mode_reglement_id : 0);
1499 $object->fk_delivery_address = 0;
1500
1501 /*if (isModEnabled('project'))
1502 {
1503 $project = new Project($db);
1504 if ($this->fk_project > 0 && $project->fetch($this->fk_project)) {
1505 if ($project->socid <= 0) $clonedObj->fk_project = $this->fk_project;
1506 else $clonedObj->fk_project = '';
1507 } else {
1508 $clonedObj->fk_project = '';
1509 }
1510 }*/
1511 $object->fk_project = 0; // A cloned proposal is set by default to no project.
1512 }
1513
1514 // reset ref_client
1515 if (!getDolGlobalString('MAIN_KEEP_REF_CUSTOMER_ON_CLONING')) {
1516 $object->ref_client = '';
1517 $object->ref_customer = '';
1518 }
1519
1520 // TODO Change product price if multi-prices
1521 } else {
1522 $objsoc->fetch($object->socid);
1523 }
1524
1525 // update prices
1526 if ($update_prices === true || $update_desc === true) {
1527 if ($objsoc->id > 0 && !empty($object->lines)) {
1528 if ($update_prices === true && getDolGlobalString('PRODUIT_CUSTOMER_PRICES')) {
1529 // If price per customer
1530 require_once DOL_DOCUMENT_ROOT . '/product/class/productcustomerprice.class.php';
1531 }
1532
1533 foreach ($object->lines as $line) {
1534 $line->id = 0;
1535
1536 if ($line->fk_product > 0) {
1537 $prod = new Product($this->db);
1538 $res = $prod->fetch($line->fk_product);
1539 if ($res > 0) {
1540 if ($update_prices === true) {
1541 $pu_ht = $prod->price;
1542 $tva_tx = (string) get_default_tva($mysoc, $objsoc, $prod->id);
1543 $remise_percent = $objsoc->remise_percent;
1544
1545 if (getDolGlobalString('PRODUIT_MULTIPRICES') && $objsoc->price_level > 0) {
1546 $pu_ht = $prod->multiprices[$objsoc->price_level];
1547 if (getDolGlobalString('PRODUIT_MULTIPRICES_USE_VAT_PER_LEVEL')) { // using this option is a bug. kept for backward compatibility
1548 if (isset($prod->multiprices_tva_tx[$objsoc->price_level])) {
1549 $tva_tx = (string) $prod->multiprices_tva_tx[$objsoc->price_level];
1550 }
1551 }
1552 } elseif (getDolGlobalString('PRODUIT_CUSTOMER_PRICES')) {
1553 $prodcustprice = new ProductCustomerPrice($this->db);
1554 $filter = array('t.fk_product' => (string) $prod->id, 't.fk_soc' => (string) $objsoc->id);
1555 $result = $prodcustprice->fetchAll('', '', 0, 0, $filter);
1556 if ($result) {
1557 // If there is some prices specific to the customer
1558 if (count($prodcustprice->lines) > 0) {
1559 $date_now = (int) floor(dol_now() / 86400) * 86400; // date without hours
1560 foreach ($prodcustprice->lines as $k => $custprice_line) {
1561 if ($custprice_line->date_begin <= $date_now && (empty($custprice_line->date_end) || $date_now <= $custprice_line->date_end)) {
1562 $pu_ht = price($custprice_line->price);
1563 $tva_tx = ($custprice_line->default_vat_code ? $custprice_line->tva_tx . ' (' . $custprice_line->default_vat_code . ' )' : $custprice_line->tva_tx);
1564 if ($custprice_line->default_vat_code && !preg_match('/\‍(.*\‍)/', $tva_tx)) {
1565 $tva_tx .= ' (' . $custprice_line->default_vat_code . ')';
1566 }
1567 $remise_percent = $custprice_line->discount_percent;
1568 break;
1569 }
1570 }
1571 }
1572 }
1573 }
1574
1575 $line->subprice = $pu_ht;
1576 $line->tva_tx = $tva_tx;
1577 $line->remise_percent = $remise_percent;
1578 }
1579 if ($update_desc === true) {
1580 $line->desc = $prod->description;
1581 }
1582 }
1583 }
1584 }
1585 }
1586 }
1587
1588 $object->id = 0;
1589 $object->ref = '';
1590 $object->entity = (!empty($forceentity) ? $forceentity : $object->entity);
1591 $object->statut = self::STATUS_DRAFT;
1592 $object->status = self::STATUS_DRAFT;
1593
1594 // Clear fields
1595 $object->user_creation_id = $user->id;
1596 $object->user_validation_id = 0;
1597 $object->date = $now;
1598 $object->datep = $now; // deprecated
1599 $object->fin_validite = $object->date + (int) ($object->duree_validite * 24 * 3600);
1600 if (!getDolGlobalString('MAIN_KEEP_REF_CUSTOMER_ON_CLONING')) {
1601 $object->ref_client = '';
1602 $object->ref_customer = '';
1603 }
1604 if (getDolGlobalInt('MAIN_DONT_KEEP_NOTE_ON_CLONING') == 1) {
1605 $object->note_private = '';
1606 $object->note_public = '';
1607 }
1608 // Create clone
1609 $object->context['createfromclone'] = 'createfromclone';
1610 $result = $object->create($user);
1611 if ($result < 0) {
1613 $error++;
1614 }
1615
1616 if (!$error && !getDolGlobalInt('MAIN_IGNORE_CONTACTS_ON_CLONING')) {
1617 // copy internal contacts
1618 if ($object->copy_linked_contact($this, 'internal') < 0) {
1619 $error++;
1620 }
1621 }
1622
1623 if (!$error) {
1624 // copy external contacts if same company
1625 if ($this->socid == $object->socid) {
1626 if ($object->copy_linked_contact($this, 'external') < 0) {
1627 $error++;
1628 }
1629 }
1630 }
1631
1632 if (!$error) {
1633 // Hook of thirdparty module
1634 if (is_object($hookmanager)) {
1635 $parameters = array('objFrom' => $this, 'clonedObj' => $object);
1636 $action = '';
1637 $reshook = $hookmanager->executeHooks('createFrom', $parameters, $object, $action); // Note that $action and $object may have been modified by some hooks
1638 if ($reshook < 0) {
1639 $this->setErrorsFromObject($hookmanager);
1640 $error++;
1641 }
1642 }
1643 }
1644
1645 unset($object->context['createfromclone']);
1646
1647 // End
1648 if (!$error) {
1649 $this->db->commit();
1650 return $object->id;
1651 } else {
1652 $this->db->rollback();
1653 return -1;
1654 }
1655 }
1656
1666 public function fetch($rowid, $ref = '', $ref_ext = '', $forceentity = 0)
1667 {
1668 $sql = "SELECT p.rowid, p.ref, p.entity, p.fk_soc";
1669 $sql .= ", p.total_ttc, p.total_tva, p.localtax1, p.localtax2, p.total_ht";
1670 $sql .= ", p.datec";
1671 $sql .= ", p.date_signature as dates";
1672 $sql .= ", p.date_valid as datev";
1673 $sql .= ", p.date_cloture";
1674 $sql .= ", p.datep as dp";
1675 $sql .= ", p.fin_validite as dfv";
1676 $sql .= ", p.date_livraison as delivery_date";
1677 $sql .= ", p.model_pdf, p.last_main_doc, p.ref_client, ref_ext, p.extraparams";
1678 $sql .= ", p.note_private, p.note_public";
1679 $sql .= ", p.fk_projet as fk_project, p.fk_statut";
1680 $sql .= ", p.fk_user_author, p.fk_user_valid, p.fk_user_cloture";
1681 $sql .= ", p.fk_delivery_address";
1682 $sql .= ", p.fk_availability";
1683 $sql .= ", p.fk_input_reason";
1684 $sql .= ", p.fk_cond_reglement";
1685 $sql .= ", p.fk_mode_reglement";
1686 $sql .= ', p.fk_account';
1687 $sql .= ", p.fk_shipping_method";
1688 $sql .= ", p.fk_warehouse";
1689 $sql .= ", p.fk_incoterms, p.location_incoterms";
1690 $sql .= ", p.fk_multicurrency, p.multicurrency_code, p.multicurrency_tx, p.multicurrency_total_ht, p.multicurrency_total_tva, p.multicurrency_total_ttc";
1691 $sql .= ", p.tms as date_modification";
1692 $sql .= ", i.libelle as label_incoterms";
1693 $sql .= ", c.label as statut_label";
1694 $sql .= ", ca.code as availability_code, ca.label as availability";
1695 $sql .= ", dr.code as demand_reason_code, dr.label as demand_reason";
1696 $sql .= ", cr.code as cond_reglement_code, cr.libelle as cond_reglement, cr.libelle_facture as cond_reglement_libelle_doc, p.deposit_percent";
1697 $sql .= ", cp.code as mode_reglement_code, cp.libelle as mode_reglement";
1698 $sql .= " FROM ".MAIN_DB_PREFIX.$this->table_element." as p";
1699 $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_propalst as c ON p.fk_statut = c.id';
1700 $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_paiement as cp ON p.fk_mode_reglement = cp.id AND cp.entity IN ('.getEntity('c_paiement').')';
1701 $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').')';
1702 $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_availability as ca ON p.fk_availability = ca.rowid';
1703 $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_input_reason as dr ON p.fk_input_reason = dr.rowid';
1704 $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_incoterms as i ON p.fk_incoterms = i.rowid';
1705
1706 if (!empty($ref)) {
1707 if (!empty($forceentity)) {
1708 $sql .= " WHERE p.entity = ".(int) $forceentity; // Check only the current entity because we may have the same reference in several entities
1709 } else {
1710 $sql .= " WHERE p.entity IN (".getEntity('propal').")";
1711 }
1712 $sql .= " AND p.ref='".$this->db->escape($ref)."'";
1713 } else {
1714 // Don't use entity if you use rowid
1715 $sql .= " WHERE p.rowid = ".((int) $rowid);
1716 }
1717
1718 dol_syslog(get_class($this)."::fetch", LOG_DEBUG);
1719 $resql = $this->db->query($sql);
1720 if ($resql) {
1721 if ($this->db->num_rows($resql)) {
1722 $obj = $this->db->fetch_object($resql);
1723
1724 $this->id = $obj->rowid;
1725 $this->entity = $obj->entity;
1726
1727 $this->ref = $obj->ref;
1728 $this->ref_client = $obj->ref_client;
1729 $this->ref_customer = $obj->ref_client;
1730 $this->ref_ext = $obj->ref_ext;
1731
1732 $this->total = $obj->total_ttc; // TODO deprecated
1733 $this->total_ttc = $obj->total_ttc;
1734 $this->total_ht = $obj->total_ht;
1735 $this->total_tva = $obj->total_tva;
1736 $this->total_localtax1 = $obj->localtax1;
1737 $this->total_localtax2 = $obj->localtax2;
1738
1739 $this->socid = $obj->fk_soc;
1740 $this->thirdparty = null; // Clear if another value was already set by fetch_thirdparty
1741
1742 $this->fk_project = $obj->fk_project;
1743 $this->project = null; // Clear if another value was already set by fetchProject
1744
1745 $this->model_pdf = $obj->model_pdf;
1746 $this->last_main_doc = $obj->last_main_doc;
1747 $this->note = $obj->note_private; // TODO deprecated
1748 $this->note_private = $obj->note_private;
1749 $this->note_public = $obj->note_public;
1750
1751 $this->status = (int) $obj->fk_statut;
1752 $this->statut = $this->status; // deprecated
1753
1754 $this->datec = $this->db->jdate($obj->datec); // TODO deprecated
1755 $this->datev = $this->db->jdate($obj->datev); // TODO deprecated
1756 $this->date_creation = $this->db->jdate($obj->datec); //Creation date
1757 $this->date_validation = $this->db->jdate($obj->datev); //Validation date
1758 $this->date_modification = $this->db->jdate($obj->date_modification); // tms
1759 $this->date_signature = $this->db->jdate($obj->dates); // Signature date
1760 $this->date_cloture = $this->db->jdate($obj->date_cloture); // Closing date
1761 $this->date = $this->db->jdate($obj->dp); // Proposal date
1762 $this->datep = $this->db->jdate($obj->dp); // deprecated
1763 $this->fin_validite = $this->db->jdate($obj->dfv);
1764 $this->delivery_date = $this->db->jdate($obj->delivery_date);
1765 $this->shipping_method_id = ($obj->fk_shipping_method > 0) ? $obj->fk_shipping_method : null;
1766 $this->warehouse_id = ($obj->fk_warehouse > 0) ? $obj->fk_warehouse : null;
1767 $this->availability_id = $obj->fk_availability;
1768 $this->availability_code = $obj->availability_code;
1769 $this->availability = $obj->availability;
1770 $this->demand_reason_id = $obj->fk_input_reason;
1771 $this->demand_reason_code = $obj->demand_reason_code;
1772 $this->demand_reason = $obj->demand_reason;
1773 $this->fk_address = $obj->fk_delivery_address;
1774
1775 $this->mode_reglement_id = $obj->fk_mode_reglement;
1776 $this->mode_reglement_code = $obj->mode_reglement_code;
1777 $this->mode_reglement = $obj->mode_reglement;
1778 $this->fk_account = ($obj->fk_account > 0) ? $obj->fk_account : null;
1779 $this->cond_reglement_id = $obj->fk_cond_reglement;
1780 $this->cond_reglement_code = $obj->cond_reglement_code;
1781 $this->cond_reglement = $obj->cond_reglement;
1782 $this->cond_reglement_doc = $obj->cond_reglement_libelle_doc;
1783 $this->deposit_percent = $obj->deposit_percent;
1784
1785 $this->extraparams = !empty($obj->extraparams) ? (array) json_decode($obj->extraparams, true) : array();
1786
1787 $this->user_author_id = $obj->fk_user_author;
1788 $this->user_validation_id = $obj->fk_user_valid;
1789 $this->user_closing_id = $obj->fk_user_cloture;
1790
1791 //Incoterms
1792 $this->fk_incoterms = $obj->fk_incoterms;
1793 $this->location_incoterms = $obj->location_incoterms;
1794 $this->label_incoterms = $obj->label_incoterms;
1795
1796 // Multicurrency
1797 $this->fk_multicurrency = $obj->fk_multicurrency;
1798 $this->multicurrency_code = $obj->multicurrency_code;
1799 $this->multicurrency_tx = $obj->multicurrency_tx;
1800 $this->multicurrency_total_ht = $obj->multicurrency_total_ht;
1801 $this->multicurrency_total_tva = $obj->multicurrency_total_tva;
1802 $this->multicurrency_total_ttc = $obj->multicurrency_total_ttc;
1803
1804 // Retrieve all extrafield
1805 // fetch optionals attributes and labels
1806 $this->fetch_optionals();
1807
1808 $this->db->free($resql);
1809
1810 $this->lines = array();
1811
1812 // Lines
1813 $result = $this->fetch_lines();
1814 if ($result < 0) {
1815 return -3;
1816 }
1817
1818 return 1;
1819 }
1820
1821 $this->error = "Record Not Found";
1822 return 0;
1823 } else {
1824 $this->error = $this->db->lasterror();
1825 return -1;
1826 }
1827 }
1828
1836 public function update(User $user, $notrigger = 0)
1837 {
1838 $error = 0;
1839
1840 // Clean parameters
1841 if (isset($this->ref)) {
1842 $this->ref = trim($this->ref);
1843 }
1844 if (isset($this->ref_client)) {
1845 $this->ref_client = trim($this->ref_client);
1846 }
1847 if (isset($this->note) || isset($this->note_private)) {
1848 $this->note_private = (isset($this->note_private) ? trim($this->note_private) : trim($this->note));
1849 }
1850 if (isset($this->note_public)) {
1851 $this->note_public = trim($this->note_public);
1852 }
1853 if (isset($this->model_pdf)) {
1854 $this->model_pdf = trim($this->model_pdf);
1855 }
1856 if (isset($this->import_key)) {
1857 $this->import_key = trim($this->import_key);
1858 }
1859 if (!empty($this->duree_validite) && is_numeric($this->duree_validite)) {
1860 $this->fin_validite = $this->date + ($this->duree_validite * 24 * 3600);
1861 }
1862
1863 // Check parameters
1864 // Put here code to add control on parameters values
1865
1866 // Update request
1867 $sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element." SET";
1868 $sql = "UPDATE ".MAIN_DB_PREFIX."propal SET";
1869 $sql .= " ref = ".(isset($this->ref) ? "'".$this->db->escape($this->ref)."'" : "null").",";
1870 $sql .= " ref_client = ".(isset($this->ref_client) ? "'".$this->db->escape($this->ref_client)."'" : "null").",";
1871 $sql .= " ref_ext = ".(isset($this->ref_ext) ? "'".$this->db->escape($this->ref_ext)."'" : "null").",";
1872 $sql .= " fk_soc = ".(!empty($this->socid) ? (int) $this->socid : "null").",";
1873 $sql .= " datep = ".(strval($this->date) != '' ? "'".$this->db->idate($this->date)."'" : 'null').",";
1874 if (!empty($this->fin_validite)) {
1875 $sql .= " fin_validite = ".(strval($this->fin_validite) != '' ? "'".$this->db->idate($this->fin_validite)."'" : 'null').",";
1876 }
1877 $sql .= " date_valid = ".(strval($this->date_validation) != '' ? "'".$this->db->idate($this->date_validation)."'" : 'null').",";
1878 $sql .= " total_tva = ".(isset($this->total_tva) ? (float) $this->total_tva : "null").",";
1879 $sql .= " localtax1 = ".(isset($this->total_localtax1) ? (float) $this->total_localtax1 : "null").",";
1880 $sql .= " localtax2 = ".(isset($this->total_localtax2) ? (float) $this->total_localtax2 : "null").",";
1881 $sql .= " total_ht = ".(isset($this->total_ht) ? (float) $this->total_ht : "null").",";
1882 $sql .= " total_ttc = ".(isset($this->total_ttc) ? (float) $this->total_ttc : "null").",";
1883 $sql .= " fk_statut = ".(isset($this->status) ? (int) $this->status : "null").",";
1884 $sql .= " fk_user_author = ".(!empty($this->user_author_id) ? (int) $this->user_author_id : "null").",";
1885 $sql .= " fk_user_valid = ".(!empty($this->user_validation_id) ? (int) $this->user_validation_id : "null").",";
1886
1887 $sql .= " fk_projet = ".(!empty($this->fk_project) ? (int) $this->fk_project : "null").",";
1888 $sql .= " fk_cond_reglement = ".(!empty($this->cond_reglement_id) ? (int) $this->cond_reglement_id : "null").",";
1889 $sql .= " deposit_percent = ".(!empty($this->deposit_percent) ? "'".$this->db->escape($this->deposit_percent)."'" : "null").",";
1890 $sql .= " fk_mode_reglement = ".(!empty($this->mode_reglement_id) ? (int) $this->mode_reglement_id : "null").",";
1891 $sql .= " fk_input_reason = ".(!empty($this->demand_reason_id) ? (int) $this->demand_reason_id : "null").",";
1892 $sql .= " fk_shipping_method = ".(isset($this->shipping_method_id) ? (int) $this->shipping_method_id : "null").",";
1893 $sql .= " fk_availability = ".(!empty($this->availability_id) ? (int) $this->availability_id : "null").",";
1894 $sql .= " note_private = ".(isset($this->note_private) ? "'".$this->db->escape($this->note_private)."'" : "null").",";
1895 $sql .= " note_public = ".(isset($this->note_public) ? "'".$this->db->escape($this->note_public)."'" : "null").",";
1896 $sql .= " model_pdf = ".(isset($this->model_pdf) ? "'".$this->db->escape($this->model_pdf)."'" : "null").",";
1897 $sql .= " import_key = ".(isset($this->import_key) ? "'".$this->db->escape($this->import_key)."'" : "null");
1898 $sql .= " WHERE rowid = ".((int) $this->id);
1899
1900 $this->db->begin();
1901
1902 dol_syslog(get_class($this)."::update", LOG_DEBUG);
1903 $resql = $this->db->query($sql);
1904 if (!$resql) {
1905 $error++;
1906 $this->errors[] = "Error ".$this->db->lasterror();
1907 }
1908
1909 if (!$error) {
1910 $result = $this->insertExtraFields();
1911 if ($result < 0) {
1912 $error++;
1913 }
1914 }
1915
1916 if (!$error && !$notrigger) {
1917 // Call trigger
1918 $result = $this->call_trigger('PROPAL_MODIFY', $user);
1919 if ($result < 0) {
1920 $error++;
1921 }
1922 // End call triggers
1923 }
1924
1925 // Commit or rollback
1926 if ($error) {
1927 foreach ($this->errors as $errmsg) {
1928 dol_syslog(get_class($this)."::update ".$errmsg, LOG_ERR);
1929 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
1930 }
1931 $this->db->rollback();
1932 return -1 * $error;
1933 } else {
1934 $this->db->commit();
1935 return 1;
1936 }
1937 }
1938
1939
1940 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1949 public function fetch_lines($only_product = 0, $loadalsotranslation = 0, $sqlforgedfilters = '')
1950 {
1951 // phpcs:enable
1952 $this->lines = array();
1953
1954 $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.subprice_ttc, d.fk_product,';
1955 $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,';
1956 $sql .= ' d.fk_unit,';
1957 $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,';
1958 $sql .= ' p.weight, p.weight_units, p.volume, p.volume_units,';
1959 $sql .= ' p.customcode, p.fk_country as country_id, c.code as country_code,';
1960 $sql .= ' d.date_start, d.date_end, d.extraparams,';
1961 $sql .= ' d.fk_multicurrency, d.multicurrency_code, d.multicurrency_subprice, d.multicurrency_total_ht, d.multicurrency_total_tva, d.multicurrency_total_ttc';
1962 $sql .= ' FROM '.MAIN_DB_PREFIX.$this->table_element_line.' as d';
1963 $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'product as p ON (d.fk_product = p.rowid)';
1964 $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_country as c ON c.rowid = p.fk_country';
1965 $sql .= ' WHERE d.fk_propal = '.((int) $this->id);
1966 if ($only_product) {
1967 $sql .= ' AND p.fk_product_type = 0';
1968 }
1969 if ($sqlforgedfilters) {
1970 $sql .= $sqlforgedfilters;
1971 }
1972 $sql .= ' ORDER BY d.rang, d.rowid';
1973
1974 dol_syslog(get_class($this)."::fetch_lines", LOG_DEBUG);
1975 $result = $this->db->query($sql);
1976 if ($result) {
1977 require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php';
1978
1979 $num = $this->db->num_rows($result);
1980
1981 $i = 0;
1982 while ($i < $num) {
1983 $objp = $this->db->fetch_object($result);
1984
1985 $line = new PropaleLigne($this->db);
1986
1987 $line->rowid = $objp->rowid; //Deprecated
1988 $line->id = $objp->rowid;
1989 $line->fk_propal = $objp->fk_propal;
1990 $line->fk_parent_line = $objp->fk_parent_line;
1991 $line->label = $objp->custom_label;
1992 $line->desc = $objp->description; // Line description
1993 $line->description = $objp->description; // Line description
1994 $line->qty = $objp->qty;
1995 $line->vat_src_code = $objp->vat_src_code;
1996 $line->tva_tx = $objp->tva_tx;
1997 $line->localtax1_tx = $objp->localtax1_tx;
1998 $line->localtax2_tx = $objp->localtax2_tx;
1999 $line->localtax1_type = $objp->localtax1_type;
2000 $line->localtax2_type = $objp->localtax2_type;
2001 $line->subprice = $objp->subprice;
2002 $line->subprice_ttc = $objp->subprice_ttc;
2003 $line->fk_remise_except = $objp->fk_remise_except;
2004 $line->remise_percent = $objp->remise_percent;
2005
2006 $line->info_bits = $objp->info_bits;
2007 $line->total_ht = $objp->total_ht;
2008 $line->total_tva = $objp->total_tva;
2009 $line->total_localtax1 = $objp->total_localtax1;
2010 $line->total_localtax2 = $objp->total_localtax2;
2011 $line->total_ttc = $objp->total_ttc;
2012 $line->fk_fournprice = $objp->fk_fournprice;
2013 $marginInfos = getMarginInfos($objp->subprice, $objp->remise_percent, $objp->tva_tx, $objp->localtax1_tx, $objp->localtax2_tx, $line->fk_fournprice, $objp->pa_ht);
2014 $line->pa_ht = $marginInfos[0];
2015 $line->marge_tx = $marginInfos[1];
2016 $line->marque_tx = $marginInfos[2];
2017 $line->special_code = $objp->special_code;
2018 $line->rang = $objp->rang;
2019
2020 $line->fk_product = $objp->fk_product;
2021
2022 $line->ref = $objp->product_ref; // deprecated
2023 $line->libelle = $objp->product_label; // deprecated
2024
2025 $line->product_ref = $objp->product_ref;
2026 $line->product_label = $objp->product_label;
2027 $line->product_desc = $objp->product_desc; // Description produit
2028 $line->product_tobatch = $objp->product_tobatch;
2029 $line->product_barcode = $objp->product_barcode;
2030 $line->product_custom_code = $objp->customcode;
2031 $line->product_custom_country_id = $objp->country_id;
2032 $line->product_custom_country_code = $objp->country_code;
2033 $line->product_type = $objp->product_type;
2034 $line->fk_product_type = $objp->fk_product_type; // deprecated
2035
2036 $line->fk_unit = $objp->fk_unit;
2037 $line->weight = $objp->weight;
2038 $line->weight_units = $objp->weight_units;
2039 $line->volume = $objp->volume;
2040 $line->volume_units = $objp->volume_units;
2041
2042 $line->date_start = $this->db->jdate($objp->date_start);
2043 $line->date_end = $this->db->jdate($objp->date_end);
2044
2045 $line->extraparams = !empty($objp->extraparams) ? (array) json_decode($objp->extraparams, true) : array();
2046
2047 // Multicurrency
2048 $line->fk_multicurrency = $objp->fk_multicurrency;
2049 $line->multicurrency_code = $objp->multicurrency_code;
2050 $line->multicurrency_subprice = $objp->multicurrency_subprice;
2051 $line->multicurrency_total_ht = $objp->multicurrency_total_ht;
2052 $line->multicurrency_total_tva = $objp->multicurrency_total_tva;
2053 $line->multicurrency_total_ttc = $objp->multicurrency_total_ttc;
2054
2055 $line->fetch_optionals();
2056
2057 // multilangs
2058 if (getDolGlobalInt('MAIN_MULTILANGS') && !empty($objp->fk_product) && !empty($loadalsotranslation)) {
2059 $tmpproduct = new Product($this->db);
2060 $tmpproduct->fetch($objp->fk_product);
2061 $tmpproduct->getMultiLangs();
2062
2063 $line->multilangs = $tmpproduct->multilangs;
2064 }
2065
2066 $this->lines[$i] = $line;
2067
2068 $i++;
2069 }
2070
2071 $this->db->free($result);
2072
2073 return $num;
2074 } else {
2075 $this->error = $this->db->lasterror();
2076 return -3;
2077 }
2078 }
2079
2087 public function valid($user, $notrigger = 0)
2088 {
2089 global $conf;
2090
2091 require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
2092
2093 $error = 0;
2094
2095 // Protection
2096 if ($this->status == self::STATUS_VALIDATED) {
2097 dol_syslog(get_class($this)."::valid action abandoned: already validated", LOG_WARNING);
2098 return 0;
2099 }
2100
2101 if (!((!getDolGlobalString('MAIN_USE_ADVANCED_PERMS') && $user->hasRight('propal', 'creer'))
2102 || (getDolGlobalString('MAIN_USE_ADVANCED_PERMS') && $user->hasRight('propal', 'propal_advance', 'validate')))) {
2103 $this->error = 'ErrorPermissionDenied';
2104 dol_syslog(get_class($this)."::valid ".$this->error, LOG_ERR);
2105 return -1;
2106 }
2107
2108 if (!getDolGlobalBool('PROPALE_NOCHECK_ONSALE_PRODUCTS_ONVALID') && !$this->checkActiveProductInLines()) {
2109 dol_syslog(get_class($this)."::valid checkActiveProductInLines ".$this->error, LOG_INFO);
2110 return -1;
2111 }
2112
2113 $now = dol_now();
2114
2115 $this->db->begin();
2116
2117 // Numbering module definition
2118 $soc = new Societe($this->db);
2119 $soc->fetch($this->socid);
2120
2121 // Define new ref
2122 if (preg_match('/^[\‍(]?PROV/i', $this->ref) || empty($this->ref)) { // empty should not happened, but when it occurs, the test save life
2123 $num = $this->getNextNumRef($soc);
2124 } else {
2125 $num = (string) $this->ref;
2126 }
2127 $this->newref = dol_sanitizeFileName($num);
2128
2129 $sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element;
2130 $sql .= " SET ref = '".$this->db->escape($num)."',";
2131 $sql .= " fk_statut = ".self::STATUS_VALIDATED.", date_valid='".$this->db->idate($now)."', fk_user_valid=".((int) $user->id);
2132 $sql .= " WHERE rowid = ".((int) $this->id)." AND fk_statut = ".self::STATUS_DRAFT;
2133
2134 dol_syslog(get_class($this)."::valid", LOG_DEBUG);
2135 $resql = $this->db->query($sql);
2136 if (!$resql) {
2137 dol_print_error($this->db);
2138 $error++;
2139 }
2140
2141 // Trigger calls
2142 if (!$error && !$notrigger) {
2143 // Call trigger
2144 $result = $this->call_trigger('PROPAL_VALIDATE', $user);
2145 if ($result < 0) {
2146 $error++;
2147 }
2148 // End call triggers
2149 }
2150
2151 if (!$error) {
2152 $this->oldref = $this->ref;
2153
2154 // Rename directory if dir was a temporary ref
2155 if (preg_match('/^[\‍(]?PROV/i', $this->ref)) {
2156 // Now we rename also files into index
2157 $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)."'";
2158 $sql .= " WHERE filename LIKE '".$this->db->escape($this->ref)."%' AND filepath = 'propale/".$this->db->escape($this->ref)."' and entity = ".((int) $conf->entity);
2159 $resql = $this->db->query($sql);
2160 if (!$resql) {
2161 $error++;
2162 $this->error = $this->db->lasterror();
2163 }
2164 $sql = 'UPDATE '.MAIN_DB_PREFIX."ecm_files set filepath = 'propale/".$this->db->escape($this->newref)."'";
2165 $sql .= " WHERE filepath = 'propale/".$this->db->escape($this->ref)."' and entity = ".$conf->entity;
2166 $resql = $this->db->query($sql);
2167 if (!$resql) {
2168 $error++;
2169 $this->error = $this->db->lasterror();
2170 }
2171
2172 // We rename directory ($this->ref = old ref, $num = new ref) in order not to lose the attachments
2173 $oldref = dol_sanitizeFileName($this->ref);
2174 $newref = dol_sanitizeFileName($num);
2175 $dirsource = $conf->propal->multidir_output[$this->entity].'/'.$oldref;
2176 $dirdest = $conf->propal->multidir_output[$this->entity].'/'.$newref;
2177 if (!$error && file_exists($dirsource)) {
2178 dol_syslog(get_class($this)."::validate rename dir ".$dirsource." into ".$dirdest);
2179 if (@rename($dirsource, $dirdest)) {
2180 dol_syslog("Rename ok");
2181 // Rename docs starting with $oldref with $newref
2182 $listoffiles = dol_dir_list($dirdest, 'files', 1, '^'.preg_quote($oldref, '/'));
2183 foreach ($listoffiles as $fileentry) {
2184 $dirsource = $fileentry['name'];
2185 $dirdest = preg_replace('/^'.preg_quote($oldref, '/').'/', $newref, $dirsource);
2186 $dirsource = $fileentry['path'].'/'.$dirsource;
2187 $dirdest = $fileentry['path'].'/'.$dirdest;
2188 @rename($dirsource, $dirdest);
2189 }
2190 }
2191 }
2192 }
2193
2194 $this->ref = $num;
2195 $this->statut = self::STATUS_VALIDATED; // deprecated
2197 $this->user_validation_id = $user->id;
2198 $this->datev = $now;
2199 $this->date_validation = $now;
2200
2201 $this->db->commit();
2202 return 1;
2203 } else {
2204 $this->db->rollback();
2205 return -1;
2206 }
2207 }
2208
2209
2210 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2219 public function set_date($user, $date, $notrigger = 0)
2220 {
2221 // phpcs:enable
2222 if (empty($date)) {
2223 $this->error = 'ErrorBadParameter';
2224 dol_syslog(get_class($this)."::set_date ".$this->error, LOG_ERR);
2225 return -1;
2226 }
2227
2228 if ($user->hasRight('propal', 'creer')) {
2229 $error = 0;
2230
2231 $this->db->begin();
2232
2233 $sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element;
2234 $sql .= " SET datep = '".$this->db->idate($date)."'";
2235 $sql .= " WHERE rowid = ".((int) $this->id);
2236
2237 dol_syslog(__METHOD__, LOG_DEBUG);
2238 $resql = $this->db->query($sql);
2239 if (!$resql) {
2240 $this->errors[] = $this->db->error();
2241 $error++;
2242 }
2243
2244 if (!$error) {
2245 $this->oldcopy = clone $this;
2246 $this->date = $date;
2247 $this->datep = $date; // deprecated
2248 }
2249
2250 if (!$notrigger && empty($error)) {
2251 // Call trigger
2252 $result = $this->call_trigger('PROPAL_MODIFY', $user);
2253 if ($result < 0) {
2254 $error++;
2255 }
2256 // End call triggers
2257 }
2258
2259 if (!$error) {
2260 $this->db->commit();
2261 return 1;
2262 } else {
2263 foreach ($this->errors as $errmsg) {
2264 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2265 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2266 }
2267 $this->db->rollback();
2268 return -1 * $error;
2269 }
2270 }
2271
2272 return -1;
2273 }
2274
2275 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2284 public function set_echeance($user, $date_end_validity, $notrigger = 0)
2285 {
2286 // phpcs:enable
2287 if ($user->hasRight('propal', 'creer')) {
2288 $error = 0;
2289
2290 $this->db->begin();
2291
2292 $sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element;
2293 $sql .= " SET fin_validite = ".($date_end_validity != '' ? "'".$this->db->idate($date_end_validity)."'" : 'null');
2294 $sql .= " WHERE rowid = ".((int) $this->id);
2295
2296 dol_syslog(__METHOD__, LOG_DEBUG);
2297
2298 $resql = $this->db->query($sql);
2299 if (!$resql) {
2300 $this->errors[] = $this->db->error();
2301 $error++;
2302 }
2303
2304
2305 if (!$error) {
2306 $this->oldcopy = clone $this;
2307 $this->fin_validite = $date_end_validity;
2308 }
2309
2310 if (!$notrigger && empty($error)) {
2311 // Call trigger
2312 $result = $this->call_trigger('PROPAL_MODIFY', $user);
2313 if ($result < 0) {
2314 $error++;
2315 }
2316 // End call triggers
2317 }
2318
2319 if (!$error) {
2320 $this->db->commit();
2321 return 1;
2322 } else {
2323 foreach ($this->errors as $errmsg) {
2324 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2325 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2326 }
2327 $this->db->rollback();
2328 return -1 * $error;
2329 }
2330 }
2331
2332 return -1;
2333 }
2334
2335 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2345 public function set_date_livraison($user, $delivery_date, $notrigger = 0)
2346 {
2347 // phpcs:enable
2348 return $this->setDeliveryDate($user, $delivery_date, $notrigger);
2349 }
2350
2359 public function setDeliveryDate($user, $delivery_date, $notrigger = 0)
2360 {
2361 if ($user->hasRight('propal', 'creer')) {
2362 $error = 0;
2363
2364 $this->db->begin();
2365
2366 $sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element;
2367 $sql .= " SET date_livraison = ".(isDolTms($delivery_date) ? "'".$this->db->idate($delivery_date)."'" : 'null');
2368 $sql .= " WHERE rowid = ".((int) $this->id);
2369
2370 dol_syslog(__METHOD__, LOG_DEBUG);
2371 $resql = $this->db->query($sql);
2372 if (!$resql) {
2373 $this->errors[] = $this->db->error();
2374 $error++;
2375 }
2376
2377 if (!$error) {
2378 $this->oldcopy = clone $this;
2379 $this->delivery_date = $delivery_date;
2380 }
2381
2382 if (!$notrigger && empty($error)) {
2383 // Call trigger
2384 $result = $this->call_trigger('PROPAL_MODIFY', $user);
2385 if ($result < 0) {
2386 $error++;
2387 }
2388 // End call triggers
2389 }
2390
2391 if (!$error) {
2392 $this->db->commit();
2393 return 1;
2394 } else {
2395 foreach ($this->errors as $errmsg) {
2396 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2397 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2398 }
2399 $this->db->rollback();
2400 return -1 * $error;
2401 }
2402 }
2403
2404 return -1;
2405 }
2406
2407 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2416 public function set_availability($user, $id, $notrigger = 0)
2417 {
2418 // phpcs:enable
2419 if ($this->status >= self::STATUS_DRAFT) {
2420 $error = 0;
2421
2422 $this->db->begin();
2423
2424 $sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element;
2425 $sql .= " SET fk_availability = ".((int) ($id > 0 ? $id : 0));
2426 $sql .= " WHERE rowid = ".((int) $this->id);
2427
2428 dol_syslog(__METHOD__.' availability('.$id.')', LOG_DEBUG);
2429 $resql = $this->db->query($sql);
2430 if (!$resql) {
2431 $this->errors[] = $this->db->error();
2432 $error++;
2433 }
2434
2435 if (!$error) {
2436 $this->oldcopy = clone $this;
2437 $this->fk_availability = $id;
2438 $this->availability_id = $id;
2439 }
2440
2441 if (!$notrigger && empty($error)) {
2442 // Call trigger
2443 $result = $this->call_trigger('PROPAL_MODIFY', $user);
2444 if ($result < 0) {
2445 $error++;
2446 }
2447 // End call triggers
2448 }
2449
2450 if (!$error) {
2451 $this->db->commit();
2452 return 1;
2453 } else {
2454 foreach ($this->errors as $errmsg) {
2455 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2456 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2457 }
2458 $this->db->rollback();
2459 return -1 * $error;
2460 }
2461 } else {
2462 $error_str = 'Propal status do not meet requirement '.$this->status;
2463 dol_syslog(__METHOD__.$error_str, LOG_ERR);
2464 $this->error = $error_str;
2465 $this->errors[] = $this->error;
2466 return -2;
2467 }
2468 }
2469
2470 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2479 public function set_demand_reason($user, $id, $notrigger = 0)
2480 {
2481 // phpcs:enable
2482 if ($user->hasRight('propal', 'creer') && $this->status >= self::STATUS_DRAFT) {
2483 $error = 0;
2484
2485 $this->db->begin();
2486
2487 $sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element;
2488 $sql .= " SET fk_input_reason = ".((int) $id);
2489 $sql .= " WHERE rowid = ".((int) $this->id);
2490
2491 dol_syslog(__METHOD__, LOG_DEBUG);
2492 $resql = $this->db->query($sql);
2493 if (!$resql) {
2494 $this->errors[] = $this->db->error();
2495 $error++;
2496 }
2497
2498
2499 if (!$error) {
2500 $this->oldcopy = clone $this;
2501
2502 $this->demand_reason_id = $id;
2503 }
2504
2505
2506 if (!$notrigger && empty($error)) {
2507 // Call trigger
2508 $result = $this->call_trigger('PROPAL_MODIFY', $user);
2509 if ($result < 0) {
2510 $error++;
2511 }
2512 // End call triggers
2513 }
2514
2515 if (!$error) {
2516 $this->db->commit();
2517 return 1;
2518 } else {
2519 foreach ($this->errors as $errmsg) {
2520 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2521 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2522 }
2523 $this->db->rollback();
2524 return -1 * $error;
2525 }
2526 } else {
2527 $error_str = 'Propal status do not meet requirement '.$this->status;
2528 dol_syslog(__METHOD__.$error_str, LOG_ERR);
2529 $this->error = $error_str;
2530 $this->errors[] = $this->error;
2531 return -2;
2532 }
2533 }
2534
2535 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2544 public function set_ref_client($user, $ref_client, $notrigger = 0)
2545 {
2546 // phpcs:enable
2547 if ($user->hasRight('propal', 'creer')) {
2548 $error = 0;
2549
2550 $this->db->begin();
2551
2552 $sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element;
2553 $sql .= " SET ref_client = ".(empty($ref_client) ? 'NULL' : "'".$this->db->escape($ref_client)."'");
2554 $sql .= " WHERE rowid = ".((int) $this->id);
2555
2556 dol_syslog(__METHOD__.' $this->id='.$this->id.', ref_client='.$ref_client, LOG_DEBUG);
2557 $resql = $this->db->query($sql);
2558 if (!$resql) {
2559 $this->errors[] = $this->db->error();
2560 $error++;
2561 }
2562
2563 if (!$error) {
2564 $this->oldcopy = clone $this;
2565 $this->ref_customer = $ref_client;
2566 $this->ref_client = $ref_client;
2567 }
2568
2569 if (!$notrigger && empty($error)) {
2570 // Call trigger
2571 $result = $this->call_trigger('PROPAL_MODIFY', $user);
2572 if ($result < 0) {
2573 $error++;
2574 }
2575 // End call triggers
2576 }
2577
2578 if (!$error) {
2579 $this->db->commit();
2580 return 1;
2581 } else {
2582 foreach ($this->errors as $errmsg) {
2583 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2584 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2585 }
2586 $this->db->rollback();
2587 return -1 * $error;
2588 }
2589 }
2590
2591 return -1;
2592 }
2593
2594
2604 public function reopen($user, $status, $note = '', $notrigger = 0)
2605 {
2606 $error = 0;
2607
2608 $sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element;
2609 $sql .= " SET fk_statut = ".((int) $status).",";
2610 if (!empty($note)) {
2611 $sql .= " note_private = '".$this->db->escape($note)."',";
2612 }
2613 $sql .= " date_cloture=NULL, fk_user_cloture=NULL";
2614 $sql .= " WHERE rowid = ".((int) $this->id);
2615
2616 $this->db->begin();
2617
2618 dol_syslog(get_class($this)."::reopen", LOG_DEBUG);
2619 $resql = $this->db->query($sql);
2620 if (!$resql) {
2621 $error++;
2622 $this->errors[] = "Error ".$this->db->lasterror();
2623 }
2624 if (!$error) {
2625 if (!$notrigger) {
2626 // Call trigger
2627 $result = $this->call_trigger('PROPAL_REOPEN', $user);
2628 if ($result < 0) {
2629 $error++;
2630 }
2631 // End call triggers
2632 }
2633 }
2634
2635 // Commit or rollback
2636 if ($error) {
2637 if (!empty($this->errors)) {
2638 foreach ($this->errors as $errmsg) {
2639 dol_syslog(get_class($this)."::update ".$errmsg, LOG_ERR);
2640 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2641 }
2642 }
2643 $this->db->rollback();
2644 return -1 * $error;
2645 } else {
2646 $this->statut = $status;
2647 $this->status = $status;
2648
2649 $this->db->commit();
2650 return 1;
2651 }
2652 }
2653
2664 public function closeProposal($user, $status, $note_private = '', $notrigger = 0, $note_public = '')
2665 {
2666 global $langs,$conf;
2667
2668 $error = 0;
2669 $now = dol_now();
2670
2671 $this->db->begin();
2672
2673 $newprivatenote = dol_concatdesc($this->note_private, $note_private);
2674 $newpublicnote = dol_concatdesc($this->note_public, $note_public);
2675
2676 if (!getDolGlobalString('PROPALE_KEEP_OLD_SIGNATURE_INFO')) {
2677 $date_signature = $now;
2678 $fk_user_signature = $user->id;
2679 } else {
2680 $this->info($this->id);
2681 if (!isset($this->date_signature) || $this->date_signature == '') {
2682 $date_signature = $now;
2683 $fk_user_signature = $user->id;
2684 } else {
2685 $date_signature = $this->date_signature;
2686 $fk_user_signature = $this->user_signature->id;
2687 }
2688 }
2689
2690 $sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element;
2691 $sql .= " SET fk_statut = ".((int) $status).", note_private = '".$this->db->escape($newprivatenote)."', note_public = '".$this->db->escape($newpublicnote)."'";
2692 if ($status == self::STATUS_SIGNED) {
2693 $sql .= ", date_signature='".$this->db->idate($now)."', fk_user_signature = ".($fk_user_signature);
2694 }
2695 $sql .= " WHERE rowid = ".((int) $this->id);
2696
2697 $resql = $this->db->query($sql);
2698 if ($resql) {
2699 // Status self::STATUS_REFUSED by default
2700 $modelpdf = getDolGlobalString('PROPALE_ADDON_PDF_ODT_CLOSED', $this->model_pdf);
2701 $triggerName = 'PROPAL_CLOSE_REFUSED'; // used later in call_trigger()
2702
2703 if ($status == self::STATUS_SIGNED) { // Status self::STATUS_SIGNED
2704 $triggerName = 'PROPAL_CLOSE_SIGNED'; // used later in call_trigger()
2705 $modelpdf = getDolGlobalString('PROPALE_ADDON_PDF_ODT_TOBILL', $this->model_pdf);
2706
2707 // The connected company is classified as a client
2708 $soc = new Societe($this->db);
2709 $soc->id = $this->socid;
2710 $result = $soc->setAsCustomer();
2711
2712 if ($result < 0) {
2713 $this->error = $this->db->lasterror();
2714 $this->db->rollback();
2715 return -2;
2716 }
2717 }
2718
2719 if (!getDolGlobalString('MAIN_DISABLE_PDF_AUTOUPDATE') && !getDolGlobalInt('PROPAL_DISABLE_AUTOUPDATE_ON_CLOSE')) {
2720 // Define output language
2721 $outputlangs = $langs;
2722 if (getDolGlobalInt('MAIN_MULTILANGS')) {
2723 $outputlangs = new Translate("", $conf);
2724 $newlang = (GETPOST('lang_id', 'aZ09') ? GETPOST('lang_id', 'aZ09') : $this->thirdparty->default_lang);
2725 $outputlangs->setDefaultLang($newlang);
2726 }
2727
2728 // PDF
2729 $hidedetails = (getDolGlobalString('MAIN_GENERATE_DOCUMENTS_HIDE_DETAILS') ? 1 : 0);
2730 $hidedesc = (getDolGlobalString('MAIN_GENERATE_DOCUMENTS_HIDE_DESC') ? 1 : 0);
2731 $hideref = (getDolGlobalString('MAIN_GENERATE_DOCUMENTS_HIDE_REF') ? 1 : 0);
2732
2733 //$ret=$object->fetch($id); // Reload to get new records
2734 $this->generateDocument($modelpdf, $outputlangs, $hidedetails, $hidedesc, $hideref);
2735 }
2736
2737 $this->oldcopy = clone $this;
2738 $this->statut = $status; // deprecated
2739 $this->status = $status;
2740 $this->date_signature = $date_signature;
2741 $this->note_private = $newprivatenote;
2742
2743 if (!$notrigger && empty($error)) {
2744 // Call trigger
2745 $result = $this->call_trigger($triggerName, $user);
2746 if ($result < 0) {
2747 $error++;
2748 }
2749 // End call triggers
2750 }
2751
2752 if (!$error) {
2753 $this->db->commit();
2754 return 1;
2755 } else {
2756 foreach ($this->errors as $errmsg) {
2757 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2758 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2759 }
2760 if (!empty($this->oldcopy)) {
2761 $this->statut = $this->oldcopy->status; // deprecated
2762 $this->status = $this->oldcopy->status;
2763 $this->date_signature = $this->oldcopy->date_signature;
2764 $this->note_private = $this->oldcopy->note_private;
2765 }
2766
2767 $this->db->rollback();
2768 return -1;
2769 }
2770 } else {
2771 $this->error = $this->db->lasterror();
2772 $this->db->rollback();
2773 return -1;
2774 }
2775 }
2776
2785 public function classifyBilled(User $user, $notrigger = 0, $note = '')
2786 {
2787 global $conf, $langs;
2788
2789 $error = 0;
2790
2791 $now = dol_now();
2792 $num = 0;
2793
2794 $triggerName = 'PROPAL_CLASSIFY_BILLED';
2795
2796 $this->db->begin();
2797
2798 $newprivatenote = dol_concatdesc($this->note_private, $note);
2799
2800 $sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element;
2801 $sql .= " SET fk_statut = ".self::STATUS_BILLED.", ";
2802 $sql .= " note_private = '".$this->db->escape($newprivatenote)."', date_cloture='".$this->db->idate($now)."', fk_user_cloture=".((int) $user->id);
2803 $sql .= ' WHERE rowid = '.((int) $this->id).' AND fk_statut = '.((int) self::STATUS_SIGNED);
2804
2805 dol_syslog(__METHOD__, LOG_DEBUG);
2806 $resql = $this->db->query($sql);
2807 if (!$resql) {
2808 $this->errors[] = $this->db->error();
2809 $error++;
2810 } else {
2811 $num = $this->db->affected_rows($resql);
2812 }
2813
2814 if (!$error) {
2815 $modelpdf = getDolGlobalString('PROPALE_ADDON_PDF_ODT_CLOSED', $this->model_pdf);
2816
2817 if (!getDolGlobalString('MAIN_DISABLE_PDF_AUTOUPDATE')) {
2818 // Define output language
2819 $outputlangs = $langs;
2820 if (getDolGlobalInt('MAIN_MULTILANGS')) {
2821 $outputlangs = new Translate("", $conf);
2822 $newlang = (GETPOST('lang_id', 'aZ09') ? GETPOST('lang_id', 'aZ09') : $this->thirdparty->default_lang);
2823 $outputlangs->setDefaultLang($newlang);
2824 }
2825
2826 // PDF
2827 $hidedetails = (getDolGlobalString('MAIN_GENERATE_DOCUMENTS_HIDE_DETAILS') ? 1 : 0);
2828 $hidedesc = (getDolGlobalString('MAIN_GENERATE_DOCUMENTS_HIDE_DESC') ? 1 : 0);
2829 $hideref = (getDolGlobalString('MAIN_GENERATE_DOCUMENTS_HIDE_REF') ? 1 : 0);
2830
2831 //$ret=$object->fetch($id); // Reload to get new records
2832 $this->generateDocument($modelpdf, $outputlangs, $hidedetails, $hidedesc, $hideref);
2833 }
2834
2835 $this->oldcopy = clone $this;
2836 $this->statut = self::STATUS_BILLED;
2837 $this->status = self::STATUS_BILLED;
2838 $this->date_cloture = $now;
2839 $this->note_private = $newprivatenote;
2840 }
2841
2842 if (!$notrigger && empty($error)) {
2843 // Call trigger
2844 $result = $this->call_trigger($triggerName, $user);
2845 if ($result < 0) {
2846 $error++;
2847 }
2848 // End call triggers
2849 }
2850
2851 if (!$error) {
2852 $this->db->commit();
2853 return $num;
2854 } else {
2855 foreach ($this->errors as $errmsg) {
2856 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2857 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2858 }
2859 $this->db->rollback();
2860 return -1 * $error;
2861 }
2862 }
2863
2870 public function setCancel(User $user)
2871 {
2872 $error = 0;
2873
2874 $this->db->begin();
2875
2876 $sql = "UPDATE ". MAIN_DB_PREFIX . $this->table_element;
2877 $sql .= " SET fk_statut = " . self::STATUS_CANCELED . ",";
2878 $sql .= " fk_user_modif = " . ((int) $user->id);
2879 $sql .= " WHERE rowid = " . ((int) $this->id);
2880
2881 dol_syslog(get_class($this)."::cancel", LOG_DEBUG);
2882 if ($this->db->query($sql)) {
2883 // Call trigger
2884 $result = $this->call_trigger('PROPAL_CANCEL', $user);
2885 if ($result < 0) {
2886 $error++;
2887 }
2888 // End call triggers
2889
2890 if (!$error) {
2891 $this->statut = self::STATUS_CANCELED; // deprecated
2893 $this->db->commit();
2894 return 1;
2895 } else {
2896 foreach ($this->errors as $errmsg) {
2897 dol_syslog(get_class($this)."::cancel ".$errmsg, LOG_ERR);
2898 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2899 }
2900 $this->db->rollback();
2901 return -1;
2902 }
2903 } else {
2904 $this->error = $this->db->error();
2905 $this->db->rollback();
2906 return -1;
2907 }
2908 }
2909
2910 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2918 public function setDraft($user, $notrigger = 0)
2919 {
2920 // phpcs:enable
2921 $error = 0;
2922
2923 // Protection
2924 if ($this->status <= self::STATUS_DRAFT) {
2925 return 0;
2926 }
2927
2928 dol_syslog(get_class($this)."::setDraft", LOG_DEBUG);
2929
2930 $this->db->begin();
2931
2932 $sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element;
2933 $sql .= " SET fk_statut = ".self::STATUS_DRAFT;
2934 $sql .= ", online_sign_ip = NULL , online_sign_name = NULL";
2935 $sql .= " WHERE rowid = ".((int) $this->id);
2936
2937 $resql = $this->db->query($sql);
2938 if (!$resql) {
2939 $this->errors[] = $this->db->error();
2940 $error++;
2941 }
2942
2943 if (!$error) {
2944 $this->oldcopy = clone $this;
2945 }
2946
2947 if (!$notrigger && empty($error)) {
2948 // Call trigger
2949 $result = $this->call_trigger('PROPAL_MODIFY', $user);
2950 if ($result < 0) {
2951 $error++;
2952 }
2953 // End call triggers
2954 }
2955
2956 if (!$error) {
2957 $this->statut = self::STATUS_DRAFT; // deprecated
2958 $this->status = self::STATUS_DRAFT;
2959
2960 $this->db->commit();
2961 return 1;
2962 } else {
2963 foreach ($this->errors as $errmsg) {
2964 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2965 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2966 }
2967 $this->db->rollback();
2968 return -1 * $error;
2969 }
2970 }
2971
2972
2973 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2987 public function liste_array($shortlist = 0, $draft = 0, $notcurrentuser = 0, $socid = 0, $limit = 0, $offset = 0, $sortfield = 'p.datep', $sortorder = 'DESC')
2988 {
2989 // phpcs:enable
2990 global $user;
2991
2992 $ga = array();
2993
2994 $sql = "SELECT s.rowid, s.nom as name, s.client,";
2995 $sql .= " p.rowid as propalid, p.fk_statut, p.total_ht, p.ref, p.remise, ";
2996 $sql .= " p.datep as dp, p.fin_validite as datelimite";
2997 $sql .= " FROM ".MAIN_DB_PREFIX."societe as s, ".MAIN_DB_PREFIX.$this->table_element." as p, ".MAIN_DB_PREFIX."c_propalst as c";
2998 $sql .= " WHERE p.entity IN (".getEntity('propal').")";
2999 $sql .= " AND p.fk_soc = s.rowid";
3000 $sql .= " AND p.fk_statut = c.id";
3001
3002 // If the internal user must only see his customers, force searching by him
3003 $search_sale = 0;
3004 if (empty($user->socid) && !$user->hasRight('societe', 'client', 'voir')) {
3005 $search_sale = $user->id;
3006 }
3007 // Search on sale representative
3008 if ($search_sale && $search_sale != '-1') {
3009 if ($search_sale == -2) {
3010 $sql .= " AND NOT EXISTS (SELECT sc.fk_soc FROM ".MAIN_DB_PREFIX."societe_commerciaux as sc WHERE sc.fk_soc = p.fk_soc)";
3011 } elseif ($search_sale > 0) {
3012 $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).")";
3013 }
3014 }
3015 // Search on socid
3016 if ($socid) {
3017 $sql .= " AND p.fk_soc = ".((int) $socid);
3018 }
3019 if ($draft) {
3020 $sql .= " AND p.fk_statut = ".((int) self::STATUS_DRAFT);
3021 }
3022 if ($notcurrentuser > 0) {
3023 $sql .= " AND p.fk_user_author <> ".((int) $user->id);
3024 }
3025 $sql .= $this->db->order($sortfield, $sortorder);
3026 $sql .= $this->db->plimit($limit, $offset);
3027
3028 $result = $this->db->query($sql);
3029 if ($result) {
3030 $num = $this->db->num_rows($result);
3031 if ($num) {
3032 $i = 0;
3033 while ($i < $num) {
3034 $obj = $this->db->fetch_object($result);
3035
3036 if ($shortlist == 1) {
3037 $ga[$obj->propalid] = $obj->ref;
3038 } elseif ($shortlist == 2) {
3039 $ga[$obj->propalid] = $obj->ref.' ('.$obj->name.')';
3040 } else {
3041 $ga[$i]['id'] = $obj->propalid;
3042 $ga[$i]['ref'] = $obj->ref;
3043 $ga[$i]['name'] = $obj->name;
3044 }
3045
3046 $i++;
3047 }
3048 }
3049 return $ga;
3050 } else {
3051 dol_print_error($this->db);
3052 return -1;
3053 }
3054 }
3055
3061 public function getInvoiceArrayList()
3062 {
3063 return $this->InvoiceArrayList($this->id);
3064 }
3065
3066 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3073 public function InvoiceArrayList($id)
3074 {
3075 // phpcs:enable
3076 $ga = array();
3077 $linkedInvoices = array();
3078
3079 $this->fetchObjectLinked($id, $this->element);
3080 foreach ($this->linkedObjectsIds as $objecttype => $objectid) {
3081 // Nouveau système du common object renvoi des rowid et non un id linéaire de 1 à n
3082 //We are therefore traversing a list of objects as a single object
3083 foreach ($objectid as $key => $object) {
3084 // Case of invoices directly linked
3085 if ($objecttype == 'facture') {
3086 $linkedInvoices[] = $object;
3087 } else {
3088 // Case of invoices linked by another object (e.g., order)
3089 $this->fetchObjectLinked($object, $objecttype);
3090 foreach ($this->linkedObjectsIds as $subobjecttype => $subobjectid) {
3091 foreach ($subobjectid as $subkey => $subobject) {
3092 if ($subobjecttype == 'facture') {
3093 $linkedInvoices[] = $subobject;
3094 }
3095 }
3096 }
3097 }
3098 }
3099 }
3100
3101 if (count($linkedInvoices) > 0) {
3102 $sql = "SELECT rowid as facid, ref, total_ht as total, datef as df, fk_user_author, fk_statut, paye";
3103 $sql .= " FROM ".MAIN_DB_PREFIX."facture";
3104 $sql .= " WHERE rowid IN (".$this->db->sanitize(implode(',', $linkedInvoices)).")";
3105
3106 dol_syslog(get_class($this)."::InvoiceArrayList", LOG_DEBUG);
3107 $resql = $this->db->query($sql);
3108
3109 if ($resql) {
3110 $tab_sqlobj = array();
3111 $nump = $this->db->num_rows($resql);
3112 for ($i = 0; $i < $nump; $i++) {
3113 $sqlobj = $this->db->fetch_object($resql);
3114 $tab_sqlobj[] = $sqlobj;
3115 }
3116 $this->db->free($resql);
3117
3118 $nump = count($tab_sqlobj);
3119
3120 if ($nump) {
3121 $i = 0;
3122 while ($i < $nump) {
3123 $obj = array_shift($tab_sqlobj);
3124
3125 $ga[$i] = $obj;
3126
3127 $i++;
3128 }
3129 }
3130 return $ga;
3131 } else {
3132 return -1;
3133 }
3134 } else {
3135 return $ga;
3136 }
3137 }
3138
3146 public function delete($user, $notrigger = 0)
3147 {
3148 global $conf;
3149 require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
3150
3151 $error = 0;
3152
3153 $this->db->begin();
3154
3155 if (!$notrigger) {
3156 // Call trigger
3157 $result = $this->call_trigger('PROPAL_DELETE', $user);
3158 if ($result < 0) {
3159 $error++;
3160 }
3161 // End call triggers
3162 }
3163
3164 // Delete extrafields of lines and lines
3165 if (!$error && !empty($this->table_element_line)) {
3166 $tabletodelete = $this->table_element_line;
3167 $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).")";
3168 $sql = "DELETE FROM ".MAIN_DB_PREFIX.$tabletodelete." WHERE ".$this->fk_element." = ".((int) $this->id);
3169 if (!$this->db->query($sqlef) || !$this->db->query($sql)) {
3170 $error++;
3171 $this->error = $this->db->lasterror();
3172 $this->errors[] = $this->error;
3173 dol_syslog(get_class($this)."::delete error ".$this->error, LOG_ERR);
3174 }
3175 }
3176
3177 if (!$error) {
3178 // Delete linked object
3179 $res = $this->deleteObjectLinked();
3180 if ($res < 0) {
3181 $error++;
3182 }
3183 }
3184
3185 if (!$error) {
3186 // Delete linked contacts
3187 $res = $this->delete_linked_contact();
3188 if ($res < 0) {
3189 $error++;
3190 }
3191 }
3192
3193 // Removed extrafields of object
3194 if (!$error) {
3195 $result = $this->deleteExtraFields();
3196 if ($result < 0) {
3197 $error++;
3198 dol_syslog(get_class($this)."::delete error ".$this->error, LOG_ERR);
3199 }
3200 }
3201
3202 // Delete main record
3203 if (!$error) {
3204 $sql = "DELETE FROM ".MAIN_DB_PREFIX.$this->table_element." WHERE rowid = ".((int) $this->id);
3205 $res = $this->db->query($sql);
3206 if (!$res) {
3207 $error++;
3208 $this->error = $this->db->lasterror();
3209 $this->errors[] = $this->error;
3210 dol_syslog(get_class($this)."::delete error ".$this->error, LOG_ERR);
3211 }
3212 }
3213
3214 // Delete record into ECM index and physically
3215 if (!$error) {
3216 $res = $this->deleteEcmFiles(0); // Deleting files physically is done later with the dol_delete_dir_recursive
3217 $res = $this->deleteEcmFiles(1); // Deleting files physically is done later with the dol_delete_dir_recursive
3218 if (!$res) {
3219 $error++;
3220 }
3221 }
3222
3223 if (!$error) {
3224 // We remove directory
3225 $ref = dol_sanitizeFileName($this->ref);
3226 if ($conf->propal->multidir_output[$this->entity] && !empty($this->ref)) {
3227 $dir = $conf->propal->multidir_output[$this->entity]."/".$ref;
3228 $file = $dir."/".$ref.".pdf";
3229 if (file_exists($file)) {
3230 dol_delete_preview($this);
3231
3232 if (!dol_delete_file($file, 0, 0, 0, $this)) {
3233 $this->error = 'ErrorFailToDeleteFile';
3234 $this->errors[] = $this->error;
3235 $this->db->rollback();
3236 return 0;
3237 }
3238 }
3239 if (file_exists($dir)) {
3240 $res = @dol_delete_dir_recursive($dir); // delete files physically + into ecm tables
3241 if (!$res) {
3242 $this->error = 'ErrorFailToDeleteDir';
3243 $this->errors[] = $this->error;
3244 $this->db->rollback();
3245 return 0;
3246 }
3247 }
3248 }
3249 }
3250
3251 if (!$error) {
3252 dol_syslog(get_class($this)."::delete ".$this->id." by ".$user->id, LOG_DEBUG);
3253 $this->db->commit();
3254 return 1;
3255 } else {
3256 $this->db->rollback();
3257 return -1;
3258 }
3259 }
3260
3269 public function availability($availability_id, $notrigger = 0)
3270 {
3271 global $user;
3272
3273 if ($this->status >= self::STATUS_DRAFT) {
3274 $error = 0;
3275
3276 $this->db->begin();
3277
3278 $sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element;
3279 $sql .= ' SET fk_availability = '.((int) $availability_id);
3280 $sql .= ' WHERE rowid='.((int) $this->id);
3281
3282 dol_syslog(__METHOD__.' availability('.$availability_id.')', LOG_DEBUG);
3283 $resql = $this->db->query($sql);
3284 if (!$resql) {
3285 $this->errors[] = $this->db->error();
3286 $error++;
3287 }
3288
3289 if (!$error) {
3290 $this->oldcopy = clone $this;
3291 $this->availability_id = $availability_id;
3292 }
3293
3294 if (!$notrigger && empty($error)) {
3295 // Call trigger
3296 $result = $this->call_trigger('PROPAL_MODIFY', $user);
3297 if ($result < 0) {
3298 $error++;
3299 }
3300 // End call triggers
3301 }
3302
3303 if (!$error) {
3304 $this->db->commit();
3305 return 1;
3306 } else {
3307 foreach ($this->errors as $errmsg) {
3308 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
3309 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
3310 }
3311 $this->db->rollback();
3312 return -1 * $error;
3313 }
3314 } else {
3315 $error_str = 'Propal status do not meet requirement '.$this->status;
3316 dol_syslog(__METHOD__.$error_str, LOG_ERR);
3317 $this->error = $error_str;
3318 $this->errors[] = $this->error;
3319 return -2;
3320 }
3321 }
3322
3323 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3332 public function demand_reason($demand_reason_id, $notrigger = 0)
3333 {
3334 // phpcs:enable
3335 global $user;
3336
3337 if ($this->status >= self::STATUS_DRAFT) {
3338 $error = 0;
3339
3340 $this->db->begin();
3341
3342 $sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element;
3343 $sql .= ' SET fk_input_reason = '.((int) $demand_reason_id);
3344 $sql .= ' WHERE rowid='.((int) $this->id);
3345
3346 dol_syslog(__METHOD__.' demand_reason('.$demand_reason_id.')', LOG_DEBUG);
3347 $resql = $this->db->query($sql);
3348 if (!$resql) {
3349 $this->errors[] = $this->db->error();
3350 $error++;
3351 }
3352
3353 if (!$error) {
3354 $this->oldcopy = clone $this;
3355 $this->demand_reason_id = $demand_reason_id;
3356 }
3357
3358 if (!$notrigger && empty($error)) {
3359 // Call trigger
3360 $result = $this->call_trigger('PROPAL_MODIFY', $user);
3361 if ($result < 0) {
3362 $error++;
3363 }
3364 // End call triggers
3365 }
3366
3367 if (!$error) {
3368 $this->db->commit();
3369 return 1;
3370 } else {
3371 foreach ($this->errors as $errmsg) {
3372 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
3373 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
3374 }
3375 $this->db->rollback();
3376 return -1 * $error;
3377 }
3378 } else {
3379 $error_str = 'Propal status do not meet requirement '.$this->status;
3380 dol_syslog(__METHOD__.$error_str, LOG_ERR);
3381 $this->error = $error_str;
3382 $this->errors[] = $this->error;
3383 return -2;
3384 }
3385 }
3386
3387
3394 public function info($id)
3395 {
3396 $sql = "SELECT c.rowid, ";
3397 $sql .= " c.datec, c.date_valid as datev, c.date_signature, c.date_cloture,";
3398 $sql .= " c.fk_user_author, c.fk_user_valid, c.fk_user_signature, c.fk_user_cloture";
3399 $sql .= " FROM ".MAIN_DB_PREFIX.$this->table_element." as c";
3400 $sql .= " WHERE c.rowid = ".((int) $id);
3401
3402 $result = $this->db->query($sql);
3403
3404 if ($result) {
3405 if ($this->db->num_rows($result)) {
3406 $obj = $this->db->fetch_object($result);
3407
3408 $this->id = $obj->rowid;
3409
3410 $this->date_creation = $this->db->jdate($obj->datec);
3411 $this->date_validation = $this->db->jdate($obj->datev);
3412 $this->date_signature = $this->db->jdate($obj->date_signature);
3413 $this->date_cloture = $this->db->jdate($obj->date_cloture);
3414
3415 $this->user_creation_id = $obj->fk_user_author;
3416 $this->user_validation_id = $obj->fk_user_valid;
3417
3418 if ($obj->fk_user_signature) {
3419 $user_signature = new User($this->db);
3420 $user_signature->fetch($obj->fk_user_signature);
3421 $this->user_signature = $user_signature;
3422 }
3423
3424 $this->user_closing_id = $obj->fk_user_cloture;
3425 }
3426 $this->db->free($result);
3427 } else {
3428 dol_print_error($this->db);
3429 }
3430 }
3431
3432
3439 public function getLibStatut($mode = 0)
3440 {
3441 return $this->LibStatut($this->status, $mode);
3442 }
3443
3444 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3452 public function LibStatut($status, $mode = 1)
3453 {
3454 // phpcs:enable
3455 global $hookmanager;
3456
3457 // Init/load array of translation of status
3458 if (empty($this->labelStatus) || empty($this->labelStatusShort)) {
3459 global $langs;
3460 $langs->load("propal");
3461 $this->labelStatus[-1] = $langs->transnoentitiesnoconv("PropalStatusCanceled");
3462 $this->labelStatus[0] = $langs->transnoentitiesnoconv("PropalStatusDraft");
3463 $this->labelStatus[1] = $langs->transnoentitiesnoconv("PropalStatusValidated");
3464 $this->labelStatus[2] = $langs->transnoentitiesnoconv("PropalStatusSigned");
3465 $this->labelStatus[3] = $langs->transnoentitiesnoconv("PropalStatusNotSigned");
3466 $this->labelStatus[4] = $langs->transnoentitiesnoconv("PropalStatusBilled");
3467 $this->labelStatusShort[-1] = $langs->transnoentitiesnoconv("PropalStatusCanceledShort");
3468 $this->labelStatusShort[0] = $langs->transnoentitiesnoconv("PropalStatusDraftShort");
3469 $this->labelStatusShort[1] = $langs->transnoentitiesnoconv("PropalStatusValidatedShort");
3470 $this->labelStatusShort[2] = $langs->transnoentitiesnoconv("PropalStatusSignedShort");
3471 $this->labelStatusShort[3] = $langs->transnoentitiesnoconv("PropalStatusNotSignedShort");
3472 $this->labelStatusShort[4] = $langs->transnoentitiesnoconv("PropalStatusBilledShort");
3473 }
3474
3475 $statusType = '';
3476 if ($status == self::STATUS_CANCELED) {
3477 $statusType = 'status9';
3478 } elseif ($status == self::STATUS_DRAFT) {
3479 $statusType = 'status0';
3480 } elseif ($status == self::STATUS_VALIDATED) {
3481 $statusType = 'status1';
3482 } elseif ($status == self::STATUS_SIGNED) {
3483 $statusType = 'status4';
3484 } elseif ($status == self::STATUS_NOTSIGNED) {
3485 $statusType = 'status9';
3486 } elseif ($status == self::STATUS_BILLED) {
3487 $statusType = 'status6';
3488 }
3489
3490 $parameters = array('status' => $status, 'mode' => $mode);
3491 $reshook = $hookmanager->executeHooks('LibStatut', $parameters, $this); // Note that $action and $object may have been modified by hook
3492
3493 if ($reshook > 0) {
3494 return $hookmanager->resPrint;
3495 }
3496
3497 return dolGetStatus($this->labelStatus[$status], $this->labelStatusShort[$status], '', $statusType, $mode);
3498 }
3499
3500
3501 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3509 public function load_board($user, $mode)
3510 {
3511 // phpcs:enable
3512 global $langs, $hookmanager;
3513
3514 $sql = "SELECT p.rowid, p.ref, p.datec as datec, p.fin_validite as datefin, p.total_ht";
3515 if (empty($user->socid) && !$user->hasRight('societe', 'client', 'voir')) {
3516 $sql .= " FROM ".MAIN_DB_PREFIX.$this->table_element." as p";
3517 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe_commerciaux as sc ON p.fk_soc = sc.fk_soc";
3518 } else {
3519 $sql .= " FROM ".MAIN_DB_PREFIX."propal as p";
3520 }
3521 $sql .= " WHERE p.entity IN (".getEntity('propal').")";
3522 if ($mode == 'opened') {
3523 $sql .= " AND p.fk_statut = ".self::STATUS_VALIDATED;
3524 }
3525 if ($mode == 'signed') {
3526 $sql .= " AND p.fk_statut = ".self::STATUS_SIGNED;
3527 }
3528
3529 if (empty($user->socid) && !$user->hasRight('societe', 'client', 'voir')) {
3530 $sql .= " AND sc.fk_user = ".((int) $user->id);
3531 }
3532 if ($user->socid) {
3533 $sql .= " AND p.fk_soc = ".((int) $user->socid);
3534 }
3535
3536 // If the internal user must only see his customers, force searching by him
3537 $search_sale = 0;
3538 if (empty($user->socid) && !$user->hasRight('societe', 'client', 'voir')) {
3539 $search_sale = $user->id;
3540 }
3541 // Search on sale representative
3542 if ($search_sale && $search_sale != '-1') {
3543 if ($search_sale == -2) {
3544 $sql .= " AND NOT EXISTS (SELECT sc.fk_soc FROM ".MAIN_DB_PREFIX."societe_commerciaux as sc WHERE sc.fk_soc = p.fk_soc)";
3545 } elseif ($search_sale > 0) {
3546 $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).")";
3547 }
3548 }
3549 if ($user->socid) {
3550 $sql .= " AND p.fk_soc = ".((int) $user->socid);
3551 }
3552 // Add where from hooks
3553 $parameters = array('socid' => $user->socid);
3554 $reshook = $hookmanager->executeHooks('printFieldListWhere', $parameters, $this); // Note that $action and $object may have been modified by hook
3555 $sql .= $hookmanager->resPrint;
3556
3557 $resql = $this->db->query($sql);
3558 if ($resql) {
3559 $langs->load("propal");
3560 $now = dol_now();
3561
3562 $delay_warning = 0;
3563 $status = 0;
3564 $label = $labelShort = '';
3565 if ($mode == 'opened') {
3566 $delay_warning = getWarningDelay('propal', 'cloture');
3567 $status = self::STATUS_VALIDATED;
3568 $label = $langs->transnoentitiesnoconv("PropalsToClose");
3569 $labelShort = $langs->transnoentitiesnoconv("ToAcceptRefuse");
3570 }
3571 if ($mode == 'signed') {
3572 $delay_warning = getWarningDelay('propal', 'facturation');
3573 $status = self::STATUS_SIGNED;
3574 $label = $langs->trans("PropalsToBill"); // We set here bill but may be billed or ordered
3575 $labelShort = $langs->trans("ToBill");
3576 }
3577
3578 $response = new WorkboardResponse();
3579 $response->warning_delay = $delay_warning / 60 / 60 / 24;
3580 $response->label = $label;
3581 $response->labelShort = $labelShort;
3582 $response->url = dolBuildUrl(DOL_URL_ROOT.'/comm/propal/list.php', ['search_status' => $status, 'mainmenu' => 'commercial', 'leftmenu' => 'propals']);
3583 $response->url_late = dolBuildUrl(DOL_URL_ROOT.'/comm/propal/list.php', ['search_option' => 'late', 'mainmenu' => 'commercial', 'leftmenu' => 'propals', 'sortfield' => 'p.datep', 'sortorder' => 'asc']);
3584 $response->img = img_object('', "propal");
3585
3586 // This assignment in condition is not a bug. It allows walking the results.
3587 while ($obj = $this->db->fetch_object($resql)) {
3588 $response->nbtodo++;
3589 $response->total += $obj->total_ht;
3590
3591 if ($mode == 'opened') {
3592 $datelimit = $this->db->jdate($obj->datefin);
3593 if ($datelimit < ($now - $delay_warning)) {
3594 $response->nbtodolate++;
3595 }
3596 }
3597 // TODO Definir regle des propales a facturer en retard
3598 // if ($mode == 'signed' && ! count($this->FactureListeArray($obj->rowid))) $this->nbtodolate++;
3599 }
3600
3601 return $response;
3602 } else {
3603 $this->error = $this->db->error();
3604 return -1;
3605 }
3606 }
3607
3608
3617 public function initAsSpecimen($param = array())
3618 {
3619 global $conf, $langs;
3620
3621 // Load array of products prodids
3622 $num_prods = 0;
3623 $prodids = array();
3624 $sql = "SELECT rowid";
3625 $sql .= " FROM ".MAIN_DB_PREFIX."product";
3626 $sql .= " WHERE entity IN (".getEntity('product').")";
3627 if (array_key_exists('tosell', $param)) {
3628 $sql .= " AND tosell = ".((int) $param['tosell']);
3629 }
3630 $sql .= $this->db->plimit(100);
3631
3632 $resql = $this->db->query($sql);
3633 if ($resql) {
3634 $num_prods = $this->db->num_rows($resql);
3635 $i = 0;
3636 while ($i < $num_prods) {
3637 $i++;
3638 $row = $this->db->fetch_row($resql);
3639 $prodids[$i] = $row[0];
3640 }
3641 }
3642
3643 // Initialise parameters
3644 $this->id = 0;
3645 $this->ref = 'SPECIMEN';
3646 $this->ref_customer = 'NEMICEPS';
3647 $this->ref_client = 'NEMICEPS';
3648 $this->specimen = 1;
3649 $this->socid = 1;
3650 $this->date = time();
3651 $this->fin_validite = $this->date + 3600 * 24 * 30;
3652 $this->cond_reglement_id = 1;
3653 $this->cond_reglement_code = 'RECEP';
3654 $this->mode_reglement_id = 7;
3655 $this->mode_reglement_code = 'CHQ';
3656 $this->availability_id = 1;
3657 $this->availability_code = 'AV_NOW';
3658 $this->demand_reason_id = 1;
3659 $this->demand_reason_code = 'SRC_00';
3660 $this->note_public = 'This is a comment (public)';
3661 $this->note_private = 'This is a comment (private)';
3662
3663 $this->multicurrency_tx = 1;
3664 $this->multicurrency_code = getDolCurrency();
3665
3666 // Lines
3667 $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)
3668 $xnbp = 0;
3669 while ($xnbp < $nbp) {
3670 $line = new PropaleLigne($this->db);
3671 $line->desc = $langs->trans("Description")." ".$xnbp;
3672 $line->qty = 1;
3673 $line->subprice = 100;
3674 $line->tva_tx = 20;
3675 $line->localtax1_tx = 0;
3676 $line->localtax2_tx = 0;
3677 if ($xnbp == 2) {
3678 $line->total_ht = 50;
3679 $line->total_ttc = 60;
3680 $line->total_tva = 10;
3681 $line->remise_percent = 50;
3682 } else {
3683 $line->total_ht = 100;
3684 $line->total_ttc = 120;
3685 $line->total_tva = 20;
3686 $line->remise_percent = 00;
3687 }
3688
3689 if ($num_prods > 0) {
3690 $prodid = mt_rand(1, $num_prods);
3691 $line->fk_product = $prodids[$prodid];
3692 $line->product_ref = 'SPECIMEN';
3693 }
3694
3695 $this->lines[$xnbp] = $line;
3696
3697 $this->total_ht += $line->total_ht;
3698 $this->total_tva += $line->total_tva;
3699 $this->total_ttc += $line->total_ttc;
3700
3701 $xnbp++;
3702 }
3703
3704 return 1;
3705 }
3706
3712 public function loadStateBoard()
3713 {
3714 global $user, $hookmanager;
3715
3716 $this->nb = array();
3717
3718 $sql = "SELECT count(p.rowid) as nb";
3719 $sql .= " FROM ".MAIN_DB_PREFIX.$this->table_element." as p";
3720 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe as s ON p.fk_soc = s.rowid";
3721 $sql .= " WHERE p.entity IN (".getEntity('propal').")";
3722
3723 // If the internal user must only see his customers, force searching by him
3724 $search_sale = 0;
3725 if (empty($user->socid) && !$user->hasRight('societe', 'client', 'voir')) {
3726 $search_sale = $user->id;
3727 }
3728 // Search on sale representative
3729 if ($search_sale && $search_sale != '-1') {
3730 if ($search_sale == -2) {
3731 $sql .= " AND NOT EXISTS (SELECT sc.fk_soc FROM ".MAIN_DB_PREFIX."societe_commerciaux as sc WHERE sc.fk_soc = p.fk_soc)";
3732 } elseif ($search_sale > 0) {
3733 $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).")";
3734 }
3735 }
3736
3737 // Add where from hooks
3738 $parameters = array();
3739 $hookmanager->executeHooks('printFieldListWhere', $parameters, $this); // Note that $action and $object may have been modified by hook
3740 $sql .= $hookmanager->resPrint;
3741 $resql = $this->db->query($sql);
3742 if ($resql) {
3743 // This assignment in condition is not a bug. It allows walking the results.
3744 while ($obj = $this->db->fetch_object($resql)) {
3745 $this->nb["proposals"] = $obj->nb;
3746 }
3747 $this->db->free($resql);
3748 return 1;
3749 } else {
3750 dol_print_error($this->db);
3751 $this->error = $this->db->error();
3752 return -1;
3753 }
3754 }
3755
3756
3764 public function getNextNumRef($soc)
3765 {
3766 global $conf, $langs;
3767 $langs->load("propal");
3768
3769 $classname = getDolGlobalString('PROPALE_ADDON');
3770
3771 if (!empty($classname)) {
3772 $mybool = false;
3773
3774 $file = $classname.".php";
3775
3776 // Include file with class
3777 $dirmodels = array_merge(array('/'), (array) $conf->modules_parts['models']);
3778 foreach ($dirmodels as $reldir) {
3779 $dir = dol_buildpath($reldir."core/modules/propale/");
3780
3781 // Load file with numbering class (if found)
3782 $mybool = ((bool) @include_once $dir.$file) || $mybool;
3783 }
3784
3785 if (!$mybool) {
3786 dol_print_error(null, "Failed to include file ".$file);
3787 return '';
3788 }
3789
3790 $obj = new $classname();
3791 '@phan-var-force ModeleNumRefPropales $obj';
3794 $numref = $obj->getNextValue($soc, $this);
3795
3796 if ($numref != "") {
3797 return $numref;
3798 } else {
3799 $this->error = $obj->error;
3800 //dol_print_error($db,"Propale::getNextNumRef ".$obj->error);
3801 return "";
3802 }
3803 } else {
3804 $langs->load("errors");
3805 print $langs->trans("Error")." ".$langs->trans("ErrorModuleSetupNotComplete", $langs->transnoentitiesnoconv("Proposal"));
3806 return "";
3807 }
3808 }
3809
3816 public function getTooltipContentArray($params)
3817 {
3818 global $conf, $langs, $user;
3819
3820 $langs->load('propal');
3821 $datas = [];
3822 $nofetch = !empty($params['nofetch']);
3823
3824 if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
3825 return ['optimize' => $langs->trans("Proposal")];
3826 }
3827 if ($user->hasRight('propal', 'lire')) {
3828 $datas['picto'] = img_picto('', $this->picto, '', 0, 0, 0, '', 'paddingrightonly').'<u>'.$langs->trans("Proposal").'</u>';
3829 if (isset($this->status)) {
3830 $datas['status'] = ' '.$this->getLibStatut(5);
3831 }
3832 if (!empty($this->ref)) {
3833 $datas['ref'] = '<br><b>'.$langs->trans('Ref').':</b> '.$this->ref;
3834 }
3835 if (!$nofetch) {
3836 $langs->load('companies');
3837 if (empty($this->thirdparty)) {
3838 $this->fetch_thirdparty();
3839 }
3840 $datas['customer'] = '<br><b>'.$langs->trans('Customer').':</b> '.$this->thirdparty->getNomUrl(1, '', 0, 1);
3841 }
3842 if (!empty($this->ref_customer)) {
3843 $datas['refcustomer'] = '<br><b>'.$langs->trans('RefCustomer').':</b> '.$this->ref_customer;
3844 }
3845 if (!$nofetch) {
3846 $langs->load('project');
3847 if (is_null($this->project) || (is_object($this->project) && $this->project->isEmpty())) {
3848 $res = $this->fetchProject();
3849 if ($res > 0 && $this->project instanceof Project) {
3850 $datas['project'] = '<br><b>'.$langs->trans('Project').':</b> '.$this->project->getNomUrl(1, '', 0, '1');
3851 }
3852 }
3853 }
3854 if (!empty($this->total_ht)) {
3855 $datas['amountht'] = '<br><b>'.$langs->trans('AmountHT').':</b> '.price($this->total_ht, 0, $langs, 0, -1, -1, $conf->currency);
3856 }
3857 if (!empty($this->total_tva)) {
3858 $datas['vat'] = '<br><b>'.$langs->trans('VAT').':</b> '.price($this->total_tva, 0, $langs, 0, -1, -1, $conf->currency);
3859 }
3860 if (!empty($this->total_ttc)) {
3861 $datas['amountttc'] = '<br><b>'.$langs->trans('AmountTTC').':</b> '.price($this->total_ttc, 0, $langs, 0, -1, -1, $conf->currency);
3862 }
3863 if (!empty($this->date)) {
3864 $datas['date'] = '<br><b>'.$langs->trans('Date').':</b> '.dol_print_date($this->date, 'day');
3865 }
3866 if (!empty($this->date_signature)) {
3867 $datas['datesignature'] = '<br><b>'.$langs->trans('DateSigning').':</b> '.dol_print_date($this->date_signature, 'day');
3868 }
3869 if (!empty($this->delivery_date)) {
3870 $datas['deliverydate'] = '<br><b>'.$langs->trans('DeliveryDate').':</b> '.dol_print_date($this->delivery_date, 'dayhour');
3871 }
3872 }
3873
3874 return $datas;
3875 }
3876
3888 public function getNomUrl($withpicto = 0, $option = '', $get_params = '', $notooltip = 0, $save_lastsearch_value = -1, $addlinktonotes = -1)
3889 {
3890 global $langs, $conf, $user, $hookmanager;
3891
3892 if (!empty($conf->dol_no_mouse_hover)) {
3893 $notooltip = 1; // Force disable tooltips
3894 }
3895
3896 $result = '';
3897 $params = [
3898 'id' => $this->id,
3899 'objecttype' => $this->element,
3900 'option' => $option,
3901 'nofetch' => 1,
3902 ];
3903 $classfortooltip = 'classfortooltip';
3904 $dataparams = '';
3905 if (getDolGlobalInt('MAIN_ENABLE_AJAX_TOOLTIP')) {
3906 $classfortooltip = 'classforajaxtooltip';
3907 $dataparams = ' data-params="'.dol_escape_htmltag(json_encode($params)).'"';
3908 $label = '';
3909 } else {
3910 $label = implode($this->getTooltipContentArray($params));
3911 }
3912
3913 $baseurl = '';
3914 $query = [];
3915 if ($user->hasRight('propal', 'lire')) {
3916 parse_str($get_params, $query);
3917 $query = array_merge($query, ['id' => $this->id]);
3918 if ($option == '') {
3919 $baseurl = DOL_URL_ROOT . '/comm/propal/card.php';
3920 } elseif ($option == 'compta') { // deprecated
3921 $baseurl = DOL_URL_ROOT . '/comm/propal/card.php';
3922 } elseif ($option == 'expedition') {
3923 $baseurl = DOL_URL_ROOT . '/expedition/propal.php';
3924 } elseif ($option == 'document') {
3925 $baseurl = DOL_URL_ROOT . '/comm/propal/document.php';
3926 }
3927
3928 if ($option != 'nolink') {
3929 // Add param to save lastsearch_values or not
3930 $add_save_lastsearch_values = ($save_lastsearch_value == 1 ? 1 : 0);
3931 if ($save_lastsearch_value == -1 && isset($_SERVER["PHP_SELF"]) && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) {
3932 $add_save_lastsearch_values = 1;
3933 }
3934 if ($add_save_lastsearch_values) {
3935 $query = array_merge($query, ['save_lastsearch_values' => 1]);
3936 }
3937 }
3938 }
3939 $url = dolBuildUrl($baseurl, $query);
3940
3941 $linkclose = '';
3942 if (empty($notooltip) && $user->hasRight('propal', 'lire')) {
3943 if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
3944 $label = $langs->trans("Proposal");
3945 $linkclose .= ' alt="'.dolPrintHTMLForAttribute($label).'"';
3946 }
3947 $linkclose .= ($label ? ' title="'.dolPrintHTMLForAttribute($label).'"' : ' title="tocomplete"');
3948 $linkclose .= $dataparams.' class="'.$classfortooltip.'"';
3949 }
3950
3951 $linkstart = '<a href="'.$url.'"';
3952 $linkstart .= $linkclose.'>';
3953 $linkend = '</a>';
3954
3955 $result .= $linkstart;
3956 if ($withpicto) {
3957 $result .= img_object(($notooltip ? '' : $label), ($this->picto ? $this->picto : 'generic'), (($withpicto != 2) ? 'class="paddingright"' : ''), 0, 0, $notooltip ? 0 : 1);
3958 }
3959 if ($withpicto != 2) {
3960 $result .= $this->ref;
3961 }
3962 $result .= $linkend;
3963
3964 if ($addlinktonotes >= 0) {
3965 $txttoshow = '';
3966
3967 if ($addlinktonotes == 0) {
3968 if (!empty($this->note_private) || !empty($this->note_public)) {
3969 $txttoshow = $langs->trans('ViewPrivateNote');
3970 }
3971 } elseif ($addlinktonotes == 1) {
3972 if (!empty($this->note_private)) {
3973 $txttoshow .= ($user->socid > 0 ? '' : dol_string_nohtmltag($this->note_private, 1));
3974 }
3975 } elseif ($addlinktonotes == 2) {
3976 if (!empty($this->note_public)) {
3977 $txttoshow .= dol_string_nohtmltag($this->note_public, 1);
3978 }
3979 } elseif ($addlinktonotes == 3) {
3980 if ($user->socid > 0) {
3981 if (!empty($this->note_public)) {
3982 $txttoshow .= dol_string_nohtmltag($this->note_public, 1);
3983 }
3984 } else {
3985 if (!empty($this->note_public)) {
3986 $txttoshow .= dol_string_nohtmltag($this->note_public, 1);
3987 }
3988 if (!empty($this->note_private)) {
3989 if (!empty($txttoshow)) {
3990 $txttoshow .= '<br><br>';
3991 }
3992 $txttoshow .= dol_string_nohtmltag($this->note_private, 1);
3993 }
3994 }
3995 }
3996
3997 if ($txttoshow) {
3998 $result .= ' <span class="note inline-block">';
3999 $result .= '<a href="'.dolBuildUrl(DOL_URL_ROOT.'/comm/propal/note.php', ['id' => $this->id]).'" class="classfortooltip" title="'.dol_escape_htmltag($txttoshow).'">';
4000 $result .= img_picto('', 'note');
4001 $result .= '</a>';
4002 $result .= '</span>';
4003 }
4004 }
4005
4006 global $action;
4007 $hookmanager->initHooks(array($this->element . 'dao'));
4008 $parameters = array('id' => $this->id, 'getnomurl' => &$result);
4009 $reshook = $hookmanager->executeHooks('getNomUrl', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
4010 if ($reshook > 0) {
4011 $result = $hookmanager->resPrint;
4012 } else {
4013 $result .= $hookmanager->resPrint;
4014 }
4015 return $result;
4016 }
4017
4024 public function getLinesArray($sqlforgedfilters = '')
4025 {
4026 return $this->fetch_lines(0, 0, $sqlforgedfilters);
4027 }
4028
4040 public function generateDocument($modele, $outputlangs, $hidedetails = 0, $hidedesc = 0, $hideref = 0, $moreparams = null)
4041 {
4042 $outputlangs->loadLangs(array("propale", "products"));
4043
4044 if (!dol_strlen($modele)) {
4045 $modele = getDolGlobalString('PROPALE_ADDON_PDF', 'cyan');
4046
4047 if ($this->model_pdf) {
4048 $modele = $this->model_pdf;
4049 }
4050 }
4051
4052 $modelpath = "core/modules/propale/doc/";
4053
4054 return $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams);
4055 }
4056
4065 public static function replaceThirdparty(DoliDB $dbs, $origin_id, $dest_id)
4066 {
4067 $tables = array(
4068 'propal'
4069 );
4070
4071 return CommonObject::commonReplaceThirdparty($dbs, $origin_id, $dest_id, $tables);
4072 }
4073
4082 public static function replaceProduct(DoliDB $db, $origin_id, $dest_id)
4083 {
4084 $tables = array(
4085 'propaldet'
4086 );
4087
4088 return CommonObject::commonReplaceProduct($db, $origin_id, $dest_id, $tables);
4089 }
4090
4098 public function getKanbanView($option = '', $arraydata = null)
4099 {
4100 global $langs;
4101
4102 $selected = (empty($arraydata['selected']) ? 0 : $arraydata['selected']);
4103
4104 $return = '<div class="box-flex-item box-flex-grow-zero">';
4105 $return .= '<div class="info-box info-box-sm">';
4106 $return .= '<div class="info-box-icon bg-infobox-action">';
4107 $return .= img_picto('', $this->picto);
4108 $return .= '</div>';
4109 $return .= '<div class="info-box-content">';
4110 $return .= '<span class="info-box-ref inline-block tdoverflowmax150 valignmiddle">' . $this->getNomUrl() . '</span>';
4111 if ($selected >= 0) {
4112 $return .= '<input id="cb'.$this->id.'" class="flat checkforselect fright" type="checkbox" name="toselect[]" value="'.$this->id.'"'.($selected ? ' checked="checked"' : '').'>';
4113 }
4114 if (!empty($arraydata['projectlink'])) {
4115 $return .= '<span class="info-box-ref"> | '.$arraydata['projectlink'].'</span>';
4116 }
4117 $return .= '<br>';
4118 if (is_object($this->thirdparty)) {
4119 $return .= '<div class="info-box-ref tdoverflowmax150">'.$this->thirdparty->getNomUrl(1).'</div>';
4120 }
4121 $return .= '<span class="info-box-label amount" title="'.$langs->trans("AmountHT").'">'.price($this->total_ht).'</span>';
4122 if (!empty($arraydata['authorlink'])) {
4123 $return .= ' &nbsp; <span class="info-box-label">'.$arraydata['authorlink'].'</span>';
4124 }
4125 $return .= '<br><div class="info-box-status">'.$this->getLibStatut(3).'</div>';
4126 $return .= '</div>';
4127 $return .= '</div>';
4128 $return .= '</div>';
4129 return $return;
4130 }
4131
4142 public function setCategories($categories)
4143 {
4144 require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php';
4145 return parent::setCategoriesCommon($categories, Categorie::TYPE_PROPOSAL);
4146 }
4147}
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.
commonGenerateDocument($modelspath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams=null)
Common function for all objects extending CommonObject for generating documents.
fetchProject()
Load the project with id $this->fk_project into this->project.
fetch_thirdparty($force_thirdparty_id=0)
Load the third party of object, from id $this->socid or $this->fk_soc, into this->thirdparty.
deleteObjectLinked($sourceid=null, $sourcetype='', $targetid=null, $targettype='', $rowid=0, $f_user=null, $notrigger=0)
Delete all links between an object $this.
setErrorsFromObject($object)
setErrorsFromObject
static isExistingObject($element, $id, $ref='', $ref_ext='')
Check if an object id or ref exists If you don't need or want to instantiate the object and just need...
updateRangOfLine($rowid, $rang)
Update position of line (rang)
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 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:168
global $mysoc
if(!isModEnabled('ai')||!getDolGlobalString('AI_ASSISTANT_ENABLED')) global $conf
The main.inc.php has been included so the following variable are now defined:
if(!isModEnabled('ai')||!getDolGlobalString('AI_ASSISTANT_ENABLED')) global $db
API class for accounts.
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.
$date_start
Variables from include:
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.
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 '.
dolBuildUrl($url, $params=[], $addtoken=false, $anchor='')
Return path of url.
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)
setEventMessage($mesgs, $style='mesgs', $noduplicate=0, $attop=0)
Set event message in dol_events session object.
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', ...)
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...
print $langs trans("Show") . '< td style="' . $timeColor . '" align="center"> s</td > badge status0 badge status4 badge status3 Error badge status8< td align="center">< span class="badge ' . $badge . '"></span ></td >< td align="center">< a href="#" class="button button-small" onclick="openLogModal(this)" data-req="' . dol_escape_htmltag($reqSafe) . '" data-res="' . dol_escape_htmltag($resSafe) . '" data-err="' . dol_escape_htmltag($errSafe) . '">< span class="fa fa-search-plus"></span ></a ></td ></tr >< tr >< td colspan="' . $colspan . '" class="opacitymedium"></td ></tr ></table ></div ></form > logModal none logModal none s a JSON string
buildzip.php
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
print $langs trans('Date')." left Ref 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 Paid right PaymentTypeShortLIQ right SELECT p pos_change as p datep as date
Definition receipt.php:487