ExpenseReport   F
last analyzed

Complexity

Total Complexity 320

Size/Duplication

Total Lines 2817
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 1518
dl 0
loc 2817
rs 0.8
c 0
b 0
f 0
wmc 320

42 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 15 1
A update_totaux_add() 0 19 2
F getNomUrl() 0 78 23
F delete() 0 112 24
B periode_existe() 0 41 6
A set_save_from_refuse() 0 32 3
B applyOffset() 0 52 7
A set_unpaid() 0 5 1
F update() 0 52 16
C fetch() 0 99 10
A getVentilExportCompta() 0 22 4
B checkRules() 0 66 7
A getLibStatut() 0 3 1
B hasDelay() 0 17 8
B setPaid() 0 39 6
C updateline() 0 117 11
B getTooltipContentArray() 0 31 7
A set_paid() 0 5 1
B getNextNumRef() 0 39 6
B load_board() 0 62 10
A info() 0 41 3
B fetch_line_by_project() 0 92 9
F create() 0 158 26
A generateDocument() 0 18 5
F setValidate() 0 113 21
A LibStatut() 0 13 1
B setUnpaid() 0 42 6
B fetch_lines() 0 83 5
B set_cancel() 0 44 6
A fetch_users_approver_expensereport() 0 29 3
B setApproved() 0 43 6
B getKanbanView() 0 31 9
A getSumPayments() 0 18 3
A initAsSpecimen() 0 55 2
A listOfTypes() 0 21 4
A loadStateBoard() 0 27 4
A offsetAlreadyGiven() 0 23 4
F addline() 0 102 15
C computeTotalKm() 0 89 13
A deleteLine() 0 33 5
B setDeny() 0 48 6
B createFromClone() 0 68 10

How to fix   Complexity   

Complex Class

Complex classes like ExpenseReport often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use ExpenseReport, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/* Copyright (C) 2011       Dimitri Mouillard           <[email protected]>
4
 * Copyright (C) 2015 		Laurent Destailleur 	    <[email protected]>
5
 * Copyright (C) 2015 		Alexandre Spangaro  	    <[email protected]>
6
 * Copyright (C) 2018       Nicolas ZABOURI             <[email protected]>
7
 * Copyright (c) 2018-2024  Frédéric France             <[email protected]>
8
 * Copyright (C) 2016-2020 	Ferran Marcet       	    <[email protected]>
9
 * Copyright (C) 2024		MDW							<[email protected]>
10
 * Copyright (C) 2024       Frédéric France             <[email protected]>
11
 * Copyright (C) 2024       Rafael San José             <[email protected]>
12
 *
13
 * This program is free software; you can redistribute it and/or modify
14
 * it under the terms of the GNU General Public License as published by
15
 * the Free Software Foundation; either version 3 of the License, or
16
 * (at your option) any later version.
17
 *
18
 * This program is distributed in the hope that it will be useful,
19
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21
 * GNU General Public License for more details.
22
 *
23
 * You should have received a copy of the GNU General Public License
24
 * along with this program. If not, see <https://www.gnu.org/licenses/>.
25
 */
26
27
namespace Dolibarr\Code\ExpenseReport\Classes;
28
29
use Dolibarr\Code\Core\Classes\Translate;
30
use Dolibarr\Code\Core\Classes\WorkboardResponse;
31
use Dolibarr\Code\Societe\Classes\Societe;
32
use Dolibarr\Code\User\Classes\User;
33
use Dolibarr\Core\Base\CommonObject;
34
use DoliDB;
35
36
/**
37
 *       \file       htdocs/expensereport/class/expensereport.class.php
38
 *       \ingroup    expensereport
39
 *       \brief      File to manage Expense Reports
40
 */
41
42
/**
43
 * Class to manage Trips and Expenses
44
 */
