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 $this->statut = $this->oldcopy->status; // deprecated
2773 $this->status = $this->oldcopy->status;
2774 $this->date_signature = $this->oldcopy->date_signature;
2775 $this->note_private = $this->oldcopy->note_private;
2776
2777 $this->db->rollback();
2778 return -1;
2779 }
2780 } else {
2781 $this->error = $this->db->lasterror();
2782 $this->db->rollback();
2783 return -1;
2784 }
2785 }
2786
2795 public function classifyBilled(User $user, $notrigger = 0, $note = '')
2796 {
2797 global $conf, $langs;
2798
2799 $error = 0;
2800
2801 $now = dol_now();
2802 $num = 0;
2803
2804 $triggerName = 'PROPAL_CLASSIFY_BILLED';
2805
2806 $this->db->begin();
2807
2808 $newprivatenote = dol_concatdesc($this->note_private, $note);
2809
2810 $sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element;
2811 $sql .= " SET fk_statut = ".self::STATUS_BILLED.", ";
2812 $sql .= " note_private = '".$this->db->escape($newprivatenote)."', date_cloture='".$this->db->idate($now)."', fk_user_cloture=".((int) $user->id);
2813 $sql .= ' WHERE rowid = '.((int) $this->id).' AND fk_statut = '.((int) self::STATUS_SIGNED);
2814
2815 dol_syslog(__METHOD__, LOG_DEBUG);
2816 $resql = $this->db->query($sql);
2817 if (!$resql) {
2818 $this->errors[] = $this->db->error();
2819 $error++;
2820 } else {
2821 $num = $this->db->affected_rows($resql);
2822 }
2823
2824 if (!$error) {
2825 $modelpdf = getDolGlobalString('PROPALE_ADDON_PDF_ODT_CLOSED', $this->model_pdf);
2826
2827 if (!getDolGlobalString('MAIN_DISABLE_PDF_AUTOUPDATE')) {
2828 // Define output language
2829 $outputlangs = $langs;
2830 if (getDolGlobalInt('MAIN_MULTILANGS')) {
2831 $outputlangs = new Translate("", $conf);
2832 $newlang = (GETPOST('lang_id', 'aZ09') ? GETPOST('lang_id', 'aZ09') : $this->thirdparty->default_lang);
2833 $outputlangs->setDefaultLang($newlang);
2834 }
2835
2836 // PDF
2837 $hidedetails = (getDolGlobalString('MAIN_GENERATE_DOCUMENTS_HIDE_DETAILS') ? 1 : 0);
2838 $hidedesc = (getDolGlobalString('MAIN_GENERATE_DOCUMENTS_HIDE_DESC') ? 1 : 0);
2839 $hideref = (getDolGlobalString('MAIN_GENERATE_DOCUMENTS_HIDE_REF') ? 1 : 0);
2840
2841 //$ret=$object->fetch($id); // Reload to get new records
2842 $this->generateDocument($modelpdf, $outputlangs, $hidedetails, $hidedesc, $hideref);
2843 }
2844
2845 $this->oldcopy = clone $this;
2846 $this->statut = self::STATUS_BILLED;
2847 $this->status = self::STATUS_BILLED;
2848 $this->date_cloture = $now;
2849 $this->note_private = $newprivatenote;
2850 }
2851
2852 if (!$notrigger && empty($error)) {
2853 // Call trigger
2854 $result = $this->call_trigger($triggerName, $user);
2855 if ($result < 0) {
2856 $error++;
2857 }
2858 // End call triggers
2859 }
2860
2861 if (!$error) {
2862 $this->db->commit();
2863 return $num;
2864 } else {
2865 foreach ($this->errors as $errmsg) {
2866 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2867 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2868 }
2869 $this->db->rollback();
2870 return -1 * $error;
2871 }
2872 }
2873
2880 public function setCancel(User $user)
2881 {
2882 $error = 0;
2883
2884 $this->db->begin();
2885
2886 $sql = "UPDATE ". MAIN_DB_PREFIX . $this->table_element;
2887 $sql .= " SET fk_statut = " . self::STATUS_CANCELED . ",";
2888 $sql .= " fk_user_modif = " . ((int) $user->id);
2889 $sql .= " WHERE rowid = " . ((int) $this->id);
2890
2891 dol_syslog(get_class($this)."::cancel", LOG_DEBUG);
2892 if ($this->db->query($sql)) {
2893 if (!$error) {
2894 // Call trigger
2895 $result = $this->call_trigger('PROPAL_CANCEL', $user);
2896 if ($result < 0) {
2897 $error++;
2898 }
2899 // End call triggers
2900 }
2901
2902 if (!$error) {
2903 $this->statut = self::STATUS_CANCELED; // deprecated
2905 $this->db->commit();
2906 return 1;
2907 } else {
2908 foreach ($this->errors as $errmsg) {
2909 dol_syslog(get_class($this)."::cancel ".$errmsg, LOG_ERR);
2910 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2911 }
2912 $this->db->rollback();
2913 return -1;
2914 }
2915 } else {
2916 $this->error = $this->db->error();
2917 $this->db->rollback();
2918 return -1;
2919 }
2920 }
2921
2922 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2930 public function setDraft($user, $notrigger = 0)
2931 {
2932 // phpcs:enable
2933 $error = 0;
2934
2935 // Protection
2936 if ($this->status <= self::STATUS_DRAFT) {
2937 return 0;
2938 }
2939
2940 dol_syslog(get_class($this)."::setDraft", LOG_DEBUG);
2941
2942 $this->db->begin();
2943
2944 $sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element;
2945 $sql .= " SET fk_statut = ".self::STATUS_DRAFT;
2946 $sql .= ", online_sign_ip = NULL , online_sign_name = NULL";
2947 $sql .= " WHERE rowid = ".((int) $this->id);
2948
2949 $resql = $this->db->query($sql);
2950 if (!$resql) {
2951 $this->errors[] = $this->db->error();
2952 $error++;
2953 }
2954
2955 if (!$error) {
2956 $this->oldcopy = clone $this;
2957 }
2958
2959 if (!$notrigger && empty($error)) {
2960 // Call trigger
2961 $result = $this->call_trigger('PROPAL_MODIFY', $user);
2962 if ($result < 0) {
2963 $error++;
2964 }
2965 // End call triggers
2966 }
2967
2968 if (!$error) {
2969 $this->statut = self::STATUS_DRAFT; // deprecated
2970 $this->status = self::STATUS_DRAFT;
2971
2972 $this->db->commit();
2973 return 1;
2974 } else {
2975 foreach ($this->errors as $errmsg) {
2976 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2977 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2978 }
2979 $this->db->rollback();
2980 return -1 * $error;
2981 }
2982 }
2983
2984
2985 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2999 public function liste_array($shortlist = 0, $draft = 0, $notcurrentuser = 0, $socid = 0, $limit = 0, $offset = 0, $sortfield = 'p.datep', $sortorder = 'DESC')
3000 {
3001 // phpcs:enable
3002 global $user;
3003
3004 $ga = array();
3005
3006 $sql = "SELECT s.rowid, s.nom as name, s.client,";
3007 $sql .= " p.rowid as propalid, p.fk_statut, p.total_ht, p.ref, p.remise, ";
3008 $sql .= " p.datep as dp, p.fin_validite as datelimite";
3009 $sql .= " FROM ".MAIN_DB_PREFIX."societe as s, ".MAIN_DB_PREFIX.$this->table_element." as p, ".MAIN_DB_PREFIX."c_propalst as c";
3010 $sql .= " WHERE p.entity IN (".getEntity('propal').")";
3011 $sql .= " AND p.fk_soc = s.rowid";
3012 $sql .= " AND p.fk_statut = c.id";
3013
3014 // If the internal user must only see his customers, force searching by him
3015 $search_sale = 0;
3016 if (empty($user->socid) && !$user->hasRight('societe', 'client', 'voir')) {
3017 $search_sale = $user->id;
3018 }
3019 // Search on sale representative
3020 if ($search_sale && $search_sale != '-1') {
3021 if ($search_sale == -2) {
3022 $sql .= " AND NOT EXISTS (SELECT sc.fk_soc FROM ".MAIN_DB_PREFIX."societe_commerciaux as sc WHERE sc.fk_soc = p.fk_soc)";
3023 } elseif ($search_sale > 0) {
3024 $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).")";
3025 }
3026 }
3027 // Search on socid
3028 if ($socid) {
3029 $sql .= " AND p.fk_soc = ".((int) $socid);
3030 }
3031 if ($draft) {
3032 $sql .= " AND p.fk_statut = ".((int) self::STATUS_DRAFT);
3033 }
3034 if ($notcurrentuser > 0) {
3035 $sql .= " AND p.fk_user_author <> ".((int) $user->id);
3036 }
3037 $sql .= $this->db->order($sortfield, $sortorder);
3038 $sql .= $this->db->plimit($limit, $offset);
3039
3040 $result = $this->db->query($sql);
3041 if ($result) {
3042 $num = $this->db->num_rows($result);
3043 if ($num) {
3044 $i = 0;
3045 while ($i < $num) {
3046 $obj = $this->db->fetch_object($result);
3047
3048 if ($shortlist == 1) {
3049 $ga[$obj->propalid] = $obj->ref;
3050 } elseif ($shortlist == 2) {
3051 $ga[$obj->propalid] = $obj->ref.' ('.$obj->name.')';
3052 } else {
3053 $ga[$i]['id'] = $obj->propalid;
3054 $ga[$i]['ref'] = $obj->ref;
3055 $ga[$i]['name'] = $obj->name;
3056 }
3057
3058 $i++;
3059 }
3060 }
3061 return $ga;
3062 } else {
3063 dol_print_error($this->db);
3064 return -1;
3065 }
3066 }
3067
3073 public function getInvoiceArrayList()
3074 {
3075 return $this->InvoiceArrayList($this->id);
3076 }
3077
3078 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3085 public function InvoiceArrayList($id)
3086 {
3087 // phpcs:enable
3088 $ga = array();
3089 $linkedInvoices = array();
3090
3091 $this->fetchObjectLinked($id, $this->element);
3092 foreach ($this->linkedObjectsIds as $objecttype => $objectid) {
3093 // Nouveau système du common object renvoi des rowid et non un id linéaire de 1 à n
3094 // On parcourt donc une liste d'objets en tant qu'objet unique
3095 foreach ($objectid as $key => $object) {
3096 // Cas des factures liees directement
3097 if ($objecttype == 'facture') {
3098 $linkedInvoices[] = $object;
3099 } else {
3100 // Cas des factures liees par un autre object (ex: commande)
3101 $this->fetchObjectLinked($object, $objecttype);
3102 foreach ($this->linkedObjectsIds as $subobjecttype => $subobjectid) {
3103 foreach ($subobjectid as $subkey => $subobject) {
3104 if ($subobjecttype == 'facture') {
3105 $linkedInvoices[] = $subobject;
3106 }
3107 }
3108 }
3109 }
3110 }
3111 }
3112
3113 if (count($linkedInvoices) > 0) {
3114 $sql = "SELECT rowid as facid, ref, total_ht as total, datef as df, fk_user_author, fk_statut, paye";
3115 $sql .= " FROM ".MAIN_DB_PREFIX."facture";
3116 $sql .= " WHERE rowid IN (".$this->db->sanitize(implode(',', $linkedInvoices)).")";
3117
3118 dol_syslog(get_class($this)."::InvoiceArrayList", LOG_DEBUG);
3119 $resql = $this->db->query($sql);
3120
3121 if ($resql) {
3122 $tab_sqlobj = array();
3123 $nump = $this->db->num_rows($resql);
3124 for ($i = 0; $i < $nump; $i++) {
3125 $sqlobj = $this->db->fetch_object($resql);
3126 $tab_sqlobj[] = $sqlobj;
3127 }
3128 $this->db->free($resql);
3129
3130 $nump = count($tab_sqlobj);
3131
3132 if ($nump) {
3133 $i = 0;
3134 while ($i < $nump) {
3135 $obj = array_shift($tab_sqlobj);
3136
3137 $ga[$i] = $obj;
3138
3139 $i++;
3140 }
3141 }
3142 return $ga;
3143 } else {
3144 return -1;
3145 }
3146 } else {
3147 return $ga;
3148 }
3149 }
3150
3158 public function delete($user, $notrigger = 0)
3159 {
3160 global $conf;
3161 require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
3162
3163 $error = 0;
3164
3165 $this->db->begin();
3166
3167 if (!$notrigger) {
3168 // Call trigger
3169 $result = $this->call_trigger('PROPAL_DELETE', $user);
3170 if ($result < 0) {
3171 $error++;
3172 }
3173 // End call triggers
3174 }
3175
3176 // Delete extrafields of lines and lines
3177 if (!$error && !empty($this->table_element_line)) {
3178 $tabletodelete = $this->table_element_line;
3179 $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).")";
3180 $sql = "DELETE FROM ".MAIN_DB_PREFIX.$tabletodelete." WHERE ".$this->fk_element." = ".((int) $this->id);
3181 if (!$this->db->query($sqlef) || !$this->db->query($sql)) {
3182 $error++;
3183 $this->error = $this->db->lasterror();
3184 $this->errors[] = $this->error;
3185 dol_syslog(get_class($this)."::delete error ".$this->error, LOG_ERR);
3186 }
3187 }
3188
3189 if (!$error) {
3190 // Delete linked object
3191 $res = $this->deleteObjectLinked();
3192 if ($res < 0) {
3193 $error++;
3194 }
3195 }
3196
3197 if (!$error) {
3198 // Delete linked contacts
3199 $res = $this->delete_linked_contact();
3200 if ($res < 0) {
3201 $error++;
3202 }
3203 }
3204
3205 // Removed extrafields of object
3206 if (!$error) {
3207 $result = $this->deleteExtraFields();
3208 if ($result < 0) {
3209 $error++;
3210 dol_syslog(get_class($this)."::delete error ".$this->error, LOG_ERR);
3211 }
3212 }
3213
3214 // Delete main record
3215 if (!$error) {
3216 $sql = "DELETE FROM ".MAIN_DB_PREFIX.$this->table_element." WHERE rowid = ".((int) $this->id);
3217 $res = $this->db->query($sql);
3218 if (!$res) {
3219 $error++;
3220 $this->error = $this->db->lasterror();
3221 $this->errors[] = $this->error;
3222 dol_syslog(get_class($this)."::delete error ".$this->error, LOG_ERR);
3223 }
3224 }
3225
3226 // Delete record into ECM index and physically
3227 if (!$error) {
3228 $res = $this->deleteEcmFiles(0); // Deleting files physically is done later with the dol_delete_dir_recursive
3229 $res = $this->deleteEcmFiles(1); // Deleting files physically is done later with the dol_delete_dir_recursive
3230 if (!$res) {
3231 $error++;
3232 }
3233 }
3234
3235 if (!$error) {
3236 // We remove directory
3237 $ref = dol_sanitizeFileName($this->ref);
3238 if ($conf->propal->multidir_output[$this->entity] && !empty($this->ref)) {
3239 $dir = $conf->propal->multidir_output[$this->entity]."/".$ref;
3240 $file = $dir."/".$ref.".pdf";
3241 if (file_exists($file)) {
3242 dol_delete_preview($this);
3243
3244 if (!dol_delete_file($file, 0, 0, 0, $this)) {
3245 $this->error = 'ErrorFailToDeleteFile';
3246 $this->errors[] = $this->error;
3247 $this->db->rollback();
3248 return 0;
3249 }
3250 }
3251 if (file_exists($dir)) {
3252 $res = @dol_delete_dir_recursive($dir); // delete files physically + into ecm tables
3253 if (!$res) {
3254 $this->error = 'ErrorFailToDeleteDir';
3255 $this->errors[] = $this->error;
3256 $this->db->rollback();
3257 return 0;
3258 }
3259 }
3260 }
3261 }
3262
3263 if (!$error) {
3264 dol_syslog(get_class($this)."::delete ".$this->id." by ".$user->id, LOG_DEBUG);
3265 $this->db->commit();
3266 return 1;
3267 } else {
3268 $this->db->rollback();
3269 return -1;
3270 }
3271 }
3272
3281 public function availability($availability_id, $notrigger = 0)
3282 {
3283 global $user;
3284
3285 if ($this->status >= self::STATUS_DRAFT) {
3286 $error = 0;
3287
3288 $this->db->begin();
3289
3290 $sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element;
3291 $sql .= ' SET fk_availability = '.((int) $availability_id);
3292 $sql .= ' WHERE rowid='.((int) $this->id);
3293
3294 dol_syslog(__METHOD__.' availability('.$availability_id.')', LOG_DEBUG);
3295 $resql = $this->db->query($sql);
3296 if (!$resql) {
3297 $this->errors[] = $this->db->error();
3298 $error++;
3299 }
3300
3301 if (!$error) {
3302 $this->oldcopy = clone $this;
3303 $this->availability_id = $availability_id;
3304 }
3305
3306 if (!$notrigger && empty($error)) {
3307 // Call trigger
3308 $result = $this->call_trigger('PROPAL_MODIFY', $user);
3309 if ($result < 0) {
3310 $error++;
3311 }
3312 // End call triggers
3313 }
3314
3315 if (!$error) {
3316 $this->db->commit();
3317 return 1;
3318 } else {
3319 foreach ($this->errors as $errmsg) {
3320 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
3321 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
3322 }
3323 $this->db->rollback();
3324 return -1 * $error;
3325 }
3326 } else {
3327 $error_str = 'Propal status do not meet requirement '.$this->status;
3328 dol_syslog(__METHOD__.$error_str, LOG_ERR);
3329 $this->error = $error_str;
3330 $this->errors[] = $this->error;
3331 return -2;
3332 }
3333 }
3334
3335 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3344 public function demand_reason($demand_reason_id, $notrigger = 0)
3345 {
3346 // phpcs:enable
3347 global $user;
3348
3349 if ($this->status >= self::STATUS_DRAFT) {
3350 $error = 0;
3351
3352 $this->db->begin();
3353
3354 $sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element;
3355 $sql .= ' SET fk_input_reason = '.((int) $demand_reason_id);
3356 $sql .= ' WHERE rowid='.((int) $this->id);
3357
3358 dol_syslog(__METHOD__.' demand_reason('.$demand_reason_id.')', LOG_DEBUG);
3359 $resql = $this->db->query($sql);
3360 if (!$resql) {
3361 $this->errors[] = $this->db->error();
3362 $error++;
3363 }
3364
3365 if (!$error) {
3366 $this->oldcopy = clone $this;
3367 $this->demand_reason_id = $demand_reason_id;
3368 }
3369
3370 if (!$notrigger && empty($error)) {
3371 // Call trigger
3372 $result = $this->call_trigger('PROPAL_MODIFY', $user);
3373 if ($result < 0) {
3374 $error++;
3375 }
3376 // End call triggers
3377 }
3378
3379 if (!$error) {
3380 $this->db->commit();
3381 return 1;
3382 } else {
3383 foreach ($this->errors as $errmsg) {
3384 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
3385 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
3386 }
3387 $this->db->rollback();
3388 return -1 * $error;
3389 }
3390 } else {
3391 $error_str = 'Propal status do not meet requirement '.$this->status;
3392 dol_syslog(__METHOD__.$error_str, LOG_ERR);
3393 $this->error = $error_str;
3394 $this->errors[] = $this->error;
3395 return -2;
3396 }
3397 }
3398
3399
3406 public function info($id)
3407 {
3408 $sql = "SELECT c.rowid, ";
3409 $sql .= " c.datec, c.date_valid as datev, c.date_signature, c.date_cloture,";
3410 $sql .= " c.fk_user_author, c.fk_user_valid, c.fk_user_signature, c.fk_user_cloture";
3411 $sql .= " FROM ".MAIN_DB_PREFIX.$this->table_element." as c";
3412 $sql .= " WHERE c.rowid = ".((int) $id);
3413
3414 $result = $this->db->query($sql);
3415
3416 if ($result) {
3417 if ($this->db->num_rows($result)) {
3418 $obj = $this->db->fetch_object($result);
3419
3420 $this->id = $obj->rowid;
3421
3422 $this->date_creation = $this->db->jdate($obj->datec);
3423 $this->date_validation = $this->db->jdate($obj->datev);
3424 $this->date_signature = $this->db->jdate($obj->date_signature);
3425 $this->date_cloture = $this->db->jdate($obj->date_cloture);
3426
3427 $this->user_creation_id = $obj->fk_user_author;
3428 $this->user_validation_id = $obj->fk_user_valid;
3429
3430 if ($obj->fk_user_signature) {
3431 $user_signature = new User($this->db);
3432 $user_signature->fetch($obj->fk_user_signature);
3433 $this->user_signature = $user_signature;
3434 }
3435
3436 $this->user_closing_id = $obj->fk_user_cloture;
3437 }
3438 $this->db->free($result);
3439 } else {
3440 dol_print_error($this->db);
3441 }
3442 }
3443
3444
3451 public function getLibStatut($mode = 0)
3452 {
3453 return $this->LibStatut($this->status, $mode);
3454 }
3455
3456 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3464 public function LibStatut($status, $mode = 1)
3465 {
3466 // phpcs:enable
3467 global $hookmanager;
3468
3469 // Init/load array of translation of status
3470 if (empty($this->labelStatus) || empty($this->labelStatusShort)) {
3471 global $langs;
3472 $langs->load("propal");
3473 $this->labelStatus[-1] = $langs->transnoentitiesnoconv("PropalStatusCanceled");
3474 $this->labelStatus[0] = $langs->transnoentitiesnoconv("PropalStatusDraft");
3475 $this->labelStatus[1] = $langs->transnoentitiesnoconv("PropalStatusValidated");
3476 $this->labelStatus[2] = $langs->transnoentitiesnoconv("PropalStatusSigned");
3477 $this->labelStatus[3] = $langs->transnoentitiesnoconv("PropalStatusNotSigned");
3478 $this->labelStatus[4] = $langs->transnoentitiesnoconv("PropalStatusBilled");
3479 $this->labelStatusShort[-1] = $langs->transnoentitiesnoconv("PropalStatusCanceledShort");
3480 $this->labelStatusShort[0] = $langs->transnoentitiesnoconv("PropalStatusDraftShort");
3481 $this->labelStatusShort[1] = $langs->transnoentitiesnoconv("PropalStatusValidatedShort");
3482 $this->labelStatusShort[2] = $langs->transnoentitiesnoconv("PropalStatusSignedShort");
3483 $this->labelStatusShort[3] = $langs->transnoentitiesnoconv("PropalStatusNotSignedShort");
3484 $this->labelStatusShort[4] = $langs->transnoentitiesnoconv("PropalStatusBilledShort");
3485 }
3486
3487 $statusType = '';
3488 if ($status == self::STATUS_CANCELED) {
3489 $statusType = 'status9';
3490 } elseif ($status == self::STATUS_DRAFT) {
3491 $statusType = 'status0';
3492 } elseif ($status == self::STATUS_VALIDATED) {
3493 $statusType = 'status1';
3494 } elseif ($status == self::STATUS_SIGNED) {
3495 $statusType = 'status4';
3496 } elseif ($status == self::STATUS_NOTSIGNED) {
3497 $statusType = 'status9';
3498 } elseif ($status == self::STATUS_BILLED) {
3499 $statusType = 'status6';
3500 }
3501
3502 $parameters = array('status' => $status, 'mode' => $mode);
3503 $reshook = $hookmanager->executeHooks('LibStatut', $parameters, $this); // Note that $action and $object may have been modified by hook
3504
3505 if ($reshook > 0) {
3506 return $hookmanager->resPrint;
3507 }
3508
3509 return dolGetStatus($this->labelStatus[$status], $this->labelStatusShort[$status], '', $statusType, $mode);
3510 }
3511
3512
3513 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3521 public function load_board($user, $mode)
3522 {
3523 // phpcs:enable
3524 global $conf, $langs;
3525
3526 $sql = "SELECT p.rowid, p.ref, p.datec as datec, p.fin_validite as datefin, p.total_ht";
3527 $sql .= " FROM ".MAIN_DB_PREFIX.$this->table_element." as p";
3528 $sql .= " WHERE p.entity IN (".getEntity('propal').")";
3529 if ($mode == 'opened') {
3530 $sql .= " AND p.fk_statut = ".self::STATUS_VALIDATED;
3531 }
3532 if ($mode == 'signed') {
3533 $sql .= " AND p.fk_statut = ".self::STATUS_SIGNED;
3534 }
3535 // If the internal user must only see his customers, force searching by him
3536 $search_sale = 0;
3537 if (empty($user->socid) && !$user->hasRight('societe', 'client', 'voir')) {
3538 $search_sale = $user->id;
3539 }
3540 // Search on sale representative
3541 if ($search_sale && $search_sale != '-1') {
3542 if ($search_sale == -2) {
3543 $sql .= " AND NOT EXISTS (SELECT sc.fk_soc FROM ".MAIN_DB_PREFIX."societe_commerciaux as sc WHERE sc.fk_soc = p.fk_soc)";
3544 } elseif ($search_sale > 0) {
3545 $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).")";
3546 }
3547 }
3548 if ($user->socid) {
3549 $sql .= " AND p.fk_soc = ".((int) $user->socid);
3550 }
3551
3552 $resql = $this->db->query($sql);
3553 if ($resql) {
3554 $langs->load("propal");
3555 $now = dol_now();
3556
3557 $delay_warning = 0;
3558 $status = 0;
3559 $label = $labelShort = '';
3560 if ($mode == 'opened') {
3561 $delay_warning = $conf->propal->cloture->warning_delay;
3562 $status = self::STATUS_VALIDATED;
3563 $label = $langs->transnoentitiesnoconv("PropalsToClose");
3564 $labelShort = $langs->transnoentitiesnoconv("ToAcceptRefuse");
3565 }
3566 if ($mode == 'signed') {
3567 $delay_warning = $conf->propal->facturation->warning_delay;
3568 $status = self::STATUS_SIGNED;
3569 $label = $langs->trans("PropalsToBill"); // We set here bill but may be billed or ordered
3570 $labelShort = $langs->trans("ToBill");
3571 }
3572
3573 $response = new WorkboardResponse();
3574 $response->warning_delay = $delay_warning / 60 / 60 / 24;
3575 $response->label = $label;
3576 $response->labelShort = $labelShort;
3577 $response->url = DOL_URL_ROOT.'/comm/propal/list.php?search_status='.$status.'&mainmenu=commercial&leftmenu=propals';
3578 $response->url_late = DOL_URL_ROOT.'/comm/propal/list.php?search_option=late&mainmenu=commercial&leftmenu=propals&sortfield=p.datep&sortorder=asc';
3579 $response->img = img_object('', "propal");
3580
3581 // This assignment in condition is not a bug. It allows walking the results.
3582 while ($obj = $this->db->fetch_object($resql)) {
3583 $response->nbtodo++;
3584 $response->total += $obj->total_ht;
3585
3586 if ($mode == 'opened') {
3587 $datelimit = $this->db->jdate($obj->datefin);
3588 if ($datelimit < ($now - $delay_warning)) {
3589 $response->nbtodolate++;
3590 }
3591 }
3592 // TODO Definir regle des propales a facturer en retard
3593 // if ($mode == 'signed' && ! count($this->FactureListeArray($obj->rowid))) $this->nbtodolate++;
3594 }
3595
3596 return $response;
3597 } else {
3598 $this->error = $this->db->error();
3599 return -1;
3600 }
3601 }
3602
3603
3611 public function initAsSpecimen()
3612 {
3613 global $conf, $langs;
3614
3615 // Load array of products prodids
3616 $num_prods = 0;
3617 $prodids = array();
3618 $sql = "SELECT rowid";
3619 $sql .= " FROM ".MAIN_DB_PREFIX."product";
3620 $sql .= " WHERE entity IN (".getEntity('product').")";
3621 $sql .= $this->db->plimit(100);
3622
3623 $resql = $this->db->query($sql);
3624 if ($resql) {
3625 $num_prods = $this->db->num_rows($resql);
3626 $i = 0;
3627 while ($i < $num_prods) {
3628 $i++;
3629 $row = $this->db->fetch_row($resql);
3630 $prodids[$i] = $row[0];
3631 }
3632 }
3633
3634 // Initialise parameters
3635 $this->id = 0;
3636 $this->ref = 'SPECIMEN';
3637 $this->ref_client = 'NEMICEPS';
3638 $this->specimen = 1;
3639 $this->socid = 1;
3640 $this->date = time();
3641 $this->fin_validite = $this->date + 3600 * 24 * 30;
3642 $this->cond_reglement_id = 1;
3643 $this->cond_reglement_code = 'RECEP';
3644 $this->mode_reglement_id = 7;
3645 $this->mode_reglement_code = 'CHQ';
3646 $this->availability_id = 1;
3647 $this->availability_code = 'AV_NOW';
3648 $this->demand_reason_id = 1;
3649 $this->demand_reason_code = 'SRC_00';
3650 $this->note_public = 'This is a comment (public)';
3651 $this->note_private = 'This is a comment (private)';
3652
3653 $this->multicurrency_tx = 1;
3654 $this->multicurrency_code = $conf->currency;
3655
3656 // Lines
3657 $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)
3658 $xnbp = 0;
3659 while ($xnbp < $nbp) {
3660 $line = new PropaleLigne($this->db);
3661 $line->desc = $langs->trans("Description")." ".$xnbp;
3662 $line->qty = 1;
3663 $line->subprice = 100;
3664 $line->price = 100;
3665 $line->tva_tx = 20;
3666 $line->localtax1_tx = 0;
3667 $line->localtax2_tx = 0;
3668 if ($xnbp == 2) {
3669 $line->total_ht = 50;
3670 $line->total_ttc = 60;
3671 $line->total_tva = 10;
3672 $line->remise_percent = 50;
3673 } else {
3674 $line->total_ht = 100;
3675 $line->total_ttc = 120;
3676 $line->total_tva = 20;
3677 $line->remise_percent = 00;
3678 }
3679
3680 if ($num_prods > 0) {
3681 $prodid = mt_rand(1, $num_prods);
3682 $line->fk_product = $prodids[$prodid];
3683 $line->product_ref = 'SPECIMEN';
3684 }
3685
3686 $this->lines[$xnbp] = $line;
3687
3688 $this->total_ht += $line->total_ht;
3689 $this->total_tva += $line->total_tva;
3690 $this->total_ttc += $line->total_ttc;
3691
3692 $xnbp++;
3693 }
3694
3695 return 1;
3696 }
3697
3703 public function loadStateBoard()
3704 {
3705 global $user;
3706
3707 $this->nb = array();
3708
3709 $sql = "SELECT count(p.rowid) as nb";
3710 $sql .= " FROM ".MAIN_DB_PREFIX.$this->table_element." as p";
3711 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe as s ON p.fk_soc = s.rowid";
3712 $sql .= " WHERE p.entity IN (".getEntity('propal').")";
3713
3714 // If the internal user must only see his customers, force searching by him
3715 $search_sale = 0;
3716 if (empty($user->socid) && !$user->hasRight('societe', 'client', 'voir')) {
3717 $search_sale = $user->id;
3718 }
3719 // Search on sale representative
3720 if ($search_sale && $search_sale != '-1') {
3721 if ($search_sale == -2) {
3722 $sql .= " AND NOT EXISTS (SELECT sc.fk_soc FROM ".MAIN_DB_PREFIX."societe_commerciaux as sc WHERE sc.fk_soc = p.fk_soc)";
3723 } elseif ($search_sale > 0) {
3724 $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).")";
3725 }
3726 }
3727
3728 $resql = $this->db->query($sql);
3729 if ($resql) {
3730 // This assignment in condition is not a bug. It allows walking the results.
3731 while ($obj = $this->db->fetch_object($resql)) {
3732 $this->nb["proposals"] = $obj->nb;
3733 }
3734 $this->db->free($resql);
3735 return 1;
3736 } else {
3737 dol_print_error($this->db);
3738 $this->error = $this->db->error();
3739 return -1;
3740 }
3741 }
3742
3743
3751 public function getNextNumRef($soc)
3752 {
3753 global $conf, $langs;
3754 $langs->load("propal");
3755
3756 $classname = getDolGlobalString('PROPALE_ADDON');
3757
3758 if (!empty($classname)) {
3759 $mybool = false;
3760
3761 $file = $classname.".php";
3762
3763 // Include file with class
3764 $dirmodels = array_merge(array('/'), (array) $conf->modules_parts['models']);
3765 foreach ($dirmodels as $reldir) {
3766 $dir = dol_buildpath($reldir."core/modules/propale/");
3767
3768 // Load file with numbering class (if found)
3769 $mybool = ((bool) @include_once $dir.$file) || $mybool;
3770 }
3771
3772 if (!$mybool) {
3773 dol_print_error(null, "Failed to include file ".$file);
3774 return '';
3775 }
3776
3777 $obj = new $classname();
3778 '@phan-var-force ModeleNumRefPropales $obj';
3779
3780 $numref = $obj->getNextValue($soc, $this);
3781
3782 if ($numref != "") {
3783 return $numref;
3784 } else {
3785 $this->error = $obj->error;
3786 //dol_print_error($db,"Propale::getNextNumRef ".$obj->error);
3787 return "";
3788 }
3789 } else {
3790 $langs->load("errors");
3791 print $langs->trans("Error")." ".$langs->trans("ErrorModuleSetupNotComplete", $langs->transnoentitiesnoconv("Proposal"));
3792 return "";
3793 }
3794 }
3795
3802 public function getTooltipContentArray($params)
3803 {
3804 global $conf, $langs, $user;
3805
3806 $langs->load('propal');
3807 $datas = [];
3808 $nofetch = !empty($params['nofetch']);
3809
3810 if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
3811 return ['optimize' => $langs->trans("Proposal")];
3812 }
3813 if ($user->hasRight('propal', 'lire')) {
3814 $datas['picto'] = img_picto('', $this->picto, '', 0, 0, 0, '', 'paddingrightonly').'<u>'.$langs->trans("Proposal").'</u>';
3815 if (isset($this->status)) {
3816 $datas['status'] = ' '.$this->getLibStatut(5);
3817 }
3818 if (!empty($this->ref)) {
3819 $datas['ref'] = '<br><b>'.$langs->trans('Ref').':</b> '.$this->ref;
3820 }
3821 if (!$nofetch) {
3822 $langs->load('companies');
3823 if (empty($this->thirdparty)) {
3824 $this->fetch_thirdparty();
3825 }
3826 $datas['customer'] = '<br><b>'.$langs->trans('Customer').':</b> '.$this->thirdparty->getNomUrl(1, '', 0, 1);
3827 }
3828 if (!empty($this->ref_customer)) {
3829 $datas['refcustomer'] = '<br><b>'.$langs->trans('RefCustomer').':</b> '.$this->ref_customer;
3830 }
3831 if (!$nofetch) {
3832 $langs->load('project');
3833 if (is_null($this->project) || (is_object($this->project) && $this->project->isEmpty())) {
3834 $res = $this->fetchProject();
3835 if ($res > 0 && $this->project instanceof Project) {
3836 $datas['project'] = '<br><b>'.$langs->trans('Project').':</b> '.$this->project->getNomUrl(1, '', 0, '1');
3837 }
3838 }
3839 }
3840 if (!empty($this->total_ht)) {
3841 $datas['amountht'] = '<br><b>'.$langs->trans('AmountHT').':</b> '.price($this->total_ht, 0, $langs, 0, -1, -1, $conf->currency);
3842 }
3843 if (!empty($this->total_tva)) {
3844 $datas['vat'] = '<br><b>'.$langs->trans('VAT').':</b> '.price($this->total_tva, 0, $langs, 0, -1, -1, $conf->currency);
3845 }
3846 if (!empty($this->total_ttc)) {
3847 $datas['amountttc'] = '<br><b>'.$langs->trans('AmountTTC').':</b> '.price($this->total_ttc, 0, $langs, 0, -1, -1, $conf->currency);
3848 }
3849 if (!empty($this->date)) {
3850 $datas['date'] = '<br><b>'.$langs->trans('Date').':</b> '.dol_print_date($this->date, 'day');
3851 }
3852 if (!empty($this->date_signature)) {
3853 $datas['datesignature'] = '<br><b>'.$langs->trans('DateSigning').':</b> '.dol_print_date($this->date_signature, 'day');
3854 }
3855 if (!empty($this->delivery_date)) {
3856 $datas['deliverydate'] = '<br><b>'.$langs->trans('DeliveryDate').':</b> '.dol_print_date($this->delivery_date, 'dayhour');
3857 }
3858 }
3859
3860 return $datas;
3861 }
3862
3874 public function getNomUrl($withpicto = 0, $option = '', $get_params = '', $notooltip = 0, $save_lastsearch_value = -1, $addlinktonotes = -1)
3875 {
3876 global $langs, $conf, $user, $hookmanager;
3877
3878 if (!empty($conf->dol_no_mouse_hover)) {
3879 $notooltip = 1; // Force disable tooltips
3880 }
3881
3882 $result = '';
3883 $params = [
3884 'id' => $this->id,
3885 'objecttype' => $this->element,
3886 'option' => $option,
3887 'nofetch' => 1,
3888 ];
3889 $classfortooltip = 'classfortooltip';
3890 $dataparams = '';
3891 if (getDolGlobalInt('MAIN_ENABLE_AJAX_TOOLTIP')) {
3892 $classfortooltip = 'classforajaxtooltip';
3893 $dataparams = ' data-params="'.dol_escape_htmltag(json_encode($params)).'"';
3894 $label = '';
3895 } else {
3896 $label = implode($this->getTooltipContentArray($params));
3897 }
3898
3899 $url = '';
3900 if ($user->hasRight('propal', 'lire')) {
3901 if ($option == '') {
3902 $url = DOL_URL_ROOT.'/comm/propal/card.php?id='.$this->id.$get_params;
3903 } elseif ($option == 'compta') { // deprecated
3904 $url = DOL_URL_ROOT.'/comm/propal/card.php?id='.$this->id.$get_params;
3905 } elseif ($option == 'expedition') {
3906 $url = DOL_URL_ROOT.'/expedition/propal.php?id='.$this->id.$get_params;
3907 } elseif ($option == 'document') {
3908 $url = DOL_URL_ROOT.'/comm/propal/document.php?id='.$this->id.$get_params;
3909 }
3910
3911 if ($option != 'nolink') {
3912 // Add param to save lastsearch_values or not
3913 $add_save_lastsearch_values = ($save_lastsearch_value == 1 ? 1 : 0);
3914 if ($save_lastsearch_value == -1 && isset($_SERVER["PHP_SELF"]) && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) {
3915 $add_save_lastsearch_values = 1;
3916 }
3917 if ($add_save_lastsearch_values) {
3918 $url .= '&save_lastsearch_values=1';
3919 }
3920 }
3921 }
3922
3923 $linkclose = '';
3924 if (empty($notooltip) && $user->hasRight('propal', 'lire')) {
3925 if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
3926 $label = $langs->trans("Proposal");
3927 $linkclose .= ' alt="'.dolPrintHTMLForAttribute($label).'"';
3928 }
3929 $linkclose .= ($label ? ' title="'.dolPrintHTMLForAttribute($label).'"' : ' title="tocomplete"');
3930 $linkclose .= $dataparams.' class="'.$classfortooltip.'"';
3931 }
3932
3933 $linkstart = '<a href="'.$url.'"';
3934 $linkstart .= $linkclose.'>';
3935 $linkend = '</a>';
3936
3937 $result .= $linkstart;
3938 if ($withpicto) {
3939 $result .= img_object(($notooltip ? '' : $label), ($this->picto ? $this->picto : 'generic'), (($withpicto != 2) ? 'class="paddingright"' : ''), 0, 0, $notooltip ? 0 : 1);
3940 }
3941 if ($withpicto != 2) {
3942 $result .= $this->ref;
3943 }
3944 $result .= $linkend;
3945
3946 if ($addlinktonotes >= 0) {
3947 $txttoshow = '';
3948
3949 if ($addlinktonotes == 0) {
3950 if (!empty($this->note_private) || !empty($this->note_public)) {
3951 $txttoshow = $langs->trans('ViewPrivateNote');
3952 }
3953 } elseif ($addlinktonotes == 1) {
3954 if (!empty($this->note_private)) {
3955 $txttoshow .= ($user->socid > 0 ? '' : dol_string_nohtmltag($this->note_private, 1));
3956 }
3957 } elseif ($addlinktonotes == 2) {
3958 if (!empty($this->note_public)) {
3959 $txttoshow .= dol_string_nohtmltag($this->note_public, 1);
3960 }
3961 } elseif ($addlinktonotes == 3) {
3962 if ($user->socid > 0) {
3963 if (!empty($this->note_public)) {
3964 $txttoshow .= dol_string_nohtmltag($this->note_public, 1);
3965 }
3966 } else {
3967 if (!empty($this->note_public)) {
3968 $txttoshow .= dol_string_nohtmltag($this->note_public, 1);
3969 }
3970 if (!empty($this->note_private)) {
3971 if (!empty($txttoshow)) {
3972 $txttoshow .= '<br><br>';
3973 }
3974 $txttoshow .= dol_string_nohtmltag($this->note_private, 1);
3975 }
3976 }
3977 }
3978
3979 if ($txttoshow) {
3980 $result .= ' <span class="note inline-block">';
3981 $result .= '<a href="'.DOL_URL_ROOT.'/comm/propal/note.php?id='.$this->id.'" class="classfortooltip" title="'.dol_escape_htmltag($txttoshow).'">';
3982 $result .= img_picto('', 'note');
3983 $result .= '</a>';
3984 $result .= '</span>';
3985 }
3986 }
3987
3988 global $action;
3989 $hookmanager->initHooks(array($this->element . 'dao'));
3990 $parameters = array('id' => $this->id, 'getnomurl' => &$result);
3991 $reshook = $hookmanager->executeHooks('getNomUrl', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
3992 if ($reshook > 0) {
3993 $result = $hookmanager->resPrint;
3994 } else {
3995 $result .= $hookmanager->resPrint;
3996 }
3997 return $result;
3998 }
3999
4006 public function getLinesArray($sqlforgedfilters = '')
4007 {
4008 return $this->fetch_lines(0, 0, $sqlforgedfilters);
4009 }
4010
4022 public function generateDocument($modele, $outputlangs, $hidedetails = 0, $hidedesc = 0, $hideref = 0, $moreparams = null)
4023 {
4024 $outputlangs->loadLangs(array("propale", "products"));
4025
4026 if (!dol_strlen($modele)) {
4027 $modele = 'azur';
4028
4029 if ($this->model_pdf) {
4030 $modele = $this->model_pdf;
4031 } elseif (getDolGlobalString('PROPALE_ADDON_PDF')) {
4032 $modele = getDolGlobalString('PROPALE_ADDON_PDF');
4033 }
4034 }
4035
4036 $modelpath = "core/modules/propale/doc/";
4037
4038 return $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams);
4039 }
4040
4049 public static function replaceThirdparty(DoliDB $dbs, $origin_id, $dest_id)
4050 {
4051 $tables = array(
4052 'propal'
4053 );
4054
4055 return CommonObject::commonReplaceThirdparty($dbs, $origin_id, $dest_id, $tables);
4056 }
4057
4066 public static function replaceProduct(DoliDB $db, $origin_id, $dest_id)
4067 {
4068 $tables = array(
4069 'propaldet'
4070 );
4071
4072 return CommonObject::commonReplaceProduct($db, $origin_id, $dest_id, $tables);
4073 }
4074
4082 public function getKanbanView($option = '', $arraydata = null)
4083 {
4084 global $langs;
4085
4086 $selected = (empty($arraydata['selected']) ? 0 : $arraydata['selected']);
4087
4088 $return = '<div class="box-flex-item box-flex-grow-zero">';
4089 $return .= '<div class="info-box info-box-sm">';
4090 $return .= '<div class="info-box-icon bg-infobox-action">';
4091 $return .= img_picto('', $this->picto);
4092 $return .= '</div>';
4093 $return .= '<div class="info-box-content">';
4094 $return .= '<span class="info-box-ref inline-block tdoverflowmax150 valignmiddle">'.(method_exists($this, 'getNomUrl') ? $this->getNomUrl() : $this->ref).'</span>';
4095 if ($selected >= 0) {
4096 $return .= '<input id="cb'.$this->id.'" class="flat checkforselect fright" type="checkbox" name="toselect[]" value="'.$this->id.'"'.($selected ? ' checked="checked"' : '').'>';
4097 }
4098 if (!empty($arraydata['projectlink'])) {
4099 $return .= '<span class="info-box-ref"> | '.$arraydata['projectlink'].'</span>';
4100 }
4101 $return .= '<br>';
4102 if (property_exists($this, 'thirdparty') && is_object($this->thirdparty)) {
4103 $return .= '<div class="info-box-ref tdoverflowmax150">'.$this->thirdparty->getNomUrl(1).'</div>';
4104 }
4105 if (property_exists($this, 'total_ht')) {
4106 $return .= '<span class="info-box-label amount" title="'.$langs->trans("AmountHT").'">'.price($this->total_ht).'</span>';
4107 }
4108 if (!empty($arraydata['authorlink'])) {
4109 $return .= ' &nbsp; <span class="info-box-label">'.$arraydata['authorlink'].'</span>';
4110 }
4111 if (method_exists($this, 'getLibStatut')) {
4112 $return .= '<br><div class="info-box-status">'.$this->getLibStatut(3).'</div>';
4113 }
4114 $return .= '</div>';
4115 $return .= '</div>';
4116 $return .= '</div>';
4117 return $return;
4118 }
4119}
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