Passed
Pull Request — dev (#8)
by Rafael
58:47
created

Propal::setCancel()   B

Complexity

Conditions 7

Size

Total Lines 38
Code Lines 26

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
eloc 26
nop 1
dl 0
loc 38
rs 8.5706
c 0
b 0
f 0
1
<?php
2
3
/* Copyright (C) 2002-2004  Rodolphe Quiedeville        <[email protected]>
4
 * Copyright (C) 2004       Eric Seigne                 <[email protected]>
5
 * Copyright (C) 2004-2011  Laurent Destailleur         <[email protected]>
6
 * Copyright (C) 2005       Marc Barilley               <[email protected]>
7
 * Copyright (C) 2005-2013  Regis Houssin               <[email protected]>
8
 * Copyright (C) 2006       Andre Cianfarani            <[email protected]>
9
 * Copyright (C) 2008       Raphael Bertrand            <[email protected]>
10
 * Copyright (C) 2010-2020  Juanjo Menent               <[email protected]>
11
 * Copyright (C) 2010-2022  Philippe Grand              <[email protected]>
12
 * Copyright (C) 2012-2014  Christophe Battarel         <[email protected]>
13
 * Copyright (C) 2012       Cedric Salvador             <[email protected]>
14
 * Copyright (C) 2013       Florian Henry               <[email protected]>
15
 * Copyright (C) 2014-2015  Marcos García               <[email protected]>
16
 * Copyright (C) 2018       Nicolas ZABOURI             <[email protected]>
17
 * Copyright (C) 2018-2024  Frédéric France             <[email protected]>
18
 * Copyright (C) 2018       Ferran Marcet               <[email protected]>
19
 * Copyright (C) 2022       ATM Consulting              <[email protected]>
20
 * Copyright (C) 2022       OpenDSI                     <[email protected]>
21
 * Copyright (C) 2022      	Gauthier VERDOL     	    <[email protected]>
22
 * Copyright (C) 2023		William Mead			    <[email protected]>
23
 * Copyright (C) 2024		MDW							<[email protected]>
24
 * Copyright (C) 2024       Rafael San José             <[email protected]>
25
 *
26
 * This program is free software; you can redistribute it and/or modify
27
 * it under the terms of the GNU General Public License as published by
28
 * the Free Software Foundation; either version 3 of the License, or
29
 * (at your option) any later version.
30
 *
31
 * This program is distributed in the hope that it will be useful,
32
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
33
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
34
 * GNU General Public License for more details.
35
 *
36
 * You should have received a copy of the GNU General Public License
37
 * along with this program. If not, see <https://www.gnu.org/licenses/>.
38
 */
39
40
namespace Dolibarr\Code\Comm\Classes;
41
42
/**
43
 *  \file       htdocs/comm/propal/class/propal.class.php
44
 *  \brief      File of class to manage proposals
45
 */
46
47
use Dolibarr\Code\Core\Classes\WorkboardResponse;
48
use Dolibarr\Code\Core\Traits\CommonIncoterm;
49
use Dolibarr\Code\MultiCurrency\Classes\MultiCurrency;
50
use Dolibarr\Core\Base\CommonObject;
51
52
require_once constant('DOL_DOCUMENT_ROOT') . '/margin/lib/margins.lib.php';
53
54
/**
55
 *  Class to manage proposals
56
 */
57
class Propal extends CommonObject
58
{
59
    use CommonIncoterm;
60
61
    /**
62
     * Canceled status
63
     */
64
    const STATUS_CANCELED = -1;
65
    /**
66
     * Draft status
67
     */
68
    const STATUS_DRAFT = 0;
69
    /**
70
     * Validated status
71
     */
72
    const STATUS_VALIDATED = 1;
73
    /**
74
     * Signed quote
75
     */
76
    const STATUS_SIGNED = 2;
77
    /**
78
     * Not signed quote
79
     */
80
    const STATUS_NOTSIGNED = 3;
81
/**
82
     * Billed or processed quote
83
     */
84
    const STATUS_BILLED = 4;
85
    /**
86
     * @var string code
87
     */
88
    public $code = "";
89
    /**
90
     * @var string ID to identify managed object
91
     */
92
    public $element = 'propal';
93
    /**
94
     * @var string Name of table without prefix where object is stored
95
     */
96
    public $table_element = 'propal';
97
    /**
98
     * @var string    Name of subtable line
99
     */
100
    public $table_element_line = 'propaldet';
101
    /**
102
     * @var string Fieldname with ID of parent key if this field has a parent
103
     */
104
    public $fk_element = 'fk_propal';
105
    /**
106
     * @var string String with name of icon for myobject. Must be the part after the 'object_' into object_myobject.png
107
     */
108
    public $picto = 'propal';
109
    /**
110
     * 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
111
     * @var integer
112
     */
113
    public $restrictiononfksoc = 1;
114
    /**
115
     * ID of the client
116
     * @var int
117
     */
118
    public $socid;
119
    /**
120
     * ID of the contact
121
     * @var int
122
     */
123
    public $contactid;
124
    public $author;
125
    /**
126
     * Ref from thirdparty
127
     * @var string
128
     * @deprecated
129
     * @see $ref_customer
130
     */
131
    public $ref_client;
132
    /**
133
     * Ref from thirdparty
134
     * @var string
135
     */
136
    public $ref_customer;
137
    /**
138
     * @var static oldcopy with propal properties
139
     */
140
    public $oldcopy;
141
    /**
142
     * Status of the quote
143
     * @var int
144
     * @deprecated Try to use $status now
145
     * @see Propal::STATUS_DRAFT, Propal::STATUS_VALIDATED, Propal::STATUS_SIGNED, Propal::STATUS_NOTSIGNED, Propal::STATUS_BILLED, Propal::STATUS_CANCELED
146
     */
147
    public $statut;
148
    /**
149
     * Status of the quote
150
     * @var int
151
     * @see Propal::STATUS_DRAFT, Propal::STATUS_VALIDATED, Propal::STATUS_SIGNED, Propal::STATUS_NOTSIGNED, Propal::STATUS_BILLED, Propal::STATUS_CANCELED
152
     */
153
    public $status;
154
    /**
155
     * @deprecated
156
     * @see $date_creation
157
     */
158
    public $datec;
159
    /**
160
     * @var integer|string $date_creation ;
161
     */
162
    public $date_creation;
163
    /**
164
     * @deprecated
165
     * @see $date_validation
166
     */
167
    public $datev;
168
    /**
169
     * @var integer|string $date_validation ;
170
     */
171
    public $date_validation; // Date expected of shipment (date starting shipment, not the reception that occurs some days after)
172
    /**
173
     * @var integer|string $date_signature ;
174
     */
175
    public $date_signature;
176
    /**
177
     * @var User $user_signature
178
     */
179
    public $user_signature;
180
    /**
181
     * @var integer|string date of the quote;
182
     */
183
    public $date;
184
    /**
185
     * @deprecated
186
     * @see $date
187
     */
188
    public $datep;
189
/**
190
     * @var integer|string $delivery_date ;
191
     */
192
    public $delivery_date;
193
    public $fin_validite;    // code
194
        public $user_author_id;         // label
195
        /**
196
     * @deprecated
197
     * @see $total_ht
198
     */
199
    public $price;     // label doc
200
    /**
201
     * @deprecated
202
     * @see $total_tva
203
     */
204
    public $tva;    // code
205
        /**
206
     * @deprecated
207
     * @see $total_ttc
208
     */
209
    public $total;         // label
210
public $cond_reglement_code;
211
public $cond_reglement;
212
public $cond_reglement_doc;
213
public $mode_reglement_code;
214
public $mode_reglement;
215
    public $deposit_percent;
216
    /**
217
     * @var int ID
218
     * @deprecated
219
     */
220
    public $fk_address;
221
    public $address_type;
222
    public $address;
223
    /**
224
     * @var int availability ID
225
     */
226
    public $availability_id;       // id
227
        /**
228
     * @var int availability ID
229
     * @deprecated
230
     * @see $availability_id
231
     */
232
    public $fk_availability;     // code
233
        /**
234
     * @var string availability code
235
     */
236
    public $availability_code;          // label
237
    /**
238
     * @var string availability label
239
     */
240
    public $availability;
241
    public $duree_validite;
242
public $demand_reason_id;
243
public $demand_reason_code;
244
public $demand_reason;
245
    public $warehouse_id;
246
247
248
    /**
249
     *  'type' if the field format ('integer', 'integer:ObjectClass:PathToClass[:AddCreateButtonOrNot[:Filter]]', 'varchar(x)', 'double(24,8)', 'real', 'price', 'text', 'html', 'date', 'datetime', 'timestamp', 'duration', 'mail', 'phone', 'url', 'password')
250
     *         Note: Filter can be a string like "(t.ref:like:'SO-%') or (t.date_creation:<:'20160101') or (t.nature:is:NULL)"
251
     *  'label' the translation key.
252
     *  'enabled' is a condition when the field must be managed.
253
     *  'position' is the sort order of field.
254
     *  'notnull' is set to 1 if not null in database. Set to -1 if we must set data to null if empty ('' or 0).
255
     *  'visible' says if field is visible in list (Examples: 0=Not visible, 1=Visible on list and create/update/view forms, 2=Visible on list only, 3=Visible on create/update/view form only (not list), 4=Visible on list and update/view form only (not create). 5=Visible on list and view only (not create/not update). Using a negative value means field is not shown by default on list but can be selected for viewing)
256
     *  'noteditable' says if field is not editable (1 or 0)
257
     *  'default' is a default value for creation (can still be overwrote by the Setup of Default Values if field is editable in creation form). Note: If default is set to '(PROV)' and field is 'ref', the default value will be set to '(PROVid)' where id is rowid when a new record is created.
258
     *  'index' if we want an index in database.
259
     *  'foreignkey'=>'tablename.field' if the field is a foreign key (it is recommended to name the field fk_...).
260
     *  'searchall' is 1 if we want to search in this field when making a search from the quick search button.
261
     *  'isameasure' must be set to 1 if you want to have a total on list for this field. Field type must be summable like integer or double(24,8).
262
     *  'css' is the CSS style to use on field. For example: 'maxwidth200'
263
     *  'help' is a string visible as a tooltip on field
264
     *  'showoncombobox' if value of the field must be visible into the label of the combobox that list record
265
     *  'disabled' is 1 if we want to have the field locked by a 'disabled' attribute. In most cases, this is never set into the definition of $fields into class, but is set dynamically by some part of code.
266
     *  'arrayofkeyval' to set list of value if type is a list of predefined values. For example: array("0"=>"Draft","1"=>"Active","-1"=>"Cancel")
267
     *  'comment' is not used. You can store here any text of your choice. It is not used by application.
268
     *
269
     *  Note: To have value dynamic, you can set value to 0 in definition and edit the value on the fly into the constructor.
270
     */
271
272
    // BEGIN MODULEBUILDER PROPERTIES
273
    public $extraparams = array();
274
    // END MODULEBUILDER PROPERTIES
275
    /**
276
     * @var PropaleLigne[]
277
     */
278
    public $lines = array();
279
    /**
280
     * @var PropaleLigne
281
     */
282
    public $line;
283
    public $labelStatus = array();
284
    public $labelStatusShort = array();
285
    /**
286
     * @var array<string,array{type:string,label:string,enabled:int<0,2>|string,position:int,notnull?:int,visible:int,noteditable?:int,default?:string,index?:int,foreignkey?:string,searchall?:int,isameasure?:int,css?:string,csslist?:string,help?:string,showoncombobox?:int,disabled?:int,arrayofkeyval?:array<int,string>,comment?:string}>  Array with all fields and their property. Do not use it as a static var. It may be modified by constructor.
287
     */
288
    public $fields = array(
289
        'rowid' => array('type' => 'integer', 'label' => 'TechnicalID', 'enabled' => 1, 'visible' => -1, 'notnull' => 1, 'position' => 10),
290
        'entity' => array('type' => 'integer', 'label' => 'Entity', 'default' => '1', 'enabled' => 1, 'visible' => -2, 'notnull' => 1, 'position' => 15, 'index' => 1),
291
        'ref' => array('type' => 'varchar(30)', 'label' => 'Ref', 'enabled' => 1, 'visible' => -1, 'notnull' => 1, 'showoncombobox' => 1, 'position' => 20),
292
        'ref_client' => array('type' => 'varchar(255)', 'label' => 'RefCustomer', 'enabled' => 1, 'visible' => -1, 'position' => 22),
293
        'ref_ext' => array('type' => 'varchar(255)', 'label' => 'RefExt', 'enabled' => 1, 'visible' => 0, 'position' => 40),
294
        'fk_soc' => array('type' => 'integer:Societe:societe/class/societe.class.php', 'label' => 'ThirdParty', 'enabled' => 'isModEnabled("societe")', 'visible' => -1, 'position' => 23),
295
        'fk_projet' => array('type' => 'integer:Project:projet/class/project.class.php:1:(fk_statut:=:1)', 'label' => 'Fk projet', 'enabled' => "isModEnabled('project')", 'visible' => -1, 'position' => 24),
296
        'tms' => array('type' => 'timestamp', 'label' => 'DateModification', 'enabled' => 1, 'visible' => -1, 'notnull' => 1, 'position' => 25),
297
        'datec' => array('type' => 'datetime', 'label' => 'DateCreation', 'enabled' => 1, 'visible' => -1, 'position' => 55),
298
        'datep' => array('type' => 'date', 'label' => 'Date', 'enabled' => 1, 'visible' => -1, 'position' => 60),
299
        'fin_validite' => array('type' => 'datetime', 'label' => 'DateEnd', 'enabled' => 1, 'visible' => -1, 'position' => 65),
300
        'date_valid' => array('type' => 'datetime', 'label' => 'DateValidation', 'enabled' => 1, 'visible' => -1, 'position' => 70),
301
        'date_cloture' => array('type' => 'datetime', 'label' => 'DateClosing', 'enabled' => 1, 'visible' => -1, 'position' => 75),
302
        'fk_user_author' => array('type' => 'integer:User:user/class/user.class.php', 'label' => 'Fk user author', 'enabled' => 1, 'visible' => -1, 'position' => 80),
303
        'fk_user_modif' => array('type' => 'integer:User:user/class/user.class.php', 'label' => 'UserModif', 'enabled' => 1, 'visible' => -2, 'notnull' => -1, 'position' => 85),
304
        'fk_user_valid' => array('type' => 'integer:User:user/class/user.class.php', 'label' => 'UserValidation', 'enabled' => 1, 'visible' => -1, 'position' => 90),
305
        'fk_user_cloture' => array('type' => 'integer:User:user/class/user.class.php', 'label' => 'Fk user cloture', 'enabled' => 1, 'visible' => -1, 'position' => 95),
306
        'price' => array('type' => 'double', 'label' => 'Price', 'enabled' => 1, 'visible' => -1, 'position' => 105),
307
        'total_ht' => array('type' => 'double(24,8)', 'label' => 'TotalHT', 'enabled' => 1, 'visible' => -1, 'position' => 125, 'isameasure' => 1),
308
        'total_tva' => array('type' => 'double(24,8)', 'label' => 'VAT', 'enabled' => 1, 'visible' => -1, 'position' => 130, 'isameasure' => 1),
309
        'localtax1' => array('type' => 'double(24,8)', 'label' => 'LocalTax1', 'enabled' => 1, 'visible' => -1, 'position' => 135, 'isameasure' => 1),
310
        'localtax2' => array('type' => 'double(24,8)', 'label' => 'LocalTax2', 'enabled' => 1, 'visible' => -1, 'position' => 140, 'isameasure' => 1),
311
        'total_ttc' => array('type' => 'double(24,8)', 'label' => 'TotalTTC', 'enabled' => 1, 'visible' => -1, 'position' => 145, 'isameasure' => 1),
312
        'fk_account' => array('type' => 'integer', 'label' => 'BankAccount', 'enabled' => 'isModEnabled("bank")', 'visible' => -1, 'position' => 150),
313
        'fk_currency' => array('type' => 'varchar(3)', 'label' => 'Currency', 'enabled' => 1, 'visible' => -1, 'position' => 155),
314
        'fk_cond_reglement' => array('type' => 'integer', 'label' => 'PaymentTerm', 'enabled' => 1, 'visible' => -1, 'position' => 160),
315
        'deposit_percent' => array('type' => 'varchar(63)', 'label' => 'DepositPercent', 'enabled' => 1, 'visible' => -1, 'position' => 161),
316
        'fk_mode_reglement' => array('type' => 'integer', 'label' => 'PaymentMode', 'enabled' => 1, 'visible' => -1, 'position' => 165),
317
        'note_private' => array('type' => 'html', 'label' => 'NotePrivate', 'enabled' => 1, 'visible' => 0, 'position' => 170),
318
        'note_public' => array('type' => 'html', 'label' => 'NotePublic', 'enabled' => 1, 'visible' => 0, 'position' => 175),
319
        'model_pdf' => array('type' => 'varchar(255)', 'label' => 'PDFTemplate', 'enabled' => 1, 'visible' => 0, 'position' => 180),
320
        'date_livraison' => array('type' => 'date', 'label' => 'DateDeliveryPlanned', 'enabled' => 1, 'visible' => -1, 'position' => 185),
321
        'fk_shipping_method' => array('type' => 'integer', 'label' => 'ShippingMethod', 'enabled' => 1, 'visible' => -1, 'position' => 190),
322
        'fk_warehouse' => array('type' => 'integer:Entrepot:product/stock/class/entrepot.class.php', 'label' => 'Fk warehouse', 'enabled' => 'isModEnabled("stock")', 'visible' => -1, 'position' => 191),
323
        'fk_availability' => array('type' => 'integer', 'label' => 'Availability', 'enabled' => 1, 'visible' => -1, 'position' => 195),
324
        'fk_delivery_address' => array('type' => 'integer', 'label' => 'DeliveryAddress', 'enabled' => 1, 'visible' => 0, 'position' => 200), // deprecated
325
        'fk_input_reason' => array('type' => 'integer', 'label' => 'InputReason', 'enabled' => 1, 'visible' => -1, 'position' => 205),
326
        'extraparams' => array('type' => 'varchar(255)', 'label' => 'Extraparams', 'enabled' => 1, 'visible' => -1, 'position' => 215),
327
        'fk_incoterms' => array('type' => 'integer', 'label' => 'IncotermCode', 'enabled' => '$conf->incoterm->enabled', 'visible' => -1, 'position' => 220),
328
        'location_incoterms' => array('type' => 'varchar(255)', 'label' => 'IncotermLabel', 'enabled' => '$conf->incoterm->enabled', 'visible' => -1, 'position' => 225),
329
        'fk_multicurrency' => array('type' => 'integer', 'label' => 'MulticurrencyID', 'enabled' => 1, 'visible' => -1, 'position' => 230),
330
        'multicurrency_code' => array('type' => 'varchar(255)', 'label' => 'MulticurrencyCurrency', 'enabled' => 'isModEnabled("multicurrency")', 'visible' => -1, 'position' => 235),
331
        'multicurrency_tx' => array('type' => 'double(24,8)', 'label' => 'MulticurrencyRate', 'enabled' => 'isModEnabled("multicurrency")', 'visible' => -1, 'position' => 240, 'isameasure' => 1),
332
        'multicurrency_total_ht' => array('type' => 'double(24,8)', 'label' => 'MulticurrencyAmountHT', 'enabled' => 'isModEnabled("multicurrency")', 'visible' => -1, 'position' => 245, 'isameasure' => 1),
333
        'multicurrency_total_tva' => array('type' => 'double(24,8)', 'label' => 'MulticurrencyAmountVAT', 'enabled' => 'isModEnabled("multicurrency")', 'visible' => -1, 'position' => 250, 'isameasure' => 1),
334
        'multicurrency_total_ttc' => array('type' => 'double(24,8)', 'label' => 'MulticurrencyAmountTTC', 'enabled' => 'isModEnabled("multicurrency")', 'visible' => -1, 'position' => 255, 'isameasure' => 1),
335
        'last_main_doc' => array('type' => 'varchar(255)', 'label' => 'LastMainDoc', 'enabled' => 1, 'visible' => -1, 'position' => 260),
336
        'fk_statut' => array('type' => 'smallint(6)', 'label' => 'Status', 'enabled' => 1, 'visible' => -1, 'notnull' => 1, 'position' => 500),
337
        'import_key' => array('type' => 'varchar(14)', 'label' => 'ImportId', 'enabled' => 1, 'visible' => -2, 'position' => 900),
338
    );
339
        /**
340
     * {@inheritdoc}
341
     */
342
    protected $table_ref_field = 'ref'; // Todo rename into STATUS_CLOSE ?
343
344
    /**
345
     *  Constructor
346
     *
347
     * @param DoliDB $db Database handler
0 ignored issues
show
Bug introduced by
The type Dolibarr\Code\Comm\Classes\DoliDB was not found. Did you mean DoliDB? If so, make sure to prefix the type with \.
Loading history...
348
     * @param int $socid Id third party
349
     * @param int $propalid Id proposal
350
     */
351
    public function __construct($db, $socid = 0, $propalid = 0)
352
    {
353
        $this->db = $db;
354
355
        $this->ismultientitymanaged = 1;
356
        $this->socid = $socid;
357
        $this->id = $propalid;
358
359
        $this->duree_validite = getDolGlobalInt('PROPALE_VALIDITY_DURATION', 0);
360
    }
361
362
363
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
364
365
    /**
366
     * Function used to replace a thirdparty id with another one.
367
     *
368
     * @param DoliDB $dbs Database handler, because function is static we name it $dbs not $db to avoid breaking coding test
369
     * @param int $origin_id Old thirdparty id
370
     * @param int $dest_id New thirdparty id
371
     * @return  bool
372
     */
373
    public static function replaceThirdparty(DoliDB $dbs, $origin_id, $dest_id)
374
    {
375
        $tables = array(
376
            'propal'
377
        );
378
379
        return CommonObject::commonReplaceThirdparty($dbs, $origin_id, $dest_id, $tables);
380
    }
381
382
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
383
384
    /**
385
     * Function used to replace a product id with another one.
386
     *
387
     * @param DoliDB $db Database handler
388
     * @param int $origin_id Old product id
389
     * @param int $dest_id New product id
390
     * @return bool
391
     */
392
    public static function replaceProduct(DoliDB $db, $origin_id, $dest_id)
393
    {
394
        $tables = array(
395
            'propaldet'
396
        );
397
398
        return CommonObject::commonReplaceProduct($db, $origin_id, $dest_id, $tables);
399
    }
400
401
    /**
402
     *  Add line into array ->lines
403
     *  $this->thirdparty should be loaded
404
     *
405
     * @param int $idproduct Product Id to add
406
     * @param float $qty Quantity
407
     * @param float $remise_percent Discount effected on Product
408
     * @return int                         Return integer <0 if KO, >0 if OK
409
     *
410
     *  TODO    Replace calls to this function by generation object Ligne
411
     */
412
    public function add_product($idproduct, $qty, $remise_percent = 0)
413
    {
414
        // phpcs:enable
415
        global $conf, $mysoc;
416
417
        if (!$qty) {
418
            $qty = 1;
419
        }
420
421
        dol_syslog(get_class($this) . "::add_product $idproduct, $qty, $remise_percent");
422
        if ($idproduct > 0) {
423
            $prod = new Product($this->db);
424
            $prod->fetch($idproduct);
425
426
            $productdesc = $prod->description;
427
428
            $tva_tx = get_default_tva($mysoc, $this->thirdparty, $prod->id);
429
            $tva_npr = get_default_npr($mysoc, $this->thirdparty, $prod->id);
430
            if (empty($tva_tx)) {
431
                $tva_npr = 0;
432
            }
433
            $vat_src_code = ''; // May be defined into tva_tx
434
435
            $localtax1_tx = get_localtax($tva_tx, 1, $mysoc, $this->thirdparty, $tva_npr);
436
            $localtax2_tx = get_localtax($tva_tx, 2, $mysoc, $this->thirdparty, $tva_npr);
437
438
            // multiprices
439
            if ($conf->global->PRODUIT_MULTIPRICES && $this->thirdparty->price_level) {
440
                $price = $prod->multiprices[$this->thirdparty->price_level];
441
            } else {
442
                $price = $prod->price;
443
            }
444
445
            $line = new PropaleLigne($this->db);
446
447
            $line->fk_product = $idproduct;
448
            $line->desc = $productdesc;
449
            $line->qty = $qty;
450
            $line->subprice = $price;
451
            $line->remise_percent = $remise_percent;
452
            $line->vat_src_code = $vat_src_code;
453
            $line->tva_tx = $tva_tx;
454
            $line->fk_unit = $prod->fk_unit;
455
            if ($tva_npr) {
456
                $line->info_bits = 1;
457
            }
458
459
            $this->lines[] = $line;
460
        }
461
462
        return 1;
463
    }
464
465
    /**
466
     *  Load a proposal from database. Get also lines.
467
     *
468
     * @param int $rowid Id of object to load
469
     * @param string $ref Ref of proposal
470
     * @param string $ref_ext Ref ext of proposal
471
     * @param int $forceentity Entity id to force when searching on ref or ref_ext
472
     * @return     int                         >0 if OK, <0 if KO
473
     */
474
    public function fetch($rowid, $ref = '', $ref_ext = '', $forceentity = 0)
475
    {
476
        $sql = "SELECT p.rowid, p.ref, p.entity, p.fk_soc";
477
        $sql .= ", p.total_ttc, p.total_tva, p.localtax1, p.localtax2, p.total_ht";
478
        $sql .= ", p.datec";
479
        $sql .= ", p.date_signature as dates";
480
        $sql .= ", p.date_valid as datev";
481
        $sql .= ", p.datep as dp";
482
        $sql .= ", p.fin_validite as dfv";
483
        $sql .= ", p.date_livraison as delivery_date";
484
        $sql .= ", p.model_pdf, p.last_main_doc, p.ref_client, ref_ext, p.extraparams";
485
        $sql .= ", p.note_private, p.note_public";
486
        $sql .= ", p.fk_projet as fk_project, p.fk_statut";
487
        $sql .= ", p.fk_user_author, p.fk_user_valid, p.fk_user_cloture";
488
        $sql .= ", p.fk_delivery_address";
489
        $sql .= ", p.fk_availability";
490
        $sql .= ", p.fk_input_reason";
491
        $sql .= ", p.fk_cond_reglement";
492
        $sql .= ", p.fk_mode_reglement";
493
        $sql .= ', p.fk_account';
494
        $sql .= ", p.fk_shipping_method";
495
        $sql .= ", p.fk_warehouse";
496
        $sql .= ", p.fk_incoterms, p.location_incoterms";
497
        $sql .= ", p.fk_multicurrency, p.multicurrency_code, p.multicurrency_tx, p.multicurrency_total_ht, p.multicurrency_total_tva, p.multicurrency_total_ttc";
498
        $sql .= ", p.tms as date_modification";
499
        $sql .= ", i.libelle as label_incoterms";
500
        $sql .= ", c.label as statut_label";
501
        $sql .= ", ca.code as availability_code, ca.label as availability";
502
        $sql .= ", dr.code as demand_reason_code, dr.label as demand_reason";
503
        $sql .= ", cr.code as cond_reglement_code, cr.libelle as cond_reglement, cr.libelle_facture as cond_reglement_libelle_doc, p.deposit_percent";
504
        $sql .= ", cp.code as mode_reglement_code, cp.libelle as mode_reglement";
505
        $sql .= " FROM " . MAIN_DB_PREFIX . "propal as p";
506
        $sql .= ' LEFT JOIN ' . MAIN_DB_PREFIX . 'c_propalst as c ON p.fk_statut = c.id';
507
        $sql .= ' LEFT JOIN ' . MAIN_DB_PREFIX . 'c_paiement as cp ON p.fk_mode_reglement = cp.id AND cp.entity IN (' . getEntity('c_paiement') . ')';
508
        $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') . ')';
509
        $sql .= ' LEFT JOIN ' . MAIN_DB_PREFIX . 'c_availability as ca ON p.fk_availability = ca.rowid';
510
        $sql .= ' LEFT JOIN ' . MAIN_DB_PREFIX . 'c_input_reason as dr ON p.fk_input_reason = dr.rowid';
511
        $sql .= ' LEFT JOIN ' . MAIN_DB_PREFIX . 'c_incoterms as i ON p.fk_incoterms = i.rowid';
512
513
        if (!empty($ref)) {
514
            if (!empty($forceentity)) {
515
                $sql .= " WHERE p.entity = " . (int)$forceentity; // Check only the current entity because we may have the same reference in several entities
516
            } else {
517
                $sql .= " WHERE p.entity IN (" . getEntity('propal') . ")";
518
            }
519
            $sql .= " AND p.ref='" . $this->db->escape($ref) . "'";
520
        } else {
521
            // Don't use entity if you use rowid
522
            $sql .= " WHERE p.rowid = " . ((int)$rowid);
523
        }
524
525
        dol_syslog(get_class($this) . "::fetch", LOG_DEBUG);
526
        $resql = $this->db->query($sql);
527
        if ($resql) {
528
            if ($this->db->num_rows($resql)) {
529
                $obj = $this->db->fetch_object($resql);
530
531
                $this->id = $obj->rowid;
532
                $this->entity = $obj->entity;
533
534
                $this->ref = $obj->ref;
535
                $this->ref_client = $obj->ref_client;
536
                $this->ref_customer = $obj->ref_client;
537
                $this->ref_ext = $obj->ref_ext;
538
539
                $this->total = $obj->total_ttc;          // TODO deprecated
540
                $this->total_ttc = $obj->total_ttc;
541
                $this->total_ht = $obj->total_ht;
542
                $this->total_tva = $obj->total_tva;
543
                $this->total_localtax1 = $obj->localtax1;
544
                $this->total_localtax2 = $obj->localtax2;
545
546
                $this->socid = $obj->fk_soc;
547
                $this->thirdparty = null; // Clear if another value was already set by fetch_thirdparty
548
549
                $this->fk_project = $obj->fk_project;
550
                $this->project = null; // Clear if another value was already set by fetch_projet
551
552
                $this->model_pdf = $obj->model_pdf;
553
                $this->last_main_doc = $obj->last_main_doc;
554
                $this->note = $obj->note_private; // TODO deprecated
555
                $this->note_private = $obj->note_private;
556
                $this->note_public = $obj->note_public;
557
558
                $this->status = (int)$obj->fk_statut;
559
                $this->statut = $this->status; // deprecated
560
561
                $this->datec = $this->db->jdate($obj->datec); // TODO deprecated
562
                $this->datev = $this->db->jdate($obj->datev); // TODO deprecated
563
                $this->date_creation = $this->db->jdate($obj->datec); //Creation date
564
                $this->date_validation = $this->db->jdate($obj->datev); //Validation date
565
                $this->date_modification = $this->db->jdate($obj->date_modification); // tms
566
                $this->date_signature = $this->db->jdate($obj->dates); // Signature date
567
                $this->date = $this->db->jdate($obj->dp); // Proposal date
568
                $this->datep = $this->db->jdate($obj->dp); // deprecated
569
                $this->fin_validite = $this->db->jdate($obj->dfv);
570
                $this->delivery_date = $this->db->jdate($obj->delivery_date);
571
                $this->shipping_method_id = ($obj->fk_shipping_method > 0) ? $obj->fk_shipping_method : null;
572
                $this->warehouse_id = ($obj->fk_warehouse > 0) ? $obj->fk_warehouse : null;
573
                $this->availability_id = $obj->fk_availability;
574
                $this->availability_code = $obj->availability_code;
575
                $this->availability = $obj->availability;
576
                $this->demand_reason_id = $obj->fk_input_reason;
577
                $this->demand_reason_code = $obj->demand_reason_code;
578
                $this->demand_reason = $obj->demand_reason;
579
                $this->fk_address = $obj->fk_delivery_address;
580
581
                $this->mode_reglement_id = $obj->fk_mode_reglement;
582
                $this->mode_reglement_code = $obj->mode_reglement_code;
583
                $this->mode_reglement = $obj->mode_reglement;
584
                $this->fk_account = ($obj->fk_account > 0) ? $obj->fk_account : null;
585
                $this->cond_reglement_id = $obj->fk_cond_reglement;
586
                $this->cond_reglement_code = $obj->cond_reglement_code;
587
                $this->cond_reglement = $obj->cond_reglement;
588
                $this->cond_reglement_doc = $obj->cond_reglement_libelle_doc;
589
                $this->deposit_percent = $obj->deposit_percent;
590
591
                $this->extraparams = !empty($obj->extraparams) ? (array)json_decode($obj->extraparams, true) : array();
592
593
                $this->user_author_id = $obj->fk_user_author;
594
                $this->user_validation_id = $obj->fk_user_valid;
595
                $this->user_closing_id = $obj->fk_user_cloture;
596
597
                //Incoterms
598
                $this->fk_incoterms = $obj->fk_incoterms;
599
                $this->location_incoterms = $obj->location_incoterms;
600
                $this->label_incoterms = $obj->label_incoterms;
601
602
                // Multicurrency
603
                $this->fk_multicurrency = $obj->fk_multicurrency;
604
                $this->multicurrency_code = $obj->multicurrency_code;
605
                $this->multicurrency_tx = $obj->multicurrency_tx;
606
                $this->multicurrency_total_ht = $obj->multicurrency_total_ht;
607
                $this->multicurrency_total_tva = $obj->multicurrency_total_tva;
608
                $this->multicurrency_total_ttc = $obj->multicurrency_total_ttc;
609
610
                // Retrieve all extrafield
611
                // fetch optionals attributes and labels
612
                $this->fetch_optionals();
613
614
                $this->db->free($resql);
615
616
                $this->lines = array();
617
618
                // Lines
619
                $result = $this->fetch_lines();
620
                if ($result < 0) {
621
                    return -3;
622
                }
623
624
                return 1;
625
            }
626
627
            $this->error = "Record Not Found";
628
            return 0;
629
        } else {
630
            $this->error = $this->db->lasterror();
631
            return -1;
632
        }
633
    }
634
635
    /**
636
     * Load array lines
637
     *
638
     * @param int $only_product Return only physical products
639
     * @param int $loadalsotranslation Return translation for products
640
     * @param string $sqlforgedfilters Filter on other fields
641
     * @return     int                                 Return integer <0 if KO, >0 if OK
642
     */
643
    public function fetch_lines($only_product = 0, $loadalsotranslation = 0, $sqlforgedfilters = '')
644
    {
645
        // phpcs:enable
646
        $this->lines = array();
647
648
        $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,';
649
        $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,';
650
        $sql .= ' d.fk_unit,';
651
        $sql .= ' p.ref as product_ref, p.description as product_desc, p.fk_product_type, p.label as product_label, p.tobatch as product_tobatch, p.barcode as product_barcode,';
652
        $sql .= ' p.weight, p.weight_units, p.volume, p.volume_units,';
653
        $sql .= ' d.date_start, d.date_end,';
654
        $sql .= ' d.fk_multicurrency, d.multicurrency_code, d.multicurrency_subprice, d.multicurrency_total_ht, d.multicurrency_total_tva, d.multicurrency_total_ttc';
655
        $sql .= ' FROM ' . MAIN_DB_PREFIX . 'propaldet as d';
656
        $sql .= ' LEFT JOIN ' . MAIN_DB_PREFIX . 'product as p ON (d.fk_product = p.rowid)';
657
        $sql .= ' WHERE d.fk_propal = ' . ((int)$this->id);
658
        if ($only_product) {
659
            $sql .= ' AND p.fk_product_type = 0';
660
        }
661
        if ($sqlforgedfilters) {
662
            $sql .= $sqlforgedfilters;
663
        }
664
        $sql .= ' ORDER by d.rang';
665
666
        dol_syslog(get_class($this) . "::fetch_lines", LOG_DEBUG);
667
        $result = $this->db->query($sql);
668
        if ($result) {
669
670
            $num = $this->db->num_rows($result);
671
672
            $i = 0;
673
            while ($i < $num) {
674
                $objp = $this->db->fetch_object($result);
675
676
                $line = new PropaleLigne($this->db);
677
678
                $line->rowid = $objp->rowid; //Deprecated
0 ignored issues
show
Deprecated Code introduced by
The property Dolibarr\Core\Base\CommonObjectLine::$rowid has been deprecated: Try to use id property as possible (even if field into database is still rowid) ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

678
                /** @scrutinizer ignore-deprecated */ $line->rowid = $objp->rowid; //Deprecated

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
679
                $line->id = $objp->rowid;
680
                $line->fk_propal = $objp->fk_propal;
681
                $line->fk_parent_line = $objp->fk_parent_line;
682
                $line->product_type = $objp->product_type;
683
                $line->label = $objp->custom_label;
684
                $line->desc = $objp->description; // Description ligne
685
                $line->description = $objp->description; // Description ligne
686
                $line->qty = $objp->qty;
687
                $line->vat_src_code = $objp->vat_src_code;
688
                $line->tva_tx = $objp->tva_tx;
689
                $line->localtax1_tx = $objp->localtax1_tx;
690
                $line->localtax2_tx = $objp->localtax2_tx;
691
                $line->localtax1_type = $objp->localtax1_type;
692
                $line->localtax2_type = $objp->localtax2_type;
693
                $line->subprice = $objp->subprice;
694
                $line->fk_remise_except = $objp->fk_remise_except;
695
                $line->remise_percent = $objp->remise_percent;
696
                $line->price = $objp->price; // TODO deprecated
697
698
                $line->info_bits = $objp->info_bits;
699
                $line->total_ht = $objp->total_ht;
700
                $line->total_tva = $objp->total_tva;
701
                $line->total_localtax1 = $objp->total_localtax1;
702
                $line->total_localtax2 = $objp->total_localtax2;
703
                $line->total_ttc = $objp->total_ttc;
704
                $line->fk_fournprice = $objp->fk_fournprice;
705
                $marginInfos = getMarginInfos($objp->subprice, $objp->remise_percent, $objp->tva_tx, $objp->localtax1_tx, $objp->localtax2_tx, $line->fk_fournprice, $objp->pa_ht);
706
                $line->pa_ht = $marginInfos[0];
707
                $line->marge_tx = $marginInfos[1];
708
                $line->marque_tx = $marginInfos[2];
709
                $line->special_code = $objp->special_code;
710
                $line->rang = $objp->rang;
711
712
                $line->fk_product = $objp->fk_product;
713
714
                $line->ref = $objp->product_ref; // deprecated
715
                $line->libelle = $objp->product_label; // deprecated
716
717
                $line->product_ref = $objp->product_ref;
718
                $line->product_label = $objp->product_label;
719
                $line->product_desc = $objp->product_desc; // Description produit
720
                $line->product_tobatch = $objp->product_tobatch;
721
                $line->product_barcode = $objp->product_barcode;
722
723
                $line->fk_product_type = $objp->fk_product_type; // deprecated
724
                $line->fk_unit = $objp->fk_unit;
725
                $line->weight = $objp->weight;
726
                $line->weight_units = $objp->weight_units;
727
                $line->volume = $objp->volume;
728
                $line->volume_units = $objp->volume_units;
729
730
                $line->date_start = $this->db->jdate($objp->date_start);
731
                $line->date_end = $this->db->jdate($objp->date_end);
732
733
                // Multicurrency
734
                $line->fk_multicurrency = $objp->fk_multicurrency;
735
                $line->multicurrency_code = $objp->multicurrency_code;
736
                $line->multicurrency_subprice = $objp->multicurrency_subprice;
737
                $line->multicurrency_total_ht = $objp->multicurrency_total_ht;
738
                $line->multicurrency_total_tva = $objp->multicurrency_total_tva;
739
                $line->multicurrency_total_ttc = $objp->multicurrency_total_ttc;
740
741
                $line->fetch_optionals();
742
743
                // multilangs
744
                if (getDolGlobalInt('MAIN_MULTILANGS') && !empty($objp->fk_product) && !empty($loadalsotranslation)) {
745
                    $tmpproduct = new Product($this->db);
746
                    $tmpproduct->fetch($objp->fk_product);
747
                    $tmpproduct->getMultiLangs();
748
749
                    $line->multilangs = $tmpproduct->multilangs;
750
                }
751
752
                $this->lines[$i] = $line;
753
754
                $i++;
755
            }
756
757
            $this->db->free($result);
758
759
            return $num;
760
        } else {
761
            $this->error = $this->db->lasterror();
762
            return -3;
763
        }
764
    }
765
766
    /**
767
     *  Adding line of fixed discount in the proposal in DB
768
     *
769
     * @param int $idremise Id of fixed discount
770
     * @return    int                          >0 if OK, <0 if KO
771
     */
772
    public function insert_discount($idremise)
773
    {
774
        // phpcs:enable
775
        global $langs;
776
777
        include_once DOL_DOCUMENT_ROOT . '/core/lib/price.lib.php';
778
        include_once DOL_DOCUMENT_ROOT . '/core/class/discount.class.php';
779
780
        $this->db->begin();
781
782
        $remise = new DiscountAbsolute($this->db);
783
        $result = $remise->fetch($idremise);
784
785
        if ($result > 0) {
786
            if ($remise->fk_facture) {  // Protection against multiple submission
787
                $this->error = $langs->trans("ErrorDiscountAlreadyUsed");
788
                $this->db->rollback();
789
                return -5;
790
            }
791
792
            $line = new PropaleLigne($this->db);
793
794
            $line->context = $this->context;
795
796
            $line->fk_propal = $this->id;
797
            $line->fk_remise_except = $remise->id;
798
            $line->desc = $remise->description; // Description ligne
799
            $line->vat_src_code = $remise->vat_src_code;
800
            $line->tva_tx = $remise->tva_tx;
801
            $line->subprice = -$remise->amount_ht;
802
            $line->fk_product = 0; // Id produit predefined
803
            $line->qty = 1;
804
            $line->remise_percent = 0;
805
            $line->rang = -1;
806
            $line->info_bits = 2;
807
808
            // TODO deprecated
809
            $line->price = -$remise->amount_ht;
810
811
            $line->total_ht = -$remise->amount_ht;
812
            $line->total_tva = -$remise->amount_tva;
813
            $line->total_ttc = -$remise->amount_ttc;
814
815
            $result = $line->insert();
816
            if ($result > 0) {
817
                $result = $this->update_price(1);
818
                if ($result > 0) {
819
                    $this->db->commit();
820
                    return 1;
821
                } else {
822
                    $this->db->rollback();
823
                    return -1;
824
                }
825
            } else {
826
                $this->error = $line->error;
827
                $this->errors = $line->errors;
828
                $this->db->rollback();
829
                return -2;
830
            }
831
        } else {
832
            $this->db->rollback();
833
            return -2;
834
        }
835
    }
836
837
    /**
838
     *  Update a proposal line
839
     *
840
     * @param int $rowid Id of line
841
     * @param float $pu Unit price (HT or TTC depending on price_base_type)
842
     * @param float $qty Quantity
843
     * @param float $remise_percent Discount on line
844
     * @param float|string $txtva VAT Rate (Can be '1.23' or '1.23 (ABC)')
845
     * @param float $txlocaltax1 Local tax 1 rate
846
     * @param float $txlocaltax2 Local tax 2 rate
847
     * @param string $desc Description
848
     * @param string $price_base_type HT or TTC
849
     * @param int $info_bits Miscellaneous information
850
     * @param int $special_code Special code (also used by externals modules!)
851
     * @param int $fk_parent_line Id of parent line (0 in most cases, used by modules adding sublevels into lines).
852
     * @param int $skip_update_total Keep fields total_xxx to 0 (used for special lines by some modules)
853
     * @param int $fk_fournprice Id of origin supplier price
854
     * @param int $pa_ht Price (without tax) of product when it was bought
855
     * @param string $label ???
856
     * @param int $type 0/1=Product/service
857
     * @param int|string $date_start Start date of the line
858
     * @param int|string $date_end End date of the line
859
     * @param array $array_options extrafields array
860
     * @param int|null $fk_unit Code of the unit to use. Null to use the default one
861
     * @param double $pu_ht_devise Unit price in currency
862
     * @param int $notrigger disable line update trigger
863
     * @param integer $rang line rank
864
     * @return     int                             0 if OK, <0 if KO
865
     */
866
    public function updateline($rowid, $pu, $qty, $remise_percent, $txtva, $txlocaltax1 = 0.0, $txlocaltax2 = 0.0, $desc = '', $price_base_type = 'HT', $info_bits = 0, $special_code = 0, $fk_parent_line = 0, $skip_update_total = 0, $fk_fournprice = 0, $pa_ht = 0, $label = '', $type = 0, $date_start = '', $date_end = '', $array_options = array(), $fk_unit = null, $pu_ht_devise = 0, $notrigger = 0, $rang = 0)
867
    {
868
        global $mysoc, $langs;
869
870
        dol_syslog(get_class($this) . "::updateLine rowid=$rowid, pu=$pu, qty=$qty, remise_percent=$remise_percent,
871
        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");
872
        include_once DOL_DOCUMENT_ROOT . '/core/lib/price.lib.php';
873
874
        // Clean parameters
875
        $remise_percent = price2num($remise_percent);
876
        $qty = (float)price2num($qty);
877
        $pu = price2num($pu);
878
        $pu_ht_devise = price2num($pu_ht_devise);
879
        if (!preg_match('/\((.*)\)/', (string)$txtva)) {
880
            $txtva = price2num($txtva); // $txtva can have format '5.0(XXX)' or '5'
881
        }
882
        $txlocaltax1 = price2num($txlocaltax1);
883
        $txlocaltax2 = price2num($txlocaltax2);
884
        $pa_ht = price2num($pa_ht);
885
        if (empty($qty) && empty($special_code)) {
886
            $special_code = 3; // Set option tag
887
        }
888
        if (!empty($qty) && $special_code == 3) {
889
            $special_code = 0; // Remove option tag
890
        }
891
        if (empty($type)) {
892
            $type = 0;
893
        }
894
895
        if ($date_start && $date_end && $date_start > $date_end) {
896
            $langs->load("errors");
897
            $this->error = $langs->trans('ErrorStartDateGreaterEnd');
898
            return -1;
899
        }
900
901
        if ($this->status == self::STATUS_DRAFT) {
902
            $this->db->begin();
903
904
            // Calcul du total TTC et de la TVA pour la ligne a partir de
905
            // qty, pu, remise_percent et txtva
906
            // TRES IMPORTANT: C'est au moment de l'insertion ligne qu'on doit stocker
907
            // la part ht, tva et ttc, et ce au niveau de la ligne qui a son propre taux tva.
908
909
            $localtaxes_type = getLocalTaxesFromRate($txtva, 0, $this->thirdparty, $mysoc);
910
911
            // Clean vat code
912
            $reg = array();
913
            $vat_src_code = '';
914
            if (preg_match('/\((.*)\)/', $txtva, $reg)) {
915
                $vat_src_code = $reg[1];
916
                $txtva = preg_replace('/\s*\(.*\)/', '', $txtva); // Remove code into vatrate.
917
            }
918
919
            // TODO Implement  if (getDolGlobalInt('MAIN_UNIT_PRICE_WITH_TAX_IS_FOR_ALL_TAXES')) ?
920
921
            $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);
922
            $total_ht = $tabprice[0];
923
            $total_tva = $tabprice[1];
924
            $total_ttc = $tabprice[2];
925
            $total_localtax1 = $tabprice[9];
926
            $total_localtax2 = $tabprice[10];
927
            $pu_ht = $tabprice[3];
928
            $pu_tva = $tabprice[4];
929
            $pu_ttc = $tabprice[5];
930
931
            // MultiCurrency
932
            $multicurrency_total_ht = $tabprice[16];
933
            $multicurrency_total_tva = $tabprice[17];
934
            $multicurrency_total_ttc = $tabprice[18];
935
            $pu_ht_devise = $tabprice[19];
936
937
            // Anciens indicateurs: $price, $remise (a ne plus utiliser)
938
            $price = $pu;
939
            $remise = 0;
940
            if ((float)$remise_percent > 0) {
941
                $remise = round(((float)$pu * (float)$remise_percent / 100), 2);
942
                $price = (float)$pu - $remise;
943
            }
944
945
            //Fetch current line from the database and then clone the object and set it in $oldline property
946
            $line = new PropaleLigne($this->db);
947
            $line->fetch($rowid);
948
949
            $staticline = clone $line;
950
951
            $line->oldline = $staticline;
952
            $this->line = $line;
953
            $this->line->context = $this->context;
954
            $this->line->rang = $rang;
955
956
            // Reorder if fk_parent_line change
957
            if (!empty($fk_parent_line) && !empty($staticline->fk_parent_line) && $fk_parent_line != $staticline->fk_parent_line) {
958
                $rangmax = $this->line_max($fk_parent_line);
959
                $this->line->rang = $rangmax + 1;
960
            }
961
962
            $this->line->id = $rowid;
963
            $this->line->label = $label;
964
            $this->line->desc = $desc;
965
            $this->line->qty = $qty;
966
            $this->line->product_type = $type;
967
            $this->line->vat_src_code = $vat_src_code;
968
            $this->line->tva_tx = $txtva;
969
            $this->line->localtax1_tx = $txlocaltax1;
970
            $this->line->localtax2_tx = $txlocaltax2;
971
            $this->line->localtax1_type = empty($localtaxes_type[0]) ? '' : $localtaxes_type[0];
972
            $this->line->localtax2_type = empty($localtaxes_type[2]) ? '' : $localtaxes_type[2];
973
            $this->line->remise_percent = $remise_percent;
974
            $this->line->subprice = $pu_ht;
975
            $this->line->info_bits = $info_bits;
976
977
            $this->line->total_ht = $total_ht;
978
            $this->line->total_tva = $total_tva;
979
            $this->line->total_localtax1 = $total_localtax1;
980
            $this->line->total_localtax2 = $total_localtax2;
981
            $this->line->total_ttc = $total_ttc;
982
            $this->line->special_code = $special_code;
983
            $this->line->fk_parent_line = $fk_parent_line;
984
            $this->line->skip_update_total = $skip_update_total;
985
            $this->line->fk_unit = $fk_unit;
986
987
            $this->line->fk_fournprice = $fk_fournprice;
988
            $this->line->pa_ht = $pa_ht;
989
990
            $this->line->date_start = $date_start;
991
            $this->line->date_end = $date_end;
992
993
            if (is_array($array_options) && count($array_options) > 0) {
994
                // We replace values in this->line->array_options only for entries defined into $array_options
995
                foreach ($array_options as $key => $value) {
996
                    $this->line->array_options[$key] = $array_options[$key];
997
                }
998
            }
999
1000
            // Multicurrency
1001
            $this->line->multicurrency_subprice = $pu_ht_devise;
1002
            $this->line->multicurrency_total_ht = $multicurrency_total_ht;
1003
            $this->line->multicurrency_total_tva = $multicurrency_total_tva;
1004
            $this->line->multicurrency_total_ttc = $multicurrency_total_ttc;
1005
1006
            $result = $this->line->update($notrigger);
1007
            if ($result > 0) {
1008
                // Reorder if child line
1009
                if (!empty($fk_parent_line)) {
1010
                    $this->line_order(true, 'DESC');
1011
                }
1012
1013
                $this->update_price(1, 'auto');
1014
1015
                // $this is Propal
1016
                // $this->fk_propal = $this->id;
1017
                // $this->rowid = $rowid;
1018
1019
                $this->db->commit();
1020
                return $result;
1021
            } else {
1022
                $this->error = $this->line->error;
1023
                $this->errors = $this->line->errors;
1024
                $this->db->rollback();
1025
                return -1;
1026
            }
1027
        } else {
1028
            dol_syslog(get_class($this) . "::updateline Erreur -2 Propal en mode incompatible pour cette action");
1029
            return -2;
1030
        }
1031
    }
1032
1033
    /**
1034
     *      Update database
1035
     *
1036
     * @param User $user User that modify
1037
     * @param int $notrigger 0=launch triggers after, 1=disable triggers
1038
     * @return     int                     Return integer <0 if KO, >0 if OK
1039
     */
1040
    public function update(User $user, $notrigger = 0)
1041
    {
1042
        global $conf;
1043
1044
        $error = 0;
1045
1046
        // Clean parameters
1047
        if (isset($this->ref)) {
1048
            $this->ref = trim($this->ref);
1049
        }
1050
        if (isset($this->ref_client)) {
1051
            $this->ref_client = trim($this->ref_client);
1052
        }
1053
        if (isset($this->note) || isset($this->note_private)) {
1054
            $this->note_private = (isset($this->note_private) ? trim($this->note_private) : trim($this->note));
1055
        }
1056
        if (isset($this->note_public)) {
1057
            $this->note_public = trim($this->note_public);
1058
        }
1059
        if (isset($this->model_pdf)) {
1060
            $this->model_pdf = trim($this->model_pdf);
1061
        }
1062
        if (isset($this->import_key)) {
1063
            $this->import_key = trim($this->import_key);
1064
        }
1065
        if (!empty($this->duree_validite) && is_numeric($this->duree_validite)) {
1066
            $this->fin_validite = $this->date + ($this->duree_validite * 24 * 3600);
1067
        }
1068
1069
        // Check parameters
1070
        // Put here code to add control on parameters values
1071
1072
        // Update request
1073
        $sql = "UPDATE " . MAIN_DB_PREFIX . "propal SET";
1074
        $sql .= " ref=" . (isset($this->ref) ? "'" . $this->db->escape($this->ref) . "'" : "null") . ",";
1075
        $sql .= " ref_client=" . (isset($this->ref_client) ? "'" . $this->db->escape($this->ref_client) . "'" : "null") . ",";
1076
        $sql .= " ref_ext=" . (isset($this->ref_ext) ? "'" . $this->db->escape($this->ref_ext) . "'" : "null") . ",";
1077
        $sql .= " fk_soc=" . (isset($this->socid) ? $this->socid : "null") . ",";
1078
        $sql .= " datep=" . (strval($this->date) != '' ? "'" . $this->db->idate($this->date) . "'" : 'null') . ",";
1079
        if (!empty($this->fin_validite)) {
1080
            $sql .= " fin_validite=" . (strval($this->fin_validite) != '' ? "'" . $this->db->idate($this->fin_validite) . "'" : 'null') . ",";
1081
        }
1082
        $sql .= " date_valid=" . (strval($this->date_validation) != '' ? "'" . $this->db->idate($this->date_validation) . "'" : 'null') . ",";
1083
        $sql .= " total_tva=" . (isset($this->total_tva) ? $this->total_tva : "null") . ",";
1084
        $sql .= " localtax1=" . (isset($this->total_localtax1) ? $this->total_localtax1 : "null") . ",";
1085
        $sql .= " localtax2=" . (isset($this->total_localtax2) ? $this->total_localtax2 : "null") . ",";
1086
        $sql .= " total_ht=" . (isset($this->total_ht) ? $this->total_ht : "null") . ",";
1087
        $sql .= " total_ttc=" . (isset($this->total_ttc) ? $this->total_ttc : "null") . ",";
1088
        $sql .= " fk_statut=" . (isset($this->statut) ? $this->statut : "null") . ",";
1089
        $sql .= " fk_user_author=" . (isset($this->user_author_id) ? $this->user_author_id : "null") . ",";
1090
        $sql .= " fk_user_valid=" . (isset($this->user_validation_id) ? $this->user_validation_id : "null") . ",";
1091
        $sql .= " fk_projet=" . (isset($this->fk_project) ? $this->fk_project : "null") . ",";
1092
        $sql .= " fk_cond_reglement=" . (isset($this->cond_reglement_id) ? $this->cond_reglement_id : "null") . ",";
1093
        $sql .= " deposit_percent=" . (!empty($this->deposit_percent) ? "'" . $this->db->escape($this->deposit_percent) . "'" : "null") . ",";
1094
        $sql .= " fk_mode_reglement=" . (isset($this->mode_reglement_id) ? $this->mode_reglement_id : "null") . ",";
1095
        $sql .= " fk_input_reason=" . (isset($this->demand_reason_id) ? $this->demand_reason_id : "null") . ",";
1096
        $sql .= " note_private=" . (isset($this->note_private) ? "'" . $this->db->escape($this->note_private) . "'" : "null") . ",";
1097
        $sql .= " note_public=" . (isset($this->note_public) ? "'" . $this->db->escape($this->note_public) . "'" : "null") . ",";
1098
        $sql .= " model_pdf=" . (isset($this->model_pdf) ? "'" . $this->db->escape($this->model_pdf) . "'" : "null") . ",";
1099
        $sql .= " import_key=" . (isset($this->import_key) ? "'" . $this->db->escape($this->import_key) . "'" : "null");
1100
        $sql .= " WHERE rowid=" . ((int)$this->id);
1101
1102
        $this->db->begin();
1103
1104
        dol_syslog(get_class($this) . "::update", LOG_DEBUG);
1105
        $resql = $this->db->query($sql);
1106
        if (!$resql) {
1107
            $error++;
1108
            $this->errors[] = "Error " . $this->db->lasterror();
1109
        }
1110
1111
        if (!$error) {
1112
            $result = $this->insertExtraFields();
1113
            if ($result < 0) {
1114
                $error++;
1115
            }
1116
        }
1117
1118
        if (!$error && !$notrigger) {
1119
            // Call trigger
1120
            $result = $this->call_trigger('PROPAL_MODIFY', $user);
1121
            if ($result < 0) {
1122
                $error++;
1123
            }
1124
            // End call triggers
1125
        }
1126
1127
        // Commit or rollback
1128
        if ($error) {
1129
            foreach ($this->errors as $errmsg) {
1130
                dol_syslog(get_class($this) . "::update " . $errmsg, LOG_ERR);
1131
                $this->error .= ($this->error ? ', ' . $errmsg : $errmsg);
1132
            }
1133
            $this->db->rollback();
1134
            return -1 * $error;
1135
        } else {
1136
            $this->db->commit();
1137
            return 1;
1138
        }
1139
    }
1140
1141
    /**
1142
     *  Delete detail line
1143
     *
1144
     * @param int $lineid Id of line to delete
1145
     * @param int $id Id of object (for a check)
1146
     * @return     int                     >0 if OK, <0 if KO
1147
     */
1148
    public function deleteLine($lineid, $id = 0)
1149
    {
1150
        global $user;
1151
1152
        if ($this->statut == self::STATUS_DRAFT) {
1153
            $this->db->begin();
1154
1155
            $line = new PropaleLigne($this->db);
1156
1157
            $line->context = $this->context;
1158
1159
            // Load data
1160
            $line->fetch($lineid);
1161
1162
            if ($id > 0 && $line->fk_propal != $id) {
1163
                $this->error = 'ErrorLineIDDoesNotMatchWithObjectID';
1164
                return -1;
1165
            }
1166
1167
            // Memorize previous line for triggers
1168
            $staticline = clone $line;
1169
            $line->oldline = $staticline;
1170
1171
            if ($line->delete($user) > 0) {
1172
                $this->update_price(1);
1173
1174
                $this->db->commit();
1175
                return 1;
1176
            } else {
1177
                $this->error = $line->error;
1178
                $this->errors = $line->errors;
1179
                $this->db->rollback();
1180
                return -1;
1181
            }
1182
        } else {
1183
            $this->error = 'ErrorDeleteLineNotAllowedByObjectStatus';
1184
            return -2;
1185
        }
1186
    }
1187
1188
1189
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1190
1191
    /**
1192
     *  Delete proposal
1193
     *
1194
     * @param User $user Object user that delete
1195
     * @param int $notrigger 1=Does not execute triggers, 0= execute triggers
1196
     * @return int                     >0 if OK, <=0 if KO
1197
     */
1198
    public function delete($user, $notrigger = 0)
1199
    {
1200
        global $conf;
1201
        require_once constant('DOL_DOCUMENT_ROOT') . '/core/lib/files.lib.php';
1202
1203
        $error = 0;
1204
1205
        $this->db->begin();
1206
1207
        if (!$notrigger) {
1208
            // Call trigger
1209
            $result = $this->call_trigger('PROPAL_DELETE', $user);
1210
            if ($result < 0) {
1211
                $error++;
1212
            }
1213
            // End call triggers
1214
        }
1215
1216
        // Delete extrafields of lines and lines
1217
        if (!$error && !empty($this->table_element_line)) {
1218
            $tabletodelete = $this->table_element_line;
1219
            $sqlef = "DELETE FROM " . MAIN_DB_PREFIX . $tabletodelete . "_extrafields WHERE fk_object IN (SELECT rowid FROM " . MAIN_DB_PREFIX . $tabletodelete . " WHERE " . $this->fk_element . " = " . ((int)$this->id) . ")";
1220
            $sql = "DELETE FROM " . MAIN_DB_PREFIX . $tabletodelete . " WHERE " . $this->fk_element . " = " . ((int)$this->id);
1221
            if (!$this->db->query($sqlef) || !$this->db->query($sql)) {
1222
                $error++;
1223
                $this->error = $this->db->lasterror();
1224
                $this->errors[] = $this->error;
1225
                dol_syslog(get_class($this) . "::delete error " . $this->error, LOG_ERR);
1226
            }
1227
        }
1228
1229
        if (!$error) {
1230
            // Delete linked object
1231
            $res = $this->deleteObjectLinked();
1232
            if ($res < 0) {
1233
                $error++;
1234
            }
1235
        }
1236
1237
        if (!$error) {
1238
            // Delete linked contacts
1239
            $res = $this->delete_linked_contact();
1240
            if ($res < 0) {
1241
                $error++;
1242
            }
1243
        }
1244
1245
        // Removed extrafields of object
1246
        if (!$error) {
1247
            $result = $this->deleteExtraFields();
1248
            if ($result < 0) {
1249
                $error++;
1250
                dol_syslog(get_class($this) . "::delete error " . $this->error, LOG_ERR);
1251
            }
1252
        }
1253
1254
        // Delete main record
1255
        if (!$error) {
1256
            $sql = "DELETE FROM " . MAIN_DB_PREFIX . $this->table_element . " WHERE rowid = " . ((int)$this->id);
1257
            $res = $this->db->query($sql);
1258
            if (!$res) {
1259
                $error++;
1260
                $this->error = $this->db->lasterror();
1261
                $this->errors[] = $this->error;
1262
                dol_syslog(get_class($this) . "::delete error " . $this->error, LOG_ERR);
1263
            }
1264
        }
1265
1266
        // Delete record into ECM index and physically
1267
        if (!$error) {
1268
            $res = $this->deleteEcmFiles(0); // Deleting files physically is done later with the dol_delete_dir_recursive
1269
            $res = $this->deleteEcmFiles(1); // Deleting files physically is done later with the dol_delete_dir_recursive
1270
            if (!$res) {
1271
                $error++;
1272
            }
1273
        }
1274
1275
        if (!$error) {
1276
            // We remove directory
1277
            $ref = dol_sanitizeFileName($this->ref);
1278
            if ($conf->propal->multidir_output[$this->entity] && !empty($this->ref)) {
1279
                $dir = $conf->propal->multidir_output[$this->entity] . "/" . $ref;
1280
                $file = $dir . "/" . $ref . ".pdf";
1281
                if (file_exists($file)) {
1282
                    dol_delete_preview($this);
1283
1284
                    if (!dol_delete_file($file, 0, 0, 0, $this)) {
1285
                        $this->error = 'ErrorFailToDeleteFile';
1286
                        $this->errors[] = $this->error;
1287
                        $this->db->rollback();
1288
                        return 0;
1289
                    }
1290
                }
1291
                if (file_exists($dir)) {
1292
                    $res = @dol_delete_dir_recursive($dir);     // delete files physically + into ecm tables
1293
                    if (!$res) {
1294
                        $this->error = 'ErrorFailToDeleteDir';
1295
                        $this->errors[] = $this->error;
1296
                        $this->db->rollback();
1297
                        return 0;
1298
                    }
1299
                }
1300
            }
1301
        }
1302
1303
        if (!$error) {
1304
            dol_syslog(get_class($this) . "::delete " . $this->id . " by " . $user->id, LOG_DEBUG);
1305
            $this->db->commit();
1306
            return 1;
1307
        } else {
1308
            $this->db->rollback();
1309
            return -1;
1310
        }
1311
    }
1312
1313
    /**
1314
     *      Load an object from its id and create a new one in database
1315
     *
1316
     * @param User $user User making the clone
1317
     * @param int $socid Id of thirdparty
1318
     * @param int $forceentity Entity id to force
1319
     * @param bool $update_prices [=false] Update prices if true
1320
     * @param bool $update_desc [=false] Update description if true
1321
     * @return     int                     New id of clone
1322
     */
1323
    public function createFromClone(User $user, $socid = 0, $forceentity = null, $update_prices = false, $update_desc = false)
1324
    {
1325
        global $conf, $hookmanager, $mysoc;
1326
1327
        dol_include_once('/projet/class/project.class.php');
1328
1329
        $error = 0;
1330
        $now = dol_now();
1331
1332
        dol_syslog(__METHOD__, LOG_DEBUG);
1333
1334
        $object = new self($this->db);
1335
1336
        $this->db->begin();
1337
1338
        // Load source object
1339
        $object->fetch($this->id);
1340
1341
        $objsoc = new Societe($this->db);
1342
1343
        // Change socid if needed
1344
        if (!empty($socid) && $socid != $object->socid) {
1345
            if ($objsoc->fetch($socid) > 0) {
1346
                $object->socid = $objsoc->id;
1347
                $object->cond_reglement_id = (!empty($objsoc->cond_reglement_id) ? $objsoc->cond_reglement_id : 0);
1348
                $object->deposit_percent = (!empty($objsoc->deposit_percent) ? $objsoc->deposit_percent : null);
1349
                $object->mode_reglement_id = (!empty($objsoc->mode_reglement_id) ? $objsoc->mode_reglement_id : 0);
1350
                $object->fk_delivery_address = 0;
1351
1352
                /*if (isModEnabled('project'))
1353
                {
1354
                    $project = new Project($db);
1355
                    if ($this->fk_project > 0 && $project->fetch($this->fk_project)) {
1356
                        if ($project->socid <= 0) $clonedObj->fk_project = $this->fk_project;
1357
                        else $clonedObj->fk_project = '';
1358
                    } else {
1359
                        $clonedObj->fk_project = '';
1360
                    }
1361
                }*/
1362
                $object->fk_project = 0; // A cloned proposal is set by default to no project.
1363
            }
1364
1365
            // reset ref_client
1366
            $object->ref_client = '';
1367
1368
            // TODO Change product price if multi-prices
1369
        } else {
1370
            $objsoc->fetch($object->socid);
1371
        }
1372
1373
        // update prices
1374
        if ($update_prices === true || $update_desc === true) {
1375
            if ($objsoc->id > 0 && !empty($object->lines)) {
1376
                if ($update_prices === true && getDolGlobalString('PRODUIT_CUSTOMER_PRICES')) {
1377
                    // If price per customer
1378
                }
1379
1380
                foreach ($object->lines as $line) {
1381
                    $line->id = 0;
1382
1383
                    if ($line->fk_product > 0) {
1384
                        $prod = new Product($this->db);
1385
                        $res = $prod->fetch($line->fk_product);
1386
                        if ($res > 0) {
1387
                            if ($update_prices === true) {
1388
                                $pu_ht = $prod->price;
1389
                                $tva_tx = get_default_tva($mysoc, $objsoc, $prod->id);
1390
                                $remise_percent = $objsoc->remise_percent;
1391
1392
                                if (getDolGlobalString('PRODUIT_MULTIPRICES') && $objsoc->price_level > 0) {
1393
                                    $pu_ht = $prod->multiprices[$objsoc->price_level];
1394
                                    if (getDolGlobalString('PRODUIT_MULTIPRICES_USE_VAT_PER_LEVEL')) {  // using this option is a bug. kept for backward compatibility
1395
                                        if (isset($prod->multiprices_tva_tx[$objsoc->price_level])) {
1396
                                            $tva_tx = $prod->multiprices_tva_tx[$objsoc->price_level];
1397
                                        }
1398
                                    }
1399
                                } elseif (getDolGlobalString('PRODUIT_CUSTOMER_PRICES')) {
1400
                                    $prodcustprice = new ProductCustomerPrice($this->db);
1401
                                    $filter = array('t.fk_product' => $prod->id, 't.fk_soc' => $objsoc->id);
1402
                                    $result = $prodcustprice->fetchAll('', '', 0, 0, $filter);
1403
                                    if ($result) {
1404
                                        // If there is some prices specific to the customer
1405
                                        if (count($prodcustprice->lines) > 0) {
1406
                                            $pu_ht = price($prodcustprice->lines[0]->price);
1407
                                            $tva_tx = ($prodcustprice->lines[0]->default_vat_code ? $prodcustprice->lines[0]->tva_tx . ' (' . $prodcustprice->lines[0]->default_vat_code . ' )' : $prodcustprice->lines[0]->tva_tx);
1408
                                            if ($prodcustprice->lines[0]->default_vat_code && !preg_match('/\(.*\)/', $tva_tx)) {
1409
                                                $tva_tx .= ' (' . $prodcustprice->lines[0]->default_vat_code . ')';
1410
                                            }
1411
                                        }
1412
                                    }
1413
                                }
1414
1415
                                $line->subprice = $pu_ht;
1416
                                $line->tva_tx = $tva_tx;
1417
                                $line->remise_percent = $remise_percent;
1418
                            }
1419
                            if ($update_desc === true) {
1420
                                $line->desc = $prod->description;
1421
                            }
1422
                        }
1423
                    }
1424
                }
1425
            }
1426
        }
1427
1428
        $object->id = 0;
1429
        $object->ref = '';
1430
        $object->entity = (!empty($forceentity) ? $forceentity : $object->entity);
1431
        $object->statut = self::STATUS_DRAFT;
1432
1433
        // Clear fields
1434
        $object->user_creation_id = $user->id;
1435
        $object->user_validation_id = 0;
1436
        $object->date = $now;
1437
        $object->datep = $now; // deprecated
1438
        $object->fin_validite = $object->date + ($object->duree_validite * 24 * 3600);
1439
        if (!getDolGlobalString('MAIN_KEEP_REF_CUSTOMER_ON_CLONING')) {
1440
            $object->ref_client = '';
1441
        }
1442
        if (getDolGlobalInt('MAIN_DONT_KEEP_NOTE_ON_CLONING') == 1) {
1443
            $object->note_private = '';
1444
            $object->note_public = '';
1445
        }
1446
        // Create clone
1447
        $object->context['createfromclone'] = 'createfromclone';
1448
        $result = $object->create($user);
1449
        if ($result < 0) {
1450
            $this->error = $object->error;
1451
            $this->errors = array_merge($this->errors, $object->errors);
1452
            $error++;
1453
        }
1454
1455
        if (!$error && !getDolGlobalInt('MAIN_IGNORE_CONTACTS_ON_CLONING')) {
1456
            // copy internal contacts
1457
            if ($object->copy_linked_contact($this, 'internal') < 0) {
1458
                $error++;
1459
            }
1460
        }
1461
1462
        if (!$error) {
1463
            // copy external contacts if same company
1464
            if ($this->socid == $object->socid) {
1465
                if ($object->copy_linked_contact($this, 'external') < 0) {
1466
                    $error++;
1467
                }
1468
            }
1469
        }
1470
1471
        if (!$error) {
1472
            // Hook of thirdparty module
1473
            if (is_object($hookmanager)) {
1474
                $parameters = array('objFrom' => $this, 'clonedObj' => $object);
1475
                $action = '';
1476
                $reshook = $hookmanager->executeHooks('createFrom', $parameters, $object, $action); // Note that $action and $object may have been modified by some hooks
1477
                if ($reshook < 0) {
1478
                    $this->setErrorsFromObject($hookmanager);
1479
                    $error++;
1480
                }
1481
            }
1482
        }
1483
1484
        unset($object->context['createfromclone']);
1485
1486
        // End
1487
        if (!$error) {
1488
            $this->db->commit();
1489
            return $object->id;
1490
        } else {
1491
            $this->db->rollback();
1492
            return -1;
1493
        }
1494
    }
1495
1496
1497
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1498
1499
    /**
1500
     *  Create commercial proposal into database
1501
     *  this->ref can be set or empty. If empty, we will use "(PROVid)"
1502
     *
1503
     * @param User $user User that create
1504
     * @param int $notrigger 1=Does not execute triggers, 0= execute triggers
1505
     * @return     int                 Return integer <0 if KO, >=0 if OK
1506
     */
1507
    public function create($user, $notrigger = 0)
1508
    {
1509
        global $conf, $hookmanager, $mysoc;
1510
        $error = 0;
1511
1512
        $now = dol_now();
1513
1514
        // Clean parameters
1515
        if (empty($this->date)) {
1516
            $this->date = $this->datep;
1517
        }
1518
        $this->fin_validite = $this->date + ($this->duree_validite * 24 * 3600);
1519
        if (empty($this->availability_id)) {
1520
            $this->availability_id = 0;
1521
        }
1522
        if (empty($this->demand_reason_id)) {
1523
            $this->demand_reason_id = 0;
1524
        }
1525
1526
        // Multicurrency (test on $this->multicurrency_tx because we should take the default rate only if not using origin rate)
1527
        if (!empty($this->multicurrency_code) && empty($this->multicurrency_tx)) {
1528
            list($this->fk_multicurrency, $this->multicurrency_tx) = MultiCurrency::getIdAndTxFromCode($this->db, $this->multicurrency_code, $this->date);
1529
        } else {
1530
            $this->fk_multicurrency = MultiCurrency::getIdFromCode($this->db, $this->multicurrency_code);
1531
        }
1532
        if (empty($this->fk_multicurrency)) {
1533
            $this->multicurrency_code = $conf->currency;
1534
            $this->fk_multicurrency = 0;
1535
            $this->multicurrency_tx = 1;
1536
        }
1537
1538
        // Set tmp vars
1539
        $delivery_date = $this->delivery_date;
1540
1541
        dol_syslog(get_class($this) . "::create");
1542
1543
        // Check parameters
1544
        $result = $this->fetch_thirdparty();
1545
        if ($result < 0) {
1546
            $this->error = "Failed to fetch company";
1547
            dol_syslog(get_class($this) . "::create " . $this->error, LOG_ERR);
1548
            return -3;
1549
        }
1550
1551
        // Check parameters
1552
        if (!empty($this->ref)) {   // We check that ref is not already used
1553
            $result = self::isExistingObject($this->element, 0, $this->ref); // Check ref is not yet used
1554
            if ($result > 0) {
1555
                $this->error = 'ErrorRefAlreadyExists';
1556
                dol_syslog(get_class($this) . "::create " . $this->error, LOG_WARNING);
1557
                $this->db->rollback();
1558
                return -1;
1559
            }
1560
        }
1561
1562
        if (empty($this->date)) {
1563
            $this->error = "Date of proposal is required";
1564
            dol_syslog(get_class($this) . "::create " . $this->error, LOG_ERR);
1565
            return -4;
1566
        }
1567
1568
1569
        $this->db->begin();
1570
1571
        // Insert into database
1572
        $sql = "INSERT INTO " . MAIN_DB_PREFIX . "propal (";
1573
        $sql .= "fk_soc";
1574
        $sql .= ", price";
1575
        $sql .= ", total_tva";
1576
        $sql .= ", total_ttc";
1577
        $sql .= ", datep";
1578
        $sql .= ", datec";
1579
        $sql .= ", ref";
1580
        $sql .= ", fk_user_author";
1581
        $sql .= ", note_private";
1582
        $sql .= ", note_public";
1583
        $sql .= ", model_pdf";
1584
        $sql .= ", fin_validite";
1585
        $sql .= ", fk_cond_reglement";
1586
        $sql .= ", deposit_percent";
1587
        $sql .= ", fk_mode_reglement";
1588
        $sql .= ", fk_account";
1589
        $sql .= ", ref_client";
1590
        $sql .= ", ref_ext";
1591
        $sql .= ", date_livraison";
1592
        $sql .= ", fk_shipping_method";
1593
        $sql .= ", fk_warehouse";
1594
        $sql .= ", fk_availability";
1595
        $sql .= ", fk_input_reason";
1596
        $sql .= ", fk_projet";
1597
        $sql .= ", fk_incoterms";
1598
        $sql .= ", location_incoterms";
1599
        $sql .= ", entity";
1600
        $sql .= ", fk_multicurrency";
1601
        $sql .= ", multicurrency_code";
1602
        $sql .= ", multicurrency_tx";
1603
        $sql .= ") ";
1604
        $sql .= " VALUES (";
1605
        $sql .= $this->socid;
1606
        $sql .= ", 0";
1607
        $sql .= ", 0";
1608
        $sql .= ", 0";
1609
        $sql .= ", '" . $this->db->idate($this->date) . "'";
1610
        $sql .= ", '" . $this->db->idate($now) . "'";
1611
        $sql .= ", '(PROV)'";
1612
        $sql .= ", " . ($user->id > 0 ? ((int)$user->id) : "NULL");
1613
        $sql .= ", '" . $this->db->escape($this->note_private) . "'";
1614
        $sql .= ", '" . $this->db->escape($this->note_public) . "'";
1615
        $sql .= ", '" . $this->db->escape($this->model_pdf) . "'";
1616
        $sql .= ", " . ($this->fin_validite != '' ? "'" . $this->db->idate($this->fin_validite) . "'" : "NULL");
1617
        $sql .= ", " . ($this->cond_reglement_id > 0 ? ((int)$this->cond_reglement_id) : 'NULL');
1618
        $sql .= ", " . (!empty($this->deposit_percent) ? "'" . $this->db->escape($this->deposit_percent) . "'" : 'NULL');
1619
        $sql .= ", " . ($this->mode_reglement_id > 0 ? ((int)$this->mode_reglement_id) : 'NULL');
1620
        $sql .= ", " . ($this->fk_account > 0 ? ((int)$this->fk_account) : 'NULL');
1621
        $sql .= ", '" . $this->db->escape($this->ref_client) . "'";
1622
        $sql .= ", '" . $this->db->escape($this->ref_ext) . "'";
1623
        $sql .= ", " . (empty($delivery_date) ? "NULL" : "'" . $this->db->idate($delivery_date) . "'");
1624
        $sql .= ", " . ($this->shipping_method_id > 0 ? $this->shipping_method_id : 'NULL');
1625
        $sql .= ", " . ($this->warehouse_id > 0 ? $this->warehouse_id : 'NULL');
1626
        $sql .= ", " . $this->availability_id;
1627
        $sql .= ", " . $this->demand_reason_id;
1628
        $sql .= ", " . ($this->fk_project ? $this->fk_project : "null");
1629
        $sql .= ", " . (int)$this->fk_incoterms;
1630
        $sql .= ", '" . $this->db->escape($this->location_incoterms) . "'";
1631
        $sql .= ", " . setEntity($this);
1632
        $sql .= ", " . (int)$this->fk_multicurrency;
1633
        $sql .= ", '" . $this->db->escape($this->multicurrency_code) . "'";
1634
        $sql .= ", " . (float)$this->multicurrency_tx;
1635
        $sql .= ")";
1636
1637
        dol_syslog(get_class($this) . "::create", LOG_DEBUG);
1638
        $resql = $this->db->query($sql);
1639
        if ($resql) {
1640
            $this->id = $this->db->last_insert_id(MAIN_DB_PREFIX . "propal");
1641
1642
            if ($this->id) {
1643
                $this->ref = '(PROV' . $this->id . ')';
1644
                $sql = 'UPDATE ' . MAIN_DB_PREFIX . "propal SET ref='" . $this->db->escape($this->ref) . "' WHERE rowid=" . ((int)$this->id);
1645
1646
                dol_syslog(get_class($this) . "::create", LOG_DEBUG);
1647
                $resql = $this->db->query($sql);
1648
                if (!$resql) {
1649
                    $error++;
1650
                }
1651
1652
                if (!empty($this->linkedObjectsIds) && empty($this->linked_objects)) {  // To use new linkedObjectsIds instead of old linked_objects
1653
                    $this->linked_objects = $this->linkedObjectsIds; // TODO Replace linked_objects with linkedObjectsIds
1654
                }
1655
1656
                // Add object linked
1657
                if (!$error && $this->id && !empty($this->linked_objects) && is_array($this->linked_objects)) {
1658
                    foreach ($this->linked_objects as $origin => $tmp_origin_id) {
1659
                        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, ...))
1660
                            foreach ($tmp_origin_id as $origin_id) {
1661
                                $ret = $this->add_object_linked($origin, $origin_id);
1662
                                if (!$ret) {
1663
                                    $this->error = $this->db->lasterror();
1664
                                    $error++;
1665
                                }
1666
                            }
1667
                        } else { // Old behaviour, if linked_object has only one link per type, so is something like array('contract'=>id1))
1668
                            $origin_id = $tmp_origin_id;
1669
                            $ret = $this->add_object_linked($origin, $origin_id);
1670
                            if (!$ret) {
1671
                                $this->error = $this->db->lasterror();
1672
                                $error++;
1673
                            }
1674
                        }
1675
                    }
1676
                }
1677
1678
                /*
1679
                 *  Insertion du detail des produits dans la base
1680
                 *  Insert products detail in database
1681
                 */
1682
                if (!$error) {
1683
                    $fk_parent_line = 0;
1684
                    $num = count($this->lines);
1685
1686
                    for ($i = 0; $i < $num; $i++) {
1687
                        if (!is_object($this->lines[$i])) { // If this->lines is not array of objects, coming from REST API
1688
                            // Convert into object this->lines[$i].
1689
                            $line = (object)$this->lines[$i];
1690
                        } else {
1691
                            $line = $this->lines[$i];
1692
                        }
1693
                        // Reset fk_parent_line for line that are not child lines or special product
1694
                        if (($line->product_type != 9 && empty($line->fk_parent_line)) || $line->product_type == 9) {
1695
                            $fk_parent_line = 0;
1696
                        }
1697
                        // Complete vat rate with code
1698
                        $vatrate = $line->tva_tx;
1699
                        if ($line->vat_src_code && !preg_match('/\(.*\)/', $vatrate)) {
1700
                            $vatrate .= ' (' . $line->vat_src_code . ')';
1701
                        }
1702
1703
                        if (getDolGlobalString('MAIN_CREATEFROM_KEEP_LINE_ORIGIN_INFORMATION')) {
1704
                            $originid = $line->origin_id;
1705
                            $origintype = $line->origin;
1706
                        } else {
1707
                            $originid = $line->id;
1708
                            $origintype = $this->element;
1709
                        }
1710
1711
                        $result = $this->addline(
1712
                            $line->desc,
1713
                            $line->subprice,
1714
                            $line->qty,
1715
                            $vatrate,
1716
                            $line->localtax1_tx,
1717
                            $line->localtax2_tx,
1718
                            $line->fk_product,
1719
                            $line->remise_percent,
1720
                            'HT',
1721
                            0,
1722
                            $line->info_bits,
1723
                            $line->product_type,
1724
                            $line->rang,
1725
                            $line->special_code,
1726
                            $fk_parent_line,
1727
                            $line->fk_fournprice,
1728
                            $line->pa_ht,
1729
                            $line->label,
1730
                            $line->date_start,
1731
                            $line->date_end,
1732
                            $line->array_options,
1733
                            $line->fk_unit,
1734
                            $origintype,
1735
                            $originid,
1736
                            0,
1737
                            0,
1738
                            1
1739
                        );
1740
1741
                        if ($result < 0) {
1742
                            $error++;
1743
                            $this->error = $this->db->error;
1744
                            dol_print_error($this->db);
1745
                            break;
1746
                        }
1747
1748
                        // Set the id on created row
1749
                        $line->id = $result;
1750
1751
                        // Defined the new fk_parent_line
1752
                        if ($result > 0 && $line->product_type == 9) {
1753
                            $fk_parent_line = $result;
1754
                        }
1755
                    }
1756
                }
1757
1758
                // Set delivery address
1759
                /*if (! $error && $this->fk_delivery_address)
1760
                {
1761
                    $sql = "UPDATE ".MAIN_DB_PREFIX."propal";
1762
                    $sql.= " SET fk_delivery_address = ".((int) $this->fk_delivery_address);
1763
                    $sql.= " WHERE ref = '".$this->db->escape($this->ref)."'";
1764
                    $sql.= " AND entity = ".setEntity($this);
1765
1766
                    $result=$this->db->query($sql);
1767
                }*/
1768
1769
                if (!$error) {
1770
                    // Mise a jour infos denormalisees
1771
                    $resql = $this->update_price(1, 'auto', 0, $mysoc);
1772
                    if ($resql) {
1773
                        $action = 'update';
1774
1775
                        // Actions on extra fields
1776
                        if (!$error) {
1777
                            $result = $this->insertExtraFields();
1778
                            if ($result < 0) {
1779
                                $error++;
1780
                            }
1781
                        }
1782
1783
                        if (!$error && !$notrigger) {
1784
                            // Call trigger
1785
                            $result = $this->call_trigger('PROPAL_CREATE', $user);
1786
                            if ($result < 0) {
1787
                                $error++;
1788
                            }
1789
                            // End call triggers
1790
                        }
1791
                    } else {
1792
                        $this->error = $this->db->lasterror();
1793
                        $error++;
1794
                    }
1795
                }
1796
            } else {
1797
                $this->error = $this->db->lasterror();
1798
                $error++;
1799
            }
