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