Passed
Push — EXTRACT_CLASSES ( 0382f2...c25e41 )
by Rafael
52:18
created

Propal::demand_reason()   B

Complexity

Conditions 10

Size

Total Lines 52
Code Lines 34

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 10
eloc 34
nop 2
dl 0
loc 52
rs 7.6666
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

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

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

Commonly applied refactorings include:

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

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

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

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