1800
1801
            if (!$error) {
1802
                $this->db->commit();
1803
                dol_syslog(get_class($this) . "::create done id=" . $this->id);
1804
                return $this->id;
1805
            } else {
1806
                $this->db->rollback();
1807
                return -2;
1808
            }
1809
        } else {
1810
            $this->error = $this->db->lasterror();
1811
            $this->db->rollback();
1812
            return -1;
1813
        }
1814
    }
1815
1816
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1817
1818
    /**
1819
     *      Add a proposal line into database (linked to product/service or not)
1820
     *      The parameters are already supposed to be appropriate and with final values to the call
1821
     *      of this method. Also, for the VAT rate, it must have already been defined
1822
     *      by whose calling the method get_default_tva (societe_vendeuse, societe_acheteuse, '' product)
1823
     *      and desc must already have the right value (it's up to the caller to manage multilanguage)
1824
     *
1825
     * @param string $desc Description of line
1826
     * @param float $pu_ht Unit price
1827
     * @param float $qty Quantity
1828
     * @param float|string $txtva Force Vat rate, -1 for auto (Can contain the vat_src_code too with syntax '9.9 (CODE)')
1829
     * @param float $txlocaltax1 Local tax 1 rate (deprecated, use instead txtva with code inside)
1830
     * @param float $txlocaltax2 Local tax 2 rate (deprecated, use instead txtva with code inside)
1831
     * @param int $fk_product Product/Service ID predefined
1832
     * @param float $remise_percent Pourcentage de remise de la ligne
1833
     * @param string $price_base_type HT or TTC
1834
     * @param float $pu_ttc Prix unitaire TTC
1835
     * @param int $info_bits Bits for type of lines
1836
     * @param int $type Type of line (0=product, 1=service). Not used if fk_product is defined, the type of product is used.
1837
     * @param int $rang Position of line
1838
     * @param int $special_code Special code (also used by externals modules!)
1839
     * @param int $fk_parent_line Id of parent line
1840
     * @param int $fk_fournprice Id supplier price
1841
     * @param int $pa_ht Buying price without tax
1842
     * @param string $label ???
1843
     * @param int|string $date_start Start date of the line
1844
     * @param int|string $date_end End date of the line
1845
     * @param array $array_options extrafields array
1846
     * @param int|null $fk_unit Code of the unit to use. Null to use the default one
1847
     * @param string $origin Depend on global conf MAIN_CREATEFROM_KEEP_LINE_ORIGIN_INFORMATION can be 'orderdet', 'propaldet'..., else 'order','propal,'....
1848
     * @param int $origin_id Depend on global conf MAIN_CREATEFROM_KEEP_LINE_ORIGIN_INFORMATION can be Id of origin object (aka line id), else object id
1849
     * @param double $pu_ht_devise Unit price in currency
1850
     * @param int $fk_remise_except Id discount if line is from a discount
1851
     * @param int $noupdateafterinsertline No update after insert of line
1852
     * @return     int                             >0 if OK, <0 if KO
1853
     * @see        add_product()
1854
     */
