Passed
Pull Request — master (#3)
by
unknown
25:36
created

Propal::addline()   F

Complexity

Conditions 27
Paths > 20000

Size

Total Lines 189
Code Lines 132

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 27
eloc 132
nc 164097
nop 26
dl 0
loc 189
rs 0
c 0
b 0
f 0

How to fix   Long Method    Complexity    Many Parameters   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php
2
/* Copyright (C) 2002-2004 Rodolphe Quiedeville		<[email protected]>
3
 * Copyright (C) 2004      Eric Seigne				<[email protected]>
4
 * Copyright (C) 2004-2011 Laurent Destailleur		<[email protected]>
5
 * Copyright (C) 2005      Marc Barilley			<[email protected]>
6
 * Copyright (C) 2005-2013 Regis Houssin			<[email protected]>
7
 * Copyright (C) 2006      Andre Cianfarani			<[email protected]>
8
 * Copyright (C) 2008      Raphael Bertrand			<[email protected]>
9
 * Copyright (C) 2010-2014 Juanjo Menent			<[email protected]>
10
 * Copyright (C) 2010-2017 Philippe Grand			<[email protected]>
11
 * Copyright (C) 2012-2014 Christophe Battarel  	<[email protected]>
12
 * Copyright (C) 2012      Cedric Salvador          <[email protected]>
13
 * Copyright (C) 2013      Florian Henry		  	<[email protected]>
14
 * Copyright (C) 2014-2015 Marcos García            <[email protected]>
15
 * Copyright (C) 2018      Nicolas ZABOURI			<[email protected]>
16
 * Copyright (C) 2018      Frédéric France          <[email protected]>
17
 * Copyright (C) 2018      Ferran Marcet         	<[email protected]>
18
 * Copyright (C) 2019       Alxarafe                <[email protected]>
19
 *
20
 * This program is free software; you can redistribute it and/or modify
21
 * it under the terms of the GNU General Public License as published by
22
 * the Free Software Foundation; either version 3 of the License, or
23
 * (at your option) any later version.
24
 *
25
 * This program is distributed in the hope that it will be useful,
26
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
27
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
28
 * GNU General Public License for more details.
29
 *
30
 * You should have received a copy of the GNU General Public License
31
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
32
 */
33
defined('BASE_PATH') or die('Single entry point through the index.php of the main folder');
34
35
/**
36
 * 	\file       htdocs/comm/propal/class/propal.class.php
37
 * 	\brief      File of class to manage proposals
38
 */
39
require_once DOL_DOCUMENT_ROOT . '/core/class/commonobject.class.php';
40
require_once DOL_DOCUMENT_ROOT . "/core/class/commonobjectline.class.php";
41
require_once DOL_DOCUMENT_ROOT . '/product/class/product.class.php';
42
require_once DOL_DOCUMENT_ROOT . '/contact/class/contact.class.php';
43
require_once DOL_DOCUMENT_ROOT . '/margin/lib/margins.lib.php';
44
require_once DOL_DOCUMENT_ROOT . '/multicurrency/class/multicurrency.class.php';
45
46
/**
47
 * 	Class to manage proposals
48
 */
49
class Propal extends CommonObject
50
{
51
52
    /**
53
     * @var string ID to identify managed object
54
     */
55
    public $element = 'propal';
56
57
    /**
58
     * @var string Name of table without prefix where object is stored
59
     */
60
    public $table_element = 'propal';
61
62
    /**
63
     * @var int    Name of subtable line
64
     */
65
    public $table_element_line = 'propaldet';
66
67
    /**
68
     * @var int Field with ID of parent key if this field has a parent
69
     */
70
    public $fk_element = 'fk_propal';
71
72
    /**
73
     * @var string String with name of icon for myobject. Must be the part after the 'object_' into object_myobject.png
74
     */
75
    public $picto = 'propal';
76
77
    /**
78
     * 0=No test on entity, 1=Test with field entity, 2=Test with link by societe
79
     * @var int
80
     */
81
    public $ismultientitymanaged = 1;
82
83
    /**
84
     * 0=Default, 1=View may be restricted to sales representative only if no permission to see all or to company of external user if external user
85
     * @var integer
86
     */
87
    public $restrictiononfksoc = 1;
88
89
    /**
90
     * {@inheritdoc}
91
     */
92
    protected $table_ref_field = 'ref';
93
94
    /**
95
     * ID of the client
96
     * @var int
97
     */
98
    public $socid;
99
    public $contactid;
100
    public $author;
101
    public $ref_client;
102
103
    /**
104
     * Status of the quote
105
     * @var int
106
     * @see Propal::STATUS_DRAFT, Propal::STATUS_VALIDATED, Propal::STATUS_SIGNED, Propal::STATUS_NOTSIGNED, Propal::STATUS_BILLED
107
     */
108
    public $statut;
109
110
    /**
111
     * @deprecated
112
     * @see date_creation
113
     */
114
    public $datec;
115
116
    /**
117
     * Creation date
118
     * @var int
119
     */
120
    public $date_creation;
121
122
    /**
123
     * @deprecated
124
     * @see date_validation
125
     */
126
    public $datev;
127
128
    /**
129
     * Validation date
130
     * @var int
131
     */
132
    public $date_validation;
133
134
    /**
135
     * Date of the quote
136
     * @var
137
     */
138
    public $date;
139
140
    /**
141
     * @deprecated
142
     * @see date
143
     */
144
    public $datep;
145
    public $date_livraison;
146
    public $fin_validite;
147
    public $user_author_id;
148
    public $user_valid_id;
149
    public $user_close_id;
150
151
    /**
152
     * @deprecated
153
     * @see total_ht
154
     */
155
    public $price;
156
157
    /**
158
     * @deprecated
159
     * @see total_tva
160
     */
161
    public $tva;
162
163
    /**
164
     * @deprecated
165
     * @see total_ttc
166
     */
167
    public $total;
168
    public $cond_reglement_code;
169
    public $mode_reglement_code;
170
    public $remise = 0;
171
    public $remise_percent = 0;
172
    public $remise_absolue = 0;
173
174
    /**
175
     * @var int ID
176
     */
177
    public $fk_address;
178
    public $address_type;
179
    public $address;
180
    public $availability_id;
181
    public $availability_code;
182
    public $demand_reason_id;
183
    public $demand_reason_code;
184
    public $products = array();
185
    public $extraparams = array();
186
187
    /**
188
     * @var PropaleLigne[]
189
     */
190
    public $lines = array();
191
    public $line;
192
    public $labelstatut = array();
193
    public $labelstatut_short = array();
194
    public $specimen;
195
    // Multicurrency
196
    /**
197
     * @var int ID
198
     */
199
    public $fk_multicurrency;
200
    public $multicurrency_code;
201
    public $multicurrency_tx;
202
    public $multicurrency_total_ht;
203
    public $multicurrency_total_tva;
204
    public $multicurrency_total_ttc;
205
    public $oldcopy;
206
207
    /**
208
     * Draft status
209
     */
210
    const STATUS_DRAFT = 0;
211
212
    /**
213
     * Validated status
214
     */
215
    const STATUS_VALIDATED = 1;
216
217
    /**
218
     * Signed quote
219
     */
220
    const STATUS_SIGNED = 2;
221
222
    /**
223
     * Not signed quote
224
     */
225
    const STATUS_NOTSIGNED = 3;
226
227
    /**
228
     * Billed or processed quote
229
     */
230
    const STATUS_BILLED = 4;   // Todo rename into STATUS_CLOSE
231
232
233
    /**
234
     * 	Constructor
235
     *
236
     * 	@param      DoliDB	$db         Database handler
237
     * 	@param      int		$socid		Id third party
238
     * 	@param      int		$propalid   Id proposal
239
     */
240
    function __construct($db, $socid = "", $propalid = 0)
241
    {
242
        global $conf, $langs;
243
244
        $this->db = $db;
245
246
        $this->socid = $socid;
247
        $this->id = $propalid;
248
249
        $this->products = array();
250
251
        $this->duree_validite = $conf->global->PROPALE_VALIDITY_DURATION;
252
    }
253
254
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
255
    /**
256
     *  Add line into array products
257
     *  $this->thirdparty should be loaded
258
     *
259
     * 	@param  int		$idproduct       	Product Id to add
260
     * 	@param  int		$qty             	Quantity
261
     * 	@param  int		$remise_percent  	Discount effected on Product
262
     *  @return	int							<0 if KO, >0 if OK
263
     *
264
     * 	TODO	Replace calls to this function by generation objet Ligne
265
     * 			inserted into table $this->products
266
     */
267
    function add_product($idproduct, $qty, $remise_percent = 0)
268
    {
269
        // phpcs:enable
270
        global $conf, $mysoc;
271
272
        if (!$qty)
273
            $qty = 1;
274
275
        dol_syslog(get_class($this) . "::add_product $idproduct, $qty, $remise_percent");
276
        if ($idproduct > 0) {
277
            $prod = new Product($this->db);
278
            $prod->fetch($idproduct);
279
280
            $productdesc = $prod->description;
281
282
            $tva_tx = get_default_tva($mysoc, $this->thirdparty, $prod->id);
283
            $tva_npr = get_default_npr($mysoc, $this->thirdparty, $prod->id);
284
            if (empty($tva_tx))
285
                $tva_npr = 0;
286
            $vat_src_code = '';     // May be defined into tva_tx
287
288
            $localtax1_tx = get_localtax($tva_tx, 1, $mysoc, $this->thirdparty, $tva_npr);
289
            $localtax2_tx = get_localtax($tva_tx, 2, $mysoc, $this->thirdparty, $tva_npr);
290
291
            // multiprices
292
            if ($conf->global->PRODUIT_MULTIPRICES && $this->thirdparty->price_level) {
293
                $price = $prod->multiprices[$this->thirdparty->price_level];
294
            } else {
295
                $price = $prod->price;
296
            }
297
298
            $line = new PropaleLigne($this->db);
299
300
            $line->fk_product = $idproduct;
301
            $line->desc = $productdesc;
302
            $line->qty = $qty;
303
            $line->subprice = $price;
304
            $line->remise_percent = $remise_percent;
305
            $line->vat_src_code = $vat_src_code;
306
            $line->tva_tx = $tva_tx;
307
            $line->fk_unit = $prod->fk_unit;
308
            if ($tva_npr)
309
                $line->info_bits = 1;
310
311
            $this->lines[] = $line;
312
        }
313
    }
314
315
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
316
    /**
317
     * 	Adding line of fixed discount in the proposal in DB
318
     *
319
     * 	@param     int		$idremise			Id of fixed discount
320
     *  @return    int          				>0 if OK, <0 if KO
321
     */
322
    function insert_discount($idremise)
323
    {
324
        // phpcs:enable
325
        global $langs;
326
327
        include_once DOL_DOCUMENT_ROOT . '/core/lib/price.lib.php';
328
        include_once DOL_DOCUMENT_ROOT . '/core/class/discount.class.php';
329
330
        $this->db->begin();
331
332
        $remise = new DiscountAbsolute($this->db);
333
        $result = $remise->fetch($idremise);
334
335
        if ($result > 0) {
336
            if ($remise->fk_facture) { // Protection against multiple submission
337
                $this->error = $langs->trans("ErrorDiscountAlreadyUsed");
338
                $this->db->rollback();
339
                return -5;
340
            }
341
342
            $line = new PropaleLigne($this->db);
343
344
            $this->line->context = $this->context;
345
346
            $line->fk_propal = $this->id;
347
            $line->fk_remise_except = $remise->id;
348
            $line->desc = $remise->description;    // Description ligne
349
            $line->vat_src_code = $remise->vat_src_code;
350
            $line->tva_tx = $remise->tva_tx;
351
            $line->subprice = -$remise->amount_ht;
352
            $line->fk_product = 0;     // Id produit predefined
353
            $line->qty = 1;
354
            $line->remise = 0;
355
            $line->remise_percent = 0;
356
            $line->rang = -1;
357
            $line->info_bits = 2;
358
359
            // TODO deprecated
360
            $line->price = -$remise->amount_ht;
361
362
            $line->total_ht = -$remise->amount_ht;
363
            $line->total_tva = -$remise->amount_tva;
364
            $line->total_ttc = -$remise->amount_ttc;
365
366
            $result = $line->insert();
367
            if ($result > 0) {
368
                $result = $this->update_price(1);
369
                if ($result > 0) {
370
                    $this->db->commit();
371
                    return 1;
372
                } else {
373
                    $this->db->rollback();
374
                    return -1;
375
                }
376
            } else {
377
                $this->error = $line->error;
378
                $this->db->rollback();
379
                return -2;
380
            }
381
        } else {
382
            $this->db->rollback();
383
            return -2;
384
        }
385
    }
386
387
    /**
388
     *    	Add a proposal line into database (linked to product/service or not)
389
     *      The parameters are already supposed to be appropriate and with final values to the call
390
     *      of this method. Also, for the VAT rate, it must have already been defined
391
     *      by whose calling the method get_default_tva (societe_vendeuse, societe_acheteuse, '' product)
392
     *      and desc must already have the right value (it's up to the caller to manage multilanguage)
393
     *
394
     * 		@param    	string		$desc				Description of line
395
     * 		@param    	float		$pu_ht				Unit price
396
     * 		@param    	float		$qty             	Quantity
397
     * 		@param    	float		$txtva           	Force Vat rate, -1 for auto (Can contain the vat_src_code too with syntax '9.9 (CODE)')
398
     * 		@param		float		$txlocaltax1		Local tax 1 rate (deprecated, use instead txtva with code inside)
399
     *  	@param		float		$txlocaltax2		Local tax 2 rate (deprecated, use instead txtva with code inside)
400
     * 		@param    	int			$fk_product      	Id du produit/service predefini
401
     * 		@param    	float		$remise_percent  	Pourcentage de remise de la ligne
402
     * 		@param    	string		$price_base_type	HT or TTC
403
     * 		@param    	float		$pu_ttc             Prix unitaire TTC
404
     * 		@param    	int			$info_bits			Bits de type de lignes
405
     *      @param      int			$type               Type of line (0=product, 1=service). Not used if fk_product is defined, the type of product is used.
406
     *      @param      int			$rang               Position of line
407
     *      @param		int			$special_code		Special code (also used by externals modules!)
408
     *      @param		int			$fk_parent_line		Id of parent line
409
     *      @param		int			$fk_fournprice		Id supplier price
410
     *      @param		int			$pa_ht				Buying price without tax
411
     *      @param		string		$label				???
412
     * 		@param      int			$date_start       	Start date of the line
413
     * 		@param      int			$date_end         	End date of the line
414
     *      @param		array		$array_options		extrafields array
415
     * 		@param 		string		$fk_unit 			Code of the unit to use. Null to use the default one
416
     *      @param		string		$origin				'order', ...
417
     *      @param		int			$origin_id			Id of origin object
418
     * 		@param		double		$pu_ht_devise		Unit price in currency
419
     * 		@param		int    		$fk_remise_except	Id discount if line is from a discount
420
     *    	@return    	int         	    			>0 if OK, <0 if KO
421
     *    	@see       	add_product
422
     */
423
    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)
424
    {
425
        global $mysoc, $conf, $langs;
426
427
        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);
428
        if ($this->statut == self::STATUS_DRAFT) {
429
            include_once DOL_DOCUMENT_ROOT . '/core/lib/price.lib.php';
430
431
            // Clean parameters
432
            if (empty($remise_percent))
433
                $remise_percent = 0;
434
            if (empty($qty))
435
                $qty = 0;
436
            if (empty($info_bits))
437
                $info_bits = 0;
438
            if (empty($rang))
439
                $rang = 0;
440
            if (empty($fk_parent_line) || $fk_parent_line < 0)
441
                $fk_parent_line = 0;
442
443
            $remise_percent = price2num($remise_percent);
444
            $qty = price2num($qty);
445
            $pu_ht = price2num($pu_ht);
446
            $pu_ht_devise = price2num($pu_ht_devise);
447
            $pu_ttc = price2num($pu_ttc);
448
            if (!preg_match('/\((.*)\)/', $txtva)) {
449
                $txtva = price2num($txtva);               // $txtva can have format '5,1' or '5.1' or '5.1(XXX)', we must clean only if '5,1'
450
            }
451
            $txlocaltax1 = price2num($txlocaltax1);
452
            $txlocaltax2 = price2num($txlocaltax2);
453
            $pa_ht = price2num($pa_ht);
454
            if ($price_base_type == 'HT') {
455
                $pu = $pu_ht;
456
            } else {
457
                $pu = $pu_ttc;
458
            }
459
460
            // Check parameters
461
            if ($type < 0)
462
                return -1;
463
464
            $this->db->begin();
465
466
            $product_type = $type;
467
            if (!empty($fk_product)) {
468
                $product = new Product($this->db);
469
                $result = $product->fetch($fk_product);
470
                $product_type = $product->type;
471
472
                if (!empty($conf->global->STOCK_MUST_BE_ENOUGH_FOR_PROPOSAL) && $product_type == 0 && $product->stock_reel < $qty) {
473
                    $langs->load("errors");
474
                    $this->error = $langs->trans('ErrorStockIsNotEnoughToAddProductOnProposal', $product->ref);
475
                    $this->db->rollback();
476
                    return -3;
477
                }
478
            }
479
480
            // Calcul du total TTC et de la TVA pour la ligne a partir de
481
            // qty, pu, remise_percent et txtva
482
            // TRES IMPORTANT: C'est au moment de l'insertion ligne qu'on doit stocker
483
            // la part ht, tva et ttc, et ce au niveau de la ligne qui a son propre taux tva.
484
485
            $localtaxes_type = getLocalTaxesFromRate($txtva, 0, $this->thirdparty, $mysoc);
486
487
            // Clean vat code
488
            $vat_src_code = '';
489
            if (preg_match('/\((.*)\)/', $txtva, $reg)) {
490
                $vat_src_code = $reg[1];
491
                $txtva = preg_replace('/\s*\(.*\)/', '', $txtva);    // Remove code into vatrate.
492
            }
493
494
            $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);
495
496
            $total_ht = $tabprice[0];
497
            $total_tva = $tabprice[1];
498
            $total_ttc = $tabprice[2];
499
            $total_localtax1 = $tabprice[9];
500
            $total_localtax2 = $tabprice[10];
501
            $pu_ht = $tabprice[3];
502
            $pu_tva = $tabprice[4];
503
            $pu_ttc = $tabprice[5];
504
505
            // MultiCurrency
506
            $multicurrency_total_ht = $tabprice[16];
507
            $multicurrency_total_tva = $tabprice[17];
508
            $multicurrency_total_ttc = $tabprice[18];
509
            $pu_ht_devise = $tabprice[19];
510
511
            // Rang to use
512
            $rangtouse = $rang;
513
            if ($rangtouse == -1) {
514
                $rangmax = $this->line_max($fk_parent_line);
515
                $rangtouse = $rangmax + 1;
516
            }
517
518
            // TODO A virer
519
            // Anciens indicateurs: $price, $remise (a ne plus utiliser)
520
            $price = $pu;
521
            $remise = 0;
522
            if ($remise_percent > 0) {
523
                $remise = round(($pu * $remise_percent / 100), 2);
524
                $price = $pu - $remise;
525
            }
526
527
            // Insert line
528
            $this->line = new PropaleLigne($this->db);
529
530
            $this->line->context = $this->context;
531
532
            $this->line->fk_propal = $this->id;
533
            $this->line->label = $label;
534
            $this->line->desc = $desc;
535
            $this->line->qty = $qty;
536
537
            $this->line->vat_src_code = $vat_src_code;
538
            $this->line->tva_tx = $txtva;
539
            $this->line->localtax1_tx = ($total_localtax1 ? $localtaxes_type[1] : 0);
540
            $this->line->localtax2_tx = ($total_localtax2 ? $localtaxes_type[3] : 0);
541
            $this->line->localtax1_type = $localtaxes_type[0];
542
            $this->line->localtax2_type = $localtaxes_type[2];
543
            $this->line->fk_product = $fk_product;
544
            $this->line->product_type = $type;
545
            $this->line->fk_remise_except = $fk_remise_except;
546
            $this->line->remise_percent = $remise_percent;
547
            $this->line->subprice = $pu_ht;
548
            $this->line->rang = $rangtouse;
549
            $this->line->info_bits = $info_bits;
550
            $this->line->total_ht = $total_ht;
551
            $this->line->total_tva = $total_tva;
552
            $this->line->total_localtax1 = $total_localtax1;
553
            $this->line->total_localtax2 = $total_localtax2;
554
            $this->line->total_ttc = $total_ttc;
555
            $this->line->special_code = $special_code;
556
            $this->line->fk_parent_line = $fk_parent_line;
557
            $this->line->fk_unit = $fk_unit;
558
559
            $this->line->date_start = $date_start;
560
            $this->line->date_end = $date_end;
561
562
            $this->line->fk_fournprice = $fk_fournprice;
563
            $this->line->pa_ht = $pa_ht;
564
565
            $this->line->origin_id = $origin_id;
566
            $this->line->origin = $origin;
567
568
            // Multicurrency
569
            $this->line->fk_multicurrency = $this->fk_multicurrency;
570
            $this->line->multicurrency_code = $this->multicurrency_code;
571
            $this->line->multicurrency_subprice = $pu_ht_devise;
572
            $this->line->multicurrency_total_ht = $multicurrency_total_ht;
573
            $this->line->multicurrency_total_tva = $multicurrency_total_tva;
574
            $this->line->multicurrency_total_ttc = $multicurrency_total_ttc;
575
576
            // Mise en option de la ligne
577
            if (empty($qty) && empty($special_code))
578
                $this->line->special_code = 3;
579
580
            // TODO deprecated
581
            $this->line->price = $price;
582
            $this->line->remise = $remise;
583
584
            if (is_array($array_options) && count($array_options) > 0) {
585
                $this->line->array_options = $array_options;
586
            }
587
588
            $result = $this->line->insert();
589
            if ($result > 0) {
590
                // Reorder if child line
591
                if (!empty($fk_parent_line))
592
                    $this->line_order(true, 'DESC');
593
594
                // Mise a jour informations denormalisees au niveau de la propale meme
595
                $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.
596
                if ($result > 0) {
597
                    $this->db->commit();
598
                    return $this->line->rowid;
599
                } else {
600
                    $this->error = $this->db->error();
601
                    $this->db->rollback();
602
                    return -1;
603
                }
604
            } else {
605
                $this->error = $this->line->error;
606
                $this->db->rollback();
607
                return -2;
608
            }
609
        } else {
610
            dol_syslog(get_class($this) . "::addline status of order must be Draft to allow use of ->addline()", LOG_ERR);
611
            return -3;
612
        }
