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