1855
    public function addline($desc, $pu_ht, $qty, $txtva, $txlocaltax1 = 0.0, $txlocaltax2 = 0.0, $fk_product = 0, $remise_percent = 0.0, $price_base_type = 'HT', $pu_ttc = 0.0, $info_bits = 0, $type = 0, $rang = -1, $special_code = 0, $fk_parent_line = 0, $fk_fournprice = 0, $pa_ht = 0, $label = '', $date_start = '', $date_end = '', $array_options = array(), $fk_unit = null, $origin = '', $origin_id = 0, $pu_ht_devise = 0, $fk_remise_except = 0, $noupdateafterinsertline = 0)
1856
    {
1857
        global $mysoc, $conf, $langs;
1858
1859
        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);
1860
1861
        if ($this->statut == self::STATUS_DRAFT) {
1862
            include_once DOL_DOCUMENT_ROOT . '/core/lib/price.lib.php';
1863
1864
            // Clean parameters
1865
            if (empty($remise_percent)) {
1866
                $remise_percent = 0;
1867
            }
1868
            if (empty($qty)) {
1869
                $qty = 0;
1870
            }
1871
            if (empty($info_bits)) {
1872
                $info_bits = 0;
1873
            }
1874
            if (empty($rang)) {
1875
                $rang = 0;
1876
            }
1877
            if (empty($fk_parent_line) || $fk_parent_line < 0) {
1878
                $fk_parent_line = 0;
1879
            }
1880
1881
            $remise_percent = price2num($remise_percent);
1882
            $qty = (float)price2num($qty);
1883
            $pu_ht = price2num($pu_ht);
1884
            $pu_ht_devise = price2num($pu_ht_devise);
1885
            $pu_ttc = price2num($pu_ttc);
1886
            if (!preg_match('/\((.*)\)/', (string)$txtva)) {
1887
                $txtva = price2num($txtva); // $txtva can have format '5,1' or '5.1' or '5.1(XXX)', we must clean only if '5,1'
1888
            }
1889
            $txlocaltax1 = price2num($txlocaltax1);
1890
            $txlocaltax2 = price2num($txlocaltax2);
1891
            $pa_ht = price2num($pa_ht);
1892
            if ($price_base_type == 'HT') {
1893
                $pu = $pu_ht;
1894
            } else {
1895
                $pu = $pu_ttc;
1896
            }
1897
1898
            // Check parameters
1899
            if ($type < 0) {
1900
                return -1;
1901
            }
1902
1903
            if ($date_start && $date_end && $date_start > $date_end) {
1904
                $langs->load("errors");
1905
                $this->error = $langs->trans('ErrorStartDateGreaterEnd');
1906
                return -1;
1907
            }
1908
1909
            $this->db->begin();
1910
1911
            $product_type = $type;
1912
            if (!empty($fk_product) && $fk_product > 0) {
1913
                $product = new Product($this->db);
1914
                $result = $product->fetch($fk_product);
1915
                $product_type = $product->type;
1916
1917
                if (getDolGlobalString('STOCK_MUST_BE_ENOUGH_FOR_PROPOSAL') && $product_type == 0 && $product->stock_reel < $qty) {
1918
                    $langs->load("errors");
1919
                    $this->error = $langs->trans('ErrorStockIsNotEnoughToAddProductOnProposal', $product->ref);
1920
                    $this->db->rollback();
1921
                    return -3;
1922
                }
1923
            }
1924
1925
            // Calcul du total TTC et de la TVA pour la ligne a partir de
1926
            // qty, pu, remise_percent et txtva
1927
            // TRES IMPORTANT: C'est au moment de l'insertion ligne qu'on doit stocker
1928
            // la part ht, tva et ttc, et ce au niveau de la ligne qui a son propre taux tva.
1929
1930
            $localtaxes_type = getLocalTaxesFromRate($txtva, 0, $this->thirdparty, $mysoc);
1931
1932
            // Clean vat code
1933
            $reg = array();
1934
            $vat_src_code = '';
1935
            if (preg_match('/\((.*)\)/', $txtva, $reg)) {
1936
                $vat_src_code = $reg[1];
1937
                $txtva = preg_replace('/\s*\(.*\)/', '', $txtva); // Remove code into vatrate.
1938
            }
1939
1940
            $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);