613
    }
614
615
    /**
616
     *  Update a proposal line
617
     *
618
     *  @param      int			$rowid           	Id de la ligne
619
     *  @param      float		$pu		     	  	Prix unitaire (HT ou TTC selon price_base_type)
620
     *  @param      float		$qty            	Quantity
621
     *  @param      float		$remise_percent  	Remise effectuee sur le produit
622
     *  @param      float		$txtva	          	Taux de TVA
623
     * 	@param	  	float		$txlocaltax1		Local tax 1 rate
624
     *  @param	  	float		$txlocaltax2		Local tax 2 rate
625
     *  @param      string		$desc            	Description
626
     * 	@param	  	string		$price_base_type	HT ou TTC
627
     * 	@param      int			$info_bits        	Miscellaneous informations
628
     * 	@param		int			$special_code		Special code (also used by externals modules!)
629
     * 	@param		int			$fk_parent_line		Id of parent line (0 in most cases, used by modules adding sublevels into lines).
630
     * 	@param		int			$skip_update_total	Keep fields total_xxx to 0 (used for special lines by some modules)
631
     *  @param		int			$fk_fournprice		Id of origin supplier price
632
     *  @param		int			$pa_ht				Price (without tax) of product when it was bought
633
     *  @param		string		$label				???
634
     *  @param		int			$type				0/1=Product/service
635
     * 	@param      int			$date_start       	Start date of the line
636
     * 	@param      int			$date_end         	End date of the line
637
     *  @param		array		$array_options		extrafields array
638
     * 	@param 		string		$fk_unit 			Code of the unit to use. Null to use the default one
639
     * 	@param		double		$pu_ht_devise		Unit price in currency
640
     * 	@param		int			$notrigger			disable line update trigger
641
     *  @return     int     		        		0 if OK, <0 if KO
642
     */
643
    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)
644
    {
645
        global $mysoc;
646
647
        dol_syslog(get_class($this) . "::updateLine rowid=$rowid, pu=$pu, qty=$qty, remise_percent=$remise_percent,
648
        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");
649
        include_once DOL_DOCUMENT_ROOT . '/core/lib/price.lib.php';
650
651
        // Clean parameters
652
        $remise_percent = price2num($remise_percent);
653
        $qty = price2num($qty);
654
        $pu = price2num($pu);
655
        $pu_ht_devise = price2num($pu_ht_devise);
656
        $txtva = price2num($txtva);
657
        $txlocaltax1 = price2num($txlocaltax1);
658
        $txlocaltax2 = price2num($txlocaltax2);
659
        $pa_ht = price2num($pa_ht);
660
        if (empty($qty) && empty($special_code))
661
            $special_code = 3;    // Set option tag
662
        if (!empty($qty) && $special_code == 3)
663
            $special_code = 0;    // Remove option tag
664
        if (empty($type))
665
            $type = 0;
666
667
        if ($this->statut == self::STATUS_DRAFT) {
668
            $this->db->begin();
669
670
            // Calcul du total TTC et de la TVA pour la ligne a partir de
671
            // qty, pu, remise_percent et txtva
672
            // TRES IMPORTANT: C'est au moment de l'insertion ligne qu'on doit stocker
673
            // la part ht, tva et ttc, et ce au niveau de la ligne qui a son propre taux tva.
674
675
            $localtaxes_type = getLocalTaxesFromRate($txtva, 0, $this->thirdparty, $mysoc);
676
677
            // Clean vat code
678
            $vat_src_code = '';
679
            if (preg_match('/\((.*)\)/', $txtva, $reg)) {
680
                $vat_src_code = $reg[1];
681
                $txtva = preg_replace('/\s*\(.*\)/', '', $txtva);    // Remove code into vatrate.
682
            }
683
684
            $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);
685
            $total_ht = $tabprice[0];
686
            $total_tva = $tabprice[1];
687
            $total_ttc = $tabprice[2];
688
            $total_localtax1 = $tabprice[9];
689
            $total_localtax2 = $tabprice[10];
690
            $pu_ht = $tabprice[3];
691
            $pu_tva = $tabprice[4];
692
            $pu_ttc = $tabprice[5];
693
694
            // MultiCurrency
695
            $multicurrency_total_ht = $tabprice[16];
696
            $multicurrency_total_tva = $tabprice[17];
697
            $multicurrency_total_ttc = $tabprice[18];
698
            $pu_ht_devise = $tabprice[19];
699
700
            // Anciens indicateurs: $price, $remise (a ne plus utiliser)
701
            $price = $pu;
702
            $remise = 0;
703
            if ($remise_percent > 0) {
704
                $remise = round(($pu * $remise_percent / 100), 2);
705
                $price = $pu - $remise;
706
            }
707
708
            //Fetch current line from the database and then clone the object and set it in $oldline property
709
            $line = new PropaleLigne($this->db);
710
            $line->fetch($rowid);
711
            $line->fetch_optionals(); // Fetch extrafields for oldcopy
712
713
            $staticline = clone $line;
714
715
            $line->oldline = $staticline;
716
            $this->line = $line;
717
            $this->line->context = $this->context;
718
719
            // Reorder if fk_parent_line change
720
            if (!empty($fk_parent_line) && !empty($staticline->fk_parent_line) && $fk_parent_line != $staticline->fk_parent_line) {
721
                $rangmax = $this->line_max($fk_parent_line);
722
                $this->line->rang = $rangmax + 1;
723
            }
724
725
            $this->line->rowid = $rowid;
726
            $this->line->label = $label;
727
            $this->line->desc = $desc;
728
            $this->line->qty = $qty;
729
            $this->line->product_type = $type;
730
            $this->line->vat_src_code = $vat_src_code;
731
            $this->line->tva_tx = $txtva;
732
            $this->line->localtax1_tx = $txlocaltax1;
733
            $this->line->localtax2_tx = $txlocaltax2;
734
            $this->line->localtax1_type = $localtaxes_type[0];
735
            $this->line->localtax2_type = $localtaxes_type[2];
736
            $this->line->remise_percent = $remise_percent;
737
            $this->line->subprice = $pu_ht;
738
            $this->line->info_bits = $info_bits;
739
740
            $this->line->total_ht = $total_ht;
741
            $this->line->total_tva = $total_tva;
742
            $this->line->total_localtax1 = $total_localtax1;
743
            $this->line->total_localtax2 = $total_localtax2;
744
            $this->line->total_ttc = $total_ttc;
745
            $this->line->special_code = $special_code;
746
            $this->line->fk_parent_line = $fk_parent_line;
747
            $this->line->skip_update_total = $skip_update_total;
748
            $this->line->fk_unit = $fk_unit;
749
750
            $this->line->fk_fournprice = $fk_fournprice;
751
            $this->line->pa_ht = $pa_ht;
752
753
            $this->line->date_start = $date_start;
754
            $this->line->date_end = $date_end;
755
756
            // TODO deprecated
757
            $this->line->price = $price;
758
            $this->line->remise = $remise;
759
760
            if (is_array($array_options) && count($array_options) > 0) {
761
                $this->line->array_options = $array_options;
762
            }
763
764
            // Multicurrency
765
            $this->line->multicurrency_subprice = $pu_ht_devise;
766
            $this->line->multicurrency_total_ht = $multicurrency_total_ht;
767
            $this->line->multicurrency_total_tva = $multicurrency_total_tva;
768
            $this->line->multicurrency_total_ttc = $multicurrency_total_ttc;
769
770
            $result = $this->line->update($notrigger);
771
            if ($result > 0) {
772
                // Reorder if child line
773
                if (!empty($fk_parent_line))
774
                    $this->line_order(true, 'DESC');
775
776
                $this->update_price(1);
777
778
                $this->fk_propal = $this->id;
779
                $this->rowid = $rowid;
780
781
                $this->db->commit();
782
                return $result;
783
            }
784
            else {
785
                $this->error = $this->line->error;
786
787
                $this->db->rollback();
788
                return -1;
789
            }
790
        } else {
791
            dol_syslog(get_class($this) . "::updateline Erreur -2 Propal en mode incompatible pour cette action");
792
            return -2;
793
        }
794
    }
795
796
    /**
797
     *  Delete detail line
798
     *
799
     *  @param		int		$lineid			Id of line to delete
800
     *  @return     int         			>0 if OK, <0 if KO
801
     */
802
    function deleteline($lineid)
803
    {
804
        global $user;
805
806
        if ($this->statut == self::STATUS_DRAFT) {
807
            $this->db->begin();
808
809
            $line = new PropaleLigne($this->db);
810
811
            // For triggers
812
            $line->fetch($lineid);
813
814
            if ($line->delete($user) > 0) {
815
                $this->update_price(1);
816
817
                $this->db->commit();
818
                return 1;
819
            } else {
820
                $this->db->rollback();
821
                return -1;
822
            }
823
        } else {
824
            $this->error = 'ErrorDeleteLineNotAllowedByObjectStatus';
825
            return -2;
826
        }
827
    }
828
829
    /**
830
     *  Create commercial proposal into database
831
     * 	this->ref can be set or empty. If empty, we will use "(PROVid)"
832
     *
833
     * 	@param		User	$user		User that create
834
     * 	@param		int		$notrigger	1=Does not execute triggers, 0= execute triggers
835
     *  @return     int     			<0 if KO, >=0 if OK
836
     */
837
    function create($user, $notrigger = 0)
838
    {
839
        global $conf, $hookmanager;
840
        $error = 0;
841
842
        $now = dol_now();
843
844
        // Clean parameters
845
        if (empty($this->entity))
846
            $this->entity = $conf->entity;
847
        if (empty($this->date))
848
            $this->date = $this->datep;
849
        $this->fin_validite = $this->date + ($this->duree_validite * 24 * 3600);
850
        if (empty($this->availability_id))
851
            $this->availability_id = 0;
852
        if (empty($this->demand_reason_id))
853
            $this->demand_reason_id = 0;
854
855
        // Multicurrency (test on $this->multicurrency_tx because we should take the default rate only if not using origin rate)
856
        if (!empty($this->multicurrency_code) && empty($this->multicurrency_tx))
857
            list($this->fk_multicurrency, $this->multicurrency_tx) = MultiCurrency::getIdAndTxFromCode($this->db, $this->multicurrency_code, $this->date);
858
        else
859
            $this->fk_multicurrency = MultiCurrency::getIdFromCode($this->db, $this->multicurrency_code);
860
        if (empty($this->fk_multicurrency)) {
861
            $this->multicurrency_code = $conf->currency;
862
            $this->fk_multicurrency = 0;
863
            $this->multicurrency_tx = 1;
864
        }
865
866
        dol_syslog(get_class($this) . "::create");
867
868
        // Check parameters
869
        $result = $this->fetch_thirdparty();
870
        if ($result < 0) {
871
            $this->error = "Failed to fetch company";
872
            dol_syslog(get_class($this) . "::create " . $this->error, LOG_ERR);
873
            return -3;
874
        }
875
876
        // Check parameters
877
        if (!empty($this->ref)) { // We check that ref is not already used
878
            $result = self::isExistingObject($this->element, 0, $this->ref); // Check ref is not yet used
879
            if ($result > 0) {
880
                $this->error = 'ErrorRefAlreadyExists';
881
                dol_syslog(get_class($this) . "::create " . $this->error, LOG_WARNING);
882
                $this->db->rollback();
883
                return -1;
884
            }
885
        }
886
887
        if (empty($this->date)) {
888
            $this->error = "Date of proposal is required";
889
            dol_syslog(get_class($this) . "::create " . $this->error, LOG_ERR);
890
            return -4;
891
        }
892
893
894
        $this->db->begin();
895
896
        // Insert into database
897
        $sql = "INSERT INTO " . MAIN_DB_PREFIX . "propal (";
898
        $sql .= "fk_soc";
899
        $sql .= ", price";
900
        $sql .= ", remise";
901
        $sql .= ", remise_percent";
902
        $sql .= ", remise_absolue";
903
        $sql .= ", tva";
904
        $sql .= ", total";
905
        $sql .= ", datep";
906
        $sql .= ", datec";
907
        $sql .= ", ref";
908
        $sql .= ", fk_user_author";
909
        $sql .= ", note_private";
910
        $sql .= ", note_public";
911
        $sql .= ", model_pdf";
912
        $sql .= ", fin_validite";
913
        $sql .= ", fk_cond_reglement";
914
        $sql .= ", fk_mode_reglement";
915
        $sql .= ", fk_account";
916
        $sql .= ", ref_client";
917
        $sql .= ", date_livraison";
918
        $sql .= ", fk_shipping_method";
919
        $sql .= ", fk_availability";
920
        $sql .= ", fk_input_reason";
921
        $sql .= ", fk_projet";
922
        $sql .= ", fk_incoterms";
923
        $sql .= ", location_incoterms";
924
        $sql .= ", entity";
925
        $sql .= ", fk_multicurrency";
926
        $sql .= ", multicurrency_code";
927
        $sql .= ", multicurrency_tx";
928
        $sql .= ") ";
929
        $sql .= " VALUES (";
930
        $sql .= $this->socid;
931
        $sql .= ", 0";
932
        $sql .= ", " . $this->remise;
933
        $sql .= ", " . ($this->remise_percent ? $this->db->escape($this->remise_percent) : 'NULL');
934
        $sql .= ", " . ($this->remise_absolue ? $this->db->escape($this->remise_absolue) : 'NULL');
935
        $sql .= ", 0";
936
        $sql .= ", 0";
937
        $sql .= ", '" . $this->db->idate($this->date) . "'";
938
        $sql .= ", '" . $this->db->idate($now) . "'";
939
        $sql .= ", '(PROV)'";
940
        $sql .= ", " . ($user->id > 0 ? "'" . $user->id . "'" : "NULL");
941
        $sql .= ", '" . $this->db->escape($this->note_private) . "'";
942
        $sql .= ", '" . $this->db->escape($this->note_public) . "'";
943
        $sql .= ", '" . $this->db->escape($this->modelpdf) . "'";
944
        $sql .= ", " . ($this->fin_validite != '' ? "'" . $this->db->idate($this->fin_validite) . "'" : "NULL");
945
        $sql .= ", " . ($this->cond_reglement_id > 0 ? $this->cond_reglement_id : 'NULL');
946
        $sql .= ", " . ($this->mode_reglement_id > 0 ? $this->mode_reglement_id : 'NULL');
947
        $sql .= ", " . ($this->fk_account > 0 ? $this->fk_account : 'NULL');
948
        $sql .= ", '" . $this->db->escape($this->ref_client) . "'";
949
        $sql .= ", " . ($this->date_livraison != '' ? "'" . $this->db->idate($this->date_livraison) . "'" : "NULL");
950
        $sql .= ", " . ($this->shipping_method_id > 0 ? $this->shipping_method_id : 'NULL');
951
        $sql .= ", " . $this->availability_id;
952
        $sql .= ", " . $this->demand_reason_id;
953
        $sql .= ", " . ($this->fk_project ? $this->fk_project : "null");
954
        $sql .= ", " . (int) $this->fk_incoterms;
955
        $sql .= ", '" . $this->db->escape($this->location_incoterms) . "'";
956
        $sql .= ", " . $this->entity;
957
        $sql .= ", " . (int) $this->fk_multicurrency;
958
        $sql .= ", '" . $this->db->escape($this->multicurrency_code) . "'";
959
        $sql .= ", " . (double) $this->multicurrency_tx;
960
        $sql .= ")";
961
962
        dol_syslog(get_class($this) . "::create", LOG_DEBUG);
963
        $resql = $this->db->query($sql);
964
        if ($resql) {
965
            $this->id = $this->db->last_insert_id(MAIN_DB_PREFIX . "propal");
966
967
            if ($this->id) {
968
                $this->ref = '(PROV' . $this->id . ')';
969
                $sql = 'UPDATE ' . MAIN_DB_PREFIX . "propal SET ref='" . $this->db->escape($this->ref) . "' WHERE rowid=" . $this->id;
970
971
                dol_syslog(get_class($this) . "::create", LOG_DEBUG);
972
                $resql = $this->db->query($sql);
973
                if (!$resql)
974
                    $error++;
975
976
                if (!empty($this->linkedObjectsIds) && empty($this->linked_objects)) { // To use new linkedObjectsIds instead of old linked_objects
977
                    $this->linked_objects = $this->linkedObjectsIds; // TODO Replace linked_objects with linkedObjectsIds
978
                }
979
980
                // Add object linked
981
                if (!$error && $this->id && is_array($this->linked_objects) && !empty($this->linked_objects)) {
982
                    foreach ($this->linked_objects as $origin => $tmp_origin_id) {
983
                        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, ...))
984
                            foreach ($tmp_origin_id as $origin_id) {
985
                                $ret = $this->add_object_linked($origin, $origin_id);
986
                                if (!$ret) {
987
                                    $this->error = $this->db->lasterror();
988
                                    $error++;
989
                                }
990
                            }
991
                        } else {                                // Old behaviour, if linked_object has only one link per type, so is something like array('contract'=>id1))
992
                            $origin_id = $tmp_origin_id;
993
                            $ret = $this->add_object_linked($origin, $origin_id);
994
                            if (!$ret) {
995
                                $this->error = $this->db->lasterror();
996
                                $error++;
997
                            }
998
                        }
999
                    }
1000
                }
1001
1002
                // Add linked object (deprecated, use ->linkedObjectsIds instead)
1003
                if (!$error && $this->origin && $this->origin_id) {
1004
                    dol_syslog('Deprecated use of linked object, use ->linkedObjectsIds instead', LOG_WARNING);
1005
                    $ret = $this->add_object_linked();
1006
                    if (!$ret)
1007
                        dol_print_error($this->db);
1008
                }
1009
1010
                /*
1011
                 *  Insertion du detail des produits dans la base
1012
                 *  Insert products detail in database
1013
                 */
1014
                if (!$error) {
1015
                    $fk_parent_line = 0;
1016
                    $num = count($this->lines);
1017
1018
                    for ($i = 0; $i < $num; $i++) {
1019
                        if (!is_object($this->lines[$i])) { // If this->lines is not array of objects, coming from REST API   // Convert into object this->lines[$i].
1020
                            $line = (object) $this->lines[$i];
1021
                        } else {
1022
                            $line = $this->lines[$i];
1023
                        }
1024
                        // Reset fk_parent_line for line that are not child lines or special product
1025
                        if (($line->product_type != 9 && empty($line->fk_parent_line)) || $line->product_type == 9) {
1026
                            $fk_parent_line = 0;
1027
                        }
1028
                        // Complete vat rate with code
1029
                        $vatrate = $line->tva_tx;
1030
                        if ($line->vat_src_code && !preg_match('/\(.*\)/', $vatrate))
1031
                            $vatrate .= ' (' . $line->vat_src_code . ')';
1032
1033
                        $result = $this->addline(
1034
                            $line->desc, $line->subprice, $line->qty, $vatrate, $line->localtax1_tx, $line->localtax2_tx, $line->fk_product, $line->remise_percent, 'HT', 0, $line->info_bits, $line->product_type, $line->rang, $line->special_code, $fk_parent_line, $line->fk_fournprice, $line->pa_ht, $line->label, $line->date_start, $line->date_end, $line->array_options, $line->fk_unit, $this->element, $line->id
1035
                        );
1036
1037
                        if ($result < 0) {
1038
                            $error++;
1039
                            $this->error = $this->db->error;
1040
                            dol_print_error($this->db);
1041
                            break;
1042
                        }
1043
                        // Defined the new fk_parent_line
1044
                        if ($result > 0 && $line->product_type == 9) {
1045
                            $fk_parent_line = $result;
1046
                        }
1047
                    }
1048
                }
