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 && $line->fk_propal != $id) {
1142 $this->error = 'ErrorLineIDDoesNotMatchWithObjectID';
1143 return -1;
1144 }
1145
1146 // Memorize previous line for triggers
1147 $staticline = clone $line;
1148 $line->oldline = $staticline;
1149
1150 if ($line->delete($user) > 0) {
1151 $this->update_price(1);
1152
1153 $this->db->commit();
1154 return 1;
1155 } else {
1156 $this->error = $line->error;
1157 $this->errors = $line->errors;
1158 $this->db->rollback();
1159 return -1;
1160 }
1161 } else {
1162 $this->error = 'ErrorDeleteLineNotAllowedByObjectStatus';
1163 return -2;
1164 }
1165 }
1166
1167
1176 public function create($user, $notrigger = 0)
1177 {
1178 global $conf, $hookmanager, $mysoc;
1179 $error = 0;
1180
1181 $now = dol_now();
1182
1183 // Clean parameters
1184 if (empty($this->date)) {
1185 $this->date = $this->datep;
1186 }
1187 $this->fin_validite = $this->date + ($this->duree_validite * 24 * 3600);
1188 if (empty($this->availability_id)) {
1189 $this->availability_id = 0;
1190 }
1191 if (empty($this->demand_reason_id)) {
1192 $this->demand_reason_id = 0;
1193 }
1194
1195 // Multicurrency (test on $this->multicurrency_tx because we should take the default rate only if not using origin rate)
1196 if (!empty($this->multicurrency_code) && empty($this->multicurrency_tx)) {
1197 list($this->fk_multicurrency, $this->multicurrency_tx) = MultiCurrency::getIdAndTxFromCode($this->db, $this->multicurrency_code, $this->date);
1198 } else {
1199 $this->fk_multicurrency = MultiCurrency::getIdFromCode($this->db, $this->multicurrency_code);
1200 }
1201 if (empty($this->fk_multicurrency)) {
1202 $this->multicurrency_code = $conf->currency;
1203 $this->fk_multicurrency = 0;
1204 $this->multicurrency_tx = 1;
1205 }
1206 // setEntity will set entity with the right value if empty or change it for the right value if multicompany module is active
1207 $this->entity = setEntity($this);
1208
1209 // Set tmp vars
1210 $delivery_date = $this->delivery_date;
1211
1212 dol_syslog(get_class($this)."::create");
1213
1214 // Check parameters
1215 $result = $this->fetch_thirdparty();
1216 if ($result < 0) {
1217 $this->error = "Failed to fetch company";
1218 dol_syslog(get_class($this)."::create ".$this->error, LOG_ERR);
1219 return -3;
1220 }
1221
1222 // Check parameters
1223 if (!empty($this->ref)) { // We check that ref is not already used
1224 $result = self::isExistingObject($this->element, 0, $this->ref); // Check ref is not yet used
1225 if ($result > 0) {
1226 $this->error = 'ErrorRefAlreadyExists';
1227 dol_syslog(get_class($this)."::create ".$this->error, LOG_WARNING);
1228 $this->db->rollback();
1229 return -1;
1230 }
1231 }
1232
1233 if (empty($this->date)) {
1234 $this->error = "Date of proposal is required";
1235 dol_syslog(get_class($this)."::create ".$this->error, LOG_ERR);
1236 return -4;
1237 }
1238
1239
1240 $this->db->begin();
1241
1242 // Insert into database
1243 $sql = "INSERT INTO ".MAIN_DB_PREFIX.$this->table_element." (";
1244 $sql .= "fk_soc";
1245 $sql .= ", price";
1246 $sql .= ", total_tva";
1247 $sql .= ", total_ttc";
1248 $sql .= ", datep";
1249 $sql .= ", datec";
1250 $sql .= ", ref";
1251 $sql .= ", fk_user_author";
1252 $sql .= ", note_private";
1253 $sql .= ", note_public";
1254 $sql .= ", model_pdf";
1255 $sql .= ", fin_validite";
1256 $sql .= ", fk_cond_reglement";
1257 $sql .= ", deposit_percent";
1258 $sql .= ", fk_mode_reglement";
1259 $sql .= ", fk_account";
1260 $sql .= ", ref_client";
1261 $sql .= ", ref_ext";
1262 $sql .= ", date_livraison";
1263 $sql .= ", fk_shipping_method";
1264 $sql .= ", fk_warehouse";
1265 $sql .= ", fk_availability";
1266 $sql .= ", fk_input_reason";
1267 $sql .= ", fk_projet";
1268 $sql .= ", fk_incoterms";
1269 $sql .= ", location_incoterms";
1270 $sql .= ", entity";
1271 $sql .= ", fk_multicurrency";
1272 $sql .= ", multicurrency_code";
1273 $sql .= ", multicurrency_tx";
1274 $sql .= ") ";
1275 $sql .= " VALUES (";
1276 $sql .= $this->socid;
1277 $sql .= ", 0";
1278 $sql .= ", 0";
1279 $sql .= ", 0";
1280 $sql .= ", '".$this->db->idate($this->date)."'";
1281 $sql .= ", '".$this->db->idate($now)."'";
1282 $sql .= ", '(PROV)'";
1283 $sql .= ", ".($user->id > 0 ? ((int) $user->id) : "NULL");
1284 $sql .= ", '".$this->db->escape($this->note_private)."'";
1285 $sql .= ", '".$this->db->escape($this->note_public)."'";
1286 $sql .= ", '".$this->db->escape($this->model_pdf)."'";
1287 $sql .= ", ".($this->fin_validite != '' ? "'".$this->db->idate($this->fin_validite)."'" : "NULL");
1288 $sql .= ", ".($this->cond_reglement_id > 0 ? ((int) $this->cond_reglement_id) : 'NULL');
1289 $sql .= ", ".(!empty($this->deposit_percent) ? "'".$this->db->escape($this->deposit_percent)."'" : 'NULL');
1290 $sql .= ", ".($this->mode_reglement_id > 0 ? ((int) $this->mode_reglement_id) : 'NULL');
1291 $sql .= ", ".($this->fk_account > 0 ? ((int) $this->fk_account) : 'NULL');
1292 $sql .= ", '".$this->db->escape($this->ref_client)."'";
1293 $sql .= ", '".$this->db->escape($this->ref_ext)."'";
1294 $sql .= ", ".(!isDolTms($delivery_date) ? "NULL" : "'".$this->db->idate($delivery_date)."'");
1295 $sql .= ", ".($this->shipping_method_id > 0 ? $this->shipping_method_id : 'NULL');
1296 $sql .= ", ".($this->warehouse_id > 0 ? $this->warehouse_id : 'NULL');
1297 $sql .= ", ".((int) $this->availability_id);
1298 $sql .= ", ".((int) $this->demand_reason_id);
1299 $sql .= ", ".($this->fk_project ? $this->fk_project : "null");
1300 $sql .= ", ".(int) $this->fk_incoterms;
1301 $sql .= ", '".$this->db->escape($this->location_incoterms)."'";
1302 $sql .= ", ".(int) $this->entity;
1303 $sql .= ", ".(int) $this->fk_multicurrency;
1304 $sql .= ", '".$this->db->escape($this->multicurrency_code)."'";
1305 $sql .= ", ".(float) $this->multicurrency_tx;
1306 $sql .= ")";
1307
1308 dol_syslog(get_class($this)."::create", LOG_DEBUG);
1309 $resql = $this->db->query($sql);
1310 if ($resql) {
1311 $this->id = $this->db->last_insert_id(MAIN_DB_PREFIX.$this->table_element);
1312
1313 if ($this->id) {
1314 $this->ref = '(PROV'.$this->id.')';
1315 $sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element." SET ref='".$this->db->escape($this->ref)."' WHERE rowid=".((int) $this->id);
1316
1317 dol_syslog(get_class($this)."::create", LOG_DEBUG);
1318 $resql = $this->db->query($sql);
1319 if (!$resql) {
1320 $error++;
1321 }
1322
1323 if (!empty($this->linkedObjectsIds) && empty($this->linked_objects)) { // To use new linkedObjectsIds instead of old linked_objects
1324 $this->linked_objects = $this->linkedObjectsIds; // TODO Replace linked_objects with linkedObjectsIds
1325 }
1326
1327 // Add object linked
1328 if (!$error && $this->id && !empty($this->linked_objects) && is_array($this->linked_objects)) {
1329 foreach ($this->linked_objects as $origin => $tmp_origin_id) {
1330 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, ...))
1331 foreach ($tmp_origin_id as $origin_id) {
1332 $ret = $this->add_object_linked($origin, $origin_id);
1333 if (!$ret) {
1334 $this->error = $this->db->lasterror();
1335 $error++;
1336 }
1337 }
1338 } else { // Old behaviour, if linked_object has only one link per type, so is something like array('contract'=>id1))
1339 $origin_id = $tmp_origin_id;
1340 $ret = $this->add_object_linked($origin, $origin_id);
1341 if (!$ret) {
1342 $this->error = $this->db->lasterror();
1343 $error++;
1344 }
1345 }
1346 }
1347 }
1348
1349 /*
1350 * Insertion du detail des produits dans la base
1351 * Insert products detail in database
1352 */
1353 if (!$error) {
1354 $fk_parent_line = 0;
1355 $num = count($this->lines);
1356
1357 for ($i = 0; $i < $num; $i++) {
1358 if (!is_object($this->lines[$i])) { // If this->lines is not array of objects, coming from REST API
1359 // Convert into object this->lines[$i].
1360 $line = (object) $this->lines[$i];
1361 } else {
1362 $line = $this->lines[$i];
1363 }
1364 // Reset fk_parent_line for line that are not child lines or special product
1365 if (($line->product_type != 9 && empty($line->fk_parent_line)) || $line->product_type == 9) {
1366 $fk_parent_line = 0;
1367 }
1368 // Complete vat rate with code
1369 $vatrate = $line->tva_tx;
1370 if ($line->vat_src_code && !preg_match('/\‍(.*\‍)/', $vatrate)) {
1371 $vatrate .= ' ('.$line->vat_src_code.')';
1372 }
1373
1374 if (getDolGlobalString('MAIN_CREATEFROM_KEEP_LINE_ORIGIN_INFORMATION')) {
1375 $originid = $line->origin_id;
1376 $origintype = $line->origin;
1377 } else {
1378 $originid = $line->id;
1379 $origintype = $this->element;
1380 }
1381
1382 $result = $this->addline(
1383 $line->desc,
1384 $line->subprice,
1385 $line->qty,
1386 $vatrate,
1387 $line->localtax1_tx,
1388 $line->localtax2_tx,
1389 $line->fk_product,
1390 $line->remise_percent,
1391 'HT',
1392 0,
1393 $line->info_bits,
1394 $line->product_type,
1395 $line->rang,
1396 $line->special_code,
1397 $fk_parent_line,
1398 $line->fk_fournprice,
1399 $line->pa_ht,
1400 $line->label,
1401 $line->date_start,
1402 $line->date_end,
1403 $line->array_options,
1404 $line->fk_unit,
1405 (string) $origintype,
1406 $originid,
1407 0,
1408 0,
1409 1
1410 );
1411
1412 if ($result < 0) {
1413 $error++;
1414 $this->error = $this->db->error;
1415 dol_print_error($this->db);
1416 break;
1417 }
1418
1419 // Set the id on created row
1420 $line->id = $result;
1421
1422 // Defined the new fk_parent_line
1423 if ($result > 0 && $line->product_type == 9) {
1424 $fk_parent_line = $result;
1425 }
1426 }
1427 }
1428
1429 // Set delivery address
1430 /*if (! $error && $this->fk_delivery_address)
1431 {
1432 $sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element;
1433 $sql.= " SET fk_delivery_address = ".((int) $this->fk_delivery_address);
1434 $sql.= " WHERE ref = '".$this->db->escape($this->ref)."'";
1435 $sql.= " AND entity = ".setEntity($this);
1436
1437 $result=$this->db->query($sql);
1438 }*/
1439
1440 if (!$error) {
1441 // Mise a jour infos denormalisees
1442 $resql = $this->update_price(1, 'auto', 0, $mysoc);
1443 if ($resql) {
1444 $action = 'update';
1445
1446 // Actions on extra fields
1447 if (!$error) {
1448 $result = $this->insertExtraFields();
1449 if ($result < 0) {
1450 $error++;
1451 }
1452 }
1453
1454 if (!$error && !$notrigger) {
1455 // Call trigger
1456 $result = $this->call_trigger('PROPAL_CREATE', $user);
1457 if ($result < 0) {
1458 $error++;
1459 }
1460 // End call triggers
1461 }
1462 } else {
1463 $this->error = $this->db->lasterror();
1464 $error++;
1465 }
1466 }
1467 } else {
1468 $this->error = $this->db->lasterror();
1469 $error++;
1470 }
1471
1472 if (!$error) {
1473 $this->db->commit();
1474 dol_syslog(get_class($this)."::create done id=".$this->id);
1475 return $this->id;
1476 } else {
1477 $this->db->rollback();
1478 return -2;
1479 }
1480 } else {
1481 $this->error = $this->db->lasterror();
1482 $this->db->rollback();
1483 return -1;
1484 }
1485 }
1486
1497 public function createFromClone(User $user, $socid = 0, $forceentity = null, $update_prices = false, $update_desc = false)
1498 {
1499 global $conf, $hookmanager, $mysoc;
1500
1501 dol_include_once('/projet/class/project.class.php');
1502
1503 $error = 0;
1504 $now = dol_now();
1505
1506 dol_syslog(__METHOD__, LOG_DEBUG);
1507
1508 $object = new self($this->db);
1509
1510 $this->db->begin();
1511
1512 // Load source object
1513 $object->fetch($this->id);
1514
1515 $objsoc = new Societe($this->db);
1516
1517 // Change socid if needed
1518 if (!empty($socid) && $socid != $object->socid) {
1519 if ($objsoc->fetch($socid) > 0) {
1520 $object->socid = $objsoc->id;
1521 $object->cond_reglement_id = (!empty($objsoc->cond_reglement_id) ? $objsoc->cond_reglement_id : 0);
1522 $object->deposit_percent = (!empty($objsoc->deposit_percent) ? $objsoc->deposit_percent : null);
1523 $object->mode_reglement_id = (!empty($objsoc->mode_reglement_id) ? $objsoc->mode_reglement_id : 0);
1524 $object->fk_delivery_address = 0;
1525
1526 /*if (isModEnabled('project'))
1527 {
1528 $project = new Project($db);
1529 if ($this->fk_project > 0 && $project->fetch($this->fk_project)) {
1530 if ($project->socid <= 0) $clonedObj->fk_project = $this->fk_project;
1531 else $clonedObj->fk_project = '';
1532 } else {
1533 $clonedObj->fk_project = '';
1534 }
1535 }*/
1536 $object->fk_project = 0; // A cloned proposal is set by default to no project.
1537 }
1538
1539 // reset ref_client
1540 if (!getDolGlobalString('MAIN_KEEP_REF_CUSTOMER_ON_CLONING')) {
1541 $object->ref_client = '';
1542 $object->ref_customer = '';
1543 }
1544
1545 // TODO Change product price if multi-prices
1546 } else {
1547 $objsoc->fetch($object->socid);
1548 }
1549
1550 // update prices
1551 if ($update_prices === true || $update_desc === true) {
1552 if ($objsoc->id > 0 && !empty($object->lines)) {
1553 if ($update_prices === true && getDolGlobalString('PRODUIT_CUSTOMER_PRICES')) {
1554 // If price per customer
1555 require_once DOL_DOCUMENT_ROOT . '/product/class/productcustomerprice.class.php';
1556 }
1557
1558 foreach ($object->lines as $line) {
1559 $line->id = 0;
1560
1561 if ($line->fk_product > 0) {
1562 $prod = new Product($this->db);
1563 $res = $prod->fetch($line->fk_product);
1564 if ($res > 0) {
1565 if ($update_prices === true) {
1566 $pu_ht = $prod->price;
1567 $tva_tx = (string) get_default_tva($mysoc, $objsoc, $prod->id);
1568 $remise_percent = $objsoc->remise_percent;
1569
1570 if (getDolGlobalString('PRODUIT_MULTIPRICES') && $objsoc->price_level > 0) {
1571 $pu_ht = $prod->multiprices[$objsoc->price_level];
1572 if (getDolGlobalString('PRODUIT_MULTIPRICES_USE_VAT_PER_LEVEL')) { // using this option is a bug. kept for backward compatibility
1573 if (isset($prod->multiprices_tva_tx[$objsoc->price_level])) {
1574 $tva_tx = (string) $prod->multiprices_tva_tx[$objsoc->price_level];
1575 }
1576 }
1577 } elseif (getDolGlobalString('PRODUIT_CUSTOMER_PRICES')) {
1578 $prodcustprice = new ProductCustomerPrice($this->db);
1579 $filter = array('t.fk_product' => (string) $prod->id, 't.fk_soc' => (string) $objsoc->id);
1580 $result = $prodcustprice->fetchAll('', '', 0, 0, $filter);
1581 if ($result) {
1582 // If there is some prices specific to the customer
1583 if (count($prodcustprice->lines) > 0) {
1584 $date_now = (int) floor(dol_now() / 86400) * 86400; // date without hours
1585 foreach ($prodcustprice->lines as $k => $custprice_line) {
1586 if ($custprice_line->date_begin <= $date_now && (empty($custprice_line->date_end) || $date_now <= $custprice_line->date_end)) {
1587 $pu_ht = price($custprice_line->price);
1588 $tva_tx = ($custprice_line->default_vat_code ? $custprice_line->tva_tx . ' (' . $custprice_line->default_vat_code . ' )' : $custprice_line->tva_tx);
1589 if ($custprice_line->default_vat_code && !preg_match('/\‍(.*\‍)/', $tva_tx)) {
1590 $tva_tx .= ' (' . $custprice_line->default_vat_code . ')';
1591 }
1592 $remise_percent = $custprice_line->discount_percent;
1593 break;
1594 }
1595 }
1596 }
1597 }
1598 }
1599
1600 $line->subprice = $pu_ht;
1601 $line->tva_tx = $tva_tx;
1602 $line->remise_percent = $remise_percent;
1603 }
1604 if ($update_desc === true) {
1605 $line->desc = $prod->description;
1606 }
1607 }
1608 }
1609 }
1610 }
1611 }
1612
1613 $object->id = 0;
1614 $object->ref = '';
1615 $object->entity = (!empty($forceentity) ? $forceentity : $object->entity);
1616 $object->statut = self::STATUS_DRAFT;
1617 $object->status = self::STATUS_DRAFT;
1618
1619 // Clear fields
1620 $object->user_creation_id = $user->id;
1621 $object->user_validation_id = 0;
1622 $object->date = $now;
1623 $object->datep = $now; // deprecated
1624 $object->fin_validite = $object->date + ($object->duree_validite * 24 * 3600);
1625 if (!getDolGlobalString('MAIN_KEEP_REF_CUSTOMER_ON_CLONING')) {
1626 $object->ref_client = '';
1627 $object->ref_customer = '';
1628 }
1629 if (getDolGlobalInt('MAIN_DONT_KEEP_NOTE_ON_CLONING') == 1) {
1630 $object->note_private = '';
1631 $object->note_public = '';
1632 }
1633 // Create clone
1634 $object->context['createfromclone'] = 'createfromclone';
1635 $result = $object->create($user);
1636 if ($result < 0) {
1637 $this->error = $object->error;
1638 $this->errors = array_merge($this->errors, $object->errors);
1639 $error++;
1640 }
1641
1642 if (!$error && !getDolGlobalInt('MAIN_IGNORE_CONTACTS_ON_CLONING')) {
1643 // copy internal contacts
1644 if ($object->copy_linked_contact($this, 'internal') < 0) {
1645 $error++;
1646 }
1647 }
1648
1649 if (!$error) {
1650 // copy external contacts if same company
1651 if ($this->socid == $object->socid) {
1652 if ($object->copy_linked_contact($this, 'external') < 0) {
1653 $error++;
1654 }
1655 }
1656 }
1657
1658 if (!$error) {
1659 // Hook of thirdparty module
1660 if (is_object($hookmanager)) {
1661 $parameters = array('objFrom' => $this, 'clonedObj' => $object);
1662 $action = '';
1663 $reshook = $hookmanager->executeHooks('createFrom', $parameters, $object, $action); // Note that $action and $object may have been modified by some hooks
1664 if ($reshook < 0) {
1665 $this->setErrorsFromObject($hookmanager);
1666 $error++;
1667 }
1668 }
1669 }
1670
1671 unset($object->context['createfromclone']);
1672
1673 // End
1674 if (!$error) {
1675 $this->db->commit();
1676 return $object->id;
1677 } else {
1678 $this->db->rollback();
1679 return -1;
1680 }
1681 }
1682
1692 public function fetch($rowid, $ref = '', $ref_ext = '', $forceentity = 0)
1693 {
1694 $sql = "SELECT p.rowid, p.ref, p.entity, p.fk_soc";
1695 $sql .= ", p.total_ttc, p.total_tva, p.localtax1, p.localtax2, p.total_ht";
1696 $sql .= ", p.datec";
1697 $sql .= ", p.date_signature as dates";
1698 $sql .= ", p.date_valid as datev";
1699 $sql .= ", p.datep as dp";
1700 $sql .= ", p.fin_validite as dfv";
1701 $sql .= ", p.date_livraison as delivery_date";
1702 $sql .= ", p.model_pdf, p.last_main_doc, p.ref_client, ref_ext, p.extraparams";
1703 $sql .= ", p.note_private, p.note_public";
1704 $sql .= ", p.fk_projet as fk_project, p.fk_statut";
1705 $sql .= ", p.fk_user_author, p.fk_user_valid, p.fk_user_cloture";
1706 $sql .= ", p.fk_delivery_address";
1707 $sql .= ", p.fk_availability";
1708 $sql .= ", p.fk_input_reason";
1709 $sql .= ", p.fk_cond_reglement";
1710 $sql .= ", p.fk_mode_reglement";
1711 $sql .= ', p.fk_account';
1712 $sql .= ", p.fk_shipping_method";
1713 $sql .= ", p.fk_warehouse";
1714 $sql .= ", p.fk_incoterms, p.location_incoterms";
1715 $sql .= ", p.fk_multicurrency, p.multicurrency_code, p.multicurrency_tx, p.multicurrency_total_ht, p.multicurrency_total_tva, p.multicurrency_total_ttc";
1716 $sql .= ", p.tms as date_modification";
1717 $sql .= ", i.libelle as label_incoterms";
1718 $sql .= ", c.label as statut_label";
1719 $sql .= ", ca.code as availability_code, ca.label as availability";
1720 $sql .= ", dr.code as demand_reason_code, dr.label as demand_reason";
1721 $sql .= ", cr.code as cond_reglement_code, cr.libelle as cond_reglement, cr.libelle_facture as cond_reglement_libelle_doc, p.deposit_percent";
1722 $sql .= ", cp.code as mode_reglement_code, cp.libelle as mode_reglement";
1723 $sql .= " FROM ".MAIN_DB_PREFIX.$this->table_element." as p";
1724 $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_propalst as c ON p.fk_statut = c.id';
1725 $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_paiement as cp ON p.fk_mode_reglement = cp.id AND cp.entity IN ('.getEntity('c_paiement').')';
1726 $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').')';
1727 $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_availability as ca ON p.fk_availability = ca.rowid';
1728 $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_input_reason as dr ON p.fk_input_reason = dr.rowid';
1729 $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_incoterms as i ON p.fk_incoterms = i.rowid';
1730
1731 if (!empty($ref)) {
1732 if (!empty($forceentity)) {
1733 $sql .= " WHERE p.entity = ".(int) $forceentity; // Check only the current entity because we may have the same reference in several entities
1734 } else {
1735 $sql .= " WHERE p.entity IN (".getEntity('propal').")";
1736 }
1737 $sql .= " AND p.ref='".$this->db->escape($ref)."'";
1738 } else {
1739 // Don't use entity if you use rowid
1740 $sql .= " WHERE p.rowid = ".((int) $rowid);
1741 }
1742
1743 dol_syslog(get_class($this)."::fetch", LOG_DEBUG);
1744 $resql = $this->db->query($sql);
1745 if ($resql) {
1746 if ($this->db->num_rows($resql)) {
1747 $obj = $this->db->fetch_object($resql);
1748
1749 $this->id = $obj->rowid;
1750 $this->entity = $obj->entity;
1751
1752 $this->ref = $obj->ref;
1753 $this->ref_client = $obj->ref_client;
1754 $this->ref_customer = $obj->ref_client;
1755 $this->ref_ext = $obj->ref_ext;
1756
1757 $this->total = $obj->total_ttc; // TODO deprecated
1758 $this->total_ttc = $obj->total_ttc;
1759 $this->total_ht = $obj->total_ht;
1760 $this->total_tva = $obj->total_tva;
1761 $this->total_localtax1 = $obj->localtax1;
1762 $this->total_localtax2 = $obj->localtax2;
1763
1764 $this->socid = $obj->fk_soc;
1765 $this->thirdparty = null; // Clear if another value was already set by fetch_thirdparty
1766
1767 $this->fk_project = $obj->fk_project;
1768 $this->project = null; // Clear if another value was already set by fetchProject
1769
1770 $this->model_pdf = $obj->model_pdf;
1771 $this->last_main_doc = $obj->last_main_doc;
1772 $this->note = $obj->note_private; // TODO deprecated
1773 $this->note_private = $obj->note_private;
1774 $this->note_public = $obj->note_public;
1775
1776 $this->status = (int) $obj->fk_statut;
1777 $this->statut = $this->status; // deprecated
1778
1779 $this->datec = $this->db->jdate($obj->datec); // TODO deprecated
1780 $this->datev = $this->db->jdate($obj->datev); // TODO deprecated
1781 $this->date_creation = $this->db->jdate($obj->datec); //Creation date
1782 $this->date_validation = $this->db->jdate($obj->datev); //Validation date
1783 $this->date_modification = $this->db->jdate($obj->date_modification); // tms
1784 $this->date_signature = $this->db->jdate($obj->dates); // Signature date
1785 $this->date = $this->db->jdate($obj->dp); // Proposal date
1786 $this->datep = $this->db->jdate($obj->dp); // deprecated
1787 $this->fin_validite = $this->db->jdate($obj->dfv);
1788 $this->delivery_date = $this->db->jdate($obj->delivery_date);
1789 $this->shipping_method_id = ($obj->fk_shipping_method > 0) ? $obj->fk_shipping_method : null;
1790 $this->warehouse_id = ($obj->fk_warehouse > 0) ? $obj->fk_warehouse : null;
1791 $this->availability_id = $obj->fk_availability;
1792 $this->availability_code = $obj->availability_code;
1793 $this->availability = $obj->availability;
1794 $this->demand_reason_id = $obj->fk_input_reason;
1795 $this->demand_reason_code = $obj->demand_reason_code;
1796 $this->demand_reason = $obj->demand_reason;
1797 $this->fk_address = $obj->fk_delivery_address;
1798
1799 $this->mode_reglement_id = $obj->fk_mode_reglement;
1800 $this->mode_reglement_code = $obj->mode_reglement_code;
1801 $this->mode_reglement = $obj->mode_reglement;
1802 $this->fk_account = ($obj->fk_account > 0) ? $obj->fk_account : null;
1803 $this->cond_reglement_id = $obj->fk_cond_reglement;
1804 $this->cond_reglement_code = $obj->cond_reglement_code;
1805 $this->cond_reglement = $obj->cond_reglement;
1806 $this->cond_reglement_doc = $obj->cond_reglement_libelle_doc;
1807 $this->deposit_percent = $obj->deposit_percent;
1808
1809 $this->extraparams = !empty($obj->extraparams) ? (array) json_decode($obj->extraparams, true) : array();
1810
1811 $this->user_author_id = $obj->fk_user_author;
1812 $this->user_validation_id = $obj->fk_user_valid;
1813 $this->user_closing_id = $obj->fk_user_cloture;
1814
1815 //Incoterms
1816 $this->fk_incoterms = $obj->fk_incoterms;
1817 $this->location_incoterms = $obj->location_incoterms;
1818 $this->label_incoterms = $obj->label_incoterms;
1819
1820 // Multicurrency
1821 $this->fk_multicurrency = $obj->fk_multicurrency;
1822 $this->multicurrency_code = $obj->multicurrency_code;
1823 $this->multicurrency_tx = $obj->multicurrency_tx;
1824 $this->multicurrency_total_ht = $obj->multicurrency_total_ht;
1825 $this->multicurrency_total_tva = $obj->multicurrency_total_tva;
1826 $this->multicurrency_total_ttc = $obj->multicurrency_total_ttc;
1827
1828 // Retrieve all extrafield
1829 // fetch optionals attributes and labels
1830 $this->fetch_optionals();
1831
1832 $this->db->free($resql);
1833
1834 $this->lines = array();
1835
1836 // Lines
1837 $result = $this->fetch_lines();
1838 if ($result < 0) {
1839 return -3;
1840 }
1841
1842 return 1;
1843 }
1844
1845 $this->error = "Record Not Found";
1846 return 0;
1847 } else {
1848 $this->error = $this->db->lasterror();
1849 return -1;
1850 }
1851 }
1852
1860 public function update(User $user, $notrigger = 0)
1861 {
1862 global $conf;
1863
1864 $error = 0;
1865
1866 // Clean parameters
1867 if (isset($this->ref)) {
1868 $this->ref = trim($this->ref);
1869 }
1870 if (isset($this->ref_client)) {
1871 $this->ref_client = trim($this->ref_client);
1872 }
1873 if (isset($this->note) || isset($this->note_private)) {
1874 $this->note_private = (isset($this->note_private) ? trim($this->note_private) : trim($this->note));
1875 }
1876 if (isset($this->note_public)) {
1877 $this->note_public = trim($this->note_public);
1878 }
1879 if (isset($this->model_pdf)) {
1880 $this->model_pdf = trim($this->model_pdf);
1881 }
1882 if (isset($this->import_key)) {
1883 $this->import_key = trim($this->import_key);
1884 }
1885 if (!empty($this->duree_validite) && is_numeric($this->duree_validite)) {
1886 $this->fin_validite = $this->date + ($this->duree_validite * 24 * 3600);
1887 }
1888
1889 // Check parameters
1890 // Put here code to add control on parameters values
1891
1892 // Update request
1893 $sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element." SET";
1894 $sql = "UPDATE ".MAIN_DB_PREFIX."propal SET";
1895 $sql .= " ref = ".(isset($this->ref) ? "'".$this->db->escape($this->ref)."'" : "null").",";
1896 $sql .= " ref_client = ".(isset($this->ref_client) ? "'".$this->db->escape($this->ref_client)."'" : "null").",";
1897 $sql .= " ref_ext = ".(isset($this->ref_ext) ? "'".$this->db->escape($this->ref_ext)."'" : "null").",";
1898 $sql .= " fk_soc = ".(!empty($this->socid) ? (int) $this->socid : "null").",";
1899 $sql .= " datep = ".(strval($this->date) != '' ? "'".$this->db->idate($this->date)."'" : 'null').",";
1900 if (!empty($this->fin_validite)) {
1901 $sql .= " fin_validite = ".(strval($this->fin_validite) != '' ? "'".$this->db->idate($this->fin_validite)."'" : 'null').",";
1902 }
1903 $sql .= " date_valid = ".(strval($this->date_validation) != '' ? "'".$this->db->idate($this->date_validation)."'" : 'null').",";
1904 $sql .= " total_tva = ".(isset($this->total_tva) ? (float) $this->total_tva : "null").",";
1905 $sql .= " localtax1 = ".(isset($this->total_localtax1) ? (float) $this->total_localtax1 : "null").",";
1906 $sql .= " localtax2 = ".(isset($this->total_localtax2) ? (float) $this->total_localtax2 : "null").",";
1907 $sql .= " total_ht = ".(isset($this->total_ht) ? (float) $this->total_ht : "null").",";
1908 $sql .= " total_ttc = ".(isset($this->total_ttc) ? (float) $this->total_ttc : "null").",";
1909 $sql .= " fk_statut = ".(isset($this->status) ? (int) $this->status : "null").",";
1910 $sql .= " fk_user_author = ".(!empty($this->user_author_id) ? (int) $this->user_author_id : "null").",";
1911 $sql .= " fk_user_valid = ".(!empty($this->user_validation_id) ? (int) $this->user_validation_id : "null").",";
1912
1913 $sql .= " fk_projet = ".(!empty($this->fk_project) ? (int) $this->fk_project : "null").",";
1914 $sql .= " fk_cond_reglement = ".(!empty($this->cond_reglement_id) ? (int) $this->cond_reglement_id : "null").",";
1915 $sql .= " deposit_percent = ".(!empty($this->deposit_percent) ? "'".$this->db->escape($this->deposit_percent)."'" : "null").",";
1916 $sql .= " fk_mode_reglement = ".(!empty($this->mode_reglement_id) ? (int) $this->mode_reglement_id : "null").",";
1917 $sql .= " fk_input_reason = ".(!empty($this->demand_reason_id) ? (int) $this->demand_reason_id : "null").",";
1918 $sql .= " fk_shipping_method=".(isset($this->shipping_method_id) ? (int) $this->shipping_method_id : "null").",";
1919 $sql .= " fk_availability=".(isset($this->availability_id) ? (int) $this->availability_id : "null").",";
1920 $sql .= " note_private = ".(isset($this->note_private) ? "'".$this->db->escape($this->note_private)."'" : "null").",";
1921 $sql .= " note_public = ".(isset($this->note_public) ? "'".$this->db->escape($this->note_public)."'" : "null").",";
1922 $sql .= " model_pdf = ".(isset($this->model_pdf) ? "'".$this->db->escape($this->model_pdf)."'" : "null").",";
1923 $sql .= " import_key = ".(isset($this->import_key) ? "'".$this->db->escape($this->import_key)."'" : "null");
1924 $sql .= " WHERE rowid = ".((int) $this->id);
1925
1926 $this->db->begin();
1927
1928 dol_syslog(get_class($this)."::update", LOG_DEBUG);
1929 $resql = $this->db->query($sql);
1930 if (!$resql) {
1931 $error++;
1932 $this->errors[] = "Error ".$this->db->lasterror();
1933 }
1934
1935 if (!$error) {
1936 $result = $this->insertExtraFields();
1937 if ($result < 0) {
1938 $error++;
1939 }
1940 }
1941
1942 if (!$error && !$notrigger) {
1943 // Call trigger
1944 $result = $this->call_trigger('PROPAL_MODIFY', $user);
1945 if ($result < 0) {
1946 $error++;
1947 }
1948 // End call triggers
1949 }
1950
1951 // Commit or rollback
1952 if ($error) {
1953 foreach ($this->errors as $errmsg) {
1954 dol_syslog(get_class($this)."::update ".$errmsg, LOG_ERR);
1955 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
1956 }
1957 $this->db->rollback();
1958 return -1 * $error;
1959 } else {
1960 $this->db->commit();
1961 return 1;
1962 }
1963 }
1964
1965
1966 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1975 public function fetch_lines($only_product = 0, $loadalsotranslation = 0, $sqlforgedfilters = '')
1976 {
1977 // phpcs:enable
1978 $this->lines = array();
1979
1980 $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,';
1981 $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,';
1982 $sql .= ' d.fk_unit,';
1983 $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,';
1984 $sql .= ' p.weight, p.weight_units, p.volume, p.volume_units,';
1985 $sql .= ' d.date_start, d.date_end, d.extraparams,';
1986 $sql .= ' d.fk_multicurrency, d.multicurrency_code, d.multicurrency_subprice, d.multicurrency_total_ht, d.multicurrency_total_tva, d.multicurrency_total_ttc';
1987 $sql .= ' FROM '.MAIN_DB_PREFIX.$this->table_element_line.' as d';
1988 $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'product as p ON (d.fk_product = p.rowid)';
1989 $sql .= ' WHERE d.fk_propal = '.((int) $this->id);
1990 if ($only_product) {
1991 $sql .= ' AND p.fk_product_type = 0';
1992 }
1993 if ($sqlforgedfilters) {
1994 $sql .= $sqlforgedfilters;
1995 }
1996 $sql .= ' ORDER by d.rang';
1997
1998 dol_syslog(get_class($this)."::fetch_lines", LOG_DEBUG);
1999 $result = $this->db->query($sql);
2000 if ($result) {
2001 require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php';
2002
2003 $num = $this->db->num_rows($result);
2004
2005 $i = 0;
2006 while ($i < $num) {
2007 $objp = $this->db->fetch_object($result);
2008
2009 $line = new PropaleLigne($this->db);
2010
2011 $line->rowid = $objp->rowid; //Deprecated
2012 $line->id = $objp->rowid;
2013 $line->fk_propal = $objp->fk_propal;
2014 $line->fk_parent_line = $objp->fk_parent_line;
2015 $line->product_type = $objp->product_type;
2016 $line->label = $objp->custom_label;
2017 $line->desc = $objp->description; // Description ligne
2018 $line->description = $objp->description; // Description ligne
2019 $line->qty = $objp->qty;
2020 $line->vat_src_code = $objp->vat_src_code;
2021 $line->tva_tx = $objp->tva_tx;
2022 $line->localtax1_tx = $objp->localtax1_tx;
2023 $line->localtax2_tx = $objp->localtax2_tx;
2024 $line->localtax1_type = $objp->localtax1_type;
2025 $line->localtax2_type = $objp->localtax2_type;
2026 $line->subprice = $objp->subprice;
2027 $line->fk_remise_except = $objp->fk_remise_except;
2028 $line->remise_percent = $objp->remise_percent;
2029 $line->price = $objp->price; // TODO deprecated
2030
2031 $line->info_bits = $objp->info_bits;
2032 $line->total_ht = $objp->total_ht;
2033 $line->total_tva = $objp->total_tva;
2034 $line->total_localtax1 = $objp->total_localtax1;
2035 $line->total_localtax2 = $objp->total_localtax2;
2036 $line->total_ttc = $objp->total_ttc;
2037 $line->fk_fournprice = $objp->fk_fournprice;
2038 $marginInfos = getMarginInfos($objp->subprice, $objp->remise_percent, $objp->tva_tx, $objp->localtax1_tx, $objp->localtax2_tx, $line->fk_fournprice, $objp->pa_ht);
2039 $line->pa_ht = $marginInfos[0];
2040 $line->marge_tx = $marginInfos[1];
2041 $line->marque_tx = $marginInfos[2];
2042 $line->special_code = $objp->special_code;
2043 $line->rang = $objp->rang;
2044
2045 $line->fk_product = $objp->fk_product;
2046
2047 $line->ref = $objp->product_ref; // deprecated
2048 $line->libelle = $objp->product_label; // deprecated
2049
2050 $line->product_ref = $objp->product_ref;
2051 $line->product_label = $objp->product_label;
2052 $line->product_desc = $objp->product_desc; // Description produit
2053 $line->product_tobatch = $objp->product_tobatch;
2054 $line->product_barcode = $objp->product_barcode;
2055
2056 $line->fk_product_type = $objp->fk_product_type; // deprecated
2057 $line->fk_unit = $objp->fk_unit;
2058 $line->weight = $objp->weight;
2059 $line->weight_units = $objp->weight_units;
2060 $line->volume = $objp->volume;
2061 $line->volume_units = $objp->volume_units;
2062
2063 $line->date_start = $this->db->jdate($objp->date_start);
2064 $line->date_end = $this->db->jdate($objp->date_end);
2065
2066 $line->extraparams = !empty($objp->extraparams) ? (array) json_decode($objp->extraparams, true) : array();
2067
2068 // Multicurrency
2069 $line->fk_multicurrency = $objp->fk_multicurrency;
2070 $line->multicurrency_code = $objp->multicurrency_code;
2071 $line->multicurrency_subprice = $objp->multicurrency_subprice;
2072 $line->multicurrency_total_ht = $objp->multicurrency_total_ht;
2073 $line->multicurrency_total_tva = $objp->multicurrency_total_tva;
2074 $line->multicurrency_total_ttc = $objp->multicurrency_total_ttc;
2075
2076 $line->fetch_optionals();
2077
2078 // multilangs
2079 if (getDolGlobalInt('MAIN_MULTILANGS') && !empty($objp->fk_product) && !empty($loadalsotranslation)) {
2080 $tmpproduct = new Product($this->db);
2081 $tmpproduct->fetch($objp->fk_product);
2082 $tmpproduct->getMultiLangs();
2083
2084 $line->multilangs = $tmpproduct->multilangs;
2085 }
2086
2087 $this->lines[$i] = $line;
2088
2089 $i++;
2090 }
2091
2092 $this->db->free($result);
2093
2094 return $num;
2095 } else {
2096 $this->error = $this->db->lasterror();
2097 return -3;
2098 }
2099 }
2100
2108 public function valid($user, $notrigger = 0)
2109 {
2110 global $conf;
2111
2112 require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
2113
2114 $error = 0;
2115
2116 // Protection
2117 if ($this->status == self::STATUS_VALIDATED) {
2118 dol_syslog(get_class($this)."::valid action abandoned: already validated", LOG_WARNING);
2119 return 0;
2120 }
2121
2122 if (!((!getDolGlobalString('MAIN_USE_ADVANCED_PERMS') && $user->hasRight('propal', 'creer'))
2123 || (getDolGlobalString('MAIN_USE_ADVANCED_PERMS') && $user->hasRight('propal', 'propal_advance', 'validate')))) {
2124 $this->error = 'ErrorPermissionDenied';
2125 dol_syslog(get_class($this)."::valid ".$this->error, LOG_ERR);
2126 return -1;
2127 }
2128
2129 $now = dol_now();
2130
2131 $this->db->begin();
2132
2133 // Numbering module definition
2134 $soc = new Societe($this->db);
2135 $soc->fetch($this->socid);
2136
2137 // Define new ref
2138 if (!$error && (preg_match('/^[\‍(]?PROV/i', $this->ref) || empty($this->ref))) { // empty should not happened, but when it occurs, the test save life
2139 $num = $this->getNextNumRef($soc);
2140 } else {
2141 $num = $this->ref;
2142 }
2143 $this->newref = dol_sanitizeFileName($num);
2144
2145 $sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element;
2146 $sql .= " SET ref = '".$this->db->escape($num)."',";
2147 $sql .= " fk_statut = ".self::STATUS_VALIDATED.", date_valid='".$this->db->idate($now)."', fk_user_valid=".((int) $user->id);
2148 $sql .= " WHERE rowid = ".((int) $this->id)." AND fk_statut = ".self::STATUS_DRAFT;
2149
2150 dol_syslog(get_class($this)."::valid", LOG_DEBUG);
2151 $resql = $this->db->query($sql);
2152 if (!$resql) {
2153 dol_print_error($this->db);
2154 $error++;
2155 }
2156
2157 // Trigger calls
2158 if (!$error && !$notrigger) {
2159 // Call trigger
2160 $result = $this->call_trigger('PROPAL_VALIDATE', $user);
2161 if ($result < 0) {
2162 $error++;
2163 }
2164 // End call triggers
2165 }
2166
2167 if (!$error) {
2168 $this->oldref = $this->ref;
2169
2170 // Rename directory if dir was a temporary ref
2171 if (preg_match('/^[\‍(]?PROV/i', $this->ref)) {
2172 // Now we rename also files into index
2173 $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)."'";
2174 $sql .= " WHERE filename LIKE '".$this->db->escape($this->ref)."%' AND filepath = 'propale/".$this->db->escape($this->ref)."' and entity = ".((int) $conf->entity);
2175 $resql = $this->db->query($sql);
2176 if (!$resql) {
2177 $error++;
2178 $this->error = $this->db->lasterror();
2179 }
2180 $sql = 'UPDATE '.MAIN_DB_PREFIX."ecm_files set filepath = 'propale/".$this->db->escape($this->newref)."'";
2181 $sql .= " WHERE filepath = 'propale/".$this->db->escape($this->ref)."' and entity = ".$conf->entity;
2182 $resql = $this->db->query($sql);
2183 if (!$resql) {
2184 $error++;
2185 $this->error = $this->db->lasterror();
2186 }
2187
2188 // We rename directory ($this->ref = old ref, $num = new ref) in order not to lose the attachments
2189 $oldref = dol_sanitizeFileName($this->ref);
2190 $newref = dol_sanitizeFileName($num);
2191 $dirsource = $conf->propal->multidir_output[$this->entity].'/'.$oldref;
2192 $dirdest = $conf->propal->multidir_output[$this->entity].'/'.$newref;
2193 if (!$error && file_exists($dirsource)) {
2194 dol_syslog(get_class($this)."::validate rename dir ".$dirsource." into ".$dirdest);
2195 if (@rename($dirsource, $dirdest)) {
2196 dol_syslog("Rename ok");
2197 // Rename docs starting with $oldref with $newref
2198 $listoffiles = dol_dir_list($dirdest, 'files', 1, '^'.preg_quote($oldref, '/'));
2199 foreach ($listoffiles as $fileentry) {
2200 $dirsource = $fileentry['name'];
2201 $dirdest = preg_replace('/^'.preg_quote($oldref, '/').'/', $newref, $dirsource);
2202 $dirsource = $fileentry['path'].'/'.$dirsource;
2203 $dirdest = $fileentry['path'].'/'.$dirdest;
2204 @rename($dirsource, $dirdest);
2205 }
2206 }
2207 }
2208 }
2209
2210 $this->ref = $num;
2211 $this->statut = self::STATUS_VALIDATED;
2212 $this->user_validation_id = $user->id;
2213 $this->datev = $now;
2214 $this->date_validation = $now;
2215
2216 $this->db->commit();
2217 return 1;
2218 } else {
2219 $this->db->rollback();
2220 return -1;
2221 }
2222 }
2223
2224
2225 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2234 public function set_date($user, $date, $notrigger = 0)
2235 {
2236 // phpcs:enable
2237 if (empty($date)) {
2238 $this->error = 'ErrorBadParameter';
2239 dol_syslog(get_class($this)."::set_date ".$this->error, LOG_ERR);
2240 return -1;
2241 }
2242
2243 if ($user->hasRight('propal', 'creer')) {
2244 $error = 0;
2245
2246 $this->db->begin();
2247
2248 $sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element;
2249 $sql .= " SET datep = '".$this->db->idate($date)."'";
2250 $sql .= " WHERE rowid = ".((int) $this->id);
2251
2252 dol_syslog(__METHOD__, LOG_DEBUG);
2253 $resql = $this->db->query($sql);
2254 if (!$resql) {
2255 $this->errors[] = $this->db->error();
2256 $error++;
2257 }
2258
2259 if (!$error) {
2260 $this->oldcopy = clone $this;
2261 $this->date = $date;
2262 $this->datep = $date; // deprecated
2263 }
2264
2265 if (!$notrigger && empty($error)) {
2266 // Call trigger
2267 $result = $this->call_trigger('PROPAL_MODIFY', $user);
2268 if ($result < 0) {
2269 $error++;
2270 }
2271 // End call triggers
2272 }
2273
2274 if (!$error) {
2275 $this->db->commit();
2276 return 1;
2277 } else {
2278 foreach ($this->errors as $errmsg) {
2279 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2280 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2281 }
2282 $this->db->rollback();
2283 return -1 * $error;
2284 }
2285 }
2286
2287 return -1;
2288 }
2289
2290 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2299 public function set_echeance($user, $date_end_validity, $notrigger = 0)
2300 {
2301 // phpcs:enable
2302 if ($user->hasRight('propal', 'creer')) {
2303 $error = 0;
2304
2305 $this->db->begin();
2306
2307 $sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element;
2308 $sql .= " SET fin_validite = ".($date_end_validity != '' ? "'".$this->db->idate($date_end_validity)."'" : 'null');
2309 $sql .= " WHERE rowid = ".((int) $this->id);
2310
2311 dol_syslog(__METHOD__, LOG_DEBUG);
2312
2313 $resql = $this->db->query($sql);
2314 if (!$resql) {
2315 $this->errors[] = $this->db->error();
2316 $error++;
2317 }
2318
2319
2320 if (!$error) {
2321 $this->oldcopy = clone $this;
2322 $this->fin_validite = $date_end_validity;
2323 }
2324
2325 if (!$notrigger && empty($error)) {
2326 // Call trigger
2327 $result = $this->call_trigger('PROPAL_MODIFY', $user);
2328 if ($result < 0) {
2329 $error++;
2330 }
2331 // End call triggers
2332 }
2333
2334 if (!$error) {
2335 $this->db->commit();
2336 return 1;
2337 } else {
2338 foreach ($this->errors as $errmsg) {
2339 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2340 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2341 }
2342 $this->db->rollback();
2343 return -1 * $error;
2344 }
2345 }
2346
2347 return -1;
2348 }
2349
2350 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2360 public function set_date_livraison($user, $delivery_date, $notrigger = 0)
2361 {
2362 // phpcs:enable
2363 return $this->setDeliveryDate($user, $delivery_date, $notrigger);
2364 }
2365
2374 public function setDeliveryDate($user, $delivery_date, $notrigger = 0)
2375 {
2376 if ($user->hasRight('propal', 'creer')) {
2377 $error = 0;
2378
2379 $this->db->begin();
2380
2381 $sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element;
2382 $sql .= " SET date_livraison = ".(isDolTms($delivery_date) ? "'".$this->db->idate($delivery_date)."'" : 'null');
2383 $sql .= " WHERE rowid = ".((int) $this->id);
2384
2385 dol_syslog(__METHOD__, LOG_DEBUG);
2386 $resql = $this->db->query($sql);
2387 if (!$resql) {
2388 $this->errors[] = $this->db->error();
2389 $error++;
2390 }
2391
2392 if (!$error) {
2393 $this->oldcopy = clone $this;
2394 $this->delivery_date = $delivery_date;
2395 }
2396
2397 if (!$notrigger && empty($error)) {
2398 // Call trigger
2399 $result = $this->call_trigger('PROPAL_MODIFY', $user);
2400 if ($result < 0) {
2401 $error++;
2402 }
2403 // End call triggers
2404 }
2405
2406 if (!$error) {
2407 $this->db->commit();
2408 return 1;
2409 } else {
2410 foreach ($this->errors as $errmsg) {
2411 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2412 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2413 }
2414 $this->db->rollback();
2415 return -1 * $error;
2416 }
2417 }
2418
2419 return -1;
2420 }
2421
2422 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2431 public function set_availability($user, $id, $notrigger = 0)
2432 {
2433 // phpcs:enable
2434 if ($user->hasRight('propal', 'creer') && $this->status >= self::STATUS_DRAFT) {
2435 $error = 0;
2436
2437 $this->db->begin();
2438
2439 $sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element;
2440 $sql .= " SET fk_availability = ".((int) $id);
2441 $sql .= " WHERE rowid = ".((int) $this->id);
2442
2443 dol_syslog(__METHOD__.' availability('.$id.')', LOG_DEBUG);
2444 $resql = $this->db->query($sql);
2445 if (!$resql) {
2446 $this->errors[] = $this->db->error();
2447 $error++;
2448 }
2449
2450 if (!$error) {
2451 $this->oldcopy = clone $this;
2452 $this->fk_availability = $id;
2453 $this->availability_id = $id;
2454 }
2455
2456 if (!$notrigger && empty($error)) {
2457 // Call trigger
2458 $result = $this->call_trigger('PROPAL_MODIFY', $user);
2459 if ($result < 0) {
2460 $error++;
2461 }
2462 // End call triggers
2463 }
2464
2465 if (!$error) {
2466 $this->db->commit();
2467 return 1;
2468 } else {
2469 foreach ($this->errors as $errmsg) {
2470 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2471 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2472 }
2473 $this->db->rollback();
2474 return -1 * $error;
2475 }
2476 } else {
2477 $error_str = 'Propal status do not meet requirement '.$this->status;
2478 dol_syslog(__METHOD__.$error_str, LOG_ERR);
2479 $this->error = $error_str;
2480 $this->errors[] = $this->error;
2481 return -2;
2482 }
2483 }
2484
2485 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2494 public function set_demand_reason($user, $id, $notrigger = 0)
2495 {
2496 // phpcs:enable
2497 if ($user->hasRight('propal', 'creer') && $this->status >= self::STATUS_DRAFT) {
2498 $error = 0;
2499
2500 $this->db->begin();
2501
2502 $sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element;
2503 $sql .= " SET fk_input_reason = ".((int) $id);
2504 $sql .= " WHERE rowid = ".((int) $this->id);
2505
2506 dol_syslog(__METHOD__, LOG_DEBUG);
2507 $resql = $this->db->query($sql);
2508 if (!$resql) {
2509 $this->errors[] = $this->db->error();
2510 $error++;
2511 }
2512
2513
2514 if (!$error) {
2515 $this->oldcopy = clone $this;
2516
2517 $this->demand_reason_id = $id;
2518 }
2519
2520
2521 if (!$notrigger && empty($error)) {
2522 // Call trigger
2523 $result = $this->call_trigger('PROPAL_MODIFY', $user);
2524 if ($result < 0) {
2525 $error++;
2526 }
2527 // End call triggers
2528 }
2529
2530 if (!$error) {
2531 $this->db->commit();
2532 return 1;
2533 } else {
2534 foreach ($this->errors as $errmsg) {
2535 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2536 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2537 }
2538 $this->db->rollback();
2539 return -1 * $error;
2540 }
2541 } else {
2542 $error_str = 'Propal status do not meet requirement '.$this->status;
2543 dol_syslog(__METHOD__.$error_str, LOG_ERR);
2544 $this->error = $error_str;
2545 $this->errors[] = $this->error;
2546 return -2;
2547 }
2548 }
2549
2550 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2559 public function set_ref_client($user, $ref_client, $notrigger = 0)
2560 {
2561 // phpcs:enable
2562 if ($user->hasRight('propal', 'creer')) {
2563 $error = 0;
2564
2565 $this->db->begin();
2566
2567 $sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element;
2568 $sql .= " SET ref_client = ".(empty($ref_client) ? 'NULL' : "'".$this->db->escape($ref_client)."'");
2569 $sql .= " WHERE rowid = ".((int) $this->id);
2570
2571 dol_syslog(__METHOD__.' $this->id='.$this->id.', ref_client='.$ref_client, LOG_DEBUG);
2572 $resql = $this->db->query($sql);
2573 if (!$resql) {
2574 $this->errors[] = $this->db->error();
2575 $error++;
2576 }
2577
2578 if (!$error) {
2579 $this->oldcopy = clone $this;
2580 $this->ref_client = $ref_client;
2581 }
2582
2583 if (!$notrigger && empty($error)) {
2584 // Call trigger
2585 $result = $this->call_trigger('PROPAL_MODIFY', $user);
2586 if ($result < 0) {
2587 $error++;
2588 }
2589 // End call triggers
2590 }
2591
2592 if (!$error) {
2593 $this->db->commit();
2594 return 1;
2595 } else {
2596 foreach ($this->errors as $errmsg) {
2597 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2598 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2599 }
2600 $this->db->rollback();
2601 return -1 * $error;
2602 }
2603 }
2604
2605 return -1;
2606 }
2607
2608
2618 public function reopen($user, $status, $note = '', $notrigger = 0)
2619 {
2620 $error = 0;
2621
2622 $sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element;
2623 $sql .= " SET fk_statut = ".((int) $status).",";
2624 if (!empty($note)) {
2625 $sql .= " note_private = '".$this->db->escape($note)."',";
2626 }
2627 $sql .= " date_cloture=NULL, fk_user_cloture=NULL";
2628 $sql .= " WHERE rowid = ".((int) $this->id);
2629
2630 $this->db->begin();
2631
2632 dol_syslog(get_class($this)."::reopen", LOG_DEBUG);
2633 $resql = $this->db->query($sql);
2634 if (!$resql) {
2635 $error++;
2636 $this->errors[] = "Error ".$this->db->lasterror();
2637 }
2638 if (!$error) {
2639 if (!$notrigger) {
2640 // Call trigger
2641 $result = $this->call_trigger('PROPAL_REOPEN', $user);
2642 if ($result < 0) {
2643 $error++;
2644 }
2645 // End call triggers
2646 }
2647 }
2648
2649 // Commit or rollback
2650 if ($error) {
2651 if (!empty($this->errors)) {
2652 foreach ($this->errors as $errmsg) {
2653 dol_syslog(get_class($this)."::update ".$errmsg, LOG_ERR);
2654 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2655 }
2656 }
2657 $this->db->rollback();
2658 return -1 * $error;
2659 } else {
2660 $this->statut = $status;
2661 $this->status = $status;
2662
2663 $this->db->commit();
2664 return 1;
2665 }
2666 }
2667
2678 public function closeProposal($user, $status, $note_private = '', $notrigger = 0, $note_public = '')
2679 {
2680 global $langs,$conf;
2681
2682 $error = 0;
2683 $now = dol_now();
2684
2685 $this->db->begin();
2686
2687 $newprivatenote = dol_concatdesc($this->note_private, $note_private);
2688 $newpublicnote = dol_concatdesc($this->note_public, $note_public);
2689
2690 if (!getDolGlobalString('PROPALE_KEEP_OLD_SIGNATURE_INFO')) {
2691 $date_signature = $now;
2692 $fk_user_signature = $user->id;
2693 } else {
2694 $this->info($this->id);
2695 if (!isset($this->date_signature) || $this->date_signature == '') {
2696 $date_signature = $now;
2697 $fk_user_signature = $user->id;
2698 } else {
2699 $date_signature = $this->date_signature;
2700 $fk_user_signature = $this->user_signature->id;
2701 }
2702 }
2703
2704 $sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element;
2705 $sql .= " SET fk_statut = ".((int) $status).", note_private = '".$this->db->escape($newprivatenote)."', note_public = '".$this->db->escape($newpublicnote)."'";
2706 if ($status == self::STATUS_SIGNED) {
2707 $sql .= ", date_signature='".$this->db->idate($now)."', fk_user_signature = ".($fk_user_signature);
2708 }
2709 $sql .= " WHERE rowid = ".((int) $this->id);
2710
2711 $resql = $this->db->query($sql);
2712 if ($resql) {
2713 // Status self::STATUS_REFUSED by default
2714 $modelpdf = getDolGlobalString('PROPALE_ADDON_PDF_ODT_CLOSED', $this->model_pdf);
2715 $triggerName = 'PROPAL_CLOSE_REFUSED'; // used later in call_trigger()
2716
2717 if ($status == self::STATUS_SIGNED) { // Status self::STATUS_SIGNED
2718 $triggerName = 'PROPAL_CLOSE_SIGNED'; // used later in call_trigger()
2719 $modelpdf = getDolGlobalString('PROPALE_ADDON_PDF_ODT_TOBILL', $this->model_pdf);
2720
2721 // The connected company is classified as a client
2722 $soc = new Societe($this->db);
2723 $soc->id = $this->socid;
2724 $result = $soc->setAsCustomer();
2725
2726 if ($result < 0) {
2727 $this->error = $this->db->lasterror();
2728 $this->db->rollback();
2729 return -2;
2730 }
2731 }
2732
2733 if (!getDolGlobalString('MAIN_DISABLE_PDF_AUTOUPDATE') && !getDolGlobalInt('PROPAL_DISABLE_AUTOUPDATE_ON_CLOSE')) {
2734 // Define output language
2735 $outputlangs = $langs;
2736 if (getDolGlobalInt('MAIN_MULTILANGS')) {
2737 $outputlangs = new Translate("", $conf);
2738 $newlang = (GETPOST('lang_id', 'aZ09') ? GETPOST('lang_id', 'aZ09') : $this->thirdparty->default_lang);
2739 $outputlangs->setDefaultLang($newlang);
2740 }
2741
2742 // PDF
2743 $hidedetails = (getDolGlobalString('MAIN_GENERATE_DOCUMENTS_HIDE_DETAILS') ? 1 : 0);
2744 $hidedesc = (getDolGlobalString('MAIN_GENERATE_DOCUMENTS_HIDE_DESC') ? 1 : 0);
2745 $hideref = (getDolGlobalString('MAIN_GENERATE_DOCUMENTS_HIDE_REF') ? 1 : 0);
2746
2747 //$ret=$object->fetch($id); // Reload to get new records
2748 $this->generateDocument($modelpdf, $outputlangs, $hidedetails, $hidedesc, $hideref);
2749 }
2750
2751 if (!$error) {
2752 $this->oldcopy = clone $this;
2753 $this->statut = $status; // deprecated
2754 $this->status = $status;
2755 $this->date_signature = $date_signature;
2756 $this->note_private = $newprivatenote;
2757 }
2758
2759 if (!$notrigger && empty($error)) {
2760 // Call trigger
2761 $result = $this->call_trigger($triggerName, $user);
2762 if ($result < 0) {
2763 $error++;
2764 }
2765 // End call triggers
2766 }
2767
2768 if (!$error) {
2769 $this->db->commit();
2770 return 1;
2771 } else {
2772 foreach ($this->errors as $errmsg) {
2773 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2774 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2775 }
2776 if (!empty($this->oldcopy)) {
2777 $this->statut = $this->oldcopy->status; // deprecated
2778 $this->status = $this->oldcopy->status;
2779 $this->date_signature = $this->oldcopy->date_signature;
2780 $this->note_private = $this->oldcopy->note_private;
2781 }
2782
2783 $this->db->rollback();
2784 return -1;
2785 }
2786 } else {
2787 $this->error = $this->db->lasterror();
2788 $this->db->rollback();
2789 return -1;
2790 }
2791 }
2792
2801 public function classifyBilled(User $user, $notrigger = 0, $note = '')
2802 {
2803 global $conf, $langs;
2804
2805 $error = 0;
2806
2807 $now = dol_now();
2808 $num = 0;
2809
2810 $triggerName = 'PROPAL_CLASSIFY_BILLED';
2811
2812 $this->db->begin();
2813
2814 $newprivatenote = dol_concatdesc($this->note_private, $note);
2815
2816 $sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element;
2817 $sql .= " SET fk_statut = ".self::STATUS_BILLED.", ";
2818 $sql .= " note_private = '".$this->db->escape($newprivatenote)."', date_cloture='".$this->db->idate($now)."', fk_user_cloture=".((int) $user->id);
2819 $sql .= ' WHERE rowid = '.((int) $this->id).' AND fk_statut = '.((int) self::STATUS_SIGNED);
2820
2821 dol_syslog(__METHOD__, LOG_DEBUG);
2822 $resql = $this->db->query($sql);
2823 if (!$resql) {
2824 $this->errors[] = $this->db->error();
2825 $error++;
2826 } else {
2827 $num = $this->db->affected_rows($resql);
2828 }
2829
2830 if (!$error) {
2831 $modelpdf = getDolGlobalString('PROPALE_ADDON_PDF_ODT_CLOSED', $this->model_pdf);
2832
2833 if (!getDolGlobalString('MAIN_DISABLE_PDF_AUTOUPDATE')) {
2834 // Define output language
2835 $outputlangs = $langs;
2836 if (getDolGlobalInt('MAIN_MULTILANGS')) {
2837 $outputlangs = new Translate("", $conf);
2838 $newlang = (GETPOST('lang_id', 'aZ09') ? GETPOST('lang_id', 'aZ09') : $this->thirdparty->default_lang);
2839 $outputlangs->setDefaultLang($newlang);
2840 }
2841
2842 // PDF
2843 $hidedetails = (getDolGlobalString('MAIN_GENERATE_DOCUMENTS_HIDE_DETAILS') ? 1 : 0);
2844 $hidedesc = (getDolGlobalString('MAIN_GENERATE_DOCUMENTS_HIDE_DESC') ? 1 : 0);
2845 $hideref = (getDolGlobalString('MAIN_GENERATE_DOCUMENTS_HIDE_REF') ? 1 : 0);
2846
2847 //$ret=$object->fetch($id); // Reload to get new records
2848 $this->generateDocument($modelpdf, $outputlangs, $hidedetails, $hidedesc, $hideref);
2849 }
2850
2851 $this->oldcopy = clone $this;
2852 $this->statut = self::STATUS_BILLED;
2853 $this->status = self::STATUS_BILLED;
2854 $this->date_cloture = $now;
2855 $this->note_private = $newprivatenote;
2856 }
2857
2858 if (!$notrigger && empty($error)) {
2859 // Call trigger
2860 $result = $this->call_trigger($triggerName, $user);
2861 if ($result < 0) {
2862 $error++;
2863 }
2864 // End call triggers
2865 }
2866
2867 if (!$error) {
2868 $this->db->commit();
2869 return $num;
2870 } else {
2871 foreach ($this->errors as $errmsg) {
2872 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2873 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2874 }
2875 $this->db->rollback();
2876 return -1 * $error;
2877 }
2878 }
2879
2886 public function setCancel(User $user)
2887 {
2888 $error = 0;
2889
2890 $this->db->begin();
2891
2892 $sql = "UPDATE ". MAIN_DB_PREFIX . $this->table_element;
2893 $sql .= " SET fk_statut = " . self::STATUS_CANCELED . ",";
2894 $sql .= " fk_user_modif = " . ((int) $user->id);
2895 $sql .= " WHERE rowid = " . ((int) $this->id);
2896
2897 dol_syslog(get_class($this)."::cancel", LOG_DEBUG);
2898 if ($this->db->query($sql)) {
2899 if (!$error) {
2900 // Call trigger
2901 $result = $this->call_trigger('PROPAL_CANCEL', $user);
2902 if ($result < 0) {
2903 $error++;
2904 }
2905 // End call triggers
2906 }
2907
2908 if (!$error) {
2909 $this->statut = self::STATUS_CANCELED; // deprecated
2911 $this->db->commit();
2912 return 1;
2913 } else {
2914 foreach ($this->errors as $errmsg) {
2915 dol_syslog(get_class($this)."::cancel ".$errmsg, LOG_ERR);
2916 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2917 }
2918 $this->db->rollback();
2919 return -1;
2920 }
2921 } else {
2922 $this->error = $this->db->error();
2923 $this->db->rollback();
2924 return -1;
2925 }
2926 }
2927
2928 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2936 public function setDraft($user, $notrigger = 0)
2937 {
2938 // phpcs:enable
2939 $error = 0;
2940
2941 // Protection
2942 if ($this->status <= self::STATUS_DRAFT) {
2943 return 0;
2944 }
2945
2946 dol_syslog(get_class($this)."::setDraft", LOG_DEBUG);
2947
2948 $this->db->begin();
2949
2950 $sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element;
2951 $sql .= " SET fk_statut = ".self::STATUS_DRAFT;
2952 $sql .= ", online_sign_ip = NULL , online_sign_name = NULL";
2953 $sql .= " WHERE rowid = ".((int) $this->id);
2954
2955 $resql = $this->db->query($sql);
2956 if (!$resql) {
2957 $this->errors[] = $this->db->error();
2958 $error++;
2959 }
2960
2961 if (!$error) {
2962 $this->oldcopy = clone $this;
2963 }
2964
2965 if (!$notrigger && empty($error)) {
2966 // Call trigger
2967 $result = $this->call_trigger('PROPAL_MODIFY', $user);
2968 if ($result < 0) {
2969 $error++;
2970 }
2971 // End call triggers
2972 }
2973
2974 if (!$error) {
2975 $this->statut = self::STATUS_DRAFT; // deprecated
2976 $this->status = self::STATUS_DRAFT;
2977
2978 $this->db->commit();
2979 return 1;
2980 } else {
2981 foreach ($this->errors as $errmsg) {
2982 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2983 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2984 }
2985 $this->db->rollback();
2986 return -1 * $error;
2987 }
2988 }
2989
2990
2991 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3005 public function liste_array($shortlist = 0, $draft = 0, $notcurrentuser = 0, $socid = 0, $limit = 0, $offset = 0, $sortfield = 'p.datep', $sortorder = 'DESC')
3006 {
3007 // phpcs:enable
3008 global $user;
3009
3010 $ga = array();
3011
3012 $sql = "SELECT s.rowid, s.nom as name, s.client,";
3013 $sql .= " p.rowid as propalid, p.fk_statut, p.total_ht, p.ref, p.remise, ";
3014 $sql .= " p.datep as dp, p.fin_validite as datelimite";
3015 $sql .= " FROM ".MAIN_DB_PREFIX."societe as s, ".MAIN_DB_PREFIX.$this->table_element." as p, ".MAIN_DB_PREFIX."c_propalst as c";
3016 $sql .= " WHERE p.entity IN (".getEntity('propal').")";
3017 $sql .= " AND p.fk_soc = s.rowid";
3018 $sql .= " AND p.fk_statut = c.id";
3019
3020 // If the internal user must only see his customers, force searching by him
3021 $search_sale = 0;
3022 if (empty($user->socid) && !$user->hasRight('societe', 'client', 'voir')) {
3023 $search_sale = $user->id;
3024 }
3025 // Search on sale representative
3026 if ($search_sale && $search_sale != '-1') {
3027 if ($search_sale == -2) {
3028 $sql .= " AND NOT EXISTS (SELECT sc.fk_soc FROM ".MAIN_DB_PREFIX."societe_commerciaux as sc WHERE sc.fk_soc = p.fk_soc)";
3029 } elseif ($search_sale > 0) {
3030 $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).")";
3031 }
3032 }
3033 // Search on socid
3034 if ($socid) {
3035 $sql .= " AND p.fk_soc = ".((int) $socid);
3036 }
3037 if ($draft) {
3038 $sql .= " AND p.fk_statut = ".((int) self::STATUS_DRAFT);
3039 }
3040 if ($notcurrentuser > 0) {
3041 $sql .= " AND p.fk_user_author <> ".((int) $user->id);
3042 }
3043 $sql .= $this->db->order($sortfield, $sortorder);
3044 $sql .= $this->db->plimit($limit, $offset);
3045
3046 $result = $this->db->query($sql);
3047 if ($result) {
3048 $num = $this->db->num_rows($result);
3049 if ($num) {
3050 $i = 0;
3051 while ($i < $num) {
3052 $obj = $this->db->fetch_object($result);
3053
3054 if ($shortlist == 1) {
3055 $ga[$obj->propalid] = $obj->ref;
3056 } elseif ($shortlist == 2) {
3057 $ga[$obj->propalid] = $obj->ref.' ('.$obj->name.')';
3058 } else {
3059 $ga[$i]['id'] = $obj->propalid;
3060 $ga[$i]['ref'] = $obj->ref;
3061 $ga[$i]['name'] = $obj->name;
3062 }
3063
3064 $i++;
3065 }
3066 }
3067 return $ga;
3068 } else {
3069 dol_print_error($this->db);
3070 return -1;
3071 }
3072 }
3073
3079 public function getInvoiceArrayList()
3080 {
3081 return $this->InvoiceArrayList($this->id);
3082 }
3083
3084 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3091 public function InvoiceArrayList($id)
3092 {
3093 // phpcs:enable
3094 $ga = array();
3095 $linkedInvoices = array();
3096
3097 $this->fetchObjectLinked($id, $this->element);
3098 foreach ($this->linkedObjectsIds as $objecttype => $objectid) {
3099 // Nouveau système du common object renvoi des rowid et non un id linéaire de 1 à n
3100 // On parcourt donc une liste d'objets en tant qu'objet unique
3101 foreach ($objectid as $key => $object) {
3102 // Cas des factures liees directement
3103 if ($objecttype == 'facture') {
3104 $linkedInvoices[] = $object;
3105 } else {
3106 // Cas des factures liees par un autre object (ex: commande)
3107 $this->fetchObjectLinked($object, $objecttype);
3108 foreach ($this->linkedObjectsIds as $subobjecttype => $subobjectid) {
3109 foreach ($subobjectid as $subkey => $subobject) {
3110 if ($subobjecttype == 'facture') {
3111 $linkedInvoices[] = $subobject;
3112 }
3113 }
3114 }
3115 }
3116 }
3117 }
3118
3119 if (count($linkedInvoices) > 0) {
3120 $sql = "SELECT rowid as facid, ref, total_ht as total, datef as df, fk_user_author, fk_statut, paye";
3121 $sql .= " FROM ".MAIN_DB_PREFIX."facture";
3122 $sql .= " WHERE rowid IN (".$this->db->sanitize(implode(',', $linkedInvoices)).")";
3123
3124 dol_syslog(get_class($this)."::InvoiceArrayList", LOG_DEBUG);
3125 $resql = $this->db->query($sql);
3126
3127 if ($resql) {
3128 $tab_sqlobj = array();
3129 $nump = $this->db->num_rows($resql);
3130 for ($i = 0; $i < $nump; $i++) {
3131 $sqlobj = $this->db->fetch_object($resql);
3132 $tab_sqlobj[] = $sqlobj;
3133 }
3134 $this->db->free($resql);
3135
3136 $nump = count($tab_sqlobj);
3137
3138 if ($nump) {
3139 $i = 0;
3140 while ($i < $nump) {
3141 $obj = array_shift($tab_sqlobj);
3142
3143 $ga[$i] = $obj;
3144
3145 $i++;
3146 }
3147 }
3148 return $ga;
3149 } else {
3150 return -1;
3151 }
3152 } else {
3153 return $ga;
3154 }
3155 }
3156
3164 public function delete($user, $notrigger = 0)
3165 {
3166 global $conf;
3167 require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
3168
3169 $error = 0;
3170
3171 $this->db->begin();
3172
3173 if (!$notrigger) {
3174 // Call trigger
3175 $result = $this->call_trigger('PROPAL_DELETE', $user);
3176 if ($result < 0) {
3177 $error++;
3178 }
3179 // End call triggers
3180 }
3181
3182 // Delete extrafields of lines and lines
3183 if (!$error && !empty($this->table_element_line)) {
3184 $tabletodelete = $this->table_element_line;
3185 $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).")";
3186 $sql = "DELETE FROM ".MAIN_DB_PREFIX.$tabletodelete." WHERE ".$this->fk_element." = ".((int) $this->id);
3187 if (!$this->db->query($sqlef) || !$this->db->query($sql)) {
3188 $error++;
3189 $this->error = $this->db->lasterror();
3190 $this->errors[] = $this->error;
3191 dol_syslog(get_class($this)."::delete error ".$this->error, LOG_ERR);
3192 }
3193 }
3194
3195 if (!$error) {
3196 // Delete linked object
3197 $res = $this->deleteObjectLinked();
3198 if ($res < 0) {
3199 $error++;
3200 }
3201 }
3202
3203 if (!$error) {
3204 // Delete linked contacts
3205 $res = $this->delete_linked_contact();
3206 if ($res < 0) {
3207 $error++;
3208 }
3209 }
3210
3211 // Removed extrafields of object
3212 if (!$error) {
3213 $result = $this->deleteExtraFields();
3214 if ($result < 0) {
3215 $error++;
3216 dol_syslog(get_class($this)."::delete error ".$this->error, LOG_ERR);
3217 }
3218 }
3219
3220 // Delete main record
3221 if (!$error) {
3222 $sql = "DELETE FROM ".MAIN_DB_PREFIX.$this->table_element." WHERE rowid = ".((int) $this->id);
3223 $res = $this->db->query($sql);
3224 if (!$res) {
3225 $error++;
3226 $this->error = $this->db->lasterror();
3227 $this->errors[] = $this->error;
3228 dol_syslog(get_class($this)."::delete error ".$this->error, LOG_ERR);
3229 }
3230 }
3231
3232 // Delete record into ECM index and physically
3233 if (!$error) {
3234 $res = $this->deleteEcmFiles(0); // Deleting files physically is done later with the dol_delete_dir_recursive
3235 $res = $this->deleteEcmFiles(1); // Deleting files physically is done later with the dol_delete_dir_recursive
3236 if (!$res) {
3237 $error++;
3238 }
3239 }
3240
3241 if (!$error) {
3242 // We remove directory
3243 $ref = dol_sanitizeFileName($this->ref);
3244 if ($conf->propal->multidir_output[$this->entity] && !empty($this->ref)) {
3245 $dir = $conf->propal->multidir_output[$this->entity]."/".$ref;
3246 $file = $dir."/".$ref.".pdf";
3247 if (file_exists($file)) {
3248 dol_delete_preview($this);
3249
3250 if (!dol_delete_file($file, 0, 0, 0, $this)) {
3251 $this->error = 'ErrorFailToDeleteFile';
3252 $this->errors[] = $this->error;
3253 $this->db->rollback();
3254 return 0;
3255 }
3256 }
3257 if (file_exists($dir)) {
3258 $res = @dol_delete_dir_recursive($dir); // delete files physically + into ecm tables
3259 if (!$res) {
3260 $this->error = 'ErrorFailToDeleteDir';
3261 $this->errors[] = $this->error;
3262 $this->db->rollback();
3263 return 0;
3264 }
3265 }
3266 }
3267 }
3268
3269 if (!$error) {
3270 dol_syslog(get_class($this)."::delete ".$this->id." by ".$user->id, LOG_DEBUG);
3271 $this->db->commit();
3272 return 1;
3273 } else {
3274 $this->db->rollback();
3275 return -1;
3276 }
3277 }
3278
3287 public function availability($availability_id, $notrigger = 0)
3288 {
3289 global $user;
3290
3291 if ($this->status >= self::STATUS_DRAFT) {
3292 $error = 0;
3293
3294 $this->db->begin();
3295
3296 $sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element;
3297 $sql .= ' SET fk_availability = '.((int) $availability_id);
3298 $sql .= ' WHERE rowid='.((int) $this->id);
3299
3300 dol_syslog(__METHOD__.' availability('.$availability_id.')', LOG_DEBUG);
3301 $resql = $this->db->query($sql);
3302 if (!$resql) {
3303 $this->errors[] = $this->db->error();
3304 $error++;
3305 }
3306
3307 if (!$error) {
3308 $this->oldcopy = clone $this;
3309 $this->availability_id = $availability_id;
3310 }
3311
3312 if (!$notrigger && empty($error)) {
3313 // Call trigger
3314 $result = $this->call_trigger('PROPAL_MODIFY', $user);
3315 if ($result < 0) {
3316 $error++;
3317 }
3318 // End call triggers
3319 }
3320
3321 if (!$error) {
3322 $this->db->commit();
3323 return 1;
3324 } else {
3325 foreach ($this->errors as $errmsg) {
3326 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
3327 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
3328 }
3329 $this->db->rollback();
3330 return -1 * $error;
3331 }
3332 } else {
3333 $error_str = 'Propal status do not meet requirement '.$this->status;
3334 dol_syslog(__METHOD__.$error_str, LOG_ERR);
3335 $this->error = $error_str;
3336 $this->errors[] = $this->error;
3337 return -2;
3338 }
3339 }
3340
3341 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3350 public function demand_reason($demand_reason_id, $notrigger = 0)
3351 {
3352 // phpcs:enable
3353 global $user;
3354
3355 if ($this->status >= self::STATUS_DRAFT) {
3356 $error = 0;
3357
3358 $this->db->begin();
3359
3360 $sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element;
3361 $sql .= ' SET fk_input_reason = '.((int) $demand_reason_id);
3362 $sql .= ' WHERE rowid='.((int) $this->id);
3363
3364 dol_syslog(__METHOD__.' demand_reason('.$demand_reason_id.')', LOG_DEBUG);
3365 $resql = $this->db->query($sql);
3366 if (!$resql) {
3367 $this->errors[] = $this->db->error();
3368 $error++;
3369 }
3370
3371 if (!$error) {
3372 $this->oldcopy = clone $this;
3373 $this->demand_reason_id = $demand_reason_id;
3374 }
3375
3376 if (!$notrigger && empty($error)) {
3377 // Call trigger
3378 $result = $this->call_trigger('PROPAL_MODIFY', $user);
3379 if ($result < 0) {
3380 $error++;
3381 }
3382 // End call triggers
3383 }
3384
3385 if (!$error) {
3386 $this->db->commit();
3387 return 1;
3388 } else {
3389 foreach ($this->errors as $errmsg) {
3390 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
3391 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
3392 }
3393 $this->db->rollback();
3394 return -1 * $error;
3395 }
3396 } else {
3397 $error_str = 'Propal status do not meet requirement '.$this->status;
3398 dol_syslog(__METHOD__.$error_str, LOG_ERR);
3399 $this->error = $error_str;
3400 $this->errors[] = $this->error;
3401 return -2;
3402 }
3403 }
3404
3405
3412 public function info($id)
3413 {
3414 $sql = "SELECT c.rowid, ";
3415 $sql .= " c.datec, c.date_valid as datev, c.date_signature, c.date_cloture,";
3416 $sql .= " c.fk_user_author, c.fk_user_valid, c.fk_user_signature, c.fk_user_cloture";
3417 $sql .= " FROM ".MAIN_DB_PREFIX.$this->table_element." as c";
3418 $sql .= " WHERE c.rowid = ".((int) $id);
3419
3420 $result = $this->db->query($sql);
3421
3422 if ($result) {
3423 if ($this->db->num_rows($result)) {
3424 $obj = $this->db->fetch_object($result);
3425
3426 $this->id = $obj->rowid;
3427
3428 $this->date_creation = $this->db->jdate($obj->datec);
3429 $this->date_validation = $this->db->jdate($obj->datev);
3430 $this->date_signature = $this->db->jdate($obj->date_signature);
3431 $this->date_cloture = $this->db->jdate($obj->date_cloture);
3432
3433 $this->user_creation_id = $obj->fk_user_author;
3434 $this->user_validation_id = $obj->fk_user_valid;
3435
3436 if ($obj->fk_user_signature) {
3437 $user_signature = new User($this->db);
3438 $user_signature->fetch($obj->fk_user_signature);
3439 $this->user_signature = $user_signature;
3440 }
3441
3442 $this->user_closing_id = $obj->fk_user_cloture;
3443 }
3444 $this->db->free($result);
3445 } else {
3446 dol_print_error($this->db);
3447 }
3448 }
3449
3450
3457 public function getLibStatut($mode = 0)
3458 {
3459 return $this->LibStatut($this->status, $mode);
3460 }
3461
3462 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3470 public function LibStatut($status, $mode = 1)
3471 {
3472 // phpcs:enable
3473 global $hookmanager;
3474
3475 // Init/load array of translation of status
3476 if (empty($this->labelStatus) || empty($this->labelStatusShort)) {
3477 global $langs;
3478 $langs->load("propal");
3479 $this->labelStatus[-1] = $langs->transnoentitiesnoconv("PropalStatusCanceled");
3480 $this->labelStatus[0] = $langs->transnoentitiesnoconv("PropalStatusDraft");
3481 $this->labelStatus[1] = $langs->transnoentitiesnoconv("PropalStatusValidated");
3482 $this->labelStatus[2] = $langs->transnoentitiesnoconv("PropalStatusSigned");
3483 $this->labelStatus[3] = $langs->transnoentitiesnoconv("PropalStatusNotSigned");
3484 $this->labelStatus[4] = $langs->transnoentitiesnoconv("PropalStatusBilled");
3485 $this->labelStatusShort[-1] = $langs->transnoentitiesnoconv("PropalStatusCanceledShort");
3486 $this->labelStatusShort[0] = $langs->transnoentitiesnoconv("PropalStatusDraftShort");
3487 $this->labelStatusShort[1] = $langs->transnoentitiesnoconv("PropalStatusValidatedShort");
3488 $this->labelStatusShort[2] = $langs->transnoentitiesnoconv("PropalStatusSignedShort");
3489 $this->labelStatusShort[3] = $langs->transnoentitiesnoconv("PropalStatusNotSignedShort");
3490 $this->labelStatusShort[4] = $langs->transnoentitiesnoconv("PropalStatusBilledShort");
3491 }
3492
3493 $statusType = '';
3494 if ($status == self::STATUS_CANCELED) {
3495 $statusType = 'status9';
3496 } elseif ($status == self::STATUS_DRAFT) {
3497 $statusType = 'status0';
3498 } elseif ($status == self::STATUS_VALIDATED) {
3499 $statusType = 'status1';
3500 } elseif ($status == self::STATUS_SIGNED) {
3501 $statusType = 'status4';
3502 } elseif ($status == self::STATUS_NOTSIGNED) {
3503 $statusType = 'status9';
3504 } elseif ($status == self::STATUS_BILLED) {
3505 $statusType = 'status6';
3506 }
3507
3508 $parameters = array('status' => $status, 'mode' => $mode);
3509 $reshook = $hookmanager->executeHooks('LibStatut', $parameters, $this); // Note that $action and $object may have been modified by hook
3510
3511 if ($reshook > 0) {
3512 return $hookmanager->resPrint;
3513 }
3514
3515 return dolGetStatus($this->labelStatus[$status], $this->labelStatusShort[$status], '', $statusType, $mode);
3516 }
3517
3518
3519 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3527 public function load_board($user, $mode)
3528 {
3529 // phpcs:enable
3530 global $conf, $langs;
3531
3532 $sql = "SELECT p.rowid, p.ref, p.datec as datec, p.fin_validite as datefin, p.total_ht";
3533 $sql .= " FROM ".MAIN_DB_PREFIX.$this->table_element." as p";
3534 $sql .= " WHERE p.entity IN (".getEntity('propal').")";
3535 if ($mode == 'opened') {
3536 $sql .= " AND p.fk_statut = ".self::STATUS_VALIDATED;
3537 }
3538 if ($mode == 'signed') {
3539 $sql .= " AND p.fk_statut = ".self::STATUS_SIGNED;
3540 }
3541 // If the internal user must only see his customers, force searching by him
3542 $search_sale = 0;
3543 if (empty($user->socid) && !$user->hasRight('societe', 'client', 'voir')) {
3544 $search_sale = $user->id;
3545 }
3546 // Search on sale representative
3547 if ($search_sale && $search_sale != '-1') {
3548 if ($search_sale == -2) {
3549 $sql .= " AND NOT EXISTS (SELECT sc.fk_soc FROM ".MAIN_DB_PREFIX."societe_commerciaux as sc WHERE sc.fk_soc = p.fk_soc)";
3550 } elseif ($search_sale > 0) {
3551 $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).")";
3552 }
3553 }
3554 if ($user->socid) {
3555 $sql .= " AND p.fk_soc = ".((int) $user->socid);
3556 }
3557
3558 $resql = $this->db->query($sql);
3559 if ($resql) {
3560 $langs->load("propal");
3561 $now = dol_now();
3562
3563 $delay_warning = 0;
3564 $status = 0;
3565 $label = $labelShort = '';
3566 if ($mode == 'opened') {
3567 $delay_warning = $conf->propal->cloture->warning_delay;
3568 $status = self::STATUS_VALIDATED;
3569 $label = $langs->transnoentitiesnoconv("PropalsToClose");
3570 $labelShort = $langs->transnoentitiesnoconv("ToAcceptRefuse");
3571 }
3572 if ($mode == 'signed') {
3573 $delay_warning = $conf->propal->facturation->warning_delay;
3574 $status = self::STATUS_SIGNED;
3575 $label = $langs->trans("PropalsToBill"); // We set here bill but may be billed or ordered
3576 $labelShort = $langs->trans("ToBill");
3577 }
3578
3579 $response = new WorkboardResponse();
3580 $response->warning_delay = $delay_warning / 60 / 60 / 24;
3581 $response->label = $label;
3582 $response->labelShort = $labelShort;
3583 $response->url = DOL_URL_ROOT.'/comm/propal/list.php?search_status='.$status.'&mainmenu=commercial&leftmenu=propals';
3584 $response->url_late = DOL_URL_ROOT.'/comm/propal/list.php?search_option=late&mainmenu=commercial&leftmenu=propals&sortfield=p.datep&sortorder=asc';
3585 $response->img = img_object('', "propal");
3586
3587 // This assignment in condition is not a bug. It allows walking the results.
3588 while ($obj = $this->db->fetch_object($resql)) {
3589 $response->nbtodo++;
3590 $response->total += $obj->total_ht;
3591
3592 if ($mode == 'opened') {
3593 $datelimit = $this->db->jdate($obj->datefin);
3594 if ($datelimit < ($now - $delay_warning)) {
3595 $response->nbtodolate++;
3596 }
3597 }
3598 // TODO Definir regle des propales a facturer en retard
3599 // if ($mode == 'signed' && ! count($this->FactureListeArray($obj->rowid))) $this->nbtodolate++;
3600 }
3601
3602 return $response;
3603 } else {
3604 $this->error = $this->db->error();
3605 return -1;
3606 }
3607 }
3608
3609
3617 public function initAsSpecimen()
3618 {
3619 global $conf, $langs;
3620
3621 // Load array of products prodids
3622 $num_prods = 0;
3623 $prodids = array();
3624 $sql = "SELECT rowid";
3625 $sql .= " FROM ".MAIN_DB_PREFIX."product";
3626 $sql .= " WHERE entity IN (".getEntity('product').")";
3627 $sql .= $this->db->plimit(100);
3628
3629 $resql = $this->db->query($sql);
3630 if ($resql) {
3631 $num_prods = $this->db->num_rows($resql);
3632 $i = 0;
3633 while ($i < $num_prods) {
3634 $i++;
3635 $row = $this->db->fetch_row($resql);
3636 $prodids[$i] = $row[0];
3637 }
3638 }
3639
3640 // Initialise parameters
3641 $this->id = 0;
3642 $this->ref = 'SPECIMEN';
3643 $this->ref_client = 'NEMICEPS';
3644 $this->specimen = 1;
3645 $this->socid = 1;
3646 $this->date = time();
3647 $this->fin_validite = $this->date + 3600 * 24 * 30;
3648 $this->cond_reglement_id = 1;
3649 $this->cond_reglement_code = 'RECEP';
3650 $this->mode_reglement_id = 7;
3651 $this->mode_reglement_code = 'CHQ';
3652 $this->availability_id = 1;
3653 $this->availability_code = 'AV_NOW';
3654 $this->demand_reason_id = 1;
3655 $this->demand_reason_code = 'SRC_00';
3656 $this->note_public = 'This is a comment (public)';
3657 $this->note_private = 'This is a comment (private)';
3658
3659 $this->multicurrency_tx = 1;
3660 $this->multicurrency_code = $conf->currency;
3661
3662 // Lines
3663 $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)
3664 $xnbp = 0;
3665 while ($xnbp < $nbp) {
3666 $line = new PropaleLigne($this->db);
3667 $line->desc = $langs->trans("Description")." ".$xnbp;
3668 $line->qty = 1;
3669 $line->subprice = 100;
3670 $line->price = 100;
3671 $line->tva_tx = 20;
3672 $line->localtax1_tx = 0;
3673 $line->localtax2_tx = 0;
3674 if ($xnbp == 2) {
3675 $line->total_ht = 50;
3676 $line->total_ttc = 60;
3677 $line->total_tva = 10;
3678 $line->remise_percent = 50;
3679 } else {
3680 $line->total_ht = 100;
3681 $line->total_ttc = 120;
3682 $line->total_tva = 20;
3683 $line->remise_percent = 00;
3684 }
3685
3686 if ($num_prods > 0) {
3687 $prodid = mt_rand(1, $num_prods);
3688 $line->fk_product = $prodids[$prodid];
3689 $line->product_ref = 'SPECIMEN';
3690 }
3691
3692 $this->lines[$xnbp] = $line;
3693
3694 $this->total_ht += $line->total_ht;
3695 $this->total_tva += $line->total_tva;
3696 $this->total_ttc += $line->total_ttc;
3697
3698 $xnbp++;
3699 }
3700
3701 return 1;
3702 }
3703
3709 public function loadStateBoard()
3710 {
3711 global $user;
3712
3713 $this->nb = array();
3714
3715 $sql = "SELECT count(p.rowid) as nb";
3716 $sql .= " FROM ".MAIN_DB_PREFIX.$this->table_element." as p";
3717 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe as s ON p.fk_soc = s.rowid";
3718 $sql .= " WHERE p.entity IN (".getEntity('propal').")";
3719
3720 // If the internal user must only see his customers, force searching by him
3721 $search_sale = 0;
3722 if (empty($user->socid) && !$user->hasRight('societe', 'client', 'voir')) {
3723 $search_sale = $user->id;
3724 }
3725 // Search on sale representative
3726 if ($search_sale && $search_sale != '-1') {
3727 if ($search_sale == -2) {
3728 $sql .= " AND NOT EXISTS (SELECT sc.fk_soc FROM ".MAIN_DB_PREFIX."societe_commerciaux as sc WHERE sc.fk_soc = p.fk_soc)";
3729 } elseif ($search_sale > 0) {
3730 $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).")";
3731 }
3732 }
3733
3734 $resql = $this->db->query($sql);
3735 if ($resql) {
3736 // This assignment in condition is not a bug. It allows walking the results.
3737 while ($obj = $this->db->fetch_object($resql)) {
3738 $this->nb["proposals"] = $obj->nb;
3739 }
3740 $this->db->free($resql);
3741 return 1;
3742 } else {
3743 dol_print_error($this->db);
3744 $this->error = $this->db->error();
3745 return -1;
3746 }
3747 }
3748
3749
3757 public function getNextNumRef($soc)
3758 {
3759 global $conf, $langs;
3760 $langs->load("propal");
3761
3762 $classname = getDolGlobalString('PROPALE_ADDON');
3763
3764 if (!empty($classname)) {
3765 $mybool = false;
3766
3767 $file = $classname.".php";
3768
3769 // Include file with class
3770 $dirmodels = array_merge(array('/'), (array) $conf->modules_parts['models']);
3771 foreach ($dirmodels as $reldir) {
3772 $dir = dol_buildpath($reldir."core/modules/propale/");
3773
3774 // Load file with numbering class (if found)
3775 $mybool = ((bool) @include_once $dir.$file) || $mybool;
3776 }
3777
3778 if (!$mybool) {
3779 dol_print_error(null, "Failed to include file ".$file);
3780 return '';
3781 }
3782
3783 $obj = new $classname();
3784 '@phan-var-force ModeleNumRefPropales $obj';
3785
3786 $numref = $obj->getNextValue($soc, $this);
3787
3788 if ($numref != "") {
3789 return $numref;
3790 } else {
3791 $this->error = $obj->error;
3792 //dol_print_error($db,"Propale::getNextNumRef ".$obj->error);
3793 return "";
3794 }
3795 } else {
3796 $langs->load("errors");
3797 print $langs->trans("Error")." ".$langs->trans("ErrorModuleSetupNotComplete", $langs->transnoentitiesnoconv("Proposal"));
3798 return "";
3799 }
3800 }
3801
3808 public function getTooltipContentArray($params)
3809 {
3810 global $conf, $langs, $user;
3811
3812 $langs->load('propal');
3813 $datas = [];
3814 $nofetch = !empty($params['nofetch']);
3815
3816 if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
3817 return ['optimize' => $langs->trans("Proposal")];
3818 }
3819 if ($user->hasRight('propal', 'lire')) {
3820 $datas['picto'] = img_picto('', $this->picto, '', 0, 0, 0, '', 'paddingrightonly').'<u>'.$langs->trans("Proposal").'</u>';
3821 if (isset($this->status)) {
3822 $datas['status'] = ' '.$this->getLibStatut(5);
3823 }
3824 if (!empty($this->ref)) {
3825 $datas['ref'] = '<br><b>'.$langs->trans('Ref').':</b> '.$this->ref;
3826 }
3827 if (!$nofetch) {
3828 $langs->load('companies');
3829 if (empty($this->thirdparty)) {
3830 $this->fetch_thirdparty();
3831 }
3832 $datas['customer'] = '<br><b>'.$langs->trans('Customer').':</b> '.$this->thirdparty->getNomUrl(1, '', 0, 1);
3833 }
3834 if (!empty($this->ref_customer)) {
3835 $datas['refcustomer'] = '<br><b>'.$langs->trans('RefCustomer').':</b> '.$this->ref_customer;
3836 }
3837 if (!$nofetch) {
3838 $langs->load('project');
3839 if (is_null($this->project) || (is_object($this->project) && $this->project->isEmpty())) {
3840 $res = $this->fetchProject();
3841 if ($res > 0 && $this->project instanceof Project) {
3842 $datas['project'] = '<br><b>'.$langs->trans('Project').':</b> '.$this->project->getNomUrl(1, '', 0, '1');
3843 }
3844 }
3845 }
3846 if (!empty($this->total_ht)) {
3847 $datas['amountht'] = '<br><b>'.$langs->trans('AmountHT').':</b> '.price($this->total_ht, 0, $langs, 0, -1, -1, $conf->currency);
3848 }
3849 if (!empty($this->total_tva)) {
3850 $datas['vat'] = '<br><b>'.$langs->trans('VAT').':</b> '.price($this->total_tva, 0, $langs, 0, -1, -1, $conf->currency);
3851 }
3852 if (!empty($this->total_ttc)) {
3853 $datas['amountttc'] = '<br><b>'.$langs->trans('AmountTTC').':</b> '.price($this->total_ttc, 0, $langs, 0, -1, -1, $conf->currency);
3854 }
3855 if (!empty($this->date)) {
3856 $datas['date'] = '<br><b>'.$langs->trans('Date').':</b> '.dol_print_date($this->date, 'day');
3857 }
3858 if (!empty($this->date_signature)) {
3859 $datas['datesignature'] = '<br><b>'.$langs->trans('DateSigning').':</b> '.dol_print_date($this->date_signature, 'day');
3860 }
3861 if (!empty($this->delivery_date)) {
3862 $datas['deliverydate'] = '<br><b>'.$langs->trans('DeliveryDate').':</b> '.dol_print_date($this->delivery_date, 'dayhour');
3863 }
3864 }
3865
3866 return $datas;
3867 }
3868
3880 public function getNomUrl($withpicto = 0, $option = '', $get_params = '', $notooltip = 0, $save_lastsearch_value = -1, $addlinktonotes = -1)
3881 {
3882 global $langs, $conf, $user, $hookmanager;
3883
3884 if (!empty($conf->dol_no_mouse_hover)) {
3885 $notooltip = 1; // Force disable tooltips
3886 }
3887
3888 $result = '';
3889 $params = [
3890 'id' => $this->id,
3891 'objecttype' => $this->element,
3892 'option' => $option,
3893 'nofetch' => 1,
3894 ];
3895 $classfortooltip = 'classfortooltip';
3896 $dataparams = '';
3897 if (getDolGlobalInt('MAIN_ENABLE_AJAX_TOOLTIP')) {
3898 $classfortooltip = 'classforajaxtooltip';
3899 $dataparams = ' data-params="'.dol_escape_htmltag(json_encode($params)).'"';
3900 $label = '';
3901 } else {
3902 $label = implode($this->getTooltipContentArray($params));
3903 }
3904
3905 $url = '';
3906 if ($user->hasRight('propal', 'lire')) {
3907 if ($option == '') {
3908 $url = DOL_URL_ROOT.'/comm/propal/card.php?id='.$this->id.$get_params;
3909 } elseif ($option == 'compta') { // deprecated
3910 $url = DOL_URL_ROOT.'/comm/propal/card.php?id='.$this->id.$get_params;
3911 } elseif ($option == 'expedition') {
3912 $url = DOL_URL_ROOT.'/expedition/propal.php?id='.$this->id.$get_params;
3913 } elseif ($option == 'document') {
3914 $url = DOL_URL_ROOT.'/comm/propal/document.php?id='.$this->id.$get_params;
3915 }
3916
3917 if ($option != 'nolink') {
3918 // Add param to save lastsearch_values or not
3919 $add_save_lastsearch_values = ($save_lastsearch_value == 1 ? 1 : 0);
3920 if ($save_lastsearch_value == -1 && isset($_SERVER["PHP_SELF"]) && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) {
3921 $add_save_lastsearch_values = 1;
3922 }
3923 if ($add_save_lastsearch_values) {
3924 $url .= '&save_lastsearch_values=1';
3925 }
3926 }
3927 }
3928
3929 $linkclose = '';
3930 if (empty($notooltip) && $user->hasRight('propal', 'lire')) {
3931 if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
3932 $label = $langs->trans("Proposal");
3933 $linkclose .= ' alt="'.dolPrintHTMLForAttribute($label).'"';
3934 }
3935 $linkclose .= ($label ? ' title="'.dolPrintHTMLForAttribute($label).'"' : ' title="tocomplete"');
3936 $linkclose .= $dataparams.' class="'.$classfortooltip.'"';
3937 }
3938
3939 $linkstart = '<a href="'.$url.'"';
3940 $linkstart .= $linkclose.'>';
3941 $linkend = '</a>';
3942
3943 $result .= $linkstart;
3944 if ($withpicto) {
3945 $result .= img_object(($notooltip ? '' : $label), ($this->picto ? $this->picto : 'generic'), (($withpicto != 2) ? 'class="paddingright"' : ''), 0, 0, $notooltip ? 0 : 1);
3946 }
3947 if ($withpicto != 2) {
3948 $result .= $this->ref;
3949 }
3950 $result .= $linkend;
3951
3952 if ($addlinktonotes >= 0) {
3953 $txttoshow = '';
3954
3955 if ($addlinktonotes == 0) {
3956 if (!empty($this->note_private) || !empty($this->note_public)) {
3957 $txttoshow = $langs->trans('ViewPrivateNote');
3958 }
3959 } elseif ($addlinktonotes == 1) {
3960 if (!empty($this->note_private)) {
3961 $txttoshow .= ($user->socid > 0 ? '' : dol_string_nohtmltag($this->note_private, 1));
3962 }
3963 } elseif ($addlinktonotes == 2) {
3964 if (!empty($this->note_public)) {
3965 $txttoshow .= dol_string_nohtmltag($this->note_public, 1);
3966 }
3967 } elseif ($addlinktonotes == 3) {
3968 if ($user->socid > 0) {
3969 if (!empty($this->note_public)) {
3970 $txttoshow .= dol_string_nohtmltag($this->note_public, 1);
3971 }
3972 } else {
3973 if (!empty($this->note_public)) {
3974 $txttoshow .= dol_string_nohtmltag($this->note_public, 1);
3975 }
3976 if (!empty($this->note_private)) {
3977 if (!empty($txttoshow)) {
3978 $txttoshow .= '<br><br>';
3979 }
3980 $txttoshow .= dol_string_nohtmltag($this->note_private, 1);
3981 }
3982 }
3983 }
3984
3985 if ($txttoshow) {
3986 $result .= ' <span class="note inline-block">';
3987 $result .= '<a href="'.DOL_URL_ROOT.'/comm/propal/note.php?id='.$this->id.'" class="classfortooltip" title="'.dol_escape_htmltag($txttoshow).'">';
3988 $result .= img_picto('', 'note');
3989 $result .= '</a>';
3990 $result .= '</span>';
3991 }
3992 }
3993
3994 global $action;
3995 $hookmanager->initHooks(array($this->element . 'dao'));
3996 $parameters = array('id' => $this->id, 'getnomurl' => &$result);
3997 $reshook = $hookmanager->executeHooks('getNomUrl', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
3998 if ($reshook > 0) {
3999 $result = $hookmanager->resPrint;
4000 } else {
4001 $result .= $hookmanager->resPrint;
4002 }
4003 return $result;
4004 }
4005
4012 public function getLinesArray($sqlforgedfilters = '')
4013 {
4014 return $this->fetch_lines(0, 0, $sqlforgedfilters);
4015 }
4016
4028 public function generateDocument($modele, $outputlangs, $hidedetails = 0, $hidedesc = 0, $hideref = 0, $moreparams = null)
4029 {
4030 $outputlangs->loadLangs(array("propale", "products"));
4031
4032 if (!dol_strlen($modele)) {
4033 $modele = 'azur';
4034
4035 if ($this->model_pdf) {
4036 $modele = $this->model_pdf;
4037 } elseif (getDolGlobalString('PROPALE_ADDON_PDF')) {
4038 $modele = getDolGlobalString('PROPALE_ADDON_PDF');
4039 }
4040 }
4041
4042 $modelpath = "core/modules/propale/doc/";
4043
4044 return $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams);
4045 }
4046
4055 public static function replaceThirdparty(DoliDB $dbs, $origin_id, $dest_id)
4056 {
4057 $tables = array(
4058 'propal'
4059 );
4060
4061 return CommonObject::commonReplaceThirdparty($dbs, $origin_id, $dest_id, $tables);
4062 }
4063
4072 public static function replaceProduct(DoliDB $db, $origin_id, $dest_id)
4073 {
4074 $tables = array(
4075 'propaldet'
4076 );
4077
4078 return CommonObject::commonReplaceProduct($db, $origin_id, $dest_id, $tables);
4079 }
4080
4088 public function getKanbanView($option = '', $arraydata = null)
4089 {
4090 global $langs;
4091
4092 $selected = (empty($arraydata['selected']) ? 0 : $arraydata['selected']);
4093
4094 $return = '<div class="box-flex-item box-flex-grow-zero">';
4095 $return .= '<div class="info-box info-box-sm">';
4096 $return .= '<div class="info-box-icon bg-infobox-action">';
4097 $return .= img_picto('', $this->picto);
4098 $return .= '</div>';
4099 $return .= '<div class="info-box-content">';
4100 $return .= '<span class="info-box-ref inline-block tdoverflowmax150 valignmiddle">'.(method_exists($this, 'getNomUrl') ? $this->getNomUrl() : $this->ref).'</span>';
4101 if ($selected >= 0) {
4102 $return .= '<input id="cb'.$this->id.'" class="flat checkforselect fright" type="checkbox" name="toselect[]" value="'.$this->id.'"'.($selected ? ' checked="checked"' : '').'>';
4103 }
4104 if (!empty($arraydata['projectlink'])) {
4105 $return .= '<span class="info-box-ref"> | '.$arraydata['projectlink'].'</span>';
4106 }
4107 $return .= '<br>';
4108 if (property_exists($this, 'thirdparty') && is_object($this->thirdparty)) {
4109 $return .= '<div class="info-box-ref tdoverflowmax150">'.$this->thirdparty->getNomUrl(1).'</div>';
4110 }
4111 if (property_exists($this, 'total_ht')) {
4112 $return .= '<span class="info-box-label amount" title="'.$langs->trans("AmountHT").'">'.price($this->total_ht).'</span>';
4113 }
4114 if (!empty($arraydata['authorlink'])) {
4115 $return .= ' &nbsp; <span class="info-box-label">'.$arraydata['authorlink'].'</span>';
4116 }
4117 if (method_exists($this, 'getLibStatut')) {
4118 $return .= '<br><div class="info-box-status">'.$this->getLibStatut(3).'</div>';
4119 }
4120 $return .= '</div>';
4121 $return .= '</div>';
4122 $return .= '</div>';
4123 return $return;
4124 }
4125}
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 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