1941
1942
            $total_ht = $tabprice[0];
1943
            $total_tva = $tabprice[1];
1944
            $total_ttc = $tabprice[2];
1945
            $total_localtax1 = $tabprice[9];
1946
            $total_localtax2 = $tabprice[10];
1947
            $pu_ht = $tabprice[3];
1948
            $pu_tva = $tabprice[4];
1949
            $pu_ttc = $tabprice[5];
1950
1951
            // MultiCurrency
1952
            $multicurrency_total_ht = $tabprice[16];
1953
            $multicurrency_total_tva = $tabprice[17];
1954
            $multicurrency_total_ttc = $tabprice[18];
1955
            $pu_ht_devise = $tabprice[19];
1956
1957
            // Rang to use
1958
            $ranktouse = $rang;
1959
            if ($ranktouse == -1) {
1960
                $rangmax = $this->line_max($fk_parent_line);
1961
                $ranktouse = $rangmax + 1;
1962
            }
1963
1964
            // TODO A virer
1965
            // Anciens indicateurs: $price, $remise (a ne plus utiliser)
1966
            $price = $pu;
1967
            $remise = 0;
1968
            if ((float)$remise_percent > 0) {
1969
                $remise = round(((float)$pu * (float)$remise_percent / 100), 2);
1970
                $price = (float)$pu - $remise;
1971
            }