1049
1050
                // Set delivery address
1051
                if (!$error && $this->fk_delivery_address) {
1052
                    $sql = "UPDATE " . MAIN_DB_PREFIX . "propal";
1053
                    $sql .= " SET fk_delivery_address = " . $this->fk_delivery_address;
1054
                    $sql .= " WHERE ref = '" . $this->db->escape($this->ref) . "'";
1055
                    $sql .= " AND entity = " . $conf->entity;
1056
1057
                    $result = $this->db->query($sql);
1058
                }
1059
1060
                if (!$error) {
1061
                    // Mise a jour infos denormalisees
1062
                    $resql = $this->update_price(1);
1063
                    if ($resql) {
1064
                        $action = 'update';
1065
1066
                        // Actions on extra fields
1067
                        if (!$error) {
1068
                            if (empty($conf->global->MAIN_EXTRAFIELDS_DISABLED)) { // For avoid conflicts if trigger used
1069
                                $result = $this->insertExtraFields();
1070
                                if ($result < 0) {
1071
                                    $error++;
1072
                                }
1073
                            }
1074
                        }
1075
1076
                        if (!$error && !$notrigger) {
1077
                            // Call trigger
1078
                            $result = $this->call_trigger('PROPAL_CREATE', $user);
1079
                            if ($result < 0) {
1080
                                $error++;
1081
                            }
1082
                            // End call triggers
1083
                        }
1084
                    } else {
1085
                        $this->error = $this->db->lasterror();
1086
                        $error++;
1087
                    }
1088
                }
1089
            } else {
1090
                $this->error = $this->db->lasterror();
1091
                $error++;
1092
            }
1093
1094
            if (!$error) {
1095
                $this->db->commit();
1096
                dol_syslog(get_class($this) . "::create done id=" . $this->id);
1097
                return $this->id;
1098
            } else {
1099
                $this->db->rollback();
1100
                return -2;
1101
            }
1102
        } else {
1103
            $this->error = $this->db->lasterror();
1104
            $this->db->rollback();
1105
            return -1;
1106
        }
1107
    }
1108
1109
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
1110
    /**
1111
     * 	Insert into DB a proposal object completely defined by its data members (ex, results from copy).
1112
     *
1113
     * 	@param 		User	$user	User that create
1114
     * 	@return    	int				Id of the new object if ok, <0 if ko
1115
     * 	@see       	create
1116
     */
1117
    function create_from($user)
1118
    {
1119
        // phpcs:enable
1120
        // i love this function because $this->products is not used in create function...
1121
        $this->products = $this->lines;
1122
1123
        return $this->create($user);
1124
    }
1125
1126
    /**
1127
     * 		Load an object from its id and create a new one in database
1128
     *
1129
     * 		@param		int				$socid			Id of thirdparty
1130
     * 	 	@return		int								New id of clone
1131
     */
1132
    function createFromClone($socid = 0)
1133
    {
1134
        global $user, $conf, $hookmanager;
1135
1136
        dol_include_once('/projet/class/project.class.php');
1137
1138
        $this->context['createfromclone'] = 'createfromclone';
1139
1140
        $error = 0;
1141
        $now = dol_now();
1142
1143
        $this->db->begin();
1144
1145
        // get extrafields so they will be clone
1146
        foreach ($this->lines as $line)
1147
            $line->fetch_optionals();
1148
1149
        // Load dest object
1150
        $clonedObj = clone $this;
1151
1152
        $objsoc = new Societe($this->db);
1153
1154
        // Change socid if needed
1155
        if (!empty($socid) && $socid != $clonedObj->socid) {
1156
            if ($objsoc->fetch($socid) > 0) {
1157
                $clonedObj->socid = $objsoc->id;
1158
                $clonedObj->cond_reglement_id = (!empty($objsoc->cond_reglement_id) ? $objsoc->cond_reglement_id : 0);
1159
                $clonedObj->mode_reglement_id = (!empty($objsoc->mode_reglement_id) ? $objsoc->mode_reglement_id : 0);
1160
                $clonedObj->fk_delivery_address = '';
1161
1162
                /* if (!empty($conf->projet->enabled))
1163
                  {
1164
                  $project = new Project($db);
1165
                  if ($this->fk_project > 0 && $project->fetch($this->fk_project)) {
1166
                  if ($project->socid <= 0) $clonedObj->fk_project = $this->fk_project;
1167
                  else $clonedObj->fk_project = '';
1168
                  } else {
1169
                  $clonedObj->fk_project = '';
1170
                  }
1171
                  } */
1172
                $clonedObj->fk_project = '';    // A cloned proposal is set by default to no project.
1173
            }
1174
1175
            // reset ref_client
1176
            $clonedObj->ref_client = '';
1177
1178
            // TODO Change product price if multi-prices
1179
        } else {
1180
            $objsoc->fetch($clonedObj->socid);
1181
        }
1182
1183
        $clonedObj->id = 0;
1184
        $clonedObj->ref = '';
1185
        $clonedObj->statut = self::STATUS_DRAFT;
1186
1187
        // Clear fields
1188
        $clonedObj->user_author = $user->id;
1189
        $clonedObj->user_valid = '';
1190
        $clonedObj->date = $now;
1191
        $clonedObj->datep = $now;    // deprecated
1192
        $clonedObj->fin_validite = $clonedObj->date + ($clonedObj->duree_validite * 24 * 3600);
1193
        if (empty($conf->global->MAIN_KEEP_REF_CUSTOMER_ON_CLONING))
1194
            $clonedObj->ref_client = '';
1195
1196
        // Create clone
1197
        $result = $clonedObj->create($user);
1198
        if ($result < 0)
1199
            $error++;
1200
        else {
1201
            // copy internal contacts
1202
            if ($clonedObj->copy_linked_contact($this, 'internal') < 0)
1203
                $error++;
1204
1205
            // copy external contacts if same company
1206
            elseif ($this->socid == $clonedObj->socid) {
1207
                if ($clonedObj->copy_linked_contact($this, 'external') < 0)
1208
                    $error++;
1209
            }
1210
        }
1211
1212
        if (!$error) {
1213
            // Hook of thirdparty module
1214
            if (is_object($hookmanager)) {
1215
                $parameters = array('objFrom' => $this, 'clonedObj' => $clonedObj);
1216
                $action = '';
1217
                $reshook = $hookmanager->executeHooks('createFrom', $parameters, $clonedObj, $action);    // Note that $action and $object may have been modified by some hooks
1218
                if ($reshook < 0)
1219
                    $error++;
1220
            }
1221
        }
1222
1223
        unset($this->context['createfromclone']);
1224
1225
        // End
1226
        if (!$error) {
1227
            $this->db->commit();
1228
            return $clonedObj->id;
1229
        } else {
1230
            $this->db->rollback();
1231
            return -1;
1232
        }
1233
    }
1234
1235
    /**
1236
     * 	Load a proposal from database and its ligne array
1237
     *
1238
     * 	@param      int			$rowid		id of object to load
1239
     * 	@param		string		$ref		Ref of proposal
1240
     * 	@return     int         			>0 if OK, <0 if KO
1241
     */
1242
    function fetch($rowid, $ref = '')
1243
    {
1244
1245
        $sql = "SELECT p.rowid, p.ref, p.entity, p.remise, p.remise_percent, p.remise_absolue, p.fk_soc";
1246
        $sql .= ", p.total, p.tva, p.localtax1, p.localtax2, p.total_ht";
1247
        $sql .= ", p.datec";
1248
        $sql .= ", p.date_valid as datev";
1249
        $sql .= ", p.datep as dp";
1250
        $sql .= ", p.fin_validite as dfv";
1251
        $sql .= ", p.date_livraison as date_livraison";
1252
        $sql .= ", p.model_pdf, p.last_main_doc, p.ref_client, p.extraparams";
1253
        $sql .= ", p.note_private, p.note_public";
1254
        $sql .= ", p.fk_projet, p.fk_statut";
1255
        $sql .= ", p.fk_user_author, p.fk_user_valid, p.fk_user_cloture";
1256
        $sql .= ", p.fk_delivery_address";
1257
        $sql .= ", p.fk_availability";
1258
        $sql .= ", p.fk_input_reason";
1259
        $sql .= ", p.fk_cond_reglement";
1260
        $sql .= ", p.fk_mode_reglement";
1261
        $sql .= ', p.fk_account';
1262
        $sql .= ", p.fk_shipping_method";
1263
        $sql .= ", p.fk_incoterms, p.location_incoterms";
1264
        $sql .= ", p.fk_multicurrency, p.multicurrency_code, p.multicurrency_tx, p.multicurrency_total_ht, p.multicurrency_total_tva, p.multicurrency_total_ttc";
1265
        $sql .= ", p.tms as date_modification";
1266
        $sql .= ", i.libelle as libelle_incoterms";
1267
        $sql .= ", c.label as statut_label";
1268
        $sql .= ", ca.code as availability_code, ca.label as availability";
1269
        $sql .= ", dr.code as demand_reason_code, dr.label as demand_reason";
1270
        $sql .= ", cr.code as cond_reglement_code, cr.libelle as cond_reglement, cr.libelle_facture as cond_reglement_libelle_doc";
1271
        $sql .= ", cp.code as mode_reglement_code, cp.libelle as mode_reglement";
1272
        $sql .= " FROM " . MAIN_DB_PREFIX . "propal as p";
1273
        $sql .= ' LEFT JOIN ' . MAIN_DB_PREFIX . 'c_propalst as c ON p.fk_statut = c.id';
1274
        $sql .= ' LEFT JOIN ' . MAIN_DB_PREFIX . 'c_paiement as cp ON p.fk_mode_reglement = cp.id AND cp.entity IN (' . getEntity('c_paiement') . ')';
1275
        $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') . ')';
1276
        $sql .= ' LEFT JOIN ' . MAIN_DB_PREFIX . 'c_availability as ca ON p.fk_availability = ca.rowid';
1277
        $sql .= ' LEFT JOIN ' . MAIN_DB_PREFIX . 'c_input_reason as dr ON p.fk_input_reason = dr.rowid';
1278
        $sql .= ' LEFT JOIN ' . MAIN_DB_PREFIX . 'c_incoterms as i ON p.fk_incoterms = i.rowid';
1279
1280
        if ($ref) {
1281
            $sql .= " WHERE p.entity IN (" . getEntity('propal') . ")"; // Dont't use entity if you use rowid
1282
            $sql .= " AND p.ref='" . $this->db->escape($ref) . "'";
1283
        } else
1284
            $sql .= " WHERE p.rowid=" . $rowid;
1285
1286
        dol_syslog(get_class($this) . "::fetch", LOG_DEBUG);
1287
        $resql = $this->db->query($sql);
1288
        if ($resql) {
1289
            if ($this->db->num_rows($resql)) {
1290
                $obj = $this->db->fetch_object($resql);
1291
1292
                $this->id = $obj->rowid;
1293
                $this->entity = $obj->entity;
1294
1295
                $this->ref = $obj->ref;
1296
                $this->ref_client = $obj->ref_client;
1297
                $this->remise = $obj->remise;
1298
                $this->remise_percent = $obj->remise_percent;
1299
                $this->remise_absolue = $obj->remise_absolue;
1300
                $this->total = $obj->total; // TODO deprecated
1301
                $this->total_ht = $obj->total_ht;
1302
                $this->total_tva = $obj->tva;
1303
                $this->total_localtax1 = $obj->localtax1;
1304
                $this->total_localtax2 = $obj->localtax2;
1305
                $this->total_ttc = $obj->total;
1306
                $this->socid = $obj->fk_soc;
1307
                $this->fk_project = $obj->fk_projet;
1308
                $this->modelpdf = $obj->model_pdf;
1309
                $this->last_main_doc = $obj->last_main_doc;
1310
                $this->note = $obj->note_private; // TODO deprecated
1311
                $this->note_private = $obj->note_private;
1312
                $this->note_public = $obj->note_public;
1313
                $this->statut = (int) $obj->fk_statut;
1314
                $this->statut_libelle = $obj->statut_label;
1315
1316
                $this->datec = $this->db->jdate($obj->datec); // TODO deprecated
1317
                $this->datev = $this->db->jdate($obj->datev); // TODO deprecated
1318
                $this->date_creation = $this->db->jdate($obj->datec); //Creation date
1319
                $this->date_validation = $this->db->jdate($obj->datev); //Validation date
1320
                $this->date_modification = $this->db->jdate($obj->date_modification); // tms
1321
                $this->date = $this->db->jdate($obj->dp); // Proposal date
1322
                $this->datep = $this->db->jdate($obj->dp);    // deprecated
1323
                $this->fin_validite = $this->db->jdate($obj->dfv);
1324
                $this->date_livraison = $this->db->jdate($obj->date_livraison);
1325
                $this->shipping_method_id = ($obj->fk_shipping_method > 0) ? $obj->fk_shipping_method : null;
1326
                $this->availability_id = $obj->fk_availability;
1327
                $this->availability_code = $obj->availability_code;
1328
                $this->availability = $obj->availability;
1329
                $this->demand_reason_id = $obj->fk_input_reason;
1330
                $this->demand_reason_code = $obj->demand_reason_code;
1331
                $this->demand_reason = $obj->demand_reason;
1332
                $this->fk_address = $obj->fk_delivery_address;
1333
1334
                $this->mode_reglement_id = $obj->fk_mode_reglement;
1335
                $this->mode_reglement_code = $obj->mode_reglement_code;
1336
                $this->mode_reglement = $obj->mode_reglement;
1337
                $this->fk_account = ($obj->fk_account > 0) ? $obj->fk_account : null;
1338
                $this->cond_reglement_id = $obj->fk_cond_reglement;
1339
                $this->cond_reglement_code = $obj->cond_reglement_code;
1340
                $this->cond_reglement = $obj->cond_reglement;
1341
                $this->cond_reglement_doc = $obj->cond_reglement_libelle_doc;
1342
1343
                $this->extraparams = (array) json_decode($obj->extraparams, true);
1344
1345
                $this->user_author_id = $obj->fk_user_author;
1346
                $this->user_valid_id = $obj->fk_user_valid;
1347
                $this->user_close_id = $obj->fk_user_cloture;
1348
1349
                //Incoterms
1350
                $this->fk_incoterms = $obj->fk_incoterms;
1351
                $this->location_incoterms = $obj->location_incoterms;
1352
                $this->libelle_incoterms = $obj->libelle_incoterms;
1353
1354
                // Multicurrency
1355
                $this->fk_multicurrency = $obj->fk_multicurrency;
1356
                $this->multicurrency_code = $obj->multicurrency_code;
1357
                $this->multicurrency_tx = $obj->multicurrency_tx;
1358
                $this->multicurrency_total_ht = $obj->multicurrency_total_ht;
1359
                $this->multicurrency_total_tva = $obj->multicurrency_total_tva;
1360
                $this->multicurrency_total_ttc = $obj->multicurrency_total_ttc;
1361
1362
                if ($obj->fk_statut == self::STATUS_DRAFT) {
1363
                    $this->brouillon = 1;
1364
                }
1365
1366
                // Retreive all extrafield
1367
                // fetch optionals attributes and labels
1368
                $this->fetch_optionals();
1369
1370
                $this->db->free($resql);
1371
1372
                $this->lines = array();
1373
1374
                /*
1375
                 * Lines
1376
                 */
1377
                $result = $this->fetch_lines();
1378
                if ($result < 0) {
1379
                    return -3;
1380
                }
1381
1382
                return 1;
1383
            }
1384
1385
            $this->error = "Record Not Found";
1386
            return 0;
1387
        } else {
1388
            $this->error = $this->db->lasterror();
1389
            return -1;
1390
        }
1391
    }
1392
1393
    /**
1394
     *      Update database
1395
     *
1396
     *      @param      User	$user        	User that modify
1397
     *      @param      int		$notrigger	    0=launch triggers after, 1=disable triggers
1398
     *      @return     int      			   	<0 if KO, >0 if OK
1399
     */
1400
    function update(User $user, $notrigger = 0)
1401
    {
1402
        global $conf;
1403
1404
        $error = 0;
1405
1406
        // Clean parameters
1407
        if (isset($this->ref))
1408
            $this->ref = trim($this->ref);
1409
        if (isset($this->ref_client))
1410
            $this->ref_client = trim($this->ref_client);
1411
        if (isset($this->note) || isset($this->note_private))
1412
            $this->note_private = (isset($this->note_private) ? trim($this->note_private) : trim($this->note));
1413
        if (isset($this->note_public))
1414
            $this->note_public = trim($this->note_public);
1415
        if (isset($this->modelpdf))
1416
            $this->modelpdf = trim($this->modelpdf);
1417
        if (isset($this->import_key))
1418
            $this->import_key = trim($this->import_key);
1419
1420
        // Check parameters
1421
        // Put here code to add control on parameters values
1422
        // Update request
1423
        $sql = "UPDATE " . MAIN_DB_PREFIX . "propal SET";
1424
1425
        $sql .= " ref=" . (isset($this->ref) ? "'" . $this->db->escape($this->ref) . "'" : "null") . ",";
1426
        $sql .= " ref_client=" . (isset($this->ref_client) ? "'" . $this->db->escape($this->ref_client) . "'" : "null") . ",";
1427
        $sql .= " ref_ext=" . (isset($this->ref_ext) ? "'" . $this->db->escape($this->ref_ext) . "'" : "null") . ",";
1428
        $sql .= " fk_soc=" . (isset($this->socid) ? $this->socid : "null") . ",";
1429
        $sql .= " datep=" . (strval($this->datep) != '' ? "'" . $this->db->idate($this->datep) . "'" : 'null') . ",";
1430
        $sql .= " date_valid=" . (strval($this->date_validation) != '' ? "'" . $this->db->idate($this->date_validation) . "'" : 'null') . ",";
1431
        $sql .= " tva=" . (isset($this->total_tva) ? $this->total_tva : "null") . ",";
1432
        $sql .= " localtax1=" . (isset($this->total_localtax1) ? $this->total_localtax1 : "null") . ",";
1433
        $sql .= " localtax2=" . (isset($this->total_localtax2) ? $this->total_localtax2 : "null") . ",";
1434
        $sql .= " total_ht=" . (isset($this->total_ht) ? $this->total_ht : "null") . ",";
1435
        $sql .= " total=" . (isset($this->total_ttc) ? $this->total_ttc : "null") . ",";
1436
        $sql .= " fk_statut=" . (isset($this->statut) ? $this->statut : "null") . ",";
1437
        $sql .= " fk_user_author=" . (isset($this->user_author_id) ? $this->user_author_id : "null") . ",";
1438
        $sql .= " fk_user_valid=" . (isset($this->user_valid) ? $this->user_valid : "null") . ",";
1439
        $sql .= " fk_projet=" . (isset($this->fk_project) ? $this->fk_project : "null") . ",";
1440
        $sql .= " fk_cond_reglement=" . (isset($this->cond_reglement_id) ? $this->cond_reglement_id : "null") . ",";
1441
        $sql .= " fk_mode_reglement=" . (isset($this->mode_reglement_id) ? $this->mode_reglement_id : "null") . ",";
1442
        $sql .= " note_private=" . (isset($this->note_private) ? "'" . $this->db->escape($this->note_private) . "'" : "null") . ",";
1443
        $sql .= " note_public=" . (isset($this->note_public) ? "'" . $this->db->escape($this->note_public) . "'" : "null") . ",";
1444
        $sql .= " model_pdf=" . (isset($this->modelpdf) ? "'" . $this->db->escape($this->modelpdf) . "'" : "null") . ",";
1445
        $sql .= " import_key=" . (isset($this->import_key) ? "'" . $this->db->escape($this->import_key) . "'" : "null") . "";
1446
1447
        $sql .= " WHERE rowid=" . $this->id;
1448
1449
        $this->db->begin();
1450
1451
        dol_syslog(get_class($this) . "::update", LOG_DEBUG);
1452
        $resql = $this->db->query($sql);
1453
        if (!$resql) {
1454
            $error++;
1455
            $this->errors[] = "Error " . $this->db->lasterror();
1456
        }
1457
1458
        if (!$error && empty($conf->global->MAIN_EXTRAFIELDS_DISABLED) && is_array($this->array_options) && count($this->array_options) > 0) {
1459
            $result = $this->insertExtraFields();
1460
            if ($result < 0) {
1461
                $error++;
1462
            }
1463
        }
1464
1465
        if (!$error && !$notrigger) {
1466
            // Call trigger
1467
            $result = $this->call_trigger('PROPAL_MODIFY', $user);
1468
            if ($result < 0)
1469
                $error++;
1470
            // End call triggers
1471
        }
1472
1473
        // Commit or rollback
1474
        if ($error) {
1475
            foreach ($this->errors as $errmsg) {
1476
                dol_syslog(get_class($this) . "::update " . $errmsg, LOG_ERR);
1477
                $this->error .= ($this->error ? ', ' . $errmsg : $errmsg);
1478
            }
1479
            $this->db->rollback();
1480
            return -1 * $error;
1481
        } else {
1482
            $this->db->commit();
1483
            return 1;
1484
        }
1485
    }