45
class ExpenseReport extends CommonObject
46
{
47
    /**
48
     * @var string ID to identify managed object
49
     */
50
    public $element = 'expensereport';
51
52
    /**
53
     * @var string Name of table without prefix where object is stored
54
     */
55
    public $table_element = 'expensereport';
56
57
    /**
58
     * @var string table element line name
59
     */
60
    public $table_element_line = 'expensereport_det';
61
62
    /**
63
     * @var string Fieldname with ID of parent key if this field has a parent
64
     */
65
    public $fk_element = 'fk_expensereport';
66
67
    /**
68
     * @var string String with name of icon for myobject. Must be the part after the 'object_' into object_myobject.png
69
     */
70
    public $picto = 'trip';
71
72
    /**
73
     * @var ExpenseReportLine[] array of expensereport lines
74
     */
75
    public $lines = array();
76
77
    /**
78
     * @var ExpenseReportLine expensereport lines
79
     */
80
    public $line;
81
82
    /**
83
     * @var int|string
84
     */
85
    public $date_debut;
86
87
    /**
88
     * @var int|string
89
     */
90
    public $date_fin;
91
92
    /**
93
     * @var int|string
94
     */
95
    public $date_approbation;
96
97
    /**
98
     * @var int ID
99
     */
100
    public $fk_user;
101
102
    /**
103
     * @var int ID
104
     */
105
    public $user_approve_id;
106
107
    /**
108
     * 0=draft, 2=validated (attente approb), 4=canceled, 5=approved, 6=paid, 99=denied
109
     *
110
     * @var int     Status
111
     */
112
    public $status;
113
114
    /**
115
     * 0=draft, 2=validated (attente approb), 4=canceled, 5=approved, 6=paid, 99=denied
116
     *
117
     * @var int     Status
118
     * @deprecated
119
     */
120
    public $fk_statut;
121
122
    /**
123
     * @var int ID
124
     */
125
    public $fk_c_paiement;
126
127
    /**
128
     * @var int ID
129
     */
130
    public $modepaymentid;
131
132
    public $paid;
133
134
    // Paiement
135
    /**
136
     * @var string Firstname Lastname
137
     */
138
    public $user_paid_infos;
139
140
    /**
141
     * @var string Firstname Lastname
142
     */
143
    public $user_author_infos;
144
145
    /**
146
     * @var string Firstname Lastname
147
     */
148
    public $user_validator_infos;
149
150
    public $rule_warning_message;
151
152
    // ACTIONS
153
154
    // Create
155
    /**
156
     * @var int|string
157
     */
158
    public $date_create;
159
160
    /**
161
     * @var int ID of user creator
162
     */
163
    public $fk_user_creat;
164
165
    /**
166
     * @var int ID of user who reclaim expense report
167
     */
168
    public $fk_user_author; // Note fk_user_author is not the 'author' but the guy the expense report is for.
169
170
    // Update
171
    /**
172
     * @var int|string
173
     */
174
    public $date_modif;
175
176
    /**
177
     * @var int ID
178
     */
179
    public $fk_user_modif;
180
181
    // Refus
182
    /**
183
     * @var int|string
184
     */
185
    public $date_refuse;
186
187
    /**
188
     * @var string
189
     */
190
    public $detail_refuse;
191
192
    /**
193
     * @var int ID
194
     */
195
    public $fk_user_refuse;
196
197
    // Annulation
198
    /**
199
     * @var int|string
200
     */
201
    public $date_cancel;
202
203
    /**
204
     * @var string
205
     */
206
    public $detail_cancel;
207
208
    /**
209
     * @var int ID of user who cancel expense report
210
     */
211
    public $fk_user_cancel;
212
213
    /**
214
     * @var int User that is defined to approve
215
     */
216
    public $fk_user_validator;
217
218
    /**
219
     * Validation date
220
     * @var int
221
     * @deprecated
222
     * @see $date_valid
223
     */
224
    public $datevalid;
225
226
    /**
227
     * Validation date
228
     * @var int
229
     */
230
    public $date_valid;
231
232
    /**
233
     * @var int ID of User making validation
234
     */
235
    public $fk_user_valid;
236
237
    /**
238
     * @var string Firstname Lastname
239
     */
240
    public $user_valid_infos;
241
242
    // Approve
243
    /**
244
     * @var int|string
245
     */
246
    public $date_approve;
247
248
    /**
249
     * @var int ID User that has approved
250
     */
251
    public $fk_user_approve;
252
253
    public $localtax1;  // for backward compatibility (real field should be total_localtax1 defined into CommonObject)
254
    public $localtax2;  // for backward compatibility (real field should be total_localtax2 defined into CommonObject)
255
256
    /**
257
     * Draft status
258
     */
259
    const STATUS_DRAFT = 0;
260
261
    /**
262
     * Validated (need to be paid)
263
     */
264
    const STATUS_VALIDATED = 2;
265
266
    /**
267
     * Classified canceled
268
     */
269
    const STATUS_CANCELED = 4;
270
271
    /**
272
     * Classified approved
273
     */
274
    const STATUS_APPROVED = 5;
275
276
    /**
277
     * Classified paid.
278
     */
279
    const STATUS_CLOSED = 6;
280
281
    /**
282
     * Classified refused
283
     */
284
    const STATUS_REFUSED = 99;
285
286
    public $fields = array(
287
        'rowid' => array('type' => 'integer', 'label' => 'ID', 'enabled' => 1, 'visible' => -1, 'notnull' => 1, 'position' => 10),
288
        'ref' => array('type' => 'varchar(50)', 'label' => 'Ref', 'enabled' => 1, 'visible' => -1, 'notnull' => 1, 'showoncombobox' => 1, 'position' => 15),
289
        'entity' => array('type' => 'integer', 'label' => 'Entity', 'default' => '1', 'enabled' => 1, 'visible' => -2, 'notnull' => 1, 'position' => 20),
290
        'ref_number_int' => array('type' => 'integer', 'label' => 'Ref number int', 'enabled' => 1, 'visible' => -1, 'position' => 25),
291
        'ref_ext' => array('type' => 'integer', 'label' => 'Ref ext', 'enabled' => 1, 'visible' => -1, 'position' => 30),
292
        'total_ht' => array('type' => 'double(24,8)', 'label' => 'Total ht', 'enabled' => 1, 'visible' => -1, 'position' => 35),
293
        'total_tva' => array('type' => 'double(24,8)', 'label' => 'Total tva', 'enabled' => 1, 'visible' => -1, 'position' => 40),
294
        'localtax1' => array('type' => 'double(24,8)', 'label' => 'Localtax1', 'enabled' => 1, 'visible' => -1, 'position' => 45),
295
        'localtax2' => array('type' => 'double(24,8)', 'label' => 'Localtax2', 'enabled' => 1, 'visible' => -1, 'position' => 50),
296
        'total_ttc' => array('type' => 'double(24,8)', 'label' => 'Total ttc', 'enabled' => 1, 'visible' => -1, 'position' => 55),
297
        'date_debut' => array('type' => 'date', 'label' => 'Date debut', 'enabled' => 1, 'visible' => -1, 'notnull' => 1, 'position' => 60),
298
        'date_fin' => array('type' => 'date', 'label' => 'Date fin', 'enabled' => 1, 'visible' => -1, 'notnull' => 1, 'position' => 65),
299
        'date_valid' => array('type' => 'datetime', 'label' => 'Date valid', 'enabled' => 1, 'visible' => -1, 'position' => 75),
300
        'date_approve' => array('type' => 'datetime', 'label' => 'Date approve', 'enabled' => 1, 'visible' => -1, 'position' => 80),
301
        'date_refuse' => array('type' => 'datetime', 'label' => 'Date refuse', 'enabled' => 1, 'visible' => -1, 'position' => 85),
302
        'date_cancel' => array('type' => 'datetime', 'label' => 'Date cancel', 'enabled' => 1, 'visible' => -1, 'position' => 90),
303
        'fk_user_author' => array('type' => 'integer', 'label' => 'Fk user author', 'enabled' => 1, 'visible' => -1, 'notnull' => 1, 'position' => 100),
304
        'fk_user_modif' => array('type' => 'integer', 'label' => 'Fk user modif', 'enabled' => 1, 'visible' => -1, 'position' => 105),
305
        'fk_user_valid' => array('type' => 'integer', 'label' => 'Fk user valid', 'enabled' => 1, 'visible' => -1, 'position' => 110),
306
        'fk_user_validator' => array('type' => 'integer', 'label' => 'Fk user validator', 'enabled' => 1, 'visible' => -1, 'position' => 115),
307
        'fk_user_approve' => array('type' => 'integer', 'label' => 'Fk user approve', 'enabled' => 1, 'visible' => -1, 'position' => 120),
308
        'fk_user_refuse' => array('type' => 'integer', 'label' => 'Fk user refuse', 'enabled' => 1, 'visible' => -1, 'position' => 125),
309
        'fk_user_cancel' => array('type' => 'integer', 'label' => 'Fk user cancel', 'enabled' => 1, 'visible' => -1, 'position' => 130),
310
        'fk_c_paiement' => array('type' => 'integer', 'label' => 'Fk c paiement', 'enabled' => 1, 'visible' => -1, 'position' => 140),
311
        'paid' => array('type' => 'integer', 'label' => 'Paid', 'enabled' => 1, 'visible' => -1, 'notnull' => 1, 'position' => 145),
312
        'note_public' => array('type' => 'html', 'label' => 'Note public', 'enabled' => 1, 'visible' => 0, 'position' => 150),
313
        'note_private' => array('type' => 'html', 'label' => 'Note private', 'enabled' => 1, 'visible' => 0, 'position' => 155),
314
        'detail_refuse' => array('type' => 'varchar(255)', 'label' => 'Detail refuse', 'enabled' => 1, 'visible' => -1, 'position' => 160),
315
        'detail_cancel' => array('type' => 'varchar(255)', 'label' => 'Detail cancel', 'enabled' => 1, 'visible' => -1, 'position' => 165),
316
        'integration_compta' => array('type' => 'integer', 'label' => 'Integration compta', 'enabled' => 1, 'visible' => -1, 'position' => 170),
317
        'fk_bank_account' => array('type' => 'integer', 'label' => 'Fk bank account', 'enabled' => 1, 'visible' => -1, 'position' => 175),
318
        'fk_multicurrency' => array('type' => 'integer', 'label' => 'Fk multicurrency', 'enabled' => 1, 'visible' => -1, 'position' => 185),
319
        'multicurrency_code' => array('type' => 'varchar(255)', 'label' => 'Multicurrency code', 'enabled' => 1, 'visible' => -1, 'position' => 190),
320
        'multicurrency_tx' => array('type' => 'double(24,8)', 'label' => 'Multicurrency tx', 'enabled' => 1, 'visible' => -1, 'position' => 195),
321
        'multicurrency_total_ht' => array('type' => 'double(24,8)', 'label' => 'Multicurrency total ht', 'enabled' => 1, 'visible' => -1, 'position' => 200),
322
        'multicurrency_total_tva' => array('type' => 'double(24,8)', 'label' => 'Multicurrency total tva', 'enabled' => 1, 'visible' => -1, 'position' => 205),
323
        'multicurrency_total_ttc' => array('type' => 'double(24,8)', 'label' => 'Multicurrency total ttc', 'enabled' => 1, 'visible' => -1, 'position' => 210),
324
        'extraparams' => array('type' => 'varchar(255)', 'label' => 'Extraparams', 'enabled' => 1, 'visible' => -1, 'position' => 220),
325
        'date_create' => array('type' => 'datetime', 'label' => 'Date create', 'enabled' => 1, 'visible' => -1, 'notnull' => 1, 'position' => 300),
326
        'tms' => array('type' => 'timestamp', 'label' => 'Tms', 'enabled' => 1, 'visible' => -1, 'notnull' => 1, 'position' => 305),
327
        'import_key' => array('type' => 'varchar(14)', 'label' => 'ImportId', 'enabled' => 1, 'visible' => -1, 'position' => 1000),
328
        'model_pdf' => array('type' => 'varchar(255)', 'label' => 'Model pdf', 'enabled' => 1, 'visible' => 0, 'position' => 1010),
329
        'fk_statut' => array('type' => 'integer', 'label' => 'Fk statut', 'enabled' => 1, 'visible' => -1, 'notnull' => 1, 'position' => 500),
330
    );
331
332
    /**
333
     *  Constructor
334
     *
335
     *  @param  DoliDB  $db     Handler access base de donnees
336
     */
337
    public function __construct($db)
338
    {
339
        $this->db = $db;
340
        $this->total_ht = 0;
341
        $this->total_ttc = 0;
342
        $this->total_tva = 0;
343
        $this->total_localtax1 = 0;
344
        $this->total_localtax2 = 0;
345
        $this->localtax1 = 0;   // For backward compatibility
346
        $this->localtax2 = 0;   // For backward compatibility
347
        $this->modepaymentid = 0;
348
349
        // List of language codes for status
350
        $this->labelStatusShort = array(0 => 'Draft', 2 => 'Validated', 4 => 'Canceled', 5 => 'Approved', 6 => 'Paid', 99 => 'Refused');
351
        $this->labelStatus = array(0 => 'Draft', 2 => 'ValidatedWaitingApproval', 4 => 'Canceled', 5 => 'Approved', 6 => 'Paid', 99 => 'Refused');
352
    }
353
354
    /**
355
     * Create object in database
356
     *
357
     * @param   User    $user   User that create
358
     * @param   int     $notrigger   Disable triggers
359
     * @return  int             Return integer <0 if KO, >0 if OK
360
     */
361
    public function create($user, $notrigger = 0)
362
    {
363
        global $conf, $langs;
364
365
        $now = dol_now();
366
367
        $error = 0;
368
369
        // Check parameters
370
        if (empty($this->date_debut) || empty($this->date_fin)) {
371
            $this->error = $langs->trans('ErrorFieldRequired', $langs->transnoentitiesnoconv('Date'));
372
            return -1;
373
        }
374
375
        $fuserid = $this->fk_user_author; // Note fk_user_author is not the 'author' but the guy the expense report is for.
376
        if (empty($fuserid)) {
377
            $fuserid = $user->id;
378
        }
379
380
        $this->db->begin();
381
382
        $sql = "INSERT INTO " . MAIN_DB_PREFIX . $this->table_element . " (";
383
        $sql .= "ref";
384
        $sql .= ",total_ht";
385
        $sql .= ",total_ttc";
386
        $sql .= ",total_tva";
387
        $sql .= ",date_debut";
388
        $sql .= ",date_fin";
389
        $sql .= ",date_create";
390
        $sql .= ",fk_user_creat";
391
        $sql .= ",fk_user_author";
392
        $sql .= ",fk_user_validator";
393
        $sql .= ",fk_user_approve";
394
        $sql .= ",fk_user_modif";
395
        $sql .= ",fk_statut";
396
        $sql .= ",fk_c_paiement";
397
        $sql .= ",paid";
398
        $sql .= ",note_public";
399
        $sql .= ",note_private";
400
        $sql .= ",entity";
401
        $sql .= ") VALUES(";
402
        $sql .= "'(PROV)'";
403
        $sql .= ", " . price2num($this->total_ht, 'MT');
404
        $sql .= ", " . price2num($this->total_ttc, 'MT');
405
        $sql .= ", " . price2num($this->total_tva, 'MT');
406
        $sql .= ", '" . $this->db->idate($this->date_debut) . "'";
407
        $sql .= ", '" . $this->db->idate($this->date_fin) . "'";
408
        $sql .= ", '" . $this->db->idate($now) . "'";
409
        $sql .= ", " . ((int) $user->id);
410
        $sql .= ", " . ((int) $fuserid);
411
        $sql .= ", " . ($this->fk_user_validator > 0 ? ((int) $this->fk_user_validator) : "null");
412
        $sql .= ", " . ($this->fk_user_approve > 0 ? ((int) $this->fk_user_approve) : "null");
413
        $sql .= ", " . ($this->fk_user_modif > 0 ? ((int) $this->fk_user_modif) : "null");
414
        $sql .= ", " . ($this->fk_statut > 1 ? ((int) $this->fk_statut) : 0);
415
        $sql .= ", " . ($this->modepaymentid ? ((int) $this->modepaymentid) : "null");
416
        $sql .= ", 0";
417
        $sql .= ", " . ($this->note_public ? "'" . $this->db->escape($this->note_public) . "'" : "null");
418
        $sql .= ", " . ($this->note_private ? "'" . $this->db->escape($this->note_private) . "'" : "null");
419
        $sql .= ", " . ((int) $conf->entity);
420
        $sql .= ")";
421
422
        $result = $this->db->query($sql);
423
        if ($result) {
424
            $this->id = $this->db->last_insert_id(MAIN_DB_PREFIX . $this->table_element);
425
            $this->ref = '(PROV' . $this->id . ')';
426
427
            $sql = 'UPDATE ' . MAIN_DB_PREFIX . $this->table_element . " SET ref='" . $this->db->escape($this->ref) . "' WHERE rowid=" . ((int) $this->id);
428
            $resql = $this->db->query($sql);
429
            if (!$resql) {
430
                $this->error = $this->db->lasterror();
431
                $error++;
432
            }
433
434
            if (!$error) {
435
                if (is_array($this->lines) && count($this->lines) > 0) {
436
                    foreach ($this->lines as $line) {
437
                        // Test and convert into object this->lines[$i]. When coming from REST API, we may still have an array
438
                        //if (! is_object($line)) $line=json_decode(json_encode($line), false);  // convert recursively array into object.
439
                        if (!is_object($line)) {
440
                            $line = (object) $line;
441
                            $newndfline = new ExpenseReportLine($this->db);
442
                            $newndfline->fk_expensereport = $line->fk_expensereport;
443
                            $newndfline->fk_c_type_fees = $line->fk_c_type_fees;
444
                            $newndfline->fk_project = $line->fk_project;
445
                            $newndfline->vatrate = $line->vatrate;
446
                            $newndfline->vat_src_code = $line->vat_src_code;
447
                            $newndfline->localtax1_tx = $line->localtax1_tx;
448
                            $newndfline->localtax2_tx = $line->localtax2_tx;
449
                            $newndfline->localtax1_type = $line->localtax1_type;
450
                            $newndfline->localtax2_type = $line->localtax2_type;
451
                            $newndfline->comments = $line->comments;
452
                            $newndfline->qty = $line->qty;
453
                            $newndfline->value_unit = $line->value_unit;
454
                            $newndfline->total_ht = $line->total_ht;
455
                            $newndfline->total_ttc = $line->total_ttc;
456
                            $newndfline->total_tva = $line->total_tva;
457
                            $newndfline->total_localtax1 = $line->total_localtax1;
458
                            $newndfline->total_localtax2 = $line->total_localtax2;
459
                            $newndfline->date = $line->date;
460
                            $newndfline->rule_warning_message = $line->rule_warning_message;
461
                            $newndfline->fk_c_exp_tax_cat = $line->fk_c_exp_tax_cat;
462
                            $newndfline->fk_ecm_files = $line->fk_ecm_files;
463
                        } else {
464
                            $newndfline = $line;
465
                        }
466
                        //$newndfline=new ExpenseReportLine($this->db);
467
                        $newndfline->fk_expensereport = $this->id;
468
                        $result = $newndfline->insert();
469
                        if ($result < 0) {
470
                            $this->error = $newndfline->error;
471
                            $this->errors = $newndfline->errors;
472
                            $error++;
473
                            break;
474
                        }
475
                    }
476
                }
477
            }
478
479
            if (!$error) {
480
                $result = $this->insertExtraFields();
481
                if ($result < 0) {
482
                    $error++;
483
                }
484
            }
485
486
            if (!$error) {
487
                $result = $this->update_price(1);
488
                if ($result > 0) {
489
                    if (!$notrigger) {
490
                        // Call trigger
491
                        $result = $this->call_trigger('EXPENSE_REPORT_CREATE', $user);
492
493
                        if ($result < 0) {
494
                            $error++;
495
                        }
496
                        // End call triggers
497
                    }
498
499
                    if (empty($error)) {
500
                        $this->db->commit();
501
                        return $this->id;
502
                    } else {
503
                        $this->db->rollback();
504
                        return -4;
505
                    }
506
                } else {
507
                    $this->db->rollback();
508
                    return -3;
509
                }
510
            } else {
511
                dol_syslog(get_only_class($this) . "::create error " . $this->error, LOG_ERR);
512
                $this->db->rollback();
513
                return -2;
514
            }
515
        } else {
516
            $this->error = $this->db->lasterror() . " sql=" . $sql;
517
            $this->db->rollback();
518
            return -1;
519
        }
520
    }
521
522
    /**
523
     *  Load an object from its id and create a new one in database
524
     *
525
     *  @param      User    $user               User making the clone
526
     *  @param      int     $fk_user_author     Id of new user
527
     *  @return     int                         New id of clone
528
     */
529
    public function createFromClone(User $user, $fk_user_author)
530
    {
531
        global $hookmanager;
532
533
        $error = 0;
534
535
        if (empty($fk_user_author)) {
536
            $fk_user_author = $user->id;
537
        }
538
539
        $this->db->begin();
540
541
        // get extrafields so they will be clone
542
        //foreach($this->lines as $line)
543
        //$line->fetch_optionals();
544
545
        // Load source object
546
        $objFrom = clone $this;
547
548
        $this->id = 0;
549
        $this->ref = '';
550
        $this->status = 0;
551
        $this->fk_statut = 0; // deprecated
552
553
        // Clear fields
554
        $this->fk_user_creat = $user->id;
555
        $this->fk_user_author = $fk_user_author; // Note fk_user_author is not the 'author' but the guy the expense report is for.
556
        $this->fk_user_valid = 0;
557
        $this->date_create = '';
558
        $this->date_creation = '';
559
        $this->date_validation = '';
560
561
        // Remove link on lines to a joined file
562
        if (is_array($this->lines) && count($this->lines) > 0) {
563
            foreach ($this->lines as $key => $line) {
564
                $this->lines[$key]->fk_ecm_files = 0;
565
            }
566
        }
567
568
        // Create clone
569
        $this->context['createfromclone'] = 'createfromclone';
570
        $result = $this->create($user);
571
        if ($result < 0) {
572
            $error++;
573
        }
574
575
        if (!$error) {
576
            // Hook of thirdparty module
577
            if (is_object($hookmanager)) {
578
                $parameters = array('objFrom' => $objFrom);
579
                $action = '';
580
                $reshook = $hookmanager->executeHooks('createFrom', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
581
                if ($reshook < 0) {
582
                    $this->setErrorsFromObject($hookmanager);
583
                    $error++;
584
                }
585
            }
586
        }
587
588
        unset($this->context['createfromclone']);
589
590
        // End
591
        if (!$error) {
592
            $this->db->commit();
593
            return $this->id;
594
        } else {
595
            $this->db->rollback();
596
            return -1;
597
        }
598
    }
599
600
601
    /**
602
     * update
603
     *
604
     * @param   User    $user                   User making change
605
     * @param   int     $notrigger              Disable triggers
606
     * @param   User    $userofexpensereport    New user we want to have the expense report on.
607
     * @return  int                             Return integer <0 if KO, >0 if OK
608
     */
609
    public function update($user, $notrigger = 0, $userofexpensereport = null)
610
    {
611
        global $langs;
612
613
        $error = 0;
614
        $this->db->begin();
615
616
        $sql = "UPDATE " . MAIN_DB_PREFIX . $this->table_element . " SET";
617
        $sql .= " total_ht = " . $this->total_ht;
618
        $sql .= " , total_ttc = " . $this->total_ttc;
619
        $sql .= " , total_tva = " . $this->total_tva;
620
        $sql .= " , date_debut = '" . $this->db->idate($this->date_debut) . "'";
621
        $sql .= " , date_fin = '" . $this->db->idate($this->date_fin) . "'";
622
        if ($userofexpensereport && is_object($userofexpensereport)) {
623
            $sql .= " , fk_user_author = " . ($userofexpensereport->id > 0 ? $userofexpensereport->id : "null"); // Note fk_user_author is not the 'author' but the guy the expense report is for.
624
        }
625
        $sql .= " , fk_user_validator = " . ($this->fk_user_validator > 0 ? $this->fk_user_validator : "null");
626
        $sql .= " , fk_user_valid = " . ($this->fk_user_valid > 0 ? $this->fk_user_valid : "null");
627
        $sql .= " , fk_user_approve = " . ($this->fk_user_approve > 0 ? $this->fk_user_approve : "null");
628
        $sql .= " , fk_user_modif = " . $user->id;
629
        $sql .= " , fk_statut = " . ($this->fk_statut >= 0 ? $this->fk_statut : '0');
630
        $sql .= " , fk_c_paiement = " . ($this->fk_c_paiement > 0 ? $this->fk_c_paiement : "null");
631
        $sql .= " , note_public = " . (!empty($this->note_public) ? "'" . $this->db->escape($this->note_public) . "'" : "''");
632
        $sql .= " , note_private = " . (!empty($this->note_private) ? "'" . $this->db->escape($this->note_private) . "'" : "''");
633
        $sql .= " , detail_refuse = " . (!empty($this->detail_refuse) ? "'" . $this->db->escape($this->detail_refuse) . "'" : "''");
634
        $sql .= " WHERE rowid = " . ((int) $this->id);
635
636
        dol_syslog(get_only_class($this) . "::update", LOG_DEBUG);
637
        $result = $this->db->query($sql);
638
        if ($result) {
639
            if (!$notrigger) {
640
                // Call trigger
641
                $result = $this->call_trigger('EXPENSE_REPORT_MODIFY', $user);
642
643
                if ($result < 0) {
644
                    $error++;
645
                }
646
                // End call triggers
647
            }
648
649
            if (empty($error)) {
650
                $this->db->commit();
651
                return 1;
652
            } else {
653
                $this->db->rollback();
654
                $this->error = $this->db->error();
655
                return -2;
656
            }
657
        } else {
658
            $this->db->rollback();
659
            $this->error = $this->db->error();
660
            return -1;
661
        }
662
    }
663
664
    /**
665
     *  Load an object from database
666
     *
667
     *  @param  int     $id     Id                      {@min 1}
668
     *  @param  string  $ref    Ref                     {@name ref}
669
     *  @return int             Return integer <0 if KO, >0 if OK
670
     */
671
    public function fetch($id, $ref = '')
672
    {
673
        $sql = "SELECT d.rowid, d.entity, d.ref, d.note_public, d.note_private,"; // DEFAULT
674
        $sql .= " d.detail_refuse, d.detail_cancel, d.fk_user_refuse, d.fk_user_cancel,"; // ACTIONS
675
        $sql .= " d.date_refuse, d.date_cancel,"; // ACTIONS
676
        $sql .= " d.total_ht, d.total_ttc, d.total_tva,";
677
        $sql .= " d.localtax1 as total_localtax1, d.localtax2 as total_localtax2,";
678
        $sql .= " d.date_debut, d.date_fin, d.date_create, d.tms as date_modif, d.date_valid, d.date_approve,"; // DATES (datetime)
679
        $sql .= " d.fk_user_creat, d.fk_user_author, d.fk_user_modif, d.fk_user_validator,";
680
        $sql .= " d.fk_user_valid, d.fk_user_approve,";
681
        $sql .= " d.fk_statut as status, d.fk_c_paiement, d.paid";
682
        $sql .= " FROM " . MAIN_DB_PREFIX . $this->table_element . " as d";
683
        if ($ref) {
684
            $sql .= " WHERE d.ref = '" . $this->db->escape($ref) . "'";
685
        } else {
686
            $sql .= " WHERE d.rowid = " . ((int) $id);
687
        }
688
        //$sql.= $restrict;
689
690
        dol_syslog(get_only_class($this) . "::fetch", LOG_DEBUG);
691
        $resql = $this->db->query($sql);
692
        if ($resql) {
693
            $obj = $this->db->fetch_object($resql);
694
            if ($obj) {
695
                $this->id           = $obj->rowid;
696
                $this->ref          = $obj->ref;
697
698
                $this->entity       = $obj->entity;
699
700
                $this->total_ht     = $obj->total_ht;
701
                $this->total_tva    = $obj->total_tva;
702
                $this->total_ttc    = $obj->total_ttc;
703
                $this->localtax1    = $obj->total_localtax1;        // For backward compatibility
704
                $this->localtax2    = $obj->total_localtax2;        // For backward compatibility
705
                $this->total_localtax1 = $obj->total_localtax1;
706
                $this->total_localtax2 = $obj->total_localtax2;
707
708
                $this->note_public  = $obj->note_public;
709
                $this->note_private = $obj->note_private;
710
                $this->detail_refuse = $obj->detail_refuse;
711
                $this->detail_cancel = $obj->detail_cancel;
712
713
                $this->date_debut       = $this->db->jdate($obj->date_debut);
714
                $this->date_fin         = $this->db->jdate($obj->date_fin);
715
                $this->date_valid       = $this->db->jdate($obj->date_valid);
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->db->jdate($obj->date_valid) can also be of type string. However, the property $date_valid is declared as type integer. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
716
                $this->date_approve     = $this->db->jdate($obj->date_approve);
717
                $this->date_create      = $this->db->jdate($obj->date_create);
718
                $this->date_modif       = $this->db->jdate($obj->date_modif);
719
                $this->date_refuse      = $this->db->jdate($obj->date_refuse);
720
                $this->date_cancel      = $this->db->jdate($obj->date_cancel);
721
722
                $this->fk_user_creat            = $obj->fk_user_creat;
723
                $this->fk_user_author           = $obj->fk_user_author; // Note fk_user_author is not the 'author' but the guy the expense report is for.
724
                $this->fk_user_modif            = $obj->fk_user_modif;
725
                $this->fk_user_validator        = $obj->fk_user_validator;
726
                $this->fk_user_valid            = $obj->fk_user_valid;
727
                $this->fk_user_refuse           = $obj->fk_user_refuse;
728
                $this->fk_user_cancel           = $obj->fk_user_cancel;
729
                $this->fk_user_approve          = $obj->fk_user_approve;
730
731
                $user_author = new User($this->db);
732
                if ($this->fk_user_author > 0) {
733
                    $user_author->fetch($this->fk_user_author);
734
                }
735
736
                $this->user_author_infos = dolGetFirstLastname($user_author->firstname, $user_author->lastname);
737
738
                $user_approver = new User($this->db);
739
                if ($this->fk_user_approve > 0) {
740
                    $user_approver->fetch($this->fk_user_approve);
741
                } elseif ($this->fk_user_validator > 0) {
742
                    $user_approver->fetch($this->fk_user_validator); // For backward compatibility
743
                }
744
                $this->user_validator_infos = dolGetFirstLastname($user_approver->firstname, $user_approver->lastname);
745
746
                $this->fk_statut                = $obj->status; // deprecated
747
                $this->status                   = $obj->status;
748
                $this->fk_c_paiement            = $obj->fk_c_paiement;
749
                $this->paid                     = $obj->paid;
750
751
                if ($this->status == self::STATUS_APPROVED || $this->status == self::STATUS_CLOSED) {
752
                    $user_valid = new User($this->db);
753
                    if ($this->fk_user_valid > 0) {
754
                        $user_valid->fetch($this->fk_user_valid);
755
                    }
756
                    $this->user_valid_infos = dolGetFirstLastname($user_valid->firstname, $user_valid->lastname);
757
                }
758
759
                $this->fetch_optionals();
760
761
                $result = $this->fetch_lines();
762
763
                return $result;
764
            } else {
765
                return 0;
766
            }
767
        } else {
768
            $this->error = $this->db->lasterror();
769
            return -1;
770
        }
771
    }
772
773
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
774
    /**
775
     *  Classify the expense report as paid
776
     *
777
     *  @deprecated
778
     *  @see setPaid()
779
     *  @param    int     $id                 Id of expense report
780
     *  @param    user    $fuser              User making change
781
     *  @param    int     $notrigger          Disable triggers
782
     *  @return   int                         Return integer <0 if KO, >0 if OK
783
     */
784
    public function set_paid($id, $fuser, $notrigger = 0)
785
    {
786
		// phpcs:enable
787
        dol_syslog(get_only_class($this) . "::set_paid is deprecated, use setPaid instead", LOG_NOTICE);
788
        return $this->setPaid($id, $fuser, $notrigger);
789
    }
790
791
    /**
792
     *    Classify the expense report as paid
793
     *
794
     *    @param    int     $id                 Id of expense report
795
     *    @param    user    $fuser              User making change
796
     *    @param    int     $notrigger          Disable triggers
797
     *    @return   int                         Return integer <0 if KO, >0 if OK
798
     */
799
    public function setPaid($id, $fuser, $notrigger = 0)
800
    {
801
        $error = 0;
802
        $this->db->begin();
803
804
        $sql = "UPDATE " . MAIN_DB_PREFIX . "expensereport";
805
        $sql .= " SET fk_statut = " . self::STATUS_CLOSED . ", paid=1";
806
        $sql .= " WHERE rowid = " . ((int) $id) . " AND fk_statut = " . self::STATUS_APPROVED;
807
808
        dol_syslog(get_only_class($this) . "::setPaid", LOG_DEBUG);
809
        $resql = $this->db->query($sql);
810
        if ($resql) {
811
            if ($this->db->affected_rows($resql)) {
812
                if (!$notrigger) {
813
                    // Call trigger
814
                    $result = $this->call_trigger('EXPENSE_REPORT_PAID', $fuser);
815
816
                    if ($result < 0) {
817
                        $error++;
818
                    }
819
                    // End call triggers
820
                }
821
822
                if (empty($error)) {
823
                    $this->db->commit();
824
                    return 1;
825
                } else {
826
                    $this->db->rollback();
827
                    $this->error = $this->db->error();
828
                    return -2;
829
                }
830
            } else {
831
                $this->db->commit();
832
                return 0;
833
            }
834
        } else {
835
            $this->db->rollback();
836
            dol_print_error($this->db);
837
            return -1;
838
        }
839
    }
840
841
    /**
842
     *  Returns the label status
843
     *
844
     *  @param      int     $mode       0=long label, 1=short label, 2=Picto + short label, 3=Picto, 4=Picto + long label, 5=Short label + Picto
845
     *  @return     string              Label
846
     */
847
    public function getLibStatut($mode = 0)
848
    {
849
        return $this->LibStatut($this->status, $mode);
850
    }
851
852
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
853
    /**
854
     *  Returns the label of a status
855
     *
856
     *  @param      int     $status     ID status
857
     *  @param      int     $mode       0=long label, 1=short label, 2=Picto + short label, 3=Picto, 4=Picto + long label, 5=Short label + Picto, 6=Long label + Picto
858
     *  @return     string              Label
859
     */
860
    public function LibStatut($status, $mode = 0)
861
    {
862
		// phpcs:enable
863
        global $langs;
864
865
        $labelStatus = $langs->transnoentitiesnoconv($this->labelStatus[$status]);
866
        $labelStatusShort = $langs->transnoentitiesnoconv($this->labelStatusShort[$status]);
867
868
        $statuslogo = array(0 => 'status0', 2 => 'status1', 4 => 'status6', 5 => 'status4', 6 => 'status6', 99 => 'status5');
869
870
        $statusType = $statuslogo[$status];
871
872
        return dolGetStatus($labelStatus, $labelStatusShort, '', $statusType, $mode);
873
    }
874
875
876
    /**
877
     *  Load information on object
878
     *
879
     *  @param  int     $id      Id of object
880
     *  @return void
881
     */
882
    public function info($id)
883
    {
884
        global $conf;
885
886
        $sql = "SELECT f.rowid,";
887
        $sql .= " f.date_create as datec,";
888
        $sql .= " f.tms as date_modification,";
889
        $sql .= " f.date_valid as datev,";
890
        $sql .= " f.date_approve as datea,";
891
        $sql .= " f.fk_user_creat as fk_user_creation,";
892
        $sql .= " f.fk_user_author as fk_user_author,";
893
        $sql .= " f.fk_user_modif as fk_user_modification,";
894
        $sql .= " f.fk_user_valid,";
895
        $sql .= " f.fk_user_approve";
896
        $sql .= " FROM " . MAIN_DB_PREFIX . "expensereport as f";
897
        $sql .= " WHERE f.rowid = " . ((int) $id);
898
        $sql .= " AND f.entity = " . $conf->entity;
899
900
901
902
        $resql = $this->db->query($sql);
903
        if ($resql) {
904
            if ($this->db->num_rows($resql)) {
905
                $obj = $this->db->fetch_object($resql);
906
907
                $this->id = $obj->rowid;
908
909
                $this->date_creation = $this->db->jdate($obj->datec);
910
                $this->date_modification = $this->db->jdate($obj->date_modification);
911
                $this->date_validation = $this->db->jdate($obj->datev);
912
                $this->date_approbation = $this->db->jdate($obj->datea);
913
914
                $this->user_creation_id = $obj->fk_user_author;
915
                $this->user_creation_id = $obj->fk_user_creation;
916
                $this->user_validation_id = $obj->fk_user_valid;
917
                $this->user_modification_id = $obj->fk_user_modification;
918
                $this->user_approve_id = $obj->fk_user_approve;
919
            }
920
            $this->db->free($resql);
921
        } else {
922
            dol_print_error($this->db);
923
        }
924
    }
925
926
927
928
    /**
929
     *  Initialise an instance with random values.
930
     *  Used to build previews or test instances.
931
     *  id must be 0 if object instance is a specimen.
932
     *
933
     *  @return int
934
     */
935
    public function initAsSpecimen()
936
    {
937
        global $user, $langs;
938
939
        $now = dol_now();
940
941
        // Initialise parameters
942
        $this->id = 0;
943
        $this->ref = 'SPECIMEN';
944
        $this->specimen = 1;
945
        $this->entity = 1;
946
        $this->date_create = $now;
947
        $this->date_debut = $now;
948
        $this->date_fin = $now;
949
        $this->date_valid = $now;
950
        $this->date_approve = $now;
951
952
        $type_fees_id = 2; // TF_TRIP
953
954
        $this->status = 5;
955
956
        $this->fk_user_author = $user->id;
957
        $this->fk_user_validator = $user->id;
958
        $this->fk_user_valid = $user->id;
959
        $this->fk_user_approve = $user->id;
960
961
        $this->note_private = 'Private note';
962
        $this->note_public = 'SPECIMEN';
963
        $nbp = 5;
964
        $xnbp = 0;
965
        while ($xnbp < $nbp) {
966
            $line = new ExpenseReportLine($this->db);
967
            $line->comments = $langs->trans("Comment") . " " . $xnbp;
968
            $line->date = ($now - 3600 * (1 + $xnbp));
969
            $line->total_ht = 100;
970
            $line->total_tva = 20;
971
            $line->total_ttc = 120;
972
            $line->qty = 1;
973
            $line->vatrate = 20;
974
            $line->value_unit = 120;
975
            $line->fk_expensereport = 0;
976
            $line->type_fees_code = 'TRA';
977
            $line->fk_c_type_fees = $type_fees_id;
978
979
            $line->projet_ref = 'ABC';
980
981
            $this->lines[$xnbp] = $line;
982
            $xnbp++;
983
984
            $this->total_ht += $line->total_ht;
985
            $this->total_tva += $line->total_tva;
986
            $this->total_ttc += $line->total_ttc;
987
        }
988
989
        return 1;
990
    }
991
992
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
993
    /**
994
     * fetch_line_by_project
995
     *
996
     * @param   int     $projectid      Project id
997
     * @param   User    $user           User
998
     * @return  int                     Return integer <0 if KO, >0 if OK
999
     */
1000
    public function fetch_line_by_project($projectid, $user)
1001
    {
1002
		// phpcs:enable
1003
        global $langs;
1004
1005
        $langs->load('trips');
1006
1007
        if ($user->hasRight('expensereport', 'lire')) {
1008
            $sql = "SELECT de.fk_expensereport, de.date, de.comments, de.total_ht, de.total_ttc";
1009
            $sql .= " FROM " . MAIN_DB_PREFIX . "expensereport_det as de";
1010
            $sql .= " WHERE de.fk_projet = " . ((int) $projectid);
1011
1012
            dol_syslog(get_only_class($this) . "::fetch", LOG_DEBUG);
1013
            $result = $this->db->query($sql);
1014
            if ($result) {
1015
                $num = $this->db->num_rows($result);
1016
                $i = 0;
1017
                $total_HT = 0;
1018
                $total_TTC = 0;
1019
1020
                while ($i < $num) {
1021
                    $objp = $this->db->fetch_object($result);
1022
1023
                    $sql2 = "SELECT d.rowid, d.fk_user_author, d.ref, d.fk_statut as status";
1024
                    $sql2 .= " FROM " . MAIN_DB_PREFIX . "expensereport as d";
1025
                    $sql2 .= " WHERE d.rowid = " . ((int) $objp->fk_expensereport);
1026
1027
                    $result2 = $this->db->query($sql2);
1028
                    $obj = $this->db->fetch_object($result2);
1029
1030
                    $objp->fk_user_author = $obj->fk_user_author;
1031
                    $objp->ref = $obj->ref;
1032
                    $objp->fk_c_expensereport_status = $obj->status;
1033
                    $objp->rowid = $obj->rowid;
1034
1035
                    $total_HT += $objp->total_ht;
1036
                    $total_TTC += $objp->total_ttc;
1037
                    $author = new User($this->db);
1038
                    $author->fetch($objp->fk_user_author);
1039
1040
                    print '<tr>';
1041
                    print '<td><a href="' . constant('BASE_URL') . '/expensereport/card.php?id=' . $objp->rowid . '">' . $objp->ref_num . '</a></td>';
1042
                    print '<td class="center">' . dol_print_date($objp->date, 'day') . '</td>';
1043
                    print '<td>' . $author->getNomUrl(1) . '</td>';
1044
                    print '<td>' . $objp->comments . '</td>';
1045
                    print '<td class="right">' . price($objp->total_ht) . '</td>';
1046
                    print '<td class="right">' . price($objp->total_ttc) . '</td>';
1047
                    print '<td class="right">';
1048
1049
                    switch ($objp->fk_c_expensereport_status) {
1050
                        case 4:
1051
                            print img_picto($langs->trans('StatusOrderCanceled'), 'statut5');
1052
                            break;
1053
                        case 1:
1054
                            print $langs->trans('Draft') . ' ' . img_picto($langs->trans('Draft'), 'statut0');
1055
                            break;
1056
                        case 2:
1057
                            print $langs->trans('TripForValid') . ' ' . img_picto($langs->trans('TripForValid'), 'statut3');
1058
                            break;
1059
                        case 5:
1060
                            print $langs->trans('TripForPaid') . ' ' . img_picto($langs->trans('TripForPaid'), 'statut3');
1061
                            break;
1062
                        case 6:
1063
                            print $langs->trans('TripPaid') . ' ' . img_picto($langs->trans('TripPaid'), 'statut4');
1064
                            break;
1065
                    }
1066
                    /*
1067
                     if ($status==4) return img_picto($langs->trans('StatusOrderCanceled'),'statut5');
1068
                    if ($status==1) return img_picto($langs->trans('StatusOrderDraft'),'statut0');
1069
                    if ($status==2) return img_picto($langs->trans('StatusOrderValidated'),'statut1');
1070
                    if ($status==2) return img_picto($langs->trans('StatusOrderOnProcess'),'statut3');
1071
                    if ($status==5) return img_picto($langs->trans('StatusOrderToBill'),'statut4');
1072
                    if ($status==6) return img_picto($langs->trans('StatusOrderOnProcess'),'statut6');
1073
                    */
1074
                    print '</td>';
1075
                    print '</tr>';
1076
1077
                    $i++;
1078
                }
1079
1080
                print '<tr class="liste_total"><td colspan="4">' . $langs->trans("Number") . ': ' . $i . '</td>';
1081
                print '<td class="right" width="100">' . $langs->trans("TotalHT") . ' : ' . price($total_HT) . '</td>';
1082
                print '<td class="right" width="100">' . $langs->trans("TotalTTC") . ' : ' . price($total_TTC) . '</td>';
1083
                print '<td>&nbsp;</td>';
1084
                print '</tr>';
1085
            } else {
1086
                $this->error = $this->db->lasterror();
1087
                return -1;
1088
            }
1089
        }
1090
1091
        return 0;
1092
    }
1093
1094
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1095
    /**
1096
     * fetch_lines
1097
     *
1098
     * @return  int     Return integer <0 if OK, >0 if KO
1099
     */
1100
    public function fetch_lines()
1101
    {
1102
		// phpcs:enable
1103
        $this->lines = array();
1104
1105
        $sql = ' SELECT de.rowid, de.comments, de.qty, de.value_unit, de.date, de.rang,';
1106
        $sql .= " de." . $this->fk_element . ", de.fk_c_type_fees, de.fk_c_exp_tax_cat, de.fk_projet as fk_project,";
1107
        $sql .= ' de.tva_tx, de.vat_src_code,';
1108
        $sql .= ' de.localtax1_tx, de.localtax2_tx, de.localtax1_type, de.localtax2_type,';
1109
        $sql .= ' de.fk_ecm_files,';
1110
        $sql .= ' de.total_ht, de.total_tva, de.total_ttc,';
1111
        $sql .= ' de.total_localtax1, de.total_localtax2, de.rule_warning_message,';
1112
        $sql .= ' ctf.code as code_type_fees, ctf.label as label_type_fees, ctf.accountancy_code as accountancy_code_type_fees,';
1113
        $sql .= ' p.ref as ref_projet, p.title as title_projet';
1114
        $sql .= ' FROM ' . MAIN_DB_PREFIX . $this->table_element_line . ' as de';
1115
        $sql .= ' LEFT JOIN ' . MAIN_DB_PREFIX . 'c_type_fees as ctf ON de.fk_c_type_fees = ctf.id';
1116
        $sql .= ' LEFT JOIN ' . MAIN_DB_PREFIX . 'projet as p ON de.fk_projet = p.rowid';
1117
        $sql .= " WHERE de." . $this->fk_element . " = " . ((int) $this->id);
1118
        if (getDolGlobalString('EXPENSEREPORT_LINES_SORTED_BY_ROWID')) {
1119
            $sql .= ' ORDER BY de.rang ASC, de.rowid ASC';
1120
        } else {
1121
            $sql .= ' ORDER BY de.rang ASC, de.date ASC';
1122
        }
1123
1124
        $resql = $this->db->query($sql);
1125
        if ($resql) {
1126
            $num = $this->db->num_rows($resql);
1127
            $i = 0;
1128
            while ($i < $num) {
1129
                $objp = $this->db->fetch_object($resql);
1130
1131
                $deplig = new ExpenseReportLine($this->db);
1132
1133
                $deplig->rowid          = $objp->rowid;
1134
                $deplig->id             = $objp->rowid;
1135
                $deplig->comments       = $objp->comments;
1136
                $deplig->qty            = $objp->qty;
1137
                $deplig->value_unit     = $objp->value_unit;
1138
                $deplig->date           = $objp->date;
1139
                $deplig->dates          = $this->db->jdate($objp->date);
1140
1141
                $deplig->fk_expensereport = $objp->fk_expensereport;
1142
                $deplig->fk_c_type_fees   = $objp->fk_c_type_fees;
1143
                $deplig->fk_c_exp_tax_cat = $objp->fk_c_exp_tax_cat;
1144
                $deplig->fk_projet        = $objp->fk_project; // deprecated
1145
                $deplig->fk_project       = $objp->fk_project;
1146
                $deplig->fk_ecm_files     = $objp->fk_ecm_files;
1147
1148
                $deplig->total_ht         = $objp->total_ht;
1149
                $deplig->total_tva        = $objp->total_tva;
1150
                $deplig->total_ttc        = $objp->total_ttc;
1151
                $deplig->total_localtax1  = $objp->total_localtax1;
1152
                $deplig->total_localtax2  = $objp->total_localtax2;
1153
1154
                $deplig->type_fees_code     = empty($objp->code_type_fees) ? 'TF_OTHER' : $objp->code_type_fees;
1155
                $deplig->type_fees_libelle  = $objp->label_type_fees;
1156
                $deplig->type_fees_accountancy_code = $objp->accountancy_code_type_fees;
1157
1158
                $deplig->tva_tx             = $objp->tva_tx;
1159
                $deplig->vatrate            = $objp->tva_tx;
1160
                $deplig->vat_src_code       = $objp->vat_src_code;
1161
                $deplig->localtax1_tx       = $objp->localtax1_tx;
1162
                $deplig->localtax2_tx       = $objp->localtax2_tx;
1163
                $deplig->localtax1_type     = $objp->localtax1_type;
1164
                $deplig->localtax2_type     = $objp->localtax2_type;
1165
1166
                $deplig->projet_ref         = $objp->ref_projet;
1167
                $deplig->projet_title       = $objp->title_projet;
1168
1169
                $deplig->rule_warning_message = $objp->rule_warning_message;
1170
1171
                $deplig->rang               = $objp->rang;
1172
1173
                $this->lines[$i] = $deplig;
1174
1175
                $i++;
1176
            }
1177
            $this->db->free($resql);
1178
            return 1;
1179
        } else {
1180
            $this->error = $this->db->lasterror();
1181
            dol_syslog(get_only_class($this) . "::fetch_lines: Error " . $this->error, LOG_ERR);
1182
            return -3;
1183
        }
1184
    }
1185
1186
1187
    /**
1188
     * Delete object in database
1189
     *
1190
     * @param   User|null   $user       User that delete
1191
     * @param   int         $notrigger  0=launch triggers after, 1=disable triggers
1192
     * @return  int                     Return integer <0 if KO, >0 if OK
1193
     */
1194
    public function delete(User $user = null, $notrigger = 0)
1195
    {
1196
        global $conf;
1197
        require_once constant('DOL_DOCUMENT_ROOT') . '/core/lib/files.lib.php';
1198
1199
        $error = 0;
1200
1201
        $this->db->begin();
1202
1203
        if (!$notrigger) {
1204
            // Call trigger
1205
            $result = $this->call_trigger('EXPENSE_REPORT_DELETE', $user);
1206
            if ($result < 0) {
1207
                $error++;
1208
            }
1209
            // End call triggers
1210
        }
1211
1212
        // Delete extrafields of lines and lines
1213
        if (!$error && !empty($this->table_element_line)) {
1214
            $tabletodelete = $this->table_element_line;
1215
            //$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).")";
1216
            $sql = "DELETE FROM " . MAIN_DB_PREFIX . $tabletodelete . " WHERE " . $this->fk_element . " = " . ((int) $this->id);
1217
            if (!$this->db->query($sql)) {
1218
                $error++;
1219
                $this->error = $this->db->lasterror();
1220
                $this->errors[] = $this->error;
1221
                dol_syslog(get_only_class($this) . "::delete error " . $this->error, LOG_ERR);
1222
            }
1223
        }
1224
1225
        if (!$error) {
1226
            // Delete linked object
1227
            $res = $this->deleteObjectLinked();
1228
            if ($res < 0) {
1229
                $error++;
1230
            }
1231
        }
1232
1233
        if (!$error) {
1234
            // Delete linked contacts
1235
            $res = $this->delete_linked_contact();
1236
            if ($res < 0) {
1237
                $error++;
1238
            }
1239
        }
1240
1241
        // Removed extrafields of object
1242
        if (!$error) {
1243
            $result = $this->deleteExtraFields();
1244
            if ($result < 0) {
1245
                $error++;
1246
                dol_syslog(get_only_class($this) . "::delete error " . $this->error, LOG_ERR);
1247
            }
1248
        }
1249
1250
        // Delete main record
1251
        if (!$error) {
1252
            $sql = "DELETE FROM " . MAIN_DB_PREFIX . $this->table_element . " WHERE rowid = " . ((int) $this->id);
1253
            $res = $this->db->query($sql);
1254
            if (!$res) {
1255
                $error++;
1256
                $this->error = $this->db->lasterror();
1257
                $this->errors[] = $this->error;
1258
                dol_syslog(get_only_class($this) . "::delete error " . $this->error, LOG_ERR);
1259
            }
1260
        }
1261
1262
        // Delete record into ECM index and physically
1263
        if (!$error) {
1264
            $res = $this->deleteEcmFiles(0); // Deleting files physically is done later with the dol_delete_dir_recursive
1265
            $res = $this->deleteEcmFiles(1); // Deleting files physically is done later with the dol_delete_dir_recursive
1266
            if (!$res) {
1267
                $error++;
1268
            }
1269
        }
1270
1271
        if (!$error) {
1272
            // We remove directory
1273
            $ref = dol_sanitizeFileName($this->ref);
1274
            if ($conf->expensereport->multidir_output[$this->entity] && !empty($this->ref)) {
1275
                $dir = $conf->expensereport->multidir_output[$this->entity] . "/" . $ref;
1276
                $file = $dir . "/" . $ref . ".pdf";
1277
                if (file_exists($file)) {
1278
                    dol_delete_preview($this);
1279
1280
                    if (!dol_delete_file($file, 0, 0, 0, $this)) {
1281
                        $this->error = 'ErrorFailToDeleteFile';
1282
                        $this->errors[] = $this->error;
1283
                        $this->db->rollback();
1284
                        return 0;
1285
                    }
1286
                }
1287
                if (file_exists($dir)) {
1288
                    $res = @dol_delete_dir_recursive($dir);
1289
                    if (!$res) {
1290
                        $this->error = 'ErrorFailToDeleteDir';
1291
                        $this->errors[] = $this->error;
1292
                        $this->db->rollback();
1293
                        return 0;
1294
                    }
1295
                }
1296
            }
1297
        }
1298
1299
        if (!$error) {
1300
            dol_syslog(get_only_class($this) . "::delete " . $this->id . " by " . $user->id, LOG_DEBUG);
1301
            $this->db->commit();
1302
            return 1;
1303
        } else {
1304
            $this->db->rollback();
1305
            return -1;
1306
        }
1307
    }
1308
1309
    /**
1310
     * Set to status validate
1311
     *
1312
     * @param   User    $fuser      User
1313
     * @param   int     $notrigger  Disable triggers
1314
     * @return  int                 Return integer <0 if KO, 0 if nothing done, >0 if OK
1315
     */
1316
    public function setValidate($fuser, $notrigger = 0)
1317
    {
1318
        global $conf, $langs, $user;
1319
1320
        $error = 0;
1321
        $now = dol_now();
1322
1323
        // Protection
1324
        if ($this->status == self::STATUS_VALIDATED) {
1325
            dol_syslog(get_only_class($this) . "::valid action abandoned: already validated", LOG_WARNING);
1326
            return 0;
1327
        }
1328
1329
        $this->date_valid = $now; // Required for the getNextNum later.
1330
1331
        // Define new ref
1332
        if (!$error && (preg_match('/^[\(]?PROV/i', $this->ref) || empty($this->ref))) { // empty should not happened, but when it occurs, the test save life
1333
            $num = $this->getNextNumRef();
1334
        } else {
1335
            $num = $this->ref;
1336
        }
1337
        if (empty($num) || $num < 0) {
1338
            return -1;
1339
        }
1340
1341
        $this->newref = dol_sanitizeFileName($num);
1342
1343
        $this->db->begin();
1344
1345
        // Validate
1346
        $sql = "UPDATE " . MAIN_DB_PREFIX . $this->table_element;
1347
        $sql .= " SET ref = '" . $this->db->escape($num) . "',";
1348
        $sql .= " fk_statut = " . self::STATUS_VALIDATED . ",";
1349
        $sql .= " date_valid = '" . $this->db->idate($this->date_valid) . "',";
1350
        $sql .= " fk_user_valid = " . ((int) $user->id);
1351
        $sql .= " WHERE rowid = " . ((int) $this->id);
1352
1353
        $resql = $this->db->query($sql);
1354
        if ($resql) {
1355
            if (!$error && !$notrigger) {
1356
                // Call trigger
1357
                $result = $this->call_trigger('EXPENSE_REPORT_VALIDATE', $fuser);
1358
                if ($result < 0) {
1359
                    $error++;
1360
                }
1361
                // End call triggers
1362
            }
1363
1364
            if (!$error) {
1365
                $this->oldref = $this->ref;
1366
1367
                // Rename directory if dir was a temporary ref
1368
                if (preg_match('/^[\(]?PROV/i', $this->ref)) {
1369
                    require_once constant('DOL_DOCUMENT_ROOT') . '/core/lib/files.lib.php';
1370
1371
                    // Now we rename also files into index
1372
                    $sql = 'UPDATE ' . MAIN_DB_PREFIX . "ecm_files set filename = CONCAT('" . $this->db->escape($this->newref) . "', SUBSTR(filename, " . (strlen($this->ref) + 1) . ")), filepath = 'expensereport/" . $this->db->escape($this->newref) . "'";
1373
                    $sql .= " WHERE filename LIKE '" . $this->db->escape($this->ref) . "%' AND filepath = 'expensereport/" . $this->db->escape($this->ref) . "' AND entity = " . ((int) $this->entity);
1374
                    $resql = $this->db->query($sql);
1375
                    if (!$resql) {
1376
                        $error++;
1377
                        $this->error = $this->db->lasterror();
1378
                    }
1379
                    $sql = 'UPDATE ' . MAIN_DB_PREFIX . "ecm_files set filepath = 'expensereport/" . $this->db->escape($this->newref) . "'";
1380
                    $sql .= " WHERE filepath = 'expensereport/" . $this->db->escape($this->ref) . "' and entity = " . $conf->entity;
1381
                    $resql = $this->db->query($sql);
1382
                    if (!$resql) {
1383
                        $error++;
1384
                        $this->error = $this->db->lasterror();
1385
                    }
1386
1387
                    // We rename directory ($this->ref = old ref, $num = new ref) in order not to lose the attachments
1388
                    $oldref = dol_sanitizeFileName($this->ref);
1389
                    $newref = dol_sanitizeFileName($num);
1390
                    $dirsource = $conf->expensereport->multidir_output[$this->entity] . '/' . $oldref;
1391
                    $dirdest = $conf->expensereport->multidir_output[$this->entity] . '/' . $newref;
1392
                    if (!$error && file_exists($dirsource)) {
1393
                        dol_syslog(get_only_class($this) . "::setValidate() rename dir " . $dirsource . " into " . $dirdest);
1394
1395
                        if (@rename($dirsource, $dirdest)) {
1396
                            dol_syslog("Rename ok");
1397
                            // Rename docs starting with $oldref with $newref
1398
                            $listoffiles = dol_dir_list($dirdest, 'files', 1, '^' . preg_quote($oldref, '/'));
1399
                            foreach ($listoffiles as $fileentry) {
1400
                                $dirsource = $fileentry['name'];
1401
                                $dirdest = preg_replace('/^' . preg_quote($oldref, '/') . '/', $newref, $dirsource);
1402
                                $dirsource = $fileentry['path'] . '/' . $dirsource;
1403
                                $dirdest = $fileentry['path'] . '/' . $dirdest;
1404
                                @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

1404
                                /** @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...
1405
                            }
1406
                        }
1407
                    }
1408
                }
1409
            }
1410
1411
            // Set new ref and current status
1412
            if (!$error) {
1413
                $this->ref = $num;
1414
                $this->status = self::STATUS_VALIDATED;
1415
            }
1416
1417
            if (empty($error)) {
1418
                $this->db->commit();
1419
                return 1;
1420
            } else {
1421
                $this->db->rollback();
1422
                $this->error = $this->db->error();
1423
                return -2;
1424
            }
1425
        } else {
1426
            $this->db->rollback();
1427
            $this->error = $this->db->lasterror();
1428
            return -1;
1429
        }
1430
    }
1431
1432
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1433
    /**
1434
     * set_save_from_refuse
1435
     *
1436
     * @param   User    $fuser      User
1437
     * @return  int                 Return integer <0 if KO, >0 if OK
1438
     */
1439
    public function set_save_from_refuse($fuser)
1440
    {
1441
		// phpcs:enable
1442
        // Sélection de la date de début de la NDF
1443
        $sql = 'SELECT date_debut';
1444
        $sql .= ' FROM ' . MAIN_DB_PREFIX . $this->table_element;
1445
        $sql .= " WHERE rowid = " . ((int) $this->id);
1446
1447
        $result = $this->db->query($sql);
1448
1449
        $objp = $this->db->fetch_object($result);
1450
1451
        $this->date_debut = $this->db->jdate($objp->date_debut);
1452
1453
        if ($this->status != self::STATUS_VALIDATED) {
1454
            $sql = 'UPDATE ' . MAIN_DB_PREFIX . $this->table_element;
1455
            $sql .= " SET fk_statut = " . self::STATUS_VALIDATED;
1456
            $sql .= " WHERE rowid = " . ((int) $this->id);
1457
1458
            dol_syslog(get_only_class($this) . "::set_save_from_refuse", LOG_DEBUG);
1459
1460
            if ($this->db->query($sql)) {
1461
                return 1;
1462
            } else {
1463
                $this->error = $this->db->lasterror();
1464
                return -1;
1465
            }
1466
        } else {
1467
            dol_syslog(get_only_class($this) . "::set_save_from_refuse expensereport already with save status", LOG_WARNING);
1468
        }
1469
1470
        return 0;
1471
    }
1472
1473
    /**
1474
     * Set status to approved
1475
     *
1476
     * @param   User    $fuser      User
1477
     * @param   int     $notrigger  Disable triggers
1478
     * @return  int                 Return integer <0 if KO, 0 if nothing done, >0 if OK
1479
     */
1480
    public function setApproved($fuser, $notrigger = 0)
1481
    {
1482
        $now = dol_now();
1483
        $error = 0;
1484
1485
        // date approval
1486
        $this->date_approve = $now;
1487
        if ($this->status != self::STATUS_APPROVED) {
1488
            $this->db->begin();
1489
1490
            $sql = 'UPDATE ' . MAIN_DB_PREFIX . $this->table_element;
1491
            $sql .= " SET ref = '" . $this->db->escape($this->ref) . "', fk_statut = " . self::STATUS_APPROVED . ", fk_user_approve = " . ((int) $fuser->id) . ",";
1492
            $sql .= " date_approve='" . $this->db->idate($this->date_approve) . "'";
1493
            $sql .= " WHERE rowid = " . ((int) $this->id);
1494
            if ($this->db->query($sql)) {
1495
                if (!$notrigger) {
1496
                    // Call trigger
1497
                    $result = $this->call_trigger('EXPENSE_REPORT_APPROVE', $fuser);
1498
1499
                    if ($result < 0) {
1500
                        $error++;
1501
                    }
1502
                    // End call triggers
1503
                }
1504
1505
                if (empty($error)) {
1506
                    $this->db->commit();
1507
                    return 1;
1508
                } else {
1509
                    $this->db->rollback();
1510
                    $this->error = $this->db->error();
1511
                    return -2;
1512
                }
1513
            } else {
1514
                $this->db->rollback();
1515
                $this->error = $this->db->lasterror();
1516
                return -1;
1517
            }
1518
        } else {
1519
            dol_syslog(get_only_class($this) . "::setApproved expensereport already with approve status", LOG_WARNING);
1520
        }
1521
1522
        return 0;
1523
    }
1524
1525
    /**
1526
     * setDeny
1527
     *
1528
     * @param User      $fuser      User
1529
     * @param string    $details    Details
1530
     * @param int       $notrigger  Disable triggers
1531
     * @return int
1532
     */
1533
    public function setDeny($fuser, $details, $notrigger = 0)
1534
    {
1535
        $now = dol_now();
1536
        $error = 0;
1537
1538
        // date de refus
1539
        if ($this->status != self::STATUS_REFUSED) {
1540
            $sql = 'UPDATE ' . MAIN_DB_PREFIX . $this->table_element;
1541
            $sql .= " SET ref = '" . $this->db->escape($this->ref) . "', fk_statut = " . self::STATUS_REFUSED . ", fk_user_refuse = " . ((int) $fuser->id) . ",";
1542
            $sql .= " date_refuse='" . $this->db->idate($now) . "',";
1543
            $sql .= " detail_refuse='" . $this->db->escape($details) . "',";
1544
            $sql .= " fk_user_approve = NULL";
1545
            $sql .= " WHERE rowid = " . ((int) $this->id);
1546
            if ($this->db->query($sql)) {
1547
                $this->fk_statut = 99; // deprecated
1548
                $this->status = 99;
1549
                $this->fk_user_refuse = $fuser->id;
1550
                $this->detail_refuse = $details;
1551
                $this->date_refuse = $now;
1552
1553
                if (!$notrigger) {
1554
                    // Call trigger
1555
                    $result = $this->call_trigger('EXPENSE_REPORT_DENY', $fuser);
1556
1557
                    if ($result < 0) {
1558
                        $error++;
1559
                    }
1560
                    // End call triggers
1561
                }
1562
1563
                if (empty($error)) {
1564
                    $this->db->commit();
1565
                    return 1;
1566
                } else {
1567
                    $this->db->rollback();
1568
                    $this->error = $this->db->error();
1569
                    return -2;
1570
                }
1571
            } else {
1572
                $this->db->rollback();
1573
                $this->error = $this->db->lasterror();
1574
                return -1;
1575
            }
1576
        } else {
1577
            dol_syslog(get_only_class($this) . "::setDeny expensereport already with refuse status", LOG_WARNING);
1578
        }
1579
1580
        return 0;
1581
    }
1582
1583
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1584
    /**
1585
     * set_unpaid
1586
     *
1587
     *  @deprecated
1588
     *  @see setUnpaid()
1589
     * @param   User    $fuser      User
1590
     * @param   int     $notrigger  Disable triggers
1591
     * @return  int                 Return integer <0 if KO, >0 if OK
1592
     */
1593
    public function set_unpaid($fuser, $notrigger = 0)
1594
    {
1595
		// phpcs:enable
1596
        dol_syslog(get_only_class($this) . "::set_unpaid is deprecated, use setUnpaid instead", LOG_NOTICE);
1597
        return $this->setUnpaid($fuser, $notrigger);
1598
    }
1599
1600
    /**
1601
     * set_unpaid
1602
     *
1603
     * @param   User    $fuser      User
1604
     * @param   int     $notrigger  Disable triggers
1605
     * @return  int                 Return integer <0 if KO, >0 if OK
1606
     */
1607
    public function setUnpaid($fuser, $notrigger = 0)
1608
    {
1609
        $error = 0;
1610
1611
        if ($this->paid) {
1612
            $this->db->begin();
1613
1614
            $sql = 'UPDATE ' . MAIN_DB_PREFIX . $this->table_element;
1615
            $sql .= " SET paid = 0, fk_statut = " . self::STATUS_APPROVED;
1616
            $sql .= " WHERE rowid = " . ((int) $this->id);
1617
1618
            dol_syslog(get_only_class($this) . "::set_unpaid", LOG_DEBUG);
1619
1620
            if ($this->db->query($sql)) {
1621
                if (!$notrigger) {
1622
                    // Call trigger
1623
                    $result = $this->call_trigger('EXPENSE_REPORT_UNPAID', $fuser);
1624
1625
                    if ($result < 0) {
1626
                        $error++;
1627
                    }
1628
                    // End call triggers
1629
                }
1630
1631
                if (empty($error)) {
1632
                    $this->db->commit();
1633
                    return 1;
1634
                } else {
1635
                    $this->db->rollback();
1636
                    $this->error = $this->db->error();
1637
                    return -2;
1638
                }
1639
            } else {
1640
                $this->db->rollback();
1641
                $this->error = $this->db->error();
1642
                return -1;
1643
            }
1644
        } else {
1645
            dol_syslog(get_only_class($this) . "::set_unpaid expensereport already with unpaid status", LOG_WARNING);
1646
        }
1647
1648
        return 0;
1649
    }
1650
1651
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1652
    /**
1653
     * set_cancel
1654
     *
1655
     * @param   User    $fuser      User
1656
     * @param   string  $detail     Detail
1657
     * @param   int     $notrigger  Disable triggers
1658
     * @return  int                 Return integer <0 if KO, >0 if OK
1659
     */
1660
    public function set_cancel($fuser, $detail, $notrigger = 0)
1661
    {
1662
		// phpcs:enable
1663
        $error = 0;
1664
        $this->date_cancel = $this->db->idate(dol_now());
1665
        if ($this->status != self::STATUS_CANCELED) {
1666
            $this->db->begin();
1667
1668
            $sql = 'UPDATE ' . MAIN_DB_PREFIX . $this->table_element;
1669
            $sql .= " SET fk_statut = " . self::STATUS_CANCELED . ", fk_user_cancel = " . ((int) $fuser->id);
1670
            $sql .= ", date_cancel='" . $this->db->idate($this->date_cancel) . "'";
1671
            $sql .= ", detail_cancel='" . $this->db->escape($detail) . "'";
1672
            $sql .= " WHERE rowid = " . ((int) $this->id);
1673
1674
            dol_syslog(get_only_class($this) . "::set_cancel", LOG_DEBUG);
1675
1676
            if ($this->db->query($sql)) {
1677
                if (!$notrigger) {
1678
                    // Call trigger
1679
                    $result = $this->call_trigger('EXPENSE_REPORT_CANCEL', $fuser);
1680
1681
                    if ($result < 0) {
1682
                        $error++;
1683
                    }
1684
                    // End call triggers
1685
                }
1686
1687
                if (empty($error)) {
1688
                    $this->db->commit();
1689
                    return 1;
1690
                } else {
1691
                    $this->db->rollback();
1692
                    $this->error = $this->db->error();
1693
                    return -2;
1694
                }
1695
            } else {
1696
                $this->db->rollback();
1697
                $this->error = $this->db->error();
1698
                return -1;
1699
            }
1700
        } else {
1701
            dol_syslog(get_only_class($this) . "::set_cancel expensereport already with cancel status", LOG_WARNING);
1702
        }
1703
        return 0;
1704
    }
1705
1706
    /**
1707
     * Return next reference of expense report not already used
1708
     *
1709
     * @return    string|int<-2,-1>            free ref, or <0 if error
1710
     */
1711
    public function getNextNumRef()
1712
    {
1713
        global $langs, $conf;
1714
        $langs->load("trips");
1715
1716
        if (getDolGlobalString('EXPENSEREPORT_ADDON')) {
1717
            $mybool = false;
1718
1719
            $file = getDolGlobalString('EXPENSEREPORT_ADDON') . ".php";
1720
            $classname = getDolGlobalString('EXPENSEREPORT_ADDON');
1721
1722
            // Include file with class
1723
            $dirmodels = array_merge(array('/'), (array) $conf->modules_parts['models']);
1724
            foreach ($dirmodels as $reldir) {
1725
                $dir = dol_buildpath($reldir . "core/modules/expensereport/");
1726
1727
                // Load file with numbering class (if found)
1728
                $mybool = ((bool) @include_once $dir . $file) || $mybool;
1729
            }
1730
1731
            if (!$mybool) {
1732
                dol_print_error(null, "Failed to include file " . $file);
1733
                return '';
1734
            }
1735
1736
            $obj = new $classname();
1737
            $numref = $obj->getNextValue($this);
1738
1739
            if ($numref != "") {
1740
                return $numref;
1741
            } else {
1742
                $this->error = $obj->error;
1743
                $this->errors = $obj->errors;
1744
                //dol_print_error($this->db,get_only_class($this)."::getNextNumRef ".$obj->error);
1745
                return -1;
1746
            }
1747
        } else {
1748
            $this->error = "Error_EXPENSEREPORT_ADDON_NotDefined";
1749
            return -2;
1750
        }
1751
    }
1752
1753
    /**
1754
     * getTooltipContentArray
1755
     *
1756
     * @param array $params ex option, infologin
1757
     * @since v18
1758
     * @return array{picto:string,ref?:string,total_ht?:string,total_tva?:string,total_ttc?:string}
1759
     */
1760
    public function getTooltipContentArray($params)
1761
    {
1762
        global $conf, $langs;
1763
1764
        $langs->load('trips');
1765
1766
        $nofetch = !empty($params['nofetch']);
1767
        $moretitle = $params['moretitle'] ?? '';
1768
1769
        $datas = array();
1770
        $datas['picto'] = img_picto('', $this->picto) . ' <u class="paddingrightonly">' . $langs->trans("ExpenseReport") . '</u>';
1771
        if (isset($this->status)) {
1772
            $datas['picto'] .= ' ' . $this->getLibStatut(5);
1773
        }
1774
        if ($moretitle) {
1775
            $datas['picto'] .= ' - ' . $moretitle;
1776
        }
1777
        if (!empty($this->ref)) {
1778
            $datas['ref'] = '<br><b>' . $langs->trans('Ref') . ':</b> ' . $this->ref;
1779
        }
1780
        if (!empty($this->total_ht)) {
1781
            $datas['total_ht'] = '<br><b>' . $langs->trans('AmountHT') . ':</b> ' . price($this->total_ht, 0, $langs, 0, -1, -1, $conf->currency);
1782
        }
1783
        if (!empty($this->total_tva)) {
1784
            $datas['total_tva'] = '<br><b>' . $langs->trans('VAT') . ':</b> ' . price($this->total_tva, 0, $langs, 0, -1, -1, $conf->currency);
1785
        }
1786
        if (!empty($this->total_ttc)) {
1787
            $datas['total_ttc'] = '<br><b>' . $langs->trans('AmountTTC') . ':</b> ' . price($this->total_ttc, 0, $langs, 0, -1, -1, $conf->currency);
1788
        }
1789
1790
        return $datas;
1791
    }
1792
1793
    /**
1794
     *  Return clicable name (with picto eventually)
1795
     *
1796
     *  @param      int     $withpicto                  0=No picto, 1=Include picto into link, 2=Only picto
1797
     *  @param      string  $option                     Where point the link ('', 'document', ..)
1798
     *  @param      int     $max                        Max length of shown ref
1799
     *  @param      int     $short                      1=Return just URL
1800
     *  @param      string  $moretitle                  Add more text to title tooltip
1801
     *  @param      int     $notooltip                  1=Disable tooltip
1802
     *  @param      int     $save_lastsearch_value      -1=Auto, 0=No save of lastsearch_values when clicking, 1=Save lastsearch_values whenclicking
1803
     *  @return     string                              String with URL
1804
     */
1805
    public function getNomUrl($withpicto = 0, $option = '', $max = 0, $short = 0, $moretitle = '', $notooltip = 0, $save_lastsearch_value = -1)
1806
    {
1807
        global $langs, $hookmanager;
1808
1809
        $result = '';
1810
1811
        $url = constant('BASE_URL') . '/expensereport/card.php?id=' . $this->id;
1812
1813
        if ($short) {
1814
            return $url;
1815
        }
1816
1817
        $params = [
1818
            'id' => $this->id,
1819
            'objecttype' => $this->element,
1820
            'option' => $option,
1821
            'moretitle' => $moretitle,
1822
            'nofetch' => 1,
1823
        ];
1824
        $classfortooltip = 'classfortooltip';
1825
        $dataparams = '';
1826
        if (getDolGlobalInt('MAIN_ENABLE_AJAX_TOOLTIP')) {
1827
            $classfortooltip = 'classforajaxtooltip';
1828
            $dataparams = ' data-params="' . dol_escape_htmltag(json_encode($params)) . '"';
1829
            $label = '';
1830
        } else {
1831
            $label = implode($this->getTooltipContentArray($params));
1832
        }
1833
1834
        if ($option != 'nolink') {
1835
            // Add param to save lastsearch_values or not
1836
            $add_save_lastsearch_values = ($save_lastsearch_value == 1 ? 1 : 0);
1837
            if ($save_lastsearch_value == -1 && isset($_SERVER["PHP_SELF"]) && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) {
1838
                $add_save_lastsearch_values = 1;
1839
            }
1840
            if ($add_save_lastsearch_values) {
1841
                $url .= '&save_lastsearch_values=1';
1842
            }
1843
        }
1844
1845
        $ref = $this->ref;
1846
        if (empty($ref)) {
1847
            $ref = $this->id;
1848
        }
1849
1850
        $linkclose = '';
1851
        if (empty($notooltip)) {
1852
            if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
1853
                $label = $langs->trans("ShowExpenseReport");
1854
                $linkclose .= ' alt="' . dol_escape_htmltag($label, 1) . '"';
1855
            }
1856
            $linkclose .= ($label ? ' title="' . dol_escape_htmltag($label, 1) . '"' : ' title="tocomplete"');
1857
            $linkclose .= $dataparams . ' class="' . $classfortooltip . '"';
1858
        }
1859
1860
        $linkstart = '<a href="' . $url . '"';
1861
        $linkstart .= $linkclose . '>';
1862
        $linkend = '</a>';
1863
1864
        $result .= $linkstart;
1865
        if ($withpicto) {
1866
            $result .= img_object(($notooltip ? '' : $label), ($this->picto ? $this->picto : 'generic'), ($notooltip ? (($withpicto != 2) ? 'class="paddingright"' : '') : 'class="' . (($withpicto != 2) ? 'paddingright ' : '') . '"'), 0, 0, $notooltip ? 0 : 1);
1867
        }
1868
        if ($withpicto != 2) {
1869
            $result .= ($max ? dol_trunc($ref, $max) : $ref);
1870
        }
1871
        $result .= $linkend;
1872
1873
        global $action;
1874
        $hookmanager->initHooks(array($this->element . 'dao'));
1875
        $parameters = array('id' => $this->id, 'getnomurl' => &$result);
1876
        $reshook = $hookmanager->executeHooks('getNomUrl', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
1877
        if ($reshook > 0) {
1878
            $result = $hookmanager->resPrint;
1879
        } else {
1880
            $result .= $hookmanager->resPrint;
1881
        }
1882
        return $result;
1883
    }
1884
1885
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1886
    /**
1887
     *  Update total of an expense report when you add a line.
1888
     *
1889
     *  @param    string    $ligne_total_ht     Amount without taxes
1890
     *  @param    string    $ligne_total_tva    Amount of all taxes
1891
     *  @return   int
1892
     */
1893
    public function update_totaux_add($ligne_total_ht, $ligne_total_tva)
1894
    {
1895
		// phpcs:enable
1896
        $this->total_ht += (float) $ligne_total_ht;
1897
        $this->total_tva += (float) $ligne_total_tva;
1898
        $this->total_ttc += $this->total_tva;
1899
1900
        $sql = "UPDATE " . MAIN_DB_PREFIX . $this->table_element . " SET";
1901
        $sql .= " total_ht = " . $this->total_ht;
1902
        $sql .= " , total_ttc = " . $this->total_ttc;
1903
        $sql .= " , total_tva = " . $this->total_tva;
1904
        $sql .= " WHERE rowid = " . ((int) $this->id);
1905
1906
        $result = $this->db->query($sql);
1907
        if ($result) {
1908
            return 1;
1909
        } else {
1910
            $this->error = $this->db->error();
1911
            return -1;
1912
        }
1913
    }
1914
1915
    /**
1916
     * Add expense report line
1917
     *
1918
     * @param    float       $qty                      Qty
1919
     * @param    double      $up                       Unit price (price with tax)
1920
     * @param    int         $fk_c_type_fees           Type payment
1921
     * @param    int<-1,0>|string   $vatrate                  Vat rate (Can be '10' or '10 (ABC)')
1922
     * @param    string      $date                     Date
1923
     * @param    string      $comments                 Description
1924
     * @param    int         $fk_project               Project id
1925
     * @param    int         $fk_c_exp_tax_cat         Car category id
1926
     * @param    int         $type                     Type line
1927
     * @param    int         $fk_ecm_files             Id of ECM file to link to this expensereport line
1928
     * @return   int                                   Return integer <0 if KO, >0 if OK
1929
     */
1930
    public function addline($qty = 0, $up = 0, $fk_c_type_fees = 0, $vatrate = 0, $date = '', $comments = '', $fk_project = 0, $fk_c_exp_tax_cat = 0, $type = 0, $fk_ecm_files = 0)
1931
    {
1932
        global $langs, $mysoc;
1933
1934
        dol_syslog(get_only_class($this) . "::addline qty=$qty, up=$up, fk_c_type_fees=$fk_c_type_fees, vatrate=$vatrate, date=$date, fk_project=$fk_project, type=$type, comments=$comments", LOG_DEBUG);
1935
1936
        if ($this->status == self::STATUS_DRAFT) {
1937
            if (empty($qty)) {
1938
                $qty = 0;
1939
            }
1940
            if (empty($fk_c_type_fees) || $fk_c_type_fees < 0) {
1941
                $fk_c_type_fees = 0;
1942
            }
1943
            if (empty($fk_c_exp_tax_cat) || $fk_c_exp_tax_cat < 0) {
1944
                $fk_c_exp_tax_cat = 0;
1945
            }
1946
            if (empty($vatrate) || $vatrate < 0) {
1947
                $vatrate = 0;
1948
            }
1949
            if (empty($date)) {
1950
                $date = '';
1951
            }
1952
            if (empty($fk_project)) {
1953
                $fk_project = 0;
1954
            }
1955
1956
            $qty = (float) price2num($qty);
1957
            if (!preg_match('/\s*\((.*)\)/', $vatrate)) {
1958
                $vatrate = price2num($vatrate); // $txtva can have format '5.0 (XXX)' or '5'
1959
            }
1960
            $up = price2num($up);
1961
1962
            $this->db->begin();
1963
1964
            $this->line = new ExpenseReportLine($this->db);
1965
1966
            // We don't know seller and buyer for expense reports
1967
            $seller = $mysoc;           // We use same than current company (expense report are often done in same country)
1968
            $seller->tva_assuj = 1;     // Most seller uses vat
1969
            $buyer = new Societe($this->db);
1970
1971
            $localtaxes_type = getLocalTaxesFromRate($vatrate, 0, $buyer, $seller);
1972
1973
            $vat_src_code = '';
1974
            $reg = array();
1975
            if (preg_match('/\s*\((.*)\)/', $vatrate, $reg)) {
1976
                $vat_src_code = $reg[1];
1977
                $vatrate = preg_replace('/\s*\(.*\)/', '', $vatrate); // Remove code into vatrate.
1978
            }
1979
            $vatrate = preg_replace('/\*/', '', $vatrate);
1980
1981
            $tmp = calcul_price_total($qty, $up, 0, $vatrate, -1, -1, 0, 'TTC', 0, $type, $seller, $localtaxes_type);
1982
1983
            $this->line->value_unit = $up;
1984
1985
            $this->line->vat_src_code = $vat_src_code;
1986
            $this->line->vatrate = price2num($vatrate);
1987
            $this->line->localtax1_tx = $localtaxes_type[1];
1988
            $this->line->localtax2_tx = $localtaxes_type[3];
1989
            $this->line->localtax1_type = $localtaxes_type[0];
1990
            $this->line->localtax2_type = $localtaxes_type[2];
1991
1992
            $this->line->total_ttc = $tmp[2];
1993
            $this->line->total_ht = $tmp[0];
1994
            $this->line->total_tva = $tmp[1];
1995
            $this->line->total_localtax1 = $tmp[9];
1996
            $this->line->total_localtax2 = $tmp[10];
1997
1998
            $this->line->fk_expensereport = $this->id;
1999
            $this->line->qty = $qty;
2000
            $this->line->date = $date;
2001
            $this->line->fk_c_type_fees = $fk_c_type_fees;
2002
            $this->line->fk_c_exp_tax_cat = $fk_c_exp_tax_cat;
2003
            $this->line->comments = $comments;
2004
            $this->line->fk_projet = $fk_project; // deprecated
2005
            $this->line->fk_project = $fk_project;
2006
2007
            $this->line->fk_ecm_files = $fk_ecm_files;
2008
2009
            $this->applyOffset();
2010
            $this->checkRules($type, $seller);
2011
2012
            $result = $this->line->insert(0, true);
2013
            if ($result > 0) {
2014
                $result = $this->update_price(1); // This method is designed to add line from user input so total calculation must be done using 'auto' mode.
2015
                if ($result > 0) {
2016
                    $this->db->commit();
2017
                    return $this->line->id;
2018
                } else {
2019
                    $this->db->rollback();
2020
                    return -1;
2021
                }
2022
            } else {
2023
                $this->error = $this->line->error;
2024
                dol_syslog(get_only_class($this) . "::addline error=" . $this->error, LOG_ERR);
2025
                $this->db->rollback();
2026
                return -2;
2027
            }
2028
        } else {
2029
            dol_syslog(get_only_class($this) . "::addline status of expense report must be Draft to allow use of ->addline()", LOG_ERR);
2030
            $this->error = 'ErrorExpenseNotDraft';
2031
            return -3;
2032
        }
2033
    }
2034
2035
    /**
2036
     * Check constraint of rules and update price if needed
2037
     *
2038
     * @param   int     $type       Type of line
2039
     * @param   string  $seller     Seller, but actually he is unknown
2040
     * @return  boolean             true or false
2041
     */
2042
    public function checkRules($type = 0, $seller = '')
2043
    {
2044
        global $conf, $db, $langs, $mysoc;
2045
2046
        $langs->load('trips');
2047
2048
        // We don't know seller and buyer for expense reports
2049
        if (!is_object($seller)) {
2050
            $seller = $mysoc;           // We use same than current company (expense report are often done in same country)
2051
            $seller->tva_assuj = 1;     // Most seller uses vat
0 ignored issues
show
Bug introduced by
The property tva_assuj does not exist on string.
Loading history...
2052
        }
2053
2054
        $expensereportrule = new ExpenseReportRule($db);
2055
        $rulestocheck = $expensereportrule->getAllRule($this->line->fk_c_type_fees, $this->line->date, $this->fk_user_author);
2056
2057
        $violation = 0;
2058
        $rule_warning_message_tab = array();
2059
2060
        $current_total_ttc = $this->line->total_ttc;
2061
        $new_current_total_ttc = $this->line->total_ttc;
2062
2063
        // check if one is violated
2064
        foreach ($rulestocheck as $rule) {
2065
            if (in_array($rule->code_expense_rules_type, array('EX_DAY', 'EX_MON', 'EX_YEA'))) {
2066
                $amount_to_test = $this->line->getExpAmount($rule, $this->fk_user_author, $rule->code_expense_rules_type);
2067
            } else {
2068
                $amount_to_test = $current_total_ttc; // EX_EXP
2069
            }
2070
2071
            $amount_to_test = $amount_to_test - $current_total_ttc + $new_current_total_ttc; // if amount as been modified by a previous rule
2072
2073
            if ($amount_to_test > $rule->amount) {
2074
                $violation++;
2075
2076
                if ($rule->restrictive) {
2077
                    $this->error = 'ExpenseReportConstraintViolationError';
2078
                    $this->errors[] = $this->error;
2079
2080
                    $new_current_total_ttc -= $amount_to_test - $rule->amount; // ex, entered 16€, limit 12€, subtracts 4€;
2081
                    $rule_warning_message_tab[] = $langs->trans('ExpenseReportConstraintViolationError', $rule->id, price($amount_to_test, 0, $langs, 1, -1, -1, $conf->currency), price($rule->amount, 0, $langs, 1, -1, -1, $conf->currency));
2082
                } else {
2083
                    $this->error = 'ExpenseReportConstraintViolationWarning';
2084
                    $this->errors[] = $this->error;
2085
2086
                    $rule_warning_message_tab[] = $langs->trans('ExpenseReportConstraintViolationWarning', $rule->id, price($amount_to_test, 0, $langs, 1, -1, -1, $conf->currency), price($rule->amount, 0, $langs, 1, -1, -1, $conf->currency));
2087
                }
2088
2089
                // No break, we should test if another rule is violated
2090
            }
2091
        }
2092
2093
        $this->line->rule_warning_message = implode('\n', $rule_warning_message_tab);
2094
2095
        if ($violation > 0) {
2096
            $tmp = calcul_price_total($this->line->qty, $new_current_total_ttc / $this->line->qty, 0, $this->line->vatrate, 0, 0, 0, 'TTC', 0, $type, $seller);
2097
2098
            $this->line->value_unit = $tmp[5];
2099
            $this->line->total_ttc = $tmp[2];
2100
            $this->line->total_ht = $tmp[0];
2101
            $this->line->total_tva = $tmp[1];
2102
            $this->line->total_localtax1 = $tmp[9];
2103
            $this->line->total_localtax2 = $tmp[10];
2104
2105
            return false;
2106
        } else {
2107
            return true;
2108
        }
2109
    }
2110
2111
    /**
2112
     * Method to apply the offset if needed
2113
     *
2114
     * @param   int     $type       Type of line
2115
     * @param   string  $seller     Seller, but actually he is unknown
2116
     * @return  boolean             True=applied, False=not applied
2117
     */
2118
    public function applyOffset($type = 0, $seller = '')
2119
    {
2120
        global $mysoc;
2121
2122
        if (!getDolGlobalString('MAIN_USE_EXPENSE_IK')) {
2123
            return false;
2124
        }
2125
2126
        $userauthor = new User($this->db);
2127
        if ($userauthor->fetch($this->fk_user_author) <= 0) {
2128
            $this->error = 'ErrorCantFetchUser';
2129
            $this->errors[] = 'ErrorCantFetchUser';
2130
            return false;
2131
        }
2132
2133
        // We don't know seller and buyer for expense reports
2134
        if (!is_object($seller)) {
2135
            $seller = $mysoc;           // We use same than current company (expense report are often done in same country)
2136
            $seller->tva_assuj = 1;     // Most seller uses vat
0 ignored issues
show
Bug introduced by
The property tva_assuj does not exist on string.
Loading history...
2137
        }
2138
2139
        $expenseik = new ExpenseReportIk($this->db);
2140
        $range = $expenseik->getRangeByUser($userauthor, $this->line->fk_c_exp_tax_cat);
2141
2142
        if (empty($range)) {
2143
            $this->error = 'ErrorNoRangeAvailable';
2144
            $this->errors[] = 'ErrorNoRangeAvailable';
2145
            return false;
2146
        }
2147
2148
        if (getDolGlobalString('MAIN_EXPENSE_APPLY_ENTIRE_OFFSET')) {
2149
            $ikoffset = $range->ikoffset;
0 ignored issues
show
Bug introduced by
The property ikoffset does not exist on boolean.
Loading history...
2150
        } else {
2151
            $ikoffset = $range->ikoffset / 12; // The amount of offset is a global value for the year
2152
        }
2153
2154
        // Test if ikoffset has been applied for the current month
2155
        if (!$this->offsetAlreadyGiven()) {
2156
            $new_up = $range->coef + ($ikoffset / $this->line->qty);
0 ignored issues
show
Bug introduced by
The property coef does not exist on boolean.
Loading history...
2157
            $tmp = calcul_price_total($this->line->qty, $new_up, 0, $this->line->vatrate, 0, 0, 0, 'TTC', 0, $type, $seller);
2158
2159
            $this->line->value_unit = $tmp[5];
2160
            $this->line->total_ttc = $tmp[2];
2161
            $this->line->total_ht = $tmp[0];
2162
            $this->line->total_tva = $tmp[1];
2163
            $this->line->total_localtax1 = $tmp[9];
2164
            $this->line->total_localtax2 = $tmp[10];
2165
2166
            return true;
2167
        }
2168
2169
        return false;
2170
    }
2171
2172
    /**
2173
     * If the sql find any rows then the ikoffset is already given (ikoffset is applied at the first expense report line)
2174
     *
2175
     * @return bool
2176
     */
2177
    public function offsetAlreadyGiven()
2178
    {
2179
        $sql = 'SELECT e.rowid FROM ' . MAIN_DB_PREFIX . 'expensereport e';
2180
        $sql .= " INNER JOIN " . MAIN_DB_PREFIX . "expensereport_det d ON (e.rowid = d.fk_expensereport)";
2181
        $sql .= " INNER JOIN " . MAIN_DB_PREFIX . "c_type_fees f ON (d.fk_c_type_fees = f.id AND f.code = 'EX_KME')";
2182
        $sql .= " WHERE e.fk_user_author = " . (int) $this->fk_user_author;
2183
        $sql .= " AND YEAR(d.date) = '" . dol_print_date($this->line->date, '%Y') . "' AND MONTH(d.date) = '" . dol_print_date($this->line->date, '%m') . "'";
2184
        if (!empty($this->line->id)) {
2185
            $sql .= ' AND d.rowid <> ' . ((int) $this->line->id);
2186
        }
2187
2188
        dol_syslog(get_only_class($this) . "::offsetAlreadyGiven");
2189
        $resql = $this->db->query($sql);
2190
        if ($resql) {
2191
            $num = $this->db->num_rows($resql);
2192
            if ($num > 0) {
2193
                return true;
2194
            }
2195
        } else {
2196
            dol_print_error($this->db);
2197
        }
2198
2199
        return false;
2200
    }
2201
2202
    /**
2203
     * Update an expense report line.
2204
     *
2205
     * @param   int         $rowid                  Line to edit
2206
     * @param   int         $type_fees_id           Type payment
2207
     * @param   int         $projet_id              Project id
2208
     * @param   double      $vatrate                Vat rate. Can be '8.5' or '8.5* (8.5NPROM...)'
2209
     * @param   string      $comments               Description
2210
     * @param   float       $qty                    Qty
2211
     * @param   double      $value_unit             Unit price (with taxes)
2212
     * @param   int         $date                   Date
2213
     * @param   int         $expensereport_id       Expense report id
2214
     * @param   int         $fk_c_exp_tax_cat       Id of category of car
2215
     * @param   int         $fk_ecm_files           Id of ECM file to link to this expensereport line
2216
     * @param   int         $notrigger              1=No trigger
2217
     * @return  int                                 Return integer <0 if KO, >0 if OK
2218
     */
2219
    public function updateline($rowid, $type_fees_id, $projet_id, $vatrate, $comments, $qty, $value_unit, $date, $expensereport_id, $fk_c_exp_tax_cat = 0, $fk_ecm_files = 0, $notrigger = 0)
2220
    {
2221
        global $user, $mysoc;
2222
2223
        if ($this->status == self::STATUS_DRAFT || $this->status == self::STATUS_REFUSED) {
2224
            $this->db->begin();
2225
2226
            $error = 0;
2227
            $type = 0; // TODO What if type is service ?
2228
2229
            // We don't know seller and buyer for expense reports
2230
            $seller = $mysoc;           // We use same than current company (expense report are often done in same country)
2231
            $seller->tva_assuj = 1;     // Most seller uses vat
2232
            $seller->localtax1_assuj = $mysoc->localtax1_assuj;     // We don't know, we reuse the state of company
2233
            $seller->localtax2_assuj = $mysoc->localtax1_assuj;     // We don't know, we reuse the state of company
2234
            $buyer = new Societe($this->db);
2235
2236
            $localtaxes_type = getLocalTaxesFromRate($vatrate, 0, $buyer, $seller);
2237
2238
            // Clean vat code
2239
            $reg = array();
2240
            $vat_src_code = '';
2241
            if (preg_match('/\((.*)\)/', (string) $vatrate, $reg)) {
2242
                $vat_src_code = $reg[1];
2243
                $vatrate = preg_replace('/\s*\(.*\)/', '', (string) $vatrate); // Remove code into vatrate.
2244
            }
2245
            $vatrate = preg_replace('/\*/', '', $vatrate);
2246
2247
            $tmp = calcul_price_total($qty, $value_unit, 0, $vatrate, -1, -1, 0, 'TTC', 0, $type, $seller, $localtaxes_type);
2248
            //var_dump($vatrate);var_dump($localtaxes_type);var_dump($tmp);exit;
2249
            // calcul total of line
2250
            //$total_ttc  = price2num($qty*$value_unit, 'MT');
2251
2252
            $tx_tva = 1 + (float) $vatrate / 100;
2253
2254
            $this->line = new ExpenseReportLine($this->db);
2255
            $this->line->comments        = $comments;
2256
            $this->line->qty             = $qty;
2257
            $this->line->value_unit      = $value_unit;
2258
            $this->line->date            = $date;
2259
2260
            $this->line->fk_expensereport = $expensereport_id;
2261
            $this->line->fk_c_type_fees  = $type_fees_id;
2262
            $this->line->fk_c_exp_tax_cat = $fk_c_exp_tax_cat;
2263
            $this->line->fk_projet       = $projet_id; // deprecated
2264
            $this->line->fk_project      = $projet_id;
2265
2266
            $this->line->vat_src_code = $vat_src_code;
2267
            $this->line->vatrate = price2num($vatrate);
2268
            $this->line->localtax1_tx = $localtaxes_type[1];
2269
            $this->line->localtax2_tx = $localtaxes_type[3];
2270
            $this->line->localtax1_type = $localtaxes_type[0];
2271
            $this->line->localtax2_type = $localtaxes_type[2];
2272
2273
            $this->line->total_ttc = $tmp[2];
2274
            $this->line->total_ht = $tmp[0];
2275
            $this->line->total_tva = $tmp[1];
2276
            $this->line->total_localtax1 = $tmp[9];
2277
            $this->line->total_localtax2 = $tmp[10];
2278
2279
            $this->line->fk_ecm_files = $fk_ecm_files;
2280
2281
            $this->line->id = ((int) $rowid);
2282
2283
            // Select des infos sur le type fees
2284
            $sql = "SELECT c.code as code_type_fees, c.label as label_type_fees";
2285
            $sql .= " FROM " . MAIN_DB_PREFIX . "c_type_fees as c";
2286
            $sql .= " WHERE c.id = " . ((int) $type_fees_id);
2287
            $resql = $this->db->query($sql);
2288
            if ($resql) {
2289
                $objp_fees = $this->db->fetch_object($resql);
2290
                $this->line->type_fees_code      = $objp_fees->code_type_fees;
2291
                $this->line->type_fees_libelle   = $objp_fees->label_type_fees;
2292
                $this->db->free($resql);
2293
            }
2294
2295
            // Select des information du projet
2296
            $sql = "SELECT p.ref as ref_projet, p.title as title_projet";
2297
            $sql .= " FROM " . MAIN_DB_PREFIX . "projet as p";
2298
            $sql .= " WHERE p.rowid = " . ((int) $projet_id);
2299
            $resql = $this->db->query($sql);
2300
            if ($resql) {
2301
                $objp_projet = $this->db->fetch_object($resql);
2302
                $this->line->projet_ref          = $objp_projet->ref_projet;
2303
                $this->line->projet_title        = $objp_projet->title_projet;
2304
                $this->db->free($resql);
2305
            }
2306
2307
            $this->applyOffset();
2308
            $this->checkRules();
2309
2310
            $result = $this->line->update($user);
2311
            if ($result < 0) {
2312
                $error++;
2313
            }
2314
2315
            if (!$error && !$notrigger) {
2316
                // Call triggers
2317
                $result = $this->call_trigger('EXPENSE_REPORT_DET_MODIFY', $user);
2318
                if ($result < 0) {
2319
                    $error++;
2320
                }
2321
                // End call triggers
2322
            }
2323
2324
            if (!$error) {
2325
                $this->db->commit();
2326
                return 1;
2327
            } else {
2328
                $this->error = $this->line->error;
2329
                $this->errors = $this->line->errors;
2330
                $this->db->rollback();
2331
                return -2;
2332
            }
2333
        }
2334
2335
        return 0;
2336
    }
2337
2338
    /**
2339
     * deleteline
2340
     *
2341
     * @param   int         $rowid          Row id
2342
     * @param   User|string $fuser          User
2343
     * @param   int<0,1>    $notrigger      1=No trigger
2344
     * @return  int<-1,1>                   Return integer <0 if KO, >0 if OK
2345
     */
2346
    public function deleteLine($rowid, $fuser = '', $notrigger = 0)
2347
    {
2348
        $error = 0;
2349
2350
        $this->db->begin();
2351
2352
        if (!$notrigger) {
2353
            // Call triggers
2354
            $result = $this->call_trigger('EXPENSE_REPORT_DET_DELETE', $fuser);
2355
            if ($result < 0) {
2356
                $error++;
2357
            }
2358
            // End call triggers
2359
        }
2360
2361
        $sql = ' DELETE FROM ' . MAIN_DB_PREFIX . $this->table_element_line;
2362
        $sql .= ' WHERE rowid = ' . ((int) $rowid);
2363
2364
        dol_syslog(get_only_class($this) . "::deleteline sql=" . $sql);
2365
        $result = $this->db->query($sql);
2366
2367
        if (!$result || $error > 0) {
2368
            $this->error = $this->db->error();
2369
            dol_syslog(get_only_class($this) . "::deleteline  Error " . $this->error, LOG_ERR);
2370
            $this->db->rollback();
2371
            return -1;
2372
        }
2373
2374
        $this->update_price(1);
2375
2376
        $this->db->commit();
2377
2378
        return 1;
2379
    }
2380
2381
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2382
    /**
2383
     * periode_existe
2384
     *
2385
     * @param   User       $fuser          User
2386
     * @param   integer    $date_debut     Start date
2387
     * @param   integer    $date_fin       End date
2388
     * @return  int                        Return integer <0 if KO, >0 if OK
2389
     */
2390
    public function periode_existe($fuser, $date_debut, $date_fin)
2391
    {
2392
        global $conf;
2393
2394
		// phpcs:enable
2395
        $sql = "SELECT rowid, date_debut, date_fin";
2396
        $sql .= " FROM " . MAIN_DB_PREFIX . $this->table_element;
2397
        $sql .= " WHERE entity = " . ((int) $conf->entity); // not shared, only for the current entity
2398
        $sql .= " AND fk_user_author = " . ((int) $fuser->id);
2399
2400
        dol_syslog(get_only_class($this) . "::periode_existe sql=" . $sql);
2401
        $result = $this->db->query($sql);
2402
        if ($result) {
2403
            $num_rows = $this->db->num_rows($result);
2404
            $i = 0;
2405
2406
            if ($num_rows > 0) {
2407
                $date_d_form = $date_debut;
2408
                $date_f_form = $date_fin;
2409
2410
                while ($i < $num_rows) {
2411
                    $objp = $this->db->fetch_object($result);
2412
2413
                    $date_d_req = $this->db->jdate($objp->date_debut); // 3
2414
                    $date_f_req = $this->db->jdate($objp->date_fin); // 4
2415
2416
                    if (!($date_f_form < $date_d_req || $date_d_form > $date_f_req)) {
2417
                        return $objp->rowid;
2418
                    }
2419
2420
                    $i++;
2421
                }
2422
2423
                return 0;
2424
            } else {
2425
                return 0;
2426
            }
2427
        } else {
2428
            $this->error = $this->db->lasterror();
2429
            dol_syslog(get_only_class($this) . "::periode_existe  Error " . $this->error, LOG_ERR);
2430
            return -1;
2431
        }
2432
    }
2433
2434
2435
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2436
    /**
2437
     * Return list of people with permission to validate expense reports.
2438
     * Search for permission "approve expense report"
2439
     *
2440
     * @return  array|int       Array of user ids, <0 if KO
2441
     */
2442
    public function fetch_users_approver_expensereport()
2443
    {
2444
		// phpcs:enable
2445
        $users_validator = array();
2446
2447
        $sql = "SELECT DISTINCT ur.fk_user";
2448
        $sql .= " FROM " . MAIN_DB_PREFIX . "user_rights as ur, " . MAIN_DB_PREFIX . "rights_def as rd";
2449
        $sql .= " WHERE ur.fk_id = rd.id and rd.module = 'expensereport' AND rd.perms = 'approve'"; // Permission 'Approve';
2450
        $sql .= " UNION";
2451
        $sql .= " SELECT DISTINCT ugu.fk_user";
2452
        $sql .= " FROM " . MAIN_DB_PREFIX . "usergroup_user as ugu, " . MAIN_DB_PREFIX . "usergroup_rights as ur, " . MAIN_DB_PREFIX . "rights_def as rd";
2453
        $sql .= " WHERE ugu.fk_usergroup = ur.fk_usergroup AND ur.fk_id = rd.id and rd.module = 'expensereport' AND rd.perms = 'approve'"; // Permission 'Approve';
2454
        //print $sql;
2455
2456
        dol_syslog(get_only_class($this) . "::fetch_users_approver_expensereport sql=" . $sql);
2457
        $result = $this->db->query($sql);
2458
        if ($result) {
2459
            $num_rows = $this->db->num_rows($result);
2460
            $i = 0;
2461
            while ($i < $num_rows) {
2462
                $objp = $this->db->fetch_object($result);
2463
                array_push($users_validator, $objp->fk_user);
2464
                $i++;
2465
            }
2466
            return $users_validator;
2467
        } else {
2468
            $this->error = $this->db->lasterror();
2469
            dol_syslog(get_only_class($this) . "::fetch_users_approver_expensereport  Error " . $this->error, LOG_ERR);
2470
            return -1;
2471
        }
2472
    }
2473
2474
    /**
2475
     *  Create a document onto disk according to template module.
2476
     *
2477
     *  @param      string      $modele         Force le mnodele a utiliser ('' to not force)
2478
     *  @param      Translate   $outputlangs    object lang a utiliser pour traduction
2479
     *  @param      int         $hidedetails    Hide details of lines
2480
     *  @param      int         $hidedesc       Hide description
2481
     *  @param      int         $hideref        Hide ref
2482
     *  @param      ?array  $moreparams     Array to provide more information
2483
     *  @return     int                         0 if KO, 1 if OK
2484
     */
2485
    public function generateDocument($modele, $outputlangs, $hidedetails = 0, $hidedesc = 0, $hideref = 0, $moreparams = null)
2486
    {
2487
        $outputlangs->load("trips");
2488
2489
        if (!dol_strlen($modele)) {
2490
            if (!empty($this->model_pdf)) {
2491
                $modele = $this->model_pdf;
2492
            } elseif (getDolGlobalString('EXPENSEREPORT_ADDON_PDF')) {
2493
                $modele = getDolGlobalString('EXPENSEREPORT_ADDON_PDF');
2494
            }
2495
        }
2496
2497
        if (!empty($modele)) {
2498
            $modelpath = "core/modules/expensereport/doc/";
2499
2500
            return $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams);
2501
        } else {
2502
            return 0;
2503
        }
2504
    }
2505
2506
    /**
2507
     * List of types
2508
     *
2509
     * @param   int     $active     Active or not
2510
     * @return  array
2511
     */
2512
    public function listOfTypes($active = 1)
2513
    {
2514
        global $langs;
2515
        $ret = array();
2516
        $sql = "SELECT id, code, label";
2517
        $sql .= " FROM " . MAIN_DB_PREFIX . "c_type_fees";
2518
        $sql .= " WHERE active = " . ((int) $active);
2519
        dol_syslog(get_only_class($this) . "::listOfTypes", LOG_DEBUG);
2520
        $result = $this->db->query($sql);
2521
        if ($result) {
2522
            $num = $this->db->num_rows($result);
2523
            $i = 0;
2524
            while ($i < $num) {
2525
                $obj = $this->db->fetch_object($result);
2526
                $ret[$obj->code] = (($langs->transnoentitiesnoconv($obj->code) != $obj->code) ? $langs->transnoentitiesnoconv($obj->code) : $obj->label);
2527
                $i++;
2528
            }
2529
        } else {
2530
            dol_print_error($this->db);
2531
        }
2532
        return $ret;
2533
    }
2534
2535
    /**
2536
     *      Load the indicators this->nb for the state board
2537
     *
2538
     *      @return     int         Return integer <0 if KO, >0 if OK
2539
     */
2540
    public function loadStateBoard()
2541
    {
2542
        global $user;
2543
2544
        $this->nb = array();
2545
2546
        $sql = "SELECT count(ex.rowid) as nb";
2547
        $sql .= " FROM " . MAIN_DB_PREFIX . "expensereport as ex";
2548
        $sql .= " WHERE ex.fk_statut > 0";
2549
        $sql .= " AND ex.entity IN (" . getEntity('expensereport') . ")";
2550
        if (!$user->hasRight('expensereport', 'readall')) {
2551
            $userchildids = $user->getAllChildIds(1);
2552
            $sql .= " AND (ex.fk_user_author IN (" . $this->db->sanitize(implode(',', $userchildids)) . ")";
2553
            $sql .= " OR ex.fk_user_validator IN (" . $this->db->sanitize(implode(',', $userchildids)) . "))";
2554
        }
2555
2556
        $resql = $this->db->query($sql);
2557
        if ($resql) {
2558
            while ($obj = $this->db->fetch_object($resql)) {
2559
                $this->nb["expensereports"] = $obj->nb;
2560
            }
2561
            $this->db->free($resql);
2562
            return 1;
2563
        } else {
2564
            dol_print_error($this->db);
2565
            $this->error = $this->db->error();
2566
            return -1;
2567
        }
2568
    }
2569
2570
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2571
    /**
2572
     *      Load indicators for dashboard (this->nbtodo and this->nbtodolate)
2573
     *
2574
     *      @param  User    $user           Object user
2575
     *      @param  string  $option         'topay' or 'toapprove'
2576
     *      @return WorkboardResponse|int   Return integer <0 if KO, WorkboardResponse if OK
2577
     */
2578
    public function load_board($user, $option = 'topay')
2579
    {
2580
		// phpcs:enable
2581
        global $conf, $langs;
2582
2583
        if ($user->socid) {
2584
            return -1; // protection pour eviter appel par utilisateur externe
2585
        }
2586
2587
        $now = dol_now();
2588
2589
        $sql = "SELECT ex.rowid, ex.date_valid";
2590
        $sql .= " FROM " . MAIN_DB_PREFIX . "expensereport as ex";
2591
        if ($option == 'toapprove') {
2592
            $sql .= " WHERE ex.fk_statut = " . self::STATUS_VALIDATED;
2593
        } else {
2594
            $sql .= " WHERE ex.fk_statut = " . self::STATUS_APPROVED;
2595
        }
2596
        $sql .= " AND ex.entity IN (" . getEntity('expensereport') . ")";
2597
        if (!$user->hasRight('expensereport', 'readall')) {
2598
            $userchildids = $user->getAllChildIds(1);
2599
            $sql .= " AND (ex.fk_user_author IN (" . $this->db->sanitize(implode(',', $userchildids)) . ")";
2600
            $sql .= " OR ex.fk_user_validator IN (" . $this->db->sanitize(implode(',', $userchildids)) . "))";
2601
        }
2602
2603
        $resql = $this->db->query($sql);
2604
        if ($resql) {
2605
            $langs->load("trips");
2606
2607
            $response = new WorkboardResponse();
2608
            if ($option == 'toapprove') {
2609
                $response->warning_delay = $conf->expensereport->approve->warning_delay / 60 / 60 / 24;
2610
                $response->label = $langs->trans("ExpenseReportsToApprove");
2611
                $response->labelShort = $langs->trans("ToApprove");
2612
                $response->url = constant('BASE_URL') . '/expensereport/list.php?mainmenu=hrm&amp;statut=' . self::STATUS_VALIDATED;
2613
            } else {
2614
                $response->warning_delay = $conf->expensereport->payment->warning_delay / 60 / 60 / 24;
2615
                $response->label = $langs->trans("ExpenseReportsToPay");
2616
                $response->labelShort = $langs->trans("StatusToPay");
2617
                $response->url = constant('BASE_URL') . '/expensereport/list.php?mainmenu=hrm&amp;statut=' . self::STATUS_APPROVED;
2618
            }
2619
            $response->img = img_object('', "trip");
2620
2621
            while ($obj = $this->db->fetch_object($resql)) {
2622
                $response->nbtodo++;
2623
2624
                if ($option == 'toapprove') {
2625
                    if ($this->db->jdate($obj->date_valid) < ($now - $conf->expensereport->approve->warning_delay)) {
2626
                        $response->nbtodolate++;
2627
                    }
2628
                } else {
2629
                    if ($this->db->jdate($obj->date_valid) < ($now - $conf->expensereport->payment->warning_delay)) {
2630
                        $response->nbtodolate++;
2631
                    }
2632
                }
2633
            }
2634
2635
            return $response;
2636
        } else {
2637
            dol_print_error($this->db);
2638
            $this->error = $this->db->error();
2639
            return -1;
2640
        }
2641
    }
2642
2643
    /**
2644
     * Return if an expense report is late or not
2645
     *
2646
     * @param  string  $option          'topay' or 'toapprove'
2647
     * @return boolean                  True if late, False if not late
2648
     */
2649
    public function hasDelay($option)
2650
    {
2651
        global $conf;
2652
2653
        // Only valid expenses reports
2654
        if ($option == 'toapprove' && $this->status != 2) {
2655
            return false;
2656
        }
2657
        if ($option == 'topay' && $this->status != 5) {
2658
            return false;
2659
        }
2660
2661
        $now = dol_now();
2662
        if ($option == 'toapprove') {
2663
            return (!empty($this->datevalid) ? $this->datevalid : $this->date_valid) < ($now - $conf->expensereport->approve->warning_delay);
2664
        } else {
2665
            return (!empty($this->datevalid) ? $this->datevalid : $this->date_valid) < ($now - $conf->expensereport->payment->warning_delay);
2666
        }
2667
    }
2668
2669
    /**
2670
     *  Return if object was dispatched into bookkeeping
2671
     *
2672
     *  @return     int         Return integer <0 if KO, 0=no, 1=yes
2673
     */
2674
    public function getVentilExportCompta()
2675
    {
2676
        $alreadydispatched = 0;
2677
2678
        $type = 'expense_report';
2679
2680
        $sql = " SELECT COUNT(ab.rowid) as nb FROM " . MAIN_DB_PREFIX . "accounting_bookkeeping as ab WHERE ab.doc_type='" . $this->db->escape($type) . "' AND ab.fk_doc = " . ((int) $this->id);
2681
        $resql = $this->db->query($sql);
2682
        if ($resql) {
2683
            $obj = $this->db->fetch_object($resql);
2684
            if ($obj) {
2685
                $alreadydispatched = $obj->nb;
2686
            }
2687
        } else {
2688
            $this->error = $this->db->lasterror();
2689
            return -1;
2690
        }
2691
2692
        if ($alreadydispatched) {
2693
            return 1;
2694
        }
2695
        return 0;
2696
    }
2697
2698
    /**
2699
     *  Return amount of payments already done
2700
     *
2701
     *  @return     int                     Amount of payment already done, <0 if KO
2702
     */
2703
    public function getSumPayments()
2704
    {
2705
        $table = 'payment_expensereport';
2706
        $field = 'fk_expensereport';
2707
2708
        $sql = 'SELECT sum(amount) as amount';
2709
        $sql .= ' FROM ' . MAIN_DB_PREFIX . $table;
2710
        $sql .= " WHERE " . $field . " = " . ((int) $this->id);
2711
2712
        dol_syslog(get_only_class($this) . "::getSumPayments", LOG_DEBUG);
2713
        $resql = $this->db->query($sql);
2714
        if ($resql) {
2715
            $obj = $this->db->fetch_object($resql);
2716
            $this->db->free($resql);
2717
            return (empty($obj->amount) ? 0 : $obj->amount);
2718
        } else {
2719
            $this->error = $this->db->lasterror();
2720
            return -1;
2721
        }
2722
    }
2723
2724
    /**
2725
     *  \brief Compute the cost of the kilometers expense based on the number of kilometers and the vehicle category
2726
     *
2727
     *  @param     int      $fk_cat           Category of the vehicle used
2728
     *  @param     float    $qty              Number of kilometers
2729
     *  @param     float    $tva              VAT rate
2730
     *  @return    int                        Return integer <0 if KO, total ttc if OK
2731
     */
2732
    public function computeTotalKm($fk_cat, $qty, $tva)
2733
    {
2734
        global $langs, $db, $conf;
2735
2736
        $cumulYearQty = 0;
2737
        $ranges = array();
2738
        $coef = 0;
2739
2740
2741
        if ($fk_cat < 0) {
2742
            $this->error = $langs->trans('ErrorBadParameterCat');
2743
            return -1;
2744
        }
2745
2746
        if ($qty <= 0) {
2747
            $this->error = $langs->trans('ErrorBadParameterQty');
2748
            return -1;
2749
        }
2750
2751
        $currentUser = new User($db);
2752
        $currentUser->fetch($this->fk_user);
2753
        $currentUser->getrights('expensereport');
2754
        //Clean
2755
        $qty = (float) price2num($qty);
2756
2757
        $sql  = " SELECT r.range_ik, t.ikoffset, t.coef";
2758
        $sql .= " FROM " . MAIN_DB_PREFIX . "expensereport_ik t";
2759
        $sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "c_exp_tax_range r ON r.rowid = t.fk_range";
2760
        $sql .= " WHERE t.fk_c_exp_tax_cat = " . (int) $fk_cat;
2761
        $sql .= " ORDER BY r.range_ik ASC";
2762
2763
        dol_syslog("expenseReport::computeTotalkm sql=" . $sql, LOG_DEBUG);
2764
2765
        $result = $this->db->query($sql);
2766
2767
        if ($result) {
2768
            if ($conf->global->EXPENSEREPORT_CALCULATE_MILEAGE_EXPENSE_COEFFICIENT_ON_CURRENT_YEAR) {
2769
                $arrayDate = dol_getdate(dol_now());
2770
                $sql = " SELECT count(n.qty) as cumul FROM " . MAIN_DB_PREFIX . "expensereport_det n";
2771
                $sql .= " LEFT JOIN  " . MAIN_DB_PREFIX . "expensereport e ON e.rowid = n.fk_expensereport";
2772
                $sql .= " LEFT JOIN  " . MAIN_DB_PREFIX . "c_type_fees tf ON tf.id = n.fk_c_type_fees";
2773
                $sql .= " WHERE e.fk_user_author = " . (int) $this->fk_user_author;
2774
                $sql .= " AND YEAR(n.date) = " . (int) $arrayDate['year'];
2775
                $sql .= " AND tf.code = 'EX_KME' ";
2776
                $sql .= " AND e.fk_statut = " . (int) ExpenseReport::STATUS_VALIDATED;
2777
2778
                $resql = $this->db->query($sql);
2779
2780
                if ($resql) {
2781
                    $obj = $this->db->fetch_object($resql);
2782
                    $cumulYearQty = $obj->cumul;
2783
                }
2784
2785
                $qty += (float) $cumulYearQty;
2786
            }
2787
2788
            $num = $this->db->num_rows($result);
2789
2790
            if ($num) {
2791
                for ($i = 0; $i < $num; $i++) {
2792
                    $obj = $this->db->fetch_object($result);
2793
2794
                    $ranges[$i] = $obj;
2795
                }
2796
2797
2798
                for ($i = 0; $i < $num; $i++) {
2799
                    if ($i < ($num - 1)) {
2800
                        if ($qty > $ranges[$i]->range_ik && $qty < $ranges[$i + 1]->range_ik) {
2801
                            $coef = $ranges[$i]->coef;
2802
                            $offset = $ranges[$i]->ikoffset;
2803
                        }
2804
                    } else {
2805
                        if ($qty > $ranges[$i]->range_ik) {
2806
                            $coef = $ranges[$i]->coef;
2807
                            $offset = $ranges[$i]->ikoffset;
2808
                        }
2809
                    }
2810
                }
2811
                $total_ht = $coef;
2812
                return $total_ht;
2813
            } else {
2814
                $this->error = $langs->trans('TaxUndefinedForThisCategory');
2815
                return 0;
2816
            }
2817
        } else {
2818
            $this->error = $this->db->error() . " sql=" . $sql;
2819
2820
            return -1;
2821
        }
2822
    }
2823
2824
    /**
2825
     *  Return clickable link of object (with optional picto)
2826
     *
2827
     *  @param      string      $option                 Where point the link (0=> main card, 1,2 => shipment, 'nolink'=>No link)
2828
     *  @param      array       $arraydata              Array of data
2829
     *  @return     string                              HTML Code for Kanban thumb.
2830
     */
2831
    public function getKanbanView($option = '', $arraydata = null)
2832
    {
2833
        global $langs;
2834
2835
        $selected = (empty($arraydata['selected']) ? 0 : $arraydata['selected']);
2836
2837
        $return = '<div class="box-flex-item box-flex-grow-zero">';
2838
        $return .= '<div class="info-box info-box-sm">';
2839
        $return .= '<span class="info-box-icon bg-infobox-action">';
2840
        $return .= img_picto('', $this->picto);
2841
        $return .= '</span>';
2842
        $return .= '<div class="info-box-content">';
2843
        $return .= '<span class="info-box-ref inline-block tdoverflowmax150 valignmiddle">' . (method_exists($this, 'getNomUrl') ? $this->getNomUrl(1) : $this->ref) . '</span>';
2844
        if ($selected >= 0) {
2845
            $return .= '<input id="cb' . $this->id . '" class="flat checkforselect fright" type="checkbox" name="toselect[]" value="' . $this->id . '"' . ($selected ? ' checked="checked"' : '') . '>';
2846
        }
2847
        if (array_key_exists('userauthor', $arraydata)) {
2848
            $return .= '<br><span class="info-box-label">' . $arraydata['userauthor']->getNomUrl(-1) . '</span>';
2849
        }
2850
        if (property_exists($this, 'date_debut') && property_exists($this, 'date_fin')) {
2851
            $return .= '<br><span class="info-box-label">' . dol_print_date($this->date_debut, 'day') . '</span>';
2852
            $return .= ' <span class="opacitymedium">' . $langs->trans("To") . '</span> ';
2853
            $return .= '<span class="info-box-label">' . dol_print_date($this->date_fin, 'day') . '</span>';
2854
        }
2855
        if (method_exists($this, 'getLibStatut')) {
2856
            $return .= '<br><div class="info-box-status">' . $this->getLibStatut(3) . '</div>';
2857
        }
2858
        $return .= '</div>';
2859
        $return .= '</div>';
2860
        $return .= '</div>';
2861
        return $return;
2862
    }
2863
}
2864