1972
1973
            // Insert line
1974
            $this->line = new PropaleLigne($this->db);
1975
1976
            $this->line->context = $this->context;
1977
1978
            $this->line->fk_propal = $this->id;
1979
            $this->line->label = $label;
1980
            $this->line->desc = $desc;
1981
            $this->line->qty = $qty;
1982
1983
            $this->line->vat_src_code = $vat_src_code;
1984
            $this->line->tva_tx = $txtva;
1985
            $this->line->localtax1_tx = ($total_localtax1 ? $localtaxes_type[1] : 0);
1986
            $this->line->localtax2_tx = ($total_localtax2 ? $localtaxes_type[3] : 0);
1987
            $this->line->localtax1_type = empty($localtaxes_type[0]) ? '' : $localtaxes_type[0];
1988
            $this->line->localtax2_type = empty($localtaxes_type[2]) ? '' : $localtaxes_type[2];
1989
            $this->line->fk_product = $fk_product;
1990
            $this->line->product_type = $type;
1991
            $this->line->fk_remise_except = $fk_remise_except;
1992
            $this->line->remise_percent = $remise_percent;
1993
            $this->line->subprice = $pu_ht;
1994
            $this->line->rang = $ranktouse;
1995
            $this->line->info_bits = $info_bits;
1996
            $this->line->total_ht = $total_ht;
1997
            $this->line->total_tva = $total_tva;
1998
            $this->line->total_localtax1 = $total_localtax1;
1999
            $this->line->total_localtax2 = $total_localtax2;
2000
            $this->line->total_ttc = $total_ttc;
2001
            $this->line->special_code = $special_code;
2002
            $this->line->fk_parent_line = $fk_parent_line;
2003
            $this->line->fk_unit = $fk_unit;
2004
2005
            $this->line->date_start = $date_start;
2006
            $this->line->date_end = $date_end;
2007
2008
            $this->line->fk_fournprice = $fk_fournprice;
2009
            $this->line->pa_ht = $pa_ht;
2010
2011
            $this->line->origin_id = $origin_id;
2012
            $this->line->origin = $origin;
0 ignored issues
show
Deprecated Code introduced by
The property Dolibarr\Core\Base\CommonObject::$origin has been deprecated: Use $origin_type and $origin_id instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

2012
            /** @scrutinizer ignore-deprecated */ $this->line->origin = $origin;

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
2013
2014
            // Multicurrency
2015
            $this->line->fk_multicurrency = $this->fk_multicurrency;
2016
            $this->line->multicurrency_code = $this->multicurrency_code;
2017
            $this->line->multicurrency_subprice = $pu_ht_devise;
2018
            $this->line->multicurrency_total_ht = $multicurrency_total_ht;
2019
            $this->line->multicurrency_total_tva = $multicurrency_total_tva;
2020
            $this->line->multicurrency_total_ttc = $multicurrency_total_ttc;
2021
2022
            // Mise en option de la ligne
2023
            if (empty($qty) && empty($special_code)) {
2024
                $this->line->special_code = 3;
2025
            }
2026
2027
            // TODO deprecated
2028
            $this->line->price = $price;
2029
2030
            if (is_array($array_options) && count($array_options) > 0) {
2031
                $this->line->array_options = $array_options;
2032
            }
2033
2034
            $result = $this->line->insert();
2035
            if ($result > 0) {
2036
                // Reorder if child line
2037
                if (!empty($fk_parent_line)) {
2038
                    $this->line_order(true, 'DESC');
2039
                } elseif ($ranktouse > 0 && $ranktouse <= count($this->lines)) { // Update all rank of all other lines
2040
                    $linecount = count($this->lines);
2041
                    for ($ii = $ranktouse; $ii <= $linecount; $ii++) {
2042
                        $this->updateRangOfLine($this->lines[$ii - 1]->id, $ii + 1);
2043
                    }
2044
                }
2045
2046
                // Mise a jour information denormalisees au niveau de la propale meme
2047
                if (empty($noupdateafterinsertline)) {
2048
                    $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.
2049
                }
2050
2051
                if ($result > 0) {
2052
                    $this->db->commit();
2053
                    return $this->line->id;
2054
                } else {
2055
                    $this->error = $this->db->error();
2056
                    $this->db->rollback();
2057
                    return -1;
2058
                }
2059
            } else {
2060
                $this->error = $this->line->error;
2061
                $this->errors = $this->line->errors;
2062
                $this->db->rollback();
2063
                return -2;
2064
            }
2065
        } else {
2066
            dol_syslog(get_class($this) . "::addline status of proposal must be Draft to allow use of ->addline()", LOG_ERR);
2067
            return -3;
2068
        }
2069
    }
2070
2071
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2072
2073
    /**
2074
     *  Set status to validated
2075
     *
2076
     * @param User $user Object user that validate
2077
     * @param int $notrigger 1=Does not execute triggers, 0=execute triggers
2078
     * @return int                 Return integer <0 if KO, 0=Nothing done, >=0 if OK
2079
     */
2080
    public function valid($user, $notrigger = 0)