1486
1487
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
1488
    /**
1489
     * Load array lines
1490
     *
1491
     * @param		int		$only_product	Return only physical products
1492
     * @return		int						<0 if KO, >0 if OK
1493
     */
1494
    function fetch_lines($only_product = 0)
1495
    {
1496
        // phpcs:enable
1497
        $this->lines = array();
1498
1499
        $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,';
1500
        $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,';
1501
        $sql .= ' d.fk_unit,';
1502
        $sql .= ' p.ref as product_ref, p.description as product_desc, p.fk_product_type, p.label as product_label, p.tobatch as product_batch,';
1503
        $sql .= ' p.weight, p.weight_units, p.volume, p.volume_units,';
1504
        $sql .= ' d.date_start, d.date_end,';
1505
        $sql .= ' d.fk_multicurrency, d.multicurrency_code, d.multicurrency_subprice, d.multicurrency_total_ht, d.multicurrency_total_tva, d.multicurrency_total_ttc';
1506
        $sql .= ' FROM ' . MAIN_DB_PREFIX . 'propaldet as d';
1507
        $sql .= ' LEFT JOIN ' . MAIN_DB_PREFIX . 'product as p ON (d.fk_product = p.rowid)';
1508
        $sql .= ' WHERE d.fk_propal = ' . $this->id;
1509
        if ($only_product)
1510
            $sql .= ' AND p.fk_product_type = 0';
1511
        $sql .= ' ORDER by d.rang';
1512
1513
        dol_syslog(get_class($this) . "::fetch_lines", LOG_DEBUG);
1514
        $result = $this->db->query($sql);
1515
        if ($result) {
1516
            require_once DOL_DOCUMENT_ROOT . '/core/class/extrafields.class.php';
1517
1518
            $num = $this->db->num_rows($result);
1519
1520
            $i = 0;
1521
            while ($i < $num) {
1522
                $objp = $this->db->fetch_object($result);
1523
1524
                $line = new PropaleLigne($this->db);
1525
1526
                $line->rowid = $objp->rowid; //Deprecated
1527
                $line->id = $objp->rowid;
1528
                $line->fk_propal = $objp->fk_propal;
1529
                $line->fk_parent_line = $objp->fk_parent_line;
1530
                $line->product_type = $objp->product_type;
1531
                $line->label = $objp->custom_label;
1532
                $line->desc = $objp->description;  // Description ligne
1533
                $line->description = $objp->description;  // Description ligne
1534
                $line->qty = $objp->qty;
1535
                $line->vat_src_code = $objp->vat_src_code;
1536
                $line->tva_tx = $objp->tva_tx;
1537
                $line->localtax1_tx = $objp->localtax1_tx;
1538
                $line->localtax2_tx = $objp->localtax2_tx;
1539
                $line->localtax1_type = $objp->localtax1_type;
1540
                $line->localtax2_type = $objp->localtax2_type;
1541
                $line->subprice = $objp->subprice;
1542
                $line->fk_remise_except = $objp->fk_remise_except;
1543
                $line->remise_percent = $objp->remise_percent;
1544
                $line->price = $objp->price;  // TODO deprecated
1545
1546
                $line->info_bits = $objp->info_bits;
1547
                $line->total_ht = $objp->total_ht;
1548
                $line->total_tva = $objp->total_tva;
1549
                $line->total_localtax1 = $objp->total_localtax1;
1550
                $line->total_localtax2 = $objp->total_localtax2;
1551
                $line->total_ttc = $objp->total_ttc;
1552
                $line->fk_fournprice = $objp->fk_fournprice;
1553
                $marginInfos = getMarginInfos($objp->subprice, $objp->remise_percent, $objp->tva_tx, $objp->localtax1_tx, $objp->localtax2_tx, $line->fk_fournprice, $objp->pa_ht);
1554
                $line->pa_ht = $marginInfos[0];
1555
                $line->marge_tx = $marginInfos[1];
1556
                $line->marque_tx = $marginInfos[2];
1557
                $line->special_code = $objp->special_code;
1558
                $line->rang = $objp->rang;
1559
1560
                $line->fk_product = $objp->fk_product;
1561
1562
                $line->ref = $objp->product_ref;  // TODO deprecated
1563
                $line->product_ref = $objp->product_ref;
1564
                $line->libelle = $objp->product_label;  // TODO deprecated
1565
                $line->product_label = $objp->product_label;
1566
                $line->product_desc = $objp->product_desc;   // Description produit
1567
                $line->product_tobatch = $objp->product_tobatch;
1568
                $line->fk_product_type = $objp->fk_product_type; // TODO deprecated
1569
                $line->fk_unit = $objp->fk_unit;
1570
                $line->weight = $objp->weight;
1571
                $line->weight_units = $objp->weight_units;
1572
                $line->volume = $objp->volume;
1573
                $line->volume_units = $objp->volume_units;
1574
1575
                $line->date_start = $this->db->jdate($objp->date_start);
1576
                $line->date_end = $this->db->jdate($objp->date_end);
1577
1578
                // Multicurrency
1579
                $line->fk_multicurrency = $objp->fk_multicurrency;
1580
                $line->multicurrency_code = $objp->multicurrency_code;
1581
                $line->multicurrency_subprice = $objp->multicurrency_subprice;
1582
                $line->multicurrency_total_ht = $objp->multicurrency_total_ht;
1583
                $line->multicurrency_total_tva = $objp->multicurrency_total_tva;
1584
                $line->multicurrency_total_ttc = $objp->multicurrency_total_ttc;
1585
1586
                $line->fetch_optionals();
1587
1588
                $this->lines[$i] = $line;
1589
                //dol_syslog("1 ".$line->fk_product);
1590
                //print "xx $i ".$this->lines[$i]->fk_product;
1591
                $i++;
1592
            }
1593
1594
            $this->db->free($result);
1595
1596
            return $num;
1597
        } else {
1598
            $this->error = $this->db->lasterror();
1599
            return -3;
1600
        }
1601
    }
1602
1603
    /**
1604
     *  Set status to validated
1605
     *
1606
     *  @param	User	$user       Object user that validate
1607
     *  @param	int		$notrigger	1=Does not execute triggers, 0=execute triggers
1608
     *  @return int         		<0 if KO, 0=Nothing done, >=0 if OK
1609
     */
1610
    function valid($user, $notrigger = 0)
1611
    {
1612
        require_once DOL_DOCUMENT_ROOT . '/core/lib/files.lib.php';
1613
1614
        global $conf;
1615
1616
        $error = 0;
1617
1618
        // Protection
1619
        if ($this->statut == self::STATUS_VALIDATED) {
1620
            dol_syslog(get_class($this) . "::valid action abandonned: already validated", LOG_WARNING);
1621
            return 0;
1622
        }
1623
1624
        if (!((empty($conf->global->MAIN_USE_ADVANCED_PERMS) && !empty($user->rights->propal->creer)) || (!empty($conf->global->MAIN_USE_ADVANCED_PERMS) && !empty($user->rights->propal->propal_advance->validate)))) {
1625
            $this->error = 'ErrorPermissionDenied';
1626
            dol_syslog(get_class($this) . "::valid " . $this->error, LOG_ERR);
1627
            return -1;
1628
        }
1629
1630
        $now = dol_now();
1631
1632
        $this->db->begin();
1633
1634
        // Numbering module definition
1635
        $soc = new Societe($this->db);
1636
        $soc->fetch($this->socid);
1637
1638
        // Define new ref
1639
        if (!$error && (preg_match('/^[\(]?PROV/i', $this->ref) || empty($this->ref))) { // empty should not happened, but when it occurs, the test save life
1640
            $num = $this->getNextNumRef($soc);
1641
        } else {
1642
            $num = $this->ref;
1643
        }
1644
        $this->newref = $num;
1645
1646
        $sql = "UPDATE " . MAIN_DB_PREFIX . "propal";
1647
        $sql .= " SET ref = '" . $num . "',";
1648
        $sql .= " fk_statut = " . self::STATUS_VALIDATED . ", date_valid='" . $this->db->idate($now) . "', fk_user_valid=" . $user->id;
1649
        $sql .= " WHERE rowid = " . $this->id . " AND fk_statut = " . self::STATUS_DRAFT;
1650
1651
        dol_syslog(get_class($this) . "::valid", LOG_DEBUG);
1652
        $resql = $this->db->query($sql);
1653
        if (!$resql) {
1654
            dol_print_error($this->db);
1655
            $error++;
1656
        }
1657
1658
        // Trigger calls
1659
        if (!$error && !$notrigger) {
1660
            // Call trigger
1661
            $result = $this->call_trigger('PROPAL_VALIDATE', $user);
1662
            if ($result < 0) {
1663
                $error++;
1664
            }
1665
            // End call triggers
1666
        }
1667
1668
        if (!$error) {
1669
            $this->oldref = $this->ref;
1670
1671
            // Rename directory if dir was a temporary ref
1672
            if (preg_match('/^[\(]?PROV/i', $this->ref)) {
1673
                // Rename of propal directory ($this->ref = old ref, $num = new ref)
1674
                // to  not lose the linked files
1675
                $oldref = dol_sanitizeFileName($this->ref);
1676
                $newref = dol_sanitizeFileName($num);
1677
                $dirsource = $conf->propal->multidir_output[$this->entity] . '/' . $oldref;
1678
                $dirdest = $conf->propal->multidir_output[$this->entity] . '/' . $newref;
1679
1680
                if (file_exists($dirsource)) {
1681
                    dol_syslog(get_class($this) . "::validate rename dir " . $dirsource . " into " . $dirdest);
1682
                    if (@rename($dirsource, $dirdest)) {
1683
                        dol_syslog("Rename ok");
1684
                        // Rename docs starting with $oldref with $newref
1685
                        $listoffiles = dol_dir_list($dirdest, 'files', 1, '^' . preg_quote($oldref, '/'));
1686
                        foreach ($listoffiles as $fileentry) {
1687
                            $dirsource = $fileentry['name'];
1688
                            $dirdest = preg_replace('/^' . preg_quote($oldref, '/') . '/', $newref, $dirsource);
1689
                            $dirsource = $fileentry['path'] . '/' . $dirsource;
1690
                            $dirdest = $fileentry['path'] . '/' . $dirdest;
1691
                            @rename($dirsource, $dirdest);
1692
                        }
1693
                    }
1694
                }
1695
            }
1696
1697
            $this->ref = $num;
1698
            $this->brouillon = 0;
1699
            $this->statut = self::STATUS_VALIDATED;
1700
            $this->user_valid_id = $user->id;
1701
            $this->datev = $now;
1702
1703
            $this->db->commit();
1704
            return 1;
1705
        } else {
1706
            $this->db->rollback();
1707
            return -1;
1708
        }
1709
    }
1710
1711
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
1712
    /**
1713
     *  Define proposal date
1714
     *
1715
     *  @param  User		$user      	Object user that modify
1716
     *  @param  int			$date		Date
1717
     *  @param  int			$notrigger	1=Does not execute triggers, 0= execute triggers
1718
     *  @return	int         			<0 if KO, >0 if OK
1719
     */
1720
    function set_date($user, $date, $notrigger = 0)
1721
    {
1722
        // phpcs:enable
1723
        if (empty($date)) {
1724
            $this->error = 'ErrorBadParameter';
1725
            dol_syslog(get_class($this) . "::set_date " . $this->error, LOG_ERR);
1726
            return -1;
1727
        }
1728
1729
        if (!empty($user->rights->propal->creer)) {
1730
            $error = 0;
1731
1732
            $this->db->begin();
1733
1734
            $sql = "UPDATE " . MAIN_DB_PREFIX . "propal SET datep = '" . $this->db->idate($date) . "'";
1735
            $sql .= " WHERE rowid = " . $this->id . " AND fk_statut = " . self::STATUS_DRAFT;
1736
1737
            dol_syslog(__METHOD__, LOG_DEBUG);
1738
            $resql = $this->db->query($sql);
1739
            if (!$resql) {
1740
                $this->errors[] = $this->db->error();
1741
                $error++;
1742
            }
1743
1744
            if (!$error) {
1745
                $this->oldcopy = clone $this;
1746
                $this->date = $date;
1747
                $this->datep = $date;    // deprecated
1748
            }
1749
1750
            if (!$notrigger && empty($error)) {
1751
                // Call trigger
1752
                $result = $this->call_trigger('PROPAL_MODIFY', $user);
1753
                if ($result < 0)
1754
                    $error++;
1755
                // End call triggers
1756
            }
1757
1758
            if (!$error) {
1759
                $this->db->commit();
1760
                return 1;
1761
            } else {
1762
                foreach ($this->errors as $errmsg) {
1763
                    dol_syslog(__METHOD__ . ' Error: ' . $errmsg, LOG_ERR);
1764
                    $this->error .= ($this->error ? ', ' . $errmsg : $errmsg);
1765
                }
1766
                $this->db->rollback();
1767
                return -1 * $error;
1768
            }
1769
        }
1770
    }
1771
1772
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
1773
    /**
1774
     * 	Define end validity date
1775
     *
1776
     * 	@param		User	$user        		Object user that modify
1777
     * 	@param      int		$date_fin_validite	End of validity date
1778
     *  @param  	int		$notrigger			1=Does not execute triggers, 0= execute triggers
1779
     * 	@return     int         				<0 if KO, >0 if OK
1780
     */
1781
    function set_echeance($user, $date_fin_validite, $notrigger = 0)
1782
    {
1783
        // phpcs:enable
1784
        if (!empty($user->rights->propal->creer)) {
1785
            $error = 0;
1786
1787
            $this->db->begin();
1788
1789
            $sql = "UPDATE " . MAIN_DB_PREFIX . "propal SET fin_validite = " . ($date_fin_validite != '' ? "'" . $this->db->idate($date_fin_validite) . "'" : 'null');
1790
            $sql .= " WHERE rowid = " . $this->id . " AND fk_statut = " . self::STATUS_DRAFT;
1791
1792
            dol_syslog(__METHOD__, LOG_DEBUG);
1793
            $resql = $this->db->query($sql);
1794
            if (!$resql) {
1795
                $this->errors[] = $this->db->error();
1796
                $error++;
1797
            }
1798
1799
1800
            if (!$error) {
1801
                $this->oldcopy = clone $this;
1802
                $this->fin_validite = $date_fin_validite;
1803
            }
1804
1805
            if (!$notrigger && empty($error)) {
1806
                // Call trigger
1807
                $result = $this->call_trigger('PROPAL_MODIFY', $user);
1808
                if ($result < 0)
1809
                    $error++;
1810
                // End call triggers
1811
            }
1812
1813
            if (!$error) {
1814
                $this->db->commit();
1815
                return 1;
1816
            } else {
1817
                foreach ($this->errors as $errmsg) {
1818
                    dol_syslog(__METHOD__ . ' Error: ' . $errmsg, LOG_ERR);
1819
                    $this->error .= ($this->error ? ', ' . $errmsg : $errmsg);
1820
                }
1821
                $this->db->rollback();
1822
                return -1 * $error;
1823
            }
1824
        }
1825
    }
1826
1827
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
1828
    /**
1829
     * 	Set delivery date
1830
     *
1831
     * 	@param      User 	$user        		Object user that modify
1832
     * 	@param      int		$date_livraison     Delivery date
1833
     *  @param  	int		$notrigger			1=Does not execute triggers, 0= execute triggers
1834
     * 	@return     int         				<0 if ko, >0 if ok
1835
     */
1836
    function set_date_livraison($user, $date_livraison, $notrigger = 0)
1837
    {
1838
        // phpcs:enable
1839
        if (!empty($user->rights->propal->creer)) {
1840
            $error = 0;
1841
1842
            $this->db->begin();
1843
1844
            $sql = "UPDATE " . MAIN_DB_PREFIX . "propal ";
1845
            $sql .= " SET date_livraison = " . ($date_livraison != '' ? "'" . $this->db->idate($date_livraison) . "'" : 'null');
1846
            $sql .= " WHERE rowid = " . $this->id;
1847
1848
            dol_syslog(__METHOD__, LOG_DEBUG);
1849
            $resql = $this->db->query($sql);
1850
            if (!$resql) {
1851
                $this->errors[] = $this->db->error();
1852
                $error++;
1853
            }
1854
1855
            if (!$error) {
1856
                $this->oldcopy = clone $this;
1857
                $this->date_livraison = $date_livraison;
1858
            }
1859
1860
            if (!$notrigger && empty($error)) {
1861
                // Call trigger
1862
                $result = $this->call_trigger('PROPAL_MODIFY', $user);
1863
                if ($result < 0)
1864
                    $error++;
1865
                // End call triggers
1866
            }
1867
1868
            if (!$error) {
1869
                $this->db->commit();
1870
                return 1;
1871
            } else {
1872
                foreach ($this->errors as $errmsg) {
1873
                    dol_syslog(__METHOD__ . ' Error: ' . $errmsg, LOG_ERR);
1874
                    $this->error .= ($this->error ? ', ' . $errmsg : $errmsg);
1875
                }
1876
                $this->db->rollback();
1877
                return -1 * $error;
1878
            }
1879
        }
1880
    }
1881
1882
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
1883
    /**
1884
     *  Set delivery
1885
     *
1886
     *  @param		User	$user		  	Object user that modify
1887
     *  @param      int		$id				Availability id
1888
     *  @param  	int		$notrigger		1=Does not execute triggers, 0= execute triggers
1889
     *  @return     int           			<0 if KO, >0 if OK
1890
     */
1891
    function set_availability($user, $id, $notrigger = 0)
1892
    {
1893
        // phpcs:enable
1894
        if (!empty($user->rights->propal->creer) && $this->statut >= self::STATUS_DRAFT) {
1895
            $error = 0;
1896
1897
            $this->db->begin();
1898
1899
            $sql = "UPDATE " . MAIN_DB_PREFIX . "propal ";
1900
            $sql .= " SET fk_availability = '" . $id . "'";
1901
            $sql .= " WHERE rowid = " . $this->id;
1902
1903
            dol_syslog(__METHOD__ . ' availability(' . $id . ')', LOG_DEBUG);
1904
            $resql = $this->db->query($sql);
1905
            if (!$resql) {
1906
                $this->errors[] = $this->db->error();
1907
                $error++;
1908
            }
1909
1910
            if (!$error) {
1911
                $this->oldcopy = clone $this;
1912
                $this->fk_availability = $id;
1913
                $this->availability_id = $id;
1914
            }
1915
1916
            if (!$notrigger && empty($error)) {
1917
                // Call trigger
1918
                $result = $this->call_trigger('PROPAL_MODIFY', $user);
1919
                if ($result < 0)
1920
                    $error++;
1921
                // End call triggers
1922
            }
1923
1924
            if (!$error) {
1925
                $this->db->commit();
1926
                return 1;
1927
            } else {
1928
                foreach ($this->errors as $errmsg) {
1929
                    dol_syslog(__METHOD__ . ' Error: ' . $errmsg, LOG_ERR);
1930
                    $this->error .= ($this->error ? ', ' . $errmsg : $errmsg);
1931
                }
1932
                $this->db->rollback();
1933
                return -1 * $error;
1934
            }
1935
        } else {
1936
            $error_str = 'Propal status do not meet requirement ' . $this->statut;
1937
            dol_syslog(__METHOD__ . $error_str, LOG_ERR);
1938
            $this->error = $error_str;
1939
            $this->errors[] = $this->error;
1940
            return -2;
1941
        }
1942
    }
1943
1944
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
1945
    /**
1946
     *  Set source of demand
1947
     *
1948
     *  @param		User	$user		Object user that modify
1949
     *  @param      int		$id			Input reason id
1950
     *  @param  	int		$notrigger	1=Does not execute triggers, 0= execute triggers
1951
     *  @return     int           		<0 if KO, >0 if OK
1952
     */
1953
    function set_demand_reason($user, $id, $notrigger = 0)
