dolibarr 24.0.2
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 $newqty = $this->roundQtyToPackaging($qty, $tmpproduct->packaging);
753 if ($newqty != $qty) {
754 $qty = $newqty;
755 setEventMessages($langs->trans('QtyRecalculatedWithPackaging'), null, 'warnings');
756 }
757 }
758
759 // Clean vat code
760 $reg = array();
761 $vat_src_code = '';
762 if (preg_match('/\‍((.*)\‍)/', $txtva, $reg)) {
763 $vat_src_code = $reg[1];
764 $txtva = preg_replace('/\s*\‍(.*\‍)/', '', $txtva); // Remove code into vatrate.
765 }
766
767 $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);
768
769 $total_ht = $tabprice[0];
770 $total_tva = $tabprice[1];
771 $total_ttc = $tabprice[2];
772 $total_localtax1 = $tabprice[9];
773 $total_localtax2 = $tabprice[10];
774 $pu_ht = $tabprice[3];
775 $pu_tva = $tabprice[4];
776 $pu_ttc = $tabprice[5];
777
778 // MultiCurrency
779 $multicurrency_total_ht = $tabprice[16];
780 $multicurrency_total_tva = $tabprice[17];
781 $multicurrency_total_ttc = $tabprice[18];
782 $pu_ht_devise = $tabprice[19];
783
784 // Rang to use
785 $ranktouse = $rang;
786 if (empty($ranktouse) || $ranktouse == -1) {
787 $rangmax = $this->line_max($fk_parent_line);
788 $ranktouse = $rangmax + 1;
789 }
790
791 // Insert line
792 $this->line = new PropaleLigne($this->db);
793
794 $this->line->context = $this->context;
795
796 $this->line->fk_propal = $this->id;
797 $this->line->label = $label;
798 $this->line->desc = $desc;
799 $this->line->qty = (float) $qty;
800
801 $this->line->vat_src_code = $vat_src_code;
802 $this->line->tva_tx = $txtva;
803 $this->line->localtax1_tx = ($total_localtax1 ? $localtaxes_type[1] : 0);
804 $this->line->localtax2_tx = ($total_localtax2 ? $localtaxes_type[3] : 0);
805 $this->line->localtax1_type = empty($localtaxes_type[0]) ? '' : $localtaxes_type[0];
806 $this->line->localtax2_type = empty($localtaxes_type[2]) ? '' : $localtaxes_type[2];
807 $this->line->fk_product = $fk_product;
808 $this->line->product_type = $type;
809 $this->line->fk_remise_except = $fk_remise_except;
810 $this->line->remise_percent = $remise_percent;
811 $this->line->subprice = (float) $pu_ht;
812 // Persist the original entry mode of the line so updateline() can preserve it later.
813 $this->line->subprice_ttc = ($price_base_type === 'TTC') ? (float) $pu_ttc : 0;
814 $this->line->rang = $ranktouse;
815 $this->line->info_bits = $info_bits;
816 $this->line->total_ht = (float) $total_ht;
817 $this->line->total_tva = (float) $total_tva;
818 $this->line->total_localtax1 = (float) $total_localtax1;
819 $this->line->total_localtax2 = (float) $total_localtax2;
820 $this->line->total_ttc = (float) $total_ttc;
821 $this->line->special_code = $special_code;
822 $this->line->fk_parent_line = $fk_parent_line;
823 $this->line->fk_unit = $fk_unit;
824
825 $this->line->date_start = $date_start;
826 $this->line->date_end = $date_end;
827
828 $this->line->fk_fournprice = $fk_fournprice;
829 $this->line->pa_ht = $pa_ht;
830
831 $this->line->origin_id = $origin_id;
832 $this->line->origin = $origin;
833
834 // Multicurrency
835 $this->line->fk_multicurrency = $this->fk_multicurrency;
836 $this->line->multicurrency_code = $this->multicurrency_code;
837 $this->line->multicurrency_subprice = (float) $pu_ht_devise;
838 $this->line->multicurrency_total_ht = (float) $multicurrency_total_ht;
839 $this->line->multicurrency_total_tva = (float) $multicurrency_total_tva;
840 $this->line->multicurrency_total_ttc = (float) $multicurrency_total_ttc;
841
842 // Set the line as optional
843 if (empty($qty) && empty($special_code)) {
844 $this->line->special_code = 3;
845 }
846
847 if (is_array($array_options) && count($array_options) > 0) {
848 $this->line->array_options = $array_options;
849 }
850
851 $result = $this->line->insert();
852 if ($result > 0) {
853 if (!isset($this->context['createfromclone'])) {
854 if (!empty($fk_parent_line)) {
855 // Always reorder if child line
856 $this->line_order(true, 'DESC');
857 } elseif ($ranktouse > 0 && $ranktouse <= count($this->lines)) {
858 // Update all rank of all other lines starting from the same $ranktouse
859 $linecount = count($this->lines);
860 for ($ii = $ranktouse; $ii <= $linecount; $ii++) {
861 $this->updateRangOfLine($this->lines[$ii - 1]->id, $ii + 1);
862 }
863 }
864
865 $this->lines[] = $this->line;
866 } else {
867 // Loop on all lines of parent object
868 foreach ($this->lines as $tmpline) {
869 if ($tmpline->id == $origin_id && $tmpline->element == $origin) {
870 $this->line->extraparams = $tmpline->extraparams;
871 $this->line->setExtraParameters();
872 }
873 }
874 }
875
876 // Update denormalized fields at the order level
877 if (empty($noupdateafterinsertline)) {
878 $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.
879 }
880
881 if ($result > 0) {
882 $this->db->commit();
883 return $this->line->id;
884 } else {
885 $this->error = $this->db->error();
886 $this->db->rollback();
887 return -1;
888 }
889 } else {
890 $this->setErrorsFromObject($this->line);
891 $this->db->rollback();
892 return -2;
893 }
894 } else {
895 dol_syslog(get_class($this)."::addline status of proposal must be Draft to allow use of ->addline()", LOG_ERR);
896 return -3;
897 }
898 }
899
900
930 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)
931 {
932 global $mysoc, $langs;
933
934 if (!$this->isLineOfObject($rowid)) {
935 $this->error = 'ErrorLineIDDoesNotMatchWithObjectID';
936 return -1;
937 }
938
939 dol_syslog(get_class($this)."::updateLine rowid=$rowid, pu=$pu, qty=$qty, remise_percent=$remise_percent,
940 txtva=$txtva, desc=".dol_trunc($desc, 16).", 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 // Round the quantity to the packaging before computing the amounts of the line (and checking the stock),
990 // else the line is saved with the rounded quantity but with the amounts of the quantity before rounding
991 if (getDolGlobalString('PRODUCT_USE_CUSTOMER_PACKAGING')) {
992 $tmpline = new PropaleLigne($this->db);
993 if ($tmpline->fetch($rowid) > 0) {
994 $newqty = $this->roundQtyToPackaging($qty, $tmpline->packaging);
995 if ($newqty != $qty) {
996 $qty = $newqty;
997 setEventMessages($langs->trans('QtyRecalculatedWithPackaging'), null, 'warnings');
998 }
999 }
1000 }
1001
1002 $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);
1003 $total_ht = $tabprice[0];
1004 $total_tva = $tabprice[1];
1005 $total_ttc = $tabprice[2];
1006 $total_localtax1 = $tabprice[9];
1007 $total_localtax2 = $tabprice[10];
1008 $pu_ht = $tabprice[3];
1009 $pu_tva = $tabprice[4];
1010 $pu_ttc = $tabprice[5];
1011
1012 // MultiCurrency
1013 $multicurrency_total_ht = $tabprice[16];
1014 $multicurrency_total_tva = $tabprice[17];
1015 $multicurrency_total_ttc = $tabprice[18];
1016 $pu_ht_devise = $tabprice[19];
1017
1018 // Fetch current line from the database and then clone the object and set it in $oldline property
1019 $line = new PropaleLigne($this->db);
1020 $line->fetch($rowid);
1021
1022 $staticline = clone $line;
1023
1024 $line->oldline = $staticline;
1025 $this->line = $line;
1026 $this->line->context = $this->context;
1027 $this->line->rang = $rang;
1028
1029 // Reorder if fk_parent_line change
1030 if (!empty($fk_parent_line) && !empty($staticline->fk_parent_line) && $fk_parent_line != $staticline->fk_parent_line) {
1031 $rangmax = $this->line_max($fk_parent_line);
1032 $this->line->rang = $rangmax + 1;
1033 }
1034
1035
1036 $this->line->id = $rowid;
1037 $this->line->label = $label;
1038 $this->line->desc = $desc;
1039 $this->line->qty = $qty;
1040 $this->line->product_type = $type;
1041 $this->line->vat_src_code = $vat_src_code;
1042 $this->line->tva_tx = $txtva;
1043 $this->line->localtax1_tx = $txlocaltax1;
1044 $this->line->localtax2_tx = $txlocaltax2;
1045 $this->line->localtax1_type = empty($localtaxes_type[0]) ? '' : $localtaxes_type[0];
1046 $this->line->localtax2_type = empty($localtaxes_type[2]) ? '' : $localtaxes_type[2];
1047 $this->line->remise_percent = $remise_percent;
1048 $this->line->subprice = (float) $pu_ht;
1049 // Persist the original entry mode of the line so a no-op edit can preserve it later.
1050 $this->line->subprice_ttc = ($price_base_type === 'TTC') ? (float) $pu_ttc : 0;
1051 $this->line->info_bits = $info_bits;
1052
1053 $this->line->total_ht = (float) $total_ht;
1054 $this->line->total_tva = (float) $total_tva;
1055 $this->line->total_localtax1 = (float) $total_localtax1;
1056 $this->line->total_localtax2 = (float) $total_localtax2;
1057 $this->line->total_ttc = (float) $total_ttc;
1058 $this->line->special_code = $special_code;
1059 $this->line->fk_parent_line = $fk_parent_line;
1060 $this->line->skip_update_total = $skip_update_total;
1061 $this->line->fk_unit = $fk_unit;
1062
1063 $this->line->fk_fournprice = $fk_fournprice;
1064 $this->line->pa_ht = $pa_ht;
1065
1066 $this->line->date_start = $date_start;
1067 $this->line->date_end = $date_end;
1068
1069 if (is_array($array_options) && count($array_options) > 0) {
1070 // We replace values in this->line->array_options only for entries defined into $array_options
1071 foreach ($array_options as $key => $value) {
1072 $this->line->array_options[$key] = $array_options[$key];
1073 }
1074 }
1075
1076 // Multicurrency
1077 $this->line->multicurrency_subprice = (float) $pu_ht_devise;
1078 $this->line->multicurrency_total_ht = (float) $multicurrency_total_ht;
1079 $this->line->multicurrency_total_tva = (float) $multicurrency_total_tva;
1080 $this->line->multicurrency_total_ttc = (float) $multicurrency_total_ttc;
1081
1082 $result = $this->line->update($notrigger);
1083 if ($result > 0) {
1084 // Reorder if child line
1085 if (!empty($fk_parent_line)) {
1086 $this->line_order(true, 'DESC');
1087 }
1088
1089 $this->update_price(1, 'auto');
1090
1091 // $this is Propal
1092 // $this->fk_propal = $this->id;
1093 // $this->rowid = $rowid;
1094
1095 $this->db->commit();
1096 return $result;
1097 } else {
1098 $this->setErrorsFromObject($this->line);
1099 $this->db->rollback();
1100 return -1;
1101 }
1102 } else {
1103 dol_syslog(get_class($this)."::updateline Erreur -2 Propal en mode incompatible pour cette action");
1104 return -2;
1105 }
1106 }
1107
1108
1116 public function deleteLine($lineid, $id = 0)
1117 {
1118 global $user;
1119
1120 if ($this->status == self::STATUS_DRAFT) {
1121 $this->db->begin();
1122
1123 $line = new PropaleLigne($this->db);
1124
1125 $line->context = $this->context;
1126
1127 // Load data
1128 $line->fetch($lineid);
1129
1130 if ($id <= 0) {
1131 $id = $this->id;
1132 }
1133 if ($id > 0 && (int) $line->fk_propal !== (int) $id) {
1134 $this->db->rollback();
1135 $this->error = 'ErrorLineIDDoesNotMatchWithObjectID';
1136 return -1;
1137 }
1138
1139 // Memorize previous line for triggers
1140 $staticline = clone $line;
1141 $line->oldline = $staticline;
1142
1143 if ($line->delete($user) > 0) {
1144 $this->update_price(1);
1145
1146 $this->db->commit();
1147 return 1;
1148 } else {
1149 $this->setErrorsFromObject($line);
1150 $this->db->rollback();
1151 return -1;
1152 }
1153 } else {
1154 $this->error = 'ErrorDeleteLineNotAllowedByObjectStatus';
1155 return -2;
1156 }
1157 }
1158
1159
1168 public function create($user, $notrigger = 0)
1169 {
1170 global $mysoc;
1171
1172 $error = 0;
1173
1174 $now = dol_now();
1175
1176 // Clean parameters
1177 if (empty($this->date)) {
1178 $this->date = $this->datep;
1179 }
1180 $this->fin_validite = $this->date + ($this->duree_validite * 24 * 3600);
1181 if (empty($this->availability_id)) {
1182 $this->availability_id = 0;
1183 }
1184 if (empty($this->demand_reason_id)) {
1185 $this->demand_reason_id = 0;
1186 }
1187
1188 // Multicurrency (test on $this->multicurrency_tx because we should take the default rate only if not using origin rate)
1189 if (!empty($this->multicurrency_code) && empty($this->multicurrency_tx)) {
1190 list($this->fk_multicurrency, $this->multicurrency_tx) = MultiCurrency::getIdAndTxFromCode($this->db, $this->multicurrency_code, $this->date);
1191 } else {
1192 $this->fk_multicurrency = MultiCurrency::getIdFromCode($this->db, $this->multicurrency_code);
1193 }
1194 if (empty($this->fk_multicurrency)) {
1195 $this->multicurrency_code = getDolCurrency();
1196 $this->fk_multicurrency = 0;
1197 $this->multicurrency_tx = 1;
1198 }
1199 // setEntity will set entity with the right value if empty or change it for the right value if multicompany module is active
1200 $this->entity = setEntity($this);
1201
1202 // Set tmp vars
1203 $delivery_date = $this->delivery_date;
1204
1205 dol_syslog(get_class($this)."::create");
1206
1207 // Check parameters
1208 $result = $this->fetch_thirdparty();
1209 if ($result < 0) {
1210 $this->error = "Failed to fetch company";
1211 dol_syslog(get_class($this)."::create ".$this->error, LOG_ERR);
1212 return -3;
1213 }
1214
1215 // Check parameters
1216 if (!empty($this->ref)) { // We check that ref is not already used
1217 $result = self::isExistingObject($this->element, 0, $this->ref); // Check ref is not yet used
1218 if ($result > 0) {
1219 $this->error = 'ErrorRefAlreadyExists';
1220 dol_syslog(get_class($this)."::create ".$this->error, LOG_WARNING);
1221 $this->db->rollback();
1222 return -1;
1223 }
1224 }
1225
1226 if (empty($this->date)) {
1227 $this->error = "Date of proposal is required";
1228 dol_syslog(get_class($this)."::create ".$this->error, LOG_ERR);
1229 return -4;
1230 }
1231
1232
1233 $this->db->begin();
1234
1235 // Insert into database
1236 $sql = "INSERT INTO ".MAIN_DB_PREFIX.$this->table_element." (";
1237 $sql .= "fk_soc";
1238 $sql .= ", price";
1239 $sql .= ", total_tva";
1240 $sql .= ", total_ttc";
1241 $sql .= ", datep";
1242 $sql .= ", datec";
1243 $sql .= ", ref";
1244 $sql .= ", fk_user_author";
1245 $sql .= ", note_private";
1246 $sql .= ", note_public";
1247 $sql .= ", model_pdf";
1248 $sql .= ", fin_validite";
1249 $sql .= ", fk_cond_reglement";
1250 $sql .= ", deposit_percent";
1251 $sql .= ", fk_mode_reglement";
1252 $sql .= ", fk_account";
1253 $sql .= ", ref_client";
1254 $sql .= ", ref_ext";
1255 $sql .= ", date_livraison";
1256 $sql .= ", fk_shipping_method";
1257 $sql .= ", fk_warehouse";
1258 $sql .= ", fk_availability";
1259 $sql .= ", fk_input_reason";
1260 $sql .= ", fk_projet";
1261 $sql .= ", fk_incoterms";
1262 $sql .= ", location_incoterms";
1263 $sql .= ", entity";
1264 $sql .= ", fk_multicurrency";
1265 $sql .= ", multicurrency_code";
1266 $sql .= ", multicurrency_tx";
1267 $sql .= ") ";
1268 $sql .= " VALUES (";
1269 $sql .= ((int) $this->socid);
1270 $sql .= ", 0";
1271 $sql .= ", 0";
1272 $sql .= ", 0";
1273 $sql .= ", '".$this->db->idate($this->date)."'";
1274 $sql .= ", '".$this->db->idate($now)."'";
1275 $sql .= ", '(PROV)'";
1276 $sql .= ", ".($user->id > 0 ? ((int) $user->id) : "NULL");
1277 $sql .= ", '".$this->db->escape($this->note_private)."'";
1278 $sql .= ", '".$this->db->escape($this->note_public)."'";
1279 $sql .= ", '".$this->db->escape($this->model_pdf)."'";
1280 $sql .= ", ".($this->fin_validite != '' ? "'".$this->db->idate($this->fin_validite)."'" : "NULL");
1281 $sql .= ", ".($this->cond_reglement_id > 0 ? ((int) $this->cond_reglement_id) : 'NULL');
1282 $sql .= ", ".(!empty($this->deposit_percent) ? "'".$this->db->escape($this->deposit_percent)."'" : 'NULL');
1283 $sql .= ", ".($this->mode_reglement_id > 0 ? ((int) $this->mode_reglement_id) : 'NULL');
1284 $sql .= ", ".($this->fk_account > 0 ? ((int) $this->fk_account) : 'NULL');
1285 $sql .= ", '".$this->db->escape((string) $this->ref_client)."'";
1286 $sql .= ", '".$this->db->escape((string) $this->ref_ext)."'";
1287 $sql .= ", ".(!isDolTms($delivery_date) ? "NULL" : "'".$this->db->idate($delivery_date)."'");
1288 $sql .= ", ".($this->shipping_method_id > 0 ? ((int) $this->shipping_method_id) : 'NULL');
1289 $sql .= ", ".($this->warehouse_id > 0 ? ((int) $this->warehouse_id) : 'NULL');
1290 $sql .= ", ".((int) $this->availability_id);
1291 $sql .= ", ".((int) $this->demand_reason_id);
1292 $sql .= ", ".($this->fk_project ? ((int) $this->fk_project) : "null");
1293 $sql .= ", ".(int) $this->fk_incoterms;
1294 $sql .= ", '".$this->db->escape($this->location_incoterms)."'";
1295 $sql .= ", ".(int) $this->entity;
1296 $sql .= ", ".(int) $this->fk_multicurrency;
1297 $sql .= ", '".$this->db->escape($this->multicurrency_code)."'";
1298 $sql .= ", ".(float) $this->multicurrency_tx;
1299 $sql .= ")";
1300
1301 dol_syslog(get_class($this)."::create", LOG_DEBUG);
1302 $resql = $this->db->query($sql);
1303 if ($resql) {
1304 $this->id = $this->db->last_insert_id(MAIN_DB_PREFIX.$this->table_element);
1305
1306 if ($this->id) {
1307 $this->ref = '(PROV'.$this->id.')';
1308 $sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element." SET ref='".$this->db->escape($this->ref)."' WHERE rowid=".((int) $this->id);
1309
1310 dol_syslog(get_class($this)."::create", LOG_DEBUG);
1311 $resql = $this->db->query($sql);
1312 if (!$resql) {
1313 $error++;
1314 }
1315
1316 if (!empty($this->linkedObjectsIds) && empty($this->linked_objects)) { // To use new linkedObjectsIds instead of old linked_objects
1317 $this->linked_objects = $this->linkedObjectsIds; // TODO Replace linked_objects with linkedObjectsIds
1318 }
1319
1320 // Add object linked
1321 if (!$error && !empty($this->linked_objects) && is_array($this->linked_objects)) {
1322 foreach ($this->linked_objects as $origin => $tmp_origin_id) {
1323 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, ...))
1324 foreach ($tmp_origin_id as $origin_id) {
1325 $ret = $this->add_object_linked($origin, $origin_id);
1326 if (!$ret) {
1327 $this->error = $this->db->lasterror();
1328 $error++;
1329 }
1330 }
1331 } else { // Old behaviour, if linked_object has only one link per type, so is something like array('contract'=>id1))
1332 $origin_id = $tmp_origin_id;
1333 $ret = $this->add_object_linked($origin, $origin_id);
1334 if (!$ret) {
1335 $this->error = $this->db->lasterror();
1336 $error++;
1337 }
1338 }
1339 }
1340 }
1341
1342 /*
1343 * Insert products detail in database
1344 */
1345 if (!$error) {
1346 $fk_parent_line = 0;
1347 $num = count($this->lines);
1348
1349 for ($i = 0; $i < $num; $i++) {
1350 if (!is_object($this->lines[$i])) { // If this->lines is not array of objects, coming from REST API
1351 // Convert into object this->lines[$i].
1352 $line = (object) $this->lines[$i];
1353 } else {
1354 $line = $this->lines[$i];
1355 }
1356 // Reset fk_parent_line for line that are not child lines or special product
1357 if (($line->product_type != 9 && empty($line->fk_parent_line)) || $line->product_type == 9) {
1358 $fk_parent_line = 0;
1359 }
1360 // Complete vat rate with code
1361 $vatrate = $line->tva_tx;
1362 if ($line->vat_src_code && !preg_match('/\‍(.*\‍)/', $vatrate)) {
1363 $vatrate .= ' ('.$line->vat_src_code.')';
1364 }
1365
1366 if (getDolGlobalString('MAIN_CREATEFROM_KEEP_LINE_ORIGIN_INFORMATION')) {
1367 $originid = $line->origin_id;
1368 $origintype = empty($line->origin_type) ? $line->origin : $line->origin_type;
1369 } else { // old but bugged version (we store id of line and type of parent object)
1370 $originid = $line->id;
1371 $origintype = $this->element;
1372 }
1373
1374 $result = $this->addline(
1375 $line->desc,
1376 $line->subprice,
1377 $line->qty,
1378 $vatrate,
1379 $line->localtax1_tx,
1380 $line->localtax2_tx,
1381 $line->fk_product,
1382 $line->remise_percent,
1383 'HT',
1384 0,
1385 $line->info_bits,
1386 $line->product_type,
1387 $line->rang,
1388 $line->special_code,
1389 $fk_parent_line,
1390 $line->fk_fournprice,
1391 $line->pa_ht,
1392 $line->label,
1393 $line->date_start,
1394 $line->date_end,
1395 $line->array_options,
1396 $line->fk_unit,
1397 (string) $origintype,
1398 $originid,
1399 0,
1400 0,
1401 1
1402 );
1403
1404 if ($result < 0) {
1405 $error++;
1406 $this->error = $this->db->error;
1407 dol_print_error($this->db);
1408 break;
1409 }
1410
1411 // Set the id on created row
1412 $line->id = $result;
1413
1414 // Keep the extra parameters of the source line (for example the options of subtotal lines): addline() can't
1415 // do it when the object is cloned, because the origin it receives is the one of the source line, not the source line
1416 if ($result > 0 && !empty($line->extraparams)) {
1417 $newline = new PropaleLigne($this->db);
1418 $newline->id = $result;
1419 $newline->extraparams = $line->extraparams;
1420 $newline->setExtraParameters();
1421 }
1422
1423 // Defined the new fk_parent_line
1424 if ($result > 0 && $line->product_type == 9) {
1425 $fk_parent_line = $result;
1426 }
1427 }
1428 }
1429
1430 if (!$error) {
1431 // Update denormalized data
1432 $resql = $this->update_price(1, 'auto', 0, $mysoc);
1433 if ($resql) {
1434 // Actions on extra fields
1435 $result = $this->insertExtraFields();
1436 if ($result < 0) {
1437 $error++;
1438 }
1439
1440 if (!$error && !$notrigger) {
1441 // Call trigger
1442 $result = $this->call_trigger('PROPAL_CREATE', $user);
1443 if ($result < 0) {
1444 $error++;
1445 }
1446 // End call triggers
1447 }
1448 } else {
1449 $this->error = $this->db->lasterror();
1450 $error++;
1451 }
1452 }
1453 } else {
1454 $this->error = $this->db->lasterror();
1455 $error++;
1456 }
1457
1458 if (!$error) {
1459 $this->db->commit();
1460 dol_syslog(get_class($this)."::create done id=".$this->id);
1461 return $this->id;
1462 } else {
1463 $this->db->rollback();
1464 return -2;
1465 }
1466 } else {
1467 $this->error = $this->db->lasterror();
1468 $this->db->rollback();
1469 return -1;
1470 }
1471 }
1472
1483 public function createFromClone(User $user, $socid = 0, $forceentity = null, $update_prices = false, $update_desc = false)
1484 {
1485 global $conf, $hookmanager, $mysoc;
1486
1487 dol_include_once('/projet/class/project.class.php');
1488
1489 $error = 0;
1490 $now = dol_now();
1491
1492 dol_syslog(__METHOD__, LOG_DEBUG);
1493
1494 $object = new self($this->db);
1495
1496 $this->db->begin();
1497
1498 // Load source object
1499 $object->fetch($this->id);
1500
1501 $objsoc = new Societe($this->db);
1502
1503 // Change socid if needed
1504 if (!empty($socid) && $socid != $object->socid) {
1505 if ($objsoc->fetch($socid) > 0) {
1506 $object->socid = $objsoc->id;
1507 $object->cond_reglement_id = (!empty($objsoc->cond_reglement_id) ? $objsoc->cond_reglement_id : 0);
1508 $object->deposit_percent = (!empty($objsoc->deposit_percent) ? $objsoc->deposit_percent : null);
1509 $object->mode_reglement_id = (!empty($objsoc->mode_reglement_id) ? $objsoc->mode_reglement_id : 0);
1510 $object->fk_delivery_address = 0;
1511
1512 /*if (isModEnabled('project'))
1513 {
1514 $project = new Project($db);
1515 if ($this->fk_project > 0 && $project->fetch($this->fk_project)) {
1516 if ($project->socid <= 0) $clonedObj->fk_project = $this->fk_project;
1517 else $clonedObj->fk_project = '';
1518 } else {
1519 $clonedObj->fk_project = '';
1520 }
1521 }*/
1522 $object->fk_project = 0; // A cloned proposal is set by default to no project.
1523 }
1524
1525 // reset ref_client
1526 if (!getDolGlobalString('MAIN_KEEP_REF_CUSTOMER_ON_CLONING')) {
1527 $object->ref_client = '';
1528 $object->ref_customer = '';
1529 }
1530
1531 // TODO Change product price if multi-prices
1532 } else {
1533 $objsoc->fetch($object->socid);
1534 }
1535
1536 // update prices
1537 if ($update_prices === true || $update_desc === true) {
1538 if ($objsoc->id > 0 && !empty($object->lines)) {
1539 if ($update_prices === true && getDolGlobalString('PRODUIT_CUSTOMER_PRICES')) {
1540 // If price per customer
1541 require_once DOL_DOCUMENT_ROOT . '/product/class/productcustomerprice.class.php';
1542 }
1543
1544 foreach ($object->lines as $line) {
1545 $line->id = 0;
1546
1547 if ($line->fk_product > 0) {
1548 $prod = new Product($this->db);
1549 $res = $prod->fetch($line->fk_product);
1550 if ($res > 0) {
1551 if ($update_prices === true) {
1552 $pu_ht = $prod->price;
1553 $tva_tx = (string) get_default_tva($mysoc, $objsoc, $prod->id);
1554 $remise_percent = $objsoc->remise_percent;
1555
1556 if (getDolGlobalString('PRODUIT_MULTIPRICES') && $objsoc->price_level > 0) {
1557 $pu_ht = $prod->multiprices[$objsoc->price_level];
1558 if (getDolGlobalString('PRODUIT_MULTIPRICES_USE_VAT_PER_LEVEL')) { // using this option is a bug. kept for backward compatibility
1559 if (isset($prod->multiprices_tva_tx[$objsoc->price_level])) {
1560 $tva_tx = (string) $prod->multiprices_tva_tx[$objsoc->price_level];
1561 }
1562 }
1563 } elseif (getDolGlobalString('PRODUIT_CUSTOMER_PRICES')) {
1564 $prodcustprice = new ProductCustomerPrice($this->db);
1565 $filter = array('t.fk_product' => (string) $prod->id, 't.fk_soc' => (string) $objsoc->id);
1566 $result = $prodcustprice->fetchAll('', '', 0, 0, $filter);
1567 if ($result) {
1568 // If there is some prices specific to the customer
1569 if (count($prodcustprice->lines) > 0) {
1570 $date_now = (int) floor(dol_now() / 86400) * 86400; // date without hours
1571 foreach ($prodcustprice->lines as $k => $custprice_line) {
1572 if ($custprice_line->date_begin <= $date_now && (empty($custprice_line->date_end) || $date_now <= $custprice_line->date_end)) {
1573 $pu_ht = price($custprice_line->price);
1574 $tva_tx = ($custprice_line->default_vat_code ? $custprice_line->tva_tx . ' (' . $custprice_line->default_vat_code . ' )' : $custprice_line->tva_tx);
1575 if ($custprice_line->default_vat_code && !preg_match('/\‍(.*\‍)/', $tva_tx)) {
1576 $tva_tx .= ' (' . $custprice_line->default_vat_code . ')';
1577 }
1578 $remise_percent = $custprice_line->discount_percent;
1579 break;
1580 }
1581 }
1582 }
1583 }
1584 }
1585
1586 $line->subprice = $pu_ht;
1587 $line->tva_tx = $tva_tx;
1588 $line->remise_percent = $remise_percent;
1589 // Refresh the buying price too: current supplier price if one was selected on the line, otherwise let PropaleLigne::insert() recompute it (defineBuyPrice(), according to MARGIN_TYPE)
1590 $line->pa_ht = '';
1591 if ($line->fk_fournprice > 0) {
1592 require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.product.class.php';
1593 $prodfourn = new ProductFournisseur($this->db);
1594 if ($prodfourn->fetch_product_fournisseur_price($line->fk_fournprice) > 0) {
1595 $line->pa_ht = price2num($prodfourn->fourn_unitprice * (1 - $prodfourn->fourn_remise_percent / 100), 'MU');
1596 } else {
1597 $line->fk_fournprice = 0;
1598 }
1599 }
1600 }
1601 if ($update_desc === true) {
1602 $line->desc = $prod->description;
1603 }
1604 }
1605 }
1606 }
1607 }
1608 }
1609
1610 $object->id = 0;
1611 $object->ref = '';
1612 $object->entity = (!empty($forceentity) ? $forceentity : $object->entity);
1613 $object->statut = self::STATUS_DRAFT;
1614 $object->status = self::STATUS_DRAFT;
1615
1616 // Clear fields
1617 $object->user_creation_id = $user->id;
1618 $object->user_validation_id = 0;
1619 $object->date = $now;
1620 $object->datep = $now; // deprecated
1621 $object->fin_validite = $object->date + (int) ($object->duree_validite * 24 * 3600);
1622 if (!getDolGlobalString('MAIN_KEEP_REF_CUSTOMER_ON_CLONING')) {
1623 $object->ref_client = '';
1624 $object->ref_customer = '';
1625 }
1626 if (getDolGlobalInt('MAIN_DONT_KEEP_NOTE_ON_CLONING') == 1) {
1627 $object->note_private = '';
1628 $object->note_public = '';
1629 }
1630 // Create clone
1631 $object->context['createfromclone'] = 'createfromclone';
1632 $result = $object->create($user);
1633 if ($result < 0) {
1635 $error++;
1636 }
1637
1638 if (!$error && !getDolGlobalInt('MAIN_IGNORE_CONTACTS_ON_CLONING')) {
1639 // copy internal contacts
1640 if ($object->copy_linked_contact($this, 'internal') < 0) {
1641 $error++;
1642 }
1643 }
1644
1645 if (!$error) {
1646 // copy external contacts if same company
1647 if ($this->socid == $object->socid) {
1648 if ($object->copy_linked_contact($this, 'external') < 0) {
1649 $error++;
1650 }
1651 }
1652 }
1653
1654 if (!$error) {
1655 // Hook of thirdparty module
1656 if (is_object($hookmanager)) {
1657 $parameters = array('objFrom' => $this, 'clonedObj' => $object);
1658 $action = '';
1659 $reshook = $hookmanager->executeHooks('createFrom', $parameters, $object, $action); // Note that $action and $object may have been modified by some hooks
1660 if ($reshook < 0) {
1661 $this->setErrorsFromObject($hookmanager);
1662 $error++;
1663 }
1664 }
1665 }
1666
1667 unset($object->context['createfromclone']);
1668
1669 // End
1670 if (!$error) {
1671 $this->db->commit();
1672 return $object->id;
1673 } else {
1674 $this->db->rollback();
1675 return -1;
1676 }
1677 }
1678
1688 public function fetch($rowid, $ref = '', $ref_ext = '', $forceentity = 0)
1689 {
1690 $sql = "SELECT p.rowid, p.ref, p.entity, p.fk_soc";
1691 $sql .= ", p.total_ttc, p.total_tva, p.localtax1, p.localtax2, p.total_ht";
1692 $sql .= ", p.datec";
1693 $sql .= ", p.date_signature as dates";
1694 $sql .= ", p.date_valid as datev";
1695 $sql .= ", p.date_cloture";
1696 $sql .= ", p.datep as dp";
1697 $sql .= ", p.fin_validite as dfv";
1698 $sql .= ", p.date_livraison as delivery_date";
1699 $sql .= ", p.model_pdf, p.last_main_doc, p.ref_client, ref_ext, p.extraparams";
1700 $sql .= ", p.note_private, p.note_public";
1701 $sql .= ", p.fk_projet as fk_project, p.fk_statut";
1702 $sql .= ", p.fk_user_author, p.fk_user_valid, p.fk_user_cloture";
1703 $sql .= ", p.fk_delivery_address";
1704 $sql .= ", p.fk_availability";
1705 $sql .= ", p.fk_input_reason";
1706 $sql .= ", p.fk_cond_reglement";
1707 $sql .= ", p.fk_mode_reglement";
1708 $sql .= ', p.fk_account';
1709 $sql .= ", p.fk_shipping_method";
1710 $sql .= ", p.fk_warehouse";
1711 $sql .= ", p.fk_incoterms, p.location_incoterms";
1712 $sql .= ", p.fk_multicurrency, p.multicurrency_code, p.multicurrency_tx, p.multicurrency_total_ht, p.multicurrency_total_tva, p.multicurrency_total_ttc";
1713 $sql .= ", p.tms as date_modification";
1714 $sql .= ", i.libelle as label_incoterms";
1715 $sql .= ", c.label as statut_label";
1716 $sql .= ", ca.code as availability_code, ca.label as availability";
1717 $sql .= ", dr.code as demand_reason_code, dr.label as demand_reason";
1718 $sql .= ", cr.code as cond_reglement_code, cr.libelle as cond_reglement, cr.libelle_facture as cond_reglement_libelle_doc, p.deposit_percent";
1719 $sql .= ", cp.code as mode_reglement_code, cp.libelle as mode_reglement";
1720 $sql .= " FROM ".MAIN_DB_PREFIX.$this->table_element." as p";
1721 $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_propalst as c ON p.fk_statut = c.id';
1722 $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_paiement as cp ON p.fk_mode_reglement = cp.id AND cp.entity IN ('.getEntity('c_paiement').')';
1723 $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').')';
1724 $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_availability as ca ON p.fk_availability = ca.rowid';
1725 $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_input_reason as dr ON p.fk_input_reason = dr.rowid';
1726 $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_incoterms as i ON p.fk_incoterms = i.rowid';
1727
1728 if (!empty($ref)) {
1729 if (!empty($forceentity)) {
1730 $sql .= " WHERE p.entity = ".(int) $forceentity; // Check only the current entity because we may have the same reference in several entities
1731 } else {
1732 $sql .= " WHERE p.entity IN (".getEntity('propal').")";
1733 }
1734 $sql .= " AND p.ref='".$this->db->escape($ref)."'";
1735 } else {
1736 // Don't use entity if you use rowid
1737 $sql .= " WHERE p.rowid = ".((int) $rowid);
1738 }
1739
1740 dol_syslog(get_class($this)."::fetch", LOG_DEBUG);
1741 $resql = $this->db->query($sql);
1742 if ($resql) {
1743 if ($this->db->num_rows($resql)) {
1744 $obj = $this->db->fetch_object($resql);
1745
1746 $this->id = $obj->rowid;
1747 $this->entity = $obj->entity;
1748
1749 $this->ref = $obj->ref;
1750 $this->ref_client = $obj->ref_client;
1751 $this->ref_customer = $obj->ref_client;
1752 $this->ref_ext = $obj->ref_ext;
1753
1754 $this->total = $obj->total_ttc; // TODO deprecated
1755 $this->total_ttc = $obj->total_ttc;
1756 $this->total_ht = $obj->total_ht;
1757 $this->total_tva = $obj->total_tva;
1758 $this->total_localtax1 = $obj->localtax1;
1759 $this->total_localtax2 = $obj->localtax2;
1760
1761 $this->socid = $obj->fk_soc;
1762 $this->thirdparty = null; // Clear if another value was already set by fetch_thirdparty
1763
1764 $this->fk_project = $obj->fk_project;
1765 $this->project = null; // Clear if another value was already set by fetchProject
1766
1767 $this->model_pdf = $obj->model_pdf;
1768 $this->last_main_doc = $obj->last_main_doc;
1769 $this->note = $obj->note_private; // TODO deprecated
1770 $this->note_private = $obj->note_private;
1771 $this->note_public = $obj->note_public;
1772
1773 $this->status = (int) $obj->fk_statut;
1774 $this->statut = $this->status; // deprecated
1775
1776 $this->datec = $this->db->jdate($obj->datec); // TODO deprecated
1777 $this->datev = $this->db->jdate($obj->datev); // TODO deprecated
1778 $this->date_creation = $this->db->jdate($obj->datec); //Creation date
1779 $this->date_validation = $this->db->jdate($obj->datev); //Validation date
1780 $this->date_modification = $this->db->jdate($obj->date_modification); // tms
1781 $this->date_signature = $this->db->jdate($obj->dates); // Signature date
1782 $this->date_cloture = $this->db->jdate($obj->date_cloture); // Closing date
1783 $this->date = $this->db->jdate($obj->dp); // Proposal date
1784 $this->datep = $this->db->jdate($obj->dp); // deprecated
1785 $this->fin_validite = $this->db->jdate($obj->dfv);
1786 $this->delivery_date = $this->db->jdate($obj->delivery_date);
1787 $this->shipping_method_id = ($obj->fk_shipping_method > 0) ? $obj->fk_shipping_method : null;
1788 $this->warehouse_id = ($obj->fk_warehouse > 0) ? $obj->fk_warehouse : null;
1789 $this->availability_id = $obj->fk_availability;
1790 $this->availability_code = $obj->availability_code;
1791 $this->availability = $obj->availability;
1792 $this->demand_reason_id = $obj->fk_input_reason;
1793 $this->demand_reason_code = $obj->demand_reason_code;
1794 $this->demand_reason = $obj->demand_reason;
1795 $this->fk_address = $obj->fk_delivery_address;
1796
1797 $this->mode_reglement_id = $obj->fk_mode_reglement;
1798 $this->mode_reglement_code = $obj->mode_reglement_code;
1799 $this->mode_reglement = $obj->mode_reglement;
1800 $this->fk_account = ($obj->fk_account > 0) ? $obj->fk_account : null;
1801 $this->cond_reglement_id = $obj->fk_cond_reglement;
1802 $this->cond_reglement_code = $obj->cond_reglement_code;
1803 $this->cond_reglement = $obj->cond_reglement;
1804 $this->cond_reglement_doc = $obj->cond_reglement_libelle_doc;
1805 $this->deposit_percent = $obj->deposit_percent;
1806
1807 $this->extraparams = !empty($obj->extraparams) ? (array) json_decode($obj->extraparams, true) : array();
1808
1809 $this->user_author_id = $obj->fk_user_author;
1810 $this->user_validation_id = $obj->fk_user_valid;
1811 $this->user_closing_id = $obj->fk_user_cloture;
1812
1813 //Incoterms
1814 $this->fk_incoterms = $obj->fk_incoterms;
1815 $this->location_incoterms = $obj->location_incoterms;
1816 $this->label_incoterms = $obj->label_incoterms;
1817
1818 // Multicurrency
1819 $this->fk_multicurrency = $obj->fk_multicurrency;
1820 $this->multicurrency_code = $obj->multicurrency_code;
1821 $this->multicurrency_tx = $obj->multicurrency_tx;
1822 $this->multicurrency_total_ht = $obj->multicurrency_total_ht;
1823 $this->multicurrency_total_tva = $obj->multicurrency_total_tva;
1824 $this->multicurrency_total_ttc = $obj->multicurrency_total_ttc;
1825
1826 // Retrieve all extrafield
1827 // fetch optionals attributes and labels
1828 $this->fetch_optionals();
1829
1830 $this->db->free($resql);
1831
1832 $this->lines = array();
1833
1834 // Lines
1835 $result = $this->fetch_lines();
1836 if ($result < 0) {
1837 return -3;
1838 }
1839
1840 return 1;
1841 }
1842
1843 $this->error = "Record Not Found";
1844 return 0;
1845 } else {
1846 $this->error = $this->db->lasterror();
1847 return -1;
1848 }
1849 }
1850
1858 public function update(User $user, $notrigger = 0)
1859 {
1860 $error = 0;
1861
1862 // Clean parameters
1863 if (isset($this->ref)) {
1864 $this->ref = trim($this->ref);
1865 }
1866 if (isset($this->ref_client)) {
1867 $this->ref_client = trim($this->ref_client);
1868 }
1869 if (isset($this->note) || isset($this->note_private)) {
1870 $this->note_private = (isset($this->note_private) ? trim($this->note_private) : trim($this->note));
1871 }
1872 if (isset($this->note_public)) {
1873 $this->note_public = trim($this->note_public);
1874 }
1875 if (isset($this->model_pdf)) {
1876 $this->model_pdf = trim($this->model_pdf);
1877 }
1878 if (isset($this->import_key)) {
1879 $this->import_key = trim($this->import_key);
1880 }
1881 if (!empty($this->duree_validite) && is_numeric($this->duree_validite)) {
1882 $this->fin_validite = $this->date + ($this->duree_validite * 24 * 3600);
1883 }
1884
1885 // Check parameters
1886 // Put here code to add control on parameters values
1887
1888 // Update request
1889 $sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element." SET";
1890 $sql = "UPDATE ".MAIN_DB_PREFIX."propal SET";
1891 $sql .= " ref = ".(isset($this->ref) ? "'".$this->db->escape($this->ref)."'" : "null").",";
1892 $sql .= " ref_client = ".(isset($this->ref_client) ? "'".$this->db->escape($this->ref_client)."'" : "null").",";
1893 $sql .= " ref_ext = ".(isset($this->ref_ext) ? "'".$this->db->escape($this->ref_ext)."'" : "null").",";
1894 $sql .= " fk_soc = ".(!empty($this->socid) ? (int) $this->socid : "null").",";
1895 $sql .= " datep = ".(strval($this->date) != '' ? "'".$this->db->idate($this->date)."'" : 'null').",";
1896 if (!empty($this->fin_validite)) {
1897 $sql .= " fin_validite = ".(strval($this->fin_validite) != '' ? "'".$this->db->idate($this->fin_validite)."'" : 'null').",";
1898 }
1899 $sql .= " date_valid = ".(strval($this->date_validation) != '' ? "'".$this->db->idate($this->date_validation)."'" : 'null').",";
1900 $sql .= " total_tva = ".(isset($this->total_tva) ? (float) $this->total_tva : "null").",";
1901 $sql .= " localtax1 = ".(isset($this->total_localtax1) ? (float) $this->total_localtax1 : "null").",";
1902 $sql .= " localtax2 = ".(isset($this->total_localtax2) ? (float) $this->total_localtax2 : "null").",";
1903 $sql .= " total_ht = ".(isset($this->total_ht) ? (float) $this->total_ht : "null").",";
1904 $sql .= " total_ttc = ".(isset($this->total_ttc) ? (float) $this->total_ttc : "null").",";
1905 $sql .= " fk_statut = ".(isset($this->status) ? (int) $this->status : "null").",";
1906 $sql .= " fk_user_author = ".(!empty($this->user_author_id) ? (int) $this->user_author_id : "null").",";
1907 $sql .= " fk_user_valid = ".(!empty($this->user_validation_id) ? (int) $this->user_validation_id : "null").",";
1908
1909 $sql .= " fk_projet = ".(!empty($this->fk_project) ? (int) $this->fk_project : "null").",";
1910 $sql .= " fk_cond_reglement = ".(!empty($this->cond_reglement_id) ? (int) $this->cond_reglement_id : "null").",";
1911 $sql .= " deposit_percent = ".(!empty($this->deposit_percent) ? "'".$this->db->escape($this->deposit_percent)."'" : "null").",";
1912 $sql .= " fk_mode_reglement = ".(!empty($this->mode_reglement_id) ? (int) $this->mode_reglement_id : "null").",";
1913 $sql .= " fk_input_reason = ".(!empty($this->demand_reason_id) ? (int) $this->demand_reason_id : "null").",";
1914 $sql .= " fk_shipping_method = ".(isset($this->shipping_method_id) ? (int) $this->shipping_method_id : "null").",";
1915 $sql .= " fk_availability = ".(!empty($this->availability_id) ? (int) $this->availability_id : "null").",";
1916 $sql .= " note_private = ".(isset($this->note_private) ? "'".$this->db->escape($this->note_private)."'" : "null").",";
1917 $sql .= " note_public = ".(isset($this->note_public) ? "'".$this->db->escape($this->note_public)."'" : "null").",";
1918 $sql .= " model_pdf = ".(isset($this->model_pdf) ? "'".$this->db->escape($this->model_pdf)."'" : "null").",";
1919 $sql .= " import_key = ".(isset($this->import_key) ? "'".$this->db->escape($this->import_key)."'" : "null");
1920 $sql .= " WHERE rowid = ".((int) $this->id);
1921
1922 $this->db->begin();
1923
1924 dol_syslog(get_class($this)."::update", LOG_DEBUG);
1925 $resql = $this->db->query($sql);
1926 if (!$resql) {
1927 $error++;
1928 $this->errors[] = "Error ".$this->db->lasterror();
1929 }
1930
1931 if (!$error) {
1932 $result = $this->insertExtraFields();
1933 if ($result < 0) {
1934 $error++;
1935 }
1936 }
1937
1938 if (!$error && !$notrigger) {
1939 // Call trigger
1940 $result = $this->call_trigger('PROPAL_MODIFY', $user);
1941 if ($result < 0) {
1942 $error++;
1943 }
1944 // End call triggers
1945 }
1946
1947 // Commit or rollback
1948 if ($error) {
1949 foreach ($this->errors as $errmsg) {
1950 dol_syslog(get_class($this)."::update ".$errmsg, LOG_ERR);
1951 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
1952 }
1953 $this->db->rollback();
1954 return -1 * $error;
1955 } else {
1956 $this->db->commit();
1957 return 1;
1958 }
1959 }
1960
1961
1962 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1971 public function fetch_lines($only_product = 0, $loadalsotranslation = 0, $sqlforgedfilters = '')
1972 {
1973 // phpcs:enable
1974 $this->lines = array();
1975
1976 $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,';
1977 $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,';
1978 $sql .= ' d.fk_unit,';
1979 $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,';
1980 $sql .= ' p.weight, p.weight_units, p.volume, p.volume_units,';
1981 $sql .= ' p.customcode, p.fk_country as country_id, c.code as country_code,';
1982 $sql .= ' d.date_start, d.date_end, d.extraparams,';
1983 $sql .= ' d.fk_multicurrency, d.multicurrency_code, d.multicurrency_subprice, d.multicurrency_total_ht, d.multicurrency_total_tva, d.multicurrency_total_ttc';
1984 $sql .= ' FROM '.MAIN_DB_PREFIX.$this->table_element_line.' as d';
1985 $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'product as p ON (d.fk_product = p.rowid)';
1986 $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_country as c ON c.rowid = p.fk_country';
1987 $sql .= ' WHERE d.fk_propal = '.((int) $this->id);
1988 if ($only_product) {
1989 $sql .= ' AND p.fk_product_type = 0';
1990 }
1991 if ($sqlforgedfilters) {
1992 $sql .= $sqlforgedfilters;
1993 }
1994 $sql .= ' ORDER BY d.rang, d.rowid';
1995
1996 dol_syslog(get_class($this)."::fetch_lines", LOG_DEBUG);
1997 $result = $this->db->query($sql);
1998 if ($result) {
1999 require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php';
2000
2001 $num = $this->db->num_rows($result);
2002
2003 $i = 0;
2004 while ($i < $num) {
2005 $objp = $this->db->fetch_object($result);
2006
2007 $line = new PropaleLigne($this->db);
2008
2009 $line->rowid = $objp->rowid; //Deprecated
2010 $line->id = $objp->rowid;
2011 $line->fk_propal = $objp->fk_propal;
2012 $line->fk_parent_line = $objp->fk_parent_line;
2013 $line->label = $objp->custom_label;
2014 $line->desc = $objp->description; // Line description
2015 $line->description = $objp->description; // Line description
2016 $line->qty = $objp->qty;
2017 $line->vat_src_code = $objp->vat_src_code;
2018 $line->tva_tx = $objp->tva_tx;
2019 $line->localtax1_tx = $objp->localtax1_tx;
2020 $line->localtax2_tx = $objp->localtax2_tx;
2021 $line->localtax1_type = $objp->localtax1_type;
2022 $line->localtax2_type = $objp->localtax2_type;
2023 $line->subprice = $objp->subprice;
2024 $line->subprice_ttc = $objp->subprice_ttc;
2025 $line->fk_remise_except = $objp->fk_remise_except;
2026 $line->remise_percent = $objp->remise_percent;
2027
2028 $line->info_bits = $objp->info_bits;
2029 $line->total_ht = $objp->total_ht;
2030 $line->total_tva = $objp->total_tva;
2031 $line->total_localtax1 = $objp->total_localtax1;
2032 $line->total_localtax2 = $objp->total_localtax2;
2033 $line->total_ttc = $objp->total_ttc;
2034 $line->fk_fournprice = $objp->fk_fournprice;
2035 $marginInfos = getMarginInfos($objp->subprice, $objp->remise_percent, $objp->tva_tx, $objp->localtax1_tx, $objp->localtax2_tx, $line->fk_fournprice, $objp->pa_ht);
2036 $line->pa_ht = $marginInfos[0];
2037 $line->marge_tx = $marginInfos[1];
2038 $line->marque_tx = $marginInfos[2];
2039 $line->special_code = $objp->special_code;
2040 $line->rang = $objp->rang;
2041
2042 $line->fk_product = $objp->fk_product;
2043
2044 $line->ref = $objp->product_ref; // deprecated
2045 $line->libelle = $objp->product_label; // deprecated
2046
2047 $line->product_ref = $objp->product_ref;
2048 $line->product_label = $objp->product_label;
2049 $line->product_desc = $objp->product_desc; // Description produit
2050 $line->product_tobatch = $objp->product_tobatch;
2051 $line->product_barcode = $objp->product_barcode;
2052 $line->product_custom_code = $objp->customcode;
2053 $line->product_custom_country_id = $objp->country_id;
2054 $line->product_custom_country_code = $objp->country_code;
2055 $line->product_type = $objp->product_type;
2056 $line->fk_product_type = $objp->fk_product_type; // deprecated
2057
2058 $line->fk_unit = $objp->fk_unit;
2059 $line->weight = $objp->weight;
2060 $line->weight_units = $objp->weight_units;
2061 $line->volume = $objp->volume;
2062 $line->volume_units = $objp->volume_units;
2063
2064 $line->date_start = $this->db->jdate($objp->date_start);
2065 $line->date_end = $this->db->jdate($objp->date_end);
2066
2067 $line->extraparams = !empty($objp->extraparams) ? (array) json_decode($objp->extraparams, true) : array();
2068
2069 // Multicurrency
2070 $line->fk_multicurrency = $objp->fk_multicurrency;
2071 $line->multicurrency_code = $objp->multicurrency_code;
2072 $line->multicurrency_subprice = $objp->multicurrency_subprice;
2073 $line->multicurrency_total_ht = $objp->multicurrency_total_ht;
2074 $line->multicurrency_total_tva = $objp->multicurrency_total_tva;
2075 $line->multicurrency_total_ttc = $objp->multicurrency_total_ttc;
2076
2077 $line->fetch_optionals();
2078
2079 // multilangs
2080 if (getDolGlobalInt('MAIN_MULTILANGS') && !empty($objp->fk_product) && !empty($loadalsotranslation)) {
2081 $tmpproduct = new Product($this->db);
2082 $tmpproduct->fetch($objp->fk_product);
2083 $tmpproduct->getMultiLangs();
2084
2085 $line->multilangs = $tmpproduct->multilangs;
2086 }
2087
2088 $this->lines[$i] = $line;
2089
2090 $i++;
2091 }
2092
2093 $this->db->free($result);
2094
2095 return $num;
2096 } else {
2097 $this->error = $this->db->lasterror();
2098 return -3;
2099 }
2100 }
2101
2109 public function valid($user, $notrigger = 0)
2110 {
2111 global $conf;
2112
2113 require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
2114
2115 $error = 0;
2116
2117 // Protection
2118 if ($this->status == self::STATUS_VALIDATED) {
2119 dol_syslog(get_class($this)."::valid action abandoned: already validated", LOG_WARNING);
2120 return 0;
2121 }
2122
2123 if (!((!getDolGlobalString('MAIN_USE_ADVANCED_PERMS') && $user->hasRight('propal', 'creer'))
2124 || (getDolGlobalString('MAIN_USE_ADVANCED_PERMS') && $user->hasRight('propal', 'propal_advance', 'validate')))) {
2125 $this->error = 'ErrorPermissionDenied';
2126 dol_syslog(get_class($this)."::valid ".$this->error, LOG_ERR);
2127 return -1;
2128 }
2129
2130 if (!getDolGlobalBool('PROPALE_NOCHECK_ONSALE_PRODUCTS_ONVALID') && !$this->checkActiveProductInLines()) {
2131 dol_syslog(get_class($this)."::valid checkActiveProductInLines ".$this->error, LOG_INFO);
2132 return -1;
2133 }
2134
2135 $now = dol_now();
2136
2137 $this->db->begin();
2138
2139 // Numbering module definition
2140 $soc = new Societe($this->db);
2141 $soc->fetch($this->socid);
2142
2143 // Define new ref
2144 if (preg_match('/^[\‍(]?PROV/i', $this->ref) || empty($this->ref)) { // empty should not happened, but when it occurs, the test save life
2145 $num = $this->getNextNumRef($soc);
2146 } else {
2147 $num = (string) $this->ref;
2148 }
2149 $this->newref = dol_sanitizeFileName($num);
2150
2151 $sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element;
2152 $sql .= " SET ref = '".$this->db->escape($num)."',";
2153 $sql .= " fk_statut = ".self::STATUS_VALIDATED.", date_valid='".$this->db->idate($now)."', fk_user_valid=".((int) $user->id);
2154 $sql .= " WHERE rowid = ".((int) $this->id)." AND fk_statut = ".self::STATUS_DRAFT;
2155
2156 dol_syslog(get_class($this)."::valid", LOG_DEBUG);
2157 $resql = $this->db->query($sql);
2158 if (!$resql) {
2159 dol_print_error($this->db);
2160 $error++;
2161 }
2162
2163 // Trigger calls
2164 if (!$error && !$notrigger) {
2165 // Call trigger
2166 $result = $this->call_trigger('PROPAL_VALIDATE', $user);
2167 if ($result < 0) {
2168 $error++;
2169 }
2170 // End call triggers
2171 }
2172
2173 if (!$error) {
2174 $this->oldref = $this->ref;
2175
2176 // Rename directory if dir was a temporary ref
2177 if (preg_match('/^[\‍(]?PROV/i', $this->ref)) {
2178 // Now we rename also files into index
2179 $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)."'";
2180 $sql .= " WHERE filename LIKE '".$this->db->escape($this->ref)."%' AND filepath = 'propale/".$this->db->escape($this->ref)."' and entity = ".((int) $conf->entity);
2181 $resql = $this->db->query($sql);
2182 if (!$resql) {
2183 $error++;
2184 $this->error = $this->db->lasterror();
2185 }
2186 $sql = 'UPDATE '.MAIN_DB_PREFIX."ecm_files set filepath = 'propale/".$this->db->escape($this->newref)."'";
2187 $sql .= " WHERE filepath = 'propale/".$this->db->escape($this->ref)."' and entity = ".((int) $conf->entity);
2188 $resql = $this->db->query($sql);
2189 if (!$resql) {
2190 $error++;
2191 $this->error = $this->db->lasterror();
2192 }
2193
2194 // We rename directory ($this->ref = old ref, $num = new ref) in order not to lose the attachments
2195 $oldref = dol_sanitizeFileName($this->ref);
2196 $newref = dol_sanitizeFileName($num);
2197 $dirsource = $conf->propal->multidir_output[$this->entity].'/'.$oldref;
2198 $dirdest = $conf->propal->multidir_output[$this->entity].'/'.$newref;
2199 if (!$error && file_exists($dirsource)) {
2200 dol_syslog(get_class($this)."::validate rename dir ".$dirsource." into ".$dirdest);
2201 if (@rename($dirsource, $dirdest)) {
2202 dol_syslog("Rename ok");
2203 // Rename docs starting with $oldref with $newref
2204 $listoffiles = dol_dir_list($dirdest, 'files', 1, '^'.preg_quote($oldref, '/'));
2205 foreach ($listoffiles as $fileentry) {
2206 $dirsource = $fileentry['name'];
2207 $dirdest = preg_replace('/^'.preg_quote($oldref, '/').'/', $newref, $dirsource);
2208 $dirsource = $fileentry['path'].'/'.$dirsource;
2209 $dirdest = $fileentry['path'].'/'.$dirdest;
2210 @rename($dirsource, $dirdest);
2211 }
2212 }
2213 }
2214 }
2215
2216 $this->ref = $num;
2217 $this->statut = self::STATUS_VALIDATED; // deprecated
2219 $this->user_validation_id = $user->id;
2220 $this->datev = $now;
2221 $this->date_validation = $now;
2222
2223 $this->db->commit();
2224 return 1;
2225 } else {
2226 $this->db->rollback();
2227 return -1;
2228 }
2229 }
2230
2231
2232 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2241 public function set_date($user, $date, $notrigger = 0)
2242 {
2243 // phpcs:enable
2244 if (empty($date)) {
2245 $this->error = 'ErrorBadParameter';
2246 dol_syslog(get_class($this)."::set_date ".$this->error, LOG_ERR);
2247 return -1;
2248 }
2249
2250 if ($user->hasRight('propal', 'creer')) {
2251 $error = 0;
2252
2253 $this->db->begin();
2254
2255 $sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element;
2256 $sql .= " SET datep = '".$this->db->idate($date)."'";
2257 $sql .= " WHERE rowid = ".((int) $this->id);
2258
2259 dol_syslog(__METHOD__, LOG_DEBUG);
2260 $resql = $this->db->query($sql);
2261 if (!$resql) {
2262 $this->errors[] = $this->db->error();
2263 $error++;
2264 }
2265
2266 if (!$error) {
2267 $this->oldcopy = clone $this;
2268 $this->date = $date;
2269 $this->datep = $date; // deprecated
2270 }
2271
2272 if (!$notrigger && empty($error)) {
2273 // Call trigger
2274 $result = $this->call_trigger('PROPAL_MODIFY', $user);
2275 if ($result < 0) {
2276 $error++;
2277 }
2278 // End call triggers
2279 }
2280
2281 if (!$error) {
2282 $this->db->commit();
2283 return 1;
2284 } else {
2285 foreach ($this->errors as $errmsg) {
2286 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2287 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2288 }
2289 $this->db->rollback();
2290 return -1 * $error;
2291 }
2292 }
2293
2294 return -1;
2295 }
2296
2297 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2306 public function set_echeance($user, $date_end_validity, $notrigger = 0)
2307 {
2308 // phpcs:enable
2309 if ($user->hasRight('propal', 'creer')) {
2310 $error = 0;
2311
2312 $this->db->begin();
2313
2314 $sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element;
2315 $sql .= " SET fin_validite = ".($date_end_validity != '' ? "'".$this->db->idate($date_end_validity)."'" : 'null');
2316 $sql .= " WHERE rowid = ".((int) $this->id);
2317
2318 dol_syslog(__METHOD__, LOG_DEBUG);
2319
2320 $resql = $this->db->query($sql);
2321 if (!$resql) {
2322 $this->errors[] = $this->db->error();
2323 $error++;
2324 }
2325
2326
2327 if (!$error) {
2328 $this->oldcopy = clone $this;
2329 $this->fin_validite = $date_end_validity;
2330 }
2331
2332 if (!$notrigger && empty($error)) {
2333 // Call trigger
2334 $result = $this->call_trigger('PROPAL_MODIFY', $user);
2335 if ($result < 0) {
2336 $error++;
2337 }
2338 // End call triggers
2339 }
2340
2341 if (!$error) {
2342 $this->db->commit();
2343 return 1;
2344 } else {
2345 foreach ($this->errors as $errmsg) {
2346 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2347 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2348 }
2349 $this->db->rollback();
2350 return -1 * $error;
2351 }
2352 }
2353
2354 return -1;
2355 }
2356
2357 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2367 public function set_date_livraison($user, $delivery_date, $notrigger = 0)
2368 {
2369 // phpcs:enable
2370 return $this->setDeliveryDate($user, $delivery_date, $notrigger);
2371 }
2372
2381 public function setDeliveryDate($user, $delivery_date, $notrigger = 0)
2382 {
2383 if ($user->hasRight('propal', 'creer')) {
2384 $error = 0;
2385
2386 $this->db->begin();
2387
2388 $sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element;
2389 $sql .= " SET date_livraison = ".(isDolTms($delivery_date) ? "'".$this->db->idate($delivery_date)."'" : 'null');
2390 $sql .= " WHERE rowid = ".((int) $this->id);
2391
2392 dol_syslog(__METHOD__, LOG_DEBUG);
2393 $resql = $this->db->query($sql);
2394 if (!$resql) {
2395 $this->errors[] = $this->db->error();
2396 $error++;
2397 }
2398
2399 if (!$error) {
2400 $this->oldcopy = clone $this;
2401 $this->delivery_date = $delivery_date;
2402 }
2403
2404 if (!$notrigger && empty($error)) {
2405 // Call trigger
2406 $result = $this->call_trigger('PROPAL_MODIFY', $user);
2407 if ($result < 0) {
2408 $error++;
2409 }
2410 // End call triggers
2411 }
2412
2413 if (!$error) {
2414 $this->db->commit();
2415 return 1;
2416 } else {
2417 foreach ($this->errors as $errmsg) {
2418 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2419 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2420 }
2421 $this->db->rollback();
2422 return -1 * $error;
2423 }
2424 }
2425
2426 return -1;
2427 }
2428
2429 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2438 public function set_availability($user, $id, $notrigger = 0)
2439 {
2440 // phpcs:enable
2441 if ($this->status >= self::STATUS_DRAFT) {
2442 $error = 0;
2443
2444 $this->db->begin();
2445
2446 $sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element;
2447 $sql .= " SET fk_availability = ".((int) ($id > 0 ? ((int) $id) : 0));
2448 $sql .= " WHERE rowid = ".((int) $this->id);
2449
2450 dol_syslog(__METHOD__.' availability('.$id.')', LOG_DEBUG);
2451 $resql = $this->db->query($sql);
2452 if (!$resql) {
2453 $this->errors[] = $this->db->error();
2454 $error++;
2455 }
2456
2457 if (!$error) {
2458 $this->oldcopy = clone $this;
2459 $this->fk_availability = $id;
2460 $this->availability_id = $id;
2461 }
2462
2463 if (!$notrigger && empty($error)) {
2464 // Call trigger
2465 $result = $this->call_trigger('PROPAL_MODIFY', $user);
2466 if ($result < 0) {
2467 $error++;
2468 }
2469 // End call triggers
2470 }
2471
2472 if (!$error) {
2473 $this->db->commit();
2474 return 1;
2475 } else {
2476 foreach ($this->errors as $errmsg) {
2477 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2478 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2479 }
2480 $this->db->rollback();
2481 return -1 * $error;
2482 }
2483 } else {
2484 $error_str = 'Propal status do not meet requirement '.$this->status;
2485 dol_syslog(__METHOD__.$error_str, LOG_ERR);
2486 $this->error = $error_str;
2487 $this->errors[] = $this->error;
2488 return -2;
2489 }
2490 }
2491
2492 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2501 public function set_demand_reason($user, $id, $notrigger = 0)
2502 {
2503 // phpcs:enable
2504 if ($user->hasRight('propal', 'creer') && $this->status >= self::STATUS_DRAFT) {
2505 $error = 0;
2506
2507 $this->db->begin();
2508
2509 $sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element;
2510 $sql .= " SET fk_input_reason = ".((int) $id);
2511 $sql .= " WHERE rowid = ".((int) $this->id);
2512
2513 dol_syslog(__METHOD__, LOG_DEBUG);
2514 $resql = $this->db->query($sql);
2515 if (!$resql) {
2516 $this->errors[] = $this->db->error();
2517 $error++;
2518 }
2519
2520
2521 if (!$error) {
2522 $this->oldcopy = clone $this;
2523
2524 $this->demand_reason_id = $id;
2525 }
2526
2527
2528 if (!$notrigger && empty($error)) {
2529 // Call trigger
2530 $result = $this->call_trigger('PROPAL_MODIFY', $user);
2531 if ($result < 0) {
2532 $error++;
2533 }
2534 // End call triggers
2535 }
2536
2537 if (!$error) {
2538 $this->db->commit();
2539 return 1;
2540 } else {
2541 foreach ($this->errors as $errmsg) {
2542 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2543 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2544 }
2545 $this->db->rollback();
2546 return -1 * $error;
2547 }
2548 } else {
2549 $error_str = 'Propal status do not meet requirement '.$this->status;
2550 dol_syslog(__METHOD__.$error_str, LOG_ERR);
2551 $this->error = $error_str;
2552 $this->errors[] = $this->error;
2553 return -2;
2554 }
2555 }
2556
2557 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2566 public function set_ref_client($user, $ref_client, $notrigger = 0)
2567 {
2568 // phpcs:enable
2569 if ($user->hasRight('propal', 'creer')) {
2570 $error = 0;
2571
2572 $this->db->begin();
2573
2574 $sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element;
2575 $sql .= " SET ref_client = ".(empty($ref_client) ? 'NULL' : "'".$this->db->escape($ref_client)."'");
2576 $sql .= " WHERE rowid = ".((int) $this->id);
2577
2578 dol_syslog(__METHOD__.' $this->id='.$this->id.', ref_client='.$ref_client, LOG_DEBUG);
2579 $resql = $this->db->query($sql);
2580 if (!$resql) {
2581 $this->errors[] = $this->db->error();
2582 $error++;
2583 }
2584
2585 if (!$error) {
2586 $this->oldcopy = clone $this;
2587 $this->ref_customer = $ref_client;
2588 $this->ref_client = $ref_client;
2589 }
2590
2591 if (!$notrigger && empty($error)) {
2592 // Call trigger
2593 $result = $this->call_trigger('PROPAL_MODIFY', $user);
2594 if ($result < 0) {
2595 $error++;
2596 }
2597 // End call triggers
2598 }
2599
2600 if (!$error) {
2601 $this->db->commit();
2602 return 1;
2603 } else {
2604 foreach ($this->errors as $errmsg) {
2605 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2606 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2607 }
2608 $this->db->rollback();
2609 return -1 * $error;
2610 }
2611 }
2612
2613 return -1;
2614 }
2615
2616
2626 public function reopen($user, $status, $note = '', $notrigger = 0)
2627 {
2628 $error = 0;
2629
2630 $sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element;
2631 $sql .= " SET fk_statut = ".((int) $status).",";
2632 if (!empty($note)) {
2633 $sql .= " note_private = '".$this->db->escape($note)."',";
2634 }
2635 $sql .= " date_cloture=NULL, fk_user_cloture=NULL";
2636 $sql .= " WHERE rowid = ".((int) $this->id);
2637
2638 $this->db->begin();
2639
2640 dol_syslog(get_class($this)."::reopen", LOG_DEBUG);
2641 $resql = $this->db->query($sql);
2642 if (!$resql) {
2643 $error++;
2644 $this->errors[] = "Error ".$this->db->lasterror();
2645 }
2646 if (!$error) {
2647 if (!$notrigger) {
2648 // Call trigger
2649 $result = $this->call_trigger('PROPAL_REOPEN', $user);
2650 if ($result < 0) {
2651 $error++;
2652 }
2653 // End call triggers
2654 }
2655 }
2656
2657 // Commit or rollback
2658 if ($error) {
2659 if (!empty($this->errors)) {
2660 foreach ($this->errors as $errmsg) {
2661 dol_syslog(get_class($this)."::update ".$errmsg, LOG_ERR);
2662 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2663 }
2664 }
2665 $this->db->rollback();
2666 return -1 * $error;
2667 } else {
2668 $this->statut = $status;
2669 $this->status = $status;
2670
2671 $this->db->commit();
2672 return 1;
2673 }
2674 }
2675
2686 public function closeProposal($user, $status, $note_private = '', $notrigger = 0, $note_public = '')
2687 {
2688 global $langs,$conf;
2689
2690 $error = 0;
2691 $now = dol_now();
2692
2693 $this->db->begin();
2694
2695 $newprivatenote = dol_concatdesc($this->note_private, $note_private);
2696 $newpublicnote = dol_concatdesc($this->note_public, $note_public);
2697
2698 if (!getDolGlobalString('PROPALE_KEEP_OLD_SIGNATURE_INFO')) {
2699 $date_signature = $now;
2700 $fk_user_signature = $user->id;
2701 } else {
2702 $this->info($this->id);
2703 if (!isset($this->date_signature) || $this->date_signature == '') {
2704 $date_signature = $now;
2705 $fk_user_signature = $user->id;
2706 } else {
2707 $date_signature = $this->date_signature;
2708 $fk_user_signature = $this->user_signature->id;
2709 }
2710 }
2711
2712 $sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element;
2713 $sql .= " SET fk_statut = ".((int) $status).", note_private = '".$this->db->escape($newprivatenote)."', note_public = '".$this->db->escape($newpublicnote)."'";
2714 if ($status == self::STATUS_SIGNED) {
2715 $sql .= ", date_signature='".$this->db->idate($now)."', fk_user_signature = ".((int) $fk_user_signature);
2716 }
2717 $sql .= " WHERE rowid = ".((int) $this->id);
2718
2719 $resql = $this->db->query($sql);
2720 if ($resql) {
2721 // Status self::STATUS_REFUSED by default
2722 $modelpdf = getDolGlobalString('PROPALE_ADDON_PDF_ODT_CLOSED', $this->model_pdf);
2723 $triggerName = 'PROPAL_CLOSE_REFUSED'; // used later in call_trigger()
2724
2725 if ($status == self::STATUS_SIGNED) { // Status self::STATUS_SIGNED
2726 $triggerName = 'PROPAL_CLOSE_SIGNED'; // used later in call_trigger()
2727 $modelpdf = getDolGlobalString('PROPALE_ADDON_PDF_ODT_TOBILL', $this->model_pdf);
2728
2729 // The connected company is classified as a client
2730 $soc = new Societe($this->db);
2731 $soc->id = $this->socid;
2732 $result = $soc->setAsCustomer();
2733
2734 if ($result < 0) {
2735 $this->error = $this->db->lasterror();
2736 $this->db->rollback();
2737 return -2;
2738 }
2739 }
2740
2741 if (!getDolGlobalString('MAIN_DISABLE_PDF_AUTOUPDATE') && !getDolGlobalInt('PROPAL_DISABLE_AUTOUPDATE_ON_CLOSE')) {
2742 // Define output language
2743 $outputlangs = $langs;
2744 if (getDolGlobalInt('MAIN_MULTILANGS')) {
2745 $outputlangs = new Translate("", $conf);
2746 $newlang = (GETPOST('lang_id', 'aZ09') ? GETPOST('lang_id', 'aZ09') : $this->thirdparty->default_lang);
2747 $outputlangs->setDefaultLang($newlang);
2748 }
2749
2750 // PDF
2751 $hidedetails = (getDolGlobalString('MAIN_GENERATE_DOCUMENTS_HIDE_DETAILS') ? 1 : 0);
2752 $hidedesc = (getDolGlobalString('MAIN_GENERATE_DOCUMENTS_HIDE_DESC') ? 1 : 0);
2753 $hideref = (getDolGlobalString('MAIN_GENERATE_DOCUMENTS_HIDE_REF') ? 1 : 0);
2754
2755 //$ret=$object->fetch($id); // Reload to get new records
2756 $this->generateDocument($modelpdf, $outputlangs, $hidedetails, $hidedesc, $hideref);
2757 }
2758
2759 $this->oldcopy = clone $this;
2760 $this->statut = $status; // deprecated
2761 $this->status = $status;
2762 $this->date_signature = $date_signature;
2763 $this->note_private = $newprivatenote;
2764
2765 if (!$notrigger && empty($error)) {
2766 // Call trigger
2767 $result = $this->call_trigger($triggerName, $user);
2768 if ($result < 0) {
2769 $error++;
2770 }
2771 // End call triggers
2772 }
2773
2774 if (!$error) {
2775 $this->db->commit();
2776 return 1;
2777 } else {
2778 foreach ($this->errors as $errmsg) {
2779 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2780 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2781 }
2782 if (!empty($this->oldcopy)) {
2783 $this->statut = $this->oldcopy->status; // deprecated
2784 $this->status = $this->oldcopy->status;
2785 $this->date_signature = $this->oldcopy->date_signature;
2786 $this->note_private = $this->oldcopy->note_private;
2787 }
2788
2789 $this->db->rollback();
2790 return -1;
2791 }
2792 } else {
2793 $this->error = $this->db->lasterror();
2794 $this->db->rollback();
2795 return -1;
2796 }
2797 }
2798
2807 public function classifyBilled(User $user, $notrigger = 0, $note = '')
2808 {
2809 global $conf, $langs;
2810
2811 $error = 0;
2812
2813 $now = dol_now();
2814 $num = 0;
2815
2816 $triggerName = 'PROPAL_CLASSIFY_BILLED';
2817
2818 $this->db->begin();
2819
2820 $newprivatenote = dol_concatdesc($this->note_private, $note);
2821
2822 $sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element;
2823 $sql .= " SET fk_statut = ".self::STATUS_BILLED.", ";
2824 $sql .= " note_private = '".$this->db->escape($newprivatenote)."', date_cloture='".$this->db->idate($now)."', fk_user_cloture=".((int) $user->id);
2825 $sql .= ' WHERE rowid = '.((int) $this->id).' AND fk_statut = '.((int) self::STATUS_SIGNED);
2826
2827 dol_syslog(__METHOD__, LOG_DEBUG);
2828 $resql = $this->db->query($sql);
2829 if (!$resql) {
2830 $this->errors[] = $this->db->error();
2831 $error++;
2832 } else {
2833 $num = $this->db->affected_rows($resql);
2834 }
2835
2836 if (!$error) {
2837 $modelpdf = getDolGlobalString('PROPALE_ADDON_PDF_ODT_CLOSED', $this->model_pdf);
2838
2839 if (!getDolGlobalString('MAIN_DISABLE_PDF_AUTOUPDATE')) {
2840 // Define output language
2841 $outputlangs = $langs;
2842 if (getDolGlobalInt('MAIN_MULTILANGS')) {
2843 $outputlangs = new Translate("", $conf);
2844 $newlang = (GETPOST('lang_id', 'aZ09') ? GETPOST('lang_id', 'aZ09') : $this->thirdparty->default_lang);
2845 $outputlangs->setDefaultLang($newlang);
2846 }
2847
2848 // PDF
2849 $hidedetails = (getDolGlobalString('MAIN_GENERATE_DOCUMENTS_HIDE_DETAILS') ? 1 : 0);
2850 $hidedesc = (getDolGlobalString('MAIN_GENERATE_DOCUMENTS_HIDE_DESC') ? 1 : 0);
2851 $hideref = (getDolGlobalString('MAIN_GENERATE_DOCUMENTS_HIDE_REF') ? 1 : 0);
2852
2853 //$ret=$object->fetch($id); // Reload to get new records
2854 $this->generateDocument($modelpdf, $outputlangs, $hidedetails, $hidedesc, $hideref);
2855 }
2856
2857 $this->oldcopy = clone $this;
2858 $this->statut = self::STATUS_BILLED;
2859 $this->status = self::STATUS_BILLED;
2860 $this->date_cloture = $now;
2861 $this->note_private = $newprivatenote;
2862 }
2863
2864 if (!$notrigger && empty($error)) {
2865 // Call trigger
2866 $result = $this->call_trigger($triggerName, $user);
2867 if ($result < 0) {
2868 $error++;
2869 }
2870 // End call triggers
2871 }
2872
2873 if (!$error) {
2874 $this->db->commit();
2875 return $num;
2876 } else {
2877 foreach ($this->errors as $errmsg) {
2878 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2879 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2880 }
2881 $this->db->rollback();
2882 return -1 * $error;
2883 }
2884 }
2885
2892 public function setCancel(User $user)
2893 {
2894 $error = 0;
2895
2896 $this->db->begin();
2897
2898 $sql = "UPDATE ". MAIN_DB_PREFIX . $this->table_element;
2899 $sql .= " SET fk_statut = " . self::STATUS_CANCELED . ",";
2900 $sql .= " fk_user_modif = " . ((int) $user->id);
2901 $sql .= " WHERE rowid = " . ((int) $this->id);
2902
2903 dol_syslog(get_class($this)."::cancel", LOG_DEBUG);
2904 if ($this->db->query($sql)) {
2905 // Call trigger
2906 $result = $this->call_trigger('PROPAL_CANCEL', $user);
2907 if ($result < 0) {
2908 $error++;
2909 }
2910 // End call triggers
2911
2912 if (!$error) {
2913 $this->statut = self::STATUS_CANCELED; // deprecated
2915 $this->db->commit();
2916 return 1;
2917 } else {
2918 foreach ($this->errors as $errmsg) {
2919 dol_syslog(get_class($this)."::cancel ".$errmsg, LOG_ERR);
2920 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2921 }
2922 $this->db->rollback();
2923 return -1;
2924 }
2925 } else {
2926 $this->error = $this->db->error();
2927 $this->db->rollback();
2928 return -1;
2929 }
2930 }
2931
2932 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2940 public function setDraft($user, $notrigger = 0)
2941 {
2942 // phpcs:enable
2943 $error = 0;
2944
2945 // Protection
2946 if ($this->status <= self::STATUS_DRAFT) {
2947 return 0;
2948 }
2949
2950 dol_syslog(get_class($this)."::setDraft", LOG_DEBUG);
2951
2952 $this->db->begin();
2953
2954 $sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element;
2955 $sql .= " SET fk_statut = ".self::STATUS_DRAFT;
2956 $sql .= ", online_sign_ip = NULL , online_sign_name = NULL";
2957 $sql .= " WHERE rowid = ".((int) $this->id);
2958
2959 $resql = $this->db->query($sql);
2960 if (!$resql) {
2961 $this->errors[] = $this->db->error();
2962 $error++;
2963 }
2964
2965 if (!$error) {
2966 $this->oldcopy = clone $this;
2967 }
2968
2969 if (!$notrigger && empty($error)) {
2970 // Call trigger
2971 $result = $this->call_trigger('PROPAL_MODIFY', $user);
2972 if ($result < 0) {
2973 $error++;
2974 }
2975 // End call triggers
2976 }
2977
2978 if (!$error) {
2979 $this->statut = self::STATUS_DRAFT; // deprecated
2980 $this->status = self::STATUS_DRAFT;
2981
2982 $this->db->commit();
2983 return 1;
2984 } else {
2985 foreach ($this->errors as $errmsg) {
2986 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2987 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2988 }
2989 $this->db->rollback();
2990 return -1 * $error;
2991 }
2992 }
2993
2994
2995 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3009 public function liste_array($shortlist = 0, $draft = 0, $notcurrentuser = 0, $socid = 0, $limit = 0, $offset = 0, $sortfield = 'p.datep', $sortorder = 'DESC')
3010 {
3011 // phpcs:enable
3012 global $user;
3013
3014 $ga = array();
3015
3016 $sql = "SELECT s.rowid, s.nom as name, s.client,";
3017 $sql .= " p.rowid as propalid, p.fk_statut, p.total_ht, p.ref, p.remise, ";
3018 $sql .= " p.datep as dp, p.fin_validite as datelimite";
3019 $sql .= " FROM ".MAIN_DB_PREFIX."societe as s, ".MAIN_DB_PREFIX.$this->table_element." as p, ".MAIN_DB_PREFIX."c_propalst as c";
3020 $sql .= " WHERE p.entity IN (".getEntity('propal').")";
3021 $sql .= " AND p.fk_soc = s.rowid";
3022 $sql .= " AND p.fk_statut = c.id";
3023
3024 // If the internal user must only see his customers, force searching by him
3025 $search_sale = 0;
3026 if (empty($user->socid) && !$user->hasRight('societe', 'client', 'voir')) {
3027 $search_sale = $user->id;
3028 }
3029 // Search on sale representative
3030 if ($search_sale && $search_sale != '-1') {
3031 if ($search_sale == -2) {
3032 $sql .= " AND NOT EXISTS (SELECT sc.fk_soc FROM ".MAIN_DB_PREFIX."societe_commerciaux as sc WHERE sc.fk_soc = p.fk_soc)";
3033 } elseif ($search_sale > 0) {
3034 $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).")";
3035 }
3036 }
3037 // Search on socid
3038 if ($socid) {
3039 $sql .= " AND p.fk_soc = ".((int) $socid);
3040 }
3041 if ($draft) {
3042 $sql .= " AND p.fk_statut = ".((int) self::STATUS_DRAFT);
3043 }
3044 if ($notcurrentuser > 0) {
3045 $sql .= " AND p.fk_user_author <> ".((int) $user->id);
3046 }
3047 $sql .= $this->db->order($sortfield, $sortorder);
3048 $sql .= $this->db->plimit($limit, $offset);
3049
3050 $result = $this->db->query($sql);
3051 if ($result) {
3052 $num = $this->db->num_rows($result);
3053 if ($num) {
3054 $i = 0;
3055 while ($i < $num) {
3056 $obj = $this->db->fetch_object($result);
3057
3058 if ($shortlist == 1) {
3059 $ga[$obj->propalid] = $obj->ref;
3060 } elseif ($shortlist == 2) {
3061 $ga[$obj->propalid] = $obj->ref.' ('.$obj->name.')';
3062 } else {
3063 $ga[$i]['id'] = $obj->propalid;
3064 $ga[$i]['ref'] = $obj->ref;
3065 $ga[$i]['name'] = $obj->name;
3066 }
3067
3068 $i++;
3069 }
3070 }
3071 return $ga;
3072 } else {
3073 dol_print_error($this->db);
3074 return -1;
3075 }
3076 }
3077
3083 public function getInvoiceArrayList()
3084 {
3085 return $this->InvoiceArrayList($this->id);
3086 }
3087
3088 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3095 public function InvoiceArrayList($id)
3096 {
3097 // phpcs:enable
3098 $ga = array();
3099 $linkedInvoices = array();
3100
3101 $this->fetchObjectLinked($id, $this->element);
3102 foreach ($this->linkedObjectsIds as $objecttype => $objectid) {
3103 // New system of common object return rowids and no linear id from 1 to n
3104 // We are therefore traversing a list of objects as a single object
3105 foreach ($objectid as $key => $object) {
3106 // Case of invoices directly linked
3107 if ($objecttype == 'facture') {
3108 $linkedInvoices[] = $object;
3109 } else {
3110 // Case of invoices linked by another object (e.g., order)
3111 $this->fetchObjectLinked($object, $objecttype);
3112 foreach ($this->linkedObjectsIds as $subobjecttype => $subobjectid) {
3113 foreach ($subobjectid as $subkey => $subobject) {
3114 if ($subobjecttype == 'facture') {
3115 $linkedInvoices[] = $subobject;
3116 }
3117 }
3118 }
3119 }
3120 }
3121 }
3122
3123 if (count($linkedInvoices) > 0) {
3124 $sql = "SELECT rowid as facid, ref, total_ht as total, datef as df, fk_user_author, fk_statut, paye";
3125 $sql .= " FROM ".MAIN_DB_PREFIX."facture";
3126 $sql .= " WHERE rowid IN (".$this->db->sanitize(implode(',', $linkedInvoices)).")";
3127
3128 dol_syslog(get_class($this)."::InvoiceArrayList", LOG_DEBUG);
3129 $resql = $this->db->query($sql);
3130
3131 if ($resql) {
3132 $tab_sqlobj = array();
3133 $nump = $this->db->num_rows($resql);
3134 for ($i = 0; $i < $nump; $i++) {
3135 $obj = $this->db->fetch_object($resql);
3136 $tab_sqlobj[] = $obj;
3137 }
3138 $this->db->free($resql);
3139
3140 $nump = count($tab_sqlobj);
3141
3142 if ($nump) {
3143 $i = 0;
3144 while ($i < $nump) {
3145 $obj = array_shift($tab_sqlobj);
3146
3147 $ga[$i] = $obj;
3148
3149 $i++;
3150 }
3151 }
3152 return $ga;
3153 } else {
3154 return -1;
3155 }
3156 } else {
3157 return $ga;
3158 }
3159 }
3160
3168 public function delete($user, $notrigger = 0)
3169 {
3170 global $conf;
3171 require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
3172
3173 $error = 0;
3174
3175 $this->db->begin();
3176
3177 if (!$notrigger) {
3178 // Call trigger
3179 $result = $this->call_trigger('PROPAL_DELETE', $user);
3180 if ($result < 0) {
3181 $error++;
3182 }
3183 // End call triggers
3184 }
3185
3186 // Remove linked categories.
3187 if (!$error) {
3188 $sql = "DELETE FROM ".MAIN_DB_PREFIX."categorie_propal";
3189 $sql .= " WHERE fk_propal = ".((int) $this->id);
3190
3191 $result = $this->db->query($sql);
3192 if (!$result) {
3193 $error++;
3194 $this->error = $this->db->lasterror();
3195 $this->errors[] = $this->error;
3196 }
3197 }
3198
3199 // Delete extrafields of lines and lines
3200 if (!$error && !empty($this->table_element_line)) {
3201 $tabletodelete = $this->table_element_line;
3202 $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).")";
3203 $sql = "DELETE FROM ".MAIN_DB_PREFIX.$tabletodelete." WHERE ".$this->fk_element." = ".((int) $this->id);
3204 if (!$this->db->query($sqlef) || !$this->db->query($sql)) {
3205 $error++;
3206 $this->error = $this->db->lasterror();
3207 $this->errors[] = $this->error;
3208 dol_syslog(get_class($this)."::delete error ".$this->error, LOG_ERR);
3209 }
3210 }
3211
3212 if (!$error) {
3213 // Delete linked object
3214 $res = $this->deleteObjectLinked();
3215 if ($res < 0) {
3216 $error++;
3217 }
3218 }
3219
3220 if (!$error) {
3221 // Delete linked contacts
3222 $res = $this->delete_linked_contact();
3223 if ($res < 0) {
3224 $error++;
3225 }
3226 }
3227
3228 // Removed extrafields of object
3229 if (!$error) {
3230 $result = $this->deleteExtraFields();
3231 if ($result < 0) {
3232 $error++;
3233 dol_syslog(get_class($this)."::delete error ".$this->error, LOG_ERR);
3234 }
3235 }
3236
3237 // Delete main record
3238 if (!$error) {
3239 $sql = "DELETE FROM ".MAIN_DB_PREFIX.$this->table_element." WHERE rowid = ".((int) $this->id);
3240 $res = $this->db->query($sql);
3241 if (!$res) {
3242 $error++;
3243 $this->error = $this->db->lasterror();
3244 $this->errors[] = $this->error;
3245 dol_syslog(get_class($this)."::delete error ".$this->error, LOG_ERR);
3246 }
3247 }
3248
3249 // Delete record into ECM index and physically
3250 if (!$error) {
3251 $res = $this->deleteEcmFiles(0); // Deleting files physically is done later with the dol_delete_dir_recursive
3252 $res = $this->deleteEcmFiles(1); // Deleting files physically is done later with the dol_delete_dir_recursive
3253 if (!$res) {
3254 $error++;
3255 }
3256 }
3257
3258 if (!$error) {
3259 // We remove directory
3260 $ref = dol_sanitizeFileName($this->ref);
3261 if ($conf->propal->multidir_output[$this->entity] && !empty($this->ref)) {
3262 $dir = $conf->propal->multidir_output[$this->entity]."/".$ref;
3263 $file = $dir."/".$ref.".pdf";
3264 if (file_exists($file)) {
3265 dol_delete_preview($this);
3266
3267 if (!dol_delete_file($file, 0, 0, 0, $this)) {
3268 $this->error = 'ErrorFailToDeleteFile';
3269 $this->errors[] = $this->error;
3270 $this->db->rollback();
3271 return 0;
3272 }
3273 }
3274 if (file_exists($dir)) {
3275 $res = @dol_delete_dir_recursive($dir); // delete files physically + into ecm tables
3276 if (!$res) {
3277 $this->error = 'ErrorFailToDeleteDir';
3278 $this->errors[] = $this->error;
3279 $this->db->rollback();
3280 return 0;
3281 }
3282 }
3283 }
3284 }
3285
3286 if (!$error) {
3287 dol_syslog(get_class($this)."::delete ".$this->id." by ".$user->id, LOG_DEBUG);
3288 $this->db->commit();
3289 return 1;
3290 } else {
3291 $this->db->rollback();
3292 return -1;
3293 }
3294 }
3295
3304 public function availability($availability_id, $notrigger = 0)
3305 {
3306 global $user;
3307
3308 if ($this->status >= self::STATUS_DRAFT) {
3309 $error = 0;
3310
3311 $this->db->begin();
3312
3313 $sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element;
3314 $sql .= ' SET fk_availability = '.((int) $availability_id);
3315 $sql .= ' WHERE rowid='.((int) $this->id);
3316
3317 dol_syslog(__METHOD__.' availability('.$availability_id.')', LOG_DEBUG);
3318 $resql = $this->db->query($sql);
3319 if (!$resql) {
3320 $this->errors[] = $this->db->error();
3321 $error++;
3322 }
3323
3324 if (!$error) {
3325 $this->oldcopy = clone $this;
3326 $this->availability_id = $availability_id;
3327 }
3328
3329 if (!$notrigger && empty($error)) {
3330 // Call trigger
3331 $result = $this->call_trigger('PROPAL_MODIFY', $user);
3332 if ($result < 0) {
3333 $error++;
3334 }
3335 // End call triggers
3336 }
3337
3338 if (!$error) {
3339 $this->db->commit();
3340 return 1;
3341 } else {
3342 foreach ($this->errors as $errmsg) {
3343 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
3344 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
3345 }
3346 $this->db->rollback();
3347 return -1 * $error;
3348 }
3349 } else {
3350 $error_str = 'Propal status do not meet requirement '.$this->status;
3351 dol_syslog(__METHOD__.$error_str, LOG_ERR);
3352 $this->error = $error_str;
3353 $this->errors[] = $this->error;
3354 return -2;
3355 }
3356 }
3357
3358 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3367 public function demand_reason($demand_reason_id, $notrigger = 0)
3368 {
3369 // phpcs:enable
3370 global $user;
3371
3372 if ($this->status >= self::STATUS_DRAFT) {
3373 $error = 0;
3374
3375 $this->db->begin();
3376
3377 $sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element;
3378 $sql .= ' SET fk_input_reason = '.((int) $demand_reason_id);
3379 $sql .= ' WHERE rowid='.((int) $this->id);
3380
3381 dol_syslog(__METHOD__.' demand_reason('.$demand_reason_id.')', LOG_DEBUG);
3382 $resql = $this->db->query($sql);
3383 if (!$resql) {
3384 $this->errors[] = $this->db->error();
3385 $error++;
3386 }
3387
3388 if (!$error) {
3389 $this->oldcopy = clone $this;
3390 $this->demand_reason_id = $demand_reason_id;
3391 }
3392
3393 if (!$notrigger && empty($error)) {
3394 // Call trigger
3395 $result = $this->call_trigger('PROPAL_MODIFY', $user);
3396 if ($result < 0) {
3397 $error++;
3398 }
3399 // End call triggers
3400 }
3401
3402 if (!$error) {
3403 $this->db->commit();
3404 return 1;
3405 } else {
3406 foreach ($this->errors as $errmsg) {
3407 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
3408 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
3409 }
3410 $this->db->rollback();
3411 return -1 * $error;
3412 }
3413 } else {
3414 $error_str = 'Propal status do not meet requirement '.$this->status;
3415 dol_syslog(__METHOD__.$error_str, LOG_ERR);
3416 $this->error = $error_str;
3417 $this->errors[] = $this->error;
3418 return -2;
3419 }
3420 }
3421
3422
3429 public function info($id)
3430 {
3431 $sql = "SELECT c.rowid, ";
3432 $sql .= " c.datec, c.date_valid as datev, c.date_signature, c.date_cloture,";
3433 $sql .= " c.fk_user_author, c.fk_user_valid, c.fk_user_signature, c.fk_user_cloture";
3434 $sql .= " FROM ".MAIN_DB_PREFIX.$this->table_element." as c";
3435 $sql .= " WHERE c.rowid = ".((int) $id);
3436
3437 $result = $this->db->query($sql);
3438
3439 if ($result) {
3440 if ($this->db->num_rows($result)) {
3441 $obj = $this->db->fetch_object($result);
3442
3443 $this->id = $obj->rowid;
3444
3445 $this->date_creation = $this->db->jdate($obj->datec);
3446 $this->date_validation = $this->db->jdate($obj->datev);
3447 $this->date_signature = $this->db->jdate($obj->date_signature);
3448 $this->date_cloture = $this->db->jdate($obj->date_cloture);
3449
3450 $this->user_creation_id = $obj->fk_user_author;
3451 $this->user_validation_id = $obj->fk_user_valid;
3452
3453 if ($obj->fk_user_signature) {
3454 $user_signature = new User($this->db);
3455 $user_signature->fetch($obj->fk_user_signature);
3456 $this->user_signature = $user_signature;
3457 }
3458
3459 $this->user_closing_id = $obj->fk_user_cloture;
3460 }
3461 $this->db->free($result);
3462 } else {
3463 dol_print_error($this->db);
3464 }
3465 }
3466
3467
3474 public function getLibStatut($mode = 0)
3475 {
3476 return $this->LibStatut($this->status, $mode);
3477 }
3478
3479 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3487 public function LibStatut($status, $mode = 1)
3488 {
3489 // phpcs:enable
3490 global $hookmanager;
3491
3492 // Init/load array of translation of status
3493 if (empty($this->labelStatus) || empty($this->labelStatusShort)) {
3494 global $langs;
3495 $langs->load("propal");
3496 $this->labelStatus[-1] = $langs->transnoentitiesnoconv("PropalStatusCanceled");
3497 $this->labelStatus[0] = $langs->transnoentitiesnoconv("PropalStatusDraft");
3498 $this->labelStatus[1] = $langs->transnoentitiesnoconv("PropalStatusValidated");
3499 $this->labelStatus[2] = $langs->transnoentitiesnoconv("PropalStatusSigned");
3500 $this->labelStatus[3] = $langs->transnoentitiesnoconv("PropalStatusNotSigned");
3501 $this->labelStatus[4] = $langs->transnoentitiesnoconv("PropalStatusBilled");
3502 $this->labelStatusShort[-1] = $langs->transnoentitiesnoconv("PropalStatusCanceledShort");
3503 $this->labelStatusShort[0] = $langs->transnoentitiesnoconv("PropalStatusDraftShort");
3504 $this->labelStatusShort[1] = $langs->transnoentitiesnoconv("PropalStatusValidatedShort");
3505 $this->labelStatusShort[2] = $langs->transnoentitiesnoconv("PropalStatusSignedShort");
3506 $this->labelStatusShort[3] = $langs->transnoentitiesnoconv("PropalStatusNotSignedShort");
3507 $this->labelStatusShort[4] = $langs->transnoentitiesnoconv("PropalStatusBilledShort");
3508 }
3509
3510 $statusType = '';
3511 if ($status == self::STATUS_CANCELED) {
3512 $statusType = 'status9';
3513 } elseif ($status == self::STATUS_DRAFT) {
3514 $statusType = 'status0';
3515 } elseif ($status == self::STATUS_VALIDATED) {
3516 $statusType = 'status1';
3517 } elseif ($status == self::STATUS_SIGNED) {
3518 $statusType = 'status4';
3519 } elseif ($status == self::STATUS_NOTSIGNED) {
3520 $statusType = 'status9';
3521 } elseif ($status == self::STATUS_BILLED) {
3522 $statusType = 'status6';
3523 }
3524
3525 $parameters = array('status' => $status, 'mode' => $mode);
3526 $reshook = $hookmanager->executeHooks('LibStatut', $parameters, $this); // Note that $action and $object may have been modified by hook
3527
3528 if ($reshook > 0) {
3529 return $hookmanager->resPrint;
3530 }
3531
3532 return dolGetStatus($this->labelStatus[$status], $this->labelStatusShort[$status], '', $statusType, $mode);
3533 }
3534
3535
3536 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3544 public function load_board($user, $mode)
3545 {
3546 // phpcs:enable
3547 global $langs, $hookmanager;
3548
3549 $sql = "SELECT p.rowid, p.ref, p.datec as datec, p.fin_validite as datefin, p.total_ht";
3550 if (empty($user->socid) && !$user->hasRight('societe', 'client', 'voir')) {
3551 $sql .= " FROM ".MAIN_DB_PREFIX.$this->table_element." as p";
3552 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe_commerciaux as sc ON p.fk_soc = sc.fk_soc";
3553 } else {
3554 $sql .= " FROM ".MAIN_DB_PREFIX."propal as p";
3555 }
3556 $sql .= " WHERE p.entity IN (".getEntity('propal').")";
3557 if ($mode == 'opened') {
3558 $sql .= " AND p.fk_statut = ".self::STATUS_VALIDATED;
3559 }
3560 if ($mode == 'signed') {
3561 $sql .= " AND p.fk_statut = ".self::STATUS_SIGNED;
3562 }
3563
3564 if (empty($user->socid) && !$user->hasRight('societe', 'client', 'voir')) {
3565 $sql .= " AND sc.fk_user = ".((int) $user->id);
3566 }
3567 if ($user->socid) {
3568 $sql .= " AND p.fk_soc = ".((int) $user->socid);
3569 }
3570
3571 // If the internal user must only see his customers, force searching by him
3572 $search_sale = 0;
3573 if (empty($user->socid) && !$user->hasRight('societe', 'client', 'voir')) {
3574 $search_sale = $user->id;
3575 }
3576 // Search on sale representative
3577 if ($search_sale && $search_sale != '-1') {
3578 if ($search_sale == -2) {
3579 $sql .= " AND NOT EXISTS (SELECT sc.fk_soc FROM ".MAIN_DB_PREFIX."societe_commerciaux as sc WHERE sc.fk_soc = p.fk_soc)";
3580 } elseif ($search_sale > 0) {
3581 $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).")";
3582 }
3583 }
3584 if ($user->socid) {
3585 $sql .= " AND p.fk_soc = ".((int) $user->socid);
3586 }
3587 // Add where from hooks
3588 $parameters = array('socid' => $user->socid);
3589 $reshook = $hookmanager->executeHooks('printFieldListWhere', $parameters, $this); // Note that $action and $object may have been modified by hook
3590 $sql .= $hookmanager->resPrint;
3591
3592 $resql = $this->db->query($sql);
3593 if ($resql) {
3594 $langs->load("propal");
3595 $now = dol_now();
3596
3597 $delay_warning = 0;
3598 $status = 0;
3599 $label = $labelShort = '';
3600 if ($mode == 'opened') {
3601 $delay_warning = getWarningDelay('propal', 'cloture');
3602 $status = self::STATUS_VALIDATED;
3603 $label = $langs->transnoentitiesnoconv("PropalsToClose");
3604 $labelShort = $langs->transnoentitiesnoconv("ToAcceptRefuse");
3605 }
3606 if ($mode == 'signed') {
3607 $delay_warning = getWarningDelay('propal', 'facturation');
3608 $status = self::STATUS_SIGNED;
3609 $label = $langs->trans("PropalsToBill"); // We set here bill but may be billed or ordered
3610 $labelShort = $langs->trans("ToBill");
3611 }
3612
3613 $response = new WorkboardResponse();
3614 $response->warning_delay = $delay_warning / 60 / 60 / 24;
3615 $response->label = $label;
3616 $response->labelShort = $labelShort;
3617 $response->url = dolBuildUrl(DOL_URL_ROOT.'/comm/propal/list.php', ['search_status' => $status, 'mainmenu' => 'commercial', 'leftmenu' => 'propals']);
3618 $response->url_late = dolBuildUrl(DOL_URL_ROOT.'/comm/propal/list.php', ['search_option' => 'late', 'mainmenu' => 'commercial', 'leftmenu' => 'propals', 'sortfield' => 'p.datep', 'sortorder' => 'asc']);
3619 $response->img = img_object('', "propal");
3620
3621 // This assignment in condition is not a bug. It allows walking the results.
3622 while ($obj = $this->db->fetch_object($resql)) {
3623 $response->nbtodo++;
3624 $response->total += $obj->total_ht;
3625
3626 if ($mode == 'opened') {
3627 $datelimit = $this->db->jdate($obj->datefin);
3628 if ($datelimit < ($now - $delay_warning)) {
3629 $response->nbtodolate++;
3630 }
3631 }
3632 // TODO Definir regle des propales a facturer en retard
3633 // if ($mode == 'signed' && ! count($this->FactureListeArray($obj->rowid))) $this->nbtodolate++;
3634 }
3635
3636 return $response;
3637 } else {
3638 $this->error = $this->db->error();
3639 return -1;
3640 }
3641 }
3642
3643
3652 public function initAsSpecimen($param = array())
3653 {
3654 global $conf, $langs;
3655
3656 // Load array of products prodids
3657 $num_prods = 0;
3658 $prodids = array();
3659 $sql = "SELECT rowid";
3660 $sql .= " FROM ".MAIN_DB_PREFIX."product";
3661 $sql .= " WHERE entity IN (".getEntity('product').")";
3662 if (array_key_exists('tosell', $param)) {
3663 $sql .= " AND tosell = ".((int) $param['tosell']);
3664 }
3665 $sql .= $this->db->plimit(100);
3666
3667 $resql = $this->db->query($sql);
3668 if ($resql) {
3669 $num_prods = $this->db->num_rows($resql);
3670 $i = 0;
3671 while ($i < $num_prods) {
3672 $i++;
3673 $row = $this->db->fetch_row($resql);
3674 $prodids[$i] = $row[0];
3675 }
3676 }
3677
3678 // Initialise parameters
3679 $this->id = 0;
3680 $this->ref = 'SPECIMEN';
3681 $this->ref_customer = 'NEMICEPS';
3682 $this->ref_client = 'NEMICEPS';
3683 $this->specimen = 1;
3684 $this->socid = 1;
3685 $this->date = time();
3686 $this->fin_validite = $this->date + 3600 * 24 * 30;
3687 $this->cond_reglement_id = 1;
3688 $this->cond_reglement_code = 'RECEP';
3689 $this->mode_reglement_id = 7;
3690 $this->mode_reglement_code = 'CHQ';
3691 $this->availability_id = 1;
3692 $this->availability_code = 'AV_NOW';
3693 $this->demand_reason_id = 1;
3694 $this->demand_reason_code = 'SRC_00';
3695 $this->note_public = 'This is a comment (public)';
3696 $this->note_private = 'This is a comment (private)';
3697
3698 $this->multicurrency_tx = 1;
3699 $this->multicurrency_code = getDolCurrency();
3700
3701 // Lines
3702 $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)
3703 $xnbp = 0;
3704 while ($xnbp < $nbp) {
3705 $line = new PropaleLigne($this->db);
3706 $line->desc = $langs->trans("Description")." ".$xnbp;
3707 $line->qty = 1;
3708 $line->subprice = 100;
3709 $line->tva_tx = 20;
3710 $line->localtax1_tx = 0;
3711 $line->localtax2_tx = 0;
3712 if ($xnbp == 2) {
3713 $line->total_ht = 50;
3714 $line->total_ttc = 60;
3715 $line->total_tva = 10;
3716 $line->remise_percent = 50;
3717 } else {
3718 $line->total_ht = 100;
3719 $line->total_ttc = 120;
3720 $line->total_tva = 20;
3721 $line->remise_percent = 00;
3722 }
3723
3724 if ($num_prods > 0) {
3725 $prodid = mt_rand(1, $num_prods);
3726 $line->fk_product = $prodids[$prodid];
3727 $line->product_ref = 'SPECIMEN';
3728 }
3729
3730 $this->lines[$xnbp] = $line;
3731
3732 $this->total_ht += $line->total_ht;
3733 $this->total_tva += $line->total_tva;
3734 $this->total_ttc += $line->total_ttc;
3735
3736 $xnbp++;
3737 }
3738
3739 return 1;
3740 }
3741
3747 public function loadStateBoard()
3748 {
3749 global $user, $hookmanager;
3750
3751 $this->nb = array();
3752
3753 $sql = "SELECT count(p.rowid) as nb";
3754 $sql .= " FROM ".MAIN_DB_PREFIX.$this->table_element." as p";
3755 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe as s ON p.fk_soc = s.rowid";
3756 $sql .= " WHERE p.entity IN (".getEntity('propal').")";
3757
3758 // If the internal user must only see his customers, force searching by him
3759 $search_sale = 0;
3760 if (empty($user->socid) && !$user->hasRight('societe', 'client', 'voir')) {
3761 $search_sale = $user->id;
3762 }
3763 // Search on sale representative
3764 if ($search_sale && $search_sale != '-1') {
3765 if ($search_sale == -2) {
3766 $sql .= " AND NOT EXISTS (SELECT sc.fk_soc FROM ".MAIN_DB_PREFIX."societe_commerciaux as sc WHERE sc.fk_soc = p.fk_soc)";
3767 } elseif ($search_sale > 0) {
3768 $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).")";
3769 }
3770 }
3771
3772 // Add where from hooks
3773 $parameters = array();
3774 $hookmanager->executeHooks('printFieldListWhere', $parameters, $this); // Note that $action and $object may have been modified by hook
3775 $sql .= $hookmanager->resPrint;
3776 $resql = $this->db->query($sql);
3777 if ($resql) {
3778 // This assignment in condition is not a bug. It allows walking the results.
3779 while ($obj = $this->db->fetch_object($resql)) {
3780 $this->nb["proposals"] = $obj->nb;
3781 }
3782 $this->db->free($resql);
3783 return 1;
3784 } else {
3785 dol_print_error($this->db);
3786 $this->error = $this->db->error();
3787 return -1;
3788 }
3789 }
3790
3791
3799 public function getNextNumRef($soc)
3800 {
3801 global $conf, $langs;
3802 $langs->load("propal");
3803
3804 $classname = getDolGlobalString('PROPALE_ADDON');
3805
3806 if (!empty($classname)) {
3807 $mybool = false;
3808
3809 $file = $classname.".php";
3810
3811 // Include file with class
3812 $dirmodels = array_merge(array('/'), (array) $conf->modules_parts['models']);
3813 foreach ($dirmodels as $reldir) {
3814 $dir = dol_buildpath($reldir."core/modules/propale/");
3815
3816 // Load file with numbering class (if found)
3817 $mybool = ((bool) @include_once $dir.$file) || $mybool;
3818 }
3819
3820 if (!$mybool) {
3821 dol_print_error(null, "Failed to include file ".$file);
3822 return '';
3823 }
3824
3825 $obj = new $classname();
3826 '@phan-var-force ModeleNumRefPropales $obj';
3829 $numref = $obj->getNextValue($soc, $this);
3830
3831 if ($numref != "") {
3832 return $numref;
3833 } else {
3834 $this->error = $obj->error;
3835 //dol_print_error($db,"Propale::getNextNumRef ".$obj->error);
3836 return "";
3837 }
3838 } else {
3839 $langs->load("errors");
3840 print $langs->trans("Error")." ".$langs->trans("ErrorModuleSetupNotComplete", $langs->transnoentitiesnoconv("Proposal"));
3841 return "";
3842 }
3843 }
3844
3851 public function getTooltipContentArray($params)
3852 {
3853 global $conf, $langs, $user;
3854
3855 $langs->load('propal');
3856 $datas = [];
3857 $nofetch = !empty($params['nofetch']);
3858
3859 if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
3860 return ['optimize' => $langs->trans("Proposal")];
3861 }
3862 if ($user->hasRight('propal', 'lire')) {
3863 $datas['picto'] = img_picto('', $this->picto, '', 0, 0, 0, '', 'paddingrightonly').'<u>'.$langs->trans("Proposal").'</u>';
3864 if (isset($this->status)) {
3865 $datas['status'] = ' '.$this->getLibStatut(5);
3866 }
3867 if (!empty($this->ref)) {
3868 $datas['ref'] = '<br><b>'.$langs->trans('Ref').':</b> '.$this->ref;
3869 }
3870 if (!$nofetch) {
3871 $langs->load('companies');
3872 if (empty($this->thirdparty)) {
3873 $this->fetch_thirdparty();
3874 }
3875 $datas['customer'] = '<br><b>'.$langs->trans('Customer').':</b> '.$this->thirdparty->getNomUrl(1, '', 0, 1);
3876 }
3877 if (!empty($this->ref_customer)) {
3878 $datas['refcustomer'] = '<br><b>'.$langs->trans('RefCustomer').':</b> '.$this->ref_customer;
3879 }
3880 if (!$nofetch) {
3881 $langs->load('project');
3882 if (is_null($this->project) || (is_object($this->project) && empty($this->project->id))) {
3883 $res = $this->fetchProject();
3884 if ($res > 0 && $this->project instanceof Project) {
3885 $datas['project'] = '<br><b>'.$langs->trans('Project').':</b> '.$this->project->getNomUrl(1, '', 0, '1');
3886 }
3887 }
3888 }
3889 if (!empty($this->total_ht)) {
3890 $datas['amountht'] = '<br><b>'.$langs->trans('AmountHT').':</b> '.price($this->total_ht, 0, $langs, 0, -1, -1, $conf->currency);
3891 }
3892 if (!empty($this->total_tva)) {
3893 $datas['vat'] = '<br><b>'.$langs->trans('VAT').':</b> '.price($this->total_tva, 0, $langs, 0, -1, -1, $conf->currency);
3894 }
3895 if (!empty($this->total_ttc)) {
3896 $datas['amountttc'] = '<br><b>'.$langs->trans('AmountTTC').':</b> '.price($this->total_ttc, 0, $langs, 0, -1, -1, $conf->currency);
3897 }
3898 if (!empty($this->date)) {
3899 $datas['date'] = '<br><b>'.$langs->trans('Date').':</b> '.dol_print_date($this->date, 'day');
3900 }
3901 if (!empty($this->date_signature)) {
3902 $datas['datesignature'] = '<br><b>'.$langs->trans('DateSigning').':</b> '.dol_print_date($this->date_signature, 'day');
3903 }
3904 if (!empty($this->delivery_date)) {
3905 $datas['deliverydate'] = '<br><b>'.$langs->trans('DeliveryDate').':</b> '.dol_print_date($this->delivery_date, 'dayhour');
3906 }
3907 }
3908
3909 return $datas;
3910 }
3911
3923 public function getNomUrl($withpicto = 0, $option = '', $get_params = '', $notooltip = 0, $save_lastsearch_value = -1, $addlinktonotes = -1)
3924 {
3925 global $langs, $conf, $user, $hookmanager;
3926
3927 if (!empty($conf->dol_no_mouse_hover)) {
3928 $notooltip = 1; // Force disable tooltips
3929 }
3930
3931 $result = '';
3932 $params = [
3933 'id' => $this->id,
3934 'objecttype' => $this->element,
3935 'option' => $option,
3936 'nofetch' => 1,
3937 ];
3938 $classfortooltip = 'classfortooltip';
3939 $dataparams = '';
3940 if (getDolGlobalInt('MAIN_ENABLE_AJAX_TOOLTIP')) {
3941 $classfortooltip = 'classforajaxtooltip';
3942 $dataparams = ' data-params="'.dol_escape_htmltag(json_encode($params)).'"';
3943 $label = '';
3944 } else {
3945 $label = implode($this->getTooltipContentArray($params));
3946 }
3947
3948 $baseurl = '';
3949 $query = [];
3950 if ($user->hasRight('propal', 'lire')) {
3951 parse_str($get_params, $query);
3952 $query = array_merge($query, ['id' => $this->id]);
3953 if ($option == '') {
3954 $baseurl = DOL_URL_ROOT . '/comm/propal/card.php';
3955 } elseif ($option == 'compta') { // deprecated
3956 $baseurl = DOL_URL_ROOT . '/comm/propal/card.php';
3957 } elseif ($option == 'expedition') {
3958 $baseurl = DOL_URL_ROOT . '/expedition/propal.php';
3959 } elseif ($option == 'document') {
3960 $baseurl = DOL_URL_ROOT . '/comm/propal/document.php';
3961 }
3962
3963 if ($option != 'nolink') {
3964 // Add param to save lastsearch_values or not
3965 $add_save_lastsearch_values = ($save_lastsearch_value == 1 ? 1 : 0);
3966 if ($save_lastsearch_value == -1 && isset($_SERVER["PHP_SELF"]) && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) {
3967 $add_save_lastsearch_values = 1;
3968 }
3969 if ($add_save_lastsearch_values) {
3970 $query = array_merge($query, ['save_lastsearch_values' => 1]);
3971 }
3972 }
3973 }
3974 $url = dolBuildUrl($baseurl, $query);
3975
3976 $linkclose = '';
3977 if (empty($notooltip) && $user->hasRight('propal', 'lire')) {
3978 if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
3979 $label = $langs->trans("Proposal");
3980 $linkclose .= ' alt="'.dolPrintHTMLForAttribute($label).'"';
3981 }
3982 $linkclose .= ($label ? ' title="'.dolPrintHTMLForAttribute($label).'"' : ' title="tocomplete"');
3983 $linkclose .= $dataparams.' class="'.$classfortooltip.'"';
3984 }
3985
3986 $linkstart = '<a href="'.$url.'"';
3987 $linkstart .= $linkclose.'>';
3988 $linkend = '</a>';
3989
3990 $result .= $linkstart;
3991 if ($withpicto) {
3992 $result .= img_object(($notooltip ? '' : $label), ($this->picto ? $this->picto : 'generic'), (($withpicto != 2) ? 'class="paddingright"' : ''), 0, 0, $notooltip ? 0 : 1);
3993 }
3994 if ($withpicto != 2) {
3995 $result .= $this->ref;
3996 }
3997 $result .= $linkend;
3998
3999 if ($addlinktonotes >= 0) {
4000 $txttoshow = '';
4001
4002 if ($addlinktonotes == 0) {
4003 if (!empty($this->note_private) || !empty($this->note_public)) {
4004 $txttoshow = $langs->trans('ViewPrivateNote');
4005 }
4006 } elseif ($addlinktonotes == 1) {
4007 if (!empty($this->note_private)) {
4008 $txttoshow .= ($user->socid > 0 ? '' : dol_string_nohtmltag($this->note_private, 1));
4009 }
4010 } elseif ($addlinktonotes == 2) {
4011 if (!empty($this->note_public)) {
4012 $txttoshow .= dol_string_nohtmltag($this->note_public, 1);
4013 }
4014 } elseif ($addlinktonotes == 3) {
4015 if ($user->socid > 0) {
4016 if (!empty($this->note_public)) {
4017 $txttoshow .= dol_string_nohtmltag($this->note_public, 1);
4018 }
4019 } else {
4020 if (!empty($this->note_public)) {
4021 $txttoshow .= dol_string_nohtmltag($this->note_public, 1);
4022 }
4023 if (!empty($this->note_private)) {
4024 if (!empty($txttoshow)) {
4025 $txttoshow .= '<br><br>';
4026 }
4027 $txttoshow .= dol_string_nohtmltag($this->note_private, 1);
4028 }
4029 }
4030 }
4031
4032 if ($txttoshow) {
4033 $result .= ' <span class="note inline-block">';
4034 $result .= '<a href="'.dolBuildUrl(DOL_URL_ROOT.'/comm/propal/note.php', ['id' => $this->id]).'" class="classfortooltip" title="'.dol_escape_htmltag($txttoshow).'">';
4035 $result .= img_picto('', 'note');
4036 $result .= '</a>';
4037 $result .= '</span>';
4038 }
4039 }
4040
4041 global $action;
4042 $hookmanager->initHooks(array($this->element . 'dao'));
4043 $parameters = array('id' => $this->id, 'getnomurl' => &$result);
4044 $reshook = $hookmanager->executeHooks('getNomUrl', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
4045 if ($reshook > 0) {
4046 $result = $hookmanager->resPrint;
4047 } else {
4048 $result .= $hookmanager->resPrint;
4049 }
4050 return $result;
4051 }
4052
4059 public function getLinesArray($sqlforgedfilters = '')
4060 {
4061 return $this->fetch_lines(0, 0, $sqlforgedfilters);
4062 }
4063
4075 public function generateDocument($modele, $outputlangs, $hidedetails = 0, $hidedesc = 0, $hideref = 0, $moreparams = null)
4076 {
4077 $outputlangs->loadLangs(array("propale", "products"));
4078
4079 if (!dol_strlen($modele)) {
4080 $modele = getDolGlobalString('PROPALE_ADDON_PDF', 'cyan');
4081
4082 if ($this->model_pdf) {
4083 $modele = $this->model_pdf;
4084 }
4085 }
4086
4087 $modelpath = "core/modules/propale/doc/";
4088
4089 return $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams);
4090 }
4091
4100 public static function replaceThirdparty(DoliDB $dbs, $origin_id, $dest_id)
4101 {
4102 $tables = array(
4103 'propal'
4104 );
4105
4106 return CommonObject::commonReplaceThirdparty($dbs, $origin_id, $dest_id, $tables);
4107 }
4108
4117 public static function replaceProduct(DoliDB $db, $origin_id, $dest_id)
4118 {
4119 $tables = array(
4120 'propaldet'
4121 );
4122
4123 return CommonObject::commonReplaceProduct($db, $origin_id, $dest_id, $tables);
4124 }
4125
4133 public function getKanbanView($option = '', $arraydata = null)
4134 {
4135 global $langs;
4136
4137 $selected = (empty($arraydata['selected']) ? 0 : $arraydata['selected']);
4138
4139 $return = '<div class="box-flex-item box-flex-grow-zero">';
4140 $return .= '<div class="info-box info-box-sm">';
4141 $return .= '<div class="info-box-icon bg-infobox-action">';
4142 $return .= img_picto('', $this->picto);
4143 $return .= '</div>';
4144 $return .= '<div class="info-box-content">';
4145 $return .= '<span class="info-box-ref inline-block tdoverflowmax150 valignmiddle">' . $this->getNomUrl() . '</span>';
4146 if ($selected >= 0) {
4147 $return .= '<input id="cb'.$this->id.'" class="flat checkforselect fright" type="checkbox" name="toselect[]" value="'.$this->id.'"'.($selected ? ' checked="checked"' : '').'>';
4148 }
4149 if (!empty($arraydata['projectlink'])) {
4150 $return .= '<span class="info-box-ref"> | '.$arraydata['projectlink'].'</span>';
4151 }
4152 $return .= '<br>';
4153 if (is_object($this->thirdparty)) {
4154 $return .= '<div class="info-box-ref tdoverflowmax150">'.$this->thirdparty->getNomUrl(1).'</div>';
4155 }
4156 $return .= '<span class="info-box-label amount" title="'.$langs->trans("AmountHT").'">'.price($this->total_ht).'</span>';
4157 if (!empty($arraydata['authorlink'])) {
4158 $return .= ' &nbsp; <span class="info-box-label">'.$arraydata['authorlink'].'</span>';
4159 }
4160 $return .= '<br><div class="info-box-status">'.$this->getLibStatut(3).'</div>';
4161 $return .= '</div>';
4162 $return .= '</div>';
4163 $return .= '</div>';
4164 return $return;
4165 }
4166
4177 public function setCategories($categories)
4178 {
4179 require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php';
4180 return parent::setCategoriesCommon($categories, Categorie::TYPE_PROPOSAL);
4181 }
4182}
$id
Support class for third parties, contacts, members, users or resources.
Definition account.php:47
if(! $sortfield) if(! $sortorder) $object
Definition account.php:100
$object ref
Definition info.php:90
Parent class of all other business classes (invoices, contracts, proposals, orders,...
fetch_optionals($rowid=null, $optionsArray=null)
Function to get extra fields of an object into $this->array_options This method is in most cases call...
line_order($renum=false, $rowidorder='ASC', $fk_parent_line=true)
Save a new position (field rang) for details lines.
deleteEcmFiles($mode=0)
Delete related files of object in database.
update_price($exclspec=0, $roundingadjust='auto', $nodatabaseupdate=0, $seller=null)
Update total_ht, total_ttc, total_vat, total_localtax1, total_localtax2 for an object (sum of lines).
add_object_linked($origin=null, $origin_id=null, $f_user=null, $notrigger=0)
Add an object link into llx_element_element.
roundQtyToPackaging($qty, $packaging)
Round a quantity up to the next multiple of a packaging quantity (options PRODUCT_USE_CUSTOMER_PACKAG...
commonGenerateDocument($modelspath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams=null)
Common function for all objects extending CommonObject for generating documents.
fetchProject()
Load the project with id $this->fk_project into this->project.
fetch_thirdparty($force_thirdparty_id=0)
Load the third party of object, from id $this->socid or $this->fk_soc, into this->thirdparty.
deleteObjectLinked($sourceid=null, $sourcetype='', $targetid=null, $targettype='', $rowid=0, $f_user=null, $notrigger=0)
Delete all links between an object $this.
setErrorsFromObject($object)
setErrorsFromObject
isLineOfObject($lineid)
Check that a line belongs to this object, using $this->table_element_line and $this->fk_element.
static isExistingObject($element, $id, $ref='', $ref_ext='')
Check if an object id or ref exists If you don't need or want to instantiate the object and just need...
updateRangOfLine($rowid, $rang)
Update position of line (rang)
checkActiveProductInLines($status='onsale')
Check if all products have the right status (on sale, on buy) called during validation of propal,...
deleteExtraFields()
Delete all extra fields values for the current object.
fetchObjectLinked($sourceid=null, $sourcetype='', $targetid=null, $targettype='', $clause='OR', $alsosametype=1, $orderby='sourcetype', $loadalsoobjects=1)
Fetch array of objects linked to current object (object of enabled modules only).
static commonReplaceThirdparty(DoliDB $dbs, $origin_id, $dest_id, array $tables, $ignoreerrors=0)
Function used to replace a thirdparty id with another one.
static commonReplaceProduct(DoliDB $dbs, $origin_id, $dest_id, array $tables, $ignoreerrors=0)
Function used to replace a product id with another one.
line_max($fk_parent_line=0)
Get max value used for position of line (rang)
insertExtraFields($trigger='', $userused=null)
Add/Update all extra fields values for the current object.
delete_linked_contact($source='', $code='')
Delete all links between an object $this and all its contacts in llx_element_contact.
Class to manage absolute discounts.
Class to manage Dolibarr database access.
static getIdAndTxFromCode($dbs, $code, $date_document=0)
Get id and rate of currency from code.
static getIdFromCode($dbs, $code)
Get id of currency from code.
File of class to manage predefined price products or services by customer.
Class to manage predefined suppliers products.
Class to manage products or services.
Class to manage projects.
Class to manage proposals.
getTooltipContentArray($params)
getTooltipContentArray
const STATUS_DRAFT
Draft status.
set_date($user, $date, $notrigger=0)
Define proposal date.
const STATUS_SIGNED
Signed quote.
getNomUrl($withpicto=0, $option='', $get_params='', $notooltip=0, $save_lastsearch_value=-1, $addlinktonotes=-1)
Return clickable link of object (with eventually picto)
getKanbanView($option='', $arraydata=null)
Return clickable link of object (with eventually picto)
InvoiceArrayList($id)
Returns an array with id and ref of related invoices.
availability($availability_id, $notrigger=0)
Change the delivery time.
const STATUS_NOTSIGNED
Not signed quote.
$table_ref_field
{}
setDeliveryDate($user, $delivery_date, $notrigger=0)
Set delivery date.
classifyBilled(User $user, $notrigger=0, $note='')
Classify the proposal to status Billed.
getLinesArray($sqlforgedfilters='')
Retrieve an array of proposal lines.
fetch($rowid, $ref='', $ref_ext='', $forceentity=0)
Load a proposal from database.
set_availability($user, $id, $notrigger=0)
Set delivery.
fetch_lines($only_product=0, $loadalsotranslation=0, $sqlforgedfilters='')
Load array lines.
updateline($rowid, $pu, $qty, $remise_percent, $txtva, $txlocaltax1=0.0, $txlocaltax2=0.0, $desc='', $price_base_type='HT', $info_bits=0, $special_code=0, $fk_parent_line=0, $skip_update_total=0, $fk_fournprice=0, $pa_ht=0, $label='', $type=0, $date_start='', $date_end='', $array_options=array(), $fk_unit=null, $pu_ht_devise=0, $notrigger=0, $rang=0)
Update a proposal line.
update(User $user, $notrigger=0)
Update database.
addline( $desc, $pu_ht, $qty, $txtva, $txlocaltax1=0.0, $txlocaltax2=0.0, $fk_product=0, $remise_percent=0.0, $price_base_type='HT', $pu_ttc=0.0, $info_bits=0, $type=0, $rang=-1, $special_code=0, $fk_parent_line=0, $fk_fournprice=0, $pa_ht=0, $label='', $date_start='', $date_end='', $array_options=array(), $fk_unit=null, $origin='', $origin_id=0, $pu_ht_devise=0, $fk_remise_except=0, $noupdateafterinsertline=0)
Add a proposal line into database (linked to product/service or not) The parameters are already suppo...
closeProposal($user, $status, $note_private='', $notrigger=0, $note_public='')
Close/set the commercial proposal to status signed or refused (fill also date signature)
demand_reason($demand_reason_id, $notrigger=0)
Change source demand.
info($id)
Object Proposal Information.
const STATUS_BILLED
Billed or processed quote.
initAsSpecimen($param=array())
Initialise an instance with random values.
static replaceThirdparty(DoliDB $dbs, $origin_id, $dest_id)
Function used to replace a thirdparty id with another one.
getLibStatut($mode=0)
Return label of status of proposal (draft, validated, ...)
const STATUS_CANCELED
Canceled status.
insert_discount($idremise)
Add a discount line into an proposal (as a proposal line) using an existing absolute discount (Consum...
LibStatut($status, $mode=1)
Return label of a status (draft, validated, ...)
valid($user, $notrigger=0)
Set status to validated.
static replaceProduct(DoliDB $db, $origin_id, $dest_id)
Function used to replace a product id with another one.
set_ref_client($user, $ref_client, $notrigger=0)
Set customer reference number.
setDraft($user, $notrigger=0)
Set draft status.
set_echeance($user, $date_end_validity, $notrigger=0)
Define end validity date.
set_demand_reason($user, $id, $notrigger=0)
Set source of demand.
setCategories($categories)
Sets object to supplied categories.
generateDocument($modele, $outputlangs, $hidedetails=0, $hidedesc=0, $hideref=0, $moreparams=null)
Create a document onto disk according to template module.
create($user, $notrigger=0)
Create commercial proposal into database this->ref can be set or empty.
liste_array($shortlist=0, $draft=0, $notcurrentuser=0, $socid=0, $limit=0, $offset=0, $sortfield='p.datep', $sortorder='DESC')
Return list of proposal (eventually filtered on user) into an array.
add_product($idproduct, $qty, $remise_percent=0)
Add line into array ->lines $this->thirdparty should be loaded.
reopen($user, $status, $note='', $notrigger=0)
Reopen the commercial proposal.
deleteLine($lineid, $id=0)
Delete detail line.
__construct($db, $socid=0, $propalid=0)
Constructor.
load_board($user, $mode)
Load indicators for dashboard (this->nbtodo and this->nbtodolate)
setCancel(User $user)
Cancel the proposal.
getInvoiceArrayList()
Returns an array with the numbers of related invoices.
set_date_livraison($user, $delivery_date, $notrigger=0)
Set delivery date.
const STATUS_VALIDATED
Validated status.
loadStateBoard()
Load the indicators this->nb for the state board.
createFromClone(User $user, $socid=0, $forceentity=null, $update_prices=false, $update_desc=false)
Load an object from its id and create a new one in database.
Class to manage commercial proposal lines.
Class to manage third parties objects (customers, suppliers, prospects...)
Class to manage translations.
Class to manage Dolibarr users.
print $langs trans("Ref").' m titre as m m statut as status
Or an array listing all the potential status of the object: array: int of the status => translated la...
Definition index.php:169
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)
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)
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.
GETPOST($paramname, $check='alphanohtml', $method=0, $filter=null, $options=null, $noreplace=0, $nodefault=0)
Return value of a param into GET or POST supervariable.
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.
GETPOSTINT($paramname, $method=0, $nodefault=0)
Return the value of a $_GET or $_POST supervariable, converted into integer.
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...
dol_trunc($string, $size=40, $trunc='right', $stringencoding='UTF-8', $nodot=0, $display=0)
Truncate a string to a particular length adding '...' if string larger than length.
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, $notused, $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:492