2081
    {
2082
        global $conf;
2083
2084
        require_once constant('DOL_DOCUMENT_ROOT') . '/core/lib/files.lib.php';
2085
2086
        $error = 0;
2087
2088
        // Protection
2089
        if ($this->statut == self::STATUS_VALIDATED) {
2090
            dol_syslog(get_class($this) . "::valid action abandoned: already validated", LOG_WARNING);
2091
            return 0;
2092
        }
2093
2094
        if (
2095
            !((!getDolGlobalString('MAIN_USE_ADVANCED_PERMS') && $user->hasRight('propal', 'creer'))
2096
                || (getDolGlobalString('MAIN_USE_ADVANCED_PERMS') && $user->hasRight('propal', 'propal_advance', 'validate')))
2097
        ) {
2098
            $this->error = 'ErrorPermissionDenied';
2099
            dol_syslog(get_class($this) . "::valid " . $this->error, LOG_ERR);
2100
            return -1;
2101
        }
2102
2103
        $now = dol_now();
2104
2105
        $this->db->begin();
2106
2107
        // Numbering module definition
2108
        $soc = new Societe($this->db);
2109
        $soc->fetch($this->socid);
2110
2111
        // Define new ref
2112
        if (!$error && (preg_match('/^[\(]?PROV/i', $this->ref) || empty($this->ref))) { // empty should not happened, but when it occurs, the test save life
2113
            $num = $this->getNextNumRef($soc);
2114
        } else {
2115
            $num = $this->ref;
2116
        }
2117
        $this->newref = dol_sanitizeFileName($num);
2118
2119
        $sql = "UPDATE " . MAIN_DB_PREFIX . "propal";
2120
        $sql .= " SET ref = '" . $this->db->escape($num) . "',";
2121
        $sql .= " fk_statut = " . self::STATUS_VALIDATED . ", date_valid='" . $this->db->idate($now) . "', fk_user_valid=" . ((int)$user->id);
2122
        $sql .= " WHERE rowid = " . ((int)$this->id) . " AND fk_statut = " . self::STATUS_DRAFT;
2123
2124
        dol_syslog(get_class($this) . "::valid", LOG_DEBUG);
2125
        $resql = $this->db->query($sql);
2126
        if (!$resql) {
2127
            dol_print_error($this->db);
2128
            $error++;
2129
        }
2130
2131
        // Trigger calls
2132
        if (!$error && !$notrigger) {
2133
            // Call trigger
2134
            $result = $this->call_trigger('PROPAL_VALIDATE', $user);
2135
            if ($result < 0) {
2136
                $error++;
2137
            }
2138
            // End call triggers
2139
        }
2140
2141
        if (!$error) {
2142
            $this->oldref = $this->ref;
2143
2144
            // Rename directory if dir was a temporary ref
2145
            if (preg_match('/^[\(]?PROV/i', $this->ref)) {
2146
                // Now we rename also files into index
2147
                $sql = 'UPDATE ' . MAIN_DB_PREFIX . "ecm_files set filename = CONCAT('" . $this->db->escape($this->newref) . "', SUBSTR(filename, " . (strlen($this->ref) + 1) . ")), filepath = 'propale/" . $this->db->escape($this->newref) . "'";
2148
                $sql .= " WHERE filename LIKE '" . $this->db->escape($this->ref) . "%' AND filepath = 'propale/" . $this->db->escape($this->ref) . "' and entity = " . ((int)$conf->entity);
2149
                $resql = $this->db->query($sql);
2150
                if (!$resql) {
2151
                    $error++;
2152
                    $this->error = $this->db->lasterror();
2153
                }
2154
                $sql = 'UPDATE ' . MAIN_DB_PREFIX . "ecm_files set filepath = 'propale/" . $this->db->escape($this->newref) . "'";
2155
                $sql .= " WHERE filepath = 'propale/" . $this->db->escape($this->ref) . "' and entity = " . $conf->entity;
2156
                $resql = $this->db->query($sql);
2157
                if (!$resql) {
2158
                    $error++;
2159
                    $this->error = $this->db->lasterror();
2160
                }
2161
2162
                // We rename directory ($this->ref = old ref, $num = new ref) in order not to lose the attachments
2163
                $oldref = dol_sanitizeFileName($this->ref);
2164
                $newref = dol_sanitizeFileName($num);
2165
                $dirsource = $conf->propal->multidir_output[$this->entity] . '/' . $oldref;
2166
                $dirdest = $conf->propal->multidir_output[$this->entity] . '/' . $newref;
2167
                if (!$error && file_exists($dirsource)) {
2168
                    dol_syslog(get_class($this) . "::validate rename dir " . $dirsource . " into " . $dirdest);
2169
                    if (@rename($dirsource, $dirdest)) {
2170
                        dol_syslog("Rename ok");
2171
                        // Rename docs starting with $oldref with $newref
2172
                        $listoffiles = dol_dir_list($dirdest, 'files', 1, '^' . preg_quote($oldref, '/'));
2173
                        foreach ($listoffiles as $fileentry) {
2174
                            $dirsource = $fileentry['name'];
2175
                            $dirdest = preg_replace('/^' . preg_quote($oldref, '/') . '/', $newref, $dirsource);
2176
                            $dirsource = $fileentry['path'] . '/' . $dirsource;
2177
                            $dirdest = $fileentry['path'] . '/' . $dirdest;
2178
                            @rename($dirsource, $dirdest);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for rename(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unhandled  annotation

2178
                            /** @scrutinizer ignore-unhandled */ @rename($dirsource, $dirdest);

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
2179
                        }
2180
                    }
2181
                }
2182
            }
2183
2184
            $this->ref = $num;
2185
            $this->statut = self::STATUS_VALIDATED;
2186
            $this->status = self::STATUS_VALIDATED;
2187
            $this->user_validation_id = $user->id;
2188
            $this->datev = $now;
2189
            $this->date_validation = $now;
2190
2191
            $this->db->commit();
2192
            return 1;
2193
        } else {
2194
            $this->db->rollback();
2195
            return -1;
2196
        }
2197
    }
2198
2199
    /**
2200
     *  Returns the reference to the following non used Proposal used depending on the active numbering module
2201
     *  defined into PROPALE_ADDON
2202
     *
2203
     * @param Societe $soc Object thirdparty
2204
     * @return string              Reference libre pour la propale
2205
     */
2206
    public function getNextNumRef($soc)
2207
    {
2208
        global $conf, $langs;
2209
        $langs->load("propal");
2210
2211
        $classname = getDolGlobalString('PROPALE_ADDON');
2212
2213
        if (!empty($classname)) {
2214
            $mybool = false;
2215
2216
            $file = $classname . ".php";
2217
2218
            // Include file with class
2219
            $dirmodels = array_merge(array('/'), (array)$conf->modules_parts['models']);
2220
            foreach ($dirmodels as $reldir) {
2221
                $dir = dol_buildpath($reldir . "core/modules/propale/");
2222
2223
                // Load file with numbering class (if found)
2224
                $mybool = ((bool)@include_once $dir . $file) || $mybool;
2225
            }
2226
2227
            if (!$mybool) {
2228
                dol_print_error(null, "Failed to include file " . $file);
2229
                return '';
2230
            }
2231
2232
            $obj = new $classname();
2233
            $numref = "";
2234
            $numref = $obj->getNextValue($soc, $this);
2235
2236
            if ($numref != "") {
2237
                return $numref;
2238
            } else {
2239
                $this->error = $obj->error;
2240
                //dol_print_error($db,"Propale::getNextNumRef ".$obj->error);
2241
                return "";
2242
            }
2243
        } else {
2244
            $langs->load("errors");
2245
            print $langs->trans("Error") . " " . $langs->trans("ErrorModuleSetupNotComplete", $langs->transnoentitiesnoconv("Proposal"));
2246
            return "";
2247
        }
2248
    }
2249
2250
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2251
2252
    /**
2253
     *  Define proposal date
2254
     *
2255
     * @param User $user Object user that modify
2256
     * @param int $date Date
2257
     * @param int $notrigger 1=Does not execute triggers, 0= execute triggers
2258
     * @return int                     Return integer <0 if KO, >0 if OK
2259
     */
2260
    public function set_date($user, $date, $notrigger = 0)
2261
    {
2262
        // phpcs:enable
2263
        if (empty($date)) {
2264
            $this->error = 'ErrorBadParameter';
2265
            dol_syslog(get_class($this) . "::set_date " . $this->error, LOG_ERR);
2266
            return -1;
2267
        }
2268
2269
        if ($user->hasRight('propal', 'creer')) {
2270
            $error = 0;
2271
2272
            $this->db->begin();
2273
2274
            $sql = "UPDATE " . MAIN_DB_PREFIX . "propal SET datep = '" . $this->db->idate($date) . "'";
2275
            $sql .= " WHERE rowid = " . ((int)$this->id);
2276
2277
            dol_syslog(__METHOD__, LOG_DEBUG);
2278
            $resql = $this->db->query($sql);
2279
            if (!$resql) {
2280
                $this->errors[] = $this->db->error();
2281
                $error++;
2282
            }
2283
2284
            if (!$error) {
2285
                $this->oldcopy = clone $this;
2286
                $this->date = $date;
2287
                $this->datep = $date; // deprecated
2288
            }
2289
2290
            if (!$notrigger && empty($error)) {
2291
                // Call trigger
2292
                $result = $this->call_trigger('PROPAL_MODIFY', $user);
2293
                if ($result < 0) {
2294
                    $error++;
2295
                }
2296
                // End call triggers
2297
            }
2298
2299
            if (!$error) {
2300
                $this->db->commit();
2301
                return 1;
2302
            } else {
2303
                foreach ($this->errors as $errmsg) {
2304
                    dol_syslog(__METHOD__ . ' Error: ' . $errmsg, LOG_ERR);
2305
                    $this->error .= ($this->error ? ', ' . $errmsg : $errmsg);
2306
                }
2307
                $this->db->rollback();
2308
                return -1 * $error;
2309
            }
2310
        }
2311
2312
        return -1;
2313
    }
2314
2315
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2316
2317
    /**
2318
     *  Define end validity date
2319
     *
2320
     * @param User $user Object user that modify
2321
     * @param int $date_end_validity End of validity date
2322
     * @param int $notrigger 1=Does not execute triggers, 0= execute triggers
2323
     * @return     int                         Return integer <0 if KO, >0 if OK
2324
     */
2325
    public function set_echeance($user, $date_end_validity, $notrigger = 0)
2326
    {
2327
        // phpcs:enable
2328
        if ($user->hasRight('propal', 'creer')) {
2329
            $error = 0;
2330
2331
            $this->db->begin();
2332
2333
            $sql = "UPDATE " . MAIN_DB_PREFIX . "propal SET fin_validite = " . ($date_end_validity != '' ? "'" . $this->db->idate($date_end_validity) . "'" : 'null');
2334
            $sql .= " WHERE rowid = " . ((int)$this->id);
2335
2336
            dol_syslog(__METHOD__, LOG_DEBUG);
2337
2338
            $resql = $this->db->query($sql);
2339
            if (!$resql) {
2340
                $this->errors[] = $this->db->error();
2341
                $error++;
2342
            }
2343
2344
2345
            if (!$error) {
2346
                $this->oldcopy = clone $this;
2347
                $this->fin_validite = $date_end_validity;
2348
            }
2349
2350
            if (!$notrigger && empty($error)) {
2351
                // Call trigger
2352
                $result = $this->call_trigger('PROPAL_MODIFY', $user);
2353
                if ($result < 0) {
2354
                    $error++;
2355
                }
2356
                // End call triggers
2357
            }
2358
2359
            if (!$error) {
2360
                $this->db->commit();
2361
                return 1;
2362
            } else {
2363
                foreach ($this->errors as $errmsg) {
2364
                    dol_syslog(__METHOD__ . ' Error: ' . $errmsg, LOG_ERR);
2365
                    $this->error .= ($this->error ? ', ' . $errmsg : $errmsg);
2366
                }
2367
                $this->db->rollback();
2368
                return -1 * $error;
2369
            }
2370
        }
2371
2372
        return -1;
2373
    }
2374
2375
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2376
2377
    /**
2378
     *  Set delivery date
2379
     *
2380
     * @param User $user Object user that modify
2381
     * @param int $delivery_date Delivery date
2382
     * @param int $notrigger 1=Does not execute triggers, 0= execute triggers
2383
     * @return     int                         Return integer <0 if ko, >0 if ok
2384
     * @deprecated Use  setDeliveryDate
2385
     */
2386
    public function set_date_livraison($user, $delivery_date, $notrigger = 0)
2387
    {
2388
        // phpcs:enable
2389
        return $this->setDeliveryDate($user, $delivery_date, $notrigger);
2390
    }
2391
2392
    /**
2393
     *  Set delivery date
2394
     *
2395
     * @param User $user Object user that modify
2396
     * @param int $delivery_date Delivery date
2397
     * @param int $notrigger 1=Does not execute triggers, 0= execute triggers
2398
     * @return     int                         Return integer <0 if ko, >0 if ok
2399
     */
2400
    public function setDeliveryDate($user, $delivery_date, $notrigger = 0)
2401
    {
2402
        if ($user->hasRight('propal', 'creer')) {
2403
            $error = 0;
2404
2405
            $this->db->begin();
2406
2407
            $sql = "UPDATE " . MAIN_DB_PREFIX . "propal ";
2408
            $sql .= " SET date_livraison = " . ($delivery_date != '' ? "'" . $this->db->idate($delivery_date) . "'" : 'null');
2409
            $sql .= " WHERE rowid = " . ((int)$this->id);
2410
2411
            dol_syslog(__METHOD__, LOG_DEBUG);
2412
            $resql = $this->db->query($sql);
2413
            if (!$resql) {
2414
                $this->errors[] = $this->db->error();
2415
                $error++;
2416
            }
2417
2418
            if (!$error) {
2419
                $this->oldcopy = clone $this;
2420
                $this->delivery_date = $delivery_date;
2421
            }
2422
2423
            if (!$notrigger && empty($error)) {
2424
                // Call trigger
2425
                $result = $this->call_trigger('PROPAL_MODIFY', $user);
2426
                if ($result < 0) {
2427
                    $error++;
2428
                }
2429
                // End call triggers
2430
            }
2431
2432
            if (!$error) {
2433
                $this->db->commit();
2434
                return 1;
2435
            } else {
2436
                foreach ($this->errors as $errmsg) {
2437
                    dol_syslog(__METHOD__ . ' Error: ' . $errmsg, LOG_ERR);
2438
                    $this->error .= ($this->error ? ', ' . $errmsg : $errmsg);
2439
                }
2440
                $this->db->rollback();
2441
                return -1 * $error;
2442
            }
2443
        }
2444
2445
        return -1;
2446
    }
2447
2448
    /**
2449
     *  Set delivery
2450
     *
2451
     * @param User $user Object user that modify
2452
     * @param int $id Availability id
2453
     * @param int $notrigger 1=Does not execute triggers, 0= execute triggers
2454
     * @return     int                     Return integer <0 if KO, >0 if OK
2455
     */
2456
    public function set_availability($user, $id, $notrigger = 0)
2457
    {
2458
        // phpcs:enable
2459
        if ($user->hasRight('propal', 'creer') && $this->statut >= self::STATUS_DRAFT) {
2460
            $error = 0;
2461
2462
            $this->db->begin();
2463
2464
            $sql = "UPDATE " . MAIN_DB_PREFIX . "propal";
2465
            $sql .= " SET fk_availability = " . ((int)$id);
2466
            $sql .= " WHERE rowid = " . ((int)$this->id);
2467
2468
            dol_syslog(__METHOD__ . ' availability(' . $id . ')', LOG_DEBUG);
2469
            $resql = $this->db->query($sql);
2470
            if (!$resql) {
2471
                $this->errors[] = $this->db->error();
2472
                $error++;
2473
            }
2474
2475
            if (!$error) {
2476
                $this->oldcopy = clone $this;
2477
                $this->fk_availability = $id;
2478
                $this->availability_id = $id;
2479
            }
2480
2481
            if (!$notrigger && empty($error)) {
2482
                // Call trigger
2483
                $result = $this->call_trigger('PROPAL_MODIFY', $user);
2484
                if ($result < 0) {
2485
                    $error++;
2486
                }
2487
                // End call triggers
2488
            }
2489
2490
            if (!$error) {
2491
                $this->db->commit();
2492
                return 1;
2493
            } else {
2494
                foreach ($this->errors as $errmsg) {
2495
                    dol_syslog(__METHOD__ . ' Error: ' . $errmsg, LOG_ERR);
2496
                    $this->error .= ($this->error ? ', ' . $errmsg : $errmsg);
2497
                }
2498
                $this->db->rollback();
2499
                return -1 * $error;
2500
            }
2501
        } else {
2502
            $error_str = 'Propal status do not meet requirement ' . $this->statut;
2503
            dol_syslog(__METHOD__ . $error_str, LOG_ERR);
2504
            $this->error = $error_str;
2505
            $this->errors[] = $this->error;
2506
            return -2;
2507
        }
2508
    }
2509
2510
    /**
2511
     *  Set source of demand
2512
     *
2513
     * @param User $user Object user that modify
2514
     * @param int $id Input reason id
2515
     * @param int $notrigger 1=Does not execute triggers, 0= execute triggers
2516
     * @return     int                 Return integer <0 if KO, >0 if OK
2517
     */
2518
    public function set_demand_reason($user, $id, $notrigger = 0)
2519
    {
2520
        // phpcs:enable
2521
        if ($user->hasRight('propal', 'creer') && $this->statut >= self::STATUS_DRAFT) {
2522
            $error = 0;
2523
2524
            $this->db->begin();
2525
2526
            $sql = "UPDATE " . MAIN_DB_PREFIX . "propal ";
2527
            $sql .= " SET fk_input_reason = " . ((int)$id);
2528
            $sql .= " WHERE rowid = " . ((int)$this->id);
2529
2530
            dol_syslog(__METHOD__, LOG_DEBUG);
2531
            $resql = $this->db->query($sql);
2532
            if (!$resql) {
2533
                $this->errors[] = $this->db->error();
2534
                $error++;
2535
            }
2536
2537
2538
            if (!$error) {
2539
                $this->oldcopy = clone $this;
2540
2541
                $this->demand_reason_id = $id;
2542
            }
2543
2544
2545
            if (!$notrigger && empty($error)) {
2546
                // Call trigger
2547
                $result = $this->call_trigger('PROPAL_MODIFY', $user);
2548
                if ($result < 0) {
2549
                    $error++;
2550
                }
2551
                // End call triggers
2552
            }
2553
2554
            if (!$error) {
2555
                $this->db->commit();
2556
                return 1;
2557
            } else {
2558
                foreach ($this->errors as $errmsg) {
2559
                    dol_syslog(__METHOD__ . ' Error: ' . $errmsg, LOG_ERR);
2560
                    $this->error .= ($this->error ? ', ' . $errmsg : $errmsg);
2561
                }
2562
                $this->db->rollback();
2563
                return -1 * $error;
2564
            }
2565
        } else {
2566
            $error_str = 'Propal status do not meet requirement ' . $this->statut;
2567
            dol_syslog(__METHOD__ . $error_str, LOG_ERR);
2568
            $this->error = $error_str;
2569
            $this->errors[] = $this->error;
2570
            return -2;
2571
        }
2572
    }
2573
2574
    /**
2575
     * Set customer reference number
2576
     *
2577
     * @param User $user Object user that modify
2578
     * @param string $ref_client Customer reference
2579
     * @param int $notrigger 1=Does not execute triggers, 0= execute triggers
2580
     * @return     int                     Return integer <0 if ko, >0 if ok
2581
     */
2582
    public function set_ref_client($user, $ref_client, $notrigger = 0)
2583
    {
2584
        // phpcs:enable
2585
        if ($user->hasRight('propal', 'creer')) {
2586
            $error = 0;
2587
2588
            $this->db->begin();
2589
2590
            $sql = "UPDATE " . MAIN_DB_PREFIX . "propal SET ref_client = " . (empty($ref_client) ? 'NULL' : "'" . $this->db->escape($ref_client) . "'");
2591
            $sql .= " WHERE rowid = " . ((int)$this->id);
2592
2593
            dol_syslog(__METHOD__ . ' $this->id=' . $this->id . ', ref_client=' . $ref_client, LOG_DEBUG);
2594
            $resql = $this->db->query($sql);
2595
            if (!$resql) {
2596
                $this->errors[] = $this->db->error();
2597
                $error++;
2598
            }
2599
2600
            if (!$error) {
2601
                $this->oldcopy = clone $this;
2602
                $this->ref_client = $ref_client;
2603
            }
2604
2605
            if (!$notrigger && empty($error)) {
2606
                // Call trigger
2607
                $result = $this->call_trigger('PROPAL_MODIFY', $user);
2608
                if ($result < 0) {
2609
                    $error++;
2610
                }
2611
                // End call triggers
2612
            }
2613
2614
            if (!$error) {
2615
                $this->db->commit();
2616
                return 1;
2617
            } else {
2618
                foreach ($this->errors as $errmsg) {
2619
                    dol_syslog(__METHOD__ . ' Error: ' . $errmsg, LOG_ERR);
2620
                    $this->error .= ($this->error ? ', ' . $errmsg : $errmsg);
2621
                }
2622
                $this->db->rollback();
2623
                return -1 * $error;
2624
            }
2625
        }
2626
2627
        return -1;
2628
    }
2629
2630
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2631
2632
    /**
2633
     *  Reopen the commercial proposal
2634
     *
2635
     * @param User $user Object user that close
2636
     * @param int $status Status
2637
     * @param string $note Comment
2638
     * @param int $notrigger 1=Does not execute triggers, 0= execute triggers
2639
     * @return     int                 Return integer <0 if KO, >0 if OK
2640
     */
2641
    public function reopen($user, $status, $note = '', $notrigger = 0)
2642
    {
2643
        $error = 0;
2644
2645
        $sql = "UPDATE " . MAIN_DB_PREFIX . "propal";
2646
        $sql .= " SET fk_statut = " . ((int)$status) . ",";
2647
        if (!empty($note)) {
2648
            $sql .= " note_private = '" . $this->db->escape($note) . "',";
2649
        }
2650
        $sql .= " date_cloture=NULL, fk_user_cloture=NULL";
2651
        $sql .= " WHERE rowid = " . ((int)$this->id);
2652
2653
        $this->db->begin();
2654
2655
        dol_syslog(get_class($this) . "::reopen", LOG_DEBUG);
2656
        $resql = $this->db->query($sql);
2657
        if (!$resql) {
2658
            $error++;
2659
            $this->errors[] = "Error " . $this->db->lasterror();
2660
        }
2661
        if (!$error) {
2662
            if (!$notrigger) {
2663
                // Call trigger
2664
                $result = $this->call_trigger('PROPAL_REOPEN', $user);
2665
                if ($result < 0) {
2666
                    $error++;
2667
                }
2668
                // End call triggers
2669
            }
2670
        }
2671
2672
        // Commit or rollback
2673
        if ($error) {
2674
            if (!empty($this->errors)) {
2675
                foreach ($this->errors as $errmsg) {
2676
                    dol_syslog(get_class($this) . "::update " . $errmsg, LOG_ERR);
2677
                    $this->error .= ($this->error ? ', ' . $errmsg : $errmsg);
2678
                }
2679
            }
2680
            $this->db->rollback();
2681
            return -1 * $error;
2682
        } else {
2683
            $this->statut = $status;
2684
            $this->status = $status;
2685
2686
            $this->db->commit();
2687
            return 1;
2688
        }
2689
    }
2690
2691
2692
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2693
2694
    /**
2695
     *  Close/set the commercial proposal to status signed or refused (fill also date signature)
2696
     *
2697
     * @param User $user Object user that close
2698
     * @param int $status Status (self::STATUS_BILLED or self::STATUS_REFUSED)
2699
     * @param string $note Complete private note with this note
2700
     * @param int $notrigger 1=Does not execute triggers, 0=Execute triggers
2701
     * @return     int                 Return integer <0 if KO, >0 if OK
2702
     */
2703
    public function closeProposal($user, $status, $note = '', $notrigger = 0)
2704
    {
2705
        global $langs, $conf;
2706
2707
        $error = 0;
2708
        $now = dol_now();
2709
2710
        $this->db->begin();
2711
2712
        $newprivatenote = dol_concatdesc($this->note_private, $note);
2713
2714
        if (!getDolGlobalString('PROPALE_KEEP_OLD_SIGNATURE_INFO')) {
2715
            $date_signature = $now;
2716
            $fk_user_signature = $user->id;
2717
        } else {
2718
            $this->info($this->id);
2719
            if (!isset($this->date_signature) || $this->date_signature == '') {
2720
                $date_signature = $now;
2721
                $fk_user_signature = $user->id;
2722
            } else {
2723
                $date_signature = $this->date_signature;
2724
                $fk_user_signature = $this->user_signature->id;
2725
            }
2726
        }
2727
2728
        $sql = "UPDATE " . MAIN_DB_PREFIX . "propal";
2729
        $sql .= " SET fk_statut = " . ((int)$status) . ", note_private = '" . $this->db->escape($newprivatenote) . "', date_signature='" . $this->db->idate($date_signature) . "', fk_user_signature=" . $fk_user_signature;
2730
        $sql .= " WHERE rowid = " . ((int)$this->id);
2731
2732
        $resql = $this->db->query($sql);
2733
        if ($resql) {
2734
            // Status self::STATUS_REFUSED by default
2735
            $modelpdf = getDolGlobalString('PROPALE_ADDON_PDF_ODT_CLOSED', $this->model_pdf);
2736
            $trigger_name = 'PROPAL_CLOSE_REFUSED';     // used later in call_trigger()
2737
2738
            if ($status == self::STATUS_SIGNED) {   // Status self::STATUS_SIGNED
2739
                $trigger_name = 'PROPAL_CLOSE_SIGNED';  // used later in call_trigger()
2740
                $modelpdf = getDolGlobalString('PROPALE_ADDON_PDF_ODT_TOBILL') ? $conf->global->PROPALE_ADDON_PDF_ODT_TOBILL : $this->model_pdf;
2741
2742
                // The connected company is classified as a client
2743
                $soc = new Societe($this->db);
2744
                $soc->id = $this->socid;
2745
                $result = $soc->setAsCustomer();
2746
2747
                if ($result < 0) {
2748
                    $this->error = $this->db->lasterror();
2749
                    $this->db->rollback();
2750
                    return -2;
2751
                }
2752
            }
2753
2754
            if (!getDolGlobalString('MAIN_DISABLE_PDF_AUTOUPDATE') && !getDolGlobalInt('PROPAL_DISABLE_AUTOUPDATE_ON_CLOSE')) {
2755
                // Define output language
2756
                $outputlangs = $langs;
2757
                if (getDolGlobalInt('MAIN_MULTILANGS')) {
2758
                    $outputlangs = new Translate("", $conf);
2759
                    $newlang = (GETPOST('lang_id', 'aZ09') ? GETPOST('lang_id', 'aZ09') : $this->thirdparty->default_lang);
2760
                    $outputlangs->setDefaultLang($newlang);
2761
                }
2762
2763
                // PDF
2764
                $hidedetails = (getDolGlobalString('MAIN_GENERATE_DOCUMENTS_HIDE_DETAILS') ? 1 : 0);
2765
                $hidedesc = (getDolGlobalString('MAIN_GENERATE_DOCUMENTS_HIDE_DESC') ? 1 : 0);
2766
                $hideref = (getDolGlobalString('MAIN_GENERATE_DOCUMENTS_HIDE_REF') ? 1 : 0);
2767
2768
                //$ret=$object->fetch($id);    // Reload to get new records
2769
                $this->generateDocument($modelpdf, $outputlangs, $hidedetails, $hidedesc, $hideref);
2770
            }
2771
2772
            if (!$error) {
2773
                $this->oldcopy = clone $this;
2774
                $this->statut = $status;
2775
                $this->status = $status;
2776
                $this->date_signature = $date_signature;
2777
                $this->note_private = $newprivatenote;
2778
            }
2779
2780
            if (!$notrigger && empty($error)) {
2781
                // Call trigger
2782
                $result = $this->call_trigger($trigger_name, $user);
2783
                if ($result < 0) {
2784
                    $error++;
2785
                }
2786
                // End call triggers
2787
            }
2788
2789
            if (!$error) {
2790
                $this->db->commit();
2791
                return 1;
2792
            } else {
2793
                $this->statut = $this->oldcopy->statut;
2794
                $this->status = $this->oldcopy->statut;
2795
                $this->date_signature = $this->oldcopy->date_signature;
2796
                $this->note_private = $this->oldcopy->note_private;
2797
2798
                $this->db->rollback();
2799
                return -1;
2800
            }
2801
        } else {
2802
            $this->error = $this->db->lasterror();
2803
            $this->db->rollback();
2804
            return -1;
2805
        }
2806
    }
2807
2808
    /**
2809
     *  Object Proposal Information
2810
     *
2811
     * @param int $id Proposal id
2812
     * @return void
2813
     */
2814
    public function info($id)
2815
    {
2816
        $sql = "SELECT c.rowid, ";
2817
        $sql .= " c.datec, c.date_valid as datev, c.date_signature, c.date_cloture,";
2818
        $sql .= " c.fk_user_author, c.fk_user_valid, c.fk_user_signature, c.fk_user_cloture";
2819
        $sql .= " FROM " . MAIN_DB_PREFIX . "propal as c";
2820
        $sql .= " WHERE c.rowid = " . ((int)$id);
2821
2822
        $result = $this->db->query($sql);
2823
2824
        if ($result) {
2825
            if ($this->db->num_rows($result)) {
2826
                $obj = $this->db->fetch_object($result);
2827
2828
                $this->id = $obj->rowid;
2829
2830
                $this->date_creation = $this->db->jdate($obj->datec);
2831
                $this->date_validation = $this->db->jdate($obj->datev);
2832
                $this->date_signature = $this->db->jdate($obj->date_signature);
2833
                $this->date_cloture = $this->db->jdate($obj->date_cloture);
2834
2835
                $this->user_creation_id = $obj->fk_user_author;
2836
                $this->user_validation_id = $obj->fk_user_valid;
2837
2838
                if ($obj->fk_user_signature) {
2839
                    $user_signature = new User($this->db);
2840
                    $user_signature->fetch($obj->fk_user_signature);
2841
                    $this->user_signature = $user_signature;
2842
                }
2843
2844
                $this->user_closing_id = $obj->fk_user_cloture;
2845
            }
2846
            $this->db->free($result);
2847
        } else {
2848
            dol_print_error($this->db);
2849
        }
2850
    }
2851
2852
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2853
2854
    /**
2855
     *  Create a document onto disk according to template module.
2856
     *
2857
     * @param string $modele Force model to use ('' to not force)
2858
     * @param Translate $outputlangs Object langs to use for output
2859
     * @param int $hidedetails Hide details of lines
2860
     * @param int $hidedesc Hide description
2861
     * @param int $hideref Hide ref
2862
     * @param null|array $moreparams Array to provide more information
2863
     * @return     int                         0 if KO, 1 if OK
2864
     */
2865
    public function generateDocument($modele, $outputlangs, $hidedetails = 0, $hidedesc = 0, $hideref = 0, $moreparams = null)
2866
    {
2867
        global $conf, $langs;
2868
2869
        $langs->load("propale");
2870
        $outputlangs->load("products");
2871
2872
        if (!dol_strlen($modele)) {
2873
            $modele = 'azur';
2874
2875
            if ($this->model_pdf) {
2876
                $modele = $this->model_pdf;
2877
            } elseif (getDolGlobalString('PROPALE_ADDON_PDF')) {
2878
                $modele = getDolGlobalString('PROPALE_ADDON_PDF');
2879
            }
2880
        }
2881
2882
        $modelpath = "core/modules/propale/doc/";
2883
2884
        return $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams);
2885
    }
2886
2887
    /**
2888
     *  Classify the proposal to status Billed
2889
     *
2890
     * @param User $user Object user
2891
     * @param int $notrigger 1=Does not execute triggers, 0= execute triggers
2892
     * @param string $note Complete private note with this note
2893
     * @return     int                 Return integer <0 if KO, 0 = nothing done, >0 if OK
2894
     */
2895
    public function classifyBilled(User $user, $notrigger = 0, $note = '')
