dolibarr 18.0.8
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-2021 Frédéric France <frederic.france@netlogic.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) 2025 William Mead <william@m34d.com>
22 *
23 * This program is free software; you can redistribute it and/or modify
24 * it under the terms of the GNU General Public License as published by
25 * the Free Software Foundation; either version 3 of the License, or
26 * (at your option) any later version.
27 *
28 * This program is distributed in the hope that it will be useful,
29 * but WITHOUT ANY WARRANTY; without even the implied warranty of
30 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
31 * GNU General Public License for more details.
32 *
33 * You should have received a copy of the GNU General Public License
34 * along with this program. If not, see <https://www.gnu.org/licenses/>.
35 */
36
42require_once DOL_DOCUMENT_ROOT.'/core/class/commonobject.class.php';
43require_once DOL_DOCUMENT_ROOT."/core/class/commonobjectline.class.php";
44require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
45require_once DOL_DOCUMENT_ROOT.'/contact/class/contact.class.php';
46require_once DOL_DOCUMENT_ROOT.'/margin/lib/margins.lib.php';
47require_once DOL_DOCUMENT_ROOT.'/multicurrency/class/multicurrency.class.php';
48require_once DOL_DOCUMENT_ROOT.'/core/class/commonincoterm.class.php';
49
53class Propal extends CommonObject
54{
56
60 public $code = "";
61
65 public $element = 'propal';
66
70 public $table_element = 'propal';
71
75 public $table_element_line = 'propaldet';
76
80 public $fk_element = 'fk_propal';
81
85 public $picto = 'propal';
86
91 public $ismultientitymanaged = 1;
92
97 public $restrictiononfksoc = 1;
98
102 protected $table_ref_field = 'ref';
103
108 public $socid;
109
114 public $contactid;
115 public $author;
116
121 public $ref_client;
122
129 public $statut;
130
136 public $status;
137
142 public $datec;
143
147 public $date_creation;
148
153 public $datev;
154
158 public $date_validation;
159
163 public $date_signature;
164
168 public $user_signature;
169
173 public $date;
174
179 public $datep;
180
185 public $date_livraison; // deprecated; Use delivery_date instead.
186
190 public $delivery_date; // Date expected of shipment (date starting shipment, not the reception that occurs some days after)
191
192
193 public $fin_validite;
194
195 public $user_author_id;
196 public $user_close_id;
197
202 public $price;
207 public $tva;
212 public $total;
213
214 public $cond_reglement_code;
215 public $cond_reglement_doc;
216 public $mode_reglement_code;
217
218 public $deposit_percent;
219
224
228 public $remise;
233
238 public $fk_address;
239
240 public $address_type;
241 public $address;
242
243 public $availability_id;
244 public $availability_code;
245
246 public $duree_validite;
247
248 public $demand_reason_id;
249 public $demand_reason_code;
250
251 public $warehouse_id;
252
253 public $extraparams = array();
254
258 public $lines = array();
259 public $line;
260
261 public $labelStatus = array();
262 public $labelStatusShort = array();
263
264 // Multicurrency
268 public $fk_multicurrency;
269
270 public $multicurrency_code;
271 public $multicurrency_tx;
272 public $multicurrency_total_ht;
273 public $multicurrency_total_tva;
274 public $multicurrency_total_ttc;
275 public $multicurrency_total_localtax1; // not in database
276 public $multicurrency_total_localtax2; // not in database
277
278
303 // BEGIN MODULEBUILDER PROPERTIES
307 public $fields = array(
308 'rowid' =>array('type'=>'integer', 'label'=>'TechnicalID', 'enabled'=>1, 'visible'=>-1, 'notnull'=>1, 'position'=>10),
309 'entity' =>array('type'=>'integer', 'label'=>'Entity', 'default'=>1, 'enabled'=>1, 'visible'=>-2, 'notnull'=>1, 'position'=>15, 'index'=>1),
310 'ref' =>array('type'=>'varchar(30)', 'label'=>'Ref', 'enabled'=>1, 'visible'=>-1, 'notnull'=>1, 'showoncombobox'=>1, 'position'=>20),
311 'ref_client' =>array('type'=>'varchar(255)', 'label'=>'RefCustomer', 'enabled'=>1, 'visible'=>-1, 'position'=>22),
312 'ref_ext' =>array('type'=>'varchar(255)', 'label'=>'RefExt', 'enabled'=>1, 'visible'=>0, 'position'=>40),
313 'fk_soc' =>array('type'=>'integer:Societe:societe/class/societe.class.php', 'label'=>'ThirdParty', 'enabled'=>'isModEnabled("societe")', 'visible'=>-1, 'position'=>23),
314 '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),
315 'tms' =>array('type'=>'timestamp', 'label'=>'DateModification', 'enabled'=>1, 'visible'=>-1, 'notnull'=>1, 'position'=>25),
316 'datec' =>array('type'=>'datetime', 'label'=>'DateCreation', 'enabled'=>1, 'visible'=>-1, 'position'=>55),
317 'datep' =>array('type'=>'date', 'label'=>'Date', 'enabled'=>1, 'visible'=>-1, 'position'=>60),
318 'fin_validite' =>array('type'=>'datetime', 'label'=>'DateEnd', 'enabled'=>1, 'visible'=>-1, 'position'=>65),
319 'date_valid' =>array('type'=>'datetime', 'label'=>'DateValidation', 'enabled'=>1, 'visible'=>-1, 'position'=>70),
320 'date_cloture' =>array('type'=>'datetime', 'label'=>'DateClosing', 'enabled'=>1, 'visible'=>-1, 'position'=>75),
321 'fk_user_author' =>array('type'=>'integer:User:user/class/user.class.php', 'label'=>'Fk user author', 'enabled'=>1, 'visible'=>-1, 'position'=>80),
322 'fk_user_modif' =>array('type'=>'integer:User:user/class/user.class.php', 'label'=>'UserModif', 'enabled'=>1, 'visible'=>-2, 'notnull'=>-1, 'position'=>85),
323 'fk_user_valid' =>array('type'=>'integer:User:user/class/user.class.php', 'label'=>'UserValidation', 'enabled'=>1, 'visible'=>-1, 'position'=>90),
324 'fk_user_cloture' =>array('type'=>'integer:User:user/class/user.class.php', 'label'=>'Fk user cloture', 'enabled'=>1, 'visible'=>-1, 'position'=>95),
325 'price' =>array('type'=>'double', 'label'=>'Price', 'enabled'=>1, 'visible'=>-1, 'position'=>105),
326 //'remise_percent' =>array('type'=>'double', 'label'=>'RelativeDiscount', 'enabled'=>1, 'visible'=>-1, 'position'=>110),
327 //'remise_absolue' =>array('type'=>'double', 'label'=>'CustomerRelativeDiscount', 'enabled'=>1, 'visible'=>-1, 'position'=>115),
328 //'remise' =>array('type'=>'double', 'label'=>'Remise', 'enabled'=>1, 'visible'=>-1, 'position'=>120),
329 'total_ht' =>array('type'=>'double(24,8)', 'label'=>'TotalHT', 'enabled'=>1, 'visible'=>-1, 'position'=>125, 'isameasure'=>1),
330 'total_tva' =>array('type'=>'double(24,8)', 'label'=>'VAT', 'enabled'=>1, 'visible'=>-1, 'position'=>130, 'isameasure'=>1),
331 'localtax1' =>array('type'=>'double(24,8)', 'label'=>'LocalTax1', 'enabled'=>1, 'visible'=>-1, 'position'=>135, 'isameasure'=>1),
332 'localtax2' =>array('type'=>'double(24,8)', 'label'=>'LocalTax2', 'enabled'=>1, 'visible'=>-1, 'position'=>140, 'isameasure'=>1),
333 'total_ttc' =>array('type'=>'double(24,8)', 'label'=>'TotalTTC', 'enabled'=>1, 'visible'=>-1, 'position'=>145, 'isameasure'=>1),
334 'fk_account' =>array('type'=>'integer', 'label'=>'BankAccount', 'enabled'=>'isModEnabled("banque")', 'visible'=>-1, 'position'=>150),
335 'fk_currency' =>array('type'=>'varchar(3)', 'label'=>'Currency', 'enabled'=>1, 'visible'=>-1, 'position'=>155),
336 'fk_cond_reglement' =>array('type'=>'integer', 'label'=>'PaymentTerm', 'enabled'=>1, 'visible'=>-1, 'position'=>160),
337 'deposit_percent' =>array('type'=>'varchar(63)', 'label'=>'DepositPercent', 'enabled'=>1, 'visible'=>-1, 'position'=>161),
338 'fk_mode_reglement' =>array('type'=>'integer', 'label'=>'PaymentMode', 'enabled'=>1, 'visible'=>-1, 'position'=>165),
339 'note_private' =>array('type'=>'html', 'label'=>'NotePrivate', 'enabled'=>1, 'visible'=>0, 'position'=>170),
340 'note_public' =>array('type'=>'html', 'label'=>'NotePublic', 'enabled'=>1, 'visible'=>0, 'position'=>175),
341 'model_pdf' =>array('type'=>'varchar(255)', 'label'=>'PDFTemplate', 'enabled'=>1, 'visible'=>0, 'position'=>180),
342 'date_livraison' =>array('type'=>'date', 'label'=>'DateDeliveryPlanned', 'enabled'=>1, 'visible'=>-1, 'position'=>185),
343 'fk_shipping_method' =>array('type'=>'integer', 'label'=>'ShippingMethod', 'enabled'=>1, 'visible'=>-1, 'position'=>190),
344 'fk_warehouse' =>array('type'=>'integer:Entrepot:product/stock/class/entrepot.class.php', 'label'=>'Fk warehouse', 'enabled'=>'isModEnabled("stock")', 'visible'=>-1, 'position'=>191),
345 'fk_availability' =>array('type'=>'integer', 'label'=>'Availability', 'enabled'=>1, 'visible'=>-1, 'position'=>195),
346 'fk_delivery_address' =>array('type'=>'integer', 'label'=>'DeliveryAddress', 'enabled'=>1, 'visible'=>0, 'position'=>200), // deprecated
347 'fk_input_reason' =>array('type'=>'integer', 'label'=>'InputReason', 'enabled'=>1, 'visible'=>-1, 'position'=>205),
348 'extraparams' =>array('type'=>'varchar(255)', 'label'=>'Extraparams', 'enabled'=>1, 'visible'=>-1, 'position'=>215),
349 'fk_incoterms' =>array('type'=>'integer', 'label'=>'IncotermCode', 'enabled'=>'$conf->incoterm->enabled', 'visible'=>-1, 'position'=>220),
350 'location_incoterms' =>array('type'=>'varchar(255)', 'label'=>'IncotermLabel', 'enabled'=>'$conf->incoterm->enabled', 'visible'=>-1, 'position'=>225),
351 'fk_multicurrency' =>array('type'=>'integer', 'label'=>'MulticurrencyID', 'enabled'=>1, 'visible'=>-1, 'position'=>230),
352 'multicurrency_code' =>array('type'=>'varchar(255)', 'label'=>'MulticurrencyCurrency', 'enabled'=>'isModEnabled("multicurrency")', 'visible'=>-1, 'position'=>235),
353 'multicurrency_tx' =>array('type'=>'double(24,8)', 'label'=>'MulticurrencyRate', 'enabled'=>'isModEnabled("multicurrency")', 'visible'=>-1, 'position'=>240, 'isameasure'=>1),
354 'multicurrency_total_ht' =>array('type'=>'double(24,8)', 'label'=>'MulticurrencyAmountHT', 'enabled'=>'isModEnabled("multicurrency")', 'visible'=>-1, 'position'=>245, 'isameasure'=>1),
355 'multicurrency_total_tva' =>array('type'=>'double(24,8)', 'label'=>'MulticurrencyAmountVAT', 'enabled'=>'isModEnabled("multicurrency")', 'visible'=>-1, 'position'=>250, 'isameasure'=>1),
356 'multicurrency_total_ttc' =>array('type'=>'double(24,8)', 'label'=>'MulticurrencyAmountTTC', 'enabled'=>'isModEnabled("multicurrency")', 'visible'=>-1, 'position'=>255, 'isameasure'=>1),
357 'last_main_doc' =>array('type'=>'varchar(255)', 'label'=>'LastMainDoc', 'enabled'=>1, 'visible'=>-1, 'position'=>260),
358 'fk_statut' =>array('type'=>'smallint(6)', 'label'=>'Status', 'enabled'=>1, 'visible'=>-1, 'notnull'=>1, 'position'=>500),
359 'import_key' =>array('type'=>'varchar(14)', 'label'=>'ImportId', 'enabled'=>1, 'visible'=>-2, 'position'=>900),
360 );
361 // END MODULEBUILDER PROPERTIES
362
366 const STATUS_DRAFT = 0;
374 const STATUS_SIGNED = 2;
382 const STATUS_BILLED = 4; // Todo rename into STATUS_CLOSE ?
383
384
392 public function __construct($db, $socid = 0, $propalid = 0)
393 {
394 global $conf, $langs;
395
396 $this->db = $db;
397
398 $this->socid = $socid;
399 $this->id = $propalid;
400
401 $this->duree_validite = getDolGlobalInt('PROPALE_VALIDITY_DURATION', 0);
402 }
403
404
405 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
417 public function add_product($idproduct, $qty, $remise_percent = 0)
418 {
419 // phpcs:enable
420 global $conf, $mysoc;
421
422 if (!$qty) {
423 $qty = 1;
424 }
425
426 dol_syslog(get_class($this)."::add_product $idproduct, $qty, $remise_percent");
427 if ($idproduct > 0) {
428 $prod = new Product($this->db);
429 $prod->fetch($idproduct);
430
431 $productdesc = $prod->description;
432
433 $tva_tx = get_default_tva($mysoc, $this->thirdparty, $prod->id);
434 $tva_npr = get_default_npr($mysoc, $this->thirdparty, $prod->id);
435 if (empty($tva_tx)) {
436 $tva_npr = 0;
437 }
438 $vat_src_code = ''; // May be defined into tva_tx
439
440 $localtax1_tx = get_localtax($tva_tx, 1, $mysoc, $this->thirdparty, $tva_npr);
441 $localtax2_tx = get_localtax($tva_tx, 2, $mysoc, $this->thirdparty, $tva_npr);
442
443 // multiprices
444 if ($conf->global->PRODUIT_MULTIPRICES && $this->thirdparty->price_level) {
445 $price = $prod->multiprices[$this->thirdparty->price_level];
446 } else {
447 $price = $prod->price;
448 }
449
450 $line = new PropaleLigne($this->db);
451
452 $line->fk_product = $idproduct;
453 $line->desc = $productdesc;
454 $line->qty = $qty;
455 $line->subprice = $price;
456 $line->remise_percent = $remise_percent;
457 $line->vat_src_code = $vat_src_code;
458 $line->tva_tx = $tva_tx;
459 $line->fk_unit = $prod->fk_unit;
460 if ($tva_npr) {
461 $line->info_bits = 1;
462 }
463
464 $this->lines[] = $line;
465 }
466
467 return 1;
468 }
469
470 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
477 public function insert_discount($idremise)
478 {
479 // phpcs:enable
480 global $langs;
481
482 include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
483 include_once DOL_DOCUMENT_ROOT.'/core/class/discount.class.php';
484
485 $this->db->begin();
486
487 $remise = new DiscountAbsolute($this->db);
488 $result = $remise->fetch($idremise);
489
490 if ($result > 0) {
491 if ($remise->fk_facture) { // Protection against multiple submission
492 $this->error = $langs->trans("ErrorDiscountAlreadyUsed");
493 $this->db->rollback();
494 return -5;
495 }
496
497 $line = new PropaleLigne($this->db);
498
499 $this->line->context = $this->context;
500
501 $line->fk_propal = $this->id;
502 $line->fk_remise_except = $remise->id;
503 $line->desc = $remise->description; // Description ligne
504 $line->vat_src_code = $remise->vat_src_code;
505 $line->tva_tx = $remise->tva_tx;
506 $line->subprice = -$remise->amount_ht;
507 $line->fk_product = 0; // Id produit predefined
508 $line->qty = 1;
509 $line->remise_percent = 0;
510 $line->rang = -1;
511 $line->info_bits = 2;
512
513 // TODO deprecated
514 $line->price = -$remise->amount_ht;
515
516 $line->total_ht = -$remise->amount_ht;
517 $line->total_tva = -$remise->amount_tva;
518 $line->total_ttc = -$remise->amount_ttc;
519
520 $result = $line->insert();
521 if ($result > 0) {
522 $result = $this->update_price(1);
523 if ($result > 0) {
524 $this->db->commit();
525 return 1;
526 } else {
527 $this->db->rollback();
528 return -1;
529 }
530 } else {
531 $this->error = $line->error;
532 $this->errors = $line->errors;
533 $this->db->rollback();
534 return -2;
535 }
536 } else {
537 $this->db->rollback();
538 return -2;
539 }
540 }
541
579 public function 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 = 0, $fk_unit = null, $origin = '', $origin_id = 0, $pu_ht_devise = 0, $fk_remise_except = 0, $noupdateafterinsertline = 0)
580 {
581 global $mysoc, $conf, $langs;
582
583 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);
584
585 if ($this->statut == self::STATUS_DRAFT) {
586 include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
587
588 // Clean parameters
589 if (empty($remise_percent)) {
590 $remise_percent = 0;
591 }
592 if (empty($qty)) {
593 $qty = 0;
594 }
595 if (empty($info_bits)) {
596 $info_bits = 0;
597 }
598 if (empty($rang)) {
599 $rang = 0;
600 }
601 if (empty($fk_parent_line) || $fk_parent_line < 0) {
602 $fk_parent_line = 0;
603 }
604
606 $qty = price2num($qty);
607 $pu_ht = price2num($pu_ht);
608 $pu_ht_devise = price2num($pu_ht_devise);
609 $pu_ttc = price2num($pu_ttc);
610 if (!preg_match('/\‍((.*)\‍)/', $txtva)) {
611 $txtva = price2num($txtva); // $txtva can have format '5,1' or '5.1' or '5.1(XXX)', we must clean only if '5,1'
612 }
613 $txlocaltax1 = price2num($txlocaltax1);
614 $txlocaltax2 = price2num($txlocaltax2);
615 $pa_ht = price2num($pa_ht);
616 if ($price_base_type == 'HT') {
617 $pu = $pu_ht;
618 } else {
619 $pu = $pu_ttc;
620 }
621
622 // Check parameters
623 if ($type < 0) {
624 return -1;
625 }
626
627 if ($date_start && $date_end && $date_start > $date_end) {
628 $langs->load("errors");
629 $this->error = $langs->trans('ErrorStartDateGreaterEnd');
630 return -1;
631 }
632
633 $this->db->begin();
634
635 $product_type = $type;
636 if (!empty($fk_product) && $fk_product > 0) {
637 $product = new Product($this->db);
638 $result = $product->fetch($fk_product);
639 $product_type = $product->type;
640
641 if (!empty($conf->global->STOCK_MUST_BE_ENOUGH_FOR_PROPOSAL) && $product_type == 0 && $product->stock_reel < $qty) {
642 $langs->load("errors");
643 $this->error = $langs->trans('ErrorStockIsNotEnoughToAddProductOnProposal', $product->ref);
644 $this->db->rollback();
645 return -3;
646 }
647 }
648
649 // Calcul du total TTC et de la TVA pour la ligne a partir de
650 // qty, pu, remise_percent et txtva
651 // TRES IMPORTANT: C'est au moment de l'insertion ligne qu'on doit stocker
652 // la part ht, tva et ttc, et ce au niveau de la ligne qui a son propre taux tva.
653
654 $localtaxes_type = getLocalTaxesFromRate($txtva, 0, $this->thirdparty, $mysoc);
655
656 // Clean vat code
657 $reg = array();
658 $vat_src_code = '';
659 $reg = array();
660 if (preg_match('/\‍((.*)\‍)/', $txtva, $reg)) {
661 $vat_src_code = $reg[1];
662 $txtva = preg_replace('/\s*\‍(.*\‍)/', '', $txtva); // Remove code into vatrate.
663 }
664
665 $tabprice = calcul_price_total($qty, $pu, $remise_percent, $txtva, $txlocaltax1, $txlocaltax2, 0, $price_base_type, $info_bits, $product_type, $mysoc, $localtaxes_type, 100, $this->multicurrency_tx, $pu_ht_devise);
666
667 $total_ht = $tabprice[0];
668 $total_tva = $tabprice[1];
669 $total_ttc = $tabprice[2];
670 $total_localtax1 = $tabprice[9];
671 $total_localtax2 = $tabprice[10];
672 $pu_ht = $tabprice[3];
673 $pu_tva = $tabprice[4];
674 $pu_ttc = $tabprice[5];
675
676 // MultiCurrency
677 $multicurrency_total_ht = $tabprice[16];
678 $multicurrency_total_tva = $tabprice[17];
679 $multicurrency_total_ttc = $tabprice[18];
680 $pu_ht_devise = $tabprice[19];
681
682 // Rang to use
683 $ranktouse = $rang;
684 if ($ranktouse == -1) {
685 $rangmax = $this->line_max($fk_parent_line);
686 $ranktouse = $rangmax + 1;
687 }
688
689 // TODO A virer
690 // Anciens indicateurs: $price, $remise (a ne plus utiliser)
691 $price = $pu;
692 $remise = 0;
693 if ($remise_percent > 0) {
694 $remise = round(($pu * $remise_percent / 100), 2);
695 $price = $pu - $remise;
696 }
697
698 // Insert line
699 $this->line = new PropaleLigne($this->db);
700
701 $this->line->context = $this->context;
702
703 $this->line->fk_propal = $this->id;
704 $this->line->label = $label;
705 $this->line->desc = $desc;
706 $this->line->qty = $qty;
707
708 $this->line->vat_src_code = $vat_src_code;
709 $this->line->tva_tx = $txtva;
710 $this->line->localtax1_tx = ($total_localtax1 ? $localtaxes_type[1] : 0);
711 $this->line->localtax2_tx = ($total_localtax2 ? $localtaxes_type[3] : 0);
712 $this->line->localtax1_type = empty($localtaxes_type[0]) ? '' : $localtaxes_type[0];
713 $this->line->localtax2_type = empty($localtaxes_type[2]) ? '' : $localtaxes_type[2];
714 $this->line->fk_product = $fk_product;
715 $this->line->product_type = $type;
716 $this->line->fk_remise_except = $fk_remise_except;
717 $this->line->remise_percent = $remise_percent;
718 $this->line->subprice = $pu_ht;
719 $this->line->rang = $ranktouse;
720 $this->line->info_bits = $info_bits;
721 $this->line->total_ht = $total_ht;
722 $this->line->total_tva = $total_tva;
723 $this->line->total_localtax1 = $total_localtax1;
724 $this->line->total_localtax2 = $total_localtax2;
725 $this->line->total_ttc = $total_ttc;
726 $this->line->special_code = $special_code;
727 $this->line->fk_parent_line = $fk_parent_line;
728 $this->line->fk_unit = $fk_unit;
729
730 $this->line->date_start = $date_start;
731 $this->line->date_end = $date_end;
732
733 $this->line->fk_fournprice = $fk_fournprice;
734 $this->line->pa_ht = $pa_ht;
735
736 $this->line->origin_id = $origin_id;
737 $this->line->origin = $origin;
738
739 // Multicurrency
740 $this->line->fk_multicurrency = $this->fk_multicurrency;
741 $this->line->multicurrency_code = $this->multicurrency_code;
742 $this->line->multicurrency_subprice = $pu_ht_devise;
743 $this->line->multicurrency_total_ht = $multicurrency_total_ht;
744 $this->line->multicurrency_total_tva = $multicurrency_total_tva;
745 $this->line->multicurrency_total_ttc = $multicurrency_total_ttc;
746
747 // Mise en option de la ligne
748 if (empty($qty) && empty($special_code)) {
749 $this->line->special_code = 3;
750 }
751
752 // TODO deprecated
753 $this->line->price = $price;
754
755 if (is_array($array_options) && count($array_options) > 0) {
756 $this->line->array_options = $array_options;
757 }
758
759 $result = $this->line->insert();
760 if ($result > 0) {
761 // Reorder if child line
762 if (!empty($fk_parent_line)) {
763 $this->line_order(true, 'DESC');
764 } elseif ($ranktouse > 0 && $ranktouse <= count($this->lines)) { // Update all rank of all other lines
765 $linecount = count($this->lines);
766 for ($ii = $ranktouse; $ii <= $linecount; $ii++) {
767 $this->updateRangOfLine($this->lines[$ii - 1]->id, $ii + 1);
768 }
769 }
770
771 // Mise a jour informations denormalisees au niveau de la propale meme
772 if (empty($noupdateafterinsertline)) {
773 $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.
774 }
775
776 if ($result > 0) {
777 $this->db->commit();
778 return $this->line->id;
779 } else {
780 $this->error = $this->db->error();
781 $this->db->rollback();
782 return -1;
783 }
784 } else {
785 $this->error = $this->line->error;
786 $this->errors = $this->line->errors;
787 $this->db->rollback();
788 return -2;
789 }
790 } else {
791 dol_syslog(get_class($this)."::addline status of proposal must be Draft to allow use of ->addline()", LOG_ERR);
792 return -3;
793 }
794 }
795
796
826 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 = 0, $fk_unit = null, $pu_ht_devise = 0, $notrigger = 0, $rang = 0)
827 {
828 global $mysoc, $langs;
829
830 dol_syslog(get_class($this)."::updateLine rowid=$rowid, pu=$pu, qty=$qty, remise_percent=$remise_percent,
831 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");
832 include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
833
834 // Clean parameters
836 $qty = price2num($qty);
837 $pu = price2num($pu);
838 $pu_ht_devise = price2num($pu_ht_devise);
839 if (!preg_match('/\‍((.*)\‍)/', $txtva)) {
840 $txtva = price2num($txtva); // $txtva can have format '5.0(XXX)' or '5'
841 }
842 $txlocaltax1 = price2num($txlocaltax1);
843 $txlocaltax2 = price2num($txlocaltax2);
844 $pa_ht = price2num($pa_ht);
845 if (empty($qty) && empty($special_code)) {
846 $special_code = 3; // Set option tag
847 }
848 if (!empty($qty) && $special_code == 3) {
849 $special_code = 0; // Remove option tag
850 }
851 if (empty($type)) {
852 $type = 0;
853 }
854
855 if ($date_start && $date_end && $date_start > $date_end) {
856 $langs->load("errors");
857 $this->error = $langs->trans('ErrorStartDateGreaterEnd');
858 return -1;
859 }
860
861 if ($this->statut == self::STATUS_DRAFT) {
862 $this->db->begin();
863
864 // Calcul du total TTC et de la TVA pour la ligne a partir de
865 // qty, pu, remise_percent et txtva
866 // TRES IMPORTANT: C'est au moment de l'insertion ligne qu'on doit stocker
867 // la part ht, tva et ttc, et ce au niveau de la ligne qui a son propre taux tva.
868
869 $localtaxes_type = getLocalTaxesFromRate($txtva, 0, $this->thirdparty, $mysoc);
870
871 // Clean vat code
872 $reg = array();
873 $vat_src_code = '';
874 if (preg_match('/\‍((.*)\‍)/', $txtva, $reg)) {
875 $vat_src_code = $reg[1];
876 $txtva = preg_replace('/\s*\‍(.*\‍)/', '', $txtva); // Remove code into vatrate.
877 }
878
879 // TODO Implement if (getDolGlobalInt('MAIN_UNIT_PRICE_WITH_TAX_IS_FOR_ALL_TAXES')) ?
880
881 $tabprice = calcul_price_total($qty, $pu, $remise_percent, $txtva, $txlocaltax1, $txlocaltax2, 0, $price_base_type, $info_bits, $type, $mysoc, $localtaxes_type, 100, $this->multicurrency_tx, $pu_ht_devise);
882 $total_ht = $tabprice[0];
883 $total_tva = $tabprice[1];
884 $total_ttc = $tabprice[2];
885 $total_localtax1 = $tabprice[9];
886 $total_localtax2 = $tabprice[10];
887 $pu_ht = $tabprice[3];
888 $pu_tva = $tabprice[4];
889 $pu_ttc = $tabprice[5];
890
891 // MultiCurrency
892 $multicurrency_total_ht = $tabprice[16];
893 $multicurrency_total_tva = $tabprice[17];
894 $multicurrency_total_ttc = $tabprice[18];
895 $pu_ht_devise = $tabprice[19];
896
897 // Anciens indicateurs: $price, $remise (a ne plus utiliser)
898 $price = $pu;
899 $remise = 0;
900 if ($remise_percent > 0) {
901 $remise = round(($pu * $remise_percent / 100), 2);
902 $price = $pu - $remise;
903 }
904
905 //Fetch current line from the database and then clone the object and set it in $oldline property
906 $line = new PropaleLigne($this->db);
907 $line->fetch($rowid);
908
909 $staticline = clone $line;
910
911 $line->oldline = $staticline;
912 $this->line = $line;
913 $this->line->context = $this->context;
914 $this->line->rang = $rang;
915
916 // Reorder if fk_parent_line change
917 if (!empty($fk_parent_line) && !empty($staticline->fk_parent_line) && $fk_parent_line != $staticline->fk_parent_line) {
918 $rangmax = $this->line_max($fk_parent_line);
919 $this->line->rang = $rangmax + 1;
920 }
921
922 $this->line->id = $rowid;
923 $this->line->label = $label;
924 $this->line->desc = $desc;
925 $this->line->qty = $qty;
926 $this->line->product_type = $type;
927 $this->line->vat_src_code = $vat_src_code;
928 $this->line->tva_tx = $txtva;
929 $this->line->localtax1_tx = $txlocaltax1;
930 $this->line->localtax2_tx = $txlocaltax2;
931 $this->line->localtax1_type = empty($localtaxes_type[0]) ? '' : $localtaxes_type[0];
932 $this->line->localtax2_type = empty($localtaxes_type[2]) ? '' : $localtaxes_type[2];
933 $this->line->remise_percent = $remise_percent;
934 $this->line->subprice = $pu_ht;
935 $this->line->info_bits = $info_bits;
936
937 $this->line->total_ht = $total_ht;
938 $this->line->total_tva = $total_tva;
939 $this->line->total_localtax1 = $total_localtax1;
940 $this->line->total_localtax2 = $total_localtax2;
941 $this->line->total_ttc = $total_ttc;
942 $this->line->special_code = $special_code;
943 $this->line->fk_parent_line = $fk_parent_line;
944 $this->line->skip_update_total = $skip_update_total;
945 $this->line->fk_unit = $fk_unit;
946
947 $this->line->fk_fournprice = $fk_fournprice;
948 $this->line->pa_ht = $pa_ht;
949
950 $this->line->date_start = $date_start;
951 $this->line->date_end = $date_end;
952
953 if (is_array($array_options) && count($array_options) > 0) {
954 // We replace values in this->line->array_options only for entries defined into $array_options
955 foreach ($array_options as $key => $value) {
956 $this->line->array_options[$key] = $array_options[$key];
957 }
958 }
959
960 // Multicurrency
961 $this->line->multicurrency_subprice = $pu_ht_devise;
962 $this->line->multicurrency_total_ht = $multicurrency_total_ht;
963 $this->line->multicurrency_total_tva = $multicurrency_total_tva;
964 $this->line->multicurrency_total_ttc = $multicurrency_total_ttc;
965
966 $result = $this->line->update($notrigger);
967 if ($result > 0) {
968 // Reorder if child line
969 if (!empty($fk_parent_line)) {
970 $this->line_order(true, 'DESC');
971 }
972
973 $this->update_price(1, 'auto');
974
975 $this->fk_propal = $this->id;
976 $this->rowid = $rowid;
977
978 $this->db->commit();
979 return $result;
980 } else {
981 $this->error = $this->line->error;
982 $this->errors = $this->line->errors;
983 $this->db->rollback();
984 return -1;
985 }
986 } else {
987 dol_syslog(get_class($this)."::updateline Erreur -2 Propal en mode incompatible pour cette action");
988 return -2;
989 }
990 }
991
992
1000 public function deleteline($lineid, $id = 0)
1001 {
1002 global $user;
1003
1004 if ($this->statut == self::STATUS_DRAFT) {
1005 $this->db->begin();
1006
1007 $line = new PropaleLigne($this->db);
1008
1009 $line->context = $this->context;
1010
1011 // Load data
1012 $line->fetch($lineid);
1013
1014 if ($id > 0 && $line->fk_propal != $id) {
1015 $this->error = 'ErrorLineIDDoesNotMatchWithObjectID';
1016 return -1;
1017 }
1018
1019 // Memorize previous line for triggers
1020 $staticline = clone $line;
1021 $line->oldline = $staticline;
1022
1023 if ($line->delete($user) > 0) {
1024 $this->update_price(1);
1025
1026 $this->db->commit();
1027 return 1;
1028 } else {
1029 $this->error = $line->error;
1030 $this->errors = $line->errors;
1031 $this->db->rollback();
1032 return -1;
1033 }
1034 } else {
1035 $this->error = 'ErrorDeleteLineNotAllowedByObjectStatus';
1036 return -2;
1037 }
1038 }
1039
1040
1049 public function create($user, $notrigger = 0)
1050 {
1051 global $conf, $hookmanager, $mysoc;
1052 $error = 0;
1053
1054 $now = dol_now();
1055
1056 // Clean parameters
1057 if (empty($this->date)) {
1058 $this->date = $this->datep;
1059 }
1060 $this->fin_validite = $this->date + ($this->duree_validite * 24 * 3600);
1061 if (empty($this->availability_id)) {
1062 $this->availability_id = 0;
1063 }
1064 if (empty($this->demand_reason_id)) {
1065 $this->demand_reason_id = 0;
1066 }
1067
1068 // Multicurrency (test on $this->multicurrency_tx because we should take the default rate only if not using origin rate)
1069 if (!empty($this->multicurrency_code) && empty($this->multicurrency_tx)) {
1070 list($this->fk_multicurrency, $this->multicurrency_tx) = MultiCurrency::getIdAndTxFromCode($this->db, $this->multicurrency_code, $this->date);
1071 } else {
1072 $this->fk_multicurrency = MultiCurrency::getIdFromCode($this->db, $this->multicurrency_code);
1073 }
1074 if (empty($this->fk_multicurrency)) {
1075 $this->multicurrency_code = $conf->currency;
1076 $this->fk_multicurrency = 0;
1077 $this->multicurrency_tx = 1;
1078 }
1079
1080 // Set tmp vars
1081 $delivery_date = empty($this->delivery_date) ? $this->date_livraison : $this->delivery_date;
1082
1083 dol_syslog(get_class($this)."::create");
1084
1085 // Check parameters
1086 $result = $this->fetch_thirdparty();
1087 if ($result < 0) {
1088 $this->error = "Failed to fetch company";
1089 dol_syslog(get_class($this)."::create ".$this->error, LOG_ERR);
1090 return -3;
1091 }
1092
1093 // Check parameters
1094 if (!empty($this->ref)) { // We check that ref is not already used
1095 $result = self::isExistingObject($this->element, 0, $this->ref); // Check ref is not yet used
1096 if ($result > 0) {
1097 $this->error = 'ErrorRefAlreadyExists';
1098 dol_syslog(get_class($this)."::create ".$this->error, LOG_WARNING);
1099 $this->db->rollback();
1100 return -1;
1101 }
1102 }
1103
1104 if (empty($this->date)) {
1105 $this->error = "Date of proposal is required";
1106 dol_syslog(get_class($this)."::create ".$this->error, LOG_ERR);
1107 return -4;
1108 }
1109
1110
1111 $this->db->begin();
1112
1113 // Insert into database
1114 $sql = "INSERT INTO ".MAIN_DB_PREFIX."propal (";
1115 $sql .= "fk_soc";
1116 $sql .= ", price";
1117 $sql .= ", remise"; // deprecated
1118 $sql .= ", remise_percent"; // deprecated
1119 $sql .= ", remise_absolue"; // deprecated
1120 $sql .= ", total_tva";
1121 $sql .= ", total_ttc";
1122 $sql .= ", datep";
1123 $sql .= ", datec";
1124 $sql .= ", ref";
1125 $sql .= ", fk_user_author";
1126 $sql .= ", note_private";
1127 $sql .= ", note_public";
1128 $sql .= ", model_pdf";
1129 $sql .= ", fin_validite";
1130 $sql .= ", fk_cond_reglement";
1131 $sql .= ", deposit_percent";
1132 $sql .= ", fk_mode_reglement";
1133 $sql .= ", fk_account";
1134 $sql .= ", ref_client";
1135 $sql .= ", ref_ext";
1136 $sql .= ", date_livraison";
1137 $sql .= ", fk_shipping_method";
1138 $sql .= ", fk_warehouse";
1139 $sql .= ", fk_availability";
1140 $sql .= ", fk_input_reason";
1141 $sql .= ", fk_projet";
1142 $sql .= ", fk_incoterms";
1143 $sql .= ", location_incoterms";
1144 $sql .= ", entity";
1145 $sql .= ", fk_multicurrency";
1146 $sql .= ", multicurrency_code";
1147 $sql .= ", multicurrency_tx";
1148 $sql .= ") ";
1149 $sql .= " VALUES (";
1150 $sql .= $this->socid;
1151 $sql .= ", 0";
1152 $sql .= ", ".((float) $this->remise); // deprecated
1153 $sql .= ", ".($this->remise_percent ? ((float) $this->remise_percent) : 'NULL'); // deprecated
1154 $sql .= ", ".($this->remise_absolue ? ((float) $this->remise_absolue) : 'NULL'); // deprecated
1155 $sql .= ", 0";
1156 $sql .= ", 0";
1157 $sql .= ", '".$this->db->idate($this->date)."'";
1158 $sql .= ", '".$this->db->idate($now)."'";
1159 $sql .= ", '(PROV)'";
1160 $sql .= ", ".($user->id > 0 ? ((int) $user->id) : "NULL");
1161 $sql .= ", '".$this->db->escape($this->note_private)."'";
1162 $sql .= ", '".$this->db->escape($this->note_public)."'";
1163 $sql .= ", '".$this->db->escape($this->model_pdf)."'";
1164 $sql .= ", ".($this->fin_validite != '' ? "'".$this->db->idate($this->fin_validite)."'" : "NULL");
1165 $sql .= ", ".($this->cond_reglement_id > 0 ? ((int) $this->cond_reglement_id) : 'NULL');
1166 $sql .= ", ".(!empty($this->deposit_percent) ? "'".$this->db->escape($this->deposit_percent)."'" : 'NULL');
1167 $sql .= ", ".($this->mode_reglement_id > 0 ? ((int) $this->mode_reglement_id) : 'NULL');
1168 $sql .= ", ".($this->fk_account > 0 ? ((int) $this->fk_account) : 'NULL');
1169 $sql .= ", '".$this->db->escape($this->ref_client)."'";
1170 $sql .= ", '".$this->db->escape($this->ref_ext)."'";
1171 $sql .= ", ".(empty($delivery_date) ? "NULL" : "'".$this->db->idate($delivery_date)."'");
1172 $sql .= ", ".($this->shipping_method_id > 0 ? $this->shipping_method_id : 'NULL');
1173 $sql .= ", ".($this->warehouse_id > 0 ? $this->warehouse_id : 'NULL');
1174 $sql .= ", ".$this->availability_id;
1175 $sql .= ", ".$this->demand_reason_id;
1176 $sql .= ", ".($this->fk_project ? $this->fk_project : "null");
1177 $sql .= ", ".(int) $this->fk_incoterms;
1178 $sql .= ", '".$this->db->escape($this->location_incoterms)."'";
1179 $sql .= ", ".setEntity($this);
1180 $sql .= ", ".(int) $this->fk_multicurrency;
1181 $sql .= ", '".$this->db->escape($this->multicurrency_code)."'";
1182 $sql .= ", ".(double) $this->multicurrency_tx;
1183 $sql .= ")";
1184
1185 dol_syslog(get_class($this)."::create", LOG_DEBUG);
1186 $resql = $this->db->query($sql);
1187 if ($resql) {
1188 $this->id = $this->db->last_insert_id(MAIN_DB_PREFIX."propal");
1189
1190 if ($this->id) {
1191 $this->ref = '(PROV'.$this->id.')';
1192 $sql = 'UPDATE '.MAIN_DB_PREFIX."propal SET ref='".$this->db->escape($this->ref)."' WHERE rowid=".((int) $this->id);
1193
1194 dol_syslog(get_class($this)."::create", LOG_DEBUG);
1195 $resql = $this->db->query($sql);
1196 if (!$resql) {
1197 $error++;
1198 }
1199
1200 if (!empty($this->linkedObjectsIds) && empty($this->linked_objects)) { // To use new linkedObjectsIds instead of old linked_objects
1201 $this->linked_objects = $this->linkedObjectsIds; // TODO Replace linked_objects with linkedObjectsIds
1202 }
1203
1204 // Add object linked
1205 if (!$error && $this->id && !empty($this->linked_objects) && is_array($this->linked_objects)) {
1206 foreach ($this->linked_objects as $origin => $tmp_origin_id) {
1207 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, ...))
1208 foreach ($tmp_origin_id as $origin_id) {
1209 $ret = $this->add_object_linked($origin, $origin_id);
1210 if (!$ret) {
1211 $this->error = $this->db->lasterror();
1212 $error++;
1213 }
1214 }
1215 } else // Old behaviour, if linked_object has only one link per type, so is something like array('contract'=>id1))
1216 {
1217 $origin_id = $tmp_origin_id;
1218 $ret = $this->add_object_linked($origin, $origin_id);
1219 if (!$ret) {
1220 $this->error = $this->db->lasterror();
1221 $error++;
1222 }
1223 }
1224 }
1225 }
1226
1227 /*
1228 * Insertion du detail des produits dans la base
1229 * Insert products detail in database
1230 */
1231 if (!$error) {
1232 $fk_parent_line = 0;
1233 $num = count($this->lines);
1234
1235 for ($i = 0; $i < $num; $i++) {
1236 if (!is_object($this->lines[$i])) { // If this->lines is not array of objects, coming from REST API
1237 // Convert into object this->lines[$i].
1238 $line = (object) $this->lines[$i];
1239 } else {
1240 $line = $this->lines[$i];
1241 }
1242 // Reset fk_parent_line for line that are not child lines or special product
1243 if (($line->product_type != 9 && empty($line->fk_parent_line)) || $line->product_type == 9) {
1244 $fk_parent_line = 0;
1245 }
1246 // Complete vat rate with code
1247 $vatrate = $line->tva_tx;
1248 if ($line->vat_src_code && !preg_match('/\‍(.*\‍)/', $vatrate)) {
1249 $vatrate .= ' ('.$line->vat_src_code.')';
1250 }
1251
1252 if (!empty($conf->global->MAIN_CREATEFROM_KEEP_LINE_ORIGIN_INFORMATION)) {
1253 $originid = $line->origin_id;
1254 $origintype = $line->origin;
1255 } else {
1256 $originid = $line->id;
1257 $origintype = $this->element;
1258 }
1259
1260 $result = $this->addline(
1261 $line->desc,
1262 $line->subprice,
1263 $line->qty,
1264 $vatrate,
1265 $line->localtax1_tx,
1266 $line->localtax2_tx,
1267 $line->fk_product,
1268 $line->remise_percent,
1269 'HT',
1270 0,
1271 $line->info_bits,
1272 $line->product_type,
1273 $line->rang,
1274 $line->special_code,
1275 $fk_parent_line,
1276 $line->fk_fournprice,
1277 $line->pa_ht,
1278 $line->label,
1279 $line->date_start,
1280 $line->date_end,
1281 $line->array_options,
1282 $line->fk_unit,
1283 $origintype,
1284 $originid,
1285 0,
1286 0,
1287 1
1288 );
1289
1290 if ($result < 0) {
1291 $error++;
1292 $this->error = $this->db->error;
1293 dol_print_error($this->db);
1294 break;
1295 }
1296
1297 // Set the id on created row
1298 $line->id = $result;
1299
1300 // Defined the new fk_parent_line
1301 if ($result > 0 && $line->product_type == 9) {
1302 $fk_parent_line = $result;
1303 }
1304 }
1305 }
1306
1307 // Set delivery address
1308 /*if (! $error && $this->fk_delivery_address)
1309 {
1310 $sql = "UPDATE ".MAIN_DB_PREFIX."propal";
1311 $sql.= " SET fk_delivery_address = ".((int) $this->fk_delivery_address);
1312 $sql.= " WHERE ref = '".$this->db->escape($this->ref)."'";
1313 $sql.= " AND entity = ".setEntity($this);
1314
1315 $result=$this->db->query($sql);
1316 }*/
1317
1318 if (!$error) {
1319 // Mise a jour infos denormalisees
1320 $resql = $this->update_price(1, 'auto', 0, $mysoc);
1321 if ($resql) {
1322 $action = 'update';
1323
1324 // Actions on extra fields
1325 if (!$error) {
1326 $result = $this->insertExtraFields();
1327 if ($result < 0) {
1328 $error++;
1329 }
1330 }
1331
1332 if (!$error && !$notrigger) {
1333 // Call trigger
1334 $result = $this->call_trigger('PROPAL_CREATE', $user);
1335 if ($result < 0) {
1336 $error++;
1337 }
1338 // End call triggers
1339 }
1340 } else {
1341 $this->error = $this->db->lasterror();
1342 $error++;
1343 }
1344 }
1345 } else {
1346 $this->error = $this->db->lasterror();
1347 $error++;
1348 }
1349
1350 if (!$error) {
1351 $this->db->commit();
1352 dol_syslog(get_class($this)."::create done id=".$this->id);
1353 return $this->id;
1354 } else {
1355 $this->db->rollback();
1356 return -2;
1357 }
1358 } else {
1359 $this->error = $this->db->lasterror();
1360 $this->db->rollback();
1361 return -1;
1362 }
1363 }
1364
1375 public function createFromClone(User $user, $socid = 0, $forceentity = null, $update_prices = false, $update_desc = false)
1376 {
1377 global $conf, $hookmanager, $mysoc;
1378
1379 dol_include_once('/projet/class/project.class.php');
1380
1381 $error = 0;
1382 $now = dol_now();
1383
1384 dol_syslog(__METHOD__, LOG_DEBUG);
1385
1386 $object = new self($this->db);
1387
1388 $this->db->begin();
1389
1390 // Load source object
1391 $object->fetch($this->id);
1392
1393 $objsoc = new Societe($this->db);
1394
1395 // Change socid if needed
1396 if (!empty($socid) && $socid != $object->socid) {
1397 if ($objsoc->fetch($socid) > 0) {
1398 $object->socid = $objsoc->id;
1399 $object->cond_reglement_id = (!empty($objsoc->cond_reglement_id) ? $objsoc->cond_reglement_id : 0);
1400 $object->deposit_percent = (!empty($objsoc->deposit_percent) ? $objsoc->deposit_percent : null);
1401 $object->mode_reglement_id = (!empty($objsoc->mode_reglement_id) ? $objsoc->mode_reglement_id : 0);
1402 $object->fk_delivery_address = '';
1403
1404 /*if (isModEnabled('project'))
1405 {
1406 $project = new Project($db);
1407 if ($this->fk_project > 0 && $project->fetch($this->fk_project)) {
1408 if ($project->socid <= 0) $clonedObj->fk_project = $this->fk_project;
1409 else $clonedObj->fk_project = '';
1410 } else {
1411 $clonedObj->fk_project = '';
1412 }
1413 }*/
1414 $object->fk_project = ''; // A cloned proposal is set by default to no project.
1415 }
1416
1417 // reset ref_client
1418 if (!getDolGlobalString('MAIN_KEEP_REF_CUSTOMER_ON_CLONING')) {
1419 $object->ref_client = '';
1420 }
1421
1422 // TODO Change product price if multi-prices
1423 } else {
1424 $objsoc->fetch($object->socid);
1425 }
1426
1427 // update prices
1428 if ($update_prices === true || $update_desc === true) {
1429 if ($objsoc->id > 0 && !empty($object->lines)) {
1430 if ($update_prices === true && !empty($conf->global->PRODUIT_CUSTOMER_PRICES)) {
1431 // If price per customer
1432 require_once DOL_DOCUMENT_ROOT . '/product/class/productcustomerprice.class.php';
1433 }
1434
1435 foreach ($object->lines as $line) {
1436 $line->id = 0;
1437
1438 if ($line->fk_product > 0) {
1439 $prod = new Product($this->db);
1440 $res = $prod->fetch($line->fk_product);
1441 if ($res > 0) {
1442 if ($update_prices === true) {
1443 $pu_ht = $prod->price;
1444 $tva_tx = get_default_tva($mysoc, $objsoc, $prod->id);
1445 $remise_percent = $objsoc->remise_percent;
1446
1447 if (!empty($conf->global->PRODUIT_MULTIPRICES) && $objsoc->price_level > 0) {
1448 $pu_ht = $prod->multiprices[$objsoc->price_level];
1449 if (!empty($conf->global->PRODUIT_MULTIPRICES_USE_VAT_PER_LEVEL)) { // using this option is a bug. kept for backward compatibility
1450 if (isset($prod->multiprices_tva_tx[$objsoc->price_level])) {
1451 $tva_tx = $prod->multiprices_tva_tx[$objsoc->price_level];
1452 }
1453 }
1454 } elseif (!empty($conf->global->PRODUIT_CUSTOMER_PRICES)) {
1455 $prodcustprice = new Productcustomerprice($this->db);
1456 $filter = array('t.fk_product' => $prod->id, 't.fk_soc' => $objsoc->id);
1457 $result = $prodcustprice->fetchAll('', '', 0, 0, $filter);
1458 if ($result) {
1459 // If there is some prices specific to the customer
1460 if (count($prodcustprice->lines) > 0) {
1461 $pu_ht = price($prodcustprice->lines[0]->price);
1462 $tva_tx = ($prodcustprice->lines[0]->default_vat_code ? $prodcustprice->lines[0]->tva_tx.' ('.$prodcustprice->lines[0]->default_vat_code.' )' : $prodcustprice->lines[0]->tva_tx);
1463 if ($prodcustprice->lines[0]->default_vat_code && !preg_match('/\‍(.*\‍)/', $tva_tx)) {
1464 $tva_tx .= ' ('.$prodcustprice->lines[0]->default_vat_code.')';
1465 }
1466 }
1467 }
1468 }
1469
1470 $line->subprice = $pu_ht;
1471 $line->tva_tx = $tva_tx;
1472 $line->remise_percent = $remise_percent;
1473 }
1474 if ($update_desc === true) {
1475 $line->desc = $prod->description;
1476 }
1477 }
1478 }
1479 }
1480 }
1481 }
1482
1483 $object->id = 0;
1484 $object->ref = '';
1485 $object->entity = (!empty($forceentity) ? $forceentity : $object->entity);
1486 $object->statut = self::STATUS_DRAFT;
1487
1488 // Clear fields
1489 $object->user_author = $user->id;
1490
1491 $object->user_validation_id = 0;
1492
1493 $object->date = $now;
1494 $object->datep = $now; // deprecated
1495 $object->fin_validite = $object->date + ($object->duree_validite * 24 * 3600);
1496 if (empty($conf->global->MAIN_KEEP_REF_CUSTOMER_ON_CLONING)) {
1497 $object->ref_client = '';
1498 }
1499 if (getDolGlobalInt('MAIN_DONT_KEEP_NOTE_ON_CLONING') == 1) {
1500 $object->note_private = '';
1501 $object->note_public = '';
1502 }
1503 // Create clone
1504 $object->context['createfromclone'] = 'createfromclone';
1505 $result = $object->create($user);
1506 if ($result < 0) {
1507 $this->error = $object->error;
1508 $this->errors = array_merge($this->errors, $object->errors);
1509 $error++;
1510 }
1511
1512 if (!$error) {
1513 // copy internal contacts
1514 if ($object->copy_linked_contact($this, 'internal') < 0) {
1515 $error++;
1516 }
1517 }
1518
1519 if (!$error) {
1520 // copy external contacts if same company
1521 if ($this->socid == $object->socid) {
1522 if ($object->copy_linked_contact($this, 'external') < 0) {
1523 $error++;
1524 }
1525 }
1526 }
1527
1528 if (!$error) {
1529 // Hook of thirdparty module
1530 if (is_object($hookmanager)) {
1531 $parameters = array('objFrom'=>$this, 'clonedObj'=>$object);
1532 $action = '';
1533 $reshook = $hookmanager->executeHooks('createFrom', $parameters, $object, $action); // Note that $action and $object may have been modified by some hooks
1534 if ($reshook < 0) {
1535 $this->setErrorsFromObject($hookmanager);
1536 $error++;
1537 }
1538 }
1539 }
1540
1541 unset($object->context['createfromclone']);
1542
1543 // End
1544 if (!$error) {
1545 $this->db->commit();
1546 return $object->id;
1547 } else {
1548 $this->db->rollback();
1549 return -1;
1550 }
1551 }
1552
1562 public function fetch($rowid, $ref = '', $ref_ext = '', $forceentity = 0)
1563 {
1564 $sql = "SELECT p.rowid, p.ref, p.entity, p.remise, p.remise_percent, p.remise_absolue, p.fk_soc";
1565 $sql .= ", p.total_ttc, p.total_tva, p.localtax1, p.localtax2, p.total_ht";
1566 $sql .= ", p.datec";
1567 $sql .= ", p.date_signature as dates";
1568 $sql .= ", p.date_valid as datev";
1569 $sql .= ", p.datep as dp";
1570 $sql .= ", p.fin_validite as dfv";
1571 $sql .= ", p.date_livraison as delivery_date";
1572 $sql .= ", p.model_pdf, p.last_main_doc, p.ref_client, ref_ext, p.extraparams";
1573 $sql .= ", p.note_private, p.note_public";
1574 $sql .= ", p.fk_projet as fk_project, p.fk_statut";
1575 $sql .= ", p.fk_user_author, p.fk_user_valid, p.fk_user_cloture";
1576 $sql .= ", p.fk_delivery_address";
1577 $sql .= ", p.fk_availability";
1578 $sql .= ", p.fk_input_reason";
1579 $sql .= ", p.fk_cond_reglement";
1580 $sql .= ", p.fk_mode_reglement";
1581 $sql .= ', p.fk_account';
1582 $sql .= ", p.fk_shipping_method";
1583 $sql .= ", p.fk_warehouse";
1584 $sql .= ", p.fk_incoterms, p.location_incoterms";
1585 $sql .= ", p.fk_multicurrency, p.multicurrency_code, p.multicurrency_tx, p.multicurrency_total_ht, p.multicurrency_total_tva, p.multicurrency_total_ttc";
1586 $sql .= ", p.tms as date_modification";
1587 $sql .= ", i.libelle as label_incoterms";
1588 $sql .= ", c.label as statut_label";
1589 $sql .= ", ca.code as availability_code, ca.label as availability";
1590 $sql .= ", dr.code as demand_reason_code, dr.label as demand_reason";
1591 $sql .= ", cr.code as cond_reglement_code, cr.libelle as cond_reglement, cr.libelle_facture as cond_reglement_libelle_doc, p.deposit_percent";
1592 $sql .= ", cp.code as mode_reglement_code, cp.libelle as mode_reglement";
1593 $sql .= " FROM ".MAIN_DB_PREFIX."propal as p";
1594 $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_propalst as c ON p.fk_statut = c.id';
1595 $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_paiement as cp ON p.fk_mode_reglement = cp.id AND cp.entity IN ('.getEntity('c_paiement').')';
1596 $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').')';
1597 $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_availability as ca ON p.fk_availability = ca.rowid';
1598 $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_input_reason as dr ON p.fk_input_reason = dr.rowid';
1599 $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_incoterms as i ON p.fk_incoterms = i.rowid';
1600
1601 if (!empty($ref)) {
1602 if (!empty($forceentity)) {
1603 $sql .= " WHERE p.entity = ".(int) $forceentity; // Check only the current entity because we may have the same reference in several entities
1604 } else {
1605 $sql .= " WHERE p.entity IN (".getEntity('propal').")";
1606 }
1607 $sql .= " AND p.ref='".$this->db->escape($ref)."'";
1608 } else {
1609 // Dont't use entity if you use rowid
1610 $sql .= " WHERE p.rowid = ".((int) $rowid);
1611 }
1612
1613 dol_syslog(get_class($this)."::fetch", LOG_DEBUG);
1614 $resql = $this->db->query($sql);
1615 if ($resql) {
1616 if ($this->db->num_rows($resql)) {
1617 $obj = $this->db->fetch_object($resql);
1618
1619 $this->id = $obj->rowid;
1620 $this->entity = $obj->entity;
1621
1622 $this->ref = $obj->ref;
1623 $this->ref_client = $obj->ref_client;
1624 $this->ref_customer = $obj->ref_client;
1625 $this->ref_ext = $obj->ref_ext;
1626
1627 $this->remise = $obj->remise; // TODO deprecated
1628 $this->remise_percent = $obj->remise_percent; // TODO deprecated
1629 $this->remise_absolue = $obj->remise_absolue; // TODO deprecated
1630 $this->total = $obj->total_ttc; // TODO deprecated
1631 $this->total_ttc = $obj->total_ttc;
1632 $this->total_ht = $obj->total_ht;
1633 $this->total_tva = $obj->total_tva;
1634 $this->total_localtax1 = $obj->localtax1;
1635 $this->total_localtax2 = $obj->localtax2;
1636
1637 $this->socid = $obj->fk_soc;
1638 $this->thirdparty = null; // Clear if another value was already set by fetch_thirdparty
1639
1640 $this->fk_project = $obj->fk_project;
1641 $this->project = null; // Clear if another value was already set by fetch_projet
1642
1643 $this->model_pdf = $obj->model_pdf;
1644 $this->modelpdf = $obj->model_pdf; // deprecated
1645 $this->last_main_doc = $obj->last_main_doc;
1646 $this->note = $obj->note_private; // TODO deprecated
1647 $this->note_private = $obj->note_private;
1648 $this->note_public = $obj->note_public;
1649
1650 $this->status = (int) $obj->fk_statut;
1651 $this->statut = $this->status; // deprecated
1652
1653 $this->datec = $this->db->jdate($obj->datec); // TODO deprecated
1654 $this->datev = $this->db->jdate($obj->datev); // TODO deprecated
1655 $this->date_creation = $this->db->jdate($obj->datec); //Creation date
1656 $this->date_validation = $this->db->jdate($obj->datev); //Validation date
1657 $this->date_modification = $this->db->jdate($obj->date_modification); // tms
1658 $this->date_signature = $this->db->jdate($obj->dates); // Signature date
1659 $this->date = $this->db->jdate($obj->dp); // Proposal date
1660 $this->datep = $this->db->jdate($obj->dp); // deprecated
1661 $this->fin_validite = $this->db->jdate($obj->dfv);
1662 $this->date_livraison = $this->db->jdate($obj->delivery_date); // deprecated
1663 $this->delivery_date = $this->db->jdate($obj->delivery_date);
1664 $this->shipping_method_id = ($obj->fk_shipping_method > 0) ? $obj->fk_shipping_method : null;
1665 $this->warehouse_id = ($obj->fk_warehouse > 0) ? $obj->fk_warehouse : null;
1666 $this->availability_id = $obj->fk_availability;
1667 $this->availability_code = $obj->availability_code;
1668 $this->availability = $obj->availability;
1669 $this->demand_reason_id = $obj->fk_input_reason;
1670 $this->demand_reason_code = $obj->demand_reason_code;
1671 $this->demand_reason = $obj->demand_reason;
1672 $this->fk_address = $obj->fk_delivery_address;
1673
1674 $this->mode_reglement_id = $obj->fk_mode_reglement;
1675 $this->mode_reglement_code = $obj->mode_reglement_code;
1676 $this->mode_reglement = $obj->mode_reglement;
1677 $this->fk_account = ($obj->fk_account > 0) ? $obj->fk_account : null;
1678 $this->cond_reglement_id = $obj->fk_cond_reglement;
1679 $this->cond_reglement_code = $obj->cond_reglement_code;
1680 $this->cond_reglement = $obj->cond_reglement;
1681 $this->cond_reglement_doc = $obj->cond_reglement_libelle_doc;
1682 $this->deposit_percent = $obj->deposit_percent;
1683
1684 $this->extraparams = !empty($obj->extraparams) ? (array) json_decode($obj->extraparams, true) : array();
1685
1686 $this->user_author_id = $obj->fk_user_author;
1687 $this->user_validation_id = $obj->fk_user_valid;
1688 $this->user_close_id = $obj->fk_user_cloture;
1689
1690 //Incoterms
1691 $this->fk_incoterms = $obj->fk_incoterms;
1692 $this->location_incoterms = $obj->location_incoterms;
1693 $this->label_incoterms = $obj->label_incoterms;
1694
1695 // Multicurrency
1696 $this->fk_multicurrency = $obj->fk_multicurrency;
1697 $this->multicurrency_code = $obj->multicurrency_code;
1698 $this->multicurrency_tx = $obj->multicurrency_tx;
1699 $this->multicurrency_total_ht = $obj->multicurrency_total_ht;
1700 $this->multicurrency_total_tva = $obj->multicurrency_total_tva;
1701 $this->multicurrency_total_ttc = $obj->multicurrency_total_ttc;
1702
1703 if ($obj->fk_statut == self::STATUS_DRAFT) {
1704 $this->brouillon = 1;
1705 }
1706
1707 // Retrieve all extrafield
1708 // fetch optionals attributes and labels
1709 $this->fetch_optionals();
1710
1711 $this->db->free($resql);
1712
1713 $this->lines = array();
1714
1715 // Lines
1716 $result = $this->fetch_lines();
1717 if ($result < 0) {
1718 return -3;
1719 }
1720
1721 return 1;
1722 }
1723
1724 $this->error = "Record Not Found";
1725 return 0;
1726 } else {
1727 $this->error = $this->db->lasterror();
1728 return -1;
1729 }
1730 }
1731
1739 public function update(User $user, $notrigger = 0)
1740 {
1741 global $conf;
1742
1743 $error = 0;
1744
1745 // Clean parameters
1746 if (isset($this->ref)) {
1747 $this->ref = trim($this->ref);
1748 }
1749 if (isset($this->ref_client)) {
1750 $this->ref_client = trim($this->ref_client);
1751 }
1752 if (isset($this->note) || isset($this->note_private)) {
1753 $this->note_private = (isset($this->note_private) ? trim($this->note_private) : trim($this->note));
1754 }
1755 if (isset($this->note_public)) {
1756 $this->note_public = trim($this->note_public);
1757 }
1758 if (isset($this->model_pdf)) {
1759 $this->model_pdf = trim($this->model_pdf);
1760 }
1761 if (isset($this->import_key)) {
1762 $this->import_key = trim($this->import_key);
1763 }
1764 if (!empty($this->duree_validite) && is_numeric($this->duree_validite)) {
1765 $this->fin_validite = $this->date + ($this->duree_validite * 24 * 3600);
1766 }
1767
1768 // Check parameters
1769 // Put here code to add control on parameters values
1770
1771 // Update request
1772 $sql = "UPDATE ".MAIN_DB_PREFIX."propal SET";
1773 $sql .= " ref=".(isset($this->ref) ? "'".$this->db->escape($this->ref)."'" : "null").",";
1774 $sql .= " ref_client=".(isset($this->ref_client) ? "'".$this->db->escape($this->ref_client)."'" : "null").",";
1775 $sql .= " ref_ext=".(isset($this->ref_ext) ? "'".$this->db->escape($this->ref_ext)."'" : "null").",";
1776 $sql .= " fk_soc=".(isset($this->socid) ? $this->socid : "null").",";
1777 $sql .= " datep=".(strval($this->date) != '' ? "'".$this->db->idate($this->date)."'" : 'null').",";
1778 if (!empty($this->fin_validite)) {
1779 $sql .= " fin_validite=".(strval($this->fin_validite) != '' ? "'".$this->db->idate($this->fin_validite)."'" : 'null').",";
1780 }
1781 $sql .= " date_valid=".(strval($this->date_validation) != '' ? "'".$this->db->idate($this->date_validation)."'" : 'null').",";
1782 $sql .= " total_tva=".(isset($this->total_tva) ? $this->total_tva : "null").",";
1783 $sql .= " localtax1=".(isset($this->total_localtax1) ? $this->total_localtax1 : "null").",";
1784 $sql .= " localtax2=".(isset($this->total_localtax2) ? $this->total_localtax2 : "null").",";
1785 $sql .= " total_ht=".(isset($this->total_ht) ? $this->total_ht : "null").",";
1786 $sql .= " total_ttc=".(isset($this->total_ttc) ? $this->total_ttc : "null").",";
1787 $sql .= " fk_statut=".(isset($this->statut) ? $this->statut : "null").",";
1788 $sql .= " fk_user_author=".(isset($this->user_author_id) ? $this->user_author_id : "null").",";
1789 $sql .= " fk_user_valid = ".(!empty($this->user_validation_id) ? (int) $this->user_validation_id : "null").",";
1790 $sql .= " fk_projet=".(isset($this->fk_project) ? $this->fk_project : "null").",";
1791 $sql .= " fk_cond_reglement=".(isset($this->cond_reglement_id) ? $this->cond_reglement_id : "null").",";
1792 $sql .= " deposit_percent=".(!empty($this->deposit_percent) ? "'".$this->db->escape($this->deposit_percent)."'" : "null").",";
1793 $sql .= " fk_mode_reglement=".(isset($this->mode_reglement_id) ? $this->mode_reglement_id : "null").",";
1794 $sql .= " fk_input_reason=".(isset($this->demand_reason_id) ? $this->demand_reason_id : "null").",";
1795 $sql .= " fk_shipping_method=".(isset($this->shipping_method_id) ? (int) $this->shipping_method_id : "null").",";
1796 $sql .= " fk_availability=".(isset($this->availability_id) ? (int) $this->availability_id : "null").",";
1797 $sql .= " note_private=".(isset($this->note_private) ? "'".$this->db->escape($this->note_private)."'" : "null").",";
1798 $sql .= " note_public=".(isset($this->note_public) ? "'".$this->db->escape($this->note_public)."'" : "null").",";
1799 $sql .= " model_pdf=".(isset($this->model_pdf) ? "'".$this->db->escape($this->model_pdf)."'" : "null").",";
1800 $sql .= " import_key=".(isset($this->import_key) ? "'".$this->db->escape($this->import_key)."'" : "null");
1801 $sql .= " WHERE rowid=".((int) $this->id);
1802
1803 $this->db->begin();
1804
1805 dol_syslog(get_class($this)."::update", LOG_DEBUG);
1806 $resql = $this->db->query($sql);
1807 if (!$resql) {
1808 $error++;
1809 $this->errors[] = "Error ".$this->db->lasterror();
1810 }
1811
1812 if (!$error) {
1813 $result = $this->insertExtraFields();
1814 if ($result < 0) {
1815 $error++;
1816 }
1817 }
1818
1819 if (!$error && !$notrigger) {
1820 // Call trigger
1821 $result = $this->call_trigger('PROPAL_MODIFY', $user);
1822 if ($result < 0) {
1823 $error++;
1824 }
1825 // End call triggers
1826 }
1827
1828 // Commit or rollback
1829 if ($error) {
1830 foreach ($this->errors as $errmsg) {
1831 dol_syslog(get_class($this)."::update ".$errmsg, LOG_ERR);
1832 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
1833 }
1834 $this->db->rollback();
1835 return -1 * $error;
1836 } else {
1837 $this->db->commit();
1838 return 1;
1839 }
1840 }
1841
1842
1843 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1853 public function fetch_lines($only_product = 0, $loadalsotranslation = 0, $filters = '')
1854 {
1855 // phpcs:enable
1856 global $langs, $conf;
1857
1858 $this->lines = array();
1859
1860 $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,';
1861 $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,';
1862 $sql .= ' d.fk_unit,';
1863 $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,';
1864 $sql .= ' p.weight, p.weight_units, p.volume, p.volume_units,';
1865 $sql .= ' d.date_start, d.date_end,';
1866 $sql .= ' d.fk_multicurrency, d.multicurrency_code, d.multicurrency_subprice, d.multicurrency_total_ht, d.multicurrency_total_tva, d.multicurrency_total_ttc';
1867 $sql .= ' FROM '.MAIN_DB_PREFIX.'propaldet as d';
1868 $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'product as p ON (d.fk_product = p.rowid)';
1869 $sql .= ' WHERE d.fk_propal = '.((int) $this->id);
1870 if ($only_product) {
1871 $sql .= ' AND p.fk_product_type = 0';
1872 }
1873 if ($filters) {
1874 $sql .= $filters;
1875 }
1876 $sql .= ' ORDER by d.rang';
1877
1878 dol_syslog(get_class($this)."::fetch_lines", LOG_DEBUG);
1879 $result = $this->db->query($sql);
1880 if ($result) {
1881 require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php';
1882
1883 $num = $this->db->num_rows($result);
1884
1885 $i = 0;
1886 while ($i < $num) {
1887 $objp = $this->db->fetch_object($result);
1888
1889 $line = new PropaleLigne($this->db);
1890
1891 $line->rowid = $objp->rowid; //Deprecated
1892 $line->id = $objp->rowid;
1893 $line->fk_propal = $objp->fk_propal;
1894 $line->fk_parent_line = $objp->fk_parent_line;
1895 $line->product_type = $objp->product_type;
1896 $line->label = $objp->custom_label;
1897 $line->desc = $objp->description; // Description ligne
1898 $line->description = $objp->description; // Description ligne
1899 $line->qty = $objp->qty;
1900 $line->vat_src_code = $objp->vat_src_code;
1901 $line->tva_tx = $objp->tva_tx;
1902 $line->localtax1_tx = $objp->localtax1_tx;
1903 $line->localtax2_tx = $objp->localtax2_tx;
1904 $line->localtax1_type = $objp->localtax1_type;
1905 $line->localtax2_type = $objp->localtax2_type;
1906 $line->subprice = $objp->subprice;
1907 $line->fk_remise_except = $objp->fk_remise_except;
1908 $line->remise_percent = $objp->remise_percent;
1909 $line->price = $objp->price; // TODO deprecated
1910
1911 $line->info_bits = $objp->info_bits;
1912 $line->total_ht = $objp->total_ht;
1913 $line->total_tva = $objp->total_tva;
1914 $line->total_localtax1 = $objp->total_localtax1;
1915 $line->total_localtax2 = $objp->total_localtax2;
1916 $line->total_ttc = $objp->total_ttc;
1917 $line->fk_fournprice = $objp->fk_fournprice;
1918 $marginInfos = getMarginInfos($objp->subprice, $objp->remise_percent, $objp->tva_tx, $objp->localtax1_tx, $objp->localtax2_tx, $line->fk_fournprice, $objp->pa_ht);
1919 $line->pa_ht = $marginInfos[0];
1920 $line->marge_tx = $marginInfos[1];
1921 $line->marque_tx = $marginInfos[2];
1922 $line->special_code = $objp->special_code;
1923 $line->rang = $objp->rang;
1924
1925 $line->fk_product = $objp->fk_product;
1926
1927 $line->ref = $objp->product_ref; // deprecated
1928 $line->libelle = $objp->product_label; // deprecated
1929
1930 $line->product_ref = $objp->product_ref;
1931 $line->product_label = $objp->product_label;
1932 $line->product_desc = $objp->product_desc; // Description produit
1933 $line->product_tobatch = $objp->product_tobatch;
1934 $line->product_barcode = $objp->product_barcode;
1935
1936 $line->fk_product_type = $objp->fk_product_type; // deprecated
1937 $line->fk_unit = $objp->fk_unit;
1938 $line->weight = $objp->weight;
1939 $line->weight_units = $objp->weight_units;
1940 $line->volume = $objp->volume;
1941 $line->volume_units = $objp->volume_units;
1942
1943 $line->date_start = $this->db->jdate($objp->date_start);
1944 $line->date_end = $this->db->jdate($objp->date_end);
1945
1946 // Multicurrency
1947 $line->fk_multicurrency = $objp->fk_multicurrency;
1948 $line->multicurrency_code = $objp->multicurrency_code;
1949 $line->multicurrency_subprice = $objp->multicurrency_subprice;
1950 $line->multicurrency_total_ht = $objp->multicurrency_total_ht;
1951 $line->multicurrency_total_tva = $objp->multicurrency_total_tva;
1952 $line->multicurrency_total_ttc = $objp->multicurrency_total_ttc;
1953
1954 $line->fetch_optionals();
1955
1956 // multilangs
1957 if (getDolGlobalInt('MAIN_MULTILANGS') && !empty($objp->fk_product) && !empty($loadalsotranslation)) {
1958 $tmpproduct = new Product($this->db);
1959 $tmpproduct->fetch($objp->fk_product);
1960 $tmpproduct->getMultiLangs();
1961
1962 $line->multilangs = $tmpproduct->multilangs;
1963 }
1964
1965 $this->lines[$i] = $line;
1966
1967 $i++;
1968 }
1969
1970 $this->db->free($result);
1971
1972 return $num;
1973 } else {
1974 $this->error = $this->db->lasterror();
1975 return -3;
1976 }
1977 }
1978
1986 public function valid($user, $notrigger = 0)
1987 {
1988 global $conf;
1989
1990 require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
1991
1992 $error = 0;
1993
1994 // Protection
1995 if ($this->statut == self::STATUS_VALIDATED) {
1996 dol_syslog(get_class($this)."::valid action abandonned: already validated", LOG_WARNING);
1997 return 0;
1998 }
1999
2000 if (!((empty($conf->global->MAIN_USE_ADVANCED_PERMS) && !empty($user->rights->propal->creer))
2001 || (!empty($conf->global->MAIN_USE_ADVANCED_PERMS) && !empty($user->rights->propal->propal_advance->validate)))) {
2002 $this->error = 'ErrorPermissionDenied';
2003 dol_syslog(get_class($this)."::valid ".$this->error, LOG_ERR);
2004 return -1;
2005 }
2006
2007 $now = dol_now();
2008
2009 $this->db->begin();
2010
2011 // Numbering module definition
2012 $soc = new Societe($this->db);
2013 $soc->fetch($this->socid);
2014
2015 // Define new ref
2016 if (!$error && (preg_match('/^[\‍(]?PROV/i', $this->ref) || empty($this->ref))) { // empty should not happened, but when it occurs, the test save life
2017 $num = $this->getNextNumRef($soc);
2018 } else {
2019 $num = $this->ref;
2020 }
2021 $this->newref = dol_sanitizeFileName($num);
2022
2023 $sql = "UPDATE ".MAIN_DB_PREFIX."propal";
2024 $sql .= " SET ref = '".$this->db->escape($num)."',";
2025 $sql .= " fk_statut = ".self::STATUS_VALIDATED.", date_valid='".$this->db->idate($now)."', fk_user_valid=".((int) $user->id);
2026 $sql .= " WHERE rowid = ".((int) $this->id)." AND fk_statut = ".self::STATUS_DRAFT;
2027
2028 dol_syslog(get_class($this)."::valid", LOG_DEBUG);
2029 $resql = $this->db->query($sql);
2030 if (!$resql) {
2031 dol_print_error($this->db);
2032 $error++;
2033 }
2034
2035 // Trigger calls
2036 if (!$error && !$notrigger) {
2037 // Call trigger
2038 $result = $this->call_trigger('PROPAL_VALIDATE', $user);
2039 if ($result < 0) {
2040 $error++;
2041 }
2042 // End call triggers
2043 }
2044
2045 if (!$error) {
2046 $this->oldref = $this->ref;
2047
2048 // Rename directory if dir was a temporary ref
2049 if (preg_match('/^[\‍(]?PROV/i', $this->ref)) {
2050 // Now we rename also files into index
2051 $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)."'";
2052 $sql .= " WHERE filename LIKE '".$this->db->escape($this->ref)."%' AND filepath = 'propale/".$this->db->escape($this->ref)."' and entity = ".((int) $conf->entity);
2053 $resql = $this->db->query($sql);
2054 if (!$resql) {
2055 $error++;
2056 $this->error = $this->db->lasterror();
2057 }
2058 $sql = 'UPDATE '.MAIN_DB_PREFIX."ecm_files set filepath = 'propale/".$this->db->escape($this->newref)."'";
2059 $sql .= " WHERE filepath = 'propale/".$this->db->escape($this->ref)."' and entity = ".$conf->entity;
2060 $resql = $this->db->query($sql);
2061 if (!$resql) {
2062 $error++; $this->error = $this->db->lasterror();
2063 }
2064
2065 // We rename directory ($this->ref = old ref, $num = new ref) in order not to lose the attachments
2066 $oldref = dol_sanitizeFileName($this->ref);
2067 $newref = dol_sanitizeFileName($num);
2068 $dirsource = $conf->propal->multidir_output[$this->entity].'/'.$oldref;
2069 $dirdest = $conf->propal->multidir_output[$this->entity].'/'.$newref;
2070 if (!$error && file_exists($dirsource)) {
2071 dol_syslog(get_class($this)."::validate rename dir ".$dirsource." into ".$dirdest);
2072 if (@rename($dirsource, $dirdest)) {
2073 dol_syslog("Rename ok");
2074 // Rename docs starting with $oldref with $newref
2075 $listoffiles = dol_dir_list($dirdest, 'files', 1, '^'.preg_quote($oldref, '/'));
2076 foreach ($listoffiles as $fileentry) {
2077 $dirsource = $fileentry['name'];
2078 $dirdest = preg_replace('/^'.preg_quote($oldref, '/').'/', $newref, $dirsource);
2079 $dirsource = $fileentry['path'].'/'.$dirsource;
2080 $dirdest = $fileentry['path'].'/'.$dirdest;
2081 @rename($dirsource, $dirdest);
2082 }
2083 }
2084 }
2085 }
2086
2087 $this->ref = $num;
2088 $this->brouillon = 0;
2090 $this->user_validation_id = $user->id;
2091 $this->datev = $now;
2092
2093 $this->db->commit();
2094 return 1;
2095 } else {
2096 $this->db->rollback();
2097 return -1;
2098 }
2099 }
2100
2101
2102 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2111 public function set_date($user, $date, $notrigger = 0)
2112 {
2113 // phpcs:enable
2114 if (empty($date)) {
2115 $this->error = 'ErrorBadParameter';
2116 dol_syslog(get_class($this)."::set_date ".$this->error, LOG_ERR);
2117 return -1;
2118 }
2119
2120 if (!empty($user->rights->propal->creer)) {
2121 $error = 0;
2122
2123 $this->db->begin();
2124
2125 $sql = "UPDATE ".MAIN_DB_PREFIX."propal SET datep = '".$this->db->idate($date)."'";
2126 $sql .= " WHERE rowid = ".((int) $this->id);
2127
2128 dol_syslog(__METHOD__, LOG_DEBUG);
2129 $resql = $this->db->query($sql);
2130 if (!$resql) {
2131 $this->errors[] = $this->db->error();
2132 $error++;
2133 }
2134
2135 if (!$error) {
2136 $this->oldcopy = clone $this;
2137 $this->date = $date;
2138 $this->datep = $date; // deprecated
2139 }
2140
2141 if (!$notrigger && empty($error)) {
2142 // Call trigger
2143 $result = $this->call_trigger('PROPAL_MODIFY', $user);
2144 if ($result < 0) {
2145 $error++;
2146 }
2147 // End call triggers
2148 }
2149
2150 if (!$error) {
2151 $this->db->commit();
2152 return 1;
2153 } else {
2154 foreach ($this->errors as $errmsg) {
2155 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2156 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2157 }
2158 $this->db->rollback();
2159 return -1 * $error;
2160 }
2161 }
2162
2163 return -1;
2164 }
2165
2166 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2175 public function set_echeance($user, $date_end_validity, $notrigger = 0)
2176 {
2177 // phpcs:enable
2178 if (!empty($user->rights->propal->creer)) {
2179 $error = 0;
2180
2181 $this->db->begin();
2182
2183 $sql = "UPDATE ".MAIN_DB_PREFIX."propal SET fin_validite = ".($date_end_validity != '' ? "'".$this->db->idate($date_end_validity)."'" : 'null');
2184 $sql .= " WHERE rowid = ".((int) $this->id);
2185
2186 dol_syslog(__METHOD__, LOG_DEBUG);
2187
2188 $resql = $this->db->query($sql);
2189 if (!$resql) {
2190 $this->errors[] = $this->db->error();
2191 $error++;
2192 }
2193
2194
2195 if (!$error) {
2196 $this->oldcopy = clone $this;
2197 $this->fin_validite = $date_end_validity;
2198 }
2199
2200 if (!$notrigger && empty($error)) {
2201 // Call trigger
2202 $result = $this->call_trigger('PROPAL_MODIFY', $user);
2203 if ($result < 0) {
2204 $error++;
2205 }
2206 // End call triggers
2207 }
2208
2209 if (!$error) {
2210 $this->db->commit();
2211 return 1;
2212 } else {
2213 foreach ($this->errors as $errmsg) {
2214 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2215 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2216 }
2217 $this->db->rollback();
2218 return -1 * $error;
2219 }
2220 }
2221
2222 return -1;
2223 }
2224
2225 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2235 public function set_date_livraison($user, $delivery_date, $notrigger = 0)
2236 {
2237 // phpcs:enable
2238 return $this->setDeliveryDate($user, $delivery_date, $notrigger);
2239 }
2240
2249 public function setDeliveryDate($user, $delivery_date, $notrigger = 0)
2250 {
2251 if (!empty($user->rights->propal->creer)) {
2252 $error = 0;
2253
2254 $this->db->begin();
2255
2256 $sql = "UPDATE ".MAIN_DB_PREFIX."propal ";
2257 $sql .= " SET date_livraison = ".($delivery_date != '' ? "'".$this->db->idate($delivery_date)."'" : 'null');
2258 $sql .= " WHERE rowid = ".((int) $this->id);
2259
2260 dol_syslog(__METHOD__, LOG_DEBUG);
2261 $resql = $this->db->query($sql);
2262 if (!$resql) {
2263 $this->errors[] = $this->db->error();
2264 $error++;
2265 }
2266
2267 if (!$error) {
2268 $this->oldcopy = clone $this;
2269 $this->date_livraison = $delivery_date;
2270 $this->delivery_date = $delivery_date;
2271 }
2272
2273 if (!$notrigger && empty($error)) {
2274 // Call trigger
2275 $result = $this->call_trigger('PROPAL_MODIFY', $user);
2276 if ($result < 0) {
2277 $error++;
2278 }
2279 // End call triggers
2280 }
2281
2282 if (!$error) {
2283 $this->db->commit();
2284 return 1;
2285 } else {
2286 foreach ($this->errors as $errmsg) {
2287 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2288 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2289 }
2290 $this->db->rollback();
2291 return -1 * $error;
2292 }
2293 }
2294
2295 return -1;
2296 }
2297
2298 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2307 public function set_availability($user, $id, $notrigger = 0)
2308 {
2309 // phpcs:enable
2310 if (!empty($user->rights->propal->creer) && $this->statut >= self::STATUS_DRAFT) {
2311 $error = 0;
2312
2313 $this->db->begin();
2314
2315 $sql = "UPDATE ".MAIN_DB_PREFIX."propal";
2316 $sql .= " SET fk_availability = ".((int) $id);
2317 $sql .= " WHERE rowid = ".((int) $this->id);
2318
2319 dol_syslog(__METHOD__.' availability('.$id.')', LOG_DEBUG);
2320 $resql = $this->db->query($sql);
2321 if (!$resql) {
2322 $this->errors[] = $this->db->error();
2323 $error++;
2324 }
2325
2326 if (!$error) {
2327 $this->oldcopy = clone $this;
2328 $this->fk_availability = $id;
2329 $this->availability_id = $id;
2330 }
2331
2332 if (!$notrigger && empty($error)) {
2333 // Call trigger
2334 $result = $this->call_trigger('PROPAL_MODIFY', $user);
2335 if ($result < 0) {
2336 $error++;
2337 }
2338 // End call triggers
2339 }
2340
2341 if (!$error) {
2342 $this->db->commit();
2343 return 1;
2344 } else {
2345 foreach ($this->errors as $errmsg) {
2346 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2347 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2348 }
2349 $this->db->rollback();
2350 return -1 * $error;
2351 }
2352 } else {
2353 $error_str = 'Propal status do not meet requirement '.$this->statut;
2354 dol_syslog(__METHOD__.$error_str, LOG_ERR);
2355 $this->error = $error_str;
2356 $this->errors[] = $this->error;
2357 return -2;
2358 }
2359 }
2360
2361 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2370 public function set_demand_reason($user, $id, $notrigger = 0)
2371 {
2372 // phpcs:enable
2373 if (!empty($user->rights->propal->creer) && $this->statut >= self::STATUS_DRAFT) {
2374 $error = 0;
2375
2376 $this->db->begin();
2377
2378 $sql = "UPDATE ".MAIN_DB_PREFIX."propal ";
2379 $sql .= " SET fk_input_reason = ".((int) $id);
2380 $sql .= " WHERE rowid = ".((int) $this->id);
2381
2382 dol_syslog(__METHOD__, LOG_DEBUG);
2383 $resql = $this->db->query($sql);
2384 if (!$resql) {
2385 $this->errors[] = $this->db->error();
2386 $error++;
2387 }
2388
2389
2390 if (!$error) {
2391 $this->oldcopy = clone $this;
2392 $this->fk_input_reason = $id;
2393 $this->demand_reason_id = $id;
2394 }
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 } else {
2418 $error_str = 'Propal status do not meet requirement '.$this->statut;
2419 dol_syslog(__METHOD__.$error_str, LOG_ERR);
2420 $this->error = $error_str;
2421 $this->errors[] = $this->error;
2422 return -2;
2423 }
2424 }
2425
2426 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2435 public function set_ref_client($user, $ref_client, $notrigger = 0)
2436 {
2437 // phpcs:enable
2438 if (!empty($user->rights->propal->creer)) {
2439 $error = 0;
2440
2441 $this->db->begin();
2442
2443 $sql = "UPDATE ".MAIN_DB_PREFIX."propal SET ref_client = ".(empty($ref_client) ? 'NULL' : "'".$this->db->escape($ref_client)."'");
2444 $sql .= " WHERE rowid = ".((int) $this->id);
2445
2446 dol_syslog(__METHOD__.' $this->id='.$this->id.', ref_client='.$ref_client, LOG_DEBUG);
2447 $resql = $this->db->query($sql);
2448 if (!$resql) {
2449 $this->errors[] = $this->db->error();
2450 $error++;
2451 }
2452
2453 if (!$error) {
2454 $this->oldcopy = clone $this;
2455 $this->ref_client = $ref_client;
2456 }
2457
2458 if (!$notrigger && empty($error)) {
2459 // Call trigger
2460 $result = $this->call_trigger('PROPAL_MODIFY', $user);
2461 if ($result < 0) {
2462 $error++;
2463 }
2464 // End call triggers
2465 }
2466
2467 if (!$error) {
2468 $this->db->commit();
2469 return 1;
2470 } else {
2471 foreach ($this->errors as $errmsg) {
2472 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2473 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2474 }
2475 $this->db->rollback();
2476 return -1 * $error;
2477 }
2478 }
2479
2480 return -1;
2481 }
2482
2483 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2493 public function set_remise_percent($user, $remise, $notrigger = 0)
2494 {
2495 // phpcs:enable
2496 $remise = trim($remise) ?trim($remise) : 0;
2497
2498 if (!empty($user->rights->propal->creer)) {
2500
2501 $error = 0;
2502
2503 $this->db->begin();
2504
2505 $sql = "UPDATE ".MAIN_DB_PREFIX."propal SET remise_percent = ".((float) $remise);
2506 $sql .= " WHERE rowid = ".((int) $this->id)." AND fk_statut = ".self::STATUS_DRAFT;
2507
2508 dol_syslog(__METHOD__, LOG_DEBUG);
2509 $resql = $this->db->query($sql);
2510 if (!$resql) {
2511 $this->errors[] = $this->db->error();
2512 $error++;
2513 }
2514
2515 if (!$error) {
2516 $this->oldcopy = clone $this;
2517 $this->remise_percent = $remise;
2518 $this->update_price(1);
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 }
2542
2543 return -1;
2544 }
2545
2546
2547 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2556 public function set_remise_absolue($user, $remise, $notrigger = 0)
2557 {
2558 // phpcs:enable
2559 if (empty($remise)) {
2560 $remise = 0;
2561 }
2563
2564 if (!empty($user->rights->propal->creer)) {
2565 $error = 0;
2566
2567 $this->db->begin();
2568
2569 $sql = "UPDATE ".MAIN_DB_PREFIX."propal";
2570 $sql .= " SET remise_absolue = ".((float) $remise);
2571 $sql .= " WHERE rowid = ".((int) $this->id)." AND fk_statut = ".self::STATUS_DRAFT;
2572
2573 dol_syslog(__METHOD__, LOG_DEBUG);
2574 $resql = $this->db->query($sql);
2575 if (!$resql) {
2576 $this->errors[] = $this->db->error();
2577 $error++;
2578 }
2579
2580 if (!$error) {
2581 $this->oldcopy = clone $this;
2582 $this->update_price(1);
2583 }
2584
2585 if (!$notrigger && empty($error)) {
2586 // Call trigger
2587 $result = $this->call_trigger('PROPAL_MODIFY', $user);
2588 if ($result < 0) {
2589 $error++;
2590 }
2591 // End call triggers
2592 }
2593
2594 if (!$error) {
2595 $this->db->commit();
2596 return 1;
2597 } else {
2598 foreach ($this->errors as $errmsg) {
2599 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2600 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2601 }
2602 $this->db->rollback();
2603 return -1 * $error;
2604 }
2605 }
2606
2607 return -1;
2608 }
2609
2610
2611
2621 public function reopen($user, $status, $note = '', $notrigger = 0)
2622 {
2623 $error = 0;
2624
2625 $sql = "UPDATE ".MAIN_DB_PREFIX."propal";
2626 $sql .= " SET fk_statut = ".((int) $status).",";
2627 if (!empty($note)) {
2628 $sql .= " note_private = '".$this->db->escape($note)."',";
2629 }
2630 $sql .= " date_cloture=NULL, fk_user_cloture=NULL";
2631 $sql .= " WHERE rowid = ".((int) $this->id);
2632
2633 $this->db->begin();
2634
2635 dol_syslog(get_class($this)."::reopen", LOG_DEBUG);
2636 $resql = $this->db->query($sql);
2637 if (!$resql) {
2638 $error++;
2639 $this->errors[] = "Error ".$this->db->lasterror();
2640 }
2641 if (!$error) {
2642 if (!$notrigger) {
2643 // Call trigger
2644 $result = $this->call_trigger('PROPAL_REOPEN', $user);
2645 if ($result < 0) {
2646 $error++;
2647 }
2648 // End call triggers
2649 }
2650 }
2651
2652 // Commit or rollback
2653 if ($error) {
2654 if (!empty($this->errors)) {
2655 foreach ($this->errors as $errmsg) {
2656 dol_syslog(get_class($this)."::update ".$errmsg, LOG_ERR);
2657 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2658 }
2659 }
2660 $this->db->rollback();
2661 return -1 * $error;
2662 } else {
2663 $this->statut = $status;
2664 $this->status = $status;
2665
2666 $this->db->commit();
2667 return 1;
2668 }
2669 }
2670
2680 public function closeProposal($user, $status, $note = '', $notrigger = 0)
2681 {
2682 global $langs,$conf;
2683
2684 $error = 0;
2685 $now = dol_now();
2686
2687 $this->db->begin();
2688
2689 $newprivatenote = dol_concatdesc($this->note_private, $note);
2690
2691 if (empty($conf->global->PROPALE_KEEP_OLD_SIGNATURE_INFO)) {
2692 $date_signature = $now;
2693 $fk_user_signature = $user->id;
2694 } else {
2695 $this->info($this->id);
2696 if (!isset($this->date_signature) || $this->date_signature == '') {
2697 $date_signature = $now;
2698 $fk_user_signature = $user->id;
2699 } else {
2700 $date_signature = $this->date_signature;
2701 $fk_user_signature = $this->user_signature->id;
2702 }
2703 }
2704
2705 $sql = "UPDATE ".MAIN_DB_PREFIX."propal";
2706 $sql .= " SET fk_statut = ".((int) $status).", note_private = '".$this->db->escape($newprivatenote)."'";
2707 if ($status == self::STATUS_SIGNED) {
2708 $sql .= ", date_signature='".$this->db->idate($now)."', fk_user_signature = ".($fk_user_signature);
2709 }
2710 $sql .= " WHERE rowid = ".((int) $this->id);
2711
2712 $resql = $this->db->query($sql);
2713 if ($resql) {
2714 // Status self::STATUS_REFUSED by default
2715 $modelpdf = !empty($conf->global->PROPALE_ADDON_PDF_ODT_CLOSED) ? $conf->global->PROPALE_ADDON_PDF_ODT_CLOSED : $this->model_pdf;
2716 $trigger_name = 'PROPAL_CLOSE_REFUSED'; // used later in call_trigger()
2717
2718 if ($status == self::STATUS_SIGNED) { // Status self::STATUS_SIGNED
2719 $trigger_name = 'PROPAL_CLOSE_SIGNED'; // used later in call_trigger()
2720 $modelpdf = !empty($conf->global->PROPALE_ADDON_PDF_ODT_TOBILL) ? $conf->global->PROPALE_ADDON_PDF_ODT_TOBILL : $this->model_pdf;
2721
2722 // The connected company is classified as a client
2723 $soc=new Societe($this->db);
2724 $soc->id = $this->socid;
2725 $result = $soc->set_as_client();
2726
2727 if ($result < 0) {
2728 $this->error=$this->db->lasterror();
2729 $this->db->rollback();
2730 return -2;
2731 }
2732 }
2733
2734 if (empty($conf->global->MAIN_DISABLE_PDF_AUTOUPDATE)) {
2735 // Define output language
2736 $outputlangs = $langs;
2737 if (getDolGlobalInt('MAIN_MULTILANGS')) {
2738 $outputlangs = new Translate("", $conf);
2739 $newlang = (GETPOST('lang_id', 'aZ09') ? GETPOST('lang_id', 'aZ09') : $this->thirdparty->default_lang);
2740 $outputlangs->setDefaultLang($newlang);
2741 }
2742
2743 // PDF
2744 $hidedetails = (!empty($conf->global->MAIN_GENERATE_DOCUMENTS_HIDE_DETAILS) ? 1 : 0);
2745 $hidedesc = (!empty($conf->global->MAIN_GENERATE_DOCUMENTS_HIDE_DESC) ? 1 : 0);
2746 $hideref = (!empty($conf->global->MAIN_GENERATE_DOCUMENTS_HIDE_REF) ? 1 : 0);
2747
2748 //$ret=$object->fetch($id); // Reload to get new records
2749 $this->generateDocument($modelpdf, $outputlangs, $hidedetails, $hidedesc, $hideref);
2750 }
2751
2752 if (!$error) {
2753 $this->oldcopy= clone $this;
2754 $this->statut = $status;
2755 $this->status = $status;
2756 $this->date_signature = $date_signature;
2757 $this->note_private = $newprivatenote;
2758 }
2759
2760 if (!$notrigger && empty($error)) {
2761 // Call trigger
2762 $result=$this->call_trigger($trigger_name, $user);
2763 if ($result < 0) {
2764 $error++;
2765 }
2766 // End call triggers
2767 }
2768
2769 if (!$error ) {
2770 $this->db->commit();
2771 return 1;
2772 } else {
2773 $this->statut = $this->oldcopy->statut;
2774 $this->status = $this->oldcopy->statut;
2775 $this->date_signature = $this->oldcopy->date_signature;
2776 $this->note_private = $this->oldcopy->note_private;
2777
2778 $this->db->rollback();
2779 return -1;
2780 }
2781 } else {
2782 $this->error = $this->db->lasterror();
2783 $this->db->rollback();
2784 return -1;
2785 }
2786 }
2787
2796 public function classifyBilled(User $user, $notrigger = 0, $note = '')
2797 {
2798 global $conf, $langs;
2799
2800 $error = 0;
2801
2802 $now = dol_now();
2803 $num = 0;
2804
2805 $triggerName = 'PROPAL_CLASSIFY_BILLED';
2806
2807 $this->db->begin();
2808
2809 $newprivatenote = dol_concatdesc($this->note_private, $note);
2810
2811 $sql = 'UPDATE '.MAIN_DB_PREFIX.'propal 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 = $conf->global->PROPALE_ADDON_PDF_ODT_CLOSED ? $conf->global->PROPALE_ADDON_PDF_ODT_CLOSED : $this->model_pdf;
2826
2827 if (empty($conf->global->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 = (!empty($conf->global->MAIN_GENERATE_DOCUMENTS_HIDE_DETAILS) ? 1 : 0);
2838 $hidedesc = (!empty($conf->global->MAIN_GENERATE_DOCUMENTS_HIDE_DESC) ? 1 : 0);
2839 $hideref = (!empty($conf->global->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->date_cloture = $now;
2848 $this->note_private = $newprivatenote;
2849 }
2850
2851 if (!$notrigger && empty($error)) {
2852 // Call trigger
2853 $result = $this->call_trigger($triggerName, $user);
2854 if ($result < 0) {
2855 $error++;
2856 }
2857 // End call triggers
2858 }
2859
2860 if (!$error) {
2861 $this->db->commit();
2862 return $num;
2863 } else {
2864 foreach ($this->errors as $errmsg) {
2865 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2866 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2867 }
2868 $this->db->rollback();
2869 return -1 * $error;
2870 }
2871 }
2872
2873 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2881 public function setDraft($user, $notrigger = 0)
2882 {
2883 // phpcs:enable
2884 $error = 0;
2885
2886 // Protection
2887 if ($this->statut <= self::STATUS_DRAFT) {
2888 return 0;
2889 }
2890
2891 dol_syslog(get_class($this)."::setDraft", LOG_DEBUG);
2892
2893 $this->db->begin();
2894
2895 $sql = "UPDATE ".MAIN_DB_PREFIX."propal";
2896 $sql .= " SET fk_statut = ".self::STATUS_DRAFT;
2897 $sql .= ", online_sign_ip = NULL , online_sign_name = NULL";
2898 $sql .= " WHERE rowid = ".((int) $this->id);
2899
2900 $resql = $this->db->query($sql);
2901 if (!$resql) {
2902 $this->errors[] = $this->db->error();
2903 $error++;
2904 }
2905
2906 if (!$error) {
2907 $this->oldcopy = clone $this;
2908 }
2909
2910 if (!$notrigger && empty($error)) {
2911 // Call trigger
2912 $result = $this->call_trigger('PROPAL_MODIFY', $user);
2913 if ($result < 0) {
2914 $error++;
2915 }
2916 // End call triggers
2917 }
2918
2919 if (!$error) {
2920 $this->statut = self::STATUS_DRAFT;
2921 $this->brouillon = 1;
2922
2923 $this->db->commit();
2924 return 1;
2925 } else {
2926 foreach ($this->errors as $errmsg) {
2927 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2928 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2929 }
2930 $this->db->rollback();
2931 return -1 * $error;
2932 }
2933 }
2934
2935
2936 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2950 public function liste_array($shortlist = 0, $draft = 0, $notcurrentuser = 0, $socid = 0, $limit = 0, $offset = 0, $sortfield = 'p.datep', $sortorder = 'DESC')
2951 {
2952 // phpcs:enable
2953 global $user;
2954
2955 $ga = array();
2956
2957 $sql = "SELECT s.rowid, s.nom as name, s.client,";
2958 $sql .= " p.rowid as propalid, p.fk_statut, p.total_ht, p.ref, p.remise, ";
2959 $sql .= " p.datep as dp, p.fin_validite as datelimite";
2960 if (empty($user->rights->societe->client->voir) && !$socid) {
2961 $sql .= ", sc.fk_soc, sc.fk_user";
2962 }
2963 $sql .= " FROM ".MAIN_DB_PREFIX."societe as s, ".MAIN_DB_PREFIX."propal as p, ".MAIN_DB_PREFIX."c_propalst as c";
2964 if (empty($user->rights->societe->client->voir) && !$socid) {
2965 $sql .= ", ".MAIN_DB_PREFIX."societe_commerciaux as sc";
2966 }
2967 $sql .= " WHERE p.entity IN (".getEntity('propal').")";
2968 $sql .= " AND p.fk_soc = s.rowid";
2969 $sql .= " AND p.fk_statut = c.id";
2970 if (empty($user->rights->societe->client->voir) && !$socid) { //restriction
2971 $sql .= " AND s.rowid = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
2972 }
2973 if ($socid) {
2974 $sql .= " AND s.rowid = ".((int) $socid);
2975 }
2976 if ($draft) {
2977 $sql .= " AND p.fk_statut = ".self::STATUS_DRAFT;
2978 }
2979 if ($notcurrentuser > 0) {
2980 $sql .= " AND p.fk_user_author <> ".((int) $user->id);
2981 }
2982 $sql .= $this->db->order($sortfield, $sortorder);
2983 $sql .= $this->db->plimit($limit, $offset);
2984
2985 $result = $this->db->query($sql);
2986 if ($result) {
2987 $num = $this->db->num_rows($result);
2988 if ($num) {
2989 $i = 0;
2990 while ($i < $num) {
2991 $obj = $this->db->fetch_object($result);
2992
2993 if ($shortlist == 1) {
2994 $ga[$obj->propalid] = $obj->ref;
2995 } elseif ($shortlist == 2) {
2996 $ga[$obj->propalid] = $obj->ref.' ('.$obj->name.')';
2997 } else {
2998 $ga[$i]['id'] = $obj->propalid;
2999 $ga[$i]['ref'] = $obj->ref;
3000 $ga[$i]['name'] = $obj->name;
3001 }
3002
3003 $i++;
3004 }
3005 }
3006 return $ga;
3007 } else {
3008 dol_print_error($this->db);
3009 return -1;
3010 }
3011 }
3012
3018 public function getInvoiceArrayList()
3019 {
3020 return $this->InvoiceArrayList($this->id);
3021 }
3022
3023 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3030 public function InvoiceArrayList($id)
3031 {
3032 // phpcs:enable
3033 $ga = array();
3034 $linkedInvoices = array();
3035
3036 $this->fetchObjectLinked($id, $this->element);
3037 foreach ($this->linkedObjectsIds as $objecttype => $objectid) {
3038 // Nouveau système du comon object renvoi des rowid et non un id linéaire de 1 à n
3039 // On parcourt donc une liste d'objets en tant qu'objet unique
3040 foreach ($objectid as $key => $object) {
3041 // Cas des factures liees directement
3042 if ($objecttype == 'facture') {
3043 $linkedInvoices[] = $object;
3044 } else {
3045 // Cas des factures liees par un autre objet (ex: commande)
3046 $this->fetchObjectLinked($object, $objecttype);
3047 foreach ($this->linkedObjectsIds as $subobjecttype => $subobjectid) {
3048 foreach ($subobjectid as $subkey => $subobject) {
3049 if ($subobjecttype == 'facture') {
3050 $linkedInvoices[] = $subobject;
3051 }
3052 }
3053 }
3054 }
3055 }
3056 }
3057
3058 if (count($linkedInvoices) > 0) {
3059 $sql = "SELECT rowid as facid, ref, total_ht as total, datef as df, fk_user_author, fk_statut, paye";
3060 $sql .= " FROM ".MAIN_DB_PREFIX."facture";
3061 $sql .= " WHERE rowid IN (".$this->db->sanitize(implode(',', $linkedInvoices)).")";
3062
3063 dol_syslog(get_class($this)."::InvoiceArrayList", LOG_DEBUG);
3064 $resql = $this->db->query($sql);
3065
3066 if ($resql) {
3067 $tab_sqlobj = array();
3068 $nump = $this->db->num_rows($resql);
3069 for ($i = 0; $i < $nump; $i++) {
3070 $sqlobj = $this->db->fetch_object($resql);
3071 $tab_sqlobj[] = $sqlobj;
3072 }
3073 $this->db->free($resql);
3074
3075 $nump = count($tab_sqlobj);
3076
3077 if ($nump) {
3078 $i = 0;
3079 while ($i < $nump) {
3080 $obj = array_shift($tab_sqlobj);
3081
3082 $ga[$i] = $obj;
3083
3084 $i++;
3085 }
3086 }
3087 return $ga;
3088 } else {
3089 return -1;
3090 }
3091 } else {
3092 return $ga;
3093 }
3094 }
3095
3103 public function delete($user, $notrigger = 0)
3104 {
3105 global $conf;
3106 require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
3107
3108 $error = 0;
3109
3110 $this->db->begin();
3111
3112 if (!$notrigger) {
3113 // Call trigger
3114 $result = $this->call_trigger('PROPAL_DELETE', $user);
3115 if ($result < 0) {
3116 $error++;
3117 }
3118 // End call triggers
3119 }
3120
3121 // Delete extrafields of lines and lines
3122 if (!$error && !empty($this->table_element_line)) {
3123 $tabletodelete = $this->table_element_line;
3124 $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).")";
3125 $sql = "DELETE FROM ".MAIN_DB_PREFIX.$tabletodelete." WHERE ".$this->fk_element." = ".((int) $this->id);
3126 if (!$this->db->query($sqlef) || !$this->db->query($sql)) {
3127 $error++;
3128 $this->error = $this->db->lasterror();
3129 $this->errors[] = $this->error;
3130 dol_syslog(get_class($this)."::delete error ".$this->error, LOG_ERR);
3131 }
3132 }
3133
3134 if (!$error) {
3135 // Delete linked object
3136 $res = $this->deleteObjectLinked();
3137 if ($res < 0) {
3138 $error++;
3139 }
3140 }
3141
3142 if (!$error) {
3143 // Delete linked contacts
3144 $res = $this->delete_linked_contact();
3145 if ($res < 0) {
3146 $error++;
3147 }
3148 }
3149
3150 // Removed extrafields of object
3151 if (!$error) {
3152 $result = $this->deleteExtraFields();
3153 if ($result < 0) {
3154 $error++;
3155 dol_syslog(get_class($this)."::delete error ".$this->error, LOG_ERR);
3156 }
3157 }
3158
3159 // Delete main record
3160 if (!$error) {
3161 $sql = "DELETE FROM ".MAIN_DB_PREFIX.$this->table_element." WHERE rowid = ".((int) $this->id);
3162 $res = $this->db->query($sql);
3163 if (!$res) {
3164 $error++;
3165 $this->error = $this->db->lasterror();
3166 $this->errors[] = $this->error;
3167 dol_syslog(get_class($this)."::delete error ".$this->error, LOG_ERR);
3168 }
3169 }
3170
3171 // Delete record into ECM index and physically
3172 if (!$error) {
3173 $res = $this->deleteEcmFiles(0); // Deleting files physically is done later with the dol_delete_dir_recursive
3174 if (!$res) {
3175 $error++;
3176 }
3177 }
3178
3179 if (!$error) {
3180 // We remove directory
3181 $ref = dol_sanitizeFileName($this->ref);
3182 if ($conf->propal->multidir_output[$this->entity] && !empty($this->ref)) {
3183 $dir = $conf->propal->multidir_output[$this->entity]."/".$ref;
3184 $file = $dir."/".$ref.".pdf";
3185 if (file_exists($file)) {
3186 dol_delete_preview($this);
3187
3188 if (!dol_delete_file($file, 0, 0, 0, $this)) {
3189 $this->error = 'ErrorFailToDeleteFile';
3190 $this->errors[] = $this->error;
3191 $this->db->rollback();
3192 return 0;
3193 }
3194 }
3195 if (file_exists($dir)) {
3196 $res = @dol_delete_dir_recursive($dir);
3197 if (!$res) {
3198 $this->error = 'ErrorFailToDeleteDir';
3199 $this->errors[] = $this->error;
3200 $this->db->rollback();
3201 return 0;
3202 }
3203 }
3204 }
3205 }
3206
3207 if (!$error) {
3208 dol_syslog(get_class($this)."::delete ".$this->id." by ".$user->id, LOG_DEBUG);
3209 $this->db->commit();
3210 return 1;
3211 } else {
3212 $this->db->rollback();
3213 return -1;
3214 }
3215 }
3216
3225 public function availability($availability_id, $notrigger = 0)
3226 {
3227 global $user;
3228
3229 if ($this->statut >= self::STATUS_DRAFT) {
3230 $error = 0;
3231
3232 $this->db->begin();
3233
3234 $sql = 'UPDATE '.MAIN_DB_PREFIX.'propal';
3235 $sql .= ' SET fk_availability = '.((int) $availability_id);
3236 $sql .= ' WHERE rowid='.((int) $this->id);
3237
3238 dol_syslog(__METHOD__.' availability('.$availability_id.')', LOG_DEBUG);
3239 $resql = $this->db->query($sql);
3240 if (!$resql) {
3241 $this->errors[] = $this->db->error();
3242 $error++;
3243 }
3244
3245 if (!$error) {
3246 $this->oldcopy = clone $this;
3247 $this->availability_id = $availability_id;
3248 }
3249
3250 if (!$notrigger && empty($error)) {
3251 // Call trigger
3252 $result = $this->call_trigger('PROPAL_MODIFY', $user);
3253 if ($result < 0) {
3254 $error++;
3255 }
3256 // End call triggers
3257 }
3258
3259 if (!$error) {
3260 $this->db->commit();
3261 return 1;
3262 } else {
3263 foreach ($this->errors as $errmsg) {
3264 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
3265 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
3266 }
3267 $this->db->rollback();
3268 return -1 * $error;
3269 }
3270 } else {
3271 $error_str = 'Propal status do not meet requirement '.$this->statut;
3272 dol_syslog(__METHOD__.$error_str, LOG_ERR);
3273 $this->error = $error_str;
3274 $this->errors[] = $this->error;
3275 return -2;
3276 }
3277 }
3278
3279 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3288 public function demand_reason($demand_reason_id, $notrigger = 0)
3289 {
3290 // phpcs:enable
3291 global $user;
3292
3293 if ($this->statut >= self::STATUS_DRAFT) {
3294 $error = 0;
3295
3296 $this->db->begin();
3297
3298 $sql = 'UPDATE '.MAIN_DB_PREFIX.'propal';
3299 $sql .= ' SET fk_input_reason = '.((int) $demand_reason_id);
3300 $sql .= ' WHERE rowid='.((int) $this->id);
3301
3302 dol_syslog(__METHOD__.' demand_reason('.$demand_reason_id.')', LOG_DEBUG);
3303 $resql = $this->db->query($sql);
3304 if (!$resql) {
3305 $this->errors[] = $this->db->error();
3306 $error++;
3307 }
3308
3309 if (!$error) {
3310 $this->oldcopy = clone $this;
3311 $this->demand_reason_id = $demand_reason_id;
3312 }
3313
3314 if (!$notrigger && empty($error)) {
3315 // Call trigger
3316 $result = $this->call_trigger('PROPAL_MODIFY', $user);
3317 if ($result < 0) {
3318 $error++;
3319 }
3320 // End call triggers
3321 }
3322
3323 if (!$error) {
3324 $this->db->commit();
3325 return 1;
3326 } else {
3327 foreach ($this->errors as $errmsg) {
3328 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
3329 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
3330 }
3331 $this->db->rollback();
3332 return -1 * $error;
3333 }
3334 } else {
3335 $error_str = 'Propal status do not meet requirement '.$this->statut;
3336 dol_syslog(__METHOD__.$error_str, LOG_ERR);
3337 $this->error = $error_str;
3338 $this->errors[] = $this->error;
3339 return -2;
3340 }
3341 }
3342
3343
3350 public function info($id)
3351 {
3352 $sql = "SELECT c.rowid, ";
3353 $sql .= " c.datec, c.date_valid as datev, c.date_signature, c.date_cloture,";
3354 $sql .= " c.fk_user_author, c.fk_user_valid, c.fk_user_signature, c.fk_user_cloture";
3355 $sql .= " FROM ".MAIN_DB_PREFIX."propal as c";
3356 $sql .= " WHERE c.rowid = ".((int) $id);
3357
3358 $result = $this->db->query($sql);
3359
3360 if ($result) {
3361 if ($this->db->num_rows($result)) {
3362 $obj = $this->db->fetch_object($result);
3363
3364 $this->id = $obj->rowid;
3365
3366 $this->date_creation = $this->db->jdate($obj->datec);
3367 $this->date_validation = $this->db->jdate($obj->datev);
3368 $this->date_signature = $this->db->jdate($obj->date_signature);
3369 $this->date_cloture = $this->db->jdate($obj->date_cloture);
3370
3371 $cuser = new User($this->db);
3372 $cuser->fetch($obj->fk_user_author);
3373 $this->user_creation = $cuser;
3374
3375 if ($obj->fk_user_valid) {
3376 $this->user_validation_id = $obj->fk_user_valid;
3377 }
3378
3379 if ($obj->fk_user_signature) {
3380 $user_signature = new User($this->db);
3381 $user_signature->fetch($obj->fk_user_signature);
3382 $this->user_signature = $user_signature;
3383 }
3384
3385 if ($obj->fk_user_cloture) {
3386 $cluser = new User($this->db);
3387 $cluser->fetch($obj->fk_user_cloture);
3388 $this->user_cloture = $cluser;
3389 }
3390 }
3391 $this->db->free($result);
3392 } else {
3393 dol_print_error($this->db);
3394 }
3395 }
3396
3397
3404 public function getLibStatut($mode = 0)
3405 {
3406 return $this->LibStatut($this->statut, $mode);
3407 }
3408
3409 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3417 public function LibStatut($status, $mode = 1)
3418 {
3419 // phpcs:enable
3420 global $conf, $hookmanager;
3421
3422 // Init/load array of translation of status
3423 if (empty($this->labelStatus) || empty($this->labelStatusShort)) {
3424 global $langs;
3425 $langs->load("propal");
3426 $this->labelStatus[0] = $langs->transnoentitiesnoconv("PropalStatusDraft");
3427 $this->labelStatus[1] = $langs->transnoentitiesnoconv("PropalStatusValidated");
3428 $this->labelStatus[2] = $langs->transnoentitiesnoconv("PropalStatusSigned");
3429 $this->labelStatus[3] = $langs->transnoentitiesnoconv("PropalStatusNotSigned");
3430 $this->labelStatus[4] = $langs->transnoentitiesnoconv("PropalStatusBilled");
3431 $this->labelStatusShort[0] = $langs->transnoentitiesnoconv("PropalStatusDraftShort");
3432 $this->labelStatusShort[1] = $langs->transnoentitiesnoconv("PropalStatusValidatedShort");
3433 $this->labelStatusShort[2] = $langs->transnoentitiesnoconv("PropalStatusSignedShort");
3434 $this->labelStatusShort[3] = $langs->transnoentitiesnoconv("PropalStatusNotSignedShort");
3435 $this->labelStatusShort[4] = $langs->transnoentitiesnoconv("PropalStatusBilledShort");
3436 }
3437
3438 $statusType = '';
3439 if ($status == self::STATUS_DRAFT) {
3440 $statusType = 'status0';
3441 } elseif ($status == self::STATUS_VALIDATED) {
3442 $statusType = 'status1';
3443 } elseif ($status == self::STATUS_SIGNED) {
3444 $statusType = 'status4';
3445 } elseif ($status == self::STATUS_NOTSIGNED) {
3446 $statusType = 'status9';
3447 } elseif ($status == self::STATUS_BILLED) {
3448 $statusType = 'status6';
3449 }
3450
3451
3452 $parameters = array('status' => $status, 'mode' => $mode);
3453 $reshook = $hookmanager->executeHooks('LibStatut', $parameters, $this); // Note that $action and $object may have been modified by hook
3454
3455 if ($reshook > 0) {
3456 return $hookmanager->resPrint;
3457 }
3458
3459 return dolGetStatus($this->labelStatus[$status], $this->labelStatusShort[$status], '', $statusType, $mode);
3460 }
3461
3462
3463 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3471 public function load_board($user, $mode)
3472 {
3473 // phpcs:enable
3474 global $conf, $langs;
3475
3476 $clause = " WHERE";
3477
3478 $sql = "SELECT p.rowid, p.ref, p.datec as datec, p.fin_validite as datefin, p.total_ht";
3479 $sql .= " FROM ".MAIN_DB_PREFIX."propal as p";
3480 if (empty($user->rights->societe->client->voir) && !$user->socid) {
3481 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe_commerciaux as sc ON p.fk_soc = sc.fk_soc";
3482 $sql .= " WHERE sc.fk_user = ".((int) $user->id);
3483 $clause = " AND";
3484 }
3485 $sql .= $clause." p.entity IN (".getEntity('propal').")";
3486 if ($mode == 'opened') {
3487 $sql .= " AND p.fk_statut = ".self::STATUS_VALIDATED;
3488 }
3489 if ($mode == 'signed') {
3490 $sql .= " AND p.fk_statut = ".self::STATUS_SIGNED;
3491 }
3492 if ($user->socid) {
3493 $sql .= " AND p.fk_soc = ".((int) $user->socid);
3494 }
3495
3496 $resql = $this->db->query($sql);
3497 if ($resql) {
3498 $langs->load("propal");
3499 $now = dol_now();
3500
3501 $delay_warning = 0;
3502 $status = 0;
3503 $label = $labelShort = '';
3504 if ($mode == 'opened') {
3505 $delay_warning = $conf->propal->cloture->warning_delay;
3506 $status = self::STATUS_VALIDATED;
3507 $label = $langs->transnoentitiesnoconv("PropalsToClose");
3508 $labelShort = $langs->transnoentitiesnoconv("ToAcceptRefuse");
3509 }
3510 if ($mode == 'signed') {
3511 $delay_warning = $conf->propal->facturation->warning_delay;
3512 $status = self::STATUS_SIGNED;
3513 $label = $langs->trans("PropalsToBill"); // We set here bill but may be billed or ordered
3514 $labelShort = $langs->trans("ToBill");
3515 }
3516
3517 $response = new WorkboardResponse();
3518 $response->warning_delay = $delay_warning / 60 / 60 / 24;
3519 $response->label = $label;
3520 $response->labelShort = $labelShort;
3521 $response->url = DOL_URL_ROOT.'/comm/propal/list.php?search_status='.$status.'&mainmenu=commercial&leftmenu=propals';
3522 $response->url_late = DOL_URL_ROOT.'/comm/propal/list.php?search_option=late&mainmenu=commercial&leftmenu=propals&sortfield=p.datep&sortorder=asc';
3523 $response->img = img_object('', "propal");
3524
3525 // This assignment in condition is not a bug. It allows walking the results.
3526 while ($obj = $this->db->fetch_object($resql)) {
3527 $response->nbtodo++;
3528 $response->total += $obj->total_ht;
3529
3530 if ($mode == 'opened') {
3531 $datelimit = $this->db->jdate($obj->datefin);
3532 if ($datelimit < ($now - $delay_warning)) {
3533 $response->nbtodolate++;
3534 }
3535 }
3536 // TODO Definir regle des propales a facturer en retard
3537 // if ($mode == 'signed' && ! count($this->FactureListeArray($obj->rowid))) $this->nbtodolate++;
3538 }
3539
3540 return $response;
3541 } else {
3542 $this->error = $this->db->error();
3543 return -1;
3544 }
3545 }
3546
3547
3555 public function initAsSpecimen()
3556 {
3557 global $conf, $langs;
3558
3559 // Load array of products prodids
3560 $num_prods = 0;
3561 $prodids = array();
3562 $sql = "SELECT rowid";
3563 $sql .= " FROM ".MAIN_DB_PREFIX."product";
3564 $sql .= " WHERE entity IN (".getEntity('product').")";
3565 $sql .= $this->db->plimit(100);
3566
3567 $resql = $this->db->query($sql);
3568 if ($resql) {
3569 $num_prods = $this->db->num_rows($resql);
3570 $i = 0;
3571 while ($i < $num_prods) {
3572 $i++;
3573 $row = $this->db->fetch_row($resql);
3574 $prodids[$i] = $row[0];
3575 }
3576 }
3577
3578 // Initialise parametres
3579 $this->id = 0;
3580 $this->ref = 'SPECIMEN';
3581 $this->ref_client = 'NEMICEPS';
3582 $this->specimen = 1;
3583 $this->socid = 1;
3584 $this->date = time();
3585 $this->fin_validite = $this->date + 3600 * 24 * 30;
3586 $this->cond_reglement_id = 1;
3587 $this->cond_reglement_code = 'RECEP';
3588 $this->mode_reglement_id = 7;
3589 $this->mode_reglement_code = 'CHQ';
3590 $this->availability_id = 1;
3591 $this->availability_code = 'AV_NOW';
3592 $this->demand_reason_id = 1;
3593 $this->demand_reason_code = 'SRC_00';
3594 $this->note_public = 'This is a comment (public)';
3595 $this->note_private = 'This is a comment (private)';
3596
3597 $this->multicurrency_tx = 1;
3598 $this->multicurrency_code = $conf->currency;
3599
3600 // Lines
3601 $nbp = 5;
3602 $xnbp = 0;
3603 while ($xnbp < $nbp) {
3604 $line = new PropaleLigne($this->db);
3605 $line->desc = $langs->trans("Description")." ".$xnbp;
3606 $line->qty = 1;
3607 $line->subprice = 100;
3608 $line->price = 100;
3609 $line->tva_tx = 20;
3610 $line->localtax1_tx = 0;
3611 $line->localtax2_tx = 0;
3612 if ($xnbp == 2) {
3613 $line->total_ht = 50;
3614 $line->total_ttc = 60;
3615 $line->total_tva = 10;
3616 $line->remise_percent = 50;
3617 } else {
3618 $line->total_ht = 100;
3619 $line->total_ttc = 120;
3620 $line->total_tva = 20;
3621 $line->remise_percent = 00;
3622 }
3623
3624 if ($num_prods > 0) {
3625 $prodid = mt_rand(1, $num_prods);
3626 $line->fk_product = $prodids[$prodid];
3627 $line->product_ref = 'SPECIMEN';
3628 }
3629
3630 $this->lines[$xnbp] = $line;
3631
3632 $this->total_ht += $line->total_ht;
3633 $this->total_tva += $line->total_tva;
3634 $this->total_ttc += $line->total_ttc;
3635
3636 $xnbp++;
3637 }
3638 }
3639
3640 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3646 public function load_state_board()
3647 {
3648 // phpcs:enable
3649 global $user;
3650
3651 $this->nb = array();
3652 $clause = "WHERE";
3653
3654 $sql = "SELECT count(p.rowid) as nb";
3655 $sql .= " FROM ".MAIN_DB_PREFIX."propal as p";
3656 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe as s ON p.fk_soc = s.rowid";
3657 if (empty($user->rights->societe->client->voir) && !$user->socid) {
3658 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe_commerciaux as sc ON s.rowid = sc.fk_soc";
3659 $sql .= " WHERE sc.fk_user = ".((int) $user->id);
3660 $clause = "AND";
3661 }
3662 $sql .= " ".$clause." p.entity IN (".getEntity('propal').")";
3663
3664 $resql = $this->db->query($sql);
3665 if ($resql) {
3666 // This assignment in condition is not a bug. It allows walking the results.
3667 while ($obj = $this->db->fetch_object($resql)) {
3668 $this->nb["proposals"] = $obj->nb;
3669 }
3670 $this->db->free($resql);
3671 return 1;
3672 } else {
3673 dol_print_error($this->db);
3674 $this->error = $this->db->error();
3675 return -1;
3676 }
3677 }
3678
3679
3687 public function getNextNumRef($soc)
3688 {
3689 global $conf, $langs;
3690 $langs->load("propal");
3691
3692 $classname = $conf->global->PROPALE_ADDON;
3693
3694 if (!empty($classname)) {
3695 $mybool = false;
3696
3697 $file = $classname.".php";
3698
3699 // Include file with class
3700 $dirmodels = array_merge(array('/'), (array) $conf->modules_parts['models']);
3701 foreach ($dirmodels as $reldir) {
3702 $dir = dol_buildpath($reldir."core/modules/propale/");
3703
3704 // Load file with numbering class (if found)
3705 $mybool |= @include_once $dir.$file;
3706 }
3707
3708 if (!$mybool) {
3709 dol_print_error('', "Failed to include file ".$file);
3710 return '';
3711 }
3712
3713 $obj = new $classname();
3714 $numref = "";
3715 $numref = $obj->getNextValue($soc, $this);
3716
3717 if ($numref != "") {
3718 return $numref;
3719 } else {
3720 $this->error = $obj->error;
3721 //dol_print_error($db,"Propale::getNextNumRef ".$obj->error);
3722 return "";
3723 }
3724 } else {
3725 $langs->load("errors");
3726 print $langs->trans("Error")." ".$langs->trans("ErrorModuleSetupNotComplete", $langs->transnoentitiesnoconv("Proposal"));
3727 return "";
3728 }
3729 }
3730
3737 public function getTooltipContentArray($params)
3738 {
3739 global $conf, $langs, $user;
3740
3741 $langs->load('propal');
3742 $datas = [];
3743 $nofetch = !empty($params['nofetch']);
3744
3745 if (!empty($conf->global->MAIN_OPTIMIZEFORTEXTBROWSER)) {
3746 return ['optimize' => $langs->trans("Proposal")];
3747 }
3748 if ($user->hasRight('propal', 'lire')) {
3749 $datas['picto'] = img_picto('', $this->picto).' <u class="paddingrightonly">'.$langs->trans("Proposal").'</u>';
3750 if (isset($this->statut)) {
3751 $datas['status'] = ' '.$this->getLibStatut(5);
3752 }
3753 if (!empty($this->ref)) {
3754 $datas['ref'] = '<br><b>'.$langs->trans('Ref').':</b> '.$this->ref;
3755 }
3756 if (!$nofetch) {
3757 $langs->load('companies');
3758 if (empty($this->thirdparty)) {
3759 $this->fetch_thirdparty();
3760 }
3761 $datas['customer'] = '<br><b>'.$langs->trans('Customer').':</b> '.$this->thirdparty->getNomUrl(1, '', 0, 1);
3762 }
3763 if (!empty($this->ref_client)) {
3764 $datas['refcustomer'] = '<br><b>'.$langs->trans('RefCustomer').':</b> '.$this->ref_client;
3765 }
3766 if (!$nofetch) {
3767 $langs->load('project');
3768 if (empty($this->project)) {
3769 $res = $this->fetch_project();
3770 if ($res > 0) {
3771 $datas['project'] = '<br><b>'.$langs->trans('Project').':</b> '.$this->project->getNomUrl(1, '', 0, 1);
3772 }
3773 }
3774 }
3775 if (!empty($this->total_ht)) {
3776 $datas['amountht'] = '<br><b>'.$langs->trans('AmountHT').':</b> '.price($this->total_ht, 0, $langs, 0, -1, -1, $conf->currency);
3777 }
3778 if (!empty($this->total_tva)) {
3779 $datas['vat'] = '<br><b>'.$langs->trans('VAT').':</b> '.price($this->total_tva, 0, $langs, 0, -1, -1, $conf->currency);
3780 }
3781 if (!empty($this->total_ttc)) {
3782 $datas['amountttc'] = '<br><b>'.$langs->trans('AmountTTC').':</b> '.price($this->total_ttc, 0, $langs, 0, -1, -1, $conf->currency);
3783 }
3784 if (!empty($this->date)) {
3785 $datas['date'] = '<br><b>'.$langs->trans('Date').':</b> '.dol_print_date($this->date, 'day');
3786 }
3787 if (!empty($this->delivery_date)) {
3788 $datas['deliverydate'] = '<br><b>'.$langs->trans('DeliveryDate').':</b> '.dol_print_date($this->delivery_date, 'dayhour');
3789 }
3790 }
3791
3792 return $datas;
3793 }
3794
3806 public function getNomUrl($withpicto = 0, $option = '', $get_params = '', $notooltip = 0, $save_lastsearch_value = -1, $addlinktonotes = -1)
3807 {
3808 global $langs, $conf, $user, $hookmanager;
3809
3810 if (!empty($conf->dol_no_mouse_hover)) {
3811 $notooltip = 1; // Force disable tooltips
3812 }
3813
3814 $result = '';
3815 $params = [
3816 'id' => $this->id,
3817 'objecttype' => $this->element,
3818 'option' => $option,
3819 'nofetch' => 1,
3820 ];
3821 $classfortooltip = 'classfortooltip';
3822 $dataparams = '';
3823 if (getDolGlobalInt('MAIN_ENABLE_AJAX_TOOLTIP')) {
3824 $classfortooltip = 'classforajaxtooltip';
3825 $dataparams = ' data-params="'.dol_escape_htmltag(json_encode($params)).'"';
3826 $label = '';
3827 } else {
3828 $label = implode($this->getTooltipContentArray($params));
3829 }
3830
3831 $url = '';
3832 if ($user->hasRight('propal', 'lire')) {
3833 if ($option == '') {
3834 $url = DOL_URL_ROOT.'/comm/propal/card.php?id='.$this->id.$get_params;
3835 } elseif ($option == 'compta') { // deprecated
3836 $url = DOL_URL_ROOT.'/comm/propal/card.php?id='.$this->id.$get_params;
3837 } elseif ($option == 'expedition') {
3838 $url = DOL_URL_ROOT.'/expedition/propal.php?id='.$this->id.$get_params;
3839 } elseif ($option == 'document') {
3840 $url = DOL_URL_ROOT.'/comm/propal/document.php?id='.$this->id.$get_params;
3841 }
3842
3843 if ($option != 'nolink') {
3844 // Add param to save lastsearch_values or not
3845 $add_save_lastsearch_values = ($save_lastsearch_value == 1 ? 1 : 0);
3846 if ($save_lastsearch_value == -1 && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) {
3847 $add_save_lastsearch_values = 1;
3848 }
3849 if ($add_save_lastsearch_values) {
3850 $url .= '&save_lastsearch_values=1';
3851 }
3852 }
3853 }
3854
3855 $linkclose = '';
3856 if (empty($notooltip) && $user->hasRight('propal', 'lire')) {
3857 if (!empty($conf->global->MAIN_OPTIMIZEFORTEXTBROWSER)) {
3858 $label = $langs->trans("Proposal");
3859 $linkclose .= ' alt="'.dol_escape_htmltag($label, 1).'"';
3860 }
3861 $linkclose .= ($label ? ' title="'.dol_escape_htmltag($label, 1).'"' : ' title="tocomplete"');
3862 $linkclose .= $dataparams.' class="'.$classfortooltip.'"';
3863 }
3864
3865 $linkstart = '<a href="'.$url.'"';
3866 $linkstart .= $linkclose.'>';
3867 $linkend = '</a>';
3868
3869 $result .= $linkstart;
3870 if ($withpicto) {
3871 $result .= img_object(($notooltip ? '' : $label), ($this->picto ? $this->picto : 'generic'), (($withpicto != 2) ? 'class="paddingright"' : ''), 0, 0, $notooltip ? 0 : 1);
3872 }
3873 if ($withpicto != 2) {
3874 $result .= $this->ref;
3875 }
3876 $result .= $linkend;
3877
3878 if ($addlinktonotes >= 0) {
3879 $txttoshow = '';
3880
3881 if ($addlinktonotes == 0) {
3882 if (!empty($this->note_private) || !empty($this->note_public)) {
3883 $txttoshow = $langs->trans('ViewPrivateNote');
3884 }
3885 } elseif ($addlinktonotes == 1) {
3886 if (!empty($this->note_private)) {
3887 $txttoshow .= ($user->socid > 0 ? '' : dol_string_nohtmltag($this->note_private, 1));
3888 }
3889 } elseif ($addlinktonotes == 2) {
3890 if (!empty($this->note_public)) {
3891 $txttoshow .= dol_string_nohtmltag($this->note_public, 1);
3892 }
3893 } elseif ($addlinktonotes == 3) {
3894 if ($user->socid > 0) {
3895 if (!empty($this->note_public)) {
3896 $txttoshow .= dol_string_nohtmltag($this->note_public, 1);
3897 }
3898 } else {
3899 if (!empty($this->note_public)) {
3900 $txttoshow .= dol_string_nohtmltag($this->note_public, 1);
3901 }
3902 if (!empty($this->note_private)) {
3903 if (!empty($txttoshow)) {
3904 $txttoshow .= '<br><br>';
3905 }
3906 $txttoshow .= dol_string_nohtmltag($this->note_private, 1);
3907 }
3908 }
3909 }
3910
3911 if ($txttoshow) {
3912 $result .= ' <span class="note inline-block">';
3913 $result .= '<a href="'.DOL_URL_ROOT.'/comm/propal/note.php?id='.$this->id.'" class="classfortooltip" title="'.dol_escape_htmltag($txttoshow).'">';
3914 $result .= img_picto('', 'note');
3915 $result .= '</a>';
3916 $result .= '</span>';
3917 }
3918 }
3919
3920 global $action;
3921 $hookmanager->initHooks(array($this->element . 'dao'));
3922 $parameters = array('id'=>$this->id, 'getnomurl' => &$result);
3923 $reshook = $hookmanager->executeHooks('getNomUrl', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
3924 if ($reshook > 0) {
3925 $result = $hookmanager->resPrint;
3926 } else {
3927 $result .= $hookmanager->resPrint;
3928 }
3929 return $result;
3930 }
3931
3938 public function getLinesArray($filters = '')
3939 {
3940 return $this->fetch_lines(0, 0, $filters);
3941 }
3942
3954 public function generateDocument($modele, $outputlangs, $hidedetails = 0, $hidedesc = 0, $hideref = 0, $moreparams = null)
3955 {
3956 global $conf, $langs;
3957
3958 $langs->load("propale");
3959 $outputlangs->load("products");
3960
3961 if (!dol_strlen($modele)) {
3962 $modele = 'azur';
3963
3964 if ($this->model_pdf) {
3965 $modele = $this->model_pdf;
3966 } elseif (!empty($conf->global->PROPALE_ADDON_PDF)) {
3967 $modele = $conf->global->PROPALE_ADDON_PDF;
3968 }
3969 }
3970
3971 $modelpath = "core/modules/propale/doc/";
3972
3973 return $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams);
3974 }
3975
3984 public static function replaceThirdparty(DoliDB $dbs, $origin_id, $dest_id)
3985 {
3986 $tables = array(
3987 'propal'
3988 );
3989
3990 return CommonObject::commonReplaceThirdparty($dbs, $origin_id, $dest_id, $tables);
3991 }
3992
4001 public static function replaceProduct(DoliDB $db, $origin_id, $dest_id)
4002 {
4003 $tables = array(
4004 'propaldet'
4005 );
4006
4007 return CommonObject::commonReplaceProduct($db, $origin_id, $dest_id, $tables);
4008 }
4009
4017 public function getKanbanView($option = '', $arraydata = null)
4018 {
4019 global $langs;
4020
4021 $selected = (empty($arraydata['selected']) ? 0 : $arraydata['selected']);
4022
4023 $return = '<div class="box-flex-item box-flex-grow-zero">';
4024 $return .= '<div class="info-box info-box-sm">';
4025 $return .= '<span class="info-box-icon bg-infobox-action">';
4026 $return .= img_picto('', $this->picto);
4027 $return .= '</span>';
4028 $return .= '<div class="info-box-content">';
4029 $return .= '<span class="info-box-ref inline-block tdoverflowmax150 valignmiddle">'.(method_exists($this, 'getNomUrl') ? $this->getNomUrl() : $this->ref).'</span>';
4030 $return .= '<input id="cb'.$this->id.'" class="flat checkforselect fright" type="checkbox" name="toselect[]" value="'.$this->id.'"'.($selected ? ' checked="checked"' : '').'>';
4031 if (property_exists($this, 'fk_project')) {
4032 $return .= '<span class="info-box-ref"> | '.$this->fk_project.'</span>';
4033 }
4034 if (property_exists($this, 'author')) {
4035 $return .= '<br><span class="info-box-label">'.$this->author.'</span>';
4036 }
4037 if (property_exists($this, 'total_ht')) {
4038 $return .='<br><span class="" >'.$langs->trans("AmountHT").' : </span><span class="info-box-label amount">'.price($this->total_ht).'</span>';
4039 }
4040 if (method_exists($this, 'getLibStatut')) {
4041 $return .= '<br><div class="info-box-status margintoponly">'.$this->getLibStatut(3).'</div>';
4042 }
4043 $return .= '</div>';
4044 $return .= '</div>';
4045 $return .= '</div>';
4046 return $return;
4047 }
4048}
4049
4054{
4058 public $element = 'propaldet';
4059
4063 public $table_element = 'propaldet';
4064
4065 public $oldline;
4066
4067 // From llx_propaldet
4068 public $fk_propal;
4069 public $fk_parent_line;
4070 public $desc; // Description ligne
4071 public $fk_product; // Id produit predefini
4082 public $product_type = Product::TYPE_PRODUCT;
4083
4084 public $qty;
4085
4086 public $tva_tx;
4087 public $vat_src_code;
4088
4089 public $subprice;
4090 public $remise_percent;
4091 public $fk_remise_except;
4092
4093 public $rang = 0;
4094
4095 public $fk_fournprice;
4096 public $pa_ht;
4097 public $marge_tx;
4098 public $marque_tx;
4099
4100 public $special_code; // Tag for special lines (exlusive tags)
4101 // 1: frais de port
4102 // 2: ecotaxe
4103 // 3: option line (when qty = 0)
4104
4105 public $info_bits = 0; // Some other info:
4106 // Bit 0: 0 si TVA normal - 1 si TVA NPR
4107 // Bit 1: 0 ligne normale - 1 si ligne de remise fixe
4108
4109 public $total_ht; // Total HT de la ligne toute quantite et incluant la remise ligne
4110 public $total_tva; // Total TVA de la ligne toute quantite et incluant la remise ligne
4111 public $total_ttc; // Total TTC de la ligne toute quantite et incluant la remise ligne
4112
4117 public $remise;
4122 public $price;
4123
4124 // From llx_product
4129 public $ref;
4134 public $product_ref;
4139 public $libelle;
4144 public $label;
4149 public $product_label;
4154 public $product_desc;
4155
4160 public $product_tobatch;
4161
4166 public $product_barcode;
4167
4168 public $localtax1_tx; // Local tax 1
4169 public $localtax2_tx; // Local tax 2
4170 public $localtax1_type; // Local tax 1 type
4171 public $localtax2_type; // Local tax 2 type
4172 public $total_localtax1; // Line total local tax 1
4173 public $total_localtax2; // Line total local tax 2
4174
4175 public $date_start;
4176 public $date_end;
4177
4178 public $skip_update_total; // Skip update price total for special lines
4179
4180 // Multicurrency
4181 public $fk_multicurrency;
4182 public $multicurrency_code;
4183 public $multicurrency_subprice;
4184 public $multicurrency_total_ht;
4185 public $multicurrency_total_tva;
4186 public $multicurrency_total_ttc;
4187
4188
4194 public function __construct($db)
4195 {
4196 $this->db = $db;
4197 }
4198
4205 public function fetch($rowid)
4206 {
4207 $sql = 'SELECT pd.rowid, pd.fk_propal, pd.fk_parent_line, pd.fk_product, pd.label as custom_label, pd.description, pd.price, pd.qty, pd.vat_src_code, pd.tva_tx,';
4208 $sql .= ' pd.remise, pd.remise_percent, pd.fk_remise_except, pd.subprice,';
4209 $sql .= ' pd.info_bits, pd.total_ht, pd.total_tva, pd.total_ttc, pd.fk_product_fournisseur_price as fk_fournprice, pd.buy_price_ht as pa_ht, pd.special_code, pd.rang,';
4210 $sql .= ' pd.fk_unit,';
4211 $sql .= ' pd.localtax1_tx, pd.localtax2_tx, pd.total_localtax1, pd.total_localtax2,';
4212 $sql .= ' pd.fk_multicurrency, pd.multicurrency_code, pd.multicurrency_subprice, pd.multicurrency_total_ht, pd.multicurrency_total_tva, pd.multicurrency_total_ttc,';
4213 $sql .= ' p.ref as product_ref, p.label as product_label, p.description as product_desc,';
4214 $sql .= ' pd.date_start, pd.date_end, pd.product_type';
4215 $sql .= ' FROM '.MAIN_DB_PREFIX.'propaldet as pd';
4216 $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'product as p ON pd.fk_product = p.rowid';
4217 $sql .= ' WHERE pd.rowid = '.((int) $rowid);
4218
4219 $result = $this->db->query($sql);
4220 if ($result) {
4221 $objp = $this->db->fetch_object($result);
4222
4223 if ($objp) {
4224 $this->id = $objp->rowid;
4225 $this->rowid = $objp->rowid; // deprecated
4226 $this->fk_propal = $objp->fk_propal;
4227 $this->fk_parent_line = $objp->fk_parent_line;
4228 $this->label = $objp->custom_label;
4229 $this->desc = $objp->description;
4230 $this->qty = $objp->qty;
4231 $this->price = $objp->price; // deprecated
4232 $this->subprice = $objp->subprice;
4233 $this->vat_src_code = $objp->vat_src_code;
4234 $this->tva_tx = $objp->tva_tx;
4235 $this->remise = $objp->remise; // deprecated
4236 $this->remise_percent = $objp->remise_percent;
4237 $this->fk_remise_except = $objp->fk_remise_except;
4238 $this->fk_product = $objp->fk_product;
4239 $this->info_bits = $objp->info_bits;
4240
4241 $this->total_ht = $objp->total_ht;
4242 $this->total_tva = $objp->total_tva;
4243 $this->total_ttc = $objp->total_ttc;
4244
4245 $this->fk_fournprice = $objp->fk_fournprice;
4246
4247 $marginInfos = getMarginInfos($objp->subprice, $objp->remise_percent, $objp->tva_tx, $objp->localtax1_tx, $objp->localtax2_tx, $this->fk_fournprice, $objp->pa_ht);
4248 $this->pa_ht = $marginInfos[0];
4249 $this->marge_tx = $marginInfos[1];
4250 $this->marque_tx = $marginInfos[2];
4251
4252 $this->special_code = $objp->special_code;
4253 $this->product_type = $objp->product_type;
4254 $this->rang = $objp->rang;
4255
4256 $this->ref = $objp->product_ref; // deprecated
4257 $this->product_ref = $objp->product_ref;
4258 $this->libelle = $objp->product_label; // deprecated
4259 $this->product_label = $objp->product_label;
4260 $this->product_desc = $objp->product_desc;
4261 $this->fk_unit = $objp->fk_unit;
4262
4263 $this->date_start = $this->db->jdate($objp->date_start);
4264 $this->date_end = $this->db->jdate($objp->date_end);
4265
4266 // Multicurrency
4267 $this->fk_multicurrency = $objp->fk_multicurrency;
4268 $this->multicurrency_code = $objp->multicurrency_code;
4269 $this->multicurrency_subprice = $objp->multicurrency_subprice;
4270 $this->multicurrency_total_ht = $objp->multicurrency_total_ht;
4271 $this->multicurrency_total_tva = $objp->multicurrency_total_tva;
4272 $this->multicurrency_total_ttc = $objp->multicurrency_total_ttc;
4273
4274 $this->fetch_optionals();
4275
4276 $this->db->free($result);
4277
4278 return 1;
4279 } else {
4280 return 0;
4281 }
4282 } else {
4283 return -1;
4284 }
4285 }
4286
4293 public function insert($notrigger = 0)
4294 {
4295 global $conf, $user;
4296
4297 $error = 0;
4298
4299 dol_syslog(get_class($this)."::insert rang=".$this->rang);
4300
4301 $pa_ht_isemptystring = (empty($this->pa_ht) && $this->pa_ht == ''); // If true, we can use a default value. If this->pa_ht = '0', we must use '0'.
4302
4303 // Clean parameters
4304 if (empty($this->tva_tx)) {
4305 $this->tva_tx = 0;
4306 }
4307 if (empty($this->localtax1_tx)) {
4308 $this->localtax1_tx = 0;
4309 }
4310 if (empty($this->localtax2_tx)) {
4311 $this->localtax2_tx = 0;
4312 }
4313 if (empty($this->localtax1_type)) {
4314 $this->localtax1_type = 0;
4315 }
4316 if (empty($this->localtax2_type)) {
4317 $this->localtax2_type = 0;
4318 }
4319 if (empty($this->total_localtax1)) {
4320 $this->total_localtax1 = 0;
4321 }
4322 if (empty($this->total_localtax2)) {
4323 $this->total_localtax2 = 0;
4324 }
4325 if (empty($this->rang)) {
4326 $this->rang = 0;
4327 }
4328 if (empty($this->remise_percent) || !is_numeric($this->remise_percent)) {
4329 $this->remise_percent = 0;
4330 }
4331 if (empty($this->info_bits)) {
4332 $this->info_bits = 0;
4333 }
4334 if (empty($this->special_code)) {
4335 $this->special_code = 0;
4336 }
4337 if (empty($this->fk_parent_line)) {
4338 $this->fk_parent_line = 0;
4339 }
4340 if (empty($this->fk_fournprice)) {
4341 $this->fk_fournprice = 0;
4342 }
4343 if (!is_numeric($this->qty)) {
4344 $this->qty = 0;
4345 }
4346 if (empty($this->pa_ht)) {
4347 $this->pa_ht = 0;
4348 }
4349 if (empty($this->multicurrency_subprice)) {
4350 $this->multicurrency_subprice = 0;
4351 }
4352 if (empty($this->multicurrency_total_ht)) {
4353 $this->multicurrency_total_ht = 0;
4354 }
4355 if (empty($this->multicurrency_total_tva)) {
4356 $this->multicurrency_total_tva = 0;
4357 }
4358 if (empty($this->multicurrency_total_ttc)) {
4359 $this->multicurrency_total_ttc = 0;
4360 }
4361
4362 // if buy price not defined, define buyprice as configured in margin admin
4363 if ($this->pa_ht == 0 && $pa_ht_isemptystring) {
4364 if (($result = $this->defineBuyPrice($this->subprice, $this->remise_percent, $this->fk_product)) < 0) {
4365 return $result;
4366 } else {
4367 $this->pa_ht = $result;
4368 }
4369 }
4370
4371 // Check parameters
4372 if ($this->product_type < 0) {
4373 return -1;
4374 }
4375
4376 $this->db->begin();
4377
4378 // Insert line into database
4379 $sql = 'INSERT INTO '.MAIN_DB_PREFIX.'propaldet';
4380 $sql .= ' (fk_propal, fk_parent_line, label, description, fk_product, product_type,';
4381 $sql .= ' fk_remise_except, qty, vat_src_code, tva_tx, localtax1_tx, localtax2_tx, localtax1_type, localtax2_type,';
4382 $sql .= ' subprice, remise_percent, ';
4383 $sql .= ' info_bits, ';
4384 $sql .= ' total_ht, total_tva, total_localtax1, total_localtax2, total_ttc, fk_product_fournisseur_price, buy_price_ht, special_code, rang,';
4385 $sql .= ' fk_unit,';
4386 $sql .= ' date_start, date_end';
4387 $sql .= ', fk_multicurrency, multicurrency_code, multicurrency_subprice, multicurrency_total_ht, multicurrency_total_tva, multicurrency_total_ttc)';
4388 $sql .= " VALUES (".$this->fk_propal.",";
4389 $sql .= " ".($this->fk_parent_line > 0 ? "'".$this->db->escape($this->fk_parent_line)."'" : "null").",";
4390 $sql .= " ".(!empty($this->label) ? "'".$this->db->escape($this->label)."'" : "null").",";
4391 $sql .= " '".$this->db->escape($this->desc)."',";
4392 $sql .= " ".($this->fk_product ? "'".$this->db->escape($this->fk_product)."'" : "null").",";
4393 $sql .= " '".$this->db->escape($this->product_type)."',";
4394 $sql .= " ".($this->fk_remise_except ? "'".$this->db->escape($this->fk_remise_except)."'" : "null").",";
4395 $sql .= " ".price2num($this->qty, 'MS').",";
4396 $sql .= " ".(empty($this->vat_src_code) ? "''" : "'".$this->db->escape($this->vat_src_code)."'").",";
4397 $sql .= " ".price2num($this->tva_tx).",";
4398 $sql .= " ".price2num($this->localtax1_tx).",";
4399 $sql .= " ".price2num($this->localtax2_tx).",";
4400 $sql .= " '".$this->db->escape($this->localtax1_type)."',";
4401 $sql .= " '".$this->db->escape($this->localtax2_type)."',";
4402 $sql .= " ".(price2num($this->subprice) !== '' ? price2num($this->subprice, 'MU') : "null").",";
4403 $sql .= " ".price2num($this->remise_percent, 3).",";
4404 $sql .= " ".(isset($this->info_bits) ? ((int) $this->info_bits) : "null").",";
4405 $sql .= " ".price2num($this->total_ht, 'MT').",";
4406 $sql .= " ".price2num($this->total_tva, 'MT').",";
4407 $sql .= " ".price2num($this->total_localtax1, 'MT').",";
4408 $sql .= " ".price2num($this->total_localtax2, 'MT').",";
4409 $sql .= " ".price2num($this->total_ttc, 'MT').",";
4410 $sql .= " ".(!empty($this->fk_fournprice) ? "'".$this->db->escape($this->fk_fournprice)."'" : "null").",";
4411 $sql .= " ".(isset($this->pa_ht) ? "'".price2num($this->pa_ht)."'" : "null").",";
4412 $sql .= ' '.((int) $this->special_code).',';
4413 $sql .= ' '.((int) $this->rang).',';
4414 $sql .= ' '.(empty($this->fk_unit) ? 'NULL' : ((int) $this->fk_unit)).',';
4415 $sql .= " ".(!empty($this->date_start) ? "'".$this->db->idate($this->date_start)."'" : "null").',';
4416 $sql .= " ".(!empty($this->date_end) ? "'".$this->db->idate($this->date_end)."'" : "null");
4417 $sql .= ", ".($this->fk_multicurrency > 0 ? ((int) $this->fk_multicurrency) : 'null');
4418 $sql .= ", '".$this->db->escape($this->multicurrency_code)."'";
4419 $sql .= ", ".price2num($this->multicurrency_subprice, 'CU');
4420 $sql .= ", ".price2num($this->multicurrency_total_ht, 'CT');
4421 $sql .= ", ".price2num($this->multicurrency_total_tva, 'CT');
4422 $sql .= ", ".price2num($this->multicurrency_total_ttc, 'CT');
4423 $sql .= ')';
4424
4425 dol_syslog(get_class($this).'::insert', LOG_DEBUG);
4426 $resql = $this->db->query($sql);
4427 if ($resql) {
4428 $this->rowid = $this->db->last_insert_id(MAIN_DB_PREFIX.'propaldet');
4429
4430 if (!$error) {
4431 $this->id = $this->rowid;
4432 $result = $this->insertExtraFields();
4433 if ($result < 0) {
4434 $error++;
4435 }
4436 }
4437
4438 if (!$error && !$notrigger) {
4439 // Call trigger
4440 $result = $this->call_trigger('LINEPROPAL_INSERT', $user);
4441 if ($result < 0) {
4442 $this->db->rollback();
4443 return -1;
4444 }
4445 // End call triggers
4446 }
4447
4448 if (!$error) {
4449 $this->db->commit();
4450 return 1;
4451 }
4452
4453 foreach ($this->errors as $errmsg) {
4454 dol_syslog(get_class($this)."::insert ".$errmsg, LOG_ERR);
4455 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
4456 }
4457 $this->db->rollback();
4458 return -1 * $error;
4459 } else {
4460 $this->error = $this->db->error()." sql=".$sql;
4461 $this->db->rollback();
4462 return -1;
4463 }
4464 }
4465
4473 public function delete(User $user, $notrigger = 0)
4474 {
4475 global $conf;
4476
4477 $error = 0;
4478 $this->db->begin();
4479
4480 if (!$notrigger) {
4481 // Call trigger
4482 $result = $this->call_trigger('LINEPROPAL_DELETE', $user);
4483 if ($result < 0) {
4484 $error++;
4485 }
4486 }
4487 // End call triggers
4488
4489 if (!$error) {
4490 $sql = "DELETE FROM " . MAIN_DB_PREFIX . "propaldet WHERE rowid = " . ((int) $this->rowid);
4491 dol_syslog("PropaleLigne::delete", LOG_DEBUG);
4492 if ($this->db->query($sql)) {
4493 // Remove extrafields
4494 if (!$error) {
4495 $this->id = $this->rowid;
4496 $result = $this->deleteExtraFields();
4497 if ($result < 0) {
4498 $error++;
4499 dol_syslog(get_class($this) . "::delete error -4 " . $this->error, LOG_ERR);
4500 }
4501 }
4502 } else {
4503 $this->error = $this->db->error() . " sql=" . $sql;
4504 $error++;
4505 }
4506 }
4507
4508 if ($error) {
4509 $this->db->rollback();
4510 return -1;
4511 } else {
4512 $this->db->commit();
4513 return 1;
4514 }
4515 }
4516
4523 public function update($notrigger = 0)
4524 {
4525 global $conf, $user;
4526
4527 $error = 0;
4528
4529 $pa_ht_isemptystring = (empty($this->pa_ht) && $this->pa_ht == ''); // If true, we can use a default value. If this->pa_ht = '0', we must use '0'.
4530
4531 if (empty($this->id) && !empty($this->rowid)) {
4532 $this->id = $this->rowid;
4533 }
4534
4535 // Clean parameters
4536 if (empty($this->tva_tx)) {
4537 $this->tva_tx = 0;
4538 }
4539 if (empty($this->localtax1_tx)) {
4540 $this->localtax1_tx = 0;
4541 }
4542 if (empty($this->localtax2_tx)) {
4543 $this->localtax2_tx = 0;
4544 }
4545 if (empty($this->total_localtax1)) {
4546 $this->total_localtax1 = 0;
4547 }
4548 if (empty($this->total_localtax2)) {
4549 $this->total_localtax2 = 0;
4550 }
4551 if (empty($this->localtax1_type)) {
4552 $this->localtax1_type = 0;
4553 }
4554 if (empty($this->localtax2_type)) {
4555 $this->localtax2_type = 0;
4556 }
4557 if (empty($this->marque_tx)) {
4558 $this->marque_tx = 0;
4559 }
4560 if (empty($this->marge_tx)) {
4561 $this->marge_tx = 0;
4562 }
4563 if (empty($this->price)) {
4564 $this->price = 0; // TODO A virer
4565 }
4566 if (empty($this->remise_percent)) {
4567 $this->remise_percent = 0;
4568 }
4569 if (empty($this->info_bits)) {
4570 $this->info_bits = 0;
4571 }
4572 if (empty($this->special_code)) {
4573 $this->special_code = 0;
4574 }
4575 if (empty($this->fk_parent_line)) {
4576 $this->fk_parent_line = 0;
4577 }
4578 if (empty($this->fk_fournprice)) {
4579 $this->fk_fournprice = 0;
4580 }
4581 if (empty($this->subprice)) {
4582 $this->subprice = 0;
4583 }
4584 if (empty($this->pa_ht)) {
4585 $this->pa_ht = 0;
4586 }
4587
4588 // if buy price not defined, define buyprice as configured in margin admin
4589 if ($this->pa_ht == 0 && $pa_ht_isemptystring) {
4590 if (($result = $this->defineBuyPrice($this->subprice, $this->remise_percent, $this->fk_product)) < 0) {
4591 return $result;
4592 } else {
4593 $this->pa_ht = $result;
4594 }
4595 }
4596
4597 $this->db->begin();
4598
4599 // Mise a jour ligne en base
4600 $sql = "UPDATE ".MAIN_DB_PREFIX."propaldet SET";
4601 $sql .= " description='".$this->db->escape($this->desc)."'";
4602 $sql .= ", label=".(!empty($this->label) ? "'".$this->db->escape($this->label)."'" : "null");
4603 $sql .= ", product_type=".$this->product_type;
4604 $sql .= ", vat_src_code = '".(empty($this->vat_src_code) ? '' : $this->vat_src_code)."'";
4605 $sql .= ", tva_tx='".price2num($this->tva_tx)."'";
4606 $sql .= ", localtax1_tx=".price2num($this->localtax1_tx);
4607 $sql .= ", localtax2_tx=".price2num($this->localtax2_tx);
4608 $sql .= ", localtax1_type='".$this->db->escape($this->localtax1_type)."'";
4609 $sql .= ", localtax2_type='".$this->db->escape($this->localtax2_type)."'";
4610 $sql .= ", qty='".price2num($this->qty)."'";
4611 $sql .= ", subprice=".price2num($this->subprice);
4612 $sql .= ", remise_percent=".price2num($this->remise_percent);
4613 $sql .= ", price=".(float) price2num($this->price); // TODO A virer
4614 $sql .= ", remise=".(float) price2num($this->remise); // TODO A virer
4615 $sql .= ", info_bits='".$this->db->escape($this->info_bits)."'";
4616 if (empty($this->skip_update_total)) {
4617 $sql .= ", total_ht=".price2num($this->total_ht);
4618 $sql .= ", total_tva=".price2num($this->total_tva);
4619 $sql .= ", total_ttc=".price2num($this->total_ttc);
4620 $sql .= ", total_localtax1=".price2num($this->total_localtax1);
4621 $sql .= ", total_localtax2=".price2num($this->total_localtax2);
4622 }
4623 $sql .= ", fk_product_fournisseur_price=".(!empty($this->fk_fournprice) ? "'".$this->db->escape($this->fk_fournprice)."'" : "null");
4624 $sql .= ", buy_price_ht=".price2num($this->pa_ht);
4625 if (strlen($this->special_code)) {
4626 $sql .= ", special_code=".$this->special_code;
4627 }
4628 $sql .= ", fk_parent_line=".($this->fk_parent_line > 0 ? $this->fk_parent_line : "null");
4629 if (!empty($this->rang)) {
4630 $sql .= ", rang=".((int) $this->rang);
4631 }
4632 $sql .= ", date_start=".(!empty($this->date_start) ? "'".$this->db->idate($this->date_start)."'" : "null");
4633 $sql .= ", date_end=".(!empty($this->date_end) ? "'".$this->db->idate($this->date_end)."'" : "null");
4634 $sql .= ", fk_unit=".(!$this->fk_unit ? 'NULL' : $this->fk_unit);
4635
4636 // Multicurrency
4637 $sql .= ", multicurrency_subprice=".price2num($this->multicurrency_subprice);
4638 $sql .= ", multicurrency_total_ht=".price2num($this->multicurrency_total_ht);
4639 $sql .= ", multicurrency_total_tva=".price2num($this->multicurrency_total_tva);
4640 $sql .= ", multicurrency_total_ttc=".price2num($this->multicurrency_total_ttc);
4641
4642 $sql .= " WHERE rowid = ".((int) $this->id);
4643
4644 dol_syslog(get_class($this)."::update", LOG_DEBUG);
4645 $resql = $this->db->query($sql);
4646 if ($resql) {
4647 if (!$error) {
4648 $result = $this->insertExtraFields();
4649 if ($result < 0) {
4650 $error++;
4651 }
4652 }
4653
4654 if (!$error && !$notrigger) {
4655 // Call trigger
4656 $result = $this->call_trigger('LINEPROPAL_MODIFY', $user);
4657 if ($result < 0) {
4658 $this->db->rollback();
4659 return -1;
4660 }
4661 // End call triggers
4662 }
4663
4664 if (!$error) {
4665 $this->db->commit();
4666 return 1;
4667 }
4668
4669 foreach ($this->errors as $errmsg) {
4670 dol_syslog(get_class($this)."::update ".$errmsg, LOG_ERR);
4671 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
4672 }
4673 $this->db->rollback();
4674 return -1 * $error;
4675 } else {
4676 $this->error = $this->db->error();
4677 $this->db->rollback();
4678 return -2;
4679 }
4680 }
4681
4682 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4689 public function update_total()
4690 {
4691 // phpcs:enable
4692 $this->db->begin();
4693
4694 // Mise a jour ligne en base
4695 $sql = "UPDATE ".MAIN_DB_PREFIX."propaldet SET";
4696 $sql .= " total_ht=".price2num($this->total_ht, 'MT');
4697 $sql .= ",total_tva=".price2num($this->total_tva, 'MT');
4698 $sql .= ",total_ttc=".price2num($this->total_ttc, 'MT');
4699 $sql .= " WHERE rowid = ".((int) $this->rowid);
4700
4701 dol_syslog("PropaleLigne::update_total", LOG_DEBUG);
4702
4703 $resql = $this->db->query($sql);
4704 if ($resql) {
4705 $this->db->commit();
4706 return 1;
4707 } else {
4708 $this->error = $this->db->error();
4709 $this->db->rollback();
4710 return -2;
4711 }
4712 }
4713}
$object ref
Definition info.php:78
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.
add_object_linked($origin=null, $origin_id=null, $f_user=null, $notrigger=0)
Add an object link into llx_element_element.
defineBuyPrice($unitPrice=0.0, $discountPercent=0.0, $fk_product=0)
Get buy price to use for margin calculation.
commonGenerateDocument($modelspath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams=null)
Common function for all objects extending CommonObject for generating documents.
fetch_thirdparty($force_thirdparty_id=0)
Load the third party of object, from id $this->socid or $this->fk_soc, into this->thirdparty.
setErrorsFromObject($object)
setErrorsFromObject
static isExistingObject($element, $id, $ref='', $ref_ext='')
Check an object id/ref exists If you don't need/want to instantiate object and just need to know if o...
updateRangOfLine($rowid, $rang)
Update position of line (rang)
deleteObjectLinked($sourceid=null, $sourcetype='', $targetid=null, $targettype='', $rowid='', $f_user=null, $notrigger=0)
Delete all links between an object $this.
fetch_project()
Load the project with id $this->fk_project into this->project.
update_price($exclspec=0, $roundingadjust='none', $nodatabaseupdate=0, $seller=null)
Update total_ht, total_ttc, total_vat, total_localtax1, total_localtax2 for an object (sum of lines).
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.
Parent class for class inheritance lines of business objects This class is useless for the moment so ...
Class to manage absolute discounts.
Class to manage Dolibarr database access.
static getIdFromCode($dbs, $code)
Get id of currency from code.
static getIdAndTxFromCode($dbs, $code, $date_document='')
Get id and rate of currency from code.
Class to manage products or services.
const TYPE_PRODUCT
Regular product.
File of class to manage predefined price products or services by customer.
Class to manage proposals.
getTooltipContentArray($params)
getTooltipContentArray
const STATUS_DRAFT
Draft status.
fetch_lines($only_product=0, $loadalsotranslation=0, $filters='')
Load array lines.
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 clicable link of object (with eventually picto)
getKanbanView($option='', $arraydata=null)
Return clicable 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.
load_state_board()
Charge indicateurs this->nb de tableau de bord.
set_remise_percent($user, $remise, $notrigger=0)
Set an overall discount on the proposal.
classifyBilled(User $user, $notrigger=0, $note='')
Classify the proposal to status Billed.
fetch($rowid, $ref='', $ref_ext='', $forceentity=0)
Load a proposal from database.
set_availability($user, $id, $notrigger=0)
Set delivery.
update(User $user, $notrigger=0)
Update database.
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, ...)
getLinesArray($filters='')
Retrieve an array of proposal lines.
insert_discount($idremise)
Adding line of fixed discount in the proposal in DB.
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.
set_remise_absolue($user, $remise, $notrigger=0)
Set an absolute overall discount on the proposal.
generateDocument($modele, $outputlangs, $hidedetails=0, $hidedesc=0, $hideref=0, $moreparams=null)
Create a document onto disk according to template module.
deleteline($lineid, $id=0)
Delete detail line.
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.
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=0, $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...
initAsSpecimen()
Initialise an instance with random values.
closeProposal($user, $status, $note='', $notrigger=0)
Close/set the commercial proposal to status signed or refused (fill also date signature)
reopen($user, $status, $note='', $notrigger=0)
Reopen the commercial proposal.
__construct($db, $socid=0, $propalid=0)
Constructor.
load_board($user, $mode)
Load indicators for dashboard (this->nbtodo and this->nbtodolate)
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=0, $fk_unit=null, $pu_ht_devise=0, $notrigger=0, $rang=0)
Update a proposal line.
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.
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.
__construct($db)
Class line Contructor.
update($notrigger=0)
Update propal line object into DB.
fetch($rowid)
Retrieve the propal line object.
insert($notrigger=0)
Insert object line propal in database.
update_total()
Update DB line fields total_xxx Used by migration.
Class to manage third parties objects (customers, suppliers, prospects...)
Class to manage translations.
Class to manage Dolibarr users.
print $langs trans("Ref").' m m m statut
Definition index.php:152
trait CommonIncoterm
Superclass for incoterm classes.
dol_delete_dir_recursive($dir, $count=0, $nophperrors=0, $onlysub=0, &$countdeleted=0, $indexdatabase=1, $nolog=0)
Remove a directory $dir and its subdirectories (or only files and subdirectories)
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_dir_list($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:62
dol_delete_preview($object)
Delete all preview files linked to object instance.
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 '.
dol_print_error($db='', $error='', $errors=null)
Displays error message system with all the information to facilitate the diagnosis and the escalation...
img_object($titlealt, $picto, $moreatt='', $pictoisfullpath=false, $srconly=0, $notitle=0)
Show a picto called object_picto (generic function)
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_print_date($time, $format='', $tzoutput='auto', $outputlangs='', $encodetooutput=false)
Output date in a string format according to outputlangs (or langs if not defined).
dol_now($mode='auto')
Return date for now.
getDolGlobalInt($key, $default=0)
Return dolibarr global constant int value.
img_picto($titlealt, $picto, $moreatt='', $pictoisfullpath=false, $srconly=0, $notitle=0, $alt='', $morecss='', $marginleftonlyshort=2)
Show picto whatever it's its name (generic function)
getLocalTaxesFromRate($vatrate, $local, $buyer, $seller, $firstparamisid=0)
Get type and rate of localtaxes for a particular vat rate/country of a thirdparty.
if(!function_exists( 'dol_getprefix')) dol_include_once($relpath, $classname='')
Make an include_once using default root and alternate root if it fails.
get_default_npr(Societe $thirdparty_seller, Societe $thirdparty_buyer, $idprod=0, $idprodfournprice=0)
Function that returns whether VAT must be recoverable collected VAT (e.g.: VAT NPR in France)
dol_concatdesc($text1, $text2, $forxml=false, $invert=false)
Concat 2 descriptions with a new line between them (second operand after first one with appropriate n...
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.
get_localtax($vatrate, $local, $thirdparty_buyer="", $thirdparty_seller="", $vatnpr=0)
Return localtax rate for a particular vat, when selling a product with vat $vatrate,...
dol_sanitizeFileName($str, $newstr='_', $unaccent=1)
Clean a string to use it as a file name.
getDolGlobalString($key, $default='')
Return 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...
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...
publicphonebutton2 phonegreen basiclayout basiclayout TotalHT VATCode TotalVAT TotalLT1 TotalLT2 TotalTTC TotalHT clearboth nowraponall right right takeposterminal SELECT e rowid
Definition invoice.php:1655
getMarginInfos($pvht, $remise_percent, $tva_tx, $localtax1_tx, $localtax2_tx, $fk_pa, $paht)
Return an array with margins information of a line.
calcul_price_total($qty, $pu, $remise_percent_ligne, $txtva, $uselocaltax1_rate, $uselocaltax2_rate, $remise_percent_global, $price_base_type, $info_bits, $type, $seller='', $localtaxes_array='', $progress=100, $multicurrency_tx=1, $pu_devise=0, $multicurrency_code='')
Calculate totals (net, vat, ...) of a line.
Definition price.lib.php:86