1954
    {
1955
        // phpcs:enable
1956
        if (!empty($user->rights->propal->creer) && $this->statut >= self::STATUS_DRAFT) {
1957
            $error = 0;
1958
1959
            $this->db->begin();
1960
1961
            $sql = "UPDATE " . MAIN_DB_PREFIX . "propal ";
1962
            $sql .= " SET fk_input_reason = " . $id;
1963
            $sql .= " WHERE rowid = " . $this->id;
1964
1965
            dol_syslog(__METHOD__, LOG_DEBUG);
1966
            $resql = $this->db->query($sql);
1967
            if (!$resql) {
1968
                $this->errors[] = $this->db->error();
1969
                $error++;
1970
            }
1971
1972
1973
            if (!$error) {
1974
                $this->oldcopy = clone $this;
1975
                $this->fk_input_reason = $id;
1976
                $this->demand_reason_id = $id;
1977
            }
1978
1979
1980
            if (!$notrigger && empty($error)) {
1981
                // Call trigger
1982
                $result = $this->call_trigger('PROPAL_MODIFY', $user);
1983
                if ($result < 0)
1984
                    $error++;
1985
                // End call triggers
1986
            }
1987
1988
            if (!$error) {
1989
                $this->db->commit();
1990
                return 1;
1991
            } else {
1992
                foreach ($this->errors as $errmsg) {
1993
                    dol_syslog(__METHOD__ . ' Error: ' . $errmsg, LOG_ERR);
1994
                    $this->error .= ($this->error ? ', ' . $errmsg : $errmsg);
1995
                }
1996
                $this->db->rollback();
1997
                return -1 * $error;
1998
            }
1999
        } else {
2000
            $error_str = 'Propal status do not meet requirement ' . $this->statut;
2001
            dol_syslog(__METHOD__ . $error_str, LOG_ERR);
2002
            $this->error = $error_str;
2003
            $this->errors[] = $this->error;
2004
            return -2;
2005
        }
2006
    }
2007
2008
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
2009
    /**
2010
     * Set customer reference number
2011
     *
2012
     *  @param      User	$user			Object user that modify
2013
     *  @param      string	$ref_client		Customer reference
2014
     *  @param  	int		$notrigger		1=Does not execute triggers, 0= execute triggers
2015
     *  @return     int						<0 if ko, >0 if ok
2016
     */
2017
    function set_ref_client($user, $ref_client, $notrigger = 0)
2018
    {
2019
        // phpcs:enable
2020
        if (!empty($user->rights->propal->creer)) {
2021
            $error = 0;
2022
2023
            $this->db->begin();
2024
2025
            $sql = 'UPDATE ' . MAIN_DB_PREFIX . 'propal SET ref_client = ' . (empty($ref_client) ? 'NULL' : '\'' . $this->db->escape($ref_client) . '\'');
2026
            $sql .= ' WHERE rowid = ' . $this->id;
2027
2028
            dol_syslog(__METHOD__ . ' $this->id=' . $this->id . ', ref_client=' . $ref_client, LOG_DEBUG);
2029
            $resql = $this->db->query($sql);
2030
            if (!$resql) {
2031
                $this->errors[] = $this->db->error();
2032
                $error++;
2033
            }
2034
2035
            if (!$error) {
2036
                $this->oldcopy = clone $this;
2037
                $this->ref_client = $ref_client;
2038
            }
2039
2040
            if (!$notrigger && empty($error)) {
2041
                // Call trigger
2042
                $result = $this->call_trigger('PROPAL_MODIFY', $user);
2043
                if ($result < 0)
2044
                    $error++;
2045
                // End call triggers
2046
            }
2047
2048
            if (!$error) {
2049
                $this->db->commit();
2050
                return 1;
2051
            } else {
2052
                foreach ($this->errors as $errmsg) {
2053
                    dol_syslog(__METHOD__ . ' Error: ' . $errmsg, LOG_ERR);
2054
                    $this->error .= ($this->error ? ', ' . $errmsg : $errmsg);
2055
                }
2056
                $this->db->rollback();
2057
                return -1 * $error;
2058
            }
2059
        } else {
2060
            return -1;
2061
        }
2062
    }
2063
2064
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
2065
    /**
2066
     * 	Set an overall discount on the proposal
2067
     *
2068
     * 	@param      User	$user       Object user that modify
2069
     * 	@param      double	$remise     Amount discount
2070
     *  @param  	int		$notrigger	1=Does not execute triggers, 0= execute triggers
2071
     * 	@return     int         		<0 if ko, >0 if ok
2072
     */
2073
    function set_remise_percent($user, $remise, $notrigger = 0)
2074
    {
2075
        // phpcs:enable
2076
        $remise = trim($remise) ? trim($remise) : 0;
2077
2078
        if (!empty($user->rights->propal->creer)) {
2079
            $remise = price2num($remise);
2080
2081
            $error = 0;
2082
2083
            $this->db->begin();
2084
2085
            $sql = "UPDATE " . MAIN_DB_PREFIX . "propal SET remise_percent = " . $remise;
2086
            $sql .= " WHERE rowid = " . $this->id . " AND fk_statut = " . self::STATUS_DRAFT;
2087
2088
            dol_syslog(__METHOD__, LOG_DEBUG);
2089
            $resql = $this->db->query($sql);
2090
            if (!$resql) {
2091
                $this->errors[] = $this->db->error();
2092
                $error++;
2093
            }
2094
2095
            if (!$error) {
2096
                $this->oldcopy = clone $this;
2097
                $this->remise_percent = $remise;
2098
                $this->update_price(1);
2099
            }
2100
2101
            if (!$notrigger && empty($error)) {
2102
                // Call trigger
2103
                $result = $this->call_trigger('PROPAL_MODIFY', $user);
2104
                if ($result < 0)
2105
                    $error++;
2106
                // End call triggers
2107
            }
2108
2109
            if (!$error) {
2110
                $this->db->commit();
2111
                return 1;
2112
            } else {
2113
                foreach ($this->errors as $errmsg) {
2114
                    dol_syslog(__METHOD__ . ' Error: ' . $errmsg, LOG_ERR);
2115
                    $this->error .= ($this->error ? ', ' . $errmsg : $errmsg);
2116
                }
2117
                $this->db->rollback();
2118
                return -1 * $error;
2119
            }
2120
        }
2121
    }
2122
2123
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
2124
    /**
2125
     * 	Set an absolute overall discount on the proposal
2126
     *
2127
     * 	@param      User	$user       Object user that modify
2128
     * 	@param      double	$remise     Amount discount
2129
     *  @param  	int		$notrigger	1=Does not execute triggers, 0= execute triggers
2130
     * 	@return     int         		<0 if ko, >0 if ok
2131
     */
2132
    function set_remise_absolue($user, $remise, $notrigger = 0)
2133
    {
2134
        // phpcs:enable
2135
        $remise = trim($remise) ? trim($remise) : 0;
2136
2137
        if (!empty($user->rights->propal->creer)) {
2138
            $remise = price2num($remise);
2139
2140
            $error = 0;
2141
2142
            $this->db->begin();
2143
2144
            $sql = "UPDATE " . MAIN_DB_PREFIX . "propal ";
2145
            $sql .= " SET remise_absolue = " . $remise;
2146
            $sql .= " WHERE rowid = " . $this->id . " AND fk_statut = " . self::STATUS_DRAFT;
2147
2148
            dol_syslog(__METHOD__, LOG_DEBUG);
2149
            $resql = $this->db->query($sql);
2150
            if (!$resql) {
2151
                $this->errors[] = $this->db->error();
2152
                $error++;
2153
            }
2154
2155
            if (!$error) {
2156
                $this->oldcopy = clone $this;
2157
                $this->remise_absolue = $remise;
2158
                $this->update_price(1);
2159
            }
2160
2161
            if (!$notrigger && empty($error)) {
2162
                // Call trigger
2163
                $result = $this->call_trigger('PROPAL_MODIFY', $user);
2164
                if ($result < 0)
2165
                    $error++;
2166
                // End call triggers
2167
            }
2168
2169
            if (!$error) {
2170
                $this->db->commit();
2171
                return 1;
2172
            } else {
2173
                foreach ($this->errors as $errmsg) {
2174
                    dol_syslog(__METHOD__ . ' Error: ' . $errmsg, LOG_ERR);
2175
                    $this->error .= ($this->error ? ', ' . $errmsg : $errmsg);
2176
                }
2177
                $this->db->rollback();
2178
                return -1 * $error;
2179
            }
2180
        }
2181
    }
2182
2183
    /**
2184
     * 	Reopen the commercial proposal
2185
     *
2186
     * 	@param      User	$user		Object user that close
2187
     * 	@param      int		$statut		Statut
2188
     * 	@param      string	$note		Comment
2189
     *  @param		int		$notrigger	1=Does not execute triggers, 0= execute triggers
2190
     * 	@return     int         		<0 if KO, >0 if OK
2191
     */
2192
    function reopen($user, $statut, $note = '', $notrigger = 0)
2193
    {
2194
2195
        $this->statut = $statut;
2196
        $error = 0;
2197
2198
        $sql = "UPDATE " . MAIN_DB_PREFIX . "propal";
2199
        $sql .= " SET fk_statut = " . $this->statut . ",";
2200
        if (!empty($note))
2201
            $sql .= " note_private = '" . $this->db->escape($note) . "',";
2202
        $sql .= " date_cloture=NULL, fk_user_cloture=NULL";
2203
        $sql .= " WHERE rowid = " . $this->id;
2204
2205
        $this->db->begin();
2206
2207
        dol_syslog(get_class($this) . "::reopen", LOG_DEBUG);
2208
        $resql = $this->db->query($sql);
2209
        if (!$resql) {
2210
            $error++;
2211
            $this->errors[] = "Error " . $this->db->lasterror();
2212
        }
2213
        if (!$error) {
2214
            if (!$notrigger) {
2215
                // Call trigger
2216
                $result = $this->call_trigger('PROPAL_REOPEN', $user);
2217
                if ($result < 0) {
2218
                    $error++;
2219
                }
2220
                // End call triggers
2221
            }
2222
        }
2223
2224
        // Commit or rollback
2225
        if ($error) {
2226
            if (!empty($this->errors)) {
2227
                foreach ($this->errors as $errmsg) {
2228
                    dol_syslog(get_class($this) . "::update " . $errmsg, LOG_ERR);
2229
                    $this->error .= ($this->error ? ', ' . $errmsg : $errmsg);
2230
                }
2231
            }
2232
            $this->db->rollback();
2233
            return -1 * $error;
2234
        } else {
2235
            $this->db->commit();
2236
            return 1;
2237
        }
2238
    }
2239
2240
    /**
2241
     * 	Close the commercial proposal
2242
     *
2243
     * 	@param      User	$user		Object user that close
2244
     * 	@param      int		$statut		Statut
2245
     * 	@param      string	$note		Complete private note with this note
2246
     *  @param		int		$notrigger	1=Does not execute triggers, 0=Execute triggers
2247
     * 	@return     int         		<0 if KO, >0 if OK
2248
     */
2249
    function cloture($user, $statut, $note = "", $notrigger = 0)
2250
    {
2251
        global $langs, $conf;
2252
2253
        $error = 0;
2254
        $now = dol_now();
2255
2256
        $this->db->begin();
2257
2258
        $newprivatenote = dol_concatdesc($this->note_private, $note);
2259
2260
        $sql = "UPDATE " . MAIN_DB_PREFIX . "propal";
2261
        $sql .= " SET fk_statut = " . $statut . ", note_private = '" . $this->db->escape($newprivatenote) . "', date_cloture='" . $this->db->idate($now) . "', fk_user_cloture=" . $user->id;
2262
        $sql .= " WHERE rowid = " . $this->id;
2263
2264
        $resql = $this->db->query($sql);
2265
        if ($resql) {
2266
            $modelpdf = $conf->global->PROPALE_ADDON_PDF_ODT_CLOSED ? $conf->global->PROPALE_ADDON_PDF_ODT_CLOSED : $this->modelpdf;
2267
            $trigger_name = 'PROPAL_CLOSE_REFUSED';
2268
2269
            if ($statut == self::STATUS_SIGNED) {
2270
                $trigger_name = 'PROPAL_CLOSE_SIGNED';
2271
                $modelpdf = $conf->global->PROPALE_ADDON_PDF_ODT_TOBILL ? $conf->global->PROPALE_ADDON_PDF_ODT_TOBILL : $this->modelpdf;
2272
2273
                // The connected company is classified as a client
2274
                $soc = new Societe($this->db);
2275
                $soc->id = $this->socid;
2276
                $result = $soc->set_as_client();
2277
2278
                if ($result < 0) {
2279
                    $this->error = $this->db->lasterror();
2280
                    $this->db->rollback();
2281
                    return -2;
2282
                }
2283
            }
2284
            if ($statut == self::STATUS_BILLED) { // Why this ?
2285
                $trigger_name = 'PROPAL_CLASSIFY_BILLED';
2286
            }
2287
2288
            if (empty($conf->global->MAIN_DISABLE_PDF_AUTOUPDATE)) {
2289
                // Define output language
2290
                $outputlangs = $langs;
2291
                if (!empty($conf->global->MAIN_MULTILANGS)) {
2292
                    $outputlangs = new Translate("", $conf);
2293
                    $newlang = (GETPOST('lang_id', 'aZ09') ? GETPOST('lang_id', 'aZ09') : $this->thirdparty->default_lang);
2294
                    $outputlangs->setDefaultLang($newlang);
2295
                }
2296
                //$ret=$object->fetch($id);    // Reload to get new records
2297
                $this->generateDocument($modelpdf, $outputlangs);
2298
            }
2299
2300
            if (!$error) {
2301
                $this->oldcopy = clone $this;
2302
                $this->statut = $statut;
2303
                $this->date_cloture = $now;
2304
                $this->note_private = $newprivatenote;
2305
            }
2306
2307
            if (!$notrigger && empty($error)) {
2308
                // Call trigger
2309
                $result = $this->call_trigger($trigger_name, $user);
2310
                if ($result < 0) {
2311
                    $error++;
2312
                }
2313
                // End call triggers
2314
            }
2315
2316
            if (!$error) {
2317
                $this->db->commit();
2318
                return 1;
2319
            } else {
2320
                $this->statut = $this->oldcopy->statut;
2321
                $this->date_cloture = $this->oldcopy->date_cloture;
2322
                $this->note_private = $this->oldcopy->note_private;
2323
2324
                $this->db->rollback();
2325
                return -1;
2326
            }
2327
        } else {
2328
            $this->error = $this->db->lasterror();
2329
            $this->db->rollback();
2330
            return -1;
2331
        }
2332
    }
2333
2334
    /**
2335
     * 	Class invoiced the Propal
2336
     *
2337
     * 	@param  	User	$user    	Object user
2338
     *  @param		int		$notrigger	1=Does not execute triggers, 0= execute triggers
2339
     * 	@return     int     			<0 si ko, >0 si ok
2340
     */
2341
    function classifyBilled(User $user, $notrigger = 0)
2342
    {
2343
        $error = 0;
2344
2345
        $this->db->begin();
2346
2347
        $sql = 'UPDATE ' . MAIN_DB_PREFIX . 'propal SET fk_statut = ' . self::STATUS_BILLED;
2348
        $sql .= ' WHERE rowid = ' . $this->id . ' AND fk_statut > ' . self::STATUS_DRAFT;
2349
2350
        dol_syslog(__METHOD__, LOG_DEBUG);
2351
        $resql = $this->db->query($sql);
2352
        if (!$resql) {
2353
            $this->errors[] = $this->db->error();
2354
            $error++;
2355
        }
2356
2357
        if (!$error) {
2358
            $this->oldcopy = clone $this;
2359
            $this->statut = self::STATUS_BILLED;
2360
        }
2361
2362
        if (!$notrigger && empty($error)) {
2363
            // Call trigger
2364
            $result = $this->call_trigger('PROPAL_MODIFY', $user);
2365
            if ($result < 0)
2366
                $error++;
2367
            // End call triggers
2368
        }
2369
2370
        if (!$error) {
2371
            $this->db->commit();
2372
            return 1;
2373
        } else {
2374
            foreach ($this->errors as $errmsg) {
2375
                dol_syslog(__METHOD__ . ' Error: ' . $errmsg, LOG_ERR);
2376
                $this->error .= ($this->error ? ', ' . $errmsg : $errmsg);
2377
            }
2378
            $this->db->rollback();
2379
            return -1 * $error;
2380
        }
2381
    }
2382
2383
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
2384
    /**
2385
     * 	Set draft status
2386
     *
2387
     * 	@param		User	$user		Object user that modify
2388
     *  @param		int		$notrigger	1=Does not execute triggers, 0= execute triggers
2389
     * 	@return		int					<0 if KO, >0 if OK
2390
     */
2391
    function set_draft($user, $notrigger = 0)
2392
    {
2393
        // phpcs:enable
2394
        $error = 0;
2395
2396
        $this->db->begin();
2397
2398
        $sql = "UPDATE " . MAIN_DB_PREFIX . "propal SET fk_statut = " . self::STATUS_DRAFT;
2399
        $sql .= " WHERE rowid = " . $this->id;
2400
2401
        dol_syslog(__METHOD__, LOG_DEBUG);
2402
        $resql = $this->db->query($sql);
2403
        if (!$resql) {
2404
            $this->errors[] = $this->db->error();
2405
            $error++;
2406
        }
2407
2408
        if (!$error) {
2409
            $this->oldcopy = clone $this;
2410
            $this->statut = self::STATUS_DRAFT;
2411
            $this->brouillon = 1;
2412
        }
2413
2414
        if (!$notrigger && empty($error)) {
2415
            // Call trigger
2416
            $result = $this->call_trigger('PROPAL_MODIFY', $user);
2417
            if ($result < 0)
2418
                $error++;
2419
            // End call triggers
2420
        }
2421
2422
        if (!$error) {
2423
            $this->db->commit();
2424
            return 1;
2425
        } else {
2426
            foreach ($this->errors as $errmsg) {
2427
                dol_syslog(__METHOD__ . ' Error: ' . $errmsg, LOG_ERR);
2428
                $this->error .= ($this->error ? ', ' . $errmsg : $errmsg);
2429
            }
2430
            $this->db->rollback();
2431
            return -1 * $error;
2432
        }
2433
    }
2434
2435
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
2436
    /**
2437
     *    Return list of proposal (eventually filtered on user) into an array
2438
     *
2439
     *    @param	int		$shortlist			0=Return array[id]=ref, 1=Return array[](id=>id,ref=>ref,name=>name)
2440
     *    @param	int		$draft				0=not draft, 1=draft
2441
     *    @param	int		$notcurrentuser		0=all user, 1=not current user
2442
     *    @param    int		$socid				Id third pary
2443
     *    @param    int		$limit				For pagination
2444
     *    @param    int		$offset				For pagination
2445
     *    @param    string	$sortfield			Sort criteria
2446
     *    @param    string	$sortorder			Sort order
2447
     *    @return	int		       				-1 if KO, array with result if OK
2448
     */
2449
    function liste_array($shortlist = 0, $draft = 0, $notcurrentuser = 0, $socid = 0, $limit = 0, $offset = 0, $sortfield = 'p.datep', $sortorder = 'DESC')
2450
    {
2451
        // phpcs:enable
2452
        global $user;
2453
2454
        $ga = array();
2455
2456
        $sql = "SELECT s.rowid, s.nom as name, s.client,";
2457
        $sql .= " p.rowid as propalid, p.fk_statut, p.total_ht, p.ref, p.remise, ";
2458
        $sql .= " p.datep as dp, p.fin_validite as datelimite";
2459
        if (!$user->rights->societe->client->voir && !$socid)
2460
            $sql .= ", sc.fk_soc, sc.fk_user";
2461
        $sql .= " FROM " . MAIN_DB_PREFIX . "societe as s, " . MAIN_DB_PREFIX . "propal as p, " . MAIN_DB_PREFIX . "c_propalst as c";
2462
        if (!$user->rights->societe->client->voir && !$socid)
2463
            $sql .= ", " . MAIN_DB_PREFIX . "societe_commerciaux as sc";
2464
        $sql .= " WHERE p.entity IN (" . getEntity('propal') . ")";
2465
        $sql .= " AND p.fk_soc = s.rowid";
2466
        $sql .= " AND p.fk_statut = c.id";
2467
        if (!$user->rights->societe->client->voir && !$socid) { //restriction
2468
            $sql .= " AND s.rowid = sc.fk_soc AND sc.fk_user = " . $user->id;
2469
        }
2470
        if ($socid)
2471
            $sql .= " AND s.rowid = " . $socid;
2472
        if ($draft)
2473
            $sql .= " AND p.fk_statut = " . self::STATUS_DRAFT;
2474
        if ($notcurrentuser > 0)
2475
            $sql .= " AND p.fk_user_author <> " . $user->id;
2476
        $sql .= $this->db->order($sortfield, $sortorder);
2477
        $sql .= $this->db->plimit($limit, $offset);
2478
2479
        $result = $this->db->query($sql);
2480
        if ($result) {
2481
            $num = $this->db->num_rows($result);
2482
            if ($num) {
2483
                $i = 0;
2484
                while ($i < $num) {
2485
                    $obj = $this->db->fetch_object($result);
2486
2487
                    if ($shortlist == 1) {
2488
                        $ga[$obj->propalid] = $obj->ref;
2489
                    } else if ($shortlist == 2) {
2490
                        $ga[$obj->propalid] = $obj->ref . ' (' . $obj->name . ')';
2491
                    } else {
2492
                        $ga[$i]['id'] = $obj->propalid;
2493
                        $ga[$i]['ref'] = $obj->ref;
2494
                        $ga[$i]['name'] = $obj->name;
2495
                    }
2496
2497
                    $i++;
2498
                }
2499
            }
2500
            return $ga;
2501
        } else {
2502
            dol_print_error($this->db);
2503
            return -1;
2504
        }
2505
    }