2896
    {
2897
        global $conf, $langs;
2898
2899
        $error = 0;
2900
2901
        $now = dol_now();
2902
        $num = 0;
2903
2904
        $triggerName = 'PROPAL_CLASSIFY_BILLED';
2905
2906
        $this->db->begin();
2907
2908
        $newprivatenote = dol_concatdesc($this->note_private, $note);
2909
2910
        $sql = 'UPDATE ' . MAIN_DB_PREFIX . 'propal SET fk_statut = ' . self::STATUS_BILLED . ", ";
2911
        $sql .= " note_private = '" . $this->db->escape($newprivatenote) . "', date_cloture='" . $this->db->idate($now) . "', fk_user_cloture=" . ((int)$user->id);
2912
        $sql .= ' WHERE rowid = ' . ((int)$this->id) . ' AND fk_statut = ' . ((int)self::STATUS_SIGNED);
2913
2914
        dol_syslog(__METHOD__, LOG_DEBUG);
2915
        $resql = $this->db->query($sql);
2916
        if (!$resql) {
2917
            $this->errors[] = $this->db->error();
2918
            $error++;
2919
        } else {
2920
            $num = $this->db->affected_rows($resql);
2921
        }
2922
2923
        if (!$error) {
2924
            $modelpdf = getDolGlobalString('PROPALE_ADDON_PDF_ODT_CLOSED', $this->model_pdf);
2925
2926
            if (!getDolGlobalString('MAIN_DISABLE_PDF_AUTOUPDATE')) {
2927
                // Define output language
2928
                $outputlangs = $langs;
2929
                if (getDolGlobalInt('MAIN_MULTILANGS')) {
2930
                    $outputlangs = new Translate("", $conf);
2931
                    $newlang = (GETPOST('lang_id', 'aZ09') ? GETPOST('lang_id', 'aZ09') : $this->thirdparty->default_lang);
2932
                    $outputlangs->setDefaultLang($newlang);
2933
                }
2934
2935
                // PDF
2936
                $hidedetails = (getDolGlobalString('MAIN_GENERATE_DOCUMENTS_HIDE_DETAILS') ? 1 : 0);
2937
                $hidedesc = (getDolGlobalString('MAIN_GENERATE_DOCUMENTS_HIDE_DESC') ? 1 : 0);
2938
                $hideref = (getDolGlobalString('MAIN_GENERATE_DOCUMENTS_HIDE_REF') ? 1 : 0);
2939
2940
                //$ret=$object->fetch($id);    // Reload to get new records
2941
                $this->generateDocument($modelpdf, $outputlangs, $hidedetails, $hidedesc, $hideref);
2942
            }
2943
2944
            $this->oldcopy = clone $this;
2945
            $this->statut = self::STATUS_BILLED;
2946
            $this->date_cloture = $now;
2947
            $this->note_private = $newprivatenote;
2948
        }
2949
2950
        if (!$notrigger && empty($error)) {
2951
            // Call trigger
2952
            $result = $this->call_trigger($triggerName, $user);
2953
            if ($result < 0) {
2954
                $error++;
2955
            }
2956
            // End call triggers
2957
        }
2958
2959
        if (!$error) {
2960
            $this->db->commit();
2961
            return $num;
2962
        } else {
2963
            foreach ($this->errors as $errmsg) {
2964
                dol_syslog(__METHOD__ . ' Error: ' . $errmsg, LOG_ERR);
2965
                $this->error .= ($this->error ? ', ' . $errmsg : $errmsg);
2966
            }
2967
            $this->db->rollback();
2968
            return -1 * $error;
2969
        }
2970
    }
2971
2972
    /**
2973
     *  Cancel the proposal
2974
     *
2975
     * @param User $user Object user
2976
     * @return     int             Return integer if KO <0 , if OK >0
2977
     */
2978
    public function setCancel(User $user)
2979
    {
2980
        $error = 0;
2981
2982
        $this->db->begin();
2983
2984
        $sql = "UPDATE " . MAIN_DB_PREFIX . "propal";
2985
        $sql .= " SET fk_statut = " . self::STATUS_CANCELED . ",";
2986
        $sql .= " fk_user_modif = " . ((int)$user->id);
2987
        $sql .= " WHERE rowid = " . ((int)$this->id);
2988
2989
        dol_syslog(get_class($this) . "::cancel", LOG_DEBUG);
2990
        if ($this->db->query($sql)) {
2991
            if (!$error) {
2992
                // Call trigger
2993
                $result = $this->call_trigger('PROPAL_CANCEL', $user);
2994
                if ($result < 0) {
2995
                    $error++;
2996
                }
2997
                // End call triggers
2998
            }
2999
3000
            if (!$error) {
3001
                $this->statut = self::STATUS_CANCELED;
3002
                $this->db->commit();
3003
                return 1;
3004
            } else {
3005
                foreach ($this->errors as $errmsg) {
3006
                    dol_syslog(get_class($this) . "::cancel " . $errmsg, LOG_ERR);
3007
                    $this->error .= ($this->error ? ', ' . $errmsg : $errmsg);
3008
                }
3009
                $this->db->rollback();
3010
                return -1;
3011
            }
3012
        } else {
3013
            $this->error = $this->db->error();
3014
            $this->db->rollback();
3015
            return -1;
3016
        }
3017
    }
3018
3019
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3020
3021
    /**
3022
     *  Set draft status
3023
     *
3024
     * @param User $user Object user that modify
3025
     * @param int $notrigger 1=Does not execute triggers, 0= execute triggers
3026
     * @return     int                 Return integer <0 if KO, >0 if OK
3027
     */
3028
    public function setDraft($user, $notrigger = 0)
3029
    {
3030
        // phpcs:enable
3031
        $error = 0;
3032
3033
        // Protection
3034
        if ($this->statut <= self::STATUS_DRAFT) {
3035
            return 0;
3036
        }
3037
3038
        dol_syslog(get_class($this) . "::setDraft", LOG_DEBUG);
3039
3040
        $this->db->begin();
3041
3042
        $sql = "UPDATE " . MAIN_DB_PREFIX . "propal";
3043
        $sql .= " SET fk_statut = " . self::STATUS_DRAFT;
3044
        $sql .= ",  online_sign_ip = NULL , online_sign_name = NULL";
3045
        $sql .= " WHERE rowid = " . ((int)$this->id);
3046
3047
        $resql = $this->db->query($sql);
3048
        if (!$resql) {
3049
            $this->errors[] = $this->db->error();
3050
            $error++;
3051
        }
3052
3053
        if (!$error) {
3054
            $this->oldcopy = clone $this;
3055
        }
3056
3057
        if (!$notrigger && empty($error)) {
3058
            // Call trigger
3059
            $result = $this->call_trigger('PROPAL_MODIFY', $user);
3060
            if ($result < 0) {
3061
                $error++;
3062
            }
3063
            // End call triggers
3064
        }
3065
3066
        if (!$error) {
3067
            $this->statut = self::STATUS_DRAFT;
3068
            $this->status = self::STATUS_DRAFT;
3069
3070
            $this->db->commit();
3071
            return 1;
3072
        } else {
3073
            foreach ($this->errors as $errmsg) {
3074
                dol_syslog(__METHOD__ . ' Error: ' . $errmsg, LOG_ERR);
3075
                $this->error .= ($this->error ? ', ' . $errmsg : $errmsg);
3076
            }
3077
            $this->db->rollback();
3078
            return -1 * $error;
3079
        }
3080
    }
3081
3082
    /**
3083
     *    Return list of proposal (eventually filtered on user) into an array
3084
     *
3085
     * @param int $shortlist 0=Return array[id]=ref, 1=Return array[](id=>id,ref=>ref,name=>name)
3086
     * @param int $draft 0=not draft, 1=draft
3087
     * @param int $notcurrentuser 0=all user, 1=not current user
3088
     * @param int $socid Id third party
3089
     * @param int $limit For pagination
3090
     * @param int $offset For pagination
3091
     * @param string $sortfield Sort criteria
3092
     * @param string $sortorder Sort order
3093
     * @return   array|int                   -1 if KO, array with result if OK
3094
     */
3095
    public function liste_array($shortlist = 0, $draft = 0, $notcurrentuser = 0, $socid = 0, $limit = 0, $offset = 0, $sortfield = 'p.datep', $sortorder = 'DESC')
3096
    {
3097
        // phpcs:enable
3098
        global $user;
3099
3100
        $ga = array();
3101
3102
        $sql = "SELECT s.rowid, s.nom as name, s.client,";
3103
        $sql .= " p.rowid as propalid, p.fk_statut, p.total_ht, p.ref, p.remise, ";
3104
        $sql .= " p.datep as dp, p.fin_validite as datelimite";
3105
        $sql .= " FROM " . MAIN_DB_PREFIX . "societe as s, " . MAIN_DB_PREFIX . "propal as p, " . MAIN_DB_PREFIX . "c_propalst as c";
3106
        $sql .= " WHERE p.entity IN (" . getEntity('propal') . ")";
3107
        $sql .= " AND p.fk_soc = s.rowid";
3108
        $sql .= " AND p.fk_statut = c.id";
3109
3110
        // If the internal user must only see his customers, force searching by him
3111
        $search_sale = 0;
3112
        if (!$user->hasRight('societe', 'client', 'voir')) {
3113
            $search_sale = $user->id;
3114
        }
3115
        // Search on sale representative
3116
        if ($search_sale && $search_sale != '-1') {
3117
            if ($search_sale == -2) {
3118
                $sql .= " AND NOT EXISTS (SELECT sc.fk_soc FROM " . MAIN_DB_PREFIX . "societe_commerciaux as sc WHERE sc.fk_soc = p.fk_soc)";
3119
            } elseif ($search_sale > 0) {
3120
                $sql .= " AND EXISTS (SELECT sc.fk_soc FROM " . MAIN_DB_PREFIX . "societe_commerciaux as sc WHERE sc.fk_soc = p.fk_soc AND sc.fk_user = " . ((int)$search_sale) . ")";
3121
            }
3122
        }
3123
        // Search on socid
3124
        if ($socid) {
3125
            $sql .= " AND p.fk_soc = " . ((int)$socid);
3126
        }
3127
        if ($draft) {
3128
            $sql .= " AND p.fk_statut = " . ((int)self::STATUS_DRAFT);
3129
        }
3130
        if ($notcurrentuser > 0) {
3131
            $sql .= " AND p.fk_user_author <> " . ((int)$user->id);
3132
        }
3133
        $sql .= $this->db->order($sortfield, $sortorder);
3134
        $sql .= $this->db->plimit($limit, $offset);
3135
3136
        $result = $this->db->query($sql);
3137
        if ($result) {
3138
            $num = $this->db->num_rows($result);
3139
            if ($num) {
3140
                $i = 0;
3141
                while ($i < $num) {
3142
                    $obj = $this->db->fetch_object($result);
3143
3144
                    if ($shortlist == 1) {
3145
                        $ga[$obj->propalid] = $obj->ref;
3146
                    } elseif ($shortlist == 2) {
3147
                        $ga[$obj->propalid] = $obj->ref . ' (' . $obj->name . ')';
3148
                    } else {
3149
                        $ga[$i]['id'] = $obj->propalid;
3150
                        $ga[$i]['ref'] = $obj->ref;
3151
                        $ga[$i]['name'] = $obj->name;
3152
                    }
3153
3154
                    $i++;
3155
                }
3156
            }
3157
            return $ga;
3158
        } else {
3159
            dol_print_error($this->db);
3160
            return -1;
3161
        }
3162
    }
3163
3164
    /**
3165
     *  Returns an array with the numbers of related invoices
3166
     *
3167
     * @return array       Array of invoices
3168
     */
3169
    public function getInvoiceArrayList()
3170
    {
3171
        return $this->InvoiceArrayList($this->id);
3172
    }
3173
3174
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3175
3176
    /**
3177
     *  Returns an array with id and ref of related invoices
3178
     *
3179
     * @param int $id Id propal
3180
     * @return     array|int               Array of invoices id
3181
     */
3182
    public function InvoiceArrayList($id)
3183
    {
3184
        // phpcs:enable
3185
        $ga = array();
3186
        $linkedInvoices = array();
3187
3188
        $this->fetchObjectLinked($id, $this->element);
3189
        foreach ($this->linkedObjectsIds as $objecttype => $objectid) {
3190
            // Nouveau système du common object renvoi des rowid et non un id linéaire de 1 à n
3191
            // On parcourt donc une liste d'objets en tant qu'objet unique
3192
            foreach ($objectid as $key => $object) {
3193
                // Cas des factures liees directement
3194
                if ($objecttype == 'facture') {
3195
                    $linkedInvoices[] = $object;
3196
                } else {
3197
                    // Cas des factures liees par un autre object (ex: commande)
3198
                    $this->fetchObjectLinked($object, $objecttype);
3199
                    foreach ($this->linkedObjectsIds as $subobjecttype => $subobjectid) {
3200
                        foreach ($subobjectid as $subkey => $subobject) {
3201
                            if ($subobjecttype == 'facture') {
3202
                                $linkedInvoices[] = $subobject;
3203
                            }
3204
                        }
3205
                    }
3206
                }
3207
            }
3208
        }
3209
3210
        if (count($linkedInvoices) > 0) {
3211
            $sql = "SELECT rowid as facid, ref, total_ht as total, datef as df, fk_user_author, fk_statut, paye";
3212
            $sql .= " FROM " . MAIN_DB_PREFIX . "facture";
3213
            $sql .= " WHERE rowid IN (" . $this->db->sanitize(implode(',', $linkedInvoices)) . ")";
3214
3215
            dol_syslog(get_class($this) . "::InvoiceArrayList", LOG_DEBUG);
3216
            $resql = $this->db->query($sql);
3217
3218
            if ($resql) {
3219
                $tab_sqlobj = array();
3220
                $nump = $this->db->num_rows($resql);
3221
                for ($i = 0; $i < $nump; $i++) {
3222
                    $sqlobj = $this->db->fetch_object($resql);
3223
                    $tab_sqlobj[] = $sqlobj;
3224
                }
3225
                $this->db->free($resql);
3226
3227
                $nump = count($tab_sqlobj);
3228
3229
                if ($nump) {
3230
                    $i = 0;
3231
                    while ($i < $nump) {
3232
                        $obj = array_shift($tab_sqlobj);
3233
3234
                        $ga[$i] = $obj;
3235
3236
                        $i++;
3237
                    }
3238
                }
3239
                return $ga;
3240
            } else {
3241
                return -1;
3242
            }
3243
        } else {
3244
            return $ga;
3245
        }
3246
    }
3247
3248
3249
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3250
3251
    /**
3252
     *  Change the delivery time
3253
     *
3254
     * @param int $availability_id Id of new delivery time
3255
     * @param int $notrigger 1=Does not execute triggers, 0= execute triggers
3256
     * @return int                     >0 if OK, <0 if KO
3257
     * @deprecated  use set_availability
3258
     */
3259
    public function availability($availability_id, $notrigger = 0)
3260
    {
3261
        global $user;
3262
3263
        if ($this->statut >= self::STATUS_DRAFT) {
3264
            $error = 0;
3265
3266
            $this->db->begin();
3267
3268
            $sql = 'UPDATE ' . MAIN_DB_PREFIX . 'propal';
3269
            $sql .= ' SET fk_availability = ' . ((int)$availability_id);
3270
            $sql .= ' WHERE rowid=' . ((int)$this->id);
3271
3272
            dol_syslog(__METHOD__ . ' availability(' . $availability_id . ')', LOG_DEBUG);
3273
            $resql = $this->db->query($sql);
3274
            if (!$resql) {
3275
                $this->errors[] = $this->db->error();
3276
                $error++;
3277
            }
3278
3279
            if (!$error) {
3280
                $this->oldcopy = clone $this;
3281
                $this->availability_id = $availability_id;
3282
            }
3283
3284
            if (!$notrigger && empty($error)) {
3285
                // Call trigger
3286
                $result = $this->call_trigger('PROPAL_MODIFY', $user);
3287
                if ($result < 0) {
3288
                    $error++;
3289
                }
3290
                // End call triggers
3291
            }
3292
3293
            if (!$error) {
3294
                $this->db->commit();
3295
                return 1;
3296
            } else {
3297
                foreach ($this->errors as $errmsg) {
3298
                    dol_syslog(__METHOD__ . ' Error: ' . $errmsg, LOG_ERR);
3299
                    $this->error .= ($this->error ? ', ' . $errmsg : $errmsg);
3300
                }
3301
                $this->db->rollback();
3302
                return -1 * $error;
3303
            }
3304
        } else {
3305
            $error_str = 'Propal status do not meet requirement ' . $this->statut;
3306
            dol_syslog(__METHOD__ . $error_str, LOG_ERR);
3307
            $this->error = $error_str;
3308
            $this->errors[] = $this->error;
3309
            return -2;
3310
        }
3311
    }
3312
3313
    /**
3314
     *  Change source demand
3315
     *
3316
     * @param int $demand_reason_id Id of new source demand
3317
     * @param int $notrigger 1=Does not execute triggers, 0= execute triggers
3318
     * @return int                     >0 si ok, <0 si ko
3319
     * @deprecated use set_demand_reason
3320
     */
3321
    public function demand_reason($demand_reason_id, $notrigger = 0)
3322
    {
3323
        // phpcs:enable
3324
        global $user;
3325
3326
        if ($this->status >= self::STATUS_DRAFT) {
3327
            $error = 0;
3328
3329
            $this->db->begin();
3330
3331
            $sql = 'UPDATE ' . MAIN_DB_PREFIX . 'propal';
3332
            $sql .= ' SET fk_input_reason = ' . ((int)$demand_reason_id);
3333
            $sql .= ' WHERE rowid=' . ((int)$this->id);
3334
3335
            dol_syslog(__METHOD__ . ' demand_reason(' . $demand_reason_id . ')', LOG_DEBUG);
3336
            $resql = $this->db->query($sql);
3337
            if (!$resql) {
3338
                $this->errors[] = $this->db->error();
3339
                $error++;
3340
            }
3341
3342
            if (!$error) {
3343
                $this->oldcopy = clone $this;
3344
                $this->demand_reason_id = $demand_reason_id;
3345
            }
3346
3347
            if (!$notrigger && empty($error)) {
3348
                // Call trigger
3349
                $result = $this->call_trigger('PROPAL_MODIFY', $user);
3350
                if ($result < 0) {
3351
                    $error++;
3352
                }
3353
                // End call triggers
3354
            }
3355
3356
            if (!$error) {
3357
                $this->db->commit();
3358
                return 1;
3359
            } else {
3360
                foreach ($this->errors as $errmsg) {
3361
                    dol_syslog(__METHOD__ . ' Error: ' . $errmsg, LOG_ERR);
3362
                    $this->error .= ($this->error ? ', ' . $errmsg : $errmsg);
3363
                }
3364
                $this->db->rollback();
3365
                return -1 * $error;
3366
            }
3367
        } else {
3368
            $error_str = 'Propal status do not meet requirement ' . $this->statut;
3369
            dol_syslog(__METHOD__ . $error_str, LOG_ERR);
3370
            $this->error = $error_str;
3371
            $this->errors[] = $this->error;
3372
            return -2;
3373
        }
3374
    }
3375
3376
    /**
3377
     *      Load indicators for dashboard (this->nbtodo and this->nbtodolate)
3378
     *
3379
     * @param User $user Object user
3380
     * @param string $mode "opened" for proposal to close, "signed" for proposal to invoice
3381
     * @return WorkboardResponse|int Return integer <0 if KO, WorkboardResponse if OK
3382
     */
3383
    public function load_board($user, $mode)
3384
    {
3385
        // phpcs:enable
3386
        global $conf, $langs;
3387
3388
        $clause = " WHERE";
3389
3390
        $sql = "SELECT p.rowid, p.ref, p.datec as datec, p.fin_validite as datefin, p.total_ht";
3391
        $sql .= " FROM " . MAIN_DB_PREFIX . "propal as p";
3392
        $sql .= $clause . " p.entity IN (" . getEntity('propal') . ")";
3393
        if ($mode == 'opened') {
3394
            $sql .= " AND p.fk_statut = " . self::STATUS_VALIDATED;
3395
        }
3396
        if ($mode == 'signed') {
3397
            $sql .= " AND p.fk_statut = " . self::STATUS_SIGNED;
3398
        }
3399
        // If the internal user must only see his customers, force searching by him
3400
        $search_sale = 0;
3401
        if (!$user->hasRight('societe', 'client', 'voir')) {
3402
            $search_sale = $user->id;
3403
        }
3404
        // Search on sale representative
3405
        if ($search_sale && $search_sale != '-1') {
3406
            if ($search_sale == -2) {
3407
                $sql .= " AND NOT EXISTS (SELECT sc.fk_soc FROM " . MAIN_DB_PREFIX . "societe_commerciaux as sc WHERE sc.fk_soc = p.fk_soc)";
3408
            } elseif ($search_sale > 0) {
3409
                $sql .= " AND EXISTS (SELECT sc.fk_soc FROM " . MAIN_DB_PREFIX . "societe_commerciaux as sc WHERE sc.fk_soc = p.fk_soc AND sc.fk_user = " . ((int)$search_sale) . ")";
3410
            }
3411
        }
3412
3413
        $resql = $this->db->query($sql);
3414
        if ($resql) {
3415
            $langs->load("propal");
3416
            $now = dol_now();
3417
3418
            $delay_warning = 0;
3419
            $status = 0;
3420
            $label = $labelShort = '';
3421
            if ($mode == 'opened') {
3422
                $delay_warning = $conf->propal->cloture->warning_delay;
3423
                $status = self::STATUS_VALIDATED;
3424
                $label = $langs->transnoentitiesnoconv("PropalsToClose");
3425
                $labelShort = $langs->transnoentitiesnoconv("ToAcceptRefuse");
3426
            }
3427
            if ($mode == 'signed') {
3428
                $delay_warning = $conf->propal->facturation->warning_delay;
3429
                $status = self::STATUS_SIGNED;
3430
                $label = $langs->trans("PropalsToBill"); // We set here bill but may be billed or ordered
3431
                $labelShort = $langs->trans("ToBill");
3432
            }
3433
3434
            $response = new WorkboardResponse();
3435
            $response->warning_delay = $delay_warning / 60 / 60 / 24;
3436
            $response->label = $label;
3437
            $response->labelShort = $labelShort;
3438
            $response->url = constant('BASE_URL') . '/comm/propal/list.php?search_status=' . $status . '&mainmenu=commercial&leftmenu=propals';
3439
            $response->url_late = constant('BASE_URL') . '/comm/propal/list.php?search_status=' . $status . '&mainmenu=commercial&leftmenu=propals&sortfield=p.datep&sortorder=asc';
3440
            $response->img = img_object('', "propal");
3441
3442
            // This assignment in condition is not a bug. It allows walking the results.
3443
            while ($obj = $this->db->fetch_object($resql)) {
3444
                $response->nbtodo++;
3445
                $response->total += $obj->total_ht;
3446
3447
                if ($mode == 'opened') {
3448
                    $datelimit = $this->db->jdate($obj->datefin);
3449
                    if ($datelimit < ($now - $delay_warning)) {
3450
                        $response->nbtodolate++;
3451
                    }
3452
                }
3453
                // TODO Definir regle des propales a facturer en retard
3454
                // if ($mode == 'signed' && ! count($this->FactureListeArray($obj->rowid))) $this->nbtodolate++;
3455
            }
3456
3457
            return $response;
3458
        } else {
3459
            $this->error = $this->db->error();
3460
            return -1;
3461
        }
3462
    }
