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

Propal::getNomUrl()   F

Complexity

Conditions 27
Paths > 20000

Size

Total Lines 80
Code Lines 49

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 27
eloc 49
nc 110616
nop 5
dl 0
loc 80
rs 0
c 0
b 0
f 0

How to fix   Long Method    Complexity   

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:

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