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 // New system of common object return rowids and no linear id from 1 to 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 // Remove linked categories.
3165 if (!$error) {
3166 $sql = "DELETE FROM ".MAIN_DB_PREFIX."categorie_propal";
3167 $sql .= " WHERE fk_propal = ".((int) $this->id);
3168
3169 $result = $this->db->query($sql);
3170 if (!$result) {
3171 $error++;
3172 $this->error = $this->db->lasterror();
3173 $this->errors[] = $this->error;
3174 }
3175 }
3176
3177 // Delete extrafields of lines and lines
3178 if (!$error && !empty($this->table_element_line)) {
3179 $tabletodelete = $this->table_element_line;
3180 $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).")";
3181 $sql = "DELETE FROM ".MAIN_DB_PREFIX.$tabletodelete." WHERE ".$this->fk_element." = ".((int) $this->id);
3182 if (!$this->db->query($sqlef) || !$this->db->query($sql)) {
3183 $error++;
3184 $this->error = $this->db->lasterror();
3185 $this->errors[] = $this->error;
3186 dol_syslog(get_class($this)."::delete error ".$this->error, LOG_ERR);
3187 }
3188 }
3189
3190 if (!$error) {
3191 // Delete linked object
3192 $res = $this->deleteObjectLinked();
3193 if ($res < 0) {
3194 $error++;
3195 }
3196 }
3197
3198 if (!$error) {
3199 // Delete linked contacts
3200 $res = $this->delete_linked_contact();
3201 if ($res < 0) {
3202 $error++;
3203 }
3204 }
3205
3206 // Removed extrafields of object
3207 if (!$error) {
3208 $result = $this->deleteExtraFields();
3209 if ($result < 0) {
3210 $error++;
3211 dol_syslog(get_class($this)."::delete error ".$this->error, LOG_ERR);
3212 }
3213 }
3214
3215 // Delete main record
3216 if (!$error) {
3217 $sql = "DELETE FROM ".MAIN_DB_PREFIX.$this->table_element." WHERE rowid = ".((int) $this->id);
3218 $res = $this->db->query($sql);
3219 if (!$res) {
3220 $error++;
3221 $this->error = $this->db->lasterror();
3222 $this->errors[] = $this->error;
3223 dol_syslog(get_class($this)."::delete error ".$this->error, LOG_ERR);
3224 }
3225 }
3226
3227 // Delete record into ECM index and physically
3228 if (!$error) {
3229 $res = $this->deleteEcmFiles(0); // Deleting files physically is done later with the dol_delete_dir_recursive
3230 $res = $this->deleteEcmFiles(1); // Deleting files physically is done later with the dol_delete_dir_recursive
3231 if (!$res) {
3232 $error++;
3233 }
3234 }
3235
3236 if (!$error) {
3237 // We remove directory
3238 $ref = dol_sanitizeFileName($this->ref);
3239 if ($conf->propal->multidir_output[$this->entity] && !empty($this->ref)) {
3240 $dir = $conf->propal->multidir_output[$this->entity]."/".$ref;
3241 $file = $dir."/".$ref.".pdf";
3242 if (file_exists($file)) {
3243 dol_delete_preview($this);
3244
3245 if (!dol_delete_file($file, 0, 0, 0, $this)) {
3246 $this->error = 'ErrorFailToDeleteFile';
3247 $this->errors[] = $this->error;
3248 $this->db->rollback();
3249 return 0;
3250 }
3251 }
3252 if (file_exists($dir)) {
3253 $res = @dol_delete_dir_recursive($dir); // delete files physically + into ecm tables
3254 if (!$res) {
3255 $this->error = 'ErrorFailToDeleteDir';
3256 $this->errors[] = $this->error;
3257 $this->db->rollback();
3258 return 0;
3259 }
3260 }
3261 }
3262 }
3263
3264 if (!$error) {
3265 dol_syslog(get_class($this)."::delete ".$this->id." by ".$user->id, LOG_DEBUG);
3266 $this->db->commit();
3267 return 1;
3268 } else {
3269 $this->db->rollback();
3270 return -1;
3271 }
3272 }
3273
3282 public function availability($availability_id, $notrigger = 0)
3283 {
3284 global $user;
3285
3286 if ($this->status >= self::STATUS_DRAFT) {
3287 $error = 0;
3288
3289 $this->db->begin();
3290
3291 $sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element;
3292 $sql .= ' SET fk_availability = '.((int) $availability_id);
3293 $sql .= ' WHERE rowid='.((int) $this->id);
3294
3295 dol_syslog(__METHOD__.' availability('.$availability_id.')', LOG_DEBUG);
3296 $resql = $this->db->query($sql);
3297 if (!$resql) {
3298 $this->errors[] = $this->db->error();
3299 $error++;
3300 }
3301
3302 if (!$error) {
3303 $this->oldcopy = clone $this;
3304 $this->availability_id = $availability_id;
3305 }
3306
3307 if (!$notrigger && empty($error)) {
3308 // Call trigger
3309 $result = $this->call_trigger('PROPAL_MODIFY', $user);
3310 if ($result < 0) {
3311 $error++;
3312 }
3313 // End call triggers
3314 }
3315
3316 if (!$error) {
3317 $this->db->commit();
3318 return 1;
3319 } else {
3320 foreach ($this->errors as $errmsg) {
3321 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
3322 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
3323 }
3324 $this->db->rollback();
3325 return -1 * $error;
3326 }
3327 } else {
3328 $error_str = 'Propal status do not meet requirement '.$this->status;
3329 dol_syslog(__METHOD__.$error_str, LOG_ERR);
3330 $this->error = $error_str;
3331 $this->errors[] = $this->error;
3332 return -2;
3333 }
3334 }
3335
3336 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3345 public function demand_reason($demand_reason_id, $notrigger = 0)
3346 {
3347 // phpcs:enable
3348 global $user;
3349
3350 if ($this->status >= self::STATUS_DRAFT) {
3351 $error = 0;
3352
3353 $this->db->begin();
3354
3355 $sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element;
3356 $sql .= ' SET fk_input_reason = '.((int) $demand_reason_id);
3357 $sql .= ' WHERE rowid='.((int) $this->id);
3358
3359 dol_syslog(__METHOD__.' demand_reason('.$demand_reason_id.')', LOG_DEBUG);
3360 $resql = $this->db->query($sql);
3361 if (!$resql) {
3362 $this->errors[] = $this->db->error();
3363 $error++;
3364 }
3365
3366 if (!$error) {
3367 $this->oldcopy = clone $this;
3368 $this->demand_reason_id = $demand_reason_id;
3369 }
3370
3371 if (!$notrigger && empty($error)) {
3372 // Call trigger
3373 $result = $this->call_trigger('PROPAL_MODIFY', $user);
3374 if ($result < 0) {
3375 $error++;
3376 }
3377 // End call triggers
3378 }
3379
3380 if (!$error) {
3381 $this->db->commit();
3382 return 1;
3383 } else {
3384 foreach ($this->errors as $errmsg) {
3385 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
3386 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
3387 }
3388 $this->db->rollback();
3389 return -1 * $error;
3390 }
3391 } else {
3392 $error_str = 'Propal status do not meet requirement '.$this->status;
3393 dol_syslog(__METHOD__.$error_str, LOG_ERR);
3394 $this->error = $error_str;
3395 $this->errors[] = $this->error;
3396 return -2;
3397 }
3398 }
3399
3400
3407 public function info($id)
3408 {
3409 $sql = "SELECT c.rowid, ";
3410 $sql .= " c.datec, c.date_valid as datev, c.date_signature, c.date_cloture,";
3411 $sql .= " c.fk_user_author, c.fk_user_valid, c.fk_user_signature, c.fk_user_cloture";
3412 $sql .= " FROM ".MAIN_DB_PREFIX.$this->table_element." as c";
3413 $sql .= " WHERE c.rowid = ".((int) $id);
3414
3415 $result = $this->db->query($sql);
3416
3417 if ($result) {
3418 if ($this->db->num_rows($result)) {
3419 $obj = $this->db->fetch_object($result);
3420
3421 $this->id = $obj->rowid;
3422
3423 $this->date_creation = $this->db->jdate($obj->datec);
3424 $this->date_validation = $this->db->jdate($obj->datev);
3425 $this->date_signature = $this->db->jdate($obj->date_signature);
3426 $this->date_cloture = $this->db->jdate($obj->date_cloture);
3427
3428 $this->user_creation_id = $obj->fk_user_author;
3429 $this->user_validation_id = $obj->fk_user_valid;
3430
3431 if ($obj->fk_user_signature) {
3432 $user_signature = new User($this->db);
3433 $user_signature->fetch($obj->fk_user_signature);
3434 $this->user_signature = $user_signature;
3435 }
3436
3437 $this->user_closing_id = $obj->fk_user_cloture;
3438 }
3439 $this->db->free($result);
3440 } else {
3441 dol_print_error($this->db);
3442 }
3443 }
3444
3445
3452 public function getLibStatut($mode = 0)
3453 {
3454 return $this->LibStatut($this->status, $mode);
3455 }
3456
3457 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3465 public function LibStatut($status, $mode = 1)
3466 {
3467 // phpcs:enable
3468 global $hookmanager;
3469
3470 // Init/load array of translation of status
3471 if (empty($this->labelStatus) || empty($this->labelStatusShort)) {
3472 global $langs;
3473 $langs->load("propal");
3474 $this->labelStatus[-1] = $langs->transnoentitiesnoconv("PropalStatusCanceled");
3475 $this->labelStatus[0] = $langs->transnoentitiesnoconv("PropalStatusDraft");
3476 $this->labelStatus[1] = $langs->transnoentitiesnoconv("PropalStatusValidated");
3477 $this->labelStatus[2] = $langs->transnoentitiesnoconv("PropalStatusSigned");
3478 $this->labelStatus[3] = $langs->transnoentitiesnoconv("PropalStatusNotSigned");
3479 $this->labelStatus[4] = $langs->transnoentitiesnoconv("PropalStatusBilled");
3480 $this->labelStatusShort[-1] = $langs->transnoentitiesnoconv("PropalStatusCanceledShort");
3481 $this->labelStatusShort[0] = $langs->transnoentitiesnoconv("PropalStatusDraftShort");
3482 $this->labelStatusShort[1] = $langs->transnoentitiesnoconv("PropalStatusValidatedShort");
3483 $this->labelStatusShort[2] = $langs->transnoentitiesnoconv("PropalStatusSignedShort");
3484 $this->labelStatusShort[3] = $langs->transnoentitiesnoconv("PropalStatusNotSignedShort");
3485 $this->labelStatusShort[4] = $langs->transnoentitiesnoconv("PropalStatusBilledShort");
3486 }
3487
3488 $statusType = '';
3489 if ($status == self::STATUS_CANCELED) {
3490 $statusType = 'status9';
3491 } elseif ($status == self::STATUS_DRAFT) {
3492 $statusType = 'status0';
3493 } elseif ($status == self::STATUS_VALIDATED) {
3494 $statusType = 'status1';
3495 } elseif ($status == self::STATUS_SIGNED) {
3496 $statusType = 'status4';
3497 } elseif ($status == self::STATUS_NOTSIGNED) {
3498 $statusType = 'status9';
3499 } elseif ($status == self::STATUS_BILLED) {
3500 $statusType = 'status6';
3501 }
3502
3503 $parameters = array('status' => $status, 'mode' => $mode);
3504 $reshook = $hookmanager->executeHooks('LibStatut', $parameters, $this); // Note that $action and $object may have been modified by hook
3505
3506 if ($reshook > 0) {
3507 return $hookmanager->resPrint;
3508 }
3509
3510 return dolGetStatus($this->labelStatus[$status], $this->labelStatusShort[$status], '', $statusType, $mode);
3511 }
3512
3513
3514 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3522 public function load_board($user, $mode)
3523 {
3524 // phpcs:enable
3525 global $langs, $hookmanager;
3526
3527 $sql = "SELECT p.rowid, p.ref, p.datec as datec, p.fin_validite as datefin, p.total_ht";
3528 if (empty($user->socid) && !$user->hasRight('societe', 'client', 'voir')) {
3529 $sql .= " FROM ".MAIN_DB_PREFIX.$this->table_element." as p";
3530 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe_commerciaux as sc ON p.fk_soc = sc.fk_soc";
3531 } else {
3532 $sql .= " FROM ".MAIN_DB_PREFIX."propal as p";
3533 }
3534 $sql .= " WHERE p.entity IN (".getEntity('propal').")";
3535 if ($mode == 'opened') {
3536 $sql .= " AND p.fk_statut = ".self::STATUS_VALIDATED;
3537 }
3538 if ($mode == 'signed') {
3539 $sql .= " AND p.fk_statut = ".self::STATUS_SIGNED;
3540 }
3541
3542 if (empty($user->socid) && !$user->hasRight('societe', 'client', 'voir')) {
3543 $sql .= " AND sc.fk_user = ".((int) $user->id);
3544 }
3545 if ($user->socid) {
3546 $sql .= " AND p.fk_soc = ".((int) $user->socid);
3547 }
3548
3549 // If the internal user must only see his customers, force searching by him
3550 $search_sale = 0;
3551 if (empty($user->socid) && !$user->hasRight('societe', 'client', 'voir')) {
3552 $search_sale = $user->id;
3553 }
3554 // Search on sale representative
3555 if ($search_sale && $search_sale != '-1') {
3556 if ($search_sale == -2) {
3557 $sql .= " AND NOT EXISTS (SELECT sc.fk_soc FROM ".MAIN_DB_PREFIX."societe_commerciaux as sc WHERE sc.fk_soc = p.fk_soc)";
3558 } elseif ($search_sale > 0) {
3559 $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).")";
3560 }
3561 }
3562 if ($user->socid) {
3563 $sql .= " AND p.fk_soc = ".((int) $user->socid);
3564 }
3565 // Add where from hooks
3566 $parameters = array('socid' => $user->socid);
3567 $reshook = $hookmanager->executeHooks('printFieldListWhere', $parameters, $this); // Note that $action and $object may have been modified by hook
3568 $sql .= $hookmanager->resPrint;
3569
3570 $resql = $this->db->query($sql);
3571 if ($resql) {
3572 $langs->load("propal");
3573 $now = dol_now();
3574
3575 $delay_warning = 0;
3576 $status = 0;
3577 $label = $labelShort = '';
3578 if ($mode == 'opened') {
3579 $delay_warning = getWarningDelay('propal', 'cloture');
3580 $status = self::STATUS_VALIDATED;
3581 $label = $langs->transnoentitiesnoconv("PropalsToClose");
3582 $labelShort = $langs->transnoentitiesnoconv("ToAcceptRefuse");
3583 }
3584 if ($mode == 'signed') {
3585 $delay_warning = getWarningDelay('propal', 'facturation');
3586 $status = self::STATUS_SIGNED;
3587 $label = $langs->trans("PropalsToBill"); // We set here bill but may be billed or ordered
3588 $labelShort = $langs->trans("ToBill");
3589 }
3590
3591 $response = new WorkboardResponse();
3592 $response->warning_delay = $delay_warning / 60 / 60 / 24;
3593 $response->label = $label;
3594 $response->labelShort = $labelShort;
3595 $response->url = dolBuildUrl(DOL_URL_ROOT.'/comm/propal/list.php', ['search_status' => $status, 'mainmenu' => 'commercial', 'leftmenu' => 'propals']);
3596 $response->url_late = dolBuildUrl(DOL_URL_ROOT.'/comm/propal/list.php', ['search_option' => 'late', 'mainmenu' => 'commercial', 'leftmenu' => 'propals', 'sortfield' => 'p.datep', 'sortorder' => 'asc']);
3597 $response->img = img_object('', "propal");
3598
3599 // This assignment in condition is not a bug. It allows walking the results.
3600 while ($obj = $this->db->fetch_object($resql)) {
3601 $response->nbtodo++;
3602 $response->total += $obj->total_ht;
3603
3604 if ($mode == 'opened') {
3605 $datelimit = $this->db->jdate($obj->datefin);
3606 if ($datelimit < ($now - $delay_warning)) {
3607 $response->nbtodolate++;
3608 }
3609 }
3610 // TODO Definir regle des propales a facturer en retard
3611 // if ($mode == 'signed' && ! count($this->FactureListeArray($obj->rowid))) $this->nbtodolate++;
3612 }
3613
3614 return $response;
3615 } else {
3616 $this->error = $this->db->error();
3617 return -1;
3618 }
3619 }
3620
3621
3630 public function initAsSpecimen($param = array())
3631 {
3632 global $conf, $langs;
3633
3634 // Load array of products prodids
3635 $num_prods = 0;
3636 $prodids = array();
3637 $sql = "SELECT rowid";
3638 $sql .= " FROM ".MAIN_DB_PREFIX."product";
3639 $sql .= " WHERE entity IN (".getEntity('product').")";
3640 if (array_key_exists('tosell', $param)) {
3641 $sql .= " AND tosell = ".((int) $param['tosell']);
3642 }
3643 $sql .= $this->db->plimit(100);
3644
3645 $resql = $this->db->query($sql);
3646 if ($resql) {
3647 $num_prods = $this->db->num_rows($resql);
3648 $i = 0;
3649 while ($i < $num_prods) {
3650 $i++;
3651 $row = $this->db->fetch_row($resql);
3652 $prodids[$i] = $row[0];
3653 }
3654 }
3655
3656 // Initialise parameters
3657 $this->id = 0;
3658 $this->ref = 'SPECIMEN';
3659 $this->ref_customer = 'NEMICEPS';
3660 $this->ref_client = 'NEMICEPS';
3661 $this->specimen = 1;
3662 $this->socid = 1;
3663 $this->date = time();
3664 $this->fin_validite = $this->date + 3600 * 24 * 30;
3665 $this->cond_reglement_id = 1;
3666 $this->cond_reglement_code = 'RECEP';
3667 $this->mode_reglement_id = 7;
3668 $this->mode_reglement_code = 'CHQ';
3669 $this->availability_id = 1;
3670 $this->availability_code = 'AV_NOW';
3671 $this->demand_reason_id = 1;
3672 $this->demand_reason_code = 'SRC_00';
3673 $this->note_public = 'This is a comment (public)';
3674 $this->note_private = 'This is a comment (private)';
3675
3676 $this->multicurrency_tx = 1;
3677 $this->multicurrency_code = getDolCurrency();
3678
3679 // Lines
3680 $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)
3681 $xnbp = 0;
3682 while ($xnbp < $nbp) {
3683 $line = new PropaleLigne($this->db);
3684 $line->desc = $langs->trans("Description")." ".$xnbp;
3685 $line->qty = 1;
3686 $line->subprice = 100;
3687 $line->tva_tx = 20;
3688 $line->localtax1_tx = 0;
3689 $line->localtax2_tx = 0;
3690 if ($xnbp == 2) {
3691 $line->total_ht = 50;
3692 $line->total_ttc = 60;
3693 $line->total_tva = 10;
3694 $line->remise_percent = 50;
3695 } else {
3696 $line->total_ht = 100;
3697 $line->total_ttc = 120;
3698 $line->total_tva = 20;
3699 $line->remise_percent = 00;
3700 }
3701
3702 if ($num_prods > 0) {
3703 $prodid = mt_rand(1, $num_prods);
3704 $line->fk_product = $prodids[$prodid];
3705 $line->product_ref = 'SPECIMEN';
3706 }
3707
3708 $this->lines[$xnbp] = $line;
3709
3710 $this->total_ht += $line->total_ht;
3711 $this->total_tva += $line->total_tva;
3712 $this->total_ttc += $line->total_ttc;
3713
3714 $xnbp++;
3715 }
3716
3717 return 1;
3718 }
3719
3725 public function loadStateBoard()
3726 {
3727 global $user, $hookmanager;
3728
3729 $this->nb = array();
3730
3731 $sql = "SELECT count(p.rowid) as nb";
3732 $sql .= " FROM ".MAIN_DB_PREFIX.$this->table_element." as p";
3733 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe as s ON p.fk_soc = s.rowid";
3734 $sql .= " WHERE p.entity IN (".getEntity('propal').")";
3735
3736 // If the internal user must only see his customers, force searching by him
3737 $search_sale = 0;
3738 if (empty($user->socid) && !$user->hasRight('societe', 'client', 'voir')) {
3739 $search_sale = $user->id;
3740 }
3741 // Search on sale representative
3742 if ($search_sale && $search_sale != '-1') {
3743 if ($search_sale == -2) {
3744 $sql .= " AND NOT EXISTS (SELECT sc.fk_soc FROM ".MAIN_DB_PREFIX."societe_commerciaux as sc WHERE sc.fk_soc = p.fk_soc)";
3745 } elseif ($search_sale > 0) {
3746 $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).")";
3747 }
3748 }
3749
3750 // Add where from hooks
3751 $parameters = array();
3752 $hookmanager->executeHooks('printFieldListWhere', $parameters, $this); // Note that $action and $object may have been modified by hook
3753 $sql .= $hookmanager->resPrint;
3754 $resql = $this->db->query($sql);
3755 if ($resql) {
3756 // This assignment in condition is not a bug. It allows walking the results.
3757 while ($obj = $this->db->fetch_object($resql)) {
3758 $this->nb["proposals"] = $obj->nb;
3759 }
3760 $this->db->free($resql);
3761 return 1;
3762 } else {
3763 dol_print_error($this->db);
3764 $this->error = $this->db->error();
3765 return -1;
3766 }
3767 }
3768
3769
3777 public function getNextNumRef($soc)
3778 {
3779 global $conf, $langs;
3780 $langs->load("propal");
3781
3782 $classname = getDolGlobalString('PROPALE_ADDON');
3783
3784 if (!empty($classname)) {
3785 $mybool = false;
3786
3787 $file = $classname.".php";
3788
3789 // Include file with class
3790 $dirmodels = array_merge(array('/'), (array) $conf->modules_parts['models']);
3791 foreach ($dirmodels as $reldir) {
3792 $dir = dol_buildpath($reldir."core/modules/propale/");
3793
3794 // Load file with numbering class (if found)
3795 $mybool = ((bool) @include_once $dir.$file) || $mybool;
3796 }
3797
3798 if (!$mybool) {
3799 dol_print_error(null, "Failed to include file ".$file);
3800 return '';
3801 }
3802
3803 $obj = new $classname();
3804 '@phan-var-force ModeleNumRefPropales $obj';
3807 $numref = $obj->getNextValue($soc, $this);
3808
3809 if ($numref != "") {
3810 return $numref;
3811 } else {
3812 $this->error = $obj->error;
3813 //dol_print_error($db,"Propale::getNextNumRef ".$obj->error);
3814 return "";
3815 }
3816 } else {
3817 $langs->load("errors");
3818 print $langs->trans("Error")." ".$langs->trans("ErrorModuleSetupNotComplete", $langs->transnoentitiesnoconv("Proposal"));
3819 return "";
3820 }
3821 }
3822
3829 public function getTooltipContentArray($params)
3830 {
3831 global $conf, $langs, $user;
3832
3833 $langs->load('propal');
3834 $datas = [];
3835 $nofetch = !empty($params['nofetch']);
3836
3837 if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
3838 return ['optimize' => $langs->trans("Proposal")];
3839 }
3840 if ($user->hasRight('propal', 'lire')) {
3841 $datas['picto'] = img_picto('', $this->picto, '', 0, 0, 0, '', 'paddingrightonly').'<u>'.$langs->trans("Proposal").'</u>';
3842 if (isset($this->status)) {
3843 $datas['status'] = ' '.$this->getLibStatut(5);
3844 }
3845 if (!empty($this->ref)) {
3846 $datas['ref'] = '<br><b>'.$langs->trans('Ref').':</b> '.$this->ref;
3847 }
3848 if (!$nofetch) {
3849 $langs->load('companies');
3850 if (empty($this->thirdparty)) {
3851 $this->fetch_thirdparty();
3852 }
3853 $datas['customer'] = '<br><b>'.$langs->trans('Customer').':</b> '.$this->thirdparty->getNomUrl(1, '', 0, 1);
3854 }
3855 if (!empty($this->ref_customer)) {
3856 $datas['refcustomer'] = '<br><b>'.$langs->trans('RefCustomer').':</b> '.$this->ref_customer;
3857 }
3858 if (!$nofetch) {
3859 $langs->load('project');
3860 if (is_null($this->project) || (is_object($this->project) && $this->project->isEmpty())) {
3861 $res = $this->fetchProject();
3862 if ($res > 0 && $this->project instanceof Project) {
3863 $datas['project'] = '<br><b>'.$langs->trans('Project').':</b> '.$this->project->getNomUrl(1, '', 0, '1');
3864 }
3865 }
3866 }
3867 if (!empty($this->total_ht)) {
3868 $datas['amountht'] = '<br><b>'.$langs->trans('AmountHT').':</b> '.price($this->total_ht, 0, $langs, 0, -1, -1, $conf->currency);
3869 }
3870 if (!empty($this->total_tva)) {
3871 $datas['vat'] = '<br><b>'.$langs->trans('VAT').':</b> '.price($this->total_tva, 0, $langs, 0, -1, -1, $conf->currency);
3872 }
3873 if (!empty($this->total_ttc)) {
3874 $datas['amountttc'] = '<br><b>'.$langs->trans('AmountTTC').':</b> '.price($this->total_ttc, 0, $langs, 0, -1, -1, $conf->currency);
3875 }
3876 if (!empty($this->date)) {
3877 $datas['date'] = '<br><b>'.$langs->trans('Date').':</b> '.dol_print_date($this->date, 'day');
3878 }
3879 if (!empty($this->date_signature)) {
3880 $datas['datesignature'] = '<br><b>'.$langs->trans('DateSigning').':</b> '.dol_print_date($this->date_signature, 'day');
3881 }
3882 if (!empty($this->delivery_date)) {
3883 $datas['deliverydate'] = '<br><b>'.$langs->trans('DeliveryDate').':</b> '.dol_print_date($this->delivery_date, 'dayhour');
3884 }
3885 }
3886
3887 return $datas;
3888 }
3889
3901 public function getNomUrl($withpicto = 0, $option = '', $get_params = '', $notooltip = 0, $save_lastsearch_value = -1, $addlinktonotes = -1)
3902 {
3903 global $langs, $conf, $user, $hookmanager;
3904
3905 if (!empty($conf->dol_no_mouse_hover)) {
3906 $notooltip = 1; // Force disable tooltips
3907 }
3908
3909 $result = '';
3910 $params = [
3911 'id' => $this->id,
3912 'objecttype' => $this->element,
3913 'option' => $option,
3914 'nofetch' => 1,
3915 ];
3916 $classfortooltip = 'classfortooltip';
3917 $dataparams = '';
3918 if (getDolGlobalInt('MAIN_ENABLE_AJAX_TOOLTIP')) {
3919 $classfortooltip = 'classforajaxtooltip';
3920 $dataparams = ' data-params="'.dol_escape_htmltag(json_encode($params)).'"';
3921 $label = '';
3922 } else {
3923 $label = implode($this->getTooltipContentArray($params));
3924 }
3925
3926 $baseurl = '';
3927 $query = [];
3928 if ($user->hasRight('propal', 'lire')) {
3929 parse_str($get_params, $query);
3930 $query = array_merge($query, ['id' => $this->id]);
3931 if ($option == '') {
3932 $baseurl = DOL_URL_ROOT . '/comm/propal/card.php';
3933 } elseif ($option == 'compta') { // deprecated
3934 $baseurl = DOL_URL_ROOT . '/comm/propal/card.php';
3935 } elseif ($option == 'expedition') {
3936 $baseurl = DOL_URL_ROOT . '/expedition/propal.php';
3937 } elseif ($option == 'document') {
3938 $baseurl = DOL_URL_ROOT . '/comm/propal/document.php';
3939 }
3940
3941 if ($option != 'nolink') {
3942 // Add param to save lastsearch_values or not
3943 $add_save_lastsearch_values = ($save_lastsearch_value == 1 ? 1 : 0);
3944 if ($save_lastsearch_value == -1 && isset($_SERVER["PHP_SELF"]) && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) {
3945 $add_save_lastsearch_values = 1;
3946 }
3947 if ($add_save_lastsearch_values) {
3948 $query = array_merge($query, ['save_lastsearch_values' => 1]);
3949 }
3950 }
3951 }
3952 $url = dolBuildUrl($baseurl, $query);
3953
3954 $linkclose = '';
3955 if (empty($notooltip) && $user->hasRight('propal', 'lire')) {
3956 if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
3957 $label = $langs->trans("Proposal");
3958 $linkclose .= ' alt="'.dolPrintHTMLForAttribute($label).'"';
3959 }
3960 $linkclose .= ($label ? ' title="'.dolPrintHTMLForAttribute($label).'"' : ' title="tocomplete"');
3961 $linkclose .= $dataparams.' class="'.$classfortooltip.'"';
3962 }
3963
3964 $linkstart = '<a href="'.$url.'"';
3965 $linkstart .= $linkclose.'>';
3966 $linkend = '</a>';
3967
3968 $result .= $linkstart;
3969 if ($withpicto) {
3970 $result .= img_object(($notooltip ? '' : $label), ($this->picto ? $this->picto : 'generic'), (($withpicto != 2) ? 'class="paddingright"' : ''), 0, 0, $notooltip ? 0 : 1);
3971 }
3972 if ($withpicto != 2) {
3973 $result .= $this->ref;
3974 }
3975 $result .= $linkend;
3976
3977 if ($addlinktonotes >= 0) {
3978 $txttoshow = '';
3979
3980 if ($addlinktonotes == 0) {
3981 if (!empty($this->note_private) || !empty($this->note_public)) {
3982 $txttoshow = $langs->trans('ViewPrivateNote');
3983 }
3984 } elseif ($addlinktonotes == 1) {
3985 if (!empty($this->note_private)) {
3986 $txttoshow .= ($user->socid > 0 ? '' : dol_string_nohtmltag($this->note_private, 1));
3987 }
3988 } elseif ($addlinktonotes == 2) {
3989 if (!empty($this->note_public)) {
3990 $txttoshow .= dol_string_nohtmltag($this->note_public, 1);
3991 }
3992 } elseif ($addlinktonotes == 3) {
3993 if ($user->socid > 0) {
3994 if (!empty($this->note_public)) {
3995 $txttoshow .= dol_string_nohtmltag($this->note_public, 1);
3996 }
3997 } else {
3998 if (!empty($this->note_public)) {
3999 $txttoshow .= dol_string_nohtmltag($this->note_public, 1);
4000 }
4001 if (!empty($this->note_private)) {
4002 if (!empty($txttoshow)) {
4003 $txttoshow .= '<br><br>';
4004 }
4005 $txttoshow .= dol_string_nohtmltag($this->note_private, 1);
4006 }
4007 }
4008 }
4009
4010 if ($txttoshow) {
4011 $result .= ' <span class="note inline-block">';
4012 $result .= '<a href="'.dolBuildUrl(DOL_URL_ROOT.'/comm/propal/note.php', ['id' => $this->id]).'" class="classfortooltip" title="'.dol_escape_htmltag($txttoshow).'">';
4013 $result .= img_picto('', 'note');
4014 $result .= '</a>';
4015 $result .= '</span>';
4016 }
4017 }
4018
4019 global $action;
4020 $hookmanager->initHooks(array($this->element . 'dao'));
4021 $parameters = array('id' => $this->id, 'getnomurl' => &$result);
4022 $reshook = $hookmanager->executeHooks('getNomUrl', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
4023 if ($reshook > 0) {
4024 $result = $hookmanager->resPrint;
4025 } else {
4026 $result .= $hookmanager->resPrint;
4027 }
4028 return $result;
4029 }
4030
4037 public function getLinesArray($sqlforgedfilters = '')
4038 {
4039 return $this->fetch_lines(0, 0, $sqlforgedfilters);
4040 }
4041
4053 public function generateDocument($modele, $outputlangs, $hidedetails = 0, $hidedesc = 0, $hideref = 0, $moreparams = null)
4054 {
4055 $outputlangs->loadLangs(array("propale", "products"));
4056
4057 if (!dol_strlen($modele)) {
4058 $modele = getDolGlobalString('PROPALE_ADDON_PDF', 'cyan');
4059
4060 if ($this->model_pdf) {
4061 $modele = $this->model_pdf;
4062 }
4063 }
4064
4065 $modelpath = "core/modules/propale/doc/";
4066
4067 return $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams);
4068 }
4069
4078 public static function replaceThirdparty(DoliDB $dbs, $origin_id, $dest_id)
4079 {
4080 $tables = array(
4081 'propal'
4082 );
4083
4084 return CommonObject::commonReplaceThirdparty($dbs, $origin_id, $dest_id, $tables);
4085 }
4086
4095 public static function replaceProduct(DoliDB $db, $origin_id, $dest_id)
4096 {
4097 $tables = array(
4098 'propaldet'
4099 );
4100
4101 return CommonObject::commonReplaceProduct($db, $origin_id, $dest_id, $tables);
4102 }
4103
4111 public function getKanbanView($option = '', $arraydata = null)
4112 {
4113 global $langs;
4114
4115 $selected = (empty($arraydata['selected']) ? 0 : $arraydata['selected']);
4116
4117 $return = '<div class="box-flex-item box-flex-grow-zero">';
4118 $return .= '<div class="info-box info-box-sm">';
4119 $return .= '<div class="info-box-icon bg-infobox-action">';
4120 $return .= img_picto('', $this->picto);
4121 $return .= '</div>';
4122 $return .= '<div class="info-box-content">';
4123 $return .= '<span class="info-box-ref inline-block tdoverflowmax150 valignmiddle">' . $this->getNomUrl() . '</span>';
4124 if ($selected >= 0) {
4125 $return .= '<input id="cb'.$this->id.'" class="flat checkforselect fright" type="checkbox" name="toselect[]" value="'.$this->id.'"'.($selected ? ' checked="checked"' : '').'>';
4126 }
4127 if (!empty($arraydata['projectlink'])) {
4128 $return .= '<span class="info-box-ref"> | '.$arraydata['projectlink'].'</span>';
4129 }
4130 $return .= '<br>';
4131 if (is_object($this->thirdparty)) {
4132 $return .= '<div class="info-box-ref tdoverflowmax150">'.$this->thirdparty->getNomUrl(1).'</div>';
4133 }
4134 $return .= '<span class="info-box-label amount" title="'.$langs->trans("AmountHT").'">'.price($this->total_ht).'</span>';
4135 if (!empty($arraydata['authorlink'])) {
4136 $return .= ' &nbsp; <span class="info-box-label">'.$arraydata['authorlink'].'</span>';
4137 }
4138 $return .= '<br><div class="info-box-status">'.$this->getLibStatut(3).'</div>';
4139 $return .= '</div>';
4140 $return .= '</div>';
4141 $return .= '</div>';
4142 return $return;
4143 }
4144
4155 public function setCategories($categories)
4156 {
4157 require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php';
4158 return parent::setCategoriesCommon($categories, Categorie::TYPE_PROPOSAL);
4159 }
4160}
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', ...)
dolGetStatus($statusLabel='', $statusLabelShort='', $html='', $statusType='status0', $displayMode=0, $url='', $params=array())
Output the badge of a status.
GETPOST($paramname, $check='alphanohtml', $method=0, $filter=null, $options=null, $noreplace=0)
Return value of a param into GET or POST supervariable.
dol_buildpath($path, $type=0, $returnemptyifnotfound=0)
Return path of url or filesystem.
dol_print_date($time, $format='', $tzoutput='auto', $outputlangs=null, $encodetooutput=false, $decorate=0)
Output date in a string format according to outputlangs (or langs if not defined).
getDolGlobalBool($key, $default=false)
Return a Dolibarr global constant boolean value.
dol_print_error($db=null, $error='', $errors=null)
Displays error message system with all the information to facilitate the diagnosis and the escalation...
getDolGlobalString($key, $default='')
Return a Dolibarr global constant string value.
getWarningDelay($module, $parmlevel1, $parmlevel2='')
Return a warning delay You can use it like this: if (getWarningDelay('module', 'paramlevel1')) It rep...
get_default_tva(Societe $thirdparty_seller, Societe $thirdparty_buyer, $idprod=0, $idprodfournprice=0)
Function that return vat rate of a product line (according to seller, buyer and product vat rate) VAT...
get_localtax($vatrate, $local, $thirdparty_buyer=null, $thirdparty_seller=null, $vatnpr=0)
Return localtax rate for a particular VAT rate, when selling a product with vat $vatrate,...
dol_syslog($message, $level=LOG_INFO, $ident=0, $suffixinfilename='', $restricttologhandler='', $logcontext=null)
Write log message into outputs.
getEntity($element, $shared=1, $currentobject=null)
Get list of entity id to use.
dol_escape_htmltag($stringtoescape, $keepb=0, $keepn=0, $noescapetags='', $escapeonlyhtmltags=0, $cleanalsojavascript=0)
Returns text escaped for inclusion in HTML alt or title or value tags, or into values of HTML input f...
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