3463
3464
    /**
3465
     *  Initialise an instance with random values.
3466
     *  Used to build previews or test instances.
3467
     *  id must be 0 if object instance is a specimen.
3468
     *
3469
     * @return int
3470
     */
3471
    public function initAsSpecimen()
3472
    {
3473
        global $conf, $langs;
3474
3475
        // Load array of products prodids
3476
        $num_prods = 0;
3477
        $prodids = array();
3478
        $sql = "SELECT rowid";
3479
        $sql .= " FROM " . MAIN_DB_PREFIX . "product";
3480
        $sql .= " WHERE entity IN (" . getEntity('product') . ")";
3481
        $sql .= $this->db->plimit(100);
3482
3483
        $resql = $this->db->query($sql);
3484
        if ($resql) {
3485
            $num_prods = $this->db->num_rows($resql);
3486
            $i = 0;
3487
            while ($i < $num_prods) {
3488
                $i++;
3489
                $row = $this->db->fetch_row($resql);
3490
                $prodids[$i] = $row[0];
3491
            }
3492
        }
3493
3494
        // Initialise parameters
3495
        $this->id = 0;
3496
        $this->ref = 'SPECIMEN';
3497
        $this->ref_client = 'NEMICEPS';
3498
        $this->specimen = 1;
3499
        $this->socid = 1;
3500
        $this->date = time();
3501
        $this->fin_validite = $this->date + 3600 * 24 * 30;
3502
        $this->cond_reglement_id = 1;
3503
        $this->cond_reglement_code = 'RECEP';
3504
        $this->mode_reglement_id = 7;
3505
        $this->mode_reglement_code = 'CHQ';
3506
        $this->availability_id = 1;
3507
        $this->availability_code = 'AV_NOW';
3508
        $this->demand_reason_id = 1;
3509
        $this->demand_reason_code = 'SRC_00';
3510
        $this->note_public = 'This is a comment (public)';
3511
        $this->note_private = 'This is a comment (private)';
3512
3513
        $this->multicurrency_tx = 1;
3514
        $this->multicurrency_code = $conf->currency;
3515
3516
        // Lines
3517
        $nbp = 5;
3518
        $xnbp = 0;
3519
        while ($xnbp < $nbp) {
3520
            $line = new PropaleLigne($this->db);
3521
            $line->desc = $langs->trans("Description") . " " . $xnbp;
3522
            $line->qty = 1;
3523
            $line->subprice = 100;
3524
            $line->price = 100;
3525
            $line->tva_tx = 20;
3526
            $line->localtax1_tx = 0;
3527
            $line->localtax2_tx = 0;
3528
            if ($xnbp == 2) {
3529
                $line->total_ht = 50;
3530
                $line->total_ttc = 60;
3531
                $line->total_tva = 10;
3532
                $line->remise_percent = 50;
3533
            } else {
3534
                $line->total_ht = 100;
3535
                $line->total_ttc = 120;
3536
                $line->total_tva = 20;
3537
                $line->remise_percent = 0;
3538
            }
3539
3540
            if ($num_prods > 0) {
3541
                $prodid = mt_rand(1, $num_prods);
3542
                $line->fk_product = $prodids[$prodid];
3543
                $line->product_ref = 'SPECIMEN';
3544
            }
3545
3546
            $this->lines[$xnbp] = $line;
3547
3548
            $this->total_ht += $line->total_ht;
3549
            $this->total_tva += $line->total_tva;
3550
            $this->total_ttc += $line->total_ttc;
3551
3552
            $xnbp++;
3553
        }
3554
3555
        return 1;
3556
    }
3557
3558
    /**
3559
     *      Load the indicators this->nb for the state board
3560
     *
3561
     * @return     int         Return integer <0 if ko, >0 if ok
3562
     */
3563
    public function loadStateBoard()
3564
    {
3565
        global $user;
3566
3567
        $this->nb = array();
3568
        $clause = "WHERE";
3569
3570
        $sql = "SELECT count(p.rowid) as nb";
3571
        $sql .= " FROM " . MAIN_DB_PREFIX . "propal as p";
3572
        $sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "societe as s ON p.fk_soc = s.rowid";
3573
        $sql .= " " . $clause . " p.entity IN (" . getEntity('propal') . ")";
3574
3575
        // If the internal user must only see his customers, force searching by him
3576
        $search_sale = 0;
3577
        if (!$user->hasRight('societe', 'client', 'voir')) {
3578
            $search_sale = $user->id;
3579
        }
3580
        // Search on sale representative
3581
        if ($search_sale && $search_sale != '-1') {
3582
            if ($search_sale == -2) {
3583
                $sql .= " AND NOT EXISTS (SELECT sc.fk_soc FROM " . MAIN_DB_PREFIX . "societe_commerciaux as sc WHERE sc.fk_soc = p.fk_soc)";
3584
            } elseif ($search_sale > 0) {
3585
                $sql .= " AND EXISTS (SELECT sc.fk_soc FROM " . MAIN_DB_PREFIX . "societe_commerciaux as sc WHERE sc.fk_soc = p.fk_soc AND sc.fk_user = " . ((int)$search_sale) . ")";
3586
            }
3587
        }
3588
3589
        $resql = $this->db->query($sql);
3590
        if ($resql) {
3591
            // This assignment in condition is not a bug. It allows walking the results.
3592
            while ($obj = $this->db->fetch_object($resql)) {
3593
                $this->nb["proposals"] = $obj->nb;
3594
            }
3595
            $this->db->free($resql);
3596
            return 1;
3597
        } else {
3598
            dol_print_error($this->db);
3599
            $this->error = $this->db->error();
3600
            return -1;
3601
        }
3602
    }
3603
3604
    /**
3605
     * getTooltipContentArray
3606
     * @param array $params params to construct tooltip data
3607
     * @return array
3608
     * @since v18
3609
     */
3610
    public function getTooltipContentArray($params)
3611
    {
3612
        global $conf, $langs, $user;
3613
3614
        $langs->load('propal');
3615
        $datas = [];
3616
        $nofetch = !empty($params['nofetch']);
3617
3618
        if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
3619
            return ['optimize' => $langs->trans("Proposal")];
3620
        }
3621
        if ($user->hasRight('propal', 'lire')) {
3622
            $datas['picto'] = img_picto('', $this->picto) . ' <u class="paddingrightonly">' . $langs->trans("Proposal") . '</u>';
3623
            if (isset($this->status)) {
3624
                $datas['status'] = ' ' . $this->getLibStatut(5);
3625
            }
3626
            if (!empty($this->ref)) {
3627
                $datas['ref'] = '<br><b>' . $langs->trans('Ref') . ':</b> ' . $this->ref;
3628
            }
3629
            if (!$nofetch) {
3630
                $langs->load('companies');
3631
                if (empty($this->thirdparty)) {
3632
                    $this->fetch_thirdparty();
3633
                }
3634
                $datas['customer'] = '<br><b>' . $langs->trans('Customer') . ':</b> ' . $this->thirdparty->getNomUrl(1, '', 0, 1);
3635
            }
3636
            if (!empty($this->ref_customer)) {
3637
                $datas['refcustomer'] = '<br><b>' . $langs->trans('RefCustomer') . ':</b> ' . $this->ref_customer;
3638
            }
3639
            if (!$nofetch) {
3640
                $langs->load('project');
3641
                if (is_null($this->project) || (is_object($this->project) && $this->project->isEmpty())) {
3642
                    $res = $this->fetch_project();
3643
                    if ($res > 0 && $this->project instanceof Project) {
3644
                        $datas['project'] = '<br><b>' . $langs->trans('Project') . ':</b> ' . $this->project->getNomUrl(1, '', 0, 1);
3645
                    }
3646
                }
3647
            }
3648
            if (!empty($this->total_ht)) {
3649
                $datas['amountht'] = '<br><b>' . $langs->trans('AmountHT') . ':</b> ' . price($this->total_ht, 0, $langs, 0, -1, -1, $conf->currency);
3650
            }
3651
            if (!empty($this->total_tva)) {
3652
                $datas['vat'] = '<br><b>' . $langs->trans('VAT') . ':</b> ' . price($this->total_tva, 0, $langs, 0, -1, -1, $conf->currency);
3653
            }
3654
            if (!empty($this->total_ttc)) {
3655
                $datas['amountttc'] = '<br><b>' . $langs->trans('AmountTTC') . ':</b> ' . price($this->total_ttc, 0, $langs, 0, -1, -1, $conf->currency);
3656
            }
3657
            if (!empty($this->date)) {
3658
                $datas['date'] = '<br><b>' . $langs->trans('Date') . ':</b> ' . dol_print_date($this->date, 'day');
3659
            }
3660
            if (!empty($this->delivery_date)) {
3661
                $datas['deliverydate'] = '<br><b>' . $langs->trans('DeliveryDate') . ':</b> ' . dol_print_date($this->delivery_date, 'dayhour');
3662
            }
3663
        }
3664
3665
        return $datas;
3666
    }
3667
3668
    /**
3669
     *      Return label of status of proposal (draft, validated, ...)
3670
     *
3671
     * @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
3672
     * @return     string      Label
3673
     */
3674
    public function getLibStatut($mode = 0)
3675
    {
3676
        return $this->LibStatut($this->statut, $mode);
3677
    }
3678
3679
    /**
3680
     *      Return label of a status (draft, validated, ...)
3681
     *
3682
     * @param int $status Id status
3683
     * @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
3684
     * @return     string      Label
3685
     */
3686
    public function LibStatut($status, $mode = 1)
3687
    {
3688
        // phpcs:enable
3689
        global $hookmanager;
3690
3691
        // Init/load array of translation of status
3692
        if (empty($this->labelStatus) || empty($this->labelStatusShort)) {
3693
            global $langs;
3694
            $langs->load("propal");
3695
            $this->labelStatus[-1] = $langs->transnoentitiesnoconv("PropalStatusCanceled");
3696
            $this->labelStatus[0] = $langs->transnoentitiesnoconv("PropalStatusDraft");
3697
            $this->labelStatus[1] = $langs->transnoentitiesnoconv("PropalStatusValidated");
3698
            $this->labelStatus[2] = $langs->transnoentitiesnoconv("PropalStatusSigned");
3699
            $this->labelStatus[3] = $langs->transnoentitiesnoconv("PropalStatusNotSigned");
3700
            $this->labelStatus[4] = $langs->transnoentitiesnoconv("PropalStatusBilled");
3701
            $this->labelStatusShort[-1] = $langs->transnoentitiesnoconv("PropalStatusCanceledShort");
3702
            $this->labelStatusShort[0] = $langs->transnoentitiesnoconv("PropalStatusDraftShort");
3703
            $this->labelStatusShort[1] = $langs->transnoentitiesnoconv("PropalStatusValidatedShort");
3704
            $this->labelStatusShort[2] = $langs->transnoentitiesnoconv("PropalStatusSignedShort");
3705
            $this->labelStatusShort[3] = $langs->transnoentitiesnoconv("PropalStatusNotSignedShort");
3706
            $this->labelStatusShort[4] = $langs->transnoentitiesnoconv("PropalStatusBilledShort");
3707
        }
3708
3709
        $statusType = '';
3710
        if ($status == self::STATUS_CANCELED) {
3711
            $statusType = 'status9';
3712
        } elseif ($status == self::STATUS_DRAFT) {
3713
            $statusType = 'status0';
3714
        } elseif ($status == self::STATUS_VALIDATED) {
3715
            $statusType = 'status1';
3716
        } elseif ($status == self::STATUS_SIGNED) {
3717
            $statusType = 'status4';
3718
        } elseif ($status == self::STATUS_NOTSIGNED) {
3719
            $statusType = 'status9';
3720
        } elseif ($status == self::STATUS_BILLED) {
3721
            $statusType = 'status6';
3722
        }
3723
3724
        $parameters = array('status' => $status, 'mode' => $mode);
3725
        $reshook = $hookmanager->executeHooks('LibStatut', $parameters, $this); // Note that $action and $object may have been modified by hook
3726
3727
        if ($reshook > 0) {
3728
            return $hookmanager->resPrint;
3729
        }
3730
3731
        return dolGetStatus($this->labelStatus[$status], $this->labelStatusShort[$status], '', $statusType, $mode);
3732
    }
3733
3734
    /**
3735
     *  Return clicable link of object (with eventually picto)
3736
     *
3737
     * @param int $withpicto Add picto into link
3738
     * @param string $option Where point the link ('expedition', 'document', ...)
3739
     * @param string $get_params Parameters added to url
3740
     * @param int $notooltip 1=Disable tooltip
3741
     * @param int $save_lastsearch_value -1=Auto, 0=No save of lastsearch_values when clicking, 1=Save lastsearch_values whenclicking
3742
     * @param int $addlinktonotes -1=Disable, 0=Just add label show notes, 1=Add private note (only internal user), 2=Add public note (internal or external user), 3=Add private (internal user) and public note (internal and external user)
3743
     * @return     string                            String with URL
3744
     */
3745
    public function getNomUrl($withpicto = 0, $option = '', $get_params = '', $notooltip = 0, $save_lastsearch_value = -1, $addlinktonotes = -1)
3746
    {
3747
        global $langs, $conf, $user, $hookmanager;
3748
3749
        if (!empty($conf->dol_no_mouse_hover)) {
3750
            $notooltip = 1; // Force disable tooltips
3751
        }
3752
3753
        $result = '';
3754
        $params = [
3755
            'id' => $this->id,
3756
            'objecttype' => $this->element,
3757
            'option' => $option,
3758
            'nofetch' => 1,
3759
        ];
3760
        $classfortooltip = 'classfortooltip';
3761
        $dataparams = '';
3762
        if (getDolGlobalInt('MAIN_ENABLE_AJAX_TOOLTIP')) {
3763
            $classfortooltip = 'classforajaxtooltip';
3764
            $dataparams = ' data-params="' . dol_escape_htmltag(json_encode($params)) . '"';
3765
            $label = '';
3766
        } else {
3767
            $label = implode($this->getTooltipContentArray($params));
3768
        }
3769
3770
        $url = '';
3771
        if ($user->hasRight('propal', 'lire')) {
3772
            if ($option == '') {
3773
                $url = constant('BASE_URL') . '/comm/propal/card.php?id=' . $this->id . $get_params;
3774
            } elseif ($option == 'compta') {  // deprecated
3775
                $url = constant('BASE_URL') . '/comm/propal/card.php?id=' . $this->id . $get_params;
3776
            } elseif ($option == 'expedition') {
3777
                $url = constant('BASE_URL') . '/expedition/propal.php?id=' . $this->id . $get_params;
3778
            } elseif ($option == 'document') {
3779
                $url = constant('BASE_URL') . '/comm/propal/document.php?id=' . $this->id . $get_params;
3780
            }
3781
3782
            if ($option != 'nolink') {
3783
                // Add param to save lastsearch_values or not
3784
                $add_save_lastsearch_values = ($save_lastsearch_value == 1 ? 1 : 0);
3785
                if ($save_lastsearch_value == -1 && isset($_SERVER["PHP_SELF"]) && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) {
3786
                    $add_save_lastsearch_values = 1;
3787
                }
3788
                if ($add_save_lastsearch_values) {
3789
                    $url .= '&save_lastsearch_values=1';
3790
                }
3791
            }
3792
        }
3793
3794
        $linkclose = '';
3795
        if (empty($notooltip) && $user->hasRight('propal', 'lire')) {
3796
            if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
3797
                $label = $langs->trans("Proposal");
3798
                $linkclose .= ' alt="' . dol_escape_htmltag($label, 1) . '"';
3799
            }
3800
            $linkclose .= ($label ? ' title="' . dol_escape_htmltag($label, 1) . '"' : ' title="tocomplete"');
3801
            $linkclose .= $dataparams . ' class="' . $classfortooltip . '"';
3802
        }
3803
3804
        $linkstart = '<a href="' . $url . '"';
3805
        $linkstart .= $linkclose . '>';
3806
        $linkend = '</a>';
3807
3808
        $result .= $linkstart;
3809
        if ($withpicto) {
3810
            $result .= img_object(($notooltip ? '' : $label), ($this->picto ? $this->picto : 'generic'), (($withpicto != 2) ? 'class="paddingright"' : ''), 0, 0, $notooltip ? 0 : 1);
3811
        }
3812
        if ($withpicto != 2) {
3813
            $result .= $this->ref;
3814
        }
3815
        $result .= $linkend;
3816
3817
        if ($addlinktonotes >= 0) {
3818
            $txttoshow = '';
3819
3820
            if ($addlinktonotes == 0) {
3821
                if (!empty($this->note_private) || !empty($this->note_public)) {
3822
                    $txttoshow = $langs->trans('ViewPrivateNote');
3823
                }
3824
            } elseif ($addlinktonotes == 1) {
3825
                if (!empty($this->note_private)) {
3826
                    $txttoshow .= ($user->socid > 0 ? '' : dol_string_nohtmltag($this->note_private, 1));
3827
                }
3828
            } elseif ($addlinktonotes == 2) {
3829
                if (!empty($this->note_public)) {
3830
                    $txttoshow .= dol_string_nohtmltag($this->note_public, 1);
3831
                }
3832
            } elseif ($addlinktonotes == 3) {
3833
                if ($user->socid > 0) {
3834
                    if (!empty($this->note_public)) {
3835
                        $txttoshow .= dol_string_nohtmltag($this->note_public, 1);
3836
                    }
3837
                } else {
3838
                    if (!empty($this->note_public)) {
3839
                        $txttoshow .= dol_string_nohtmltag($this->note_public, 1);
3840
                    }
3841
                    if (!empty($this->note_private)) {
3842
                        if (!empty($txttoshow)) {
3843
                            $txttoshow .= '<br><br>';
3844
                        }
3845
                        $txttoshow .= dol_string_nohtmltag($this->note_private, 1);
3846
                    }
3847
                }
3848
            }
3849
3850
            if ($txttoshow) {
3851
                $result .= ' <span class="note inline-block">';
3852
                $result .= '<a href="' . constant('BASE_URL') . '/comm/propal/note.php?id=' . $this->id . '" class="classfortooltip" title="' . dol_escape_htmltag($txttoshow) . '">';
3853
                $result .= img_picto('', 'note');
3854
                $result .= '</a>';
3855
                $result .= '</span>';
3856
            }
3857
        }
3858
3859
        global $action;
3860
        $hookmanager->initHooks(array($this->element . 'dao'));
3861
        $parameters = array('id' => $this->id, 'getnomurl' => &$result);
3862
        $reshook = $hookmanager->executeHooks('getNomUrl', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
3863
        if ($reshook > 0) {
3864
            $result = $hookmanager->resPrint;
3865
        } else {
3866
            $result .= $hookmanager->resPrint;
3867
        }
3868
        return $result;
3869
    }
3870
3871
    /**
3872
     *  Retrieve an array of proposal lines
3873
     *
3874
     * @param string $sqlforgedfilters Filter on other fields
3875
     * @return int                             >0 if OK, <0 if KO
3876
     */
3877
    public function getLinesArray($sqlforgedfilters = '')
3878
    {
3879
        return $this->fetch_lines(0, 0, $sqlforgedfilters);
3880
    }
3881
3882
    /**
3883
     *  Return clicable link of object (with eventually picto)
3884
     *
3885
     * @param string $option Where point the link (0=> main card, 1,2 => shipment, 'nolink'=>No link)
3886
     * @param array $arraydata Array of data
3887
     * @return     string                              HTML Code for Kanban thumb.
3888
     */
3889
    public function getKanbanView($option = '', $arraydata = null)
3890
    {
3891
        global $langs;
3892
3893
        $selected = (empty($arraydata['selected']) ? 0 : $arraydata['selected']);
3894
3895
        $return = '<div class="box-flex-item box-flex-grow-zero">';
3896
        $return .= '<div class="info-box info-box-sm">';
3897
        $return .= '<div class="info-box-icon bg-infobox-action">';
3898
        $return .= img_picto('', $this->picto);
3899
        $return .= '</div>';
3900
        $return .= '<div class="info-box-content">';
3901
        $return .= '<span class="info-box-ref inline-block tdoverflowmax150 valignmiddle">' . (method_exists($this, 'getNomUrl') ? $this->getNomUrl() : $this->ref) . '</span>';
3902
        if ($selected >= 0) {
3903
            $return .= '<input id="cb' . $this->id . '" class="flat checkforselect fright" type="checkbox" name="toselect[]" value="' . $this->id . '"' . ($selected ? ' checked="checked"' : '') . '>';
3904
        }
3905
        if (!empty($arraydata['projectlink'])) {
3906
            $return .= '<span class="info-box-ref"> | ' . $arraydata['projectlink'] . '</span>';
3907
        }
3908
        $return .= '<br>';
3909
        if (property_exists($this, 'thirdparty') && is_object($this->thirdparty)) {
3910
            $return .= '<div class="info-box-ref tdoverflowmax150">' . $this->thirdparty->getNomUrl(1) . '</div>';
3911
        }
3912
        if (property_exists($this, 'total_ht')) {
3913
            $return .= '<span class="info-box-label amount" title="' . $langs->trans("AmountHT") . '">' . price($this->total_ht) . '</span>';
3914
        }
3915
        if (!empty($arraydata['authorlink'])) {
3916
            $return .= ' &nbsp; <span class="info-box-label">' . $arraydata['authorlink'] . '</span>';
3917
        }
3918
        if (method_exists($this, 'getLibStatut')) {
3919
            $return .= '<br><div class="info-box-status">' . $this->getLibStatut(3) . '</div>';
3920
        }
3921
        $return .= '</div>';
3922
        $return .= '</div>';
3923
        $return .= '</div>';
3924
        return $return;
3925
    }
3926
}
3927