2506
2507
    /**
2508
     *  Returns an array with the numbers of related invoices
2509
     *
2510
     * 	@return	array		Array of invoices
2511
     */
2512
    function getInvoiceArrayList()
2513
    {
2514
        return $this->InvoiceArrayList($this->id);
2515
    }
2516
2517
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
2518
    /**
2519
     *  Returns an array with id and ref of related invoices
2520
     *
2521
     * 	@param		int		$id			Id propal
2522
     * 	@return		array				Array of invoices id
2523
     */
2524
    function InvoiceArrayList($id)
2525
    {
2526
        // phpcs:enable
2527
        $ga = array();
2528
        $linkedInvoices = array();
2529
2530
        $this->fetchObjectLinked($id, $this->element);
2531
        foreach ($this->linkedObjectsIds as $objecttype => $objectid) {
2532
            // Nouveau système du comon object renvoi des rowid et non un id linéaire de 1 à n
2533
            // On parcourt donc une liste d'objets en tant qu'objet unique
2534
            foreach ($objectid as $key => $object) {
2535
                // Cas des factures liees directement
2536
                if ($objecttype == 'facture') {
2537
                    $linkedInvoices[] = $object;
2538
                }
2539
                // Cas des factures liees par un autre objet (ex: commande)
2540
                else {
2541
                    $this->fetchObjectLinked($object, $objecttype);
2542
                    foreach ($this->linkedObjectsIds as $subobjecttype => $subobjectid) {
2543
                        foreach ($subobjectid as $subkey => $subobject) {
2544
                            if ($subobjecttype == 'facture') {
2545
                                $linkedInvoices[] = $subobject;
2546
                            }
2547
                        }
2548
                    }
2549
                }
2550
            }
2551
        }
2552
2553
        if (count($linkedInvoices) > 0) {
2554
            $sql = "SELECT rowid as facid, ref, total, datef as df, fk_user_author, fk_statut, paye";
2555
            $sql .= " FROM " . MAIN_DB_PREFIX . "facture";
2556
            $sql .= " WHERE rowid IN (" . implode(',', $linkedInvoices) . ")";
2557
2558
            dol_syslog(get_class($this) . "::InvoiceArrayList", LOG_DEBUG);
2559
            $resql = $this->db->query($sql);
2560
2561
            if ($resql) {
2562
                $tab_sqlobj = array();
2563
                $nump = $this->db->num_rows($resql);
2564
                for ($i = 0; $i < $nump; $i++) {
2565
                    $sqlobj = $this->db->fetch_object($resql);
2566
                    $tab_sqlobj[] = $sqlobj;
2567
                }
2568
                $this->db->free($resql);
2569
2570
                $nump = count($tab_sqlobj);
2571
2572
                if ($nump) {
2573
                    $i = 0;
2574
                    while ($i < $nump) {
2575
                        $obj = array_shift($tab_sqlobj);
2576
2577
                        $ga[$i] = $obj;
2578
2579
                        $i++;
2580
                    }
2581
                }
2582
                return $ga;
2583
            } else {
2584
                return -1;
2585
            }
2586
        } else
2587
            return $ga;
2588
    }
2589
2590
    /**
2591
     * 	Delete proposal
2592
     *
2593
     * 	@param	User	$user        	Object user that delete
2594
     * 	@param	int		$notrigger		1=Does not execute triggers, 0= execute triggers
2595
     * 	@return	int						1 if ok, otherwise if error
2596
     */
2597
    function delete($user, $notrigger = 0)
2598
    {
2599
        global $conf;
2600
        require_once DOL_DOCUMENT_ROOT . '/core/lib/files.lib.php';
2601
2602
        $error = 0;
2603
2604
        $this->db->begin();
2605
2606
        if (!$notrigger) {
2607
            // Call trigger
2608
            $result = $this->call_trigger('PROPAL_DELETE', $user);
2609
            if ($result < 0) {
2610
                $error++;
2611
            }
2612
            // End call triggers
2613
        }
2614
2615
        if (!$error) {
2616
            $sql = "DELETE FROM " . MAIN_DB_PREFIX . "propaldet WHERE fk_propal = " . $this->id;
2617
            if ($this->db->query($sql)) {
2618
                $sql = "DELETE FROM " . MAIN_DB_PREFIX . "propal WHERE rowid = " . $this->id;
2619
                if ($this->db->query($sql)) {
2620
                    // Delete linked object
2621
                    $res = $this->deleteObjectLinked();
2622
                    if ($res < 0)
2623
                        $error++;
2624
2625
                    // Delete linked contacts
2626
                    $res = $this->delete_linked_contact();
2627
                    if ($res < 0)
2628
                        $error++;
2629
2630
                    if (!$error) {
2631
                        // We remove directory
2632
                        $ref = dol_sanitizeFileName($this->ref);
2633
                        if ($conf->propal->multidir_output[$this->entity] && !empty($this->ref)) {
2634
                            $dir = $conf->propal->multidir_output[$this->entity] . "/" . $ref;
2635
                            $file = $dir . "/" . $ref . ".pdf";
2636
                            if (file_exists($file)) {
2637
                                dol_delete_preview($this);
2638
2639
                                if (!dol_delete_file($file, 0, 0, 0, $this)) { // For triggers
2640
                                    $this->error = 'ErrorFailToDeleteFile';
2641
                                    $this->errors = array('ErrorFailToDeleteFile');
2642
                                    $this->db->rollback();
2643
                                    return 0;
2644
                                }
2645
                            }
2646
                            if (file_exists($dir)) {
2647
                                $res = @dol_delete_dir_recursive($dir);
2648
                                if (!$res) {
2649
                                    $this->error = 'ErrorFailToDeleteDir';
2650
                                    $this->errors = array('ErrorFailToDeleteDir');
2651
                                    $this->db->rollback();
2652
                                    return 0;
2653
                                }
2654
                            }
2655
                        }
2656
                    }
2657
2658
                    // Removed extrafields
2659
                    if (!$error) {
2660
                        if (empty($conf->global->MAIN_EXTRAFIELDS_DISABLED)) { // For avoid conflicts if trigger used
2661
                            $result = $this->deleteExtraFields();
2662
                            if ($result < 0) {
2663
                                $error++;
2664
                                $errorflag = -4;
2665
                                dol_syslog(get_class($this) . "::delete erreur " . $errorflag . " " . $this->error, LOG_ERR);
2666
                            }
2667
                        }
2668
                    }
2669
2670
                    if (!$error) {
2671
                        dol_syslog(get_class($this) . "::delete " . $this->id . " by " . $user->id, LOG_DEBUG);
2672
                        $this->db->commit();
2673
                        return 1;
2674
                    } else {
2675
                        $this->error = $this->db->lasterror();
2676
                        $this->db->rollback();
2677
                        return 0;
2678
                    }
2679
                } else {
2680
                    $this->error = $this->db->lasterror();
2681
                    $this->db->rollback();
2682
                    return -3;
2683
                }
2684
            } else {
2685
                $this->error = $this->db->lasterror();
2686
                $this->db->rollback();
2687
                return -2;
2688
            }
2689
        } else {
2690
            $this->db->rollback();
2691
            return -1;
2692
        }
2693
    }
2694
2695
    /**
2696
     *  Change the delivery time
2697
     *
2698
     *  @param	int	$availability_id	Id of new delivery time
2699
     * 	@param	int	$notrigger			1=Does not execute triggers, 0= execute triggers
2700
     *  @return int                  	>0 if OK, <0 if KO
2701
     *  @deprecated  use set_availability
2702
     */
2703
    function availability($availability_id, $notrigger = 0)
2704
    {
2705
        global $user;
2706
2707
        if ($this->statut >= self::STATUS_DRAFT) {
2708
            $error = 0;
2709
2710
            $this->db->begin();
2711
2712
            $sql = 'UPDATE ' . MAIN_DB_PREFIX . 'propal';
2713
            $sql .= ' SET fk_availability = ' . $availability_id;
2714
            $sql .= ' WHERE rowid=' . $this->id;
2715
2716
            dol_syslog(__METHOD__ . ' availability(' . $availability_id . ')', LOG_DEBUG);
2717
            $resql = $this->db->query($sql);
2718
            if (!$resql) {
2719
                $this->errors[] = $this->db->error();
2720
                $error++;
2721
            }
2722
2723
            if (!$error) {
2724
                $this->oldcopy = clone $this;
2725
                $this->availability_id = $availability_id;
2726
            }
2727
2728
            if (!$notrigger && empty($error)) {
2729
                // Call trigger
2730
                $result = $this->call_trigger('PROPAL_MODIFY', $user);
2731
                if ($result < 0)
2732
                    $error++;
2733
                // End call triggers
2734
            }
2735
2736
            if (!$error) {
2737
                $this->db->commit();
2738
                return 1;
2739
            } else {
2740
                foreach ($this->errors as $errmsg) {
2741
                    dol_syslog(__METHOD__ . ' Error: ' . $errmsg, LOG_ERR);
2742
                    $this->error .= ($this->error ? ', ' . $errmsg : $errmsg);
2743
                }
2744
                $this->db->rollback();
2745
                return -1 * $error;
2746
            }
2747
        } else {
2748
            $error_str = 'Propal status do not meet requirement ' . $this->statut;
2749
            dol_syslog(__METHOD__ . $error_str, LOG_ERR);
2750
            $this->error = $error_str;
2751
            $this->errors[] = $this->error;
2752
            return -2;
2753
        }
2754
    }
2755
2756
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
2757
    /**
2758
     * 	Change source demand
2759
     *
2760
     * 	@param	int $demand_reason_id 	Id of new source demand
2761
     * 	@param	int	$notrigger			1=Does not execute triggers, 0= execute triggers
2762
     * 	@return int						>0 si ok, <0 si ko
2763
     * 	@deprecated use set_demand_reason
2764
     */
2765
    function demand_reason($demand_reason_id, $notrigger = 0)
2766
    {
2767
        // phpcs:enable
2768
        global $user;
2769
2770
        if ($this->statut >= self::STATUS_DRAFT) {
2771
            $error = 0;
2772
2773
            $this->db->begin();
2774
2775
            $sql = 'UPDATE ' . MAIN_DB_PREFIX . 'propal';
2776
            $sql .= ' SET fk_input_reason = ' . $demand_reason_id;
2777
            $sql .= ' WHERE rowid=' . $this->id;
2778
2779
            dol_syslog(__METHOD__ . ' demand_reason(' . $demand_reason_id . ')', LOG_DEBUG);
2780
            $resql = $this->db->query($sql);
2781
            if (!$resql) {
2782
                $this->errors[] = $this->db->error();
2783
                $error++;
2784
            }
2785
2786
            if (!$error) {
2787
                $this->oldcopy = clone $this;
2788
                $this->demand_reason_id = $demand_reason_id;
2789
            }
2790
2791
            if (!$notrigger && empty($error)) {
2792
                // Call trigger
2793
                $result = $this->call_trigger('PROPAL_MODIFY', $user);
2794
                if ($result < 0)
2795
                    $error++;
2796
                // End call triggers
2797
            }
2798
2799
            if (!$error) {
2800
                $this->db->commit();
2801
                return 1;
2802
            } else {
2803
                foreach ($this->errors as $errmsg) {
2804
                    dol_syslog(__METHOD__ . ' Error: ' . $errmsg, LOG_ERR);
2805
                    $this->error .= ($this->error ? ', ' . $errmsg : $errmsg);
2806
                }
2807
                $this->db->rollback();
2808
                return -1 * $error;
2809
            }
2810
        } else {
2811
            $error_str = 'Propal status do not meet requirement ' . $this->statut;
2812
            dol_syslog(__METHOD__ . $error_str, LOG_ERR);
2813
            $this->error = $error_str;
2814
            $this->errors[] = $this->error;
2815
            return -2;
2816
        }
2817
    }
2818
2819
    /**
2820
     * 	Object Proposal Information
2821
     *
2822
     * 	@param	int		$id		Proposal id
2823
     *  @return	void
2824
     */
2825
    function info($id)
2826
    {
2827
        $sql = "SELECT c.rowid, ";
2828
        $sql .= " c.datec, c.date_valid as datev, c.date_cloture as dateo,";
2829
        $sql .= " c.fk_user_author, c.fk_user_valid, c.fk_user_cloture";
2830
        $sql .= " FROM " . MAIN_DB_PREFIX . "propal as c";
2831
        $sql .= " WHERE c.rowid = " . $id;
2832
2833
        $result = $this->db->query($sql);
2834
2835
        if ($result) {
2836
            if ($this->db->num_rows($result)) {
2837
                $obj = $this->db->fetch_object($result);
2838
2839
                $this->id = $obj->rowid;
2840
2841
                $this->date_creation = $this->db->jdate($obj->datec);
2842
                $this->date_validation = $this->db->jdate($obj->datev);
2843
                $this->date_cloture = $this->db->jdate($obj->dateo);
2844
2845
                $cuser = new User($this->db);
2846
                $cuser->fetch($obj->fk_user_author);
2847
                $this->user_creation = $cuser;
2848
2849
                if ($obj->fk_user_valid) {
2850
                    $vuser = new User($this->db);
2851
                    $vuser->fetch($obj->fk_user_valid);
2852
                    $this->user_validation = $vuser;
2853
                }
2854
2855
                if ($obj->fk_user_cloture) {
2856
                    $cluser = new User($this->db);
2857
                    $cluser->fetch($obj->fk_user_cloture);
2858
                    $this->user_cloture = $cluser;
2859
                }
2860
            }
2861
            $this->db->free($result);
2862
        } else {
2863
            dol_print_error($this->db);
2864
        }
2865
    }
2866
2867
    /**
2868
     *    	Return label of status of proposal (draft, validated, ...)
2869
     *
2870
     *    	@param      int			$mode        0=Long label, 1=Short label, 2=Picto + Short label, 3=Picto, 4=Picto + Long label, 5=Short label + Picto, 6=Long label + Picto
2871
     *    	@return     string		Label
2872
     */
2873
    function getLibStatut($mode = 0)
2874
    {
2875
        return $this->LibStatut($this->statut, $mode);
2876
    }
2877
2878
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
2879
    /**
2880
     *    	Return label of a status (draft, validated, ...)
2881
     *
2882
     *    	@param      int			$statut		id statut
2883
     *    	@param      int			$mode      	0=Long label, 1=Short label, 2=Picto + Short label, 3=Picto, 4=Picto + Long label, 5=Short label + Picto, 6=Long label + Picto
2884
     *    	@return     string		Label
2885
     */
2886
    function LibStatut($statut, $mode = 1)
2887
    {
2888
        // phpcs:enable
2889
        global $conf;
2890
2891
        // Init/load array of translation of status
2892
        if (empty($this->labelstatut) || empty($this->labelstatut_short)) {
2893
            global $langs;
2894
            $langs->load("propal");
2895
            $this->labelstatut[0] = $langs->trans("PropalStatusDraft");
2896
            $this->labelstatut[1] = $langs->trans("PropalStatusValidated");
2897
            $this->labelstatut[2] = $langs->trans("PropalStatusSigned");
2898
            $this->labelstatut[3] = $langs->trans("PropalStatusNotSigned");
2899
            $this->labelstatut[4] = $langs->trans("PropalStatusBilled");
2900
            $this->labelstatut_short[0] = $langs->trans("PropalStatusDraftShort");
2901
            $this->labelstatut_short[1] = $langs->trans("PropalStatusValidatedShort");
2902
            $this->labelstatut_short[2] = $langs->trans("PropalStatusSignedShort");
2903
            $this->labelstatut_short[3] = $langs->trans("PropalStatusNotSignedShort");
2904
            $this->labelstatut_short[4] = $langs->trans("PropalStatusBilledShort");
2905
        }
2906
2907
        $statuttrans = '';
2908
        if ($statut == self::STATUS_DRAFT)
2909
            $statuttrans = 'statut0';
2910
        elseif ($statut == self::STATUS_VALIDATED)
2911
            $statuttrans = 'statut1';
2912
        elseif ($statut == self::STATUS_SIGNED)
2913
            $statuttrans = 'statut3';
2914
        elseif ($statut == self::STATUS_NOTSIGNED)
2915
            $statuttrans = 'statut5';
2916
        elseif ($statut == self::STATUS_BILLED)
2917
            $statuttrans = 'statut6';
2918
2919
        if ($mode == 0)
2920
            return $this->labelstatut[$statut];
2921
        elseif ($mode == 1)
2922
            return $this->labelstatut_short[$statut];
2923
        elseif ($mode == 2)
2924
            return img_picto($this->labelstatut_short[$statut], $statuttrans) . ' ' . $this->labelstatut_short[$statut];
2925
        elseif ($mode == 3)
2926
            return img_picto($this->labelstatut[$statut], $statuttrans);
2927
        elseif ($mode == 4)
2928
            return img_picto($this->labelstatut[$statut], $statuttrans) . ' ' . $this->labelstatut[$statut];
2929
        elseif ($mode == 5)
2930
            return '<span class="hideonsmartphone">' . $this->labelstatut_short[$statut] . ' </span>' . img_picto($this->labelstatut[$statut], $statuttrans);
2931
        elseif ($mode == 6)
2932
            return '<span class="hideonsmartphone">' . $this->labelstatut[$statut] . ' </span>' . img_picto($this->labelstatut[$statut], $statuttrans);
2933
    }
2934
2935
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
2936
    /**
2937
     *      Load indicators for dashboard (this->nbtodo and this->nbtodolate)
2938
     *
2939
     *      @param          User	$user   Object user
2940
     *      @param          int		$mode   "opened" for proposal to close, "signed" for proposal to invoice
2941
     *      @return WorkboardResponse|int <0 if KO, WorkboardResponse if OK
2942
     */
2943
    function load_board($user, $mode)
2944
    {
2945
        // phpcs:enable
2946
        global $conf, $langs;
2947
2948
        $clause = " WHERE";
2949
2950
        $sql = "SELECT p.rowid, p.ref, p.datec as datec, p.fin_validite as datefin, p.total_ht";
2951
        $sql .= " FROM " . MAIN_DB_PREFIX . "propal as p";
2952
        if (!$user->rights->societe->client->voir && !$user->societe_id) {
2953
            $sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "societe_commerciaux as sc ON p.fk_soc = sc.fk_soc";
2954
            $sql .= " WHERE sc.fk_user = " . $user->id;
2955
            $clause = " AND";
2956
        }
2957
        $sql .= $clause . " p.entity IN (" . getEntity('propal') . ")";
2958
        if ($mode == 'opened')
2959
            $sql .= " AND p.fk_statut = " . self::STATUS_VALIDATED;
2960
        if ($mode == 'signed')
2961
            $sql .= " AND p.fk_statut = " . self::STATUS_SIGNED;
2962
        if ($user->societe_id)
2963
            $sql .= " AND p.fk_soc = " . $user->societe_id;
2964
2965
        $resql = $this->db->query($sql);
2966
        if ($resql) {
2967
            $langs->load("propal");
2968
            $now = dol_now();
2969
2970
            $delay_warning = 0;
2971
            $statut = 0;
2972
            $label = '';
2973
            if ($mode == 'opened') {
2974
                $delay_warning = $conf->propal->cloture->warning_delay;
2975
                $statut = self::STATUS_VALIDATED;
2976
                $label = $langs->trans("PropalsToClose");
2977
            }
2978
            if ($mode == 'signed') {
2979
                $delay_warning = $conf->propal->facturation->warning_delay;
2980
                $statut = self::STATUS_SIGNED;
2981
                $label = $langs->trans("PropalsToBill");         // We set here bill but may be billed or ordered
2982
            }
2983
2984
            $response = new WorkboardResponse();
2985
            $response->warning_delay = $delay_warning / 60 / 60 / 24;
2986
            $response->label = $label;
2987
            $response->url = DOL_URL_ROOT . '/comm/propal/list.php?viewstatut=' . $statut . '&mainmenu=commercial&leftmenu=propals';
2988
            $response->url_late = DOL_URL_ROOT . '/comm/propal/list.php?viewstatut=' . $statut . '&mainmenu=commercial&leftmenu=propals&sortfield=p.datep&sortorder=asc';
2989
            $response->img = img_object('', "propal");
2990
2991
            // This assignment in condition is not a bug. It allows walking the results.
2992
            while ($obj = $this->db->fetch_object($resql)) {
2993
                $response->nbtodo++;
2994
                $response->total += $obj->total_ht;
2995
2996
                if ($mode == 'opened') {
2997
                    $datelimit = $this->db->jdate($obj->datefin);
2998
                    if ($datelimit < ($now - $delay_warning)) {
2999
                        $response->nbtodolate++;
3000
                    }
3001
                }
3002
                // TODO Definir regle des propales a facturer en retard
3003
                // if ($mode == 'signed' && ! count($this->FactureListeArray($obj->rowid))) $this->nbtodolate++;
3004
            }
3005
3006
            return $response;
3007
        } else {
3008
            $this->error = $this->db->error();
3009
            return -1;
3010
        }
3011
    }
