dolibarr 25.0.0-alpha
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 * Copyright (C) 2026 Nick Fragoulis
25 *
26 * This program is free software; you can redistribute it and/or modify
27 * it under the terms of the GNU General Public License as published by
28 * the Free Software Foundation; either version 3 of the License, or
29 * (at your option) any later version.
30 *
31 * This program is distributed in the hope that it will be useful,
32 * but WITHOUT ANY WARRANTY; without even the implied warranty of
33 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
34 * GNU General Public License for more details.
35 *
36 * You should have received a copy of the GNU General Public License
37 * along with this program. If not, see <https://www.gnu.org/licenses/>.
38 */
39
46require_once DOL_DOCUMENT_ROOT.'/core/class/commonobject.class.php';
47require_once DOL_DOCUMENT_ROOT.'/comm/propal/class/propaleligne.class.php';
48require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
49require_once DOL_DOCUMENT_ROOT.'/margin/lib/margins.lib.php';
50require_once DOL_DOCUMENT_ROOT.'/multicurrency/class/multicurrency.class.php';
51require_once DOL_DOCUMENT_ROOT.'/core/class/commonincoterm.class.php';
52require_once DOL_DOCUMENT_ROOT.'/subtotals/class/commonsubtotal.class.php';
53
57class Propal extends CommonObject
58{
59 use CommonIncoterm;
60 use CommonSubtotal;
61
65 public $code = "";
66
70 public $element = 'propal';
71
75 public $table_element = 'propal';
76
80 public $table_element_line = 'propaldet';
81
85 public $fk_element = 'fk_propal';
86
90 public $picto = 'propal';
91
96 public $TRIGGER_PREFIX = 'PROPAL';
97
102 public $restrictiononfksoc = 1;
103
107 protected $table_ref_field = 'ref';
108
113 public $socid;
114
119 public $contactid;
120
127 public $ref_client;
128
133 public $ref_customer;
134
141 public $statut;
142
148 public $status;
149
155 public $datec;
156
162 public $datev;
163
167 public $date_validation;
168
172 public $date_signature;
173
177 public $user_signature;
178
182 public $date;
183
189 public $datep;
190
194 public $delivery_date; // Date expected of shipment (date starting shipment, not the reception that occurs some days after)
195
196
200 public $fin_validite;
201
205 public $user_author_id;
206
211 public $author;
212
218 public $price;
219
225 public $tva;
231 public $total;
232
236 public $cond_reglement_code;
237
241 public $cond_reglement;
242
246 public $cond_reglement_doc;
247
251 public $mode_reglement_code;
252
256 public $mode_reglement;
257
263 public $deposit_percent;
264
269 public $fk_address;
270
275 public $address_type;
276
281 public $address;
282
286 public $availability_id;
287
293 public $fk_availability;
294
298 public $availability_code;
299
303 public $availability;
304
308 public $duree_validite;
309
313 public $demand_reason_id;
314
318 public $demand_reason_code;
319
323 public $demand_reason;
324
328 public $warehouse_id;
329
333 public $lines = array();
334
338 public $line;
339
340 public $labelStatus = array();
341 public $labelStatusShort = array();
342
343
368 // BEGIN MODULEBUILDER PROPERTIES
372 public $fields = array(
373 'rowid' => array('type' => 'integer', 'label' => 'TechnicalID', 'enabled' => 1, 'visible' => -1, 'notnull' => 1, 'position' => 10),
374 'entity' => array('type' => 'integer', 'label' => 'Entity', 'default' => '1', 'enabled' => 1, 'visible' => -2, 'notnull' => 1, 'position' => 15, 'index' => 1),
375 'ref' => array('type' => 'varchar(30)', 'label' => 'Ref', 'enabled' => 1, 'visible' => -1, 'notnull' => 1, 'showoncombobox' => 1, 'position' => 20),
376 'ref_client' => array('type' => 'varchar(255)', 'label' => 'RefCustomer', 'enabled' => 1, 'visible' => -1, 'position' => 22),
377 'ref_ext' => array('type' => 'varchar(255)', 'label' => 'RefExt', 'enabled' => 1, 'visible' => 0, 'position' => 40),
378 'fk_soc' => array('type' => 'integer:Societe:societe/class/societe.class.php', 'label' => 'ThirdParty', 'enabled' => 'isModEnabled("societe")', 'visible' => -1, 'position' => 23),
379 'fk_projet' => array('type' => 'integer:Project:projet/class/project.class.php:1:(fk_statut:=:1)', 'label' => 'Project', 'enabled' => "isModEnabled('project')", 'visible' => -1, 'position' => 24),
380 'tms' => array('type' => 'timestamp', 'label' => 'DateModification', 'enabled' => 1, 'visible' => -1, 'notnull' => 1, 'position' => 25),
381 'datec' => array('type' => 'datetime', 'label' => 'DateCreation', 'enabled' => 1, 'visible' => -1, 'position' => 55),
382 'datep' => array('type' => 'date', 'label' => 'Date', 'enabled' => 1, 'visible' => -1, 'position' => 60),
383 'fin_validite' => array('type' => 'datetime', 'label' => 'DateEnd', 'enabled' => 1, 'visible' => -1, 'position' => 65),
384 'date_valid' => array('type' => 'datetime', 'label' => 'DateValidation', 'enabled' => 1, 'visible' => -1, 'position' => 70),
385 'date_cloture' => array('type' => 'datetime', 'label' => 'DateClosing', 'enabled' => 1, 'visible' => -1, 'position' => 75),
386 'fk_user_author' => array('type' => 'integer:User:user/class/user.class.php', 'label' => 'UserAuthor', 'enabled' => 1, 'visible' => -1, 'position' => 80),
387 'fk_user_modif' => array('type' => 'integer:User:user/class/user.class.php', 'label' => 'UserModif', 'enabled' => 1, 'visible' => -2, 'notnull' => -1, 'position' => 85),
388 'fk_user_valid' => array('type' => 'integer:User:user/class/user.class.php', 'label' => 'UserValidation', 'enabled' => 1, 'visible' => -1, 'position' => 90),
389 'fk_user_cloture' => array('type' => 'integer:User:user/class/user.class.php', 'label' => 'UserClosing', 'enabled' => 1, 'visible' => -1, 'position' => 95),
390 'price' => array('type' => 'double', 'label' => 'Price', 'enabled' => 1, 'visible' => -1, 'position' => 105),
391 'total_ht' => array('type' => 'double(24,8)', 'label' => 'TotalHT', 'enabled' => 1, 'visible' => -1, 'position' => 125, 'isameasure' => 1),
392 'total_tva' => array('type' => 'double(24,8)', 'label' => 'VAT', 'enabled' => 1, 'visible' => -1, 'position' => 130, 'isameasure' => 1),
393 'localtax1' => array('type' => 'double(24,8)', 'label' => 'LocalTax1', 'enabled' => 1, 'visible' => -1, 'position' => 135, 'isameasure' => 1),
394 'localtax2' => array('type' => 'double(24,8)', 'label' => 'LocalTax2', 'enabled' => 1, 'visible' => -1, 'position' => 140, 'isameasure' => 1),
395 'total_ttc' => array('type' => 'double(24,8)', 'label' => 'TotalTTC', 'enabled' => 1, 'visible' => -1, 'position' => 145, 'isameasure' => 1),
396 'fk_account' => array('type' => 'integer', 'label' => 'BankAccount', 'enabled' => 'isModEnabled("bank")', 'visible' => -1, 'position' => 150),
397 'fk_currency' => array('type' => 'varchar(3)', 'label' => 'Currency', 'enabled' => 1, 'visible' => -1, 'position' => 155),
398 'fk_cond_reglement' => array('type' => 'integer', 'label' => 'PaymentTerm', 'enabled' => 1, 'visible' => -1, 'position' => 160),
399 'deposit_percent' => array('type' => 'varchar(63)', 'label' => 'DepositPercent', 'enabled' => 1, 'visible' => -1, 'position' => 161),
400 'fk_mode_reglement' => array('type' => 'integer', 'label' => 'PaymentMode', 'enabled' => 1, 'visible' => -1, 'position' => 165),
401 'note_private' => array('type' => 'html', 'label' => 'NotePrivate', 'enabled' => 1, 'visible' => 0, 'position' => 170),
402 'note_public' => array('type' => 'html', 'label' => 'NotePublic', 'enabled' => 1, 'visible' => 0, 'position' => 175),
403 'model_pdf' => array('type' => 'varchar(255)', 'label' => 'PDFTemplate', 'enabled' => 1, 'visible' => 0, 'position' => 180),
404 'date_livraison' => array('type' => 'date', 'label' => 'DateDeliveryPlanned', 'enabled' => 1, 'visible' => -1, 'position' => 185),
405 'fk_shipping_method' => array('type' => 'integer', 'label' => 'ShippingMethod', 'enabled' => 1, 'visible' => -1, 'position' => 190),
406 'fk_warehouse' => array('type' => 'integer:Entrepot:product/stock/class/entrepot.class.php', 'label' => 'DefaultWarehouse', 'enabled' => 'isModEnabled("stock")', 'visible' => -1, 'position' => 191),
407 'fk_availability' => array('type' => 'integer', 'label' => 'Availability', 'enabled' => 1, 'visible' => -1, 'position' => 195),
408 'fk_delivery_address' => array('type' => 'integer', 'label' => 'DeliveryAddress', 'enabled' => 1, 'visible' => 0, 'position' => 200), // deprecated
409 'fk_input_reason' => array('type' => 'integer', 'label' => 'InputReason', 'enabled' => 1, 'visible' => -1, 'position' => 205),
410 'extraparams' => array('type' => 'varchar(255)', 'label' => 'Extraparams', 'enabled' => 1, 'visible' => -1, 'position' => 215),
411 'fk_incoterms' => array('type' => 'integer', 'label' => 'IncotermCode', 'enabled' => 'isModEnabled("incoterm")', 'visible' => -1, 'position' => 220),
412 'location_incoterms' => array('type' => 'varchar(255)', 'label' => 'IncotermLabel', 'enabled' => 'isModEnabled("incoterm")', 'visible' => -1, 'position' => 225),
413 'fk_multicurrency' => array('type' => 'integer', 'label' => 'MulticurrencyID', 'enabled' => 1, 'visible' => -1, 'position' => 230),
414 'multicurrency_code' => array('type' => 'varchar(255)', 'label' => 'MulticurrencyCurrency', 'enabled' => 'isModEnabled("multicurrency")', 'visible' => -1, 'position' => 235),
415 'multicurrency_tx' => array('type' => 'double(24,8)', 'label' => 'MulticurrencyRate', 'enabled' => 'isModEnabled("multicurrency")', 'visible' => -1, 'position' => 240, 'isameasure' => 1),
416 'multicurrency_total_ht' => array('type' => 'double(24,8)', 'label' => 'MulticurrencyAmountHT', 'enabled' => 'isModEnabled("multicurrency")', 'visible' => -1, 'position' => 245, 'isameasure' => 1),
417 'multicurrency_total_tva' => array('type' => 'double(24,8)', 'label' => 'MulticurrencyAmountVAT', 'enabled' => 'isModEnabled("multicurrency")', 'visible' => -1, 'position' => 250, 'isameasure' => 1),
418 'multicurrency_total_ttc' => array('type' => 'double(24,8)', 'label' => 'MulticurrencyAmountTTC', 'enabled' => 'isModEnabled("multicurrency")', 'visible' => -1, 'position' => 255, 'isameasure' => 1),
419 'last_main_doc' => array('type' => 'varchar(255)', 'label' => 'LastMainDoc', 'enabled' => 1, 'visible' => -1, 'position' => 260),
420 'fk_statut' => array('type' => 'smallint(6)', 'label' => 'Status', 'enabled' => 1, 'visible' => -1, 'notnull' => 1, 'position' => 500),
421 'import_key' => array('type' => 'varchar(14)', 'label' => 'ImportId', 'enabled' => 1, 'visible' => -2, 'position' => 900),
422 );
423 // END MODULEBUILDER PROPERTIES
424
428 const STATUS_CANCELED = -1;
432 const STATUS_DRAFT = 0;
440 const STATUS_SIGNED = 2;
448 const STATUS_BILLED = 4; // Todo rename into STATUS_CLOSE ?
449
450
458 public function __construct($db, $socid = 0, $propalid = 0)
459 {
460 $this->db = $db;
461
462 $this->ismultientitymanaged = 1;
463 $this->socid = $socid;
464 $this->id = $propalid;
465
466 $this->duree_validite = getDolGlobalInt('PROPALE_VALIDITY_DURATION');
467
468 $this->fields['ref_ext']['visible'] = getDolGlobalInt('MAIN_LIST_SHOW_REF_EXT');
469 }
470
471
472 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
484 public function add_product($idproduct, $qty, $remise_percent = 0)
485 {
486 // phpcs:enable
487 global $mysoc;
488
489 if (!$qty) {
490 $qty = 1;
491 }
492
493 dol_syslog(get_class($this)."::add_product $idproduct, $qty, $remise_percent");
494 if ($idproduct > 0) {
495 $prod = new Product($this->db);
496 $prod->fetch($idproduct);
497
498 $productdesc = $prod->description;
499
500 $tva_tx = (string) get_default_tva($mysoc, $this->thirdparty, $prod->id);
501 $tva_npr = get_default_npr($mysoc, $this->thirdparty, $prod->id);
502 if (empty($tva_tx)) {
503 $tva_npr = 0;
504 }
505 $vat_src_code = ''; // May be defined into tva_tx
506
507 $localtax1_tx = get_localtax($tva_tx, 1, $mysoc, $this->thirdparty, $tva_npr);
508 $localtax2_tx = get_localtax($tva_tx, 2, $mysoc, $this->thirdparty, $tva_npr);
509
510 // multiprices
511 if (getDolGlobalString('PRODUIT_MULTIPRICES') && $this->thirdparty->price_level) {
512 $price = $prod->multiprices[$this->thirdparty->price_level];
513 } else {
514 $price = $prod->price;
515 }
516
517 $line = new PropaleLigne($this->db);
518
519 $line->fk_product = $idproduct;
520 $line->desc = $productdesc;
521 $line->qty = $qty;
522 $line->subprice = $price;
523 $line->remise_percent = $remise_percent;
524 $line->vat_src_code = $vat_src_code;
525 $line->tva_tx = $tva_tx;
526 $line->fk_unit = $prod->fk_unit;
527 if ($tva_npr) {
528 $line->info_bits = 1;
529 }
530
531 $this->lines[] = $line;
532 }
533
534 return 1;
535 }
536
537 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
544 public function insert_discount($idremise)
545 {
546 // phpcs:enable
547 global $langs;
548
549 include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
550 include_once DOL_DOCUMENT_ROOT.'/core/class/discount.class.php';
551
552 $this->db->begin();
553
554 $remise = new DiscountAbsolute($this->db);
555 $result = $remise->fetch($idremise);
556
557 if ($result > 0) {
558 if ($remise->fk_facture) { // Protection against multiple submission
559 $this->error = $langs->trans("ErrorDiscountAlreadyUsed");
560 $this->db->rollback();
561 return -5;
562 }
563
564 $line = new PropaleLigne($this->db);
565
566 $line->context = $this->context;
567
568 $line->fk_propal = $this->id;
569 $line->fk_remise_except = $remise->id;
570 $line->desc = $remise->description; // Line Description
571 $line->vat_src_code = $remise->vat_src_code;
572 $line->tva_tx = $remise->tva_tx;
573 $line->localtax1_tx = $remise->localtax1_tx;
574 $line->localtax1_type = $remise->localtax1_type;
575 $line->localtax2_tx = $remise->localtax1_tx;
576 $line->localtax2_type = $remise->localtax1_type;
577 $line->subprice = -(float) $remise->amount_ht;
578 $line->fk_product = 0; // Product Id predefined
579 $line->qty = 1;
580 $line->remise_percent = 0;
581 $line->rang = -1;
582 $line->info_bits = 2;
583
584 $line->total_ht = -(float) $remise->amount_ht;
585 $line->total_tva = -(float) $remise->amount_tva;
586 $line->total_ttc = -(float) $remise->amount_ttc;
587 $line->total_localtax1 = -(float) $remise->total_localtax1;
588 $line->total_localtax2 = -(float) $remise->total_localtax2;
589
590 $result = $line->insert();
591 if ($result > 0) {
592 $result = $this->update_price(1);
593 if ($result > 0) {
594 $this->db->commit();
595 return 1;
596 } else {
597 $this->db->rollback();
598 return -1;
599 }
600 } else {
601 $this->setErrorsFromObject($line);
602 $this->db->rollback();
603 return -2;
604 }
605 } else {
606 $this->db->rollback();
607 return -2;
608 }
609 }
610
648 public function addline(
649 $desc,
650 $pu_ht,
651 $qty,
652 $txtva,
653 $txlocaltax1 = 0.0,
654 $txlocaltax2 = 0.0,
655 $fk_product = 0,
656 $remise_percent = 0.0,
657 $price_base_type = 'HT',
658 $pu_ttc = 0.0,
659 $info_bits = 0,
660 $type = 0,
661 $rang = -1,
662 $special_code = 0,
663 $fk_parent_line = 0,
664 $fk_fournprice = 0,
665 $pa_ht = 0,
666 $label = '',
667 $date_start = '',
668 $date_end = '',
669 $array_options = array(),
670 $fk_unit = null,
671 $origin = '',
672 $origin_id = 0,
673 $pu_ht_devise = 0,
674 $fk_remise_except = 0,
675 $noupdateafterinsertline = 0
676 ) {
677 global $mysoc, $langs;
678
679 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);
680
681 if ($this->status == self::STATUS_DRAFT) {
682 include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
683
684 // Clean parameters
685 if (empty($remise_percent)) {
686 $remise_percent = 0;
687 }
688 if (empty($qty)) {
689 $qty = 0;
690 }
691 if (empty($info_bits)) {
692 $info_bits = 0;
693 }
694 if (empty($rang)) {
695 $rang = 0;
696 }
697 if (empty($fk_parent_line) || $fk_parent_line < 0) {
698 $fk_parent_line = 0;
699 }
700
701 $remise_percent = price2num($remise_percent);
702 $qty = (float) price2num($qty, 'MS');
703 $pu_ht = price2num($pu_ht);
704 $pu_ht_devise = price2num($pu_ht_devise);
705 $pu_ttc = price2num($pu_ttc);
706 if (!preg_match('/\‍((.*)\‍)/', (string) $txtva)) {
707 $txtva = price2num($txtva); // $txtva can have format '5,1' or '5.1' or '5.1(XXX)', we must clean only if '5,1'
708 }
709 $txlocaltax1 = price2num($txlocaltax1);
710 $txlocaltax2 = price2num($txlocaltax2);
711 $pa_ht = price2num($pa_ht); // do not convert to float here, it breaks the functioning of $pa_ht is empty string
712 if ($price_base_type == 'HT') {
713 $pu = $pu_ht;
714 } else {
715 $pu = $pu_ttc;
716 }
717
718 // Check parameters
719 if ($type < 0) {
720 return -1;
721 }
722
723 if ($date_start && $date_end && $date_start > $date_end) {
724 $langs->load("errors");
725 $this->error = $langs->trans('ErrorStartDateGreaterEnd');
726 return -1;
727 }
728
729 $this->db->begin();
730
731 $product_type = $type;
732 if (!empty($fk_product) && $fk_product > 0) {
733 $product = new Product($this->db);
734 $result = $product->fetch($fk_product);
735 $product_type = $product->type;
736
737 if ($product->isStockManaged() && $product->stock_reel < $qty && getDolGlobalString('STOCK_MUST_BE_ENOUGH_FOR_PROPOSAL')) {
738 $langs->load("errors");
739 $this->error = $langs->trans('ErrorStockIsNotEnoughToAddProductOnProposal', $product->ref);
740 $this->db->rollback();
741 return -3;
742 }
743 }
744
745 // Calculation of the gross total (TTC) and VAT for the line from qty, pu, remise_percent and txtva
746 // VERY IMPORTANT: It's at the time of line insertion that we must store the net, VAT, and gross amounts,
747 // and this is done at the line level, which has its own VAT rate
748
749 $localtaxes_type = getLocalTaxesFromRate($txtva, 0, $this->thirdparty, $mysoc);
750
751 if (getDolGlobalString('PRODUCT_USE_CUSTOMER_PACKAGING')) {
752 $tmpproduct = new Product($this->db);
753 $result = $tmpproduct->fetch($fk_product);
754 $newqty = $this->roundQtyToPackaging($qty, $tmpproduct->packaging);
755 if ($newqty != $qty) {
756 $qty = $newqty;
757 setEventMessages($langs->trans('QtyRecalculatedWithPackaging'), null, 'warnings');
758 }
759 }
760
761 // Clean vat code
762 $reg = array();
763 $vat_src_code = '';
764 if (preg_match('/\‍((.*)\‍)/', $txtva, $reg)) {
765 $vat_src_code = $reg[1];
766 $txtva = preg_replace('/\s*\‍(.*\‍)/', '', $txtva); // Remove code into vatrate.
767 }
768
769 $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);
770
771 $total_ht = $tabprice[0];
772 $total_tva = $tabprice[1];
773 $total_ttc = $tabprice[2];
774 $total_localtax1 = $tabprice[9];
775 $total_localtax2 = $tabprice[10];
776 $pu_ht = $tabprice[3];
777 $pu_tva = $tabprice[4];
778 $pu_ttc = $tabprice[5];
779
780 // MultiCurrency
781 $multicurrency_total_ht = $tabprice[16];
782 $multicurrency_total_tva = $tabprice[17];
783 $multicurrency_total_ttc = $tabprice[18];
784 $pu_ht_devise = $tabprice[19];
785
786 // Rang to use
787 $ranktouse = $rang;
788 if (empty($ranktouse) || $ranktouse == -1) {
789 $rangmax = $this->line_max($fk_parent_line);
790 $ranktouse = $rangmax + 1;
791 }
792
793 // Insert line
794 $this->line = new PropaleLigne($this->db);
795
796 $this->line->context = $this->context;
797
798 $this->line->fk_propal = $this->id;
799 $this->line->label = $label;
800 $this->line->desc = $desc;
801 $this->line->qty = (float) $qty;
802
803 $this->line->vat_src_code = $vat_src_code;
804 $this->line->tva_tx = $txtva;
805 $this->line->localtax1_tx = ($total_localtax1 ? $localtaxes_type[1] : 0);
806 $this->line->localtax2_tx = ($total_localtax2 ? $localtaxes_type[3] : 0);
807 $this->line->localtax1_type = empty($localtaxes_type[0]) ? '' : $localtaxes_type[0];
808 $this->line->localtax2_type = empty($localtaxes_type[2]) ? '' : $localtaxes_type[2];
809 $this->line->fk_product = $fk_product;
810 $this->line->product_type = $type;
811 $this->line->fk_remise_except = $fk_remise_except;
812 $this->line->remise_percent = $remise_percent;
813 $this->line->subprice = (float) $pu_ht;
814 // Persist the original entry mode of the line so updateline() can preserve it later.
815 $this->line->subprice_ttc = ($price_base_type === 'TTC') ? (float) $pu_ttc : 0;
816 $this->line->rang = $ranktouse;
817 $this->line->info_bits = $info_bits;
818 $this->line->total_ht = (float) $total_ht;
819 $this->line->total_tva = (float) $total_tva;
820 $this->line->total_localtax1 = (float) $total_localtax1;
821 $this->line->total_localtax2 = (float) $total_localtax2;
822 $this->line->total_ttc = (float) $total_ttc;
823 $this->line->special_code = $special_code;
824 $this->line->fk_parent_line = $fk_parent_line;
825 $this->line->fk_unit = $fk_unit;
826
827 $this->line->date_start = $date_start;
828 $this->line->date_end = $date_end;
829
830 $this->line->fk_fournprice = $fk_fournprice;
831 $this->line->pa_ht = $pa_ht;
832
833 $this->line->origin_id = $origin_id;
834 $this->line->origin = $origin;
835
836 // Multicurrency
837 $this->line->fk_multicurrency = $this->fk_multicurrency;
838 $this->line->multicurrency_code = $this->multicurrency_code;
839 $this->line->multicurrency_subprice = (float) $pu_ht_devise;
840 $this->line->multicurrency_total_ht = (float) $multicurrency_total_ht;
841 $this->line->multicurrency_total_tva = (float) $multicurrency_total_tva;
842 $this->line->multicurrency_total_ttc = (float) $multicurrency_total_ttc;
843
844 // Set the line as optional
845 if (empty($qty) && empty($special_code)) {
846 $this->line->special_code = 3;
847 }
848
849 if (is_array($array_options) && count($array_options) > 0) {
850 $this->line->array_options = $array_options;
851 }
852
853 $result = $this->line->insert();
854 if ($result > 0) {
855 if (!isset($this->context['createfromclone'])) {
856 if (!empty($fk_parent_line)) {
857 // Always reorder if child line
858 $this->line_order(true, 'DESC');
859 } elseif ($ranktouse > 0 && $ranktouse <= count($this->lines)) {
860 // Update all rank of all other lines starting from the same $ranktouse
861 $linecount = count($this->lines);
862 for ($ii = $ranktouse; $ii <= $linecount; $ii++) {
863 $this->updateRangOfLine($this->lines[$ii - 1]->id, $ii + 1);
864 }
865 }
866
867 $this->lines[] = $this->line;
868 } else {
869 // Loop on all lines of parent object
870 foreach ($this->lines as $tmpline) {
871 if ($tmpline->id == $origin_id && $tmpline->element == $origin) {
872 $this->line->extraparams = $tmpline->extraparams;
873 $this->line->setExtraParameters();
874 }
875 }
876 }
877
878 // Update denormalized fields at the order level
879 if (empty($noupdateafterinsertline)) {
880 $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.
881 }
882
883 if ($result > 0) {
884 $this->db->commit();
885 return $this->line->id;
886 } else {
887 $this->error = $this->db->error();
888 $this->db->rollback();
889 return -1;
890 }
891 } else {
892 $this->setErrorsFromObject($this->line);
893 $this->db->rollback();
894 return -2;
895 }
896 } else {
897 dol_syslog(get_class($this)."::addline status of proposal must be Draft to allow use of ->addline()", LOG_ERR);
898 return -3;
899 }
900 }
901
902
932 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)
933 {
934 global $mysoc, $langs;
935
936 if (!$this->isLineOfObject($rowid)) {
937 $this->error = 'ErrorLineIDDoesNotMatchWithObjectID';
938 return -1;
939 }
940
941 dol_syslog(get_class($this)."::updateLine rowid=$rowid, pu=$pu, qty=$qty, remise_percent=$remise_percent,
942 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");
943 include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
944
945 // Clean parameters
946 $remise_percent = price2num($remise_percent);
947 $qty = (float) price2num($qty);
948 $pu = price2num($pu);
949 $pu_ht_devise = price2num($pu_ht_devise);
950 if (!preg_match('/\‍((.*)\‍)/', (string) $txtva)) {
951 $txtva = price2num($txtva); // $txtva can have format '5.0(XXX)' or '5'
952 }
953 $txlocaltax1 = price2num($txlocaltax1);
954 $txlocaltax2 = price2num($txlocaltax2);
955 $pa_ht = price2num($pa_ht); // do not convert to float here, it breaks the functioning of $pa_ht_isemptystring
956 if (empty($qty) && empty($special_code)) {
957 $special_code = 3; // Set option tag
958 }
959 if (!empty($qty) && $special_code == 3) {
960 $special_code = 0; // Remove option tag
961 }
962 if (empty($type)) {
963 $type = 0;
964 }
965
966 if ($date_start && $date_end && $date_start > $date_end) {
967 $langs->load("errors");
968 $this->error = $langs->trans('ErrorStartDateGreaterEnd');
969 return -1;
970 }
971
972 if ($this->status == self::STATUS_DRAFT) {
973 $this->db->begin();
974
975 // Calculation of the gross total (TTC) and VAT for the line from qty, pu, remise_percent and txtva
976 // VERY IMPORTANT: It's at the time of line insertion that we must store the net, VAT, and gross amounts,
977 // and this is done at the line level, which has its own VAT rate
978
979 $localtaxes_type = getLocalTaxesFromRate($txtva, 0, $this->thirdparty, $mysoc);
980
981 // Clean vat code
982 $reg = array();
983 $vat_src_code = '';
984 if (preg_match('/\‍((.*)\‍)/', $txtva, $reg)) {
985 $vat_src_code = $reg[1];
986 $txtva = preg_replace('/\s*\‍(.*\‍)/', '', $txtva); // Remove code into vatrate.
987 }
988
989 // TODO Implement if (getDolGlobalInt('MAIN_UNIT_PRICE_WITH_TAX_IS_FOR_ALL_TAXES')) ?
990
991 // Round the quantity to the packaging before computing the amounts of the line (and checking the stock),
992 // else the line is saved with the rounded quantity but with the amounts of the quantity before rounding
993 if (getDolGlobalString('PRODUCT_USE_CUSTOMER_PACKAGING')) {
994 $tmpline = new PropaleLigne($this->db);
995 if ($tmpline->fetch($rowid) > 0) {
996 $newqty = $this->roundQtyToPackaging($qty, $tmpline->packaging);
997 if ($newqty != $qty) {
998 $qty = $newqty;
999 setEventMessages($langs->trans('QtyRecalculatedWithPackaging'), null, 'warnings');
1000 }
1001 }
1002 }
1003
1004 $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);
1005 $total_ht = $tabprice[0];
1006 $total_tva = $tabprice[1];
1007 $total_ttc = $tabprice[2];
1008 $total_localtax1 = $tabprice[9];
1009 $total_localtax2 = $tabprice[10];
1010 $pu_ht = $tabprice[3];
1011 $pu_tva = $tabprice[4];
1012 $pu_ttc = $tabprice[5];
1013
1014 // MultiCurrency
1015 $multicurrency_total_ht = $tabprice[16];
1016 $multicurrency_total_tva = $tabprice[17];
1017 $multicurrency_total_ttc = $tabprice[18];
1018 $pu_ht_devise = $tabprice[19];
1019
1020 // Fetch current line from the database and then clone the object and set it in $oldline property
1021 $line = new PropaleLigne($this->db);
1022 $line->fetch($rowid);
1023
1024 $staticline = clone $line;
1025
1026 $line->oldline = $staticline;
1027 $this->line = $line;
1028 $this->line->context = $this->context;
1029 $this->line->rang = $rang;
1030
1031 // Reorder if fk_parent_line change
1032 if (!empty($fk_parent_line) && !empty($staticline->fk_parent_line) && $fk_parent_line != $staticline->fk_parent_line) {
1033 $rangmax = $this->line_max($fk_parent_line);
1034 $this->line->rang = $rangmax + 1;
1035 }
1036
1037
1038 $this->line->id = $rowid;
1039 $this->line->label = $label;
1040 $this->line->desc = $desc;
1041 $this->line->qty = $qty;
1042 $this->line->product_type = $type;
1043 $this->line->vat_src_code = $vat_src_code;
1044 $this->line->tva_tx = $txtva;
1045 $this->line->localtax1_tx = $txlocaltax1;
1046 $this->line->localtax2_tx = $txlocaltax2;
1047 $this->line->localtax1_type = empty($localtaxes_type[0]) ? '' : $localtaxes_type[0];
1048 $this->line->localtax2_type = empty($localtaxes_type[2]) ? '' : $localtaxes_type[2];
1049 $this->line->remise_percent = $remise_percent;
1050 $this->line->subprice = (float) $pu_ht;
1051 // Persist the original entry mode of the line so a no-op edit can preserve it later.
1052 $this->line->subprice_ttc = ($price_base_type === 'TTC') ? (float) $pu_ttc : 0;
1053 $this->line->info_bits = $info_bits;
1054
1055 $this->line->total_ht = (float) $total_ht;
1056 $this->line->total_tva = (float) $total_tva;
1057 $this->line->total_localtax1 = (float) $total_localtax1;
1058 $this->line->total_localtax2 = (float) $total_localtax2;
1059 $this->line->total_ttc = (float) $total_ttc;
1060 $this->line->special_code = $special_code;
1061 $this->line->fk_parent_line = $fk_parent_line;
1062 $this->line->skip_update_total = $skip_update_total;
1063 $this->line->fk_unit = $fk_unit;
1064
1065 $this->line->fk_fournprice = $fk_fournprice;
1066 $this->line->pa_ht = $pa_ht;
1067
1068 $this->line->date_start = $date_start;
1069 $this->line->date_end = $date_end;
1070
1071 if (is_array($array_options) && count($array_options) > 0) {
1072 // We replace values in this->line->array_options only for entries defined into $array_options
1073 foreach ($array_options as $key => $value) {
1074 $this->line->array_options[$key] = $array_options[$key];
1075 }
1076 }
1077
1078 // Multicurrency
1079 $this->line->multicurrency_subprice = (float) $pu_ht_devise;
1080 $this->line->multicurrency_total_ht = (float) $multicurrency_total_ht;
1081 $this->line->multicurrency_total_tva = (float) $multicurrency_total_tva;
1082 $this->line->multicurrency_total_ttc = (float) $multicurrency_total_ttc;
1083
1084 $result = $this->line->update($notrigger);
1085 if ($result > 0) {
1086 // Reorder if child line
1087 if (!empty($fk_parent_line)) {
1088 $this->line_order(true, 'DESC');
1089 }
1090
1091 $this->update_price(1, 'auto');
1092
1093 // $this is Propal
1094 // $this->fk_propal = $this->id;
1095 // $this->rowid = $rowid;
1096
1097 $this->db->commit();
1098 return $result;
1099 } else {
1100 $this->setErrorsFromObject($this->line);
1101 $this->db->rollback();
1102 return -1;
1103 }
1104 } else {
1105 dol_syslog(get_class($this)."::updateline Erreur -2 Propal en mode incompatible pour cette action");
1106 return -2;
1107 }
1108 }
1109
1110
1118 public function deleteLine($lineid, $id = 0)
1119 {
1120 global $user;
1121
1122 if ($this->status == self::STATUS_DRAFT) {
1123 $this->db->begin();
1124
1125 $line = new PropaleLigne($this->db);
1126
1127 $line->context = $this->context;
1128
1129 // Load data
1130 $line->fetch($lineid);
1131
1132 if ($id <= 0) {
1133 $id = $this->id;
1134 }
1135 if ($id > 0 && (int) $line->fk_propal !== (int) $id) {
1136 $this->db->rollback();
1137 $this->error = 'ErrorLineIDDoesNotMatchWithObjectID';
1138 return -1;
1139 }
1140
1141 // Memorize previous line for triggers
1142 $staticline = clone $line;
1143 $line->oldline = $staticline;
1144
1145 if ($line->delete($user) > 0) {
1146 $this->update_price(1);
1147
1148 $this->db->commit();
1149 return 1;
1150 } else {
1151 $this->setErrorsFromObject($line);
1152 $this->db->rollback();
1153 return -1;
1154 }
1155 } else {
1156 $this->error = 'ErrorDeleteLineNotAllowedByObjectStatus';
1157 return -2;
1158 }
1159 }
1160
1161
1170 public function create($user, $notrigger = 0)
1171 {
1172 global $mysoc;
1173
1174 $error = 0;
1175
1176 $now = dol_now();
1177
1178 // Clean parameters
1179 if (empty($this->date)) {
1180 $this->date = $this->datep;
1181 }
1182 $this->fin_validite = $this->date + ($this->duree_validite * 24 * 3600);
1183 if (empty($this->availability_id)) {
1184 $this->availability_id = 0;
1185 }
1186 if (empty($this->demand_reason_id)) {
1187 $this->demand_reason_id = 0;
1188 }
1189
1190 // Multicurrency (test on $this->multicurrency_tx because we should take the default rate only if not using origin rate)
1191 if (!empty($this->multicurrency_code) && empty($this->multicurrency_tx)) {
1192 list($this->fk_multicurrency, $this->multicurrency_tx) = MultiCurrency::getIdAndTxFromCode($this->db, $this->multicurrency_code, $this->date);
1193 } else {
1194 $this->fk_multicurrency = MultiCurrency::getIdFromCode($this->db, $this->multicurrency_code);
1195 }
1196 if (empty($this->fk_multicurrency)) {
1197 $this->multicurrency_code = getDolCurrency();
1198 $this->fk_multicurrency = 0;
1199 $this->multicurrency_tx = 1;
1200 }
1201 // setEntity will set entity with the right value if empty or change it for the right value if multicompany module is active
1202 $this->entity = setEntity($this);
1203
1204 // Set tmp vars
1205 $delivery_date = $this->delivery_date;
1206
1207 dol_syslog(get_class($this)."::create");
1208
1209 // Check parameters
1210 $result = $this->fetch_thirdparty();
1211 if ($result < 0) {
1212 $this->error = "Failed to fetch company";
1213 dol_syslog(get_class($this)."::create ".$this->error, LOG_ERR);
1214 return -3;
1215 }
1216
1217 // Check parameters
1218 if (!empty($this->ref)) { // We check that ref is not already used
1219 $result = self::isExistingObject($this->element, 0, $this->ref); // Check ref is not yet used
1220 if ($result > 0) {
1221 $this->error = 'ErrorRefAlreadyExists';
1222 dol_syslog(get_class($this)."::create ".$this->error, LOG_WARNING);
1223 $this->db->rollback();
1224 return -1;
1225 }
1226 }
1227
1228 if (empty($this->date)) {
1229 $this->error = "Date of proposal is required";
1230 dol_syslog(get_class($this)."::create ".$this->error, LOG_ERR);
1231 return -4;
1232 }
1233
1234
1235 $this->db->begin();
1236
1237 // Insert into database
1238 $sql = "INSERT INTO ".MAIN_DB_PREFIX.$this->table_element." (";
1239 $sql .= "fk_soc";
1240 $sql .= ", price";
1241 $sql .= ", total_tva";
1242 $sql .= ", total_ttc";
1243 $sql .= ", datep";
1244 $sql .= ", datec";
1245 $sql .= ", ref";
1246 $sql .= ", fk_user_author";
1247 $sql .= ", note_private";
1248 $sql .= ", note_public";
1249 $sql .= ", model_pdf";
1250 $sql .= ", fin_validite";
1251 $sql .= ", fk_cond_reglement";
1252 $sql .= ", deposit_percent";
1253 $sql .= ", fk_mode_reglement";
1254 $sql .= ", fk_account";
1255 $sql .= ", ref_client";
1256 $sql .= ", ref_ext";
1257 $sql .= ", date_livraison";
1258 $sql .= ", fk_shipping_method";
1259 $sql .= ", fk_warehouse";
1260 $sql .= ", fk_availability";
1261 $sql .= ", fk_input_reason";
1262 $sql .= ", fk_projet";
1263 $sql .= ", fk_incoterms";
1264 $sql .= ", location_incoterms";
1265 $sql .= ", entity";
1266 $sql .= ", fk_multicurrency";
1267 $sql .= ", multicurrency_code";
1268 $sql .= ", multicurrency_tx";
1269 $sql .= ") ";
1270 $sql .= " VALUES (";
1271 $sql .= ((int) $this->socid);
1272 $sql .= ", 0";
1273 $sql .= ", 0";
1274 $sql .= ", 0";
1275 $sql .= ", '".$this->db->idate($this->date)."'";
1276 $sql .= ", '".$this->db->idate($now)."'";
1277 $sql .= ", '(PROV)'";
1278 $sql .= ", ".($user->id > 0 ? ((int) $user->id) : "NULL");
1279 $sql .= ", '".$this->db->escape($this->note_private)."'";
1280 $sql .= ", '".$this->db->escape($this->note_public)."'";
1281 $sql .= ", '".$this->db->escape($this->model_pdf)."'";
1282 $sql .= ", ".($this->fin_validite != '' ? "'".$this->db->idate($this->fin_validite)."'" : "NULL");
1283 $sql .= ", ".($this->cond_reglement_id > 0 ? ((int) $this->cond_reglement_id) : 'NULL');
1284 $sql .= ", ".(!empty($this->deposit_percent) ? "'".$this->db->escape($this->deposit_percent)."'" : 'NULL');
1285 $sql .= ", ".($this->mode_reglement_id > 0 ? ((int) $this->mode_reglement_id) : 'NULL');
1286 $sql .= ", ".($this->fk_account > 0 ? ((int) $this->fk_account) : 'NULL');
1287 $sql .= ", '".$this->db->escape((string) $this->ref_client)."'";
1288 $sql .= ", '".$this->db->escape((string) $this->ref_ext)."'";
1289 $sql .= ", ".(!isDolTms($delivery_date) ? "NULL" : "'".$this->db->idate($delivery_date)."'");
1290 $sql .= ", ".($this->shipping_method_id > 0 ? ((int) $this->shipping_method_id) : 'NULL');
1291 $sql .= ", ".($this->warehouse_id > 0 ? ((int) $this->warehouse_id) : 'NULL');
1292 $sql .= ", ".((int) $this->availability_id);
1293 $sql .= ", ".((int) $this->demand_reason_id);
1294 $sql .= ", ".($this->fk_project ? ((int) $this->fk_project) : "null");
1295 $sql .= ", ".(int) $this->fk_incoterms;
1296 $sql .= ", '".$this->db->escape($this->location_incoterms)."'";
1297 $sql .= ", ".(int) $this->entity;
1298 $sql .= ", ".(int) $this->fk_multicurrency;
1299 $sql .= ", '".$this->db->escape($this->multicurrency_code)."'";
1300 $sql .= ", ".(float) $this->multicurrency_tx;
1301 $sql .= ")";
1302
1303 dol_syslog(get_class($this)."::create", LOG_DEBUG);
1304 $resql = $this->db->query($sql);
1305 if ($resql) {
1306 $this->id = $this->db->last_insert_id(MAIN_DB_PREFIX.$this->table_element);
1307
1308 if ($this->id) {
1309 $this->ref = '(PROV'.$this->id.')';
1310 $sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element." SET ref='".$this->db->escape($this->ref)."' WHERE rowid=".((int) $this->id);
1311
1312 dol_syslog(get_class($this)."::create", LOG_DEBUG);
1313 $resql = $this->db->query($sql);
1314 if (!$resql) {
1315 $error++;
1316 }
1317
1318 if (!empty($this->linkedObjectsIds) && empty($this->linked_objects)) { // To use new linkedObjectsIds instead of old linked_objects
1319 $this->linked_objects = $this->linkedObjectsIds; // TODO Replace linked_objects with linkedObjectsIds
1320 }
1321
1322 // Add object linked
1323 if (!$error && !empty($this->linked_objects) && is_array($this->linked_objects)) {
1324 foreach ($this->linked_objects as $origin => $tmp_origin_id) {
1325 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, ...))
1326 foreach ($tmp_origin_id as $origin_id) {
1327 $ret = $this->add_object_linked($origin, $origin_id);
1328 if (!$ret) {
1329 $this->error = $this->db->lasterror();
1330 $error++;
1331 }
1332 }
1333 } else { // Old behaviour, if linked_object has only one link per type, so is something like array('contract'=>id1))
1334 $origin_id = $tmp_origin_id;
1335 $ret = $this->add_object_linked($origin, $origin_id);
1336 if (!$ret) {
1337 $this->error = $this->db->lasterror();
1338 $error++;
1339 }
1340 }
1341 }
1342 }
1343
1344 /*
1345 * Insert products detail in database
1346 */
1347 if (!$error) {
1348 $fk_parent_line = 0;
1349 $num = count($this->lines);
1350
1351 for ($i = 0; $i < $num; $i++) {
1352 if (!is_object($this->lines[$i])) { // If this->lines is not array of objects, coming from REST API
1353 // Build a real line object: the loop below calls methods on it
1354 // (getPriceBaseType), which a cast to stdClass cannot answer.
1355 $lineobj = new PropaleLigne($this->db);
1356 foreach ($this->lines[$i] as $key => $val) {
1357 $lineobj->$key = $val;
1358 }
1359 $line = $lineobj;
1360 $this->lines[$i] = $line;
1361 } else {
1362 $line = $this->lines[$i];
1363 }
1364 // Reset fk_parent_line for line that are not child lines or special product
1365 if (($line->product_type != 9 && empty($line->fk_parent_line)) || $line->product_type == 9) {
1366 $fk_parent_line = 0;
1367 }
1368 // Complete vat rate with code
1369 $vatrate = $line->tva_tx;
1370 if ($line->vat_src_code && !preg_match('/\‍(.*\‍)/', $vatrate)) {
1371 $vatrate .= ' ('.$line->vat_src_code.')';
1372 }
1373
1374 if (getDolGlobalString('MAIN_CREATEFROM_KEEP_LINE_ORIGIN_INFORMATION')) {
1375 $originid = $line->origin_id;
1376 $origintype = empty($line->origin_type) ? $line->origin : $line->origin_type;
1377 } else { // old but bugged version (we store id of line and type of parent object)
1378 $originid = $line->id;
1379 $origintype = $this->element;
1380 }
1381
1382 // Preserve the original entry mode of the line so the total is computed from the typed value (no rounding drift).
1383 $line_price_base_type = $line->getPriceBaseType();
1384 $result = $this->addline(
1385 $line->desc,
1386 (float) $line->subprice,
1387 $line->qty,
1388 $vatrate,
1389 $line->localtax1_tx,
1390 $line->localtax2_tx,
1391 $line->fk_product,
1392 $line->remise_percent,
1393 $line_price_base_type,
1394 (float) $line->subprice_ttc,
1395 $line->info_bits,
1396 $line->product_type,
1397 $line->rang,
1398 $line->special_code,
1399 $fk_parent_line,
1400 $line->fk_fournprice,
1401 $line->pa_ht,
1402 $line->label,
1403 $line->date_start,
1404 $line->date_end,
1405 $line->array_options,
1406 $line->fk_unit,
1407 (string) $origintype,
1408 $originid,
1409 0,
1410 0,
1411 1
1412 );
1413
1414 if ($result < 0) {
1415 $error++;
1416 $this->error = $this->db->error;
1417 dol_print_error($this->db);
1418 break;
1419 }
1420
1421 // Set the id on created row
1422 $line->id = $result;
1423
1424 // Defined the new fk_parent_line
1425 if ($result > 0 && $line->product_type == 9) {
1426 $fk_parent_line = $result;
1427 }
1428 }
1429 }
1430
1431 if (!$error) {
1432 // Update denormalized data
1433 $resql = $this->update_price(1, 'auto', 0, $mysoc);
1434 if ($resql) {
1435 // Actions on extra fields
1436 $result = $this->insertExtraFields();
1437 if ($result < 0) {
1438 $error++;
1439 }
1440
1441 if (!$error && !$notrigger) {
1442 // Call trigger
1443 $result = $this->call_trigger('PROPAL_CREATE', $user);
1444 if ($result < 0) {
1445 $error++;
1446 }
1447 // End call triggers
1448 }
1449 } else {
1450 $this->error = $this->db->lasterror();
1451 $error++;
1452 }
1453 }
1454 } else {
1455 $this->error = $this->db->lasterror();
1456 $error++;
1457 }
1458
1459 if (!$error) {
1460 $this->db->commit();
1461 dol_syslog(get_class($this)."::create done id=".$this->id);
1462 return $this->id;
1463 } else {
1464 $this->db->rollback();
1465 return -2;
1466 }
1467 } else {
1468 $this->error = $this->db->lasterror();
1469 $this->db->rollback();
1470 return -1;
1471 }
1472 }
1473
1484 public function createFromClone(User $user, $socid = 0, $forceentity = null, $update_prices = false, $update_desc = false)
1485 {
1486 global $conf, $hookmanager, $mysoc;
1487
1488 dol_include_once('/projet/class/project.class.php');
1489
1490 $error = 0;
1491 $now = dol_now();
1492
1493 dol_syslog(__METHOD__, LOG_DEBUG);
1494
1495 $object = new self($this->db);
1496
1497 $this->db->begin();
1498
1499 // Load source object
1500 $object->fetch($this->id);
1501
1502 $objsoc = new Societe($this->db);
1503
1504 // Change socid if needed
1505 if (!empty($socid) && $socid != $object->socid) {
1506 if ($objsoc->fetch($socid) > 0) {
1507 $object->socid = $objsoc->id;
1508 $object->cond_reglement_id = (!empty($objsoc->cond_reglement_id) ? $objsoc->cond_reglement_id : 0);
1509 $object->deposit_percent = (!empty($objsoc->deposit_percent) ? $objsoc->deposit_percent : null);
1510 $object->mode_reglement_id = (!empty($objsoc->mode_reglement_id) ? $objsoc->mode_reglement_id : 0);
1511 $object->fk_delivery_address = 0;
1512
1513 /*if (isModEnabled('project'))
1514 {
1515 $project = new Project($db);
1516 if ($this->fk_project > 0 && $project->fetch($this->fk_project)) {
1517 if ($project->socid <= 0) $clonedObj->fk_project = $this->fk_project;
1518 else $clonedObj->fk_project = '';
1519 } else {
1520 $clonedObj->fk_project = '';
1521 }
1522 }*/
1523 $object->fk_project = 0; // A cloned proposal is set by default to no project.
1524 }
1525
1526 // reset ref_client
1527 if (!getDolGlobalString('MAIN_KEEP_REF_CUSTOMER_ON_CLONING')) {
1528 $object->ref_client = '';
1529 $object->ref_customer = '';
1530 }
1531
1532 // TODO Change product price if multi-prices
1533 } else {
1534 $objsoc->fetch($object->socid);
1535 }
1536
1537 // update prices
1538 if ($update_prices === true || $update_desc === true) {
1539 if ($objsoc->id > 0 && !empty($object->lines)) {
1540 if ($update_prices === true && getDolGlobalString('PRODUIT_CUSTOMER_PRICES')) {
1541 // If price per customer
1542 require_once DOL_DOCUMENT_ROOT . '/product/class/productcustomerprice.class.php';
1543 }
1544
1545 foreach ($object->lines as $line) {
1546 $line->id = 0;
1547
1548 if ($line->fk_product > 0) {
1549 $prod = new Product($this->db);
1550 $res = $prod->fetch($line->fk_product);
1551 if ($res > 0) {
1552 if ($update_prices === true) {
1553 $pu_ht = $prod->price;
1554 $tva_tx = (string) get_default_tva($mysoc, $objsoc, $prod->id);
1555 $remise_percent = $objsoc->remise_percent;
1556
1557 if (getDolGlobalString('PRODUIT_MULTIPRICES') && $objsoc->price_level > 0) {
1558 $pu_ht = $prod->multiprices[$objsoc->price_level];
1559 if (getDolGlobalString('PRODUIT_MULTIPRICES_USE_VAT_PER_LEVEL')) { // using this option is a bug. kept for backward compatibility
1560 if (isset($prod->multiprices_tva_tx[$objsoc->price_level])) {
1561 $tva_tx = (string) $prod->multiprices_tva_tx[$objsoc->price_level];
1562 }
1563 }
1564 } elseif (getDolGlobalString('PRODUIT_CUSTOMER_PRICES')) {
1565 $prodcustprice = new ProductCustomerPrice($this->db);
1566 $filter = array('t.fk_product' => (string) $prod->id, 't.fk_soc' => (string) $objsoc->id);
1567 $result = $prodcustprice->fetchAll('', '', 0, 0, $filter);
1568 if ($result) {
1569 // If there is some prices specific to the customer
1570 if (count($prodcustprice->lines) > 0) {
1571 $date_now = (int) floor(dol_now() / 86400) * 86400; // date without hours
1572 foreach ($prodcustprice->lines as $k => $custprice_line) {
1573 if ($custprice_line->date_begin <= $date_now && (empty($custprice_line->date_end) || $date_now <= $custprice_line->date_end)) {
1574 $pu_ht = price($custprice_line->price);
1575 $tva_tx = ($custprice_line->default_vat_code ? $custprice_line->tva_tx . ' (' . $custprice_line->default_vat_code . ' )' : $custprice_line->tva_tx);
1576 if ($custprice_line->default_vat_code && !preg_match('/\‍(.*\‍)/', $tva_tx)) {
1577 $tva_tx .= ' (' . $custprice_line->default_vat_code . ')';
1578 }
1579 $remise_percent = $custprice_line->discount_percent;
1580 break;
1581 }
1582 }
1583 }
1584 }
1585 }
1586
1587 $line->subprice = $pu_ht;
1588 $line->tva_tx = $tva_tx;
1589 $line->remise_percent = $remise_percent;
1590
1591 // Also update the buy price (margin) with the same fallback logic used when a line is added or updated
1592 // (cost price, or PMP, or best supplier price, depending on MARGIN_TYPE). Keep line->pa_ht unchanged if nothing better is found.
1593 $buyPrice = $this->defineBuyPrice($pu_ht, $remise_percent, $prod->id);
1594 if ($buyPrice > 0) {
1595 $line->pa_ht = $buyPrice;
1596 }
1597 }
1598 if ($update_desc === true) {
1599 $line->desc = $prod->description;
1600 }
1601 }
1602 }
1603 }
1604 }
1605 }
1606
1607 $object->id = 0;
1608 $object->ref = '';
1609 $object->entity = (!empty($forceentity) ? $forceentity : $object->entity);
1610 $object->statut = self::STATUS_DRAFT;
1611 $object->status = self::STATUS_DRAFT;
1612
1613 // Clear fields
1614 $object->user_creation_id = $user->id;
1615 $object->user_validation_id = 0;
1616 $object->date = $now;
1617 $object->datep = $now; // deprecated
1618 $object->fin_validite = $object->date + (int) ($object->duree_validite * 24 * 3600);
1619 if (!getDolGlobalString('MAIN_KEEP_REF_CUSTOMER_ON_CLONING')) {
1620 $object->ref_client = '';
1621 $object->ref_customer = '';
1622 }
1623 if (getDolGlobalInt('MAIN_DONT_KEEP_NOTE_ON_CLONING') == 1) {
1624 $object->note_private = '';
1625 $object->note_public = '';
1626 }
1627 // Create clone
1628 $object->context['createfromclone'] = 'createfromclone';
1629 $result = $object->create($user);
1630 if ($result < 0) {
1632 $error++;
1633 }
1634
1635 if (!$error && !getDolGlobalInt('MAIN_IGNORE_CONTACTS_ON_CLONING')) {
1636 // copy internal contacts
1637 if ($object->copy_linked_contact($this, 'internal') < 0) {
1638 $error++;
1639 }
1640 }
1641
1642 if (!$error) {
1643 // copy external contacts if same company
1644 if ($this->socid == $object->socid) {
1645 if ($object->copy_linked_contact($this, 'external') < 0) {
1646 $error++;
1647 }
1648 }
1649 }
1650
1651 if (!$error) {
1652 // Hook of thirdparty module
1653 if (is_object($hookmanager)) {
1654 $parameters = array('objFrom' => $this, 'clonedObj' => $object);
1655 $action = '';
1656 $reshook = $hookmanager->executeHooks('createFrom', $parameters, $object, $action); // Note that $action and $object may have been modified by some hooks
1657 if ($reshook < 0) {
1658 $this->setErrorsFromObject($hookmanager);
1659 $error++;
1660 }
1661 }
1662 }
1663
1664 unset($object->context['createfromclone']);
1665
1666 // End
1667 if (!$error) {
1668 $this->db->commit();
1669 return $object->id;
1670 } else {
1671 $this->db->rollback();
1672 return -1;
1673 }
1674 }
1675
1685 public function fetch($rowid, $ref = '', $ref_ext = '', $forceentity = 0)
1686 {
1687 $sql = "SELECT p.rowid, p.ref, p.entity, p.fk_soc";
1688 $sql .= ", p.total_ttc, p.total_tva, p.localtax1, p.localtax2, p.total_ht";
1689 $sql .= ", p.datec";
1690 $sql .= ", p.date_signature as dates";
1691 $sql .= ", p.date_valid as datev";
1692 $sql .= ", p.date_cloture";
1693 $sql .= ", p.datep as dp";
1694 $sql .= ", p.fin_validite as dfv";
1695 $sql .= ", p.date_livraison as delivery_date";
1696 $sql .= ", p.model_pdf, p.last_main_doc, p.ref_client, ref_ext, p.extraparams";
1697 $sql .= ", p.note_private, p.note_public";
1698 $sql .= ", p.fk_projet as fk_project, p.fk_statut";
1699 $sql .= ", p.fk_user_author, p.fk_user_valid, p.fk_user_cloture";
1700 $sql .= ", p.fk_delivery_address";
1701 $sql .= ", p.fk_availability";
1702 $sql .= ", p.fk_input_reason";
1703 $sql .= ", p.fk_cond_reglement";
1704 $sql .= ", p.fk_mode_reglement";
1705 $sql .= ', p.fk_account';
1706 $sql .= ", p.fk_shipping_method";
1707 $sql .= ", p.fk_warehouse";
1708 $sql .= ", p.fk_incoterms, p.location_incoterms";
1709 $sql .= ", p.fk_multicurrency, p.multicurrency_code, p.multicurrency_tx, p.multicurrency_total_ht, p.multicurrency_total_tva, p.multicurrency_total_ttc";
1710 $sql .= ", p.tms as date_modification";
1711 $sql .= ", i.libelle as label_incoterms";
1712 $sql .= ", c.label as statut_label";
1713 $sql .= ", ca.code as availability_code, ca.label as availability";
1714 $sql .= ", dr.code as demand_reason_code, dr.label as demand_reason";
1715 $sql .= ", cr.code as cond_reglement_code, cr.libelle as cond_reglement, cr.libelle_facture as cond_reglement_libelle_doc, p.deposit_percent";
1716 $sql .= ", cp.code as mode_reglement_code, cp.libelle as mode_reglement";
1717 $sql .= " FROM ".MAIN_DB_PREFIX.$this->table_element." as p";
1718 $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_propalst as c ON p.fk_statut = c.id';
1719 $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_paiement as cp ON p.fk_mode_reglement = cp.id AND cp.entity IN ('.getEntity('c_paiement').')';
1720 $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').')';
1721 $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_availability as ca ON p.fk_availability = ca.rowid';
1722 $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_input_reason as dr ON p.fk_input_reason = dr.rowid';
1723 $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_incoterms as i ON p.fk_incoterms = i.rowid';
1724
1725 if (!empty($ref)) {
1726 if (!empty($forceentity)) {
1727 $sql .= " WHERE p.entity = ".(int) $forceentity; // Check only the current entity because we may have the same reference in several entities
1728 } else {
1729 $sql .= " WHERE p.entity IN (".getEntity('propal').")";
1730 }
1731 $sql .= " AND p.ref='".$this->db->escape($ref)."'";
1732 } else {
1733 // Don't use entity if you use rowid
1734 $sql .= " WHERE p.rowid = ".((int) $rowid);
1735 }
1736
1737 dol_syslog(get_class($this)."::fetch", LOG_DEBUG);
1738 $resql = $this->db->query($sql);
1739 if ($resql) {
1740 if ($this->db->num_rows($resql)) {
1741 $obj = $this->db->fetch_object($resql);
1742
1743 $this->id = $obj->rowid;
1744 $this->entity = $obj->entity;
1745
1746 $this->ref = $obj->ref;
1747 $this->ref_client = $obj->ref_client;
1748 $this->ref_customer = $obj->ref_client;
1749 $this->ref_ext = $obj->ref_ext;
1750
1751 $this->total = $obj->total_ttc; // TODO deprecated
1752 $this->total_ttc = $obj->total_ttc;
1753 $this->total_ht = $obj->total_ht;
1754 $this->total_tva = $obj->total_tva;
1755 $this->total_localtax1 = $obj->localtax1;
1756 $this->total_localtax2 = $obj->localtax2;
1757
1758 $this->socid = $obj->fk_soc;
1759 $this->thirdparty = null; // Clear if another value was already set by fetch_thirdparty
1760
1761 $this->fk_project = $obj->fk_project;
1762 $this->project = null; // Clear if another value was already set by fetchProject
1763
1764 $this->model_pdf = $obj->model_pdf;
1765 $this->last_main_doc = $obj->last_main_doc;
1766 $this->note = $obj->note_private; // TODO deprecated
1767 $this->note_private = $obj->note_private;
1768 $this->note_public = $obj->note_public;
1769
1770 $this->status = (int) $obj->fk_statut;
1771 $this->statut = $this->status; // deprecated
1772
1773 $this->datec = $this->db->jdate($obj->datec); // TODO deprecated
1774 $this->datev = $this->db->jdate($obj->datev); // TODO deprecated
1775 $this->date_creation = $this->db->jdate($obj->datec); //Creation date
1776 $this->date_validation = $this->db->jdate($obj->datev); //Validation date
1777 $this->date_modification = $this->db->jdate($obj->date_modification); // tms
1778 $this->date_signature = $this->db->jdate($obj->dates); // Signature date
1779 $this->date_cloture = $this->db->jdate($obj->date_cloture); // Closing date
1780 $this->date = $this->db->jdate($obj->dp); // Proposal date
1781 $this->datep = $this->db->jdate($obj->dp); // deprecated
1782 $this->fin_validite = $this->db->jdate($obj->dfv);
1783 $this->delivery_date = $this->db->jdate($obj->delivery_date);
1784 $this->shipping_method_id = ($obj->fk_shipping_method > 0) ? $obj->fk_shipping_method : null;
1785 $this->warehouse_id = ($obj->fk_warehouse > 0) ? $obj->fk_warehouse : null;
1786 $this->availability_id = $obj->fk_availability;
1787 $this->availability_code = $obj->availability_code;
1788 $this->availability = $obj->availability;
1789 $this->demand_reason_id = $obj->fk_input_reason;
1790 $this->demand_reason_code = $obj->demand_reason_code;
1791 $this->demand_reason = $obj->demand_reason;
1792 $this->fk_address = $obj->fk_delivery_address;
1793
1794 $this->mode_reglement_id = $obj->fk_mode_reglement;
1795 $this->mode_reglement_code = $obj->mode_reglement_code;
1796 $this->mode_reglement = $obj->mode_reglement;
1797 $this->fk_account = ($obj->fk_account > 0) ? $obj->fk_account : null;
1798 $this->cond_reglement_id = $obj->fk_cond_reglement;
1799 $this->cond_reglement_code = $obj->cond_reglement_code;
1800 $this->cond_reglement = $obj->cond_reglement;
1801 $this->cond_reglement_doc = $obj->cond_reglement_libelle_doc;
1802 $this->deposit_percent = $obj->deposit_percent;
1803
1804 $this->extraparams = !empty($obj->extraparams) ? (array) json_decode($obj->extraparams, true) : array();
1805
1806 $this->user_author_id = $obj->fk_user_author;
1807 $this->user_validation_id = $obj->fk_user_valid;
1808 $this->user_closing_id = $obj->fk_user_cloture;
1809
1810 //Incoterms
1811 $this->fk_incoterms = $obj->fk_incoterms;
1812 $this->location_incoterms = $obj->location_incoterms;
1813 $this->label_incoterms = $obj->label_incoterms;
1814
1815 // Multicurrency
1816 $this->fk_multicurrency = $obj->fk_multicurrency;
1817 $this->multicurrency_code = $obj->multicurrency_code;
1818 $this->multicurrency_tx = $obj->multicurrency_tx;
1819 $this->multicurrency_total_ht = $obj->multicurrency_total_ht;
1820 $this->multicurrency_total_tva = $obj->multicurrency_total_tva;
1821 $this->multicurrency_total_ttc = $obj->multicurrency_total_ttc;
1822
1823 // Retrieve all extrafield
1824 // fetch optionals attributes and labels
1825 $this->fetch_optionals();
1826
1827 $this->db->free($resql);
1828
1829 $this->lines = array();
1830
1831 // Lines
1832 $result = $this->fetch_lines();
1833 if ($result < 0) {
1834 return -3;
1835 }
1836
1837 return 1;
1838 }
1839
1840 $this->error = "Record Not Found";
1841 return 0;
1842 } else {
1843 $this->error = $this->db->lasterror();
1844 return -1;
1845 }
1846 }
1847
1855 public function update(User $user, $notrigger = 0)
1856 {
1857 $error = 0;
1858
1859 // Clean parameters
1860 if (isset($this->ref)) {
1861 $this->ref = trim($this->ref);
1862 }
1863 if (isset($this->ref_client)) {
1864 $this->ref_client = trim($this->ref_client);
1865 }
1866 if (isset($this->note) || isset($this->note_private)) {
1867 $this->note_private = (isset($this->note_private) ? trim($this->note_private) : trim($this->note));
1868 }
1869 if (isset($this->note_public)) {
1870 $this->note_public = trim($this->note_public);
1871 }
1872 if (isset($this->model_pdf)) {
1873 $this->model_pdf = trim($this->model_pdf);
1874 }
1875 if (isset($this->import_key)) {
1876 $this->import_key = trim($this->import_key);
1877 }
1878 if (!empty($this->duree_validite) && is_numeric($this->duree_validite)) {
1879 $this->fin_validite = $this->date + ($this->duree_validite * 24 * 3600);
1880 }
1881
1882 // Check parameters
1883 // Put here code to add control on parameters values
1884
1885 // Update request
1886 $sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element." SET";
1887 $sql = "UPDATE ".MAIN_DB_PREFIX."propal SET";
1888 $sql .= " ref = ".(isset($this->ref) ? "'".$this->db->escape($this->ref)."'" : "null").",";
1889 $sql .= " ref_client = ".(isset($this->ref_client) ? "'".$this->db->escape($this->ref_client)."'" : "null").",";
1890 $sql .= " ref_ext = ".(isset($this->ref_ext) ? "'".$this->db->escape($this->ref_ext)."'" : "null").",";
1891 $sql .= " fk_soc = ".(!empty($this->socid) ? (int) $this->socid : "null").",";
1892 $sql .= " datep = ".(strval($this->date) != '' ? "'".$this->db->idate($this->date)."'" : 'null').",";
1893 if (!empty($this->fin_validite)) {
1894 $sql .= " fin_validite = ".(strval($this->fin_validite) != '' ? "'".$this->db->idate($this->fin_validite)."'" : 'null').",";
1895 }
1896 $sql .= " date_valid = ".(strval($this->date_validation) != '' ? "'".$this->db->idate($this->date_validation)."'" : 'null').",";
1897 $sql .= " total_tva = ".(isset($this->total_tva) ? (float) $this->total_tva : "null").",";
1898 $sql .= " localtax1 = ".(isset($this->total_localtax1) ? (float) $this->total_localtax1 : "null").",";
1899 $sql .= " localtax2 = ".(isset($this->total_localtax2) ? (float) $this->total_localtax2 : "null").",";
1900 $sql .= " total_ht = ".(isset($this->total_ht) ? (float) $this->total_ht : "null").",";
1901 $sql .= " total_ttc = ".(isset($this->total_ttc) ? (float) $this->total_ttc : "null").",";
1902 $sql .= " fk_statut = ".(isset($this->status) ? (int) $this->status : "null").",";
1903 $sql .= " fk_user_author = ".(!empty($this->user_author_id) ? (int) $this->user_author_id : "null").",";
1904 $sql .= " fk_user_valid = ".(!empty($this->user_validation_id) ? (int) $this->user_validation_id : "null").",";
1905
1906 $sql .= " fk_projet = ".(!empty($this->fk_project) ? (int) $this->fk_project : "null").",";
1907 $sql .= " fk_cond_reglement = ".(!empty($this->cond_reglement_id) ? (int) $this->cond_reglement_id : "null").",";
1908 $sql .= " deposit_percent = ".(!empty($this->deposit_percent) ? "'".$this->db->escape($this->deposit_percent)."'" : "null").",";
1909 $sql .= " fk_mode_reglement = ".(!empty($this->mode_reglement_id) ? (int) $this->mode_reglement_id : "null").",";
1910 $sql .= " fk_input_reason = ".(!empty($this->demand_reason_id) ? (int) $this->demand_reason_id : "null").",";
1911 $sql .= " fk_shipping_method = ".(isset($this->shipping_method_id) ? (int) $this->shipping_method_id : "null").",";
1912 $sql .= " fk_availability = ".(!empty($this->availability_id) ? (int) $this->availability_id : "null").",";
1913 $sql .= " note_private = ".(isset($this->note_private) ? "'".$this->db->escape($this->note_private)."'" : "null").",";
1914 $sql .= " note_public = ".(isset($this->note_public) ? "'".$this->db->escape($this->note_public)."'" : "null").",";
1915 $sql .= " model_pdf = ".(isset($this->model_pdf) ? "'".$this->db->escape($this->model_pdf)."'" : "null").",";
1916 $sql .= " import_key = ".(isset($this->import_key) ? "'".$this->db->escape($this->import_key)."'" : "null");
1917 $sql .= " WHERE rowid = ".((int) $this->id);
1918
1919 $this->db->begin();
1920
1921 dol_syslog(get_class($this)."::update", LOG_DEBUG);
1922 $resql = $this->db->query($sql);
1923 if (!$resql) {
1924 $error++;
1925 $this->errors[] = "Error ".$this->db->lasterror();
1926 }
1927
1928 if (!$error) {
1929 $result = $this->insertExtraFields();
1930 if ($result < 0) {
1931 $error++;
1932 }
1933 }
1934
1935 if (!$error && !$notrigger) {
1936 // Call trigger
1937 $result = $this->call_trigger('PROPAL_MODIFY', $user);
1938 if ($result < 0) {
1939 $error++;
1940 }
1941 // End call triggers
1942 }
1943
1944 // Commit or rollback
1945 if ($error) {
1946 foreach ($this->errors as $errmsg) {
1947 dol_syslog(get_class($this)."::update ".$errmsg, LOG_ERR);
1948 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
1949 }
1950 $this->db->rollback();
1951 return -1 * $error;
1952 } else {
1953 $this->db->commit();
1954 return 1;
1955 }
1956 }
1957
1958
1959 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1968 public function fetch_lines($only_product = 0, $loadalsotranslation = 0, $sqlforgedfilters = '')
1969 {
1970 // phpcs:enable
1971 $this->lines = array();
1972
1973 $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,';
1974 $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,';
1975 $sql .= ' d.fk_unit,';
1976 $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,';
1977 $sql .= ' p.weight, p.weight_units, p.volume, p.volume_units,';
1978 $sql .= ' p.customcode, p.fk_country as country_id, c.code as country_code,';
1979 $sql .= ' d.date_start, d.date_end, d.extraparams,';
1980 $sql .= ' d.fk_multicurrency, d.multicurrency_code, d.multicurrency_subprice, d.multicurrency_total_ht, d.multicurrency_total_tva, d.multicurrency_total_ttc';
1981 $sql .= ' FROM '.MAIN_DB_PREFIX.$this->table_element_line.' as d';
1982 $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'product as p ON (d.fk_product = p.rowid)';
1983 $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_country as c ON c.rowid = p.fk_country';
1984 $sql .= ' WHERE d.fk_propal = '.((int) $this->id);
1985 if ($only_product) {
1986 $sql .= ' AND p.fk_product_type = 0';
1987 }
1988 if ($sqlforgedfilters) {
1989 $sql .= $sqlforgedfilters;
1990 }
1991 $sql .= ' ORDER BY d.rang, d.rowid';
1992
1993 dol_syslog(get_class($this)."::fetch_lines", LOG_DEBUG);
1994 $result = $this->db->query($sql);
1995 if ($result) {
1996 require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php';
1997
1998 $num = $this->db->num_rows($result);
1999
2000 $i = 0;
2001 while ($i < $num) {
2002 $objp = $this->db->fetch_object($result);
2003
2004 $line = new PropaleLigne($this->db);
2005
2006 $line->rowid = $objp->rowid; //Deprecated
2007 $line->id = $objp->rowid;
2008 $line->fk_propal = $objp->fk_propal;
2009 $line->fk_parent_line = $objp->fk_parent_line;
2010 $line->label = $objp->custom_label;
2011 $line->desc = $objp->description; // Line description
2012 $line->description = $objp->description; // Line description
2013 $line->qty = $objp->qty;
2014 $line->vat_src_code = $objp->vat_src_code;
2015 $line->tva_tx = $objp->tva_tx;
2016 $line->localtax1_tx = $objp->localtax1_tx;
2017 $line->localtax2_tx = $objp->localtax2_tx;
2018 $line->localtax1_type = $objp->localtax1_type;
2019 $line->localtax2_type = $objp->localtax2_type;
2020 $line->subprice = $objp->subprice;
2021 $line->subprice_ttc = $objp->subprice_ttc;
2022 $line->fk_remise_except = $objp->fk_remise_except;
2023 $line->remise_percent = $objp->remise_percent;
2024
2025 $line->info_bits = $objp->info_bits;
2026 $line->total_ht = $objp->total_ht;
2027 $line->total_tva = $objp->total_tva;
2028 $line->total_localtax1 = $objp->total_localtax1;
2029 $line->total_localtax2 = $objp->total_localtax2;
2030 $line->total_ttc = $objp->total_ttc;
2031 $line->fk_fournprice = $objp->fk_fournprice;
2032 $marginInfos = getMarginInfos($objp->subprice, $objp->remise_percent, $objp->tva_tx, $objp->localtax1_tx, $objp->localtax2_tx, $line->fk_fournprice, $objp->pa_ht);
2033 $line->pa_ht = $marginInfos[0];
2034 $line->marge_tx = $marginInfos[1];
2035 $line->marque_tx = $marginInfos[2];
2036 $line->special_code = $objp->special_code;
2037 $line->rang = $objp->rang;
2038
2039 $line->fk_product = $objp->fk_product;
2040
2041 $line->ref = $objp->product_ref; // deprecated
2042 $line->libelle = $objp->product_label; // deprecated
2043
2044 $line->product_ref = $objp->product_ref;
2045 $line->product_label = $objp->product_label;
2046 $line->product_desc = $objp->product_desc; // Description produit
2047 $line->product_tobatch = $objp->product_tobatch;
2048 $line->product_barcode = $objp->product_barcode;
2049 $line->product_custom_code = $objp->customcode;
2050 $line->product_custom_country_id = $objp->country_id;
2051 $line->product_custom_country_code = $objp->country_code;
2052 $line->product_type = $objp->product_type;
2053 $line->fk_product_type = $objp->fk_product_type; // deprecated
2054
2055 $line->fk_unit = $objp->fk_unit;
2056 $line->weight = $objp->weight;
2057 $line->weight_units = $objp->weight_units;
2058 $line->volume = $objp->volume;
2059 $line->volume_units = $objp->volume_units;
2060
2061 $line->date_start = $this->db->jdate($objp->date_start);
2062 $line->date_end = $this->db->jdate($objp->date_end);
2063
2064 $line->extraparams = !empty($objp->extraparams) ? (array) json_decode($objp->extraparams, true) : array();
2065
2066 // Multicurrency
2067 $line->fk_multicurrency = $objp->fk_multicurrency;
2068 $line->multicurrency_code = $objp->multicurrency_code;
2069 $line->multicurrency_subprice = $objp->multicurrency_subprice;
2070 $line->multicurrency_total_ht = $objp->multicurrency_total_ht;
2071 $line->multicurrency_total_tva = $objp->multicurrency_total_tva;
2072 $line->multicurrency_total_ttc = $objp->multicurrency_total_ttc;
2073
2074 $line->fetch_optionals();
2075
2076 // multilangs
2077 if (getDolGlobalInt('MAIN_MULTILANGS') && !empty($objp->fk_product) && !empty($loadalsotranslation)) {
2078 $tmpproduct = new Product($this->db);
2079 $tmpproduct->fetch($objp->fk_product);
2080 $tmpproduct->getMultiLangs();
2081
2082 $line->multilangs = $tmpproduct->multilangs;
2083 }
2084
2085 $this->lines[$i] = $line;
2086
2087 $i++;
2088 }
2089
2090 $this->db->free($result);
2091
2092 return $num;
2093 } else {
2094 $this->error = $this->db->lasterror();
2095 return -3;
2096 }
2097 }
2098
2106 public function valid($user, $notrigger = 0)
2107 {
2108 global $conf;
2109
2110 require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
2111
2112 $error = 0;
2113
2114 // Protection
2115 if ($this->status == self::STATUS_VALIDATED) {
2116 dol_syslog(get_class($this)."::valid action abandoned: already validated", LOG_WARNING);
2117 return 0;
2118 }
2119
2120 if (!((!getDolGlobalString('MAIN_USE_ADVANCED_PERMS') && $user->hasRight('propal', 'creer'))
2121 || (getDolGlobalString('MAIN_USE_ADVANCED_PERMS') && $user->hasRight('propal', 'propal_advance', 'validate')))) {
2122 $this->error = 'ErrorPermissionDenied';
2123 dol_syslog(get_class($this)."::valid ".$this->error, LOG_ERR);
2124 return -1;
2125 }
2126
2127 if (!getDolGlobalBool('PROPALE_NOCHECK_ONSALE_PRODUCTS_ONVALID') && !$this->checkActiveProductInLines()) {
2128 dol_syslog(get_class($this)."::valid checkActiveProductInLines ".$this->error, LOG_INFO);
2129 return -1;
2130 }
2131
2132 $now = dol_now();
2133
2134 $this->db->begin();
2135
2136 // Numbering module definition
2137 $soc = new Societe($this->db);
2138 $soc->fetch($this->socid);
2139
2140 // Define new ref
2141 if (preg_match('/^[\‍(]?PROV/i', $this->ref) || empty($this->ref)) { // empty should not happened, but when it occurs, the test save life
2142 $num = $this->getNextNumRef($soc);
2143 } else {
2144 $num = (string) $this->ref;
2145 }
2146 $this->newref = dol_sanitizeFileName($num);
2147
2148 $sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element;
2149 $sql .= " SET ref = '".$this->db->escape($num)."',";
2150 $sql .= " fk_statut = ".self::STATUS_VALIDATED.", date_valid='".$this->db->idate($now)."', fk_user_valid=".((int) $user->id);
2151 $sql .= " WHERE rowid = ".((int) $this->id)." AND fk_statut = ".self::STATUS_DRAFT;
2152
2153 dol_syslog(get_class($this)."::valid", LOG_DEBUG);
2154 $resql = $this->db->query($sql);
2155 if (!$resql) {
2156 dol_print_error($this->db);
2157 $error++;
2158 }
2159
2160 // Trigger calls
2161 if (!$error && !$notrigger) {
2162 // Call trigger
2163 $result = $this->call_trigger('PROPAL_VALIDATE', $user);
2164 if ($result < 0) {
2165 $error++;
2166 }
2167 // End call triggers
2168 }
2169
2170 if (!$error) {
2171 $this->oldref = $this->ref;
2172
2173 // Rename directory if dir was a temporary ref
2174 if (preg_match('/^[\‍(]?PROV/i', $this->ref)) {
2175 // Now we rename also files into index
2176 $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)."'";
2177 $sql .= " WHERE filename LIKE '".$this->db->escape($this->ref)."%' AND filepath = 'propale/".$this->db->escape($this->ref)."' and entity = ".((int) $conf->entity);
2178 $resql = $this->db->query($sql);
2179 if (!$resql) {
2180 $error++;
2181 $this->error = $this->db->lasterror();
2182 }
2183 $sql = 'UPDATE '.MAIN_DB_PREFIX."ecm_files set filepath = 'propale/".$this->db->escape($this->newref)."'";
2184 $sql .= " WHERE filepath = 'propale/".$this->db->escape($this->ref)."' and entity = ".((int) $conf->entity);
2185 $resql = $this->db->query($sql);
2186 if (!$resql) {
2187 $error++;
2188 $this->error = $this->db->lasterror();
2189 }
2190
2191 // We rename directory ($this->ref = old ref, $num = new ref) in order not to lose the attachments
2192 $oldref = dol_sanitizeFileName($this->ref);
2193 $newref = dol_sanitizeFileName($num);
2194 $dirsource = $conf->propal->multidir_output[$this->entity].'/'.$oldref;
2195 $dirdest = $conf->propal->multidir_output[$this->entity].'/'.$newref;
2196 if (!$error && file_exists($dirsource)) {
2197 dol_syslog(get_class($this)."::validate rename dir ".$dirsource." into ".$dirdest);
2198 if (@rename($dirsource, $dirdest)) {
2199 dol_syslog("Rename ok");
2200 // Rename docs starting with $oldref with $newref
2201 $listoffiles = dol_dir_list($dirdest, 'files', 1, '^'.preg_quote($oldref, '/'));
2202 foreach ($listoffiles as $fileentry) {
2203 $dirsource = $fileentry['name'];
2204 $dirdest = preg_replace('/^'.preg_quote($oldref, '/').'/', $newref, $dirsource);
2205 $dirsource = $fileentry['path'].'/'.$dirsource;
2206 $dirdest = $fileentry['path'].'/'.$dirdest;
2207 @rename($dirsource, $dirdest);
2208 }
2209 }
2210 }
2211 }
2212
2213 $this->ref = $num;
2214 $this->statut = self::STATUS_VALIDATED; // deprecated
2216 $this->user_validation_id = $user->id;
2217 $this->datev = $now;
2218 $this->date_validation = $now;
2219
2220 $this->db->commit();
2221 return 1;
2222 } else {
2223 $this->db->rollback();
2224 return -1;
2225 }
2226 }
2227
2228
2229 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2238 public function set_date($user, $date, $notrigger = 0)
2239 {
2240 // phpcs:enable
2241 if (empty($date)) {
2242 $this->error = 'ErrorBadParameter';
2243 dol_syslog(get_class($this)."::set_date ".$this->error, LOG_ERR);
2244 return -1;
2245 }
2246
2247 if ($user->hasRight('propal', 'creer')) {
2248 $error = 0;
2249
2250 $this->db->begin();
2251
2252 $sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element;
2253 $sql .= " SET datep = '".$this->db->idate($date)."'";
2254 $sql .= " WHERE rowid = ".((int) $this->id);
2255
2256 dol_syslog(__METHOD__, LOG_DEBUG);
2257 $resql = $this->db->query($sql);
2258 if (!$resql) {
2259 $this->errors[] = $this->db->error();
2260 $error++;
2261 }
2262
2263 if (!$error) {
2264 $this->oldcopy = clone $this;
2265 $this->date = $date;
2266 $this->datep = $date; // deprecated
2267 }
2268
2269 if (!$notrigger && empty($error)) {
2270 // Call trigger
2271 $result = $this->call_trigger('PROPAL_MODIFY', $user);
2272 if ($result < 0) {
2273 $error++;
2274 }
2275 // End call triggers
2276 }
2277
2278 if (!$error) {
2279 $this->db->commit();
2280 return 1;
2281 } else {
2282 foreach ($this->errors as $errmsg) {
2283 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2284 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2285 }
2286 $this->db->rollback();
2287 return -1 * $error;
2288 }
2289 }
2290
2291 return -1;
2292 }
2293
2294 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2303 public function set_echeance($user, $date_end_validity, $notrigger = 0)
2304 {
2305 // phpcs:enable
2306 if ($user->hasRight('propal', 'creer')) {
2307 $error = 0;
2308
2309 $this->db->begin();
2310
2311 $sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element;
2312 $sql .= " SET fin_validite = ".($date_end_validity != '' ? "'".$this->db->idate($date_end_validity)."'" : 'null');
2313 $sql .= " WHERE rowid = ".((int) $this->id);
2314
2315 dol_syslog(__METHOD__, LOG_DEBUG);
2316
2317 $resql = $this->db->query($sql);
2318 if (!$resql) {
2319 $this->errors[] = $this->db->error();
2320 $error++;
2321 }
2322
2323
2324 if (!$error) {
2325 $this->oldcopy = clone $this;
2326 $this->fin_validite = $date_end_validity;
2327 }
2328
2329 if (!$notrigger && empty($error)) {
2330 // Call trigger
2331 $result = $this->call_trigger('PROPAL_MODIFY', $user);
2332 if ($result < 0) {
2333 $error++;
2334 }
2335 // End call triggers
2336 }
2337
2338 if (!$error) {
2339 $this->db->commit();
2340 return 1;
2341 } else {
2342 foreach ($this->errors as $errmsg) {
2343 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2344 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2345 }
2346 $this->db->rollback();
2347 return -1 * $error;
2348 }
2349 }
2350
2351 return -1;
2352 }
2353
2354 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2364 public function set_date_livraison($user, $delivery_date, $notrigger = 0)
2365 {
2366 // phpcs:enable
2367 return $this->setDeliveryDate($user, $delivery_date, $notrigger);
2368 }
2369
2378 public function setDeliveryDate($user, $delivery_date, $notrigger = 0)
2379 {
2380 if ($user->hasRight('propal', 'creer')) {
2381 $error = 0;
2382
2383 $this->db->begin();
2384
2385 $sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element;
2386 $sql .= " SET date_livraison = ".(isDolTms($delivery_date) ? "'".$this->db->idate($delivery_date)."'" : 'null');
2387 $sql .= " WHERE rowid = ".((int) $this->id);
2388
2389 dol_syslog(__METHOD__, LOG_DEBUG);
2390 $resql = $this->db->query($sql);
2391 if (!$resql) {
2392 $this->errors[] = $this->db->error();
2393 $error++;
2394 }
2395
2396 if (!$error) {
2397 $this->oldcopy = clone $this;
2398 $this->delivery_date = $delivery_date;
2399 }
2400
2401 if (!$notrigger && empty($error)) {
2402 // Call trigger
2403 $result = $this->call_trigger('PROPAL_MODIFY', $user);
2404 if ($result < 0) {
2405 $error++;
2406 }
2407 // End call triggers
2408 }
2409
2410 if (!$error) {
2411 $this->db->commit();
2412 return 1;
2413 } else {
2414 foreach ($this->errors as $errmsg) {
2415 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2416 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2417 }
2418 $this->db->rollback();
2419 return -1 * $error;
2420 }
2421 }
2422
2423 return -1;
2424 }
2425
2426 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2435 public function set_availability($user, $id, $notrigger = 0)
2436 {
2437 // phpcs:enable
2438 if ($this->status >= self::STATUS_DRAFT) {
2439 $error = 0;
2440
2441 $this->db->begin();
2442
2443 $sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element;
2444 $sql .= " SET fk_availability = ".((int) ($id > 0 ? ((int) $id) : 0));
2445 $sql .= " WHERE rowid = ".((int) $this->id);
2446
2447 dol_syslog(__METHOD__.' availability('.$id.')', LOG_DEBUG);
2448 $resql = $this->db->query($sql);
2449 if (!$resql) {
2450 $this->errors[] = $this->db->error();
2451 $error++;
2452 }
2453
2454 if (!$error) {
2455 $this->oldcopy = clone $this;
2456 $this->fk_availability = $id;
2457 $this->availability_id = $id;
2458 }
2459
2460 if (!$notrigger && empty($error)) {
2461 // Call trigger
2462 $result = $this->call_trigger('PROPAL_MODIFY', $user);
2463 if ($result < 0) {
2464 $error++;
2465 }
2466 // End call triggers
2467 }
2468
2469 if (!$error) {
2470 $this->db->commit();
2471 return 1;
2472 } else {
2473 foreach ($this->errors as $errmsg) {
2474 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2475 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2476 }
2477 $this->db->rollback();
2478 return -1 * $error;
2479 }
2480 } else {
2481 $error_str = 'Propal status do not meet requirement '.$this->status;
2482 dol_syslog(__METHOD__.$error_str, LOG_ERR);
2483 $this->error = $error_str;
2484 $this->errors[] = $this->error;
2485 return -2;
2486 }
2487 }
2488
2489 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2498 public function set_demand_reason($user, $id, $notrigger = 0)
2499 {
2500 // phpcs:enable
2501 if ($user->hasRight('propal', 'creer') && $this->status >= self::STATUS_DRAFT) {
2502 $error = 0;
2503
2504 $this->db->begin();
2505
2506 $sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element;
2507 $sql .= " SET fk_input_reason = ".((int) $id);
2508 $sql .= " WHERE rowid = ".((int) $this->id);
2509
2510 dol_syslog(__METHOD__, LOG_DEBUG);
2511 $resql = $this->db->query($sql);
2512 if (!$resql) {
2513 $this->errors[] = $this->db->error();
2514 $error++;
2515 }
2516
2517
2518 if (!$error) {
2519 $this->oldcopy = clone $this;
2520
2521 $this->demand_reason_id = $id;
2522 }
2523
2524
2525 if (!$notrigger && empty($error)) {
2526 // Call trigger
2527 $result = $this->call_trigger('PROPAL_MODIFY', $user);
2528 if ($result < 0) {
2529 $error++;
2530 }
2531 // End call triggers
2532 }
2533
2534 if (!$error) {
2535 $this->db->commit();
2536 return 1;
2537 } else {
2538 foreach ($this->errors as $errmsg) {
2539 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2540 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2541 }
2542 $this->db->rollback();
2543 return -1 * $error;
2544 }
2545 } else {
2546 $error_str = 'Propal status do not meet requirement '.$this->status;
2547 dol_syslog(__METHOD__.$error_str, LOG_ERR);
2548 $this->error = $error_str;
2549 $this->errors[] = $this->error;
2550 return -2;
2551 }
2552 }
2553
2554 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2563 public function set_ref_client($user, $ref_client, $notrigger = 0)
2564 {
2565 // phpcs:enable
2566 if ($user->hasRight('propal', 'creer')) {
2567 $error = 0;
2568
2569 $this->db->begin();
2570
2571 $sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element;
2572 $sql .= " SET ref_client = ".(empty($ref_client) ? 'NULL' : "'".$this->db->escape($ref_client)."'");
2573 $sql .= " WHERE rowid = ".((int) $this->id);
2574
2575 dol_syslog(__METHOD__.' $this->id='.$this->id.', ref_client='.$ref_client, LOG_DEBUG);
2576 $resql = $this->db->query($sql);
2577 if (!$resql) {
2578 $this->errors[] = $this->db->error();
2579 $error++;
2580 }
2581
2582 if (!$error) {
2583 $this->oldcopy = clone $this;
2584 $this->ref_customer = $ref_client;
2585 $this->ref_client = $ref_client;
2586 }
2587
2588 if (!$notrigger && empty($error)) {
2589 // Call trigger
2590 $result = $this->call_trigger('PROPAL_MODIFY', $user);
2591 if ($result < 0) {
2592 $error++;
2593 }
2594 // End call triggers
2595 }
2596
2597 if (!$error) {
2598 $this->db->commit();
2599 return 1;
2600 } else {
2601 foreach ($this->errors as $errmsg) {
2602 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2603 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2604 }
2605 $this->db->rollback();
2606 return -1 * $error;
2607 }
2608 }
2609
2610 return -1;
2611 }
2612
2613
2623 public function reopen($user, $status, $note = '', $notrigger = 0)
2624 {
2625 $error = 0;
2626
2627 $sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element;
2628 $sql .= " SET fk_statut = ".((int) $status).",";
2629 if (!empty($note)) {
2630 $sql .= " note_private = '".$this->db->escape($note)."',";
2631 }
2632 $sql .= " date_cloture=NULL, fk_user_cloture=NULL";
2633 $sql .= " WHERE rowid = ".((int) $this->id);
2634
2635 $this->db->begin();
2636
2637 dol_syslog(get_class($this)."::reopen", LOG_DEBUG);
2638 $resql = $this->db->query($sql);
2639 if (!$resql) {
2640 $error++;
2641 $this->errors[] = "Error ".$this->db->lasterror();
2642 }
2643 if (!$error) {
2644 if (!$notrigger) {
2645 // Call trigger
2646 $result = $this->call_trigger('PROPAL_REOPEN', $user);
2647 if ($result < 0) {
2648 $error++;
2649 }
2650 // End call triggers
2651 }
2652 }
2653
2654 // Commit or rollback
2655 if ($error) {
2656 if (!empty($this->errors)) {
2657 foreach ($this->errors as $errmsg) {
2658 dol_syslog(get_class($this)."::update ".$errmsg, LOG_ERR);
2659 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2660 }
2661 }
2662 $this->db->rollback();
2663 return -1 * $error;
2664 } else {
2665 $this->statut = $status;
2666 $this->status = $status;
2667
2668 $this->db->commit();
2669 return 1;
2670 }
2671 }
2672
2683 public function closeProposal($user, $status, $note_private = '', $notrigger = 0, $note_public = '')
2684 {
2685 global $langs,$conf;
2686
2687 $error = 0;
2688 $now = dol_now();
2689
2690 $this->db->begin();
2691
2692 $newprivatenote = dol_concatdesc($this->note_private, $note_private);
2693 $newpublicnote = dol_concatdesc($this->note_public, $note_public);
2694
2695 if (!getDolGlobalString('PROPALE_KEEP_OLD_SIGNATURE_INFO')) {
2696 $date_signature = $now;
2697 $fk_user_signature = $user->id;
2698 } else {
2699 $this->info($this->id);
2700 if (!isset($this->date_signature) || $this->date_signature == '') {
2701 $date_signature = $now;
2702 $fk_user_signature = $user->id;
2703 } else {
2704 $date_signature = $this->date_signature;
2705 $fk_user_signature = $this->user_signature->id;
2706 }
2707 }
2708
2709 $sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element;
2710 $sql .= " SET fk_statut = ".((int) $status).", note_private = '".$this->db->escape($newprivatenote)."', note_public = '".$this->db->escape($newpublicnote)."'";
2711 if ($status == self::STATUS_SIGNED) {
2712 $sql .= ", date_signature='".$this->db->idate($now)."', fk_user_signature = ".((int) $fk_user_signature);
2713 }
2714 $sql .= " WHERE rowid = ".((int) $this->id);
2715
2716 $resql = $this->db->query($sql);
2717 if ($resql) {
2718 // Status self::STATUS_REFUSED by default
2719 $modelpdf = getDolGlobalString('PROPALE_ADDON_PDF_ODT_CLOSED', $this->model_pdf);
2720 $triggerName = 'PROPAL_CLOSE_REFUSED'; // used later in call_trigger()
2721
2722 if ($status == self::STATUS_SIGNED) { // Status self::STATUS_SIGNED
2723 $triggerName = 'PROPAL_CLOSE_SIGNED'; // used later in call_trigger()
2724 $modelpdf = getDolGlobalString('PROPALE_ADDON_PDF_ODT_TOBILL', $this->model_pdf);
2725
2726 // The connected company is classified as a client
2727 $soc = new Societe($this->db);
2728 $soc->id = $this->socid;
2729 $result = $soc->setAsCustomer();
2730
2731 if ($result < 0) {
2732 $this->error = $this->db->lasterror();
2733 $this->db->rollback();
2734 return -2;
2735 }
2736 }
2737
2738 if (!getDolGlobalString('MAIN_DISABLE_PDF_AUTOUPDATE') && !getDolGlobalInt('PROPAL_DISABLE_AUTOUPDATE_ON_CLOSE')) {
2739 // Define output language
2740 $outputlangs = $langs;
2741 if (getDolGlobalInt('MAIN_MULTILANGS')) {
2742 $outputlangs = new Translate("", $conf);
2743 $newlang = (GETPOST('lang_id', 'aZ09') ? GETPOST('lang_id', 'aZ09') : $this->thirdparty->default_lang);
2744 $outputlangs->setDefaultLang($newlang);
2745 }
2746
2747 // PDF
2748 $hidedetails = (getDolGlobalString('MAIN_GENERATE_DOCUMENTS_HIDE_DETAILS') ? 1 : 0);
2749 $hidedesc = (getDolGlobalString('MAIN_GENERATE_DOCUMENTS_HIDE_DESC') ? 1 : 0);
2750 $hideref = (getDolGlobalString('MAIN_GENERATE_DOCUMENTS_HIDE_REF') ? 1 : 0);
2751
2752 //$ret=$object->fetch($id); // Reload to get new records
2753 $this->generateDocument($modelpdf, $outputlangs, $hidedetails, $hidedesc, $hideref);
2754 }
2755
2756 $this->oldcopy = clone $this;
2757 $this->statut = $status; // deprecated
2758 $this->status = $status;
2759 $this->date_signature = $date_signature;
2760 $this->note_private = $newprivatenote;
2761
2762 if (!$notrigger && empty($error)) {
2763 // Call trigger
2764 $result = $this->call_trigger($triggerName, $user);
2765 if ($result < 0) {
2766 $error++;
2767 }
2768 // End call triggers
2769 }
2770
2771 if (!$error) {
2772 $this->db->commit();
2773 return 1;
2774 } else {
2775 foreach ($this->errors as $errmsg) {
2776 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2777 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2778 }
2779 if (!empty($this->oldcopy)) {
2780 $this->statut = $this->oldcopy->status; // deprecated
2781 $this->status = $this->oldcopy->status;
2782 $this->date_signature = $this->oldcopy->date_signature;
2783 $this->note_private = $this->oldcopy->note_private;
2784 }
2785
2786 $this->db->rollback();
2787 return -1;
2788 }
2789 } else {
2790 $this->error = $this->db->lasterror();
2791 $this->db->rollback();
2792 return -1;
2793 }
2794 }
2795
2804 public function classifyBilled(User $user, $notrigger = 0, $note = '')
2805 {
2806 global $conf, $langs;
2807
2808 $error = 0;
2809
2810 $now = dol_now();
2811 $num = 0;
2812
2813 $triggerName = 'PROPAL_CLASSIFY_BILLED';
2814
2815 $this->db->begin();
2816
2817 $newprivatenote = dol_concatdesc($this->note_private, $note);
2818
2819 $sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element;
2820 $sql .= " SET fk_statut = ".self::STATUS_BILLED.", ";
2821 $sql .= " note_private = '".$this->db->escape($newprivatenote)."', date_cloture='".$this->db->idate($now)."', fk_user_cloture=".((int) $user->id);
2822 $sql .= ' WHERE rowid = '.((int) $this->id).' AND fk_statut = '.((int) self::STATUS_SIGNED);
2823
2824 dol_syslog(__METHOD__, LOG_DEBUG);
2825 $resql = $this->db->query($sql);
2826 if (!$resql) {
2827 $this->errors[] = $this->db->error();
2828 $error++;
2829 } else {
2830 $num = $this->db->affected_rows($resql);
2831 }
2832
2833 if (!$error) {
2834 $modelpdf = getDolGlobalString('PROPALE_ADDON_PDF_ODT_CLOSED', $this->model_pdf);
2835
2836 if (!getDolGlobalString('MAIN_DISABLE_PDF_AUTOUPDATE')) {
2837 // Define output language
2838 $outputlangs = $langs;
2839 if (getDolGlobalInt('MAIN_MULTILANGS')) {
2840 $outputlangs = new Translate("", $conf);
2841 $newlang = (GETPOST('lang_id', 'aZ09') ? GETPOST('lang_id', 'aZ09') : $this->thirdparty->default_lang);
2842 $outputlangs->setDefaultLang($newlang);
2843 }
2844
2845 // PDF
2846 $hidedetails = (getDolGlobalString('MAIN_GENERATE_DOCUMENTS_HIDE_DETAILS') ? 1 : 0);
2847 $hidedesc = (getDolGlobalString('MAIN_GENERATE_DOCUMENTS_HIDE_DESC') ? 1 : 0);
2848 $hideref = (getDolGlobalString('MAIN_GENERATE_DOCUMENTS_HIDE_REF') ? 1 : 0);
2849
2850 //$ret=$object->fetch($id); // Reload to get new records
2851 $this->generateDocument($modelpdf, $outputlangs, $hidedetails, $hidedesc, $hideref);
2852 }
2853
2854 $this->oldcopy = clone $this;
2855 $this->statut = self::STATUS_BILLED;
2856 $this->status = self::STATUS_BILLED;
2857 $this->date_cloture = $now;
2858 $this->note_private = $newprivatenote;
2859 }
2860
2861 if (!$notrigger && empty($error)) {
2862 // Call trigger
2863 $result = $this->call_trigger($triggerName, $user);
2864 if ($result < 0) {
2865 $error++;
2866 }
2867 // End call triggers
2868 }
2869
2870 if (!$error) {
2871 $this->db->commit();
2872 return $num;
2873 } else {
2874 foreach ($this->errors as $errmsg) {
2875 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2876 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2877 }
2878 $this->db->rollback();
2879 return -1 * $error;
2880 }
2881 }
2882
2889 public function setCancel(User $user)
2890 {
2891 $error = 0;
2892
2893 $this->db->begin();
2894
2895 $sql = "UPDATE ". MAIN_DB_PREFIX . $this->table_element;
2896 $sql .= " SET fk_statut = " . self::STATUS_CANCELED . ",";
2897 $sql .= " fk_user_modif = " . ((int) $user->id);
2898 $sql .= " WHERE rowid = " . ((int) $this->id);
2899
2900 dol_syslog(get_class($this)."::cancel", LOG_DEBUG);
2901 if ($this->db->query($sql)) {
2902 // Call trigger
2903 $result = $this->call_trigger('PROPAL_CANCEL', $user);
2904 if ($result < 0) {
2905 $error++;
2906 }
2907 // End call triggers
2908
2909 if (!$error) {
2910 $this->statut = self::STATUS_CANCELED; // deprecated
2912 $this->db->commit();
2913 return 1;
2914 } else {
2915 foreach ($this->errors as $errmsg) {
2916 dol_syslog(get_class($this)."::cancel ".$errmsg, LOG_ERR);
2917 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2918 }
2919 $this->db->rollback();
2920 return -1;
2921 }
2922 } else {
2923 $this->error = $this->db->error();
2924 $this->db->rollback();
2925 return -1;
2926 }
2927 }
2928
2929 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2937 public function setDraft($user, $notrigger = 0)
2938 {
2939 // phpcs:enable
2940 $error = 0;
2941
2942 // Protection
2943 if ($this->status <= self::STATUS_DRAFT) {
2944 return 0;
2945 }
2946
2947 dol_syslog(get_class($this)."::setDraft", LOG_DEBUG);
2948
2949 $this->db->begin();
2950
2951 $sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element;
2952 $sql .= " SET fk_statut = ".self::STATUS_DRAFT;
2953 $sql .= ", online_sign_ip = NULL , online_sign_name = NULL";
2954 $sql .= " WHERE rowid = ".((int) $this->id);
2955
2956 $resql = $this->db->query($sql);
2957 if (!$resql) {
2958 $this->errors[] = $this->db->error();
2959 $error++;
2960 }
2961
2962 if (!$error) {
2963 $this->oldcopy = clone $this;
2964 }
2965
2966 if (!$notrigger && empty($error)) {
2967 // Call trigger
2968 $result = $this->call_trigger('PROPAL_MODIFY', $user);
2969 if ($result < 0) {
2970 $error++;
2971 }
2972 // End call triggers
2973 }
2974
2975 if (!$error) {
2976 $this->statut = self::STATUS_DRAFT; // deprecated
2977 $this->status = self::STATUS_DRAFT;
2978
2979 $this->db->commit();
2980 return 1;
2981 } else {
2982 foreach ($this->errors as $errmsg) {
2983 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2984 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2985 }
2986 $this->db->rollback();
2987 return -1 * $error;
2988 }
2989 }
2990
2991
2992 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3006 public function liste_array($shortlist = 0, $draft = 0, $notcurrentuser = 0, $socid = 0, $limit = 0, $offset = 0, $sortfield = 'p.datep', $sortorder = 'DESC')
3007 {
3008 // phpcs:enable
3009 global $user;
3010
3011 $ga = array();
3012
3013 $sql = "SELECT s.rowid, s.nom as name, s.client,";
3014 $sql .= " p.rowid as propalid, p.fk_statut, p.total_ht, p.ref, p.remise, ";
3015 $sql .= " p.datep as dp, p.fin_validite as datelimite";
3016 $sql .= " FROM ".MAIN_DB_PREFIX."societe as s, ".MAIN_DB_PREFIX.$this->table_element." as p, ".MAIN_DB_PREFIX."c_propalst as c";
3017 $sql .= " WHERE p.entity IN (".getEntity('propal').")";
3018 $sql .= " AND p.fk_soc = s.rowid";
3019 $sql .= " AND p.fk_statut = c.id";
3020
3021 // If the internal user must only see his customers, force searching by him
3022 $search_sale = 0;
3023 if (empty($user->socid) && !$user->hasRight('societe', 'client', 'voir')) {
3024 $search_sale = $user->id;
3025 }
3026 // Search on sale representative
3027 if ($search_sale && $search_sale != '-1') {
3028 if ($search_sale == -2) {
3029 $sql .= " AND NOT EXISTS (SELECT sc.fk_soc FROM ".MAIN_DB_PREFIX."societe_commerciaux as sc WHERE sc.fk_soc = p.fk_soc)";
3030 } elseif ($search_sale > 0) {
3031 $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).")";
3032 }
3033 }
3034 // Search on socid
3035 if ($socid) {
3036 $sql .= " AND p.fk_soc = ".((int) $socid);
3037 }
3038 if ($draft) {
3039 $sql .= " AND p.fk_statut = ".((int) self::STATUS_DRAFT);
3040 }
3041 if ($notcurrentuser > 0) {
3042 $sql .= " AND p.fk_user_author <> ".((int) $user->id);
3043 }
3044 $sql .= $this->db->order($sortfield, $sortorder);
3045 $sql .= $this->db->plimit($limit, $offset);
3046
3047 $result = $this->db->query($sql);
3048 if ($result) {
3049 $num = $this->db->num_rows($result);
3050 if ($num) {
3051 $i = 0;
3052 while ($i < $num) {
3053 $obj = $this->db->fetch_object($result);
3054
3055 if ($shortlist == 1) {
3056 $ga[$obj->propalid] = $obj->ref;
3057 } elseif ($shortlist == 2) {
3058 $ga[$obj->propalid] = $obj->ref.' ('.$obj->name.')';
3059 } else {
3060 $ga[$i]['id'] = $obj->propalid;
3061 $ga[$i]['ref'] = $obj->ref;
3062 $ga[$i]['name'] = $obj->name;
3063 }
3064
3065 $i++;
3066 }
3067 }
3068 return $ga;
3069 } else {
3070 dol_print_error($this->db);
3071 return -1;
3072 }
3073 }
3074
3080 public function getInvoiceArrayList()
3081 {
3082 return $this->InvoiceArrayList($this->id);
3083 }
3084
3085 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3092 public function InvoiceArrayList($id)
3093 {
3094 // phpcs:enable
3095 $ga = array();
3096 $linkedInvoices = array();
3097
3098 $this->fetchObjectLinked($id, $this->element);
3099 foreach ($this->linkedObjectsIds as $objecttype => $objectid) {
3100 // New system of common object return rowids and no linear id from 1 to n
3101 // We are therefore traversing a list of objects as a single object
3102 foreach ($objectid as $key => $object) {
3103 // Case of invoices directly linked
3104 if ($objecttype == 'facture') {
3105 $linkedInvoices[] = $object;
3106 } else {
3107 // Case of invoices linked by another object (e.g., order)
3108 $this->fetchObjectLinked($object, $objecttype);
3109 foreach ($this->linkedObjectsIds as $subobjecttype => $subobjectid) {
3110 foreach ($subobjectid as $subkey => $subobject) {
3111 if ($subobjecttype == 'facture') {
3112 $linkedInvoices[] = $subobject;
3113 }
3114 }
3115 }
3116 }
3117 }
3118 }
3119
3120 if (count($linkedInvoices) > 0) {
3121 $sql = "SELECT rowid as facid, ref, total_ht as total, datef as df, fk_user_author, fk_statut, paye";
3122 $sql .= " FROM ".MAIN_DB_PREFIX."facture";
3123 $sql .= " WHERE rowid IN (".$this->db->sanitize(implode(',', $linkedInvoices)).")";
3124
3125 dol_syslog(get_class($this)."::InvoiceArrayList", LOG_DEBUG);
3126 $resql = $this->db->query($sql);
3127
3128 if ($resql) {
3129 $tab_sqlobj = array();
3130 $nump = $this->db->num_rows($resql);
3131 for ($i = 0; $i < $nump; $i++) {
3132 $obj = $this->db->fetch_object($resql);
3133 $tab_sqlobj[] = $obj;
3134 }
3135 $this->db->free($resql);
3136
3137 $nump = count($tab_sqlobj);
3138
3139 if ($nump) {
3140 $i = 0;
3141 while ($i < $nump) {
3142 $obj = array_shift($tab_sqlobj);
3143
3144 $ga[$i] = $obj;
3145
3146 $i++;
3147 }
3148 }
3149 return $ga;
3150 } else {
3151 return -1;
3152 }
3153 } else {
3154 return $ga;
3155 }
3156 }
3157
3165 public function delete($user, $notrigger = 0)
3166 {
3167 global $conf;
3168 require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
3169
3170 $error = 0;
3171
3172 $this->db->begin();
3173
3174 if (!$notrigger) {
3175 // Call trigger
3176 $result = $this->call_trigger('PROPAL_DELETE', $user);
3177 if ($result < 0) {
3178 $error++;
3179 }
3180 // End call triggers
3181 }
3182
3183 // Remove linked categories.
3184 if (!$error) {
3185 $sql = "DELETE FROM ".MAIN_DB_PREFIX."categorie_propal";
3186 $sql .= " WHERE fk_propal = ".((int) $this->id);
3187
3188 $result = $this->db->query($sql);
3189 if (!$result) {
3190 $error++;
3191 $this->error = $this->db->lasterror();
3192 $this->errors[] = $this->error;
3193 }
3194 }
3195
3196 // Delete extrafields of lines and lines
3197 if (!$error && !empty($this->table_element_line)) {
3198 $tabletodelete = $this->table_element_line;
3199 $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).")";
3200 $sql = "DELETE FROM ".MAIN_DB_PREFIX.$tabletodelete." WHERE ".$this->fk_element." = ".((int) $this->id);
3201 if (!$this->db->query($sqlef) || !$this->db->query($sql)) {
3202 $error++;
3203 $this->error = $this->db->lasterror();
3204 $this->errors[] = $this->error;
3205 dol_syslog(get_class($this)."::delete error ".$this->error, LOG_ERR);
3206 }
3207 }
3208
3209 if (!$error) {
3210 // Delete linked object
3211 $res = $this->deleteObjectLinked();
3212 if ($res < 0) {
3213 $error++;
3214 }
3215 }
3216
3217 if (!$error) {
3218 // Delete linked contacts
3219 $res = $this->delete_linked_contact();
3220 if ($res < 0) {
3221 $error++;
3222 }
3223 }
3224
3225 // Removed extrafields of object
3226 if (!$error) {
3227 $result = $this->deleteExtraFields();
3228 if ($result < 0) {
3229 $error++;
3230 dol_syslog(get_class($this)."::delete error ".$this->error, LOG_ERR);
3231 }
3232 }
3233
3234 // Delete main record
3235 if (!$error) {
3236 $sql = "DELETE FROM ".MAIN_DB_PREFIX.$this->table_element." WHERE rowid = ".((int) $this->id);
3237 $res = $this->db->query($sql);
3238 if (!$res) {
3239 $error++;
3240 $this->error = $this->db->lasterror();
3241 $this->errors[] = $this->error;
3242 dol_syslog(get_class($this)."::delete error ".$this->error, LOG_ERR);
3243 }
3244 }
3245
3246 // Delete record into ECM index and physically
3247 if (!$error) {
3248 $res = $this->deleteEcmFiles(0); // Deleting files physically is done later with the dol_delete_dir_recursive
3249 $res = $this->deleteEcmFiles(1); // Deleting files physically is done later with the dol_delete_dir_recursive
3250 if (!$res) {
3251 $error++;
3252 }
3253 }
3254
3255 if (!$error) {
3256 // We remove directory
3257 $ref = dol_sanitizeFileName($this->ref);
3258 if ($conf->propal->multidir_output[$this->entity] && !empty($this->ref)) {
3259 $dir = $conf->propal->multidir_output[$this->entity]."/".$ref;
3260 $file = $dir."/".$ref.".pdf";
3261 if (file_exists($file)) {
3262 dol_delete_preview($this);
3263
3264 if (!dol_delete_file($file, 0, 0, 0, $this)) {
3265 $this->error = 'ErrorFailToDeleteFile';
3266 $this->errors[] = $this->error;
3267 $this->db->rollback();
3268 return 0;
3269 }
3270 }
3271 if (file_exists($dir)) {
3272 $res = @dol_delete_dir_recursive($dir); // delete files physically + into ecm tables
3273 if (!$res) {
3274 $this->error = 'ErrorFailToDeleteDir';
3275 $this->errors[] = $this->error;
3276 $this->db->rollback();
3277 return 0;
3278 }
3279 }
3280 }
3281 }
3282
3283 if (!$error) {
3284 dol_syslog(get_class($this)."::delete ".$this->id." by ".$user->id, LOG_DEBUG);
3285 $this->db->commit();
3286 return 1;
3287 } else {
3288 $this->db->rollback();
3289 return -1;
3290 }
3291 }
3292
3301 public function availability($availability_id, $notrigger = 0)
3302 {
3303 global $user;
3304
3305 if ($this->status >= self::STATUS_DRAFT) {
3306 $error = 0;
3307
3308 $this->db->begin();
3309
3310 $sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element;
3311 $sql .= ' SET fk_availability = '.((int) $availability_id);
3312 $sql .= ' WHERE rowid='.((int) $this->id);
3313
3314 dol_syslog(__METHOD__.' availability('.$availability_id.')', LOG_DEBUG);
3315 $resql = $this->db->query($sql);
3316 if (!$resql) {
3317 $this->errors[] = $this->db->error();
3318 $error++;
3319 }
3320
3321 if (!$error) {
3322 $this->oldcopy = clone $this;
3323 $this->availability_id = $availability_id;
3324 }
3325
3326 if (!$notrigger && empty($error)) {
3327 // Call trigger
3328 $result = $this->call_trigger('PROPAL_MODIFY', $user);
3329 if ($result < 0) {
3330 $error++;
3331 }
3332 // End call triggers
3333 }
3334
3335 if (!$error) {
3336 $this->db->commit();
3337 return 1;
3338 } else {
3339 foreach ($this->errors as $errmsg) {
3340 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
3341 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
3342 }
3343 $this->db->rollback();
3344 return -1 * $error;
3345 }
3346 } else {
3347 $error_str = 'Propal status do not meet requirement '.$this->status;
3348 dol_syslog(__METHOD__.$error_str, LOG_ERR);
3349 $this->error = $error_str;
3350 $this->errors[] = $this->error;
3351 return -2;
3352 }
3353 }
3354
3355 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3364 public function demand_reason($demand_reason_id, $notrigger = 0)
3365 {
3366 // phpcs:enable
3367 global $user;
3368
3369 if ($this->status >= self::STATUS_DRAFT) {
3370 $error = 0;
3371
3372 $this->db->begin();
3373
3374 $sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element;
3375 $sql .= ' SET fk_input_reason = '.((int) $demand_reason_id);
3376 $sql .= ' WHERE rowid='.((int) $this->id);
3377
3378 dol_syslog(__METHOD__.' demand_reason('.$demand_reason_id.')', LOG_DEBUG);
3379 $resql = $this->db->query($sql);
3380 if (!$resql) {
3381 $this->errors[] = $this->db->error();
3382 $error++;
3383 }
3384
3385 if (!$error) {
3386 $this->oldcopy = clone $this;
3387 $this->demand_reason_id = $demand_reason_id;
3388 }
3389
3390 if (!$notrigger && empty($error)) {
3391 // Call trigger
3392 $result = $this->call_trigger('PROPAL_MODIFY', $user);
3393 if ($result < 0) {
3394 $error++;
3395 }
3396 // End call triggers
3397 }
3398
3399 if (!$error) {
3400 $this->db->commit();
3401 return 1;
3402 } else {
3403 foreach ($this->errors as $errmsg) {
3404 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
3405 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
3406 }
3407 $this->db->rollback();
3408 return -1 * $error;
3409 }
3410 } else {
3411 $error_str = 'Propal status do not meet requirement '.$this->status;
3412 dol_syslog(__METHOD__.$error_str, LOG_ERR);
3413 $this->error = $error_str;
3414 $this->errors[] = $this->error;
3415 return -2;
3416 }
3417 }
3418
3419
3426 public function info($id)
3427 {
3428 $sql = "SELECT c.rowid, ";
3429 $sql .= " c.datec, c.date_valid as datev, c.date_signature, c.date_cloture,";
3430 $sql .= " c.fk_user_author, c.fk_user_valid, c.fk_user_signature, c.fk_user_cloture";
3431 $sql .= " FROM ".MAIN_DB_PREFIX.$this->table_element." as c";
3432 $sql .= " WHERE c.rowid = ".((int) $id);
3433
3434 $result = $this->db->query($sql);
3435
3436 if ($result) {
3437 if ($this->db->num_rows($result)) {
3438 $obj = $this->db->fetch_object($result);
3439
3440 $this->id = $obj->rowid;
3441
3442 $this->date_creation = $this->db->jdate($obj->datec);
3443 $this->date_validation = $this->db->jdate($obj->datev);
3444 $this->date_signature = $this->db->jdate($obj->date_signature);
3445 $this->date_cloture = $this->db->jdate($obj->date_cloture);
3446
3447 $this->user_creation_id = $obj->fk_user_author;
3448 $this->user_validation_id = $obj->fk_user_valid;
3449
3450 if ($obj->fk_user_signature) {
3451 $user_signature = new User($this->db);
3452 $user_signature->fetch($obj->fk_user_signature);
3453 $this->user_signature = $user_signature;
3454 }
3455
3456 $this->user_closing_id = $obj->fk_user_cloture;
3457 }
3458 $this->db->free($result);
3459 } else {
3460 dol_print_error($this->db);
3461 }
3462 }
3463
3464
3471 public function getLibStatut($mode = 0)
3472 {
3473 return $this->LibStatut($this->status, $mode);
3474 }
3475
3476 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3484 public function LibStatut($status, $mode = 1)
3485 {
3486 // phpcs:enable
3487 global $hookmanager;
3488
3489 // Init/load array of translation of status
3490 if (empty($this->labelStatus) || empty($this->labelStatusShort)) {
3491 global $langs;
3492 $langs->load("propal");
3493 $this->labelStatus[-1] = $langs->transnoentitiesnoconv("PropalStatusCanceled");
3494 $this->labelStatus[0] = $langs->transnoentitiesnoconv("PropalStatusDraft");
3495 $this->labelStatus[1] = $langs->transnoentitiesnoconv("PropalStatusValidated");
3496 $this->labelStatus[2] = $langs->transnoentitiesnoconv("PropalStatusSigned");
3497 $this->labelStatus[3] = $langs->transnoentitiesnoconv("PropalStatusNotSigned");
3498 $this->labelStatus[4] = $langs->transnoentitiesnoconv("PropalStatusBilled");
3499 $this->labelStatusShort[-1] = $langs->transnoentitiesnoconv("PropalStatusCanceledShort");
3500 $this->labelStatusShort[0] = $langs->transnoentitiesnoconv("PropalStatusDraftShort");
3501 $this->labelStatusShort[1] = $langs->transnoentitiesnoconv("PropalStatusValidatedShort");
3502 $this->labelStatusShort[2] = $langs->transnoentitiesnoconv("PropalStatusSignedShort");
3503 $this->labelStatusShort[3] = $langs->transnoentitiesnoconv("PropalStatusNotSignedShort");
3504 $this->labelStatusShort[4] = $langs->transnoentitiesnoconv("PropalStatusBilledShort");
3505 }
3506
3507 $statusType = '';
3508 if ($status == self::STATUS_CANCELED) {
3509 $statusType = 'status9';
3510 } elseif ($status == self::STATUS_DRAFT) {
3511 $statusType = 'status0';
3512 } elseif ($status == self::STATUS_VALIDATED) {
3513 $statusType = 'status1';
3514 } elseif ($status == self::STATUS_SIGNED) {
3515 $statusType = 'status4';
3516 } elseif ($status == self::STATUS_NOTSIGNED) {
3517 $statusType = 'status9';
3518 } elseif ($status == self::STATUS_BILLED) {
3519 $statusType = 'status6';
3520 }
3521
3522 $parameters = array('status' => $status, 'mode' => $mode);
3523 $reshook = $hookmanager->executeHooks('LibStatut', $parameters, $this); // Note that $action and $object may have been modified by hook
3524
3525 if ($reshook > 0) {
3526 return $hookmanager->resPrint;
3527 }
3528
3529 return dolGetStatus($this->labelStatus[$status], $this->labelStatusShort[$status], '', $statusType, $mode);
3530 }
3531
3532
3533 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3541 public function load_board($user, $mode)
3542 {
3543 // phpcs:enable
3544 global $langs, $hookmanager;
3545
3546 $now = dol_now();
3547 $delay_warning = 0;
3548 if ($mode == 'opened') {
3549 $delay_warning = getWarningDelay('propal', 'cloture');
3550 }
3551 if ($mode == 'signed') {
3552 $delay_warning = getWarningDelay('propal', 'facturation');
3553 }
3554
3555 // The count, the total and the number of late proposals are computed by the database instead of reading every proposal.
3556 // An open proposal is late when its validity end date is before now minus the warning delay (a proposal without validity
3557 // end date was counted as late, this is kept); nothing is late in the 'signed' mode.
3558 $sql = "SELECT COUNT(p.rowid) as nb, SUM(p.total_ht) as total,";
3559 if ($mode == 'opened') {
3560 $sql .= " SUM(CASE WHEN p.fin_validite IS NULL OR p.fin_validite < '".$this->db->idate($now - $delay_warning)."' THEN 1 ELSE 0 END) as nblate";
3561 } else {
3562 $sql .= " 0 as nblate";
3563 }
3564 if (empty($user->socid) && !$user->hasRight('societe', 'client', 'voir')) {
3565 $sql .= " FROM ".MAIN_DB_PREFIX.$this->table_element." as p";
3566 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe_commerciaux as sc ON p.fk_soc = sc.fk_soc";
3567 } else {
3568 $sql .= " FROM ".MAIN_DB_PREFIX."propal as p";
3569 }
3570 $sql .= " WHERE p.entity IN (".getEntity('propal').")";
3571 if ($mode == 'opened') {
3572 $sql .= " AND p.fk_statut = ".self::STATUS_VALIDATED;
3573 }
3574 if ($mode == 'signed') {
3575 $sql .= " AND p.fk_statut = ".self::STATUS_SIGNED;
3576 }
3577
3578 if (empty($user->socid) && !$user->hasRight('societe', 'client', 'voir')) {
3579 $sql .= " AND sc.fk_user = ".((int) $user->id);
3580 }
3581 if ($user->socid) {
3582 $sql .= " AND p.fk_soc = ".((int) $user->socid);
3583 }
3584
3585 // If the internal user must only see his customers, force searching by him
3586 $search_sale = 0;
3587 if (empty($user->socid) && !$user->hasRight('societe', 'client', 'voir')) {
3588 $search_sale = $user->id;
3589 }
3590 // Search on sale representative
3591 if ($search_sale && $search_sale != '-1') {
3592 if ($search_sale == -2) {
3593 $sql .= " AND NOT EXISTS (SELECT sc.fk_soc FROM ".MAIN_DB_PREFIX."societe_commerciaux as sc WHERE sc.fk_soc = p.fk_soc)";
3594 } elseif ($search_sale > 0) {
3595 $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).")";
3596 }
3597 }
3598 if ($user->socid) {
3599 $sql .= " AND p.fk_soc = ".((int) $user->socid);
3600 }
3601 // Add where from hooks
3602 $parameters = array('socid' => $user->socid);
3603 $reshook = $hookmanager->executeHooks('printFieldListWhere', $parameters, $this); // Note that $action and $object may have been modified by hook
3604 $sql .= $hookmanager->resPrint;
3605
3606 $resql = $this->db->query($sql);
3607 if ($resql) {
3608 $langs->load("propal");
3609
3610 $status = 0;
3611 $label = $labelShort = '';
3612 if ($mode == 'opened') {
3613 $status = self::STATUS_VALIDATED;
3614 $label = $langs->transnoentitiesnoconv("PropalsToClose");
3615 $labelShort = $langs->transnoentitiesnoconv("ToAcceptRefuse");
3616 }
3617 if ($mode == 'signed') {
3618 $status = self::STATUS_SIGNED;
3619 $label = $langs->trans("PropalsToBill"); // We set here bill but may be billed or ordered
3620 $labelShort = $langs->trans("ToBill");
3621 }
3622
3623 $response = new WorkboardResponse();
3624 $response->warning_delay = $delay_warning / 60 / 60 / 24;
3625 $response->label = $label;
3626 $response->labelShort = $labelShort;
3627 $response->url = dolBuildUrl(DOL_URL_ROOT.'/comm/propal/list.php', ['search_status' => $status, 'mainmenu' => 'commercial', 'leftmenu' => 'propals']);
3628 $response->url_late = dolBuildUrl(DOL_URL_ROOT.'/comm/propal/list.php', ['search_option' => 'late', 'mainmenu' => 'commercial', 'leftmenu' => 'propals', 'sortfield' => 'p.datep', 'sortorder' => 'asc']);
3629 $response->img = img_object('', "propal");
3630
3631 // This assignment in condition is not a bug. It allows walking the results.
3632 // TODO Definir regle des propales a facturer en retard (mode 'signed')
3633 $obj = $this->db->fetch_object($resql);
3634 if ($obj) {
3635 $response->nbtodo = (int) $obj->nb;
3636 $response->total = (float) $obj->total;
3637 $response->nbtodolate = (int) $obj->nblate;
3638 }
3639
3640 return $response;
3641 } else {
3642 $this->error = $this->db->error();
3643 return -1;
3644 }
3645 }
3646
3647
3656 public function initAsSpecimen($param = array())
3657 {
3658 global $conf, $langs;
3659
3660 // Load array of products prodids
3661 $num_prods = 0;
3662 $prodids = array();
3663 $sql = "SELECT rowid";
3664 $sql .= " FROM ".MAIN_DB_PREFIX."product";
3665 $sql .= " WHERE entity IN (".getEntity('product').")";
3666 if (array_key_exists('tosell', $param)) {
3667 $sql .= " AND tosell = ".((int) $param['tosell']);
3668 }
3669 $sql .= $this->db->plimit(100);
3670
3671 $resql = $this->db->query($sql);
3672 if ($resql) {
3673 $num_prods = $this->db->num_rows($resql);
3674 $i = 0;
3675 while ($i < $num_prods) {
3676 $i++;
3677 $row = $this->db->fetch_row($resql);
3678 $prodids[$i] = $row[0];
3679 }
3680 }
3681
3682 // Initialise parameters
3683 $this->id = 0;
3684 $this->ref = 'SPECIMEN';
3685 $this->ref_customer = 'NEMICEPS';
3686 $this->ref_client = 'NEMICEPS';
3687 $this->specimen = 1;
3688 $this->socid = 1;
3689 $this->date = dol_now();
3690 $this->fin_validite = $this->date + 3600 * 24 * 30;
3691 $this->cond_reglement_id = 1;
3692 $this->cond_reglement_code = 'RECEP';
3693 $this->mode_reglement_id = 7;
3694 $this->mode_reglement_code = 'CHQ';
3695 $this->availability_id = 1;
3696 $this->availability_code = 'AV_NOW';
3697 $this->demand_reason_id = 1;
3698 $this->demand_reason_code = 'SRC_00';
3699 $this->note_public = 'This is a comment (public)';
3700 $this->note_private = 'This is a comment (private)';
3701
3702 $this->multicurrency_tx = 1;
3703 $this->multicurrency_code = getDolCurrency();
3704
3705 // Lines
3706 $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)
3707 $xnbp = 0;
3708 while ($xnbp < $nbp) {
3709 $line = new PropaleLigne($this->db);
3710 $line->desc = $langs->trans("Description")." ".$xnbp;
3711 $line->qty = 1;
3712 $line->subprice = 100;
3713 $line->tva_tx = 20;
3714 $line->localtax1_tx = 0;
3715 $line->localtax2_tx = 0;
3716 if ($xnbp == 2) {
3717 $line->total_ht = 50;
3718 $line->total_ttc = 60;
3719 $line->total_tva = 10;
3720 $line->remise_percent = 50;
3721 } else {
3722 $line->total_ht = 100;
3723 $line->total_ttc = 120;
3724 $line->total_tva = 20;
3725 $line->remise_percent = 00;
3726 }
3727
3728 if ($num_prods > 0) {
3729 $prodid = mt_rand(1, $num_prods);
3730 $line->fk_product = $prodids[$prodid];
3731 $line->product_ref = 'SPECIMEN';
3732 }
3733
3734 $this->lines[$xnbp] = $line;
3735
3736 $this->total_ht += $line->total_ht;
3737 $this->total_tva += $line->total_tva;
3738 $this->total_ttc += $line->total_ttc;
3739
3740 $xnbp++;
3741 }
3742
3743 return 1;
3744 }
3745
3751 public function loadStateBoard()
3752 {
3753 global $user, $hookmanager;
3754
3755 $this->nb = array();
3756
3757 $sql = "SELECT count(p.rowid) as nb";
3758 $sql .= " FROM ".MAIN_DB_PREFIX.$this->table_element." as p";
3759 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe as s ON p.fk_soc = s.rowid";
3760 $sql .= " WHERE p.entity IN (".getEntity('propal').")";
3761
3762 // If the internal user must only see his customers, force searching by him
3763 $search_sale = 0;
3764 if (empty($user->socid) && !$user->hasRight('societe', 'client', 'voir')) {
3765 $search_sale = $user->id;
3766 }
3767 // Search on sale representative
3768 if ($search_sale && $search_sale != '-1') {
3769 if ($search_sale == -2) {
3770 $sql .= " AND NOT EXISTS (SELECT sc.fk_soc FROM ".MAIN_DB_PREFIX."societe_commerciaux as sc WHERE sc.fk_soc = p.fk_soc)";
3771 } elseif ($search_sale > 0) {
3772 $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).")";
3773 }
3774 }
3775
3776 // Add where from hooks
3777 $parameters = array();
3778 $hookmanager->executeHooks('printFieldListWhere', $parameters, $this); // Note that $action and $object may have been modified by hook
3779 $sql .= $hookmanager->resPrint;
3780 $resql = $this->db->query($sql);
3781 if ($resql) {
3782 // This assignment in condition is not a bug. It allows walking the results.
3783 while ($obj = $this->db->fetch_object($resql)) {
3784 $this->nb["proposals"] = $obj->nb;
3785 }
3786 $this->db->free($resql);
3787 return 1;
3788 } else {
3789 dol_print_error($this->db);
3790 $this->error = $this->db->error();
3791 return -1;
3792 }
3793 }
3794
3795
3803 public function getNextNumRef($soc)
3804 {
3805 global $conf, $langs;
3806 $langs->load("propal");
3807
3808 $classname = getDolGlobalString('PROPALE_ADDON');
3809
3810 if (!empty($classname)) {
3811 $mybool = false;
3812
3813 $file = $classname.".php";
3814
3815 // Include file with class
3816 $dirmodels = array_merge(array('/'), (array) $conf->modules_parts['models']);
3817 foreach ($dirmodels as $reldir) {
3818 $dir = dol_buildpath($reldir."core/modules/propale/");
3819
3820 // Load file with numbering class (if found)
3821 $mybool = ((bool) @include_once $dir.$file) || $mybool;
3822 }
3823
3824 if (!$mybool) {
3825 dol_print_error(null, "Failed to include file ".$file);
3826 return '';
3827 }
3828
3829 $obj = new $classname();
3830 '@phan-var-force ModeleNumRefPropales $obj';
3833 $numref = $obj->getNextValue($soc, $this);
3834
3835 if ($numref != "") {
3836 return $numref;
3837 } else {
3838 $this->error = $obj->error;
3839 //dol_print_error($db,"Propale::getNextNumRef ".$obj->error);
3840 return "";
3841 }
3842 } else {
3843 $langs->load("errors");
3844 print $langs->trans("Error")." ".$langs->trans("ErrorModuleSetupNotComplete", $langs->transnoentitiesnoconv("Proposal"));
3845 return "";
3846 }
3847 }
3848
3855 public function getTooltipContentArray($params)
3856 {
3857 global $conf, $langs, $user;
3858
3859 $langs->load('propal');
3860 $datas = [];
3861 $nofetch = !empty($params['nofetch']);
3862
3863 if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
3864 return ['optimize' => $langs->trans("Proposal")];
3865 }
3866 if ($user->hasRight('propal', 'lire')) {
3867 $datas['picto'] = img_picto('', $this->picto, '', 0, 0, 0, '', 'paddingrightonly').'<u>'.$langs->trans("Proposal").'</u>';
3868 if (isset($this->status)) {
3869 $datas['status'] = ' '.$this->getLibStatut(5);
3870 }
3871 if (!empty($this->ref)) {
3872 $datas['ref'] = '<br><b>'.$langs->trans('Ref').':</b> '.$this->ref;
3873 }
3874 if (!$nofetch) {
3875 $langs->load('companies');
3876 if (empty($this->thirdparty)) {
3877 $this->fetch_thirdparty();
3878 }
3879 $datas['customer'] = '<br><b>'.$langs->trans('Customer').':</b> '.$this->thirdparty->getNomUrl(1, '', 0, 1);
3880 }
3881 if (!empty($this->ref_customer)) {
3882 $datas['refcustomer'] = '<br><b>'.$langs->trans('RefCustomer').':</b> '.$this->ref_customer;
3883 }
3884 if (!$nofetch) {
3885 $langs->load('project');
3886 if (is_null($this->project) || (is_object($this->project) && empty($this->project->id))) {
3887 $res = $this->fetchProject();
3888 if ($res > 0 && $this->project instanceof Project) {
3889 $datas['project'] = '<br><b>'.$langs->trans('Project').':</b> '.$this->project->getNomUrl(1, '', 0, '1');
3890 }
3891 }
3892 }
3893 if (!empty($this->total_ht)) {
3894 $datas['amountht'] = '<br><b>'.$langs->trans('AmountHT').':</b> '.price($this->total_ht, 0, $langs, 0, -1, -1, $conf->currency);
3895 }
3896 if (!empty($this->total_tva)) {
3897 $datas['vat'] = '<br><b>'.$langs->trans('VAT').':</b> '.price($this->total_tva, 0, $langs, 0, -1, -1, $conf->currency);
3898 }
3899 if (!empty($this->total_ttc)) {
3900 $datas['amountttc'] = '<br><b>'.$langs->trans('AmountTTC').':</b> '.price($this->total_ttc, 0, $langs, 0, -1, -1, $conf->currency);
3901 }
3902 if (!empty($this->date)) {
3903 $datas['date'] = '<br><b>'.$langs->trans('Date').':</b> '.dol_print_date($this->date, 'day');
3904 }
3905 if (!empty($this->date_signature)) {
3906 $datas['datesignature'] = '<br><b>'.$langs->trans('DateSigning').':</b> '.dol_print_date($this->date_signature, 'day');
3907 }
3908 if (!empty($this->delivery_date)) {
3909 $datas['deliverydate'] = '<br><b>'.$langs->trans('DeliveryDate').':</b> '.dol_print_date($this->delivery_date, 'dayhour');
3910 }
3911 }
3912
3913 return $datas;
3914 }
3915
3927 public function getNomUrl($withpicto = 0, $option = '', $get_params = '', $notooltip = 0, $save_lastsearch_value = -1, $addlinktonotes = -1)
3928 {
3929 global $langs, $conf, $user, $hookmanager;
3930
3931 if (!empty($conf->dol_no_mouse_hover)) {
3932 $notooltip = 1; // Force disable tooltips
3933 }
3934
3935 $result = '';
3936 $params = [
3937 'id' => $this->id,
3938 'objecttype' => $this->element,
3939 'option' => $option,
3940 'nofetch' => 1,
3941 ];
3942 $classfortooltip = 'classfortooltip';
3943 $dataparams = '';
3944 if (getDolGlobalInt('MAIN_ENABLE_AJAX_TOOLTIP')) {
3945 $classfortooltip = 'classforajaxtooltip';
3946 $dataparams = ' data-params="'.dol_escape_htmltag(json_encode($params)).'"';
3947 $label = '';
3948 } else {
3949 $label = implode($this->getTooltipContentArray($params));
3950 }
3951
3952 $baseurl = '';
3953 $query = [];
3954 if ($user->hasRight('propal', 'lire')) {
3955 parse_str($get_params, $query);
3956 $query = array_merge($query, ['id' => $this->id]);
3957 if ($option == '') {
3958 $baseurl = DOL_URL_ROOT . '/comm/propal/card.php';
3959 } elseif ($option == 'compta') { // deprecated
3960 $baseurl = DOL_URL_ROOT . '/comm/propal/card.php';
3961 } elseif ($option == 'expedition') {
3962 $baseurl = DOL_URL_ROOT . '/expedition/propal.php';
3963 } elseif ($option == 'document') {
3964 $baseurl = DOL_URL_ROOT . '/comm/propal/document.php';
3965 }
3966
3967 if ($option != 'nolink') {
3968 // Add param to save lastsearch_values or not
3969 $add_save_lastsearch_values = ($save_lastsearch_value == 1 ? 1 : 0);
3970 if ($save_lastsearch_value == -1 && isset($_SERVER["PHP_SELF"]) && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) {
3971 $add_save_lastsearch_values = 1;
3972 }
3973 if ($add_save_lastsearch_values) {
3974 $query = array_merge($query, ['save_lastsearch_values' => 1]);
3975 }
3976 }
3977 }
3978 $url = dolBuildUrl($baseurl, $query);
3979
3980 $linkclose = '';
3981 if (empty($notooltip) && $user->hasRight('propal', 'lire')) {
3982 if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
3983 $label = $langs->trans("Proposal");
3984 $linkclose .= ' alt="'.dolPrintHTMLForAttribute($label).'"';
3985 }
3986 $linkclose .= ($label ? ' title="'.dolPrintHTMLForAttribute($label).'"' : ' title="tocomplete"');
3987 $linkclose .= $dataparams.' class="'.$classfortooltip.'"';
3988 }
3989
3990 $linkstart = '<a href="'.$url.'"';
3991 $linkstart .= $linkclose.'>';
3992 $linkend = '</a>';
3993
3994 $result .= $linkstart;
3995 if ($withpicto) {
3996 $result .= img_object(($notooltip ? '' : $label), ($this->picto ? $this->picto : 'generic'), (($withpicto != 2) ? 'class="paddingright"' : ''), 0, 0, $notooltip ? 0 : 1);
3997 }
3998 if ($withpicto != 2) {
3999 $result .= $this->ref;
4000 }
4001 $result .= $linkend;
4002
4003 if ($addlinktonotes >= 0) {
4004 $txttoshow = '';
4005
4006 if ($addlinktonotes == 0) {
4007 if (!empty($this->note_private) || !empty($this->note_public)) {
4008 $txttoshow = $langs->trans('ViewPrivateNote');
4009 }
4010 } elseif ($addlinktonotes == 1) {
4011 if (!empty($this->note_private)) {
4012 $txttoshow .= ($user->socid > 0 ? '' : dol_string_nohtmltag($this->note_private, 1));
4013 }
4014 } elseif ($addlinktonotes == 2) {
4015 if (!empty($this->note_public)) {
4016 $txttoshow .= dol_string_nohtmltag($this->note_public, 1);
4017 }
4018 } elseif ($addlinktonotes == 3) {
4019 if ($user->socid > 0) {
4020 if (!empty($this->note_public)) {
4021 $txttoshow .= dol_string_nohtmltag($this->note_public, 1);
4022 }
4023 } else {
4024 if (!empty($this->note_public)) {
4025 $txttoshow .= dol_string_nohtmltag($this->note_public, 1);
4026 }
4027 if (!empty($this->note_private)) {
4028 if (!empty($txttoshow)) {
4029 $txttoshow .= '<br><br>';
4030 }
4031 $txttoshow .= dol_string_nohtmltag($this->note_private, 1);
4032 }
4033 }
4034 }
4035
4036 if ($txttoshow) {
4037 $result .= ' <span class="note inline-block">';
4038 $result .= '<a href="'.dolBuildUrl(DOL_URL_ROOT.'/comm/propal/note.php', ['id' => $this->id]).'" class="classfortooltip" title="'.dol_escape_htmltag($txttoshow).'">';
4039 $result .= img_picto('', 'note');
4040 $result .= '</a>';
4041 $result .= '</span>';
4042 }
4043 }
4044
4045 global $action;
4046 $hookmanager->initHooks(array($this->element . 'dao'));
4047 $parameters = array('id' => $this->id, 'getnomurl' => &$result);
4048 $reshook = $hookmanager->executeHooks('getNomUrl', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
4049 if ($reshook > 0) {
4050 $result = $hookmanager->resPrint;
4051 } else {
4052 $result .= $hookmanager->resPrint;
4053 }
4054 return $result;
4055 }
4056
4063 public function getLinesArray($sqlforgedfilters = '')
4064 {
4065 return $this->fetch_lines(0, 0, $sqlforgedfilters);
4066 }
4067
4079 public function generateDocument($modele, $outputlangs, $hidedetails = 0, $hidedesc = 0, $hideref = 0, $moreparams = null)
4080 {
4081 $outputlangs->loadLangs(array("propale", "products"));
4082
4083 if (!dol_strlen($modele)) {
4084 $modele = getDolGlobalString('PROPALE_ADDON_PDF', 'cyan');
4085
4086 if ($this->model_pdf) {
4087 $modele = $this->model_pdf;
4088 }
4089 }
4090
4091 $modelpath = "core/modules/propale/doc/";
4092
4093 return $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams);
4094 }
4095
4104 public static function replaceThirdparty(DoliDB $dbs, $origin_id, $dest_id)
4105 {
4106 $tables = array(
4107 'propal'
4108 );
4109
4110 return CommonObject::commonReplaceThirdparty($dbs, $origin_id, $dest_id, $tables);
4111 }
4112
4121 public static function replaceProduct(DoliDB $db, $origin_id, $dest_id)
4122 {
4123 $tables = array(
4124 'propaldet'
4125 );
4126
4127 return CommonObject::commonReplaceProduct($db, $origin_id, $dest_id, $tables);
4128 }
4129
4137 public function getKanbanView($option = '', $arraydata = null)
4138 {
4139 global $langs;
4140
4141 $selected = (empty($arraydata['selected']) ? 0 : $arraydata['selected']);
4142
4143 $return = '<div class="box-flex-item box-flex-grow-zero">';
4144 $return .= '<div class="info-box info-box-sm">';
4145 $return .= '<div class="info-box-icon bg-infobox-action">';
4146 $return .= img_picto('', $this->picto);
4147 $return .= '</div>';
4148 $return .= '<div class="info-box-content">';
4149 $return .= '<span class="info-box-ref inline-block tdoverflowmax150 valignmiddle">' . $this->getNomUrl() . '</span>';
4150 if ($selected >= 0) {
4151 $return .= '<input id="cb'.$this->id.'" class="flat checkforselect fright" type="checkbox" name="toselect[]" value="'.$this->id.'"'.($selected ? ' checked="checked"' : '').'>';
4152 }
4153 if (!empty($arraydata['projectlink'])) {
4154 $return .= '<span class="info-box-ref"> | '.$arraydata['projectlink'].'</span>';
4155 }
4156 $return .= '<br>';
4157 if (is_object($this->thirdparty)) {
4158 $return .= '<div class="info-box-ref tdoverflowmax150">'.$this->thirdparty->getNomUrl(1).'</div>';
4159 }
4160 $return .= '<span class="info-box-label amount" title="'.$langs->trans("AmountHT").'">'.price($this->total_ht).'</span>';
4161 if (!empty($arraydata['authorlink'])) {
4162 $return .= ' &nbsp; <span class="info-box-label">'.$arraydata['authorlink'].'</span>';
4163 }
4164 $return .= '<br><div class="info-box-status">'.$this->getLibStatut(3).'</div>';
4165 $return .= '</div>';
4166 $return .= '</div>';
4167 $return .= '</div>';
4168 return $return;
4169 }
4170
4181 public function setCategories($categories)
4182 {
4183 require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php';
4184 return parent::setCategoriesCommon($categories, Categorie::TYPE_PROPOSAL);
4185 }
4186}
$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
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.
defineBuyPrice($unitPrice=0.0, $discountPercent=0.0, $fk_product=0, $qty=0)
Get buy price to use for margin calculation.
Class to manage absolute discounts.
Class to manage Dolibarr database access.
static getIdAndTxFromCode($dbs, $code, $date_document=0)
Get id and rate of currency from code.
static getIdFromCode($dbs, $code)
Get id of currency from code.
File of class to manage predefined price products or services by customer.
Class to manage products or services.
Class to manage projects.
Class to manage proposals.
getTooltipContentArray($params)
getTooltipContentArray
const STATUS_DRAFT
Draft status.
set_date($user, $date, $notrigger=0)
Define proposal date.
const STATUS_SIGNED
Signed quote.
getNomUrl($withpicto=0, $option='', $get_params='', $notooltip=0, $save_lastsearch_value=-1, $addlinktonotes=-1)
Return clickable link of object (with eventually picto)
getKanbanView($option='', $arraydata=null)
Return clickable link of object (with eventually picto)
InvoiceArrayList($id)
Returns an array with id and ref of related invoices.
availability($availability_id, $notrigger=0)
Change the delivery time.
const STATUS_NOTSIGNED
Not signed quote.
$table_ref_field
{}
setDeliveryDate($user, $delivery_date, $notrigger=0)
Set delivery date.
classifyBilled(User $user, $notrigger=0, $note='')
Classify the proposal to status Billed.
getLinesArray($sqlforgedfilters='')
Retrieve an array of proposal lines.
fetch($rowid, $ref='', $ref_ext='', $forceentity=0)
Load a proposal from database.
set_availability($user, $id, $notrigger=0)
Set delivery.
fetch_lines($only_product=0, $loadalsotranslation=0, $sqlforgedfilters='')
Load array lines.
updateline($rowid, $pu, $qty, $remise_percent, $txtva, $txlocaltax1=0.0, $txlocaltax2=0.0, $desc='', $price_base_type='HT', $info_bits=0, $special_code=0, $fk_parent_line=0, $skip_update_total=0, $fk_fournprice=0, $pa_ht=0, $label='', $type=0, $date_start='', $date_end='', $array_options=array(), $fk_unit=null, $pu_ht_devise=0, $notrigger=0, $rang=0)
Update a proposal line.
update(User $user, $notrigger=0)
Update database.
addline( $desc, $pu_ht, $qty, $txtva, $txlocaltax1=0.0, $txlocaltax2=0.0, $fk_product=0, $remise_percent=0.0, $price_base_type='HT', $pu_ttc=0.0, $info_bits=0, $type=0, $rang=-1, $special_code=0, $fk_parent_line=0, $fk_fournprice=0, $pa_ht=0, $label='', $date_start='', $date_end='', $array_options=array(), $fk_unit=null, $origin='', $origin_id=0, $pu_ht_devise=0, $fk_remise_except=0, $noupdateafterinsertline=0)
Add a proposal line into database (linked to product/service or not) The parameters are already suppo...
closeProposal($user, $status, $note_private='', $notrigger=0, $note_public='')
Close/set the commercial proposal to status signed or refused (fill also date signature)
demand_reason($demand_reason_id, $notrigger=0)
Change source demand.
info($id)
Object Proposal Information.
const STATUS_BILLED
Billed or processed quote.
initAsSpecimen($param=array())
Initialise an instance with random values.
static replaceThirdparty(DoliDB $dbs, $origin_id, $dest_id)
Function used to replace a thirdparty id with another one.
getLibStatut($mode=0)
Return label of status of proposal (draft, validated, ...)
const STATUS_CANCELED
Canceled status.
insert_discount($idremise)
Add a discount line into an proposal (as a proposal line) using an existing absolute discount (Consum...
LibStatut($status, $mode=1)
Return label of a status (draft, validated, ...)
valid($user, $notrigger=0)
Set status to validated.
static replaceProduct(DoliDB $db, $origin_id, $dest_id)
Function used to replace a product id with another one.
set_ref_client($user, $ref_client, $notrigger=0)
Set customer reference number.
setDraft($user, $notrigger=0)
Set draft status.
set_echeance($user, $date_end_validity, $notrigger=0)
Define end validity date.
set_demand_reason($user, $id, $notrigger=0)
Set source of demand.
setCategories($categories)
Sets object to supplied categories.
generateDocument($modele, $outputlangs, $hidedetails=0, $hidedesc=0, $hideref=0, $moreparams=null)
Create a document onto disk according to template module.
create($user, $notrigger=0)
Create commercial proposal into database this->ref can be set or empty.
liste_array($shortlist=0, $draft=0, $notcurrentuser=0, $socid=0, $limit=0, $offset=0, $sortfield='p.datep', $sortorder='DESC')
Return list of proposal (eventually filtered on user) into an array.
add_product($idproduct, $qty, $remise_percent=0)
Add line into array ->lines $this->thirdparty should be loaded.
reopen($user, $status, $note='', $notrigger=0)
Reopen the commercial proposal.
deleteLine($lineid, $id=0)
Delete detail line.
__construct($db, $socid=0, $propalid=0)
Constructor.
load_board($user, $mode)
Load indicators for dashboard (this->nbtodo and this->nbtodolate)
setCancel(User $user)
Cancel the proposal.
getInvoiceArrayList()
Returns an array with the numbers of related invoices.
set_date_livraison($user, $delivery_date, $notrigger=0)
Set delivery date.
const STATUS_VALIDATED
Validated status.
loadStateBoard()
Load the indicators this->nb for the state board.
createFromClone(User $user, $socid=0, $forceentity=null, $update_prices=false, $update_desc=false)
Load an object from its id and create a new one in database.
Class to manage commercial proposal lines.
Class to manage third parties objects (customers, suppliers, prospects...)
Class to manage translations.
Class to manage Dolibarr users.
print $langs trans("Ref").' m titre as m m statut as status
Or an array listing all the potential status of the object: array: int of the status => translated la...
Definition index.php: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:
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:65
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.
setEntity($currentobject)
Set entity id to use when to create an object.
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.
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', ...)
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_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)
Definition html.lib.php:181
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