Passed
Push — main ( f1540e...02d90d )
by Rafael
45:15
created

Propal::createFromClone()   D

Complexity

Conditions 42

Size

Total Lines 171
Code Lines 96

Duplication

Lines 0
Ratio 0 %

Importance

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

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

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

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

2216
                            /** @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...
2217
                        }
2218
                    }
2219
                }
2220
            }
2221
2222
            $this->ref = $num;
2223
            $this->statut = self::STATUS_VALIDATED;
2224
            $this->status = self::STATUS_VALIDATED;
2225
            $this->user_validation_id = $user->id;
2226
            $this->datev = $now;
2227
            $this->date_validation = $now;
2228
2229
            $this->db->commit();
2230
            return 1;
2231
        } else {
2232
            $this->db->rollback();
2233
            return -1;
2234
        }
2235
    }
2236
2237
    /**
2238
     *  Returns the reference to the following non used Proposal used depending on the active numbering module
2239
     *  defined into PROPALE_ADDON
2240
     *
2241
     * @param Societe $soc Object thirdparty
2242
     *
2243
     * @return string              Reference libre pour la propale
2244
     */
2245
    public function getNextNumRef($soc)
2246
    {
2247
        global $conf, $langs;
2248
        $langs->load("propal");
2249
2250
        $classname = getDolGlobalString('PROPALE_ADDON');
2251
2252
        if (!empty($classname)) {
2253
            $mybool = false;
2254
2255
            $file = $classname . ".php";
2256
2257
            // Include file with class
2258
            $dirmodels = array_merge(['/'], (array) $conf->modules_parts['models']);
2259
            foreach ($dirmodels as $reldir) {
2260
                $dir = dol_buildpath($reldir . "core/modules/propale/");
2261
2262
                // Load file with numbering class (if found)
2263
                $mybool |= @include_once $dir . $file;
2264
            }
2265
2266
            if (!$mybool) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $mybool of type false|integer is loosely compared to false; this is ambiguous if the integer can be 0. You might want to explicitly use === false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

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