3012
3013
    /**
3014
     *  Initialise an instance with random values.
3015
     *  Used to build previews or test instances.
3016
     * 	id must be 0 if object instance is a specimen.
3017
     *
3018
     *  @return	void
3019
     */
3020
    function initAsSpecimen()
3021
    {
3022
        global $langs;
3023
3024
        // Load array of products prodids
3025
        $num_prods = 0;
3026
        $prodids = array();
3027
        $sql = "SELECT rowid";
3028
        $sql .= " FROM " . MAIN_DB_PREFIX . "product";
3029
        $sql .= " WHERE entity IN (" . getEntity('product') . ")";
3030
        $resql = $this->db->query($sql);
3031
        if ($resql) {
3032
            $num_prods = $this->db->num_rows($resql);
3033
            $i = 0;
3034
            while ($i < $num_prods) {
3035
                $i++;
3036
                $row = $this->db->fetch_row($resql);
3037
                $prodids[$i] = $row[0];
3038
            }
3039
        }
3040
3041
        // Initialise parametres
3042
        $this->id = 0;
3043
        $this->ref = 'SPECIMEN';
3044
        $this->ref_client = 'NEMICEPS';
3045
        $this->specimen = 1;
3046
        $this->socid = 1;
3047
        $this->date = time();
3048
        $this->fin_validite = $this->date + 3600 * 24 * 30;
3049
        $this->cond_reglement_id = 1;
3050
        $this->cond_reglement_code = 'RECEP';
3051
        $this->mode_reglement_id = 7;
3052
        $this->mode_reglement_code = 'CHQ';
3053
        $this->availability_id = 1;
3054
        $this->availability_code = 'AV_NOW';
3055
        $this->demand_reason_id = 1;
3056
        $this->demand_reason_code = 'SRC_00';
3057
        $this->note_public = 'This is a comment (public)';
3058
        $this->note_private = 'This is a comment (private)';
3059
        // Lines
3060
        $nbp = 5;
3061
        $xnbp = 0;
3062
        while ($xnbp < $nbp) {
3063
            $line = new PropaleLigne($this->db);
3064
            $line->desc = $langs->trans("Description") . " " . $xnbp;
3065
            $line->qty = 1;
3066
            $line->subprice = 100;
3067
            $line->price = 100;
3068
            $line->tva_tx = 20;
3069
            $line->localtax1_tx = 0;
3070
            $line->localtax2_tx = 0;
3071
            if ($xnbp == 2) {
3072
                $line->total_ht = 50;
3073
                $line->total_ttc = 60;
3074
                $line->total_tva = 10;
3075
                $line->remise_percent = 50;
3076
            } else {
3077
                $line->total_ht = 100;
3078
                $line->total_ttc = 120;
3079
                $line->total_tva = 20;
3080
                $line->remise_percent = 00;
0 ignored issues
show
Bug introduced by
A parse error occurred: The alleged octal '0' is invalid
Loading history...
3081
            }
3082
3083
            if ($num_prods > 0) {
3084
                $prodid = mt_rand(1, $num_prods);
3085
                $line->fk_product = $prodids[$prodid];
3086
                $line->product_ref = 'SPECIMEN';
3087
            }
3088
3089
            $this->lines[$xnbp] = $line;
3090
3091
            $this->total_ht += $line->total_ht;
3092
            $this->total_tva += $line->total_tva;
3093
            $this->total_ttc += $line->total_ttc;
3094
3095
            $xnbp++;
3096
        }
3097
    }
3098
3099
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
3100
    /**
3101
     *      Charge indicateurs this->nb de tableau de bord
3102
     *
3103
     *      @return     int         <0 if ko, >0 if ok
3104
     */
3105
    function load_state_board()
3106
    {
3107
        // phpcs:enable
3108
        global $user;
3109
3110
        $this->nb = array();
3111
        $clause = "WHERE";
3112
3113
        $sql = "SELECT count(p.rowid) as nb";
3114
        $sql .= " FROM " . MAIN_DB_PREFIX . "propal as p";
3115
        $sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "societe as s ON p.fk_soc = s.rowid";
3116
        if (!$user->rights->societe->client->voir && !$user->societe_id) {
3117
            $sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "societe_commerciaux as sc ON s.rowid = sc.fk_soc";
3118
            $sql .= " WHERE sc.fk_user = " . $user->id;
3119
            $clause = "AND";
3120
        }
3121
        $sql .= " " . $clause . " p.entity IN (" . getEntity('propal') . ")";
3122
3123
        $resql = $this->db->query($sql);
3124
        if ($resql) {
3125
            // This assignment in condition is not a bug. It allows walking the results.
3126
            while ($obj = $this->db->fetch_object($resql)) {
3127
                $this->nb["proposals"] = $obj->nb;
3128
            }
3129
            $this->db->free($resql);
3130
            return 1;
3131
        } else {
3132
            dol_print_error($this->db);
3133
            $this->error = $this->db->error();
3134
            return -1;
3135
        }
3136
    }
3137
3138
    /**
3139
     *  Returns the reference to the following non used Proposal used depending on the active numbering module
3140
     *  defined into PROPALE_ADDON
3141
     *
3142
     *  @param	Societe		$soc  	Object thirdparty
3143
     *  @return string      		Reference libre pour la propale
3144
     */
3145
    function getNextNumRef($soc)
3146
    {
3147
        global $conf, $langs;
3148
        $langs->load("propal");
3149
3150
        $classname = $conf->global->PROPALE_ADDON;
3151
3152
        if (!empty($classname)) {
3153
            $mybool = false;
3154
3155
            $file = $classname . ".php";
3156
3157
            // Include file with class
3158
            $dirmodels = array_merge(array('/'), (array) $conf->modules_parts['models']);
3159
            foreach ($dirmodels as $reldir) {
3160
3161
                $dir = dol_buildpath($reldir . "core/modules/propale/");
3162
3163
                // Load file with numbering class (if found)
3164
                $mybool |= @include_once $dir . $file;
3165
            }
3166
3167
            if (!$mybool) {
3168
                dol_print_error('', "Failed to include file " . $file);
3169
                return '';
3170
            }
3171
3172
            $obj = new $classname();
3173
            $numref = "";
3174
            $numref = $obj->getNextValue($soc, $this);
3175
3176
            if ($numref != "") {
3177
                return $numref;
3178
            } else {
3179
                $this->error = $obj->error;
3180
                //dol_print_error($db,"Propale::getNextNumRef ".$obj->error);
3181
                return "";
3182
            }
3183
        } else {
3184
            $langs->load("errors");
3185
            print $langs->trans("Error") . " " . $langs->trans("ErrorModuleSetupNotComplete");
3186
            return "";
3187
        }
3188
    }
3189
3190
    /**
3191
     * 	Return clicable link of object (with eventually picto)
3192
     *
3193
     * 	@param      int		$withpicto		          Add picto into link
3194
     * 	@param      string	$option			          Where point the link ('expedition', 'document', ...)
3195
     * 	@param      string	$get_params    	          Parametres added to url
3196
     *  @param	    int   	$notooltip		          1=Disable tooltip
3197
     *  @param      int     $save_lastsearch_value    -1=Auto, 0=No save of lastsearch_values when clicking, 1=Save lastsearch_values whenclicking
3198
     * 	@return     string          		          String with URL
3199
     */
3200
    function getNomUrl($withpicto = 0, $option = '', $get_params = '', $notooltip = 0, $save_lastsearch_value = -1)
3201
    {
3202
        global $langs, $conf, $user;
3203
3204
        if (!empty($conf->dol_no_mouse_hover)) {
3205
            $notooltip = 1;   // Force disable tooltips
3206
        }
3207
3208
        $result = '';
3209
        $label = '';
3210
        $url = '';
3211
3212
        if ($user->rights->propal->lire) {
3213
            $label = '<u>' . $langs->trans("ShowPropal") . '</u>';
3214
            if (!empty($this->ref)) {
3215
                $label .= '<br><b>' . $langs->trans('Ref') . ':</b> ' . $this->ref;
3216
            }
3217
            if (!empty($this->ref_client)) {
3218
                $label .= '<br><b>' . $langs->trans('RefCustomer') . ':</b> ' . $this->ref_client;
3219
            }
3220
            if (!empty($this->total_ht)) {
3221
                $label .= '<br><b>' . $langs->trans('AmountHT') . ':</b> ' . price($this->total_ht, 0, $langs, 0, -1, -1, $conf->currency);
3222
            }
3223
            if (!empty($this->total_tva)) {
3224
                $label .= '<br><b>' . $langs->trans('VAT') . ':</b> ' . price($this->total_tva, 0, $langs, 0, -1, -1, $conf->currency);
3225
            }
3226
            if (!empty($this->total_ttc)) {
3227
                $label .= '<br><b>' . $langs->trans('AmountTTC') . ':</b> ' . price($this->total_ttc, 0, $langs, 0, -1, -1, $conf->currency);
3228
            }
3229
            if ($option == '') {
3230
                // $url = DOL_URL_ROOT . '/comm/propal/card.php?id=' . $this->id . $get_params;
3231
                $url = BASE_URI . '?controller=comm/propal&method=card&id=' . $this->id . $get_params;
3232
            }
3233
            if ($option == 'compta') {  // deprecated
3234
                // $url = DOL_URL_ROOT . '/comm/propal/card.php?id=' . $this->id . $get_params;
3235
                $url = BASE_URI . '?controller=comm/propal&method=card&id=' . $this->id . $get_params;
3236
            }
3237
            if ($option == 'expedition') {
3238
                // $url = DOL_URL_ROOT . '/expedition/propal.php?id=' . $this->id . $get_params;
3239
                $url = BASE_URI . '?controller=expedition&method=propal&id=' . $this->id . $get_params;
3240
            }
3241
            if ($option == 'document') {
3242
                // $url = DOL_URL_ROOT . '/comm/propal/document.php?id=' . $this->id . $get_params;
3243
                $url = BASE_URI . '?controller=comm/propal&method=document&id=' . $this->id . $get_params;
3244
            }
3245
3246
            if ($option != 'nolink') {
3247
                // Add param to save lastsearch_values or not
3248
                $add_save_lastsearch_values = ($save_lastsearch_value == 1 ? 1 : 0);
3249
                if ($save_lastsearch_value == -1 && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) {
3250
                    $add_save_lastsearch_values = 1;
3251
                }
3252
                if ($add_save_lastsearch_values) {
3253
                    $url .= '&save_lastsearch_values=1';
3254
                }
3255
            }
3256
        }
3257
3258
        $linkclose = '';
3259
        if (empty($notooltip) && $user->rights->propal->lire) {
3260
            if (!empty($conf->global->MAIN_OPTIMIZEFORTEXTBROWSER)) {
3261
                $label = $langs->trans("ShowPropal");
3262
                $linkclose .= ' alt="' . dol_escape_htmltag($label, 1) . '"';
3263
            }
3264
            $linkclose .= ' title="' . dol_escape_htmltag($label, 1) . '"';
3265
            $linkclose .= ' class="classfortooltip"';
3266
        }
3267
3268
        $linkstart = '<a href="' . $url . '"';
3269
        $linkstart .= $linkclose . '>';
3270
        $linkend = '</a>';
3271
3272
        $result .= $linkstart;
3273
        if ($withpicto)
3274
            $result .= img_object(($notooltip ? '' : $label), $this->picto, ($notooltip ? (($withpicto != 2) ? 'class="paddingright"' : '') : 'class="' . (($withpicto != 2) ? 'paddingright ' : '') . 'classfortooltip"'), 0, 0, $notooltip ? 0 : 1);
3275
        if ($withpicto != 2)
3276
            $result .= $this->ref;
3277
        $result .= $linkend;
3278
3279
        return $result;
3280
    }
3281
3282
    /**
3283
     * 	Retrieve an array of proposal lines
3284
     *
3285
     * 	@return int		>0 if OK, <0 if KO
3286
     */
3287
    function getLinesArray()
3288
    {
3289
        return $this->fetch_lines();
3290
    }
3291
3292
    /**
3293
     *  Create a document onto disk according to template module.
3294
     *
3295
     * 	@param	    string		$modele			Force model to use ('' to not force)
3296
     * 	@param		Translate	$outputlangs	Object langs to use for output
3297
     *  @param      int			$hidedetails    Hide details of lines
3298
     *  @param      int			$hidedesc       Hide description
3299
     *  @param      int			$hideref        Hide ref
3300
     *  @param   null|array  $moreparams     Array to provide more information
3301
     * 	@return     int         				0 if KO, 1 if OK
3302
     */
3303
    public function generateDocument($modele, $outputlangs, $hidedetails = 0, $hidedesc = 0, $hideref = 0, $moreparams = null)
3304
    {
3305
        global $conf, $langs;
3306
3307
        $langs->load("propale");
3308
3309
        if (!dol_strlen($modele)) {
3310
3311
            $modele = 'azur';
3312
3313
            if ($this->modelpdf) {
3314
                $modele = $this->modelpdf;
3315
            } elseif (!empty($conf->global->PROPALE_ADDON_PDF)) {
3316
                $modele = $conf->global->PROPALE_ADDON_PDF;
3317
            }
3318
        }
3319
3320
        $modelpath = "core/modules/propale/doc/";
3321
3322
        return $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams);
3323
    }
3324
3325
    /**
3326
     * Function used to replace a thirdparty id with another one.
3327
     *
3328
     * @param DoliDB $db Database handler
3329
     * @param int $origin_id Old thirdparty id
3330
     * @param int $dest_id New thirdparty id
3331
     * @return bool
3332
     */
3333
    public static function replaceThirdparty(DoliDB $db, $origin_id, $dest_id)
3334
    {
3335
        $tables = array(
3336
            'propal'
3337
        );
3338
3339
        return CommonObject::commonReplaceThirdparty($db, $origin_id, $dest_id, $tables);
3340
    }
3341
}
3342
3343
/**
3344
 * 	Class to manage commercial proposal lines
3345
 */
