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