3346
class PropaleLigne extends CommonObjectLine
3347
{
3348
3349
    /**
3350
     * @var string ID to identify managed object
3351
     */
3352
    public $element = 'propaldet';
3353
3354
    /**
3355
     * @var string Name of table without prefix where object is stored
3356
     */
3357
    public $table_element = 'propaldet';
3358
    var $oldline;
3359
    // From llx_propaldet
3360
    var $fk_propal;
3361
    var $fk_parent_line;
3362
    var $desc;           // Description ligne
3363
    var $fk_product;  // Id produit predefini
3364
    /**
3365
     * @deprecated
3366
     * @see product_type
3367
     */
3368
    var $fk_product_type;
3369
3370
    /**
3371
     * Product type.
3372
     * @var int
3373
     * @see Product::TYPE_PRODUCT, Product::TYPE_SERVICE
3374
     */
3375
    var $product_type = Product::TYPE_PRODUCT;
3376
    var $qty;
3377
    var $tva_tx;
3378
    var $subprice;
3379
    var $remise_percent;
3380
    var $fk_remise_except;
3381
    var $rang = 0;
3382
    var $fk_fournprice;
3383
    var $pa_ht;
3384
    var $marge_tx;
3385
    var $marque_tx;
3386
    var $special_code; // Tag for special lines (exlusive tags)
3387
    // 1: frais de port
3388
    // 2: ecotaxe
3389
    // 3: option line (when qty = 0)
3390
    var $info_bits = 0; // Liste d'options cumulables:
3391
    // Bit 0: 	0 si TVA normal - 1 si TVA NPR
3392
    // Bit 1:	0 ligne normale - 1 si ligne de remise fixe
3393
    var $total_ht;   // Total HT  de la ligne toute quantite et incluant la remise ligne
3394
    var $total_tva;   // Total TVA  de la ligne toute quantite et incluant la remise ligne
3395
    var $total_ttc;   // Total TTC de la ligne toute quantite et incluant la remise ligne
3396
3397
    /**
3398
     * @deprecated
3399
     * @see $remise_percent, $fk_remise_except
3400
     */
3401
    var $remise;
3402
3403
    /**
3404
     * @deprecated
3405
     * @see subprice
3406
     */
3407
    var $price;
3408
    // From llx_product
3409
    /**
3410
     * @deprecated
3411
     * @see product_ref
3412
     */
3413
    var $ref;
3414
3415
    /**
3416
     * Product reference
3417
     * @var string
3418
     */
3419
    public $product_ref;
3420
3421
    /**
3422
     * @deprecated
3423
     * @see product_label
3424
     */
3425
    var $libelle;
3426
3427
    /**
3428
     *  Product label
3429
     * @var string
3430
     */
3431
    public $product_label;
3432
3433
    /**
3434
     * Product description
3435
     * @var string
3436
     */
3437
    public $product_desc;
3438
    var $localtax1_tx;  // Local tax 1
3439
    var $localtax2_tx;  // Local tax 2
3440
    var $localtax1_type; // Local tax 1 type
3441
    var $localtax2_type; // Local tax 2 type
3442
    var $total_localtax1;   // Line total local tax 1
3443
    var $total_localtax2; // Line total local tax 2
3444
    var $date_start;
3445
    var $date_end;
3446
    var $skip_update_total; // Skip update price total for special lines
3447
    // Multicurrency
3448
    var $fk_multicurrency;
3449
    var $multicurrency_code;
3450
    var $multicurrency_subprice;
3451
    var $multicurrency_total_ht;
3452
    var $multicurrency_total_tva;
3453
    var $multicurrency_total_ttc;
3454
3455
    /**
3456
     * 	Class line Contructor
3457
     *
3458
     * 	@param	DoliDB	$db	Database handler
3459
     */
3460
    function __construct($db)
3461
    {
3462
        $this->db = $db;
3463
    }
3464
3465
    /**
3466
     * 	Retrieve the propal line object
3467
     *
3468
     * 	@param	int		$rowid		Propal line id
3469
     * 	@return	int					<0 if KO, >0 if OK
3470
     */
3471
    function fetch($rowid)
3472
    {
3473
        $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,';
3474
        $sql .= ' pd.remise, pd.remise_percent, pd.fk_remise_except, pd.subprice,';
3475
        $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,';
3476
        $sql .= ' pd.fk_unit,';
3477
        $sql .= ' pd.localtax1_tx, pd.localtax2_tx, pd.total_localtax1, pd.total_localtax2,';
3478
        $sql .= ' pd.fk_multicurrency, pd.multicurrency_code, pd.multicurrency_subprice, pd.multicurrency_total_ht, pd.multicurrency_total_tva, pd.multicurrency_total_ttc,';
3479
        $sql .= ' p.ref as product_ref, p.label as product_label, p.description as product_desc,';
3480
        $sql .= ' pd.date_start, pd.date_end, pd.product_type';
3481
        $sql .= ' FROM ' . MAIN_DB_PREFIX . 'propaldet as pd';
3482
        $sql .= ' LEFT JOIN ' . MAIN_DB_PREFIX . 'product as p ON pd.fk_product = p.rowid';
3483
        $sql .= ' WHERE pd.rowid = ' . $rowid;
3484
3485
        $result = $this->db->query($sql);
3486
        if ($result) {
3487
            $objp = $this->db->fetch_object($result);
3488
3489
            if ($objp) {
3490
                $this->id = $objp->rowid;
3491
                $this->rowid = $objp->rowid;     // deprecated
3492
                $this->fk_propal = $objp->fk_propal;
3493
                $this->fk_parent_line = $objp->fk_parent_line;
3494
                $this->label = $objp->custom_label;
3495
                $this->desc = $objp->description;
3496
                $this->qty = $objp->qty;
3497
                $this->price = $objp->price;  // deprecated
3498
                $this->subprice = $objp->subprice;
3499
                $this->vat_src_code = $objp->vat_src_code;
3500
                $this->tva_tx = $objp->tva_tx;
3501
                $this->remise = $objp->remise;    // deprecated
3502
                $this->remise_percent = $objp->remise_percent;
3503
                $this->fk_remise_except = $objp->fk_remise_except;
3504
                $this->fk_product = $objp->fk_product;
3505
                $this->info_bits = $objp->info_bits;
3506
3507
                $this->total_ht = $objp->total_ht;
3508
                $this->total_tva = $objp->total_tva;
3509
                $this->total_ttc = $objp->total_ttc;
3510
3511
                $this->fk_fournprice = $objp->fk_fournprice;
3512
3513
                $marginInfos = getMarginInfos($objp->subprice, $objp->remise_percent, $objp->tva_tx, $objp->localtax1_tx, $objp->localtax2_tx, $this->fk_fournprice, $objp->pa_ht);
3514
                $this->pa_ht = $marginInfos[0];
3515
                $this->marge_tx = $marginInfos[1];
3516
                $this->marque_tx = $marginInfos[2];
3517
3518
                $this->special_code = $objp->special_code;
3519
                $this->product_type = $objp->product_type;
3520
                $this->rang = $objp->rang;
3521
3522
                $this->ref = $objp->product_ref;      // deprecated
3523
                $this->product_ref = $objp->product_ref;
3524
                $this->libelle = $objp->product_label;  // deprecated
3525
                $this->product_label = $objp->product_label;
3526
                $this->product_desc = $objp->product_desc;
3527
                $this->fk_unit = $objp->fk_unit;
3528
3529
                $this->date_start = $this->db->jdate($objp->date_start);
3530
                $this->date_end = $this->db->jdate($objp->date_end);
3531
3532
                // Multicurrency
3533
                $this->fk_multicurrency = $objp->fk_multicurrency;
3534
                $this->multicurrency_code = $objp->multicurrency_code;
3535
                $this->multicurrency_subprice = $objp->multicurrency_subprice;
3536
                $this->multicurrency_total_ht = $objp->multicurrency_total_ht;
3537
                $this->multicurrency_total_tva = $objp->multicurrency_total_tva;
3538
                $this->multicurrency_total_ttc = $objp->multicurrency_total_ttc;
3539
3540
                $this->fetch_optionals();
3541
3542
                $this->db->free($result);
3543
3544
                return 1;
3545
            } else {
3546
                return 0;
3547
            }
3548
        } else {
3549
            return -1;
3550
        }
3551
    }
3552
3553
    /**
3554
     *  Insert object line propal in database
3555
     *
3556
     * 	@param		int		$notrigger		1=Does not execute triggers, 0= execute triggers
3557
     * 	@return		int						<0 if KO, >0 if OK
3558
     */
3559
    function insert($notrigger = 0)
3560
    {
3561
        global $conf, $user;
3562
3563
        $error = 0;
3564
3565
        dol_syslog(get_class($this) . "::insert rang=" . $this->rang);
3566
3567
        $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'.
3568
        // Clean parameters
3569
        if (empty($this->tva_tx))
3570
            $this->tva_tx = 0;
3571
        if (empty($this->localtax1_tx))
3572
            $this->localtax1_tx = 0;
3573
        if (empty($this->localtax2_tx))
3574
            $this->localtax2_tx = 0;
3575
        if (empty($this->localtax1_type))
3576
            $this->localtax1_type = 0;
3577
        if (empty($this->localtax2_type))
3578
            $this->localtax2_type = 0;
3579
        if (empty($this->total_localtax1))
3580
            $this->total_localtax1 = 0;
3581
        if (empty($this->total_localtax2))
3582
            $this->total_localtax2 = 0;
3583
        if (empty($this->rang))
3584
            $this->rang = 0;
3585
        if (empty($this->remise))
3586
            $this->remise = 0;
3587
        if (empty($this->remise_percent) || !is_numeric($this->remise_percent))
3588
            $this->remise_percent = 0;
3589
        if (empty($this->info_bits))
3590
            $this->info_bits = 0;
3591
        if (empty($this->special_code))
3592
            $this->special_code = 0;
3593
        if (empty($this->fk_parent_line))
3594
            $this->fk_parent_line = 0;
3595
        if (empty($this->fk_fournprice))
3596
            $this->fk_fournprice = 0;
3597
        if (!is_numeric($this->qty))
3598
            $this->qty = 0;
3599
        if (empty($this->pa_ht))
3600
            $this->pa_ht = 0;
3601
        if (empty($this->multicurrency_subprice))
3602
            $this->multicurrency_subprice = 0;
3603
        if (empty($this->multicurrency_total_ht))
3604
            $this->multicurrency_total_ht = 0;
3605
        if (empty($this->multicurrency_total_tva))
3606
            $this->multicurrency_total_tva = 0;
3607
        if (empty($this->multicurrency_total_ttc))
3608
            $this->multicurrency_total_ttc = 0;
3609
3610
        // if buy price not defined, define buyprice as configured in margin admin
3611
        if ($this->pa_ht == 0 && $pa_ht_isemptystring) {
3612
            if (($result = $this->defineBuyPrice($this->subprice, $this->remise_percent, $this->fk_product)) < 0) {
3613
                return $result;
3614
            } else {
3615
                $this->pa_ht = $result;
3616
            }
3617
        }
3618
3619
        // Check parameters
3620
        if ($this->product_type < 0)
3621
            return -1;
3622
3623
        $this->db->begin();
3624
3625
        // Insert line into database
3626
        $sql = 'INSERT INTO ' . MAIN_DB_PREFIX . 'propaldet';
3627
        $sql .= ' (fk_propal, fk_parent_line, label, description, fk_product, product_type,';
3628
        $sql .= ' fk_remise_except, qty, vat_src_code, tva_tx, localtax1_tx, localtax2_tx, localtax1_type, localtax2_type,';
3629
        $sql .= ' subprice, remise_percent, ';
3630
        $sql .= ' info_bits, ';
3631
        $sql .= ' total_ht, total_tva, total_localtax1, total_localtax2, total_ttc, fk_product_fournisseur_price, buy_price_ht, special_code, rang,';
3632
        $sql .= ' fk_unit,';
3633
        $sql .= ' date_start, date_end';
3634
        $sql .= ', fk_multicurrency, multicurrency_code, multicurrency_subprice, multicurrency_total_ht, multicurrency_total_tva, multicurrency_total_ttc)';
3635
        $sql .= " VALUES (" . $this->fk_propal . ",";
3636
        $sql .= " " . ($this->fk_parent_line > 0 ? "'" . $this->db->escape($this->fk_parent_line) . "'" : "null") . ",";
3637
        $sql .= " " . (!empty($this->label) ? "'" . $this->db->escape($this->label) . "'" : "null") . ",";
3638
        $sql .= " '" . $this->db->escape($this->desc) . "',";
3639
        $sql .= " " . ($this->fk_product ? "'" . $this->db->escape($this->fk_product) . "'" : "null") . ",";
3640
        $sql .= " '" . $this->db->escape($this->product_type) . "',";
3641
        $sql .= " " . ($this->fk_remise_except ? "'" . $this->db->escape($this->fk_remise_except) . "'" : "null") . ",";
3642
        $sql .= " " . price2num($this->qty) . ",";
3643
        $sql .= " " . (empty($this->vat_src_code) ? "''" : "'" . $this->db->escape($this->vat_src_code) . "'") . ",";
3644
        $sql .= " " . price2num($this->tva_tx) . ",";
3645
        $sql .= " " . price2num($this->localtax1_tx) . ",";
3646
        $sql .= " " . price2num($this->localtax2_tx) . ",";
3647
        $sql .= " '" . $this->db->escape($this->localtax1_type) . "',";
3648
        $sql .= " '" . $this->db->escape($this->localtax2_type) . "',";
3649
        $sql .= " " . (price2num($this->subprice) !== '' ? price2num($this->subprice) : "null") . ",";
3650
        $sql .= " " . price2num($this->remise_percent) . ",";
3651
        $sql .= " " . (isset($this->info_bits) ? "'" . $this->db->escape($this->info_bits) . "'" : "null") . ",";
3652
        $sql .= " " . price2num($this->total_ht) . ",";
3653
        $sql .= " " . price2num($this->total_tva) . ",";
3654
        $sql .= " " . price2num($this->total_localtax1) . ",";
3655
        $sql .= " " . price2num($this->total_localtax2) . ",";
3656
        $sql .= " " . price2num($this->total_ttc) . ",";
3657
        $sql .= " " . (!empty($this->fk_fournprice) ? "'" . $this->db->escape($this->fk_fournprice) . "'" : "null") . ",";
3658
        $sql .= " " . (isset($this->pa_ht) ? "'" . price2num($this->pa_ht) . "'" : "null") . ",";
3659
        $sql .= ' ' . $this->special_code . ',';
3660
        $sql .= ' ' . $this->rang . ',';
3661
        $sql .= ' ' . (!$this->fk_unit ? 'NULL' : $this->fk_unit) . ',';
3662
        $sql .= " " . (!empty($this->date_start) ? "'" . $this->db->idate($this->date_start) . "'" : "null") . ',';
3663
        $sql .= " " . (!empty($this->date_end) ? "'" . $this->db->idate($this->date_end) . "'" : "null");
3664
        $sql .= ", " . ($this->fk_multicurrency > 0 ? $this->fk_multicurrency : 'null');
3665
        $sql .= ", '" . $this->db->escape($this->multicurrency_code) . "'";
3666
        $sql .= ", " . $this->multicurrency_subprice;
3667
        $sql .= ", " . $this->multicurrency_total_ht;
3668
        $sql .= ", " . $this->multicurrency_total_tva;
3669
        $sql .= ", " . $this->multicurrency_total_ttc;
3670
        $sql .= ')';
3671
3672
        dol_syslog(get_class($this) . '::insert', LOG_DEBUG);
3673
        $resql = $this->db->query($sql);
3674
        if ($resql) {
3675
            $this->rowid = $this->db->last_insert_id(MAIN_DB_PREFIX . 'propaldet');
3676
3677
            if (empty($conf->global->MAIN_EXTRAFIELDS_DISABLED)) { // For avoid conflicts if trigger used
3678
                $this->id = $this->rowid;
3679
                $result = $this->insertExtraFields();
3680
                if ($result < 0) {
3681
                    $error++;
3682
                }
3683
            }
3684
3685
            if (!$error && !$notrigger) {
3686
                // Call trigger
3687
                $result = $this->call_trigger('LINEPROPAL_INSERT', $user);
3688
                if ($result < 0) {
3689
                    $this->db->rollback();
3690
                    return -1;
3691
                }
3692
                // End call triggers
3693
            }
3694
3695
            $this->db->commit();
3696
            return 1;
3697
        } else {
3698
            $this->error = $this->db->error() . " sql=" . $sql;
3699
            $this->db->rollback();
3700
            return -1;
3701
        }
3702
    }
3703
3704
    /**
3705
     * 	Delete line in database
3706
     *
3707
     *  @param	User	$user		Object user
3708
     * 	@param 	int		$notrigger	1=Does not execute triggers, 0= execute triggers
3709
     * 	@return	 int  				<0 if ko, >0 if ok
3710
     */
3711
    function delete(User $user, $notrigger = 0)
3712
    {
3713
        global $conf;
3714
3715
        $error = 0;
3716
        $this->db->begin();
3717
3718
        $sql = "DELETE FROM " . MAIN_DB_PREFIX . "propaldet WHERE rowid = " . $this->rowid;
3719
        dol_syslog("PropaleLigne::delete", LOG_DEBUG);
3720
        if ($this->db->query($sql)) {
3721
3722
            // Remove extrafields
3723
            if ((!$error) && (empty($conf->global->MAIN_EXTRAFIELDS_DISABLED))) { // For avoid conflicts if trigger used
3724
                $this->id = $this->rowid;
3725
                $result = $this->deleteExtraFields();
3726
                if ($result < 0) {
3727
                    $error++;
3728
                    dol_syslog(get_class($this) . "::delete error -4 " . $this->error, LOG_ERR);
3729
                }
3730
            }
3731
3732
            if (!$error && !$notrigger) {
3733
                // Call trigger
3734
                $result = $this->call_trigger('LINEPROPAL_DELETE', $user);
3735
                if ($result < 0) {
3736
                    $this->db->rollback();
3737
                    return -1;
3738
                }
3739
            }
3740
            // End call triggers
3741
3742
            $this->db->commit();
3743
3744
            return 1;
3745
        } else {
3746
            $this->error = $this->db->error() . " sql=" . $sql;
3747
            $this->db->rollback();
3748
            return -1;
3749
        }
3750
    }
3751
3752
    /**
3753
     * 	Update propal line object into DB
3754
     *
3755
     * 	@param 	int		$notrigger	1=Does not execute triggers, 0= execute triggers
3756
     * 	@return	int					<0 if ko, >0 if ok
3757
     */
3758
    function update($notrigger = 0)
3759
    {
3760
        global $conf, $user;
3761
3762
        $error = 0;
3763
3764
        $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'.
3765
        // Clean parameters
3766
        if (empty($this->tva_tx))
3767
            $this->tva_tx = 0;
3768
        if (empty($this->localtax1_tx))
3769
            $this->localtax1_tx = 0;
3770
        if (empty($this->localtax2_tx))
3771
            $this->localtax2_tx = 0;
3772
        if (empty($this->total_localtax1))
3773
            $this->total_localtax1 = 0;
3774
        if (empty($this->total_localtax2))
3775
            $this->total_localtax2 = 0;
3776
        if (empty($this->localtax1_type))
3777
            $this->localtax1_type = 0;
3778
        if (empty($this->localtax2_type))
3779
            $this->localtax2_type = 0;
3780
        if (empty($this->marque_tx))
3781
            $this->marque_tx = 0;
3782
        if (empty($this->marge_tx))
3783
            $this->marge_tx = 0;
3784
        if (empty($this->price))
3785
            $this->price = 0; // TODO A virer
3786
        if (empty($this->remise))
3787
            $this->remise = 0; // TODO A virer
3788
        if (empty($this->remise_percent))
3789
            $this->remise_percent = 0;
3790
        if (empty($this->info_bits))
3791
            $this->info_bits = 0;
3792
        if (empty($this->special_code))
3793
            $this->special_code = 0;
3794
        if (empty($this->fk_parent_line))
3795
            $this->fk_parent_line = 0;
3796
        if (empty($this->fk_fournprice))
3797
            $this->fk_fournprice = 0;
3798
        if (empty($this->subprice))
3799
            $this->subprice = 0;
3800
        if (empty($this->pa_ht))
3801
            $this->pa_ht = 0;
3802
3803
        // if buy price not defined, define buyprice as configured in margin admin
3804
        if ($this->pa_ht == 0 && $pa_ht_isemptystring) {
3805
            if (($result = $this->defineBuyPrice($this->subprice, $this->remise_percent, $this->fk_product)) < 0) {
3806
                return $result;
3807
            } else {
3808
                $this->pa_ht = $result;
3809
            }
3810
        }
3811
3812
        $this->db->begin();
3813
3814
        // Mise a jour ligne en base
3815
        $sql = "UPDATE " . MAIN_DB_PREFIX . "propaldet SET";
3816
        $sql .= " description='" . $this->db->escape($this->desc) . "'";
3817
        $sql .= ", label=" . (!empty($this->label) ? "'" . $this->db->escape($this->label) . "'" : "null");
3818
        $sql .= ", product_type=" . $this->product_type;
3819
        $sql .= ", vat_src_code = '" . (empty($this->vat_src_code) ? '' : $this->vat_src_code) . "'";
3820
        $sql .= ", tva_tx='" . price2num($this->tva_tx) . "'";
3821
        $sql .= ", localtax1_tx=" . price2num($this->localtax1_tx);
3822
        $sql .= ", localtax2_tx=" . price2num($this->localtax2_tx);
3823
        $sql .= ", localtax1_type='" . $this->db->escape($this->localtax1_type) . "'";
3824
        $sql .= ", localtax2_type='" . $this->db->escape($this->localtax2_type) . "'";
3825
        $sql .= ", qty='" . price2num($this->qty) . "'";
3826
        $sql .= ", subprice=" . price2num($this->subprice) . "";
3827
        $sql .= ", remise_percent=" . price2num($this->remise_percent) . "";
3828
        $sql .= ", price=" . price2num($this->price) . "";     // TODO A virer
3829
        $sql .= ", remise=" . price2num($this->remise) . "";    // TODO A virer
3830
        $sql .= ", info_bits='" . $this->db->escape($this->info_bits) . "'";
3831
        if (empty($this->skip_update_total)) {
3832
            $sql .= ", total_ht=" . price2num($this->total_ht) . "";
3833
            $sql .= ", total_tva=" . price2num($this->total_tva) . "";
3834
            $sql .= ", total_ttc=" . price2num($this->total_ttc) . "";
3835
            $sql .= ", total_localtax1=" . price2num($this->total_localtax1) . "";
3836
            $sql .= ", total_localtax2=" . price2num($this->total_localtax2) . "";
3837
        }
3838
        $sql .= ", fk_product_fournisseur_price=" . (!empty($this->fk_fournprice) ? "'" . $this->db->escape($this->fk_fournprice) . "'" : "null");
3839
        $sql .= ", buy_price_ht=" . price2num($this->pa_ht);
3840
        if (strlen($this->special_code))
3841
            $sql .= ", special_code=" . $this->special_code;
3842
        $sql .= ", fk_parent_line=" . ($this->fk_parent_line > 0 ? $this->fk_parent_line : "null");
3843
        if (!empty($this->rang))
3844
            $sql .= ", rang=" . $this->rang;
3845
        $sql .= ", date_start=" . (!empty($this->date_start) ? "'" . $this->db->idate($this->date_start) . "'" : "null");
3846
        $sql .= ", date_end=" . (!empty($this->date_end) ? "'" . $this->db->idate($this->date_end) . "'" : "null");
3847
        $sql .= ", fk_unit=" . (!$this->fk_unit ? 'NULL' : $this->fk_unit);
3848
3849
        // Multicurrency
3850
        $sql .= ", multicurrency_subprice=" . price2num($this->multicurrency_subprice) . "";
3851
        $sql .= ", multicurrency_total_ht=" . price2num($this->multicurrency_total_ht) . "";
3852
        $sql .= ", multicurrency_total_tva=" . price2num($this->multicurrency_total_tva) . "";
3853
        $sql .= ", multicurrency_total_ttc=" . price2num($this->multicurrency_total_ttc) . "";
3854
3855
        $sql .= " WHERE rowid = " . $this->rowid;
3856
3857
        dol_syslog(get_class($this) . "::update", LOG_DEBUG);
3858
        $resql = $this->db->query($sql);
3859
        if ($resql) {
3860
            if (empty($conf->global->MAIN_EXTRAFIELDS_DISABLED)) { // For avoid conflicts if trigger used
3861
                $this->id = $this->rowid;
3862
                $result = $this->insertExtraFields();
3863
                if ($result < 0) {
3864
                    $error++;
3865
                }
3866
            }
3867
3868
            if (!$error && !$notrigger) {
3869
                // Call trigger
3870
                $result = $this->call_trigger('LINEPROPAL_UPDATE', $user);
3871
                if ($result < 0) {
3872
                    $this->db->rollback();
3873
                    return -1;
3874
                }
3875
                // End call triggers
3876
            }
3877
3878
            $this->db->commit();
3879
            return 1;
3880
        } else {
3881
            $this->error = $this->db->error();
3882
            $this->db->rollback();
3883
            return -2;
3884
        }
3885
    }
3886
3887
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
3888
    /**
3889
     * 	Update DB line fields total_xxx
3890
     * 	Used by migration
3891
     *
3892
     * 	@return		int		<0 if KO, >0 if OK
3893
     */
3894
    function update_total()
3895
    {
3896
        // phpcs:enable
3897
        $this->db->begin();
3898
3899
        // Mise a jour ligne en base
3900
        $sql = "UPDATE " . MAIN_DB_PREFIX . "propaldet SET";
3901
        $sql .= " total_ht=" . price2num($this->total_ht, 'MT') . "";
3902
        $sql .= ",total_tva=" . price2num($this->total_tva, 'MT') . "";
3903
        $sql .= ",total_ttc=" . price2num($this->total_ttc, 'MT') . "";
3904
        $sql .= " WHERE rowid = " . $this->rowid;
3905
3906
        dol_syslog("PropaleLigne::update_total", LOG_DEBUG);
3907
3908
        $resql = $this->db->query($sql);
3909
        if ($resql) {
3910
            $this->db->commit();
3911
            return 1;
3912
        } else {
3913
            $this->error = $this->db->error();
3914
            $this->db->rollback();
3915
            return -2;
3916
        }
3917
    }
3918
}
3919