dolibarr 25.0.0-alpha
expensereport.class.php
Go to the documentation of this file.
1<?php
2/* Copyright (C) 2011 Dimitri Mouillard <dmouillard@teclib.com>
3 * Copyright (C) 2015 Laurent Destailleur <eldy@users.sourceforge.net>
4 * Copyright (C) 2015 Alexandre Spangaro <aspangaro@open-dsi.fr>
5 * Copyright (C) 2018 Nicolas ZABOURI <info@inovea-conseil.com>
6 * Copyright (c) 2018-2024 Frédéric France <frederic.france@free.fr>
7 * Copyright (C) 2016-2020 Ferran Marcet <fmarcet@2byte.es>
8 * Copyright (C) 2024-2026 MDW <mdeweerd@users.noreply.github.com>
9 * Copyright (C) 2024-2026 Frédéric France <frederic.france@free.fr>
10 * Copyright (C) 2025 William Mead <william@m34d.com>
11 *
12 * This program is free software; you can redistribute it and/or modify
13 * it under the terms of the GNU General Public License as published by
14 * the Free Software Foundation; either version 3 of the License, or
15 * (at your option) any later version.
16 *
17 * This program is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 * GNU General Public License for more details.
21 *
22 * You should have received a copy of the GNU General Public License
23 * along with this program. If not, see <https://www.gnu.org/licenses/>.
24 */
25
31require_once DOL_DOCUMENT_ROOT.'/core/class/commonobject.class.php';
32require_once DOL_DOCUMENT_ROOT.'/expensereport/class/expensereportline.class.php';
33require_once DOL_DOCUMENT_ROOT.'/expensereport/class/expensereport_ik.class.php';
34require_once DOL_DOCUMENT_ROOT.'/expensereport/class/expensereport_rule.class.php';
35
36
41{
45 public $element = 'expensereport';
46
50 public $table_element = 'expensereport';
51
55 public $table_element_line = 'expensereport_det';
56
60 public $class_element_line = 'ExpenseReportLine';
61
65 public $fk_element = 'fk_expensereport';
66
70 public $picto = 'trip';
71
75 public $lines = array();
76
80 public $line;
81
85 public $date_debut;
86
90 public $date_fin;
91
95 public $date_approbation;
96
100 public $fk_user;
101
105 public $user_approve_id;
106
112 public $status;
113
120 public $fk_statut;
121
125 public $fk_c_paiement;
126
130 public $modepaymentid;
131
135 public $paid;
136
137 // Paiement
141 public $user_paid_infos;
142
146 public $user_author_infos;
147
151 public $user_validator_infos;
152
156 public $rule_warning_message;
157
158 // ACTIONS
159
160 // Create
164 public $date_create;
165
169 public $fk_user_creat;
170
174 public $fk_user_author; // Note fk_user_author is not the 'author' but the guy the expense report is for.
175
176 // Update
180 public $date_modif;
181
185 public $fk_user_modif;
186
187 // Refus
191 public $date_refuse;
192
196 public $detail_refuse;
197
201 public $fk_user_refuse;
202
203 // Annulation
207 public $date_cancel;
208
212 public $detail_cancel;
213
217 public $fk_user_cancel;
218
222 public $fk_user_validator;
223
230 public $datevalid;
231
236 public $date_valid;
237
241 public $fk_user_valid;
242
246 public $user_valid_infos;
247
248 // Approve
252 public $date_approve;
253
257 public $fk_user_approve;
258
263 public $localtax1; // for backward compatibility (real field should be total_localtax1 defined into CommonObject)
268 public $localtax2; // for backward compatibility (real field should be total_localtax2 defined into CommonObject)
269
273 const STATUS_DRAFT = 0;
274
279
284
289
293 const STATUS_CLOSED = 6;
294
298 const STATUS_REFUSED = 99;
299
300 public $fields = array(
301 'rowid' => array('type' => 'integer', 'label' => 'ID', 'enabled' => 1, 'visible' => -1, 'notnull' => 1, 'position' => 10),
302 'ref' => array('type' => 'varchar(50)', 'label' => 'Ref', 'enabled' => 1, 'visible' => -1, 'notnull' => 1, 'showoncombobox' => 1, 'position' => 15),
303 'entity' => array('type' => 'integer', 'label' => 'Entity', 'default' => '1', 'enabled' => 1, 'visible' => -2, 'notnull' => 1, 'position' => 20),
304 'ref_number_int' => array('type' => 'integer', 'label' => 'Ref number int', 'enabled' => 1, 'visible' => -1, 'position' => 25),
305 'ref_ext' => array('type' => 'integer', 'label' => 'RefExt', 'enabled' => 1, 'visible' => 0, 'position' => 30),
306 'total_ht' => array('type' => 'double(24,8)', 'label' => 'Total ht', 'enabled' => 1, 'visible' => -1, 'position' => 35),
307 'total_tva' => array('type' => 'double(24,8)', 'label' => 'Total tva', 'enabled' => 1, 'visible' => -1, 'position' => 40),
308 'localtax1' => array('type' => 'double(24,8)', 'label' => 'Localtax1', 'enabled' => 1, 'visible' => -1, 'position' => 45),
309 'localtax2' => array('type' => 'double(24,8)', 'label' => 'Localtax2', 'enabled' => 1, 'visible' => -1, 'position' => 50),
310 'total_ttc' => array('type' => 'double(24,8)', 'label' => 'Total ttc', 'enabled' => 1, 'visible' => -1, 'position' => 55),
311 'date_debut' => array('type' => 'date', 'label' => 'Date debut', 'enabled' => 1, 'visible' => -1, 'notnull' => 1, 'position' => 60),
312 'date_fin' => array('type' => 'date', 'label' => 'Date fin', 'enabled' => 1, 'visible' => -1, 'notnull' => 1, 'position' => 65),
313 'date_valid' => array('type' => 'datetime', 'label' => 'Date valid', 'enabled' => 1, 'visible' => -1, 'position' => 75),
314 'date_approve' => array('type' => 'datetime', 'label' => 'Date approve', 'enabled' => 1, 'visible' => -1, 'position' => 80),
315 'date_refuse' => array('type' => 'datetime', 'label' => 'Date refuse', 'enabled' => 1, 'visible' => -1, 'position' => 85),
316 'date_cancel' => array('type' => 'datetime', 'label' => 'Date cancel', 'enabled' => 1, 'visible' => -1, 'position' => 90),
317 'fk_user_author' => array('type' => 'integer:User:user/class/user.class.php', 'label' => 'UserAuthor', 'enabled' => 1, 'visible' => -1, 'notnull' => 1, 'position' => 100),
318 'fk_user_modif' => array('type' => 'integer:User:user/class/user.class.php', 'label' => 'UserModif', 'enabled' => 1, 'visible' => -1, 'position' => 105),
319 'fk_user_valid' => array('type' => 'integer:User:user/class/user.class.php', 'label' => 'UserValidation', 'enabled' => 1, 'visible' => -1, 'position' => 110),
320 'fk_user_validator' => array('type' => 'integer:User:user/class/user.class.php', 'label' => 'UserValidation', 'enabled' => 1, 'visible' => -1, 'position' => 115),
321 'fk_user_approve' => array('type' => 'integer:User:user/class/user.class.php', 'label' => 'ApprovedBy', 'enabled' => 1, 'visible' => -1, 'position' => 120),
322 'fk_user_refuse' => array('type' => 'integer:User:user/class/user.class.php', 'label' => 'REFUSEUR', 'enabled' => 1, 'visible' => -1, 'position' => 125),
323 'fk_user_cancel' => array('type' => 'integer:User:user/class/user.class.php', 'label' => 'CANCEL_USER', 'enabled' => 1, 'visible' => -1, 'position' => 130),
324 'fk_c_paiement' => array('type' => 'integer', 'label' => 'PaymentMode', 'enabled' => 1, 'visible' => -1, 'position' => 140),
325 'paid' => array('type' => 'integer', 'label' => 'Paid', 'enabled' => 1, 'visible' => -1, 'notnull' => 1, 'position' => 145),
326 'note_public' => array('type' => 'html', 'label' => 'Note public', 'enabled' => 1, 'visible' => 0, 'position' => 150),
327 'note_private' => array('type' => 'html', 'label' => 'Note private', 'enabled' => 1, 'visible' => 0, 'position' => 155),
328 'detail_refuse' => array('type' => 'varchar(255)', 'label' => 'Detail refuse', 'enabled' => 1, 'visible' => -1, 'position' => 160),
329 'detail_cancel' => array('type' => 'varchar(255)', 'label' => 'Detail cancel', 'enabled' => 1, 'visible' => -1, 'position' => 165),
330 'integration_compta' => array('type' => 'integer', 'label' => 'Integration compta', 'enabled' => 1, 'visible' => -1, 'position' => 170),
331 'fk_bank_account' => array('type' => 'integer', 'label' => 'BankAccount', 'enabled' => 1, 'visible' => -1, 'position' => 175),
332 'fk_multicurrency' => array('type' => 'integer', 'label' => 'Currency', 'enabled' => 1, 'visible' => -1, 'position' => 185),
333 'multicurrency_code' => array('type' => 'varchar(255)', 'label' => 'Multicurrency code', 'enabled' => 1, 'visible' => -1, 'position' => 190),
334 'multicurrency_tx' => array('type' => 'double(24,8)', 'label' => 'Multicurrency tx', 'enabled' => 1, 'visible' => -1, 'position' => 195),
335 'multicurrency_total_ht' => array('type' => 'double(24,8)', 'label' => 'Multicurrency total ht', 'enabled' => 1, 'visible' => -1, 'position' => 200),
336 'multicurrency_total_tva' => array('type' => 'double(24,8)', 'label' => 'Multicurrency total tva', 'enabled' => 1, 'visible' => -1, 'position' => 205),
337 'multicurrency_total_ttc' => array('type' => 'double(24,8)', 'label' => 'Multicurrency total ttc', 'enabled' => 1, 'visible' => -1, 'position' => 210),
338 'extraparams' => array('type' => 'varchar(255)', 'label' => 'Extraparams', 'enabled' => 1, 'visible' => -1, 'position' => 220),
339 'date_create' => array('type' => 'datetime', 'label' => 'Date create', 'enabled' => 1, 'visible' => -1, 'notnull' => 1, 'position' => 300),
340 'tms' => array('type' => 'timestamp', 'label' => 'Tms', 'enabled' => 1, 'visible' => -1, 'notnull' => 1, 'position' => 305),
341 'import_key' => array('type' => 'varchar(14)', 'label' => 'ImportId', 'enabled' => 1, 'visible' => -1, 'position' => 1000),
342 'model_pdf' => array('type' => 'varchar(255)', 'label' => 'Model pdf', 'enabled' => 1, 'visible' => 0, 'position' => 1010),
343 'fk_statut' => array('type' => 'integer', 'label' => 'Status', 'enabled' => 1, 'visible' => -1, 'notnull' => 1, 'position' => 500),
344 );
345
351 public function __construct($db)
352 {
353 $this->db = $db;
354 $this->total_ht = 0;
355 $this->total_ttc = 0;
356 $this->total_tva = 0;
357 $this->total_localtax1 = 0;
358 $this->total_localtax2 = 0;
359 $this->localtax1 = 0; // For backward compatibility
360 $this->localtax2 = 0; // For backward compatibility
361 $this->modepaymentid = 0;
362
363 // List of language codes for status
364 $this->labelStatusShort = array(0 => 'Draft', 2 => 'Validated', 4 => 'Canceled', 5 => 'Approved', 6 => 'Paid', 99 => 'Refused');
365 $this->labelStatus = array(0 => 'Draft', 2 => 'ValidatedWaitingApproval', 4 => 'Canceled', 5 => 'Approved', 6 => 'Paid', 99 => 'Refused');
366
367 $this->fields['ref_ext']['visible'] = getDolGlobalInt('MAIN_LIST_SHOW_REF_EXT');
368 }
369
377 public function create($user, $notrigger = 0)
378 {
379 global $conf, $langs;
380
381 $now = dol_now();
382
383 $error = 0;
384
385 // Check parameters
386 if (empty($this->date_debut) || empty($this->date_fin)) {
387 $this->error = $langs->trans('ErrorFieldRequired', $langs->transnoentitiesnoconv('Date'));
388 return -1;
389 }
390
391 $fuserid = $this->fk_user_author; // Note fk_user_author is not the 'author' but the guy the expense report is for.
392 if (empty($fuserid)) {
393 $fuserid = $user->id;
394 }
395
396 $this->db->begin();
397
398 $sql = "INSERT INTO ".MAIN_DB_PREFIX.$this->table_element." (";
399 $sql .= "ref";
400 $sql .= ",total_ht";
401 $sql .= ",total_ttc";
402 $sql .= ",total_tva";
403 $sql .= ",date_debut";
404 $sql .= ",date_fin";
405 $sql .= ",date_create";
406 $sql .= ",fk_user_creat";
407 $sql .= ",fk_user_author";
408 $sql .= ",fk_user_validator";
409 $sql .= ",fk_user_approve";
410 $sql .= ",fk_user_modif";
411 $sql .= ",fk_statut";
412 $sql .= ",fk_c_paiement";
413 $sql .= ",paid";
414 $sql .= ",note_public";
415 $sql .= ",note_private";
416 $sql .= ",entity";
417 $sql .= ") VALUES(";
418 $sql .= "'(PROV)'";
419 $sql .= ", ".price2num($this->total_ht, 'MT');
420 $sql .= ", ".price2num($this->total_ttc, 'MT');
421 $sql .= ", ".price2num($this->total_tva, 'MT');
422 $sql .= ", '".$this->db->idate($this->date_debut)."'";
423 $sql .= ", '".$this->db->idate($this->date_fin)."'";
424 $sql .= ", '".$this->db->idate($now)."'";
425 $sql .= ", ".((int) $user->id);
426 $sql .= ", ".((int) $fuserid);
427 $sql .= ", ".($this->fk_user_validator > 0 ? ((int) $this->fk_user_validator) : "null");
428 $sql .= ", ".($this->fk_user_approve > 0 ? ((int) $this->fk_user_approve) : "null");
429 $sql .= ", ".($this->fk_user_modif > 0 ? ((int) $this->fk_user_modif) : "null");
430 $sql .= ", ".($this->fk_statut > 1 ? ((int) $this->fk_statut) : 0);
431 $sql .= ", ".($this->modepaymentid ? ((int) $this->modepaymentid) : "null");
432 $sql .= ", 0";
433 $sql .= ", ".($this->note_public ? "'".$this->db->escape($this->note_public)."'" : "null");
434 $sql .= ", ".($this->note_private ? "'".$this->db->escape($this->note_private)."'" : "null");
435 $sql .= ", ".((int) $conf->entity);
436 $sql .= ")";
437
438 $result = $this->db->query($sql);
439 if ($result) {
440 $this->id = $this->db->last_insert_id(MAIN_DB_PREFIX.$this->table_element);
441 $this->ref = '(PROV'.$this->id.')';
442
443 $sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element." SET ref='".$this->db->escape($this->ref)."' WHERE rowid=".((int) $this->id);
444 $resql = $this->db->query($sql);
445 if (!$resql) {
446 $this->error = $this->db->lasterror();
447 $error++;
448 }
449
450 if (!$error) {
451 if (is_array($this->lines) && count($this->lines) > 0) {
452 foreach ($this->lines as $line) {
453 // Test and convert into object this->lines[$i]. When coming from REST API, we may still have an array
454 //if (! is_object($line)) $line=json_decode(json_encode($line), false); // convert recursively array into object.
455 if (!is_object($line)) {
456 $line = (object) $line;
457 $newndfline = new ExpenseReportLine($this->db);
458 $newndfline->fk_expensereport = $line->fk_expensereport;
459 $newndfline->fk_c_type_fees = $line->fk_c_type_fees;
460 $newndfline->fk_project = $line->fk_project;
461 $newndfline->vatrate = $line->vatrate;
462 $newndfline->vat_src_code = $line->vat_src_code;
463 $newndfline->localtax1_tx = $line->localtax1_tx;
464 $newndfline->localtax2_tx = $line->localtax2_tx;
465 $newndfline->localtax1_type = $line->localtax1_type;
466 $newndfline->localtax2_type = $line->localtax2_type;
467 $newndfline->comments = $line->comments;
468 $newndfline->qty = $line->qty;
469 $newndfline->value_unit = $line->value_unit;
470 $newndfline->total_ht = $line->total_ht;
471 $newndfline->total_ttc = $line->total_ttc;
472 $newndfline->total_tva = $line->total_tva;
473 $newndfline->total_localtax1 = $line->total_localtax1;
474 $newndfline->total_localtax2 = $line->total_localtax2;
475 $newndfline->date = $line->date;
476 $newndfline->rule_warning_message = $line->rule_warning_message;
477 $newndfline->fk_c_exp_tax_cat = $line->fk_c_exp_tax_cat;
478 $newndfline->fk_ecm_files = $line->fk_ecm_files;
479 } else {
480 $newndfline = $line;
481 }
482 //$newndfline=new ExpenseReportLine($this->db);
483 $newndfline->fk_expensereport = $this->id;
484 $result = $newndfline->insert();
485 if ($result < 0) {
486 $this->error = $newndfline->error;
487 $this->errors = $newndfline->errors;
488 $error++;
489 break;
490 }
491 }
492 }
493 }
494
495 if (!$error) {
496 $result = $this->insertExtraFields();
497 if ($result < 0) {
498 $error++;
499 }
500 }
501
502 if (!$error) {
503 $result = $this->update_price(1);
504 if ($result > 0) {
505 if (!$notrigger) {
506 // Call trigger
507 $result = $this->call_trigger('EXPENSE_REPORT_CREATE', $user);
508
509 if ($result < 0) {
510 $error++;
511 }
512 // End call triggers
513 }
514
515 if (empty($error)) {
516 $this->db->commit();
517 return $this->id;
518 } else {
519 $this->db->rollback();
520 return -4;
521 }
522 } else {
523 $this->db->rollback();
524 return -3;
525 }
526 } else {
527 dol_syslog(get_class($this)."::create error ".$this->error, LOG_ERR);
528 $this->db->rollback();
529 return -2;
530 }
531 } else {
532 $this->error = $this->db->lasterror()." sql=".$sql;
533 $this->db->rollback();
534 return -1;
535 }
536 }
537
545 public function createFromClone(User $user, $fk_user_author)
546 {
547 global $hookmanager;
548
549 $error = 0;
550
551 if (empty($fk_user_author)) {
552 $fk_user_author = $user->id;
553 }
554
555 $this->db->begin();
556
557 // get extrafields so they will be clone
558 //foreach($this->lines as $line)
559 //$line->fetch_optionals();
560
561 // Load source object
562 $objFrom = clone $this;
563
564 $this->id = 0;
565 $this->ref = '';
566 $this->status = 0;
567 $this->fk_statut = 0; // deprecated
568
569 // Clear fields
570 $this->fk_user_creat = $user->id;
571 $this->fk_user_author = $fk_user_author; // Note fk_user_author is not the 'author' but the guy the expense report is for.
572 $this->fk_user_valid = 0;
573 $this->date_create = '';
574 $this->date_creation = '';
575 $this->date_validation = '';
576
577 // Remove link on lines to a joined file
578 if (is_array($this->lines) && count($this->lines) > 0) {
579 foreach ($this->lines as $key => $line) {
580 $this->lines[$key]->fk_ecm_files = 0;
581 }
582 }
583
584 // Create clone
585 $this->context['createfromclone'] = 'createfromclone';
586 $result = $this->create($user);
587 if ($result < 0) {
588 $error++;
589 }
590
591 if (!$error) {
592 // Hook of thirdparty module
593 if (is_object($hookmanager)) {
594 $parameters = array('objFrom' => $objFrom);
595 $action = '';
596 $reshook = $hookmanager->executeHooks('createFrom', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
597 if ($reshook < 0) {
598 $this->setErrorsFromObject($hookmanager);
599 $error++;
600 }
601 }
602 }
603
604 unset($this->context['createfromclone']);
605
606 // End
607 if (!$error) {
608 $this->db->commit();
609 return $this->id;
610 } else {
611 $this->db->rollback();
612 return -1;
613 }
614 }
615
616
625 public function update($user, $notrigger = 0, $userofexpensereport = null)
626 {
627 $error = 0;
628 $this->db->begin();
629
630 $sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element." SET";
631 $sql .= " total_ht = ".((float) $this->total_ht);
632 $sql .= " , total_ttc = ".((float) $this->total_ttc);
633 $sql .= " , total_tva = ".((float) $this->total_tva);
634 if (!empty($this->date_create)) {
635 $sql .= " , date_create = '".$this->db->idate($this->date_create)."'";
636 }
637 $sql .= " , date_debut = '".$this->db->idate($this->date_debut)."'";
638 $sql .= " , date_fin = '".$this->db->idate($this->date_fin)."'";
639 if ($userofexpensereport && is_object($userofexpensereport)) {
640 $sql .= " , fk_user_author = ".($userofexpensereport->id > 0 ? (int) $userofexpensereport->id : "null"); // Note fk_user_author is not the 'author' but the guy the expense report is for.
641 }
642 $sql .= " , fk_user_validator = ".($this->fk_user_validator > 0 ? (int) $this->fk_user_validator : "null");
643 $sql .= " , fk_user_valid = ".($this->fk_user_valid > 0 ? (int) $this->fk_user_valid : "null");
644 $sql .= " , fk_user_approve = ".($this->fk_user_approve > 0 ? (int) $this->fk_user_approve : "null");
645 $sql .= " , fk_user_modif = ".((int) $user->id);
646 $sql .= " , fk_statut = ".($this->fk_statut >= 0 ? (int) $this->fk_statut : 0);
647 $sql .= " , fk_c_paiement = ".($this->fk_c_paiement > 0 ? (int) $this->fk_c_paiement : "null");
648 $sql .= " , note_public = ".(!empty($this->note_public) ? "'".$this->db->escape($this->note_public)."'" : "''");
649 $sql .= " , note_private = ".(!empty($this->note_private) ? "'".$this->db->escape($this->note_private)."'" : "''");
650 $sql .= " , detail_refuse = ".(!empty($this->detail_refuse) ? "'".$this->db->escape($this->detail_refuse)."'" : "''");
651 $sql .= " WHERE rowid = ".((int) $this->id);
652
653 dol_syslog(get_class($this)."::update", LOG_DEBUG);
654 $result = $this->db->query($sql);
655 if ($result) {
656 $result = $this->insertExtraFields();
657 if ($result < 0) {
658 $error++;
659 }
660
661 if (!$error && !$notrigger) {
662 // Call trigger
663 $result = $this->call_trigger('EXPENSE_REPORT_MODIFY', $user);
664 if ($result < 0) {
665 $error++;
666 }
667 // End call triggers
668 }
669
670 if (!$error) {
671 $this->db->commit();
672 return 1;
673 } else {
674 $this->db->rollback();
675 $this->error = $this->db->error();
676 return -2;
677 }
678 } else {
679 $this->db->rollback();
680 $this->error = $this->db->error();
681 return -1;
682 }
683 }
684
692 public function fetch($id, $ref = '')
693 {
694 $sql = "SELECT d.rowid, d.entity, d.ref, d.note_public, d.note_private,"; // DEFAULT
695 $sql .= " d.detail_refuse, d.detail_cancel, d.fk_user_refuse, d.fk_user_cancel,"; // ACTIONS
696 $sql .= " d.date_refuse, d.date_cancel,"; // ACTIONS
697 $sql .= " d.total_ht, d.total_ttc, d.total_tva,";
698 $sql .= " d.localtax1 as total_localtax1, d.localtax2 as total_localtax2,";
699 $sql .= " d.date_debut, d.date_fin, d.date_create, d.tms as date_modif, d.date_valid, d.date_approve,"; // DATES (datetime)
700 $sql .= " d.fk_user_creat, d.fk_user_author, d.fk_user_modif, d.fk_user_validator,";
701 $sql .= " d.fk_user_valid, d.fk_user_approve,";
702 $sql .= " d.fk_statut as status, d.fk_c_paiement, d.paid";
703 $sql .= " FROM ".MAIN_DB_PREFIX.$this->table_element." as d";
704 if ($ref) {
705 $sql .= " WHERE d.ref = '".$this->db->escape($ref)."'";
706 $sql .= " AND d.entity IN (".getEntity('expensereport').")";
707 } else {
708 $sql .= " WHERE d.rowid = ".((int) $id);
709 }
710 //$sql.= $restrict;
711
712 dol_syslog(get_class($this)."::fetch", LOG_DEBUG);
713 $resql = $this->db->query($sql);
714 if ($resql) {
715 $obj = $this->db->fetch_object($resql);
716 if ($obj) {
717 $this->id = $obj->rowid;
718 $this->ref = $obj->ref;
719
720 $this->entity = $obj->entity;
721
722 $this->total_ht = $obj->total_ht;
723 $this->total_tva = $obj->total_tva;
724 $this->total_ttc = $obj->total_ttc;
725 $this->localtax1 = $obj->total_localtax1; // For backward compatibility
726 $this->localtax2 = $obj->total_localtax2; // For backward compatibility
727 $this->total_localtax1 = $obj->total_localtax1;
728 $this->total_localtax2 = $obj->total_localtax2;
729
730 $this->note_public = $obj->note_public;
731 $this->note_private = $obj->note_private;
732 $this->detail_refuse = $obj->detail_refuse;
733 $this->detail_cancel = $obj->detail_cancel;
734
735 $this->date_debut = $this->db->jdate($obj->date_debut);
736 $this->date_fin = $this->db->jdate($obj->date_fin);
737 $this->date_valid = $this->db->jdate($obj->date_valid);
738 $this->date_approve = $this->db->jdate($obj->date_approve);
739 $this->date_create = $this->db->jdate($obj->date_create);
740 $this->date_modif = $this->db->jdate($obj->date_modif);
741 $this->date_refuse = $this->db->jdate($obj->date_refuse);
742 $this->date_cancel = $this->db->jdate($obj->date_cancel);
743
744 $this->fk_user_creat = $obj->fk_user_creat;
745 $this->fk_user_author = $obj->fk_user_author; // Note fk_user_author is not the 'author' but the guy the expense report is for.
746 $this->fk_user_modif = $obj->fk_user_modif;
747 $this->fk_user_validator = $obj->fk_user_validator;
748 $this->fk_user_valid = $obj->fk_user_valid;
749 $this->fk_user_refuse = $obj->fk_user_refuse;
750 $this->fk_user_cancel = $obj->fk_user_cancel;
751 $this->fk_user_approve = $obj->fk_user_approve;
752
753 $user_author = new User($this->db);
754 if ($this->fk_user_author > 0) {
755 $user_author->fetch($this->fk_user_author);
756 }
757
758 $this->user_author_infos = dolGetFirstLastname($user_author->firstname, $user_author->lastname);
759
760 $user_approver = new User($this->db);
761 if ($this->fk_user_approve > 0) {
762 $user_approver->fetch($this->fk_user_approve);
763 } elseif ($this->fk_user_validator > 0) {
764 $user_approver->fetch($this->fk_user_validator); // For backward compatibility
765 }
766 $this->user_validator_infos = dolGetFirstLastname($user_approver->firstname, $user_approver->lastname);
767
768 $this->fk_statut = (int) $obj->status; // deprecated
769 $this->status = (int) $obj->status;
770 $this->fk_c_paiement = $obj->fk_c_paiement;
771 $this->paid = $obj->paid;
772
773 if ($this->status == self::STATUS_APPROVED || $this->status == self::STATUS_CLOSED) {
774 $user_valid = new User($this->db);
775 if ($this->fk_user_valid > 0) {
776 $user_valid->fetch($this->fk_user_valid);
777 }
778 $this->user_valid_infos = dolGetFirstLastname($user_valid->firstname, $user_valid->lastname);
779 }
780
781 $this->fetch_optionals();
782
783 $result = $this->fetch_lines();
784
785 return $result;
786 } else {
787 return 0;
788 }
789 } else {
790 $this->error = $this->db->lasterror();
791 return -1;
792 }
793 }
794
795 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
806 public function set_paid($id, $fuser, $notrigger = 0)
807 {
808 // phpcs:enable
809 dol_syslog(get_class($this)."::set_paid is deprecated, use setPaid instead", LOG_NOTICE);
810 return $this->setPaid($id, $fuser, $notrigger);
811 }
812
821 public function setPaid($id, $fuser, $notrigger = 0)
822 {
823 $error = 0;
824 $this->db->begin();
825
826 $sql = "UPDATE ".MAIN_DB_PREFIX."expensereport";
827 $sql .= " SET fk_statut = ".self::STATUS_CLOSED.", paid = 1";
828 $sql .= " WHERE rowid = ".((int) $id)." AND fk_statut = ".self::STATUS_APPROVED;
829
830 dol_syslog(get_class($this)."::setPaid", LOG_DEBUG);
831 $resql = $this->db->query($sql);
832 if ($resql) {
833 if ($this->db->affected_rows($resql)) {
834 if (!$notrigger) {
835 // Call trigger
836 $result = $this->call_trigger('EXPENSE_REPORT_PAID', $fuser);
837
838 if ($result < 0) {
839 $error++;
840 }
841 // End call triggers
842 }
843
844 if (empty($error)) {
845 $this->db->commit();
846
848 $this->paid = 1;
849
850 return 1;
851 } else {
852 $this->db->rollback();
853 $this->error = $this->db->error();
854 return -2;
855 }
856 } else {
857 $this->db->commit();
858 return 0;
859 }
860 } else {
861 $this->db->rollback();
862 dol_print_error($this->db);
863 return -1;
864 }
865 }
866
873 public function getLibStatut($mode = 0)
874 {
875 return $this->LibStatut($this->status, $mode);
876 }
877
883 public function getSummary()
884 {
885 $note = preg_replace('/(?:<br\s*\/?>|<\/(?:p|div|li)>)/i', "\n", (string) $this->note_public);
886 return dolGetFirstLineOfText($note);
887 }
888
889 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
897 public function LibStatut($status, $mode = 0)
898 {
899 // phpcs:enable
900 global $langs;
901
902 $labelStatus = $langs->transnoentitiesnoconv($this->labelStatus[$status]);
903 $labelStatusShort = $langs->transnoentitiesnoconv($this->labelStatusShort[$status]);
904
905 $statuslogo = array(0 => 'status0', 2 => 'status1', 4 => 'status6', 5 => 'status4', 6 => 'status6', 99 => 'status5');
906
907 $statusType = $statuslogo[$status];
908
909 return dolGetStatus($labelStatus, $labelStatusShort, '', $statusType, $mode);
910 }
911
912
919 public function info($id)
920 {
921 global $conf;
922
923 $sql = "SELECT f.rowid,";
924 $sql .= " f.date_create as datec,";
925 $sql .= " f.tms as date_modification,";
926 $sql .= " f.date_valid as datev,";
927 $sql .= " f.date_approve as datea,";
928 $sql .= " f.fk_user_creat as fk_user_creation,";
929 $sql .= " f.fk_user_author as fk_user_author,";
930 $sql .= " f.fk_user_modif as fk_user_modification,";
931 $sql .= " f.fk_user_valid,";
932 $sql .= " f.fk_user_approve";
933 $sql .= " FROM ".MAIN_DB_PREFIX."expensereport as f";
934 $sql .= " WHERE f.rowid = ".((int) $id);
935 $sql .= " AND f.entity = ".((int) $conf->entity);
936
937
938
939 $resql = $this->db->query($sql);
940 if ($resql) {
941 if ($this->db->num_rows($resql)) {
942 $obj = $this->db->fetch_object($resql);
943
944 $this->id = $obj->rowid;
945
946 $this->date_creation = $this->db->jdate($obj->datec);
947 $this->date_modification = $this->db->jdate($obj->date_modification);
948 $this->date_validation = $this->db->jdate($obj->datev);
949 $this->date_approbation = $this->db->jdate($obj->datea);
950
951 $this->user_creation_id = $obj->fk_user_author;
952 $this->user_creation_id = $obj->fk_user_creation;
953 $this->user_validation_id = $obj->fk_user_valid;
954 $this->user_modification_id = $obj->fk_user_modification;
955 $this->user_approve_id = $obj->fk_user_approve;
956 }
957 $this->db->free($resql);
958 } else {
959 dol_print_error($this->db);
960 }
961 }
962
963
964
972 public function initAsSpecimen()
973 {
974 global $user, $langs;
975
976 $now = dol_now();
977
978 // Initialise parameters
979 $this->id = 0;
980 $this->ref = 'SPECIMEN';
981 $this->specimen = 1;
982 $this->entity = 1;
983 $this->date_create = $now;
984 $this->date_debut = $now;
985 $this->date_fin = $now;
986 $this->date_valid = $now;
987 $this->date_approve = $now;
988
989 $type_fees_id = 2; // TF_TRIP
990
991 $this->status = 5;
992
993 $this->fk_user_author = $user->id;
994 $this->fk_user_validator = $user->id;
995 $this->fk_user_valid = $user->id;
996 $this->fk_user_approve = $user->id;
997
998 $this->note_private = 'Private note';
999 $this->note_public = 'SPECIMEN';
1000 $nbp = min(1000, GETPOSTINT('nblines') ? GETPOSTINT('nblines') : 5); // We can force the nb of lines to test from command line (but not more than 1000)
1001 $xnbp = 0;
1002 while ($xnbp < $nbp) {
1003 $line = new ExpenseReportLine($this->db);
1004 $line->comments = $langs->trans("Comment")." ".$xnbp;
1005 $line->date = ($now - 3600 * (1 + $xnbp));
1006 $line->total_ht = 100;
1007 $line->total_tva = 20;
1008 $line->total_ttc = 120;
1009 $line->qty = 1;
1010 $line->vatrate = 20;
1011 $line->value_unit = 120;
1012 $line->fk_expensereport = 0;
1013 $line->type_fees_code = 'TRA';
1014 $line->fk_c_type_fees = $type_fees_id;
1015
1016 $line->projet_ref = 'ABC';
1017
1018 $this->lines[$xnbp] = $line;
1019 $xnbp++;
1020
1021 $this->total_ht += $line->total_ht;
1022 $this->total_tva += $line->total_tva;
1023 $this->total_ttc += $line->total_ttc;
1024 }
1025
1026 return 1;
1027 }
1028
1029 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1037 public function fetch_line_by_project($projectid, $user)
1038 {
1039 // phpcs:enable
1040 global $langs;
1041
1042 $langs->load('trips');
1043
1044 if ($user->hasRight('expensereport', 'lire')) {
1045 $sql = "SELECT de.fk_expensereport, de.date, de.comments, de.total_ht, de.total_ttc";
1046 $sql .= " FROM ".MAIN_DB_PREFIX."expensereport_det as de";
1047 $sql .= " WHERE de.fk_projet = ".((int) $projectid);
1048
1049 dol_syslog(get_class($this)."::fetch", LOG_DEBUG);
1050 $result = $this->db->query($sql);
1051 if ($result) {
1052 $num = $this->db->num_rows($result);
1053 $i = 0;
1054 $total_HT = 0;
1055 $total_TTC = 0;
1056
1057 while ($i < $num) {
1058 $objp = $this->db->fetch_object($result);
1059
1060 $sql2 = "SELECT d.rowid, d.fk_user_author, d.ref, d.fk_statut as status";
1061 $sql2 .= " FROM ".MAIN_DB_PREFIX."expensereport as d";
1062 $sql2 .= " WHERE d.rowid = ".((int) $objp->fk_expensereport);
1063
1064 $result2 = $this->db->query($sql2);
1065 $obj = $this->db->fetch_object($result2);
1066
1067 $objp->fk_user_author = $obj->fk_user_author;
1068 $objp->ref = $obj->ref;
1069 $objp->fk_c_expensereport_status = $obj->status;
1070 $objp->rowid = $obj->rowid;
1071
1072 $total_HT += $objp->total_ht;
1073 $total_TTC += $objp->total_ttc;
1074 $author = new User($this->db);
1075 $author->fetch($objp->fk_user_author);
1076
1077 print '<tr>';
1078 print '<td>';
1079 print '<a href="'.dolBuildUrl(DOL_URL_ROOT.'/expensereport/card.php', ['id' => $objp->rowid]).'">'.$objp->ref_num.'</a>';
1080 print '</td>';
1081 print '<td class="center">'.dol_print_date($objp->date, 'day').'</td>';
1082 print '<td>'.$author->getNomUrl(1).'</td>';
1083 print '<td>'.$objp->comments.'</td>';
1084 print '<td class="right">'.price($objp->total_ht).'</td>';
1085 print '<td class="right">'.price($objp->total_ttc).'</td>';
1086 print '<td class="right">';
1087
1088 switch ($objp->fk_c_expensereport_status) {
1089 case 4:
1090 print img_picto($langs->trans('StatusOrderCanceled'), 'statut5');
1091 break;
1092 case 1:
1093 print $langs->trans('Draft').' '.img_picto($langs->trans('Draft'), 'statut0');
1094 break;
1095 case 2:
1096 print $langs->trans('TripForValid').' '.img_picto($langs->trans('TripForValid'), 'statut3');
1097 break;
1098 case 5:
1099 print $langs->trans('TripForPaid').' '.img_picto($langs->trans('TripForPaid'), 'statut3');
1100 break;
1101 case 6:
1102 print $langs->trans('TripPaid').' '.img_picto($langs->trans('TripPaid'), 'statut4');
1103 break;
1104 }
1105 /*
1106 if ($status==4) return img_picto($langs->trans('StatusOrderCanceled'),'statut5');
1107 if ($status==1) return img_picto($langs->trans('StatusOrderDraft'),'statut0');
1108 if ($status==2) return img_picto($langs->trans('StatusOrderValidated'),'statut1');
1109 if ($status==2) return img_picto($langs->trans('StatusOrderOnProcess'),'statut3');
1110 if ($status==5) return img_picto($langs->trans('StatusOrderToBill'),'statut4');
1111 if ($status==6) return img_picto($langs->trans('StatusOrderOnProcess'),'statut6');
1112 */
1113 print '</td>';
1114 print '</tr>';
1115
1116 $i++;
1117 }
1118
1119 print '<tr class="liste_total"><td colspan="4">'.$langs->trans("Number").': '.$i.'</td>';
1120 print '<td class="right" width="100">'.$langs->trans("TotalHT").' : '.price($total_HT).'</td>';
1121 print '<td class="right" width="100">'.$langs->trans("TotalTTC").' : '.price($total_TTC).'</td>';
1122 print '<td>&nbsp;</td>';
1123 print '</tr>';
1124 } else {
1125 $this->error = $this->db->lasterror();
1126 return -1;
1127 }
1128 }
1129
1130 return 0;
1131 }
1132
1133 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1139 public function fetch_lines()
1140 {
1141 // phpcs:enable
1142 $this->lines = array();
1143
1144 $sql = ' SELECT de.rowid, de.comments, de.qty, de.value_unit, de.date, de.rang,';
1145 $sql .= " de.".$this->db->sanitize($this->fk_element).", de.fk_c_type_fees, de.fk_c_exp_tax_cat, de.fk_projet as fk_project,";
1146 $sql .= ' de.tva_tx, de.vat_src_code,';
1147 $sql .= ' de.localtax1_tx, de.localtax2_tx, de.localtax1_type, de.localtax2_type,';
1148 $sql .= ' de.fk_ecm_files,';
1149 $sql .= ' de.total_ht, de.total_tva, de.total_ttc,';
1150 $sql .= ' de.total_localtax1, de.total_localtax2, de.rule_warning_message,';
1151 $sql .= ' ctf.code as code_type_fees, ctf.label as label_type_fees, ctf.accountancy_code as accountancy_code_type_fees,';
1152 $sql .= ' p.ref as ref_projet, p.title as title_projet';
1153 $sql .= ' FROM '.MAIN_DB_PREFIX.$this->db->sanitize($this->table_element_line).' as de';
1154 $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_type_fees as ctf ON de.fk_c_type_fees = ctf.id';
1155 $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'projet as p ON de.fk_projet = p.rowid';
1156 $sql .= " WHERE de.".$this->db->sanitize($this->fk_element)." = ".((int) $this->id);
1157 if (getDolGlobalString('EXPENSEREPORT_LINES_SORTED_BY_ROWID')) {
1158 $sql .= ' ORDER BY de.rang ASC, de.rowid ASC';
1159 } else {
1160 $sql .= ' ORDER BY de.rang ASC, de.date ASC';
1161 }
1162
1163 $resql = $this->db->query($sql);
1164 if ($resql) {
1165 $num = $this->db->num_rows($resql);
1166 $i = 0;
1167 while ($i < $num) {
1168 $objp = $this->db->fetch_object($resql);
1169
1170 $deplig = new ExpenseReportLine($this->db);
1171
1172 $deplig->rowid = $objp->rowid;
1173 $deplig->id = $objp->rowid;
1174 $deplig->comments = $objp->comments;
1175 $deplig->qty = $objp->qty;
1176 $deplig->value_unit = $objp->value_unit;
1177 $deplig->date = $objp->date;
1178 $deplig->dates = $this->db->jdate($objp->date);
1179
1180 $deplig->fk_expensereport = $objp->fk_expensereport;
1181 $deplig->fk_c_type_fees = $objp->fk_c_type_fees;
1182 $deplig->fk_c_exp_tax_cat = $objp->fk_c_exp_tax_cat;
1183 $deplig->fk_projet = $objp->fk_project; // deprecated
1184 $deplig->fk_project = $objp->fk_project;
1185 $deplig->fk_ecm_files = $objp->fk_ecm_files;
1186
1187 $deplig->total_ht = $objp->total_ht;
1188 $deplig->total_tva = $objp->total_tva;
1189 $deplig->total_ttc = $objp->total_ttc;
1190 $deplig->total_localtax1 = $objp->total_localtax1;
1191 $deplig->total_localtax2 = $objp->total_localtax2;
1192
1193 $deplig->type_fees_code = empty($objp->code_type_fees) ? 'TF_OTHER' : $objp->code_type_fees;
1194 $deplig->type_fees_libelle = $objp->label_type_fees;
1195 $deplig->type_fees_accountancy_code = $objp->accountancy_code_type_fees;
1196
1197 $deplig->tva_tx = $objp->tva_tx;
1198 $deplig->vatrate = $objp->tva_tx;
1199 $deplig->vat_src_code = $objp->vat_src_code;
1200 $deplig->localtax1_tx = $objp->localtax1_tx;
1201 $deplig->localtax2_tx = $objp->localtax2_tx;
1202 $deplig->localtax1_type = $objp->localtax1_type;
1203 $deplig->localtax2_type = $objp->localtax2_type;
1204
1205 $deplig->projet_ref = $objp->ref_projet;
1206 $deplig->projet_title = $objp->title_projet;
1207
1208 $deplig->rule_warning_message = $objp->rule_warning_message;
1209
1210 $deplig->rang = $objp->rang;
1211
1212 $this->lines[$i] = $deplig;
1213
1214 $i++;
1215 }
1216 $this->db->free($resql);
1217 return 1;
1218 } else {
1219 $this->error = $this->db->lasterror();
1220 dol_syslog(get_class($this)."::fetch_lines: Error ".$this->error, LOG_ERR);
1221 return -3;
1222 }
1223 }
1224
1225
1233 public function delete($user = null, $notrigger = 0)
1234 {
1235 global $conf;
1236 require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
1237
1238 $error = 0;
1239
1240 $this->db->begin();
1241
1242 if (!$notrigger) {
1243 // Call trigger
1244 $result = $this->call_trigger('EXPENSE_REPORT_DELETE', $user);
1245 if ($result < 0) {
1246 $error++;
1247 }
1248 // End call triggers
1249 }
1250
1251 // Delete extrafields of lines and lines
1252 if (!$error && !empty($this->table_element_line)) {
1253 $tabletodelete = $this->table_element_line;
1254 //$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).")";
1255 $sql = "DELETE FROM ".MAIN_DB_PREFIX.$tabletodelete." WHERE ".$this->fk_element." = ".((int) $this->id);
1256 if (!$this->db->query($sql)) {
1257 $error++;
1258 $this->error = $this->db->lasterror();
1259 $this->errors[] = $this->error;
1260 dol_syslog(get_class($this)."::delete error ".$this->error, LOG_ERR);
1261 }
1262 }
1263
1264 if (!$error) {
1265 // Delete linked object
1266 $res = $this->deleteObjectLinked();
1267 if ($res < 0) {
1268 $error++;
1269 }
1270 }
1271
1272 if (!$error) {
1273 // Delete linked contacts
1274 $res = $this->delete_linked_contact();
1275 if ($res < 0) {
1276 $error++;
1277 }
1278 }
1279
1280 // Removed extrafields of object
1281 if (!$error) {
1282 $result = $this->deleteExtraFields();
1283 if ($result < 0) {
1284 $error++;
1285 dol_syslog(get_class($this)."::delete error ".$this->error, LOG_ERR);
1286 }
1287 }
1288
1289 // Delete main record
1290 if (!$error) {
1291 $sql = "DELETE FROM ".MAIN_DB_PREFIX.$this->table_element." WHERE rowid = ".((int) $this->id);
1292 $res = $this->db->query($sql);
1293 if (!$res) {
1294 $error++;
1295 $this->error = $this->db->lasterror();
1296 $this->errors[] = $this->error;
1297 dol_syslog(get_class($this)."::delete error ".$this->error, LOG_ERR);
1298 }
1299 }
1300
1301 // Delete record into ECM index and physically
1302 if (!$error) {
1303 $res = $this->deleteEcmFiles(0); // Deleting files physically is done later with the dol_delete_dir_recursive
1304 $res = $this->deleteEcmFiles(1); // Deleting files physically is done later with the dol_delete_dir_recursive
1305 if (!$res) {
1306 $error++;
1307 }
1308 }
1309
1310 if (!$error) {
1311 // We remove directory
1312 $ref = dol_sanitizeFileName($this->ref);
1313 if ($conf->expensereport->multidir_output[$this->entity] && !empty($this->ref)) {
1314 $dir = $conf->expensereport->multidir_output[$this->entity]."/".$ref;
1315 $file = $dir."/".$ref.".pdf";
1316 if (file_exists($file)) {
1317 dol_delete_preview($this);
1318
1319 if (!dol_delete_file($file, 0, 0, 0, $this)) {
1320 $this->error = 'ErrorFailToDeleteFile';
1321 $this->errors[] = $this->error;
1322 $this->db->rollback();
1323 return 0;
1324 }
1325 }
1326 if (file_exists($dir)) {
1327 $res = @dol_delete_dir_recursive($dir);
1328 if (!$res) {
1329 $this->error = 'ErrorFailToDeleteDir';
1330 $this->errors[] = $this->error;
1331 $this->db->rollback();
1332 return 0;
1333 }
1334 }
1335 }
1336 }
1337
1338 if (!$error) {
1339 dol_syslog(get_class($this)."::delete ".$this->id." by ".$user->id, LOG_DEBUG);
1340 $this->db->commit();
1341 return 1;
1342 } else {
1343 $this->db->rollback();
1344 return -1;
1345 }
1346 }
1347
1355 public function setValidate($fuser, $notrigger = 0)
1356 {
1357 global $conf, $langs, $user;
1358
1359 $error = 0;
1360 $now = dol_now();
1361
1362 // Protection
1363 if ($this->status == self::STATUS_VALIDATED) {
1364 dol_syslog(get_class($this)."::valid action abandoned: already validated", LOG_WARNING);
1365 return 0;
1366 }
1367
1368 $this->date_valid = $now; // Required for the getNextNum later.
1369
1370 // Define new ref
1371 if (preg_match('/^[\‍(]?PROV/i', $this->ref) || empty($this->ref)) { // empty should not happened, but when it occurs, the test save life
1372 $num = $this->getNextNumRef();
1373 } else {
1374 $num = (string) $this->ref;
1375 }
1376 if (empty($num) || $num < 0) {
1377 return -1;
1378 }
1379
1380 $this->newref = dol_sanitizeFileName($num);
1381
1382 $this->db->begin();
1383
1384 // Validate
1385 $sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element;
1386 $sql .= " SET ref = '".$this->db->escape($num)."',";
1387 $sql .= " fk_statut = ".((int) self::STATUS_VALIDATED).",";
1388 $sql .= " date_valid = '".$this->db->idate($this->date_valid)."',";
1389 $sql .= " fk_user_valid = ".((int) $user->id);
1390 $sql .= " WHERE rowid = ".((int) $this->id);
1391
1392 $resql = $this->db->query($sql);
1393 if ($resql) {
1394 if (!$notrigger) {
1395 // Call trigger
1396 $result = $this->call_trigger('EXPENSE_REPORT_VALIDATE', $fuser);
1397 if ($result < 0) {
1398 $error++;
1399 }
1400 // End call triggers
1401 }
1402
1403 if (!$error) {
1404 $this->oldref = $this->ref;
1405
1406 // Rename directory if dir was a temporary ref
1407 if (preg_match('/^[\‍(]?PROV/i', $this->ref)) {
1408 require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
1409
1410 // Now we rename also files into index
1411 $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)."'";
1412 $sql .= " WHERE filename LIKE '".$this->db->escape($this->ref)."%' AND filepath = 'expensereport/".$this->db->escape($this->ref)."' AND entity = ".((int) $this->entity);
1413 $resql = $this->db->query($sql);
1414 if (!$resql) {
1415 $error++;
1416 $this->error = $this->db->lasterror();
1417 }
1418 $sql = 'UPDATE '.MAIN_DB_PREFIX."ecm_files set filepath = 'expensereport/".$this->db->escape($this->newref)."'";
1419 $sql .= " WHERE filepath = 'expensereport/".$this->db->escape($this->ref)."' and entity = ".((int) $conf->entity);
1420 $resql = $this->db->query($sql);
1421 if (!$resql) {
1422 $error++;
1423 $this->error = $this->db->lasterror();
1424 }
1425
1426 // We rename directory ($this->ref = old ref, $num = new ref) in order not to lose the attachments
1427 $oldref = dol_sanitizeFileName($this->ref);
1428 $newref = dol_sanitizeFileName($num);
1429 $dirsource = $conf->expensereport->multidir_output[$this->entity].'/'.$oldref;
1430 $dirdest = $conf->expensereport->multidir_output[$this->entity].'/'.$newref;
1431 if (!$error && file_exists($dirsource)) {
1432 dol_syslog(get_class($this)."::setValidate() rename dir ".$dirsource." into ".$dirdest);
1433
1434 if (@rename($dirsource, $dirdest)) {
1435 dol_syslog("Rename ok");
1436 // Rename docs starting with $oldref with $newref
1437 $listoffiles = dol_dir_list($dirdest, 'files', 1, '^'.preg_quote($oldref, '/'));
1438 foreach ($listoffiles as $fileentry) {
1439 $dirsource = $fileentry['name'];
1440 $dirdest = preg_replace('/^'.preg_quote($oldref, '/').'/', $newref, $dirsource);
1441 $dirsource = $fileentry['path'].'/'.$dirsource;
1442 $dirdest = $fileentry['path'].'/'.$dirdest;
1443 @rename($dirsource, $dirdest);
1444 }
1445 }
1446 }
1447 }
1448 }
1449
1450 // Set new ref and current status
1451 if (!$error) {
1452 $this->ref = $num;
1454 }
1455
1456 if (empty($error)) {
1457 $this->db->commit();
1458 return 1;
1459 } else {
1460 $this->db->rollback();
1461 $this->error = $this->db->error();
1462 return -2;
1463 }
1464 } else {
1465 $this->db->rollback();
1466 $this->error = $this->db->lasterror();
1467 return -1;
1468 }
1469 }
1470
1471 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1478 public function set_save_from_refuse($fuser)
1479 {
1480 // phpcs:enable
1481 // Select the start date of the expense report
1482 $sql = 'SELECT date_debut';
1483 $sql .= ' FROM '.MAIN_DB_PREFIX.$this->table_element;
1484 $sql .= " WHERE rowid = ".((int) $this->id);
1485
1486 $result = $this->db->query($sql);
1487
1488 $objp = $this->db->fetch_object($result);
1489
1490 $this->date_debut = $this->db->jdate($objp->date_debut);
1491
1492 if ($this->status != self::STATUS_VALIDATED) {
1493 $sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element;
1494 $sql .= " SET fk_statut = ".((int) self::STATUS_VALIDATED);
1495 $sql .= " WHERE rowid = ".((int) $this->id);
1496
1497 dol_syslog(get_class($this)."::set_save_from_refuse", LOG_DEBUG);
1498
1499 if ($this->db->query($sql)) {
1500 return 1;
1501 } else {
1502 $this->error = $this->db->lasterror();
1503 return -1;
1504 }
1505 } else {
1506 dol_syslog(get_class($this)."::set_save_from_refuse expensereport already with save status", LOG_WARNING);
1507 }
1508
1509 return 0;
1510 }
1511
1519 public function setApproved($fuser, $notrigger = 0)
1520 {
1521 $now = dol_now();
1522 $error = 0;
1523
1524 if ($this->status != self::STATUS_APPROVED) {
1525 $this->db->begin();
1526 $sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element;
1527 $sql .= " SET ref = '".$this->db->escape($this->ref)."', fk_statut = ".((int) self::STATUS_APPROVED).", fk_user_approve = ".((int) $fuser->id).",";
1528 $sql .= " date_approve='".$this->db->idate($now)."'";
1529 $sql .= " WHERE rowid = ".((int) $this->id);
1530 if ($this->db->query($sql)) {
1532 $this->date_approve = $now;
1533 $this->fk_user_approve = $fuser->id;
1534 if (!$notrigger) {
1535 // Call trigger
1536 $result = $this->call_trigger('EXPENSE_REPORT_APPROVE', $fuser);
1537
1538 if ($result < 0) {
1539 $error++;
1540 }
1541 // End call triggers
1542 }
1543
1544 if (empty($error)) {
1545 $this->db->commit();
1546 return 1;
1547 } else {
1548 $this->db->rollback();
1549 $this->error = $this->db->error();
1550 return -2;
1551 }
1552 } else {
1553 $this->db->rollback();
1554 $this->error = $this->db->lasterror();
1555 return -1;
1556 }
1557 } else {
1558 dol_syslog(get_class($this)."::setApproved expensereport already with approve status", LOG_WARNING);
1559 }
1560
1561 return 0;
1562 }
1563
1572 public function setDeny($fuser, $details, $notrigger = 0)
1573 {
1574 $now = dol_now();
1575 $error = 0;
1576
1577 // date de refus
1578 if ($this->status != self::STATUS_REFUSED) {
1579 $sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element;
1580 $sql .= " SET ref = '".$this->db->escape($this->ref)."', fk_statut = ".self::STATUS_REFUSED.", fk_user_refuse = ".((int) $fuser->id).",";
1581 $sql .= " date_refuse='".$this->db->idate($now)."',";
1582 $sql .= " detail_refuse='".$this->db->escape($details)."',";
1583 $sql .= " fk_user_approve = NULL";
1584 $sql .= " WHERE rowid = ".((int) $this->id);
1585 if ($this->db->query($sql)) {
1586 $this->fk_statut = 99; // deprecated
1587 $this->status = 99;
1588 $this->fk_user_refuse = $fuser->id;
1589 $this->detail_refuse = $details;
1590 $this->date_refuse = $now;
1591
1592 if (!$notrigger) {
1593 // Call trigger
1594 $result = $this->call_trigger('EXPENSE_REPORT_DENY', $fuser);
1595
1596 if ($result < 0) {
1597 $error++;
1598 }
1599 // End call triggers
1600 }
1601
1602 if (empty($error)) {
1603 $this->db->commit();
1604 return 1;
1605 } else {
1606 $this->db->rollback();
1607 $this->error = $this->db->error();
1608 return -2;
1609 }
1610 } else {
1611 $this->db->rollback();
1612 $this->error = $this->db->lasterror();
1613 return -1;
1614 }
1615 } else {
1616 dol_syslog(get_class($this)."::setDeny expensereport already with refuse status", LOG_WARNING);
1617 }
1618
1619 return 0;
1620 }
1621
1622 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1632 public function set_unpaid($fuser, $notrigger = 0)
1633 {
1634 // phpcs:enable
1635 dol_syslog(get_class($this)."::set_unpaid is deprecated, use setUnpaid instead", LOG_NOTICE);
1636 return $this->setUnpaid($fuser, $notrigger);
1637 }
1638
1646 public function setUnpaid($fuser, $notrigger = 0)
1647 {
1648 $error = 0;
1649
1650 if ($this->paid) {
1651 $this->db->begin();
1652
1653 $sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element;
1654 $sql .= " SET paid = 0, fk_statut = ".self::STATUS_APPROVED;
1655 $sql .= " WHERE rowid = ".((int) $this->id);
1656
1657 dol_syslog(get_class($this)."::set_unpaid", LOG_DEBUG);
1658
1659 if ($this->db->query($sql)) {
1660 if (!$notrigger) {
1661 // Call trigger
1662 $result = $this->call_trigger('EXPENSE_REPORT_UNPAID', $fuser);
1663
1664 if ($result < 0) {
1665 $error++;
1666 }
1667 // End call triggers
1668 }
1669
1670 if (empty($error)) {
1671 $this->db->commit();
1672 return 1;
1673 } else {
1674 $this->db->rollback();
1675 $this->error = $this->db->error();
1676 return -2;
1677 }
1678 } else {
1679 $this->db->rollback();
1680 $this->error = $this->db->error();
1681 return -1;
1682 }
1683 } else {
1684 dol_syslog(get_class($this)."::set_unpaid expensereport already with unpaid status", LOG_WARNING);
1685 }
1686
1687 return 0;
1688 }
1689
1690 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1699 public function set_cancel($fuser, $detail, $notrigger = 0)
1700 {
1701 // phpcs:enable
1702 $error = 0;
1703 $this->date_cancel = dol_now();
1704 if ($this->status != self::STATUS_CANCELED) {
1705 $this->db->begin();
1706
1707 $sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element;
1708 $sql .= " SET fk_statut = ".((int) self::STATUS_CANCELED).", fk_user_cancel = ".((int) $fuser->id);
1709 $sql .= ", date_cancel = '".$this->db->idate($this->date_cancel)."'";
1710 $sql .= ", detail_cancel = '".$this->db->escape($detail)."'";
1711 $sql .= " WHERE rowid = ".((int) $this->id);
1712
1713 dol_syslog(get_class($this)."::set_cancel", LOG_DEBUG);
1714
1715 if ($this->db->query($sql)) {
1716 if (!$notrigger) {
1717 // Call trigger
1718 $result = $this->call_trigger('EXPENSE_REPORT_CANCEL', $fuser);
1719
1720 if ($result < 0) {
1721 $error++;
1722 }
1723 // End call triggers
1724 }
1725
1726 if (empty($error)) {
1727 $this->db->commit();
1728 return 1;
1729 } else {
1730 $this->db->rollback();
1731 $this->error = $this->db->error();
1732 return -2;
1733 }
1734 } else {
1735 $this->db->rollback();
1736 $this->error = $this->db->error();
1737 return -1;
1738 }
1739 } else {
1740 dol_syslog(get_class($this)."::set_cancel expensereport already with cancel status", LOG_WARNING);
1741 }
1742 return 0;
1743 }
1744
1750 public function getNextNumRef()
1751 {
1752 global $langs, $conf;
1753 $langs->load("trips");
1754
1755 if (getDolGlobalString('EXPENSEREPORT_ADDON')) {
1756 $mybool = false;
1757
1758 $file = getDolGlobalString('EXPENSEREPORT_ADDON') . ".php";
1759 $classname = getDolGlobalString('EXPENSEREPORT_ADDON');
1760
1761 // Include file with class
1762 $dirmodels = array_merge(array('/'), (array) $conf->modules_parts['models']);
1763 foreach ($dirmodels as $reldir) {
1764 $dir = dol_buildpath($reldir."core/modules/expensereport/");
1765
1766 // Load file with numbering class (if found)
1767 $mybool = ((bool) @include_once $dir.$file) || $mybool;
1768 }
1769
1770 if (!$mybool) {
1771 dol_print_error(null, "Failed to include file ".$file);
1772 return '';
1773 }
1774
1775 $obj = new $classname();
1776 '@phan-var-force ModeleNumRefExpenseReport $obj';
1777 $numref = $obj->getNextValue($this);
1778
1779 if ($numref != "") {
1780 return $numref;
1781 } else {
1782 $this->error = $obj->error;
1783 $this->errors = $obj->errors;
1784 //dol_print_error($this->db,get_class($this)."::getNextNumRef ".$obj->error);
1785 return -1;
1786 }
1787 } else {
1788 $this->error = "Error_EXPENSEREPORT_ADDON_NotDefined";
1789 return -2;
1790 }
1791 }
1792
1799 public function getTooltipContentArray($params)
1800 {
1801 global $conf, $langs;
1802
1803 $langs->load('trips');
1804
1805 $nofetch = !empty($params['nofetch']);
1806 $moretitle = $params['moretitle'] ?? '';
1807
1808 $datas = array();
1809 $datas['picto'] = img_picto('', $this->picto).' <u class="paddingrightonly">'.$langs->trans("ExpenseReport").'</u>';
1810 if (isset($this->status)) {
1811 $datas['picto'] .= ' '.$this->getLibStatut(5);
1812 }
1813 if ($moretitle) {
1814 $datas['picto'] .= ' - '.$moretitle;
1815 }
1816 if (!empty($this->ref)) {
1817 $datas['ref'] = '<br><b>'.$langs->trans('Ref').':</b> '.$this->ref;
1818 }
1819 if (!empty($this->total_ht)) {
1820 $datas['total_ht'] = '<br><b>'.$langs->trans('AmountHT').':</b> '.price($this->total_ht, 0, $langs, 0, -1, -1, $conf->currency);
1821 }
1822 if (!empty($this->total_tva)) {
1823 $datas['total_tva'] = '<br><b>'.$langs->trans('VAT').':</b> '.price($this->total_tva, 0, $langs, 0, -1, -1, $conf->currency);
1824 }
1825 if (!empty($this->total_ttc)) {
1826 $datas['total_ttc'] = '<br><b>'.$langs->trans('AmountTTC').':</b> '.price($this->total_ttc, 0, $langs, 0, -1, -1, $conf->currency);
1827 }
1828
1829 return $datas;
1830 }
1831
1844 public function getNomUrl($withpicto = 0, $option = '', $max = 0, $short = 0, $moretitle = '', $notooltip = 0, $save_lastsearch_value = -1)
1845 {
1846 global $langs, $hookmanager;
1847
1848 $result = '';
1849
1850 $baseurl = DOL_URL_ROOT.'/expensereport/card.php';
1851 $query = ['id' => $this->id];
1852
1853 if ($short) {
1854 return dolBuildUrl($baseurl, $query);
1855 }
1856
1857 $params = [
1858 'id' => $this->id,
1859 'objecttype' => $this->element,
1860 'option' => $option,
1861 'moretitle' => $moretitle,
1862 'nofetch' => 1,
1863 ];
1864 $classfortooltip = 'classfortooltip';
1865 $dataparams = '';
1866 if (getDolGlobalInt('MAIN_ENABLE_AJAX_TOOLTIP')) {
1867 $classfortooltip = 'classforajaxtooltip';
1868 $dataparams = ' data-params="'.dol_escape_htmltag(json_encode($params)).'"';
1869 $label = '';
1870 } else {
1871 $label = implode($this->getTooltipContentArray($params));
1872 }
1873
1874 if ($option != 'nolink') {
1875 // Add param to save lastsearch_values or not
1876 $add_save_lastsearch_values = ($save_lastsearch_value == 1 ? 1 : 0);
1877 if ($save_lastsearch_value == -1 && isset($_SERVER["PHP_SELF"]) && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) {
1878 $add_save_lastsearch_values = 1;
1879 }
1880 if ($add_save_lastsearch_values) {
1881 $query += ['save_lastsearch_values' => 1];
1882 }
1883 }
1884 $url = dolBuildUrl($baseurl, $query);
1885
1886 $ref = $this->ref;
1887 if (empty($ref)) {
1888 $ref = $this->id;
1889 }
1890
1891 $linkclose = '';
1892 if (empty($notooltip)) {
1893 if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
1894 $label = $langs->trans("ShowExpenseReport");
1895 $linkclose .= ' alt="'.dolPrintHTMLForAttribute($label).'"';
1896 }
1897 $linkclose .= ($label ? ' title="'.dolPrintHTMLForAttribute($label).'"' : ' title="tocomplete"');
1898 $linkclose .= $dataparams.' class="'.$classfortooltip.'"';
1899 }
1900
1901 $linkstart = '<a href="'.$url.'"';
1902 $linkstart .= $linkclose.'>';
1903 $linkend = '</a>';
1904
1905 $result .= $linkstart;
1906 if ($withpicto) {
1907 $result .= img_object(($notooltip ? '' : $label), ($this->picto ? $this->picto : 'generic'), ($notooltip ? (($withpicto != 2) ? 'class="paddingright"' : '') : 'class="'.(($withpicto != 2) ? 'paddingright ' : '').'"'), 0, 0, $notooltip ? 0 : 1);
1908 }
1909 if ($withpicto != 2) {
1910 $result .= ($max ? dol_trunc($ref, $max) : $ref);
1911 }
1912 $result .= $linkend;
1913
1914 global $action;
1915 $hookmanager->initHooks(array($this->element . 'dao'));
1916 $parameters = array('id' => $this->id, 'getnomurl' => &$result);
1917 $reshook = $hookmanager->executeHooks('getNomUrl', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
1918 if ($reshook > 0) {
1919 $result = $hookmanager->resPrint;
1920 } else {
1921 $result .= $hookmanager->resPrint;
1922 }
1923 return $result;
1924 }
1925
1926 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1934 public function update_totaux_add($ligne_total_ht, $ligne_total_tva)
1935 {
1936 // phpcs:enable
1937 $this->total_ht += (float) $ligne_total_ht;
1938 $this->total_tva += (float) $ligne_total_tva;
1939 $this->total_ttc += $this->total_tva;
1940
1941 $sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element." SET";
1942 $sql .= " total_ht = ".((float) $this->total_ht);
1943 $sql .= " , total_ttc = ".((float) $this->total_ttc);
1944 $sql .= " , total_tva = ".((float) $this->total_tva);
1945 $sql .= " WHERE rowid = ".((int) $this->id);
1946
1947 $result = $this->db->query($sql);
1948 if ($result) {
1949 return 1;
1950 } else {
1951 $this->error = $this->db->error();
1952 return -1;
1953 }
1954 }
1955
1971 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)
1972 {
1973 global $mysoc;
1974
1975 dol_syslog(get_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);
1976
1977 if ($this->status == self::STATUS_DRAFT || $this->status == self::STATUS_REFUSED) {
1978 if (empty($qty)) {
1979 $qty = 0;
1980 }
1981 if (empty($fk_c_type_fees) || $fk_c_type_fees < 0) {
1982 $fk_c_type_fees = 0;
1983 }
1984 if (empty($fk_c_exp_tax_cat) || $fk_c_exp_tax_cat < 0) {
1985 $fk_c_exp_tax_cat = 0;
1986 }
1987 if (empty($vatrate) || $vatrate < 0) {
1988 $vatrate = 0;
1989 }
1990 if (empty($date)) {
1991 $date = '';
1992 }
1993 if (empty($fk_project)) {
1994 $fk_project = 0;
1995 }
1996
1997 $qty = (float) price2num($qty);
1998 if (!preg_match('/\s*\‍((.*)\‍)/', $vatrate)) {
1999 $vatrate = price2num($vatrate); // $txtva can have format '5.0 (XXX)' or '5'
2000 }
2001 $up = price2num($up);
2002
2003 $this->db->begin();
2004
2005 $this->line = new ExpenseReportLine($this->db);
2006
2007 // We don't know seller and buyer for expense reports
2008 $seller = $mysoc; // We use same than current company (expense report are often done in same country)
2009 $seller->tva_assuj = 1; // Most seller uses vat
2010 $buyer = new Societe($this->db);
2011
2012 $localtaxes_type = getLocalTaxesFromRate($vatrate, 0, $buyer, $seller);
2013
2014 $vat_src_code = '';
2015 $reg = array();
2016 if (preg_match('/\s*\‍((.*)\‍)/', $vatrate, $reg)) {
2017 $vat_src_code = $reg[1];
2018 $vatrate = preg_replace('/\s*\‍(.*\‍)/', '', $vatrate); // Remove code into vatrate.
2019 }
2020 $vatrate = preg_replace('/\*/', '', $vatrate);
2021
2022 $tmp = calcul_price_total($qty, (float) $up, 0, (float) price2num($vatrate), -1, -1, 0, 'TTC', 0, $type, $seller, $localtaxes_type);
2023
2024 $this->line->value_unit = $up;
2025
2026 $this->line->vat_src_code = $vat_src_code;
2027 $this->line->vatrate = price2num($vatrate);
2028 $this->line->localtax1_tx = $localtaxes_type[1];
2029 $this->line->localtax2_tx = $localtaxes_type[3];
2030 $this->line->localtax1_type = $localtaxes_type[0];
2031 $this->line->localtax2_type = $localtaxes_type[2];
2032
2033 $this->line->total_ttc = (float) $tmp[2];
2034 $this->line->total_ht = (float) $tmp[0];
2035 $this->line->total_tva = (float) $tmp[1];
2036 $this->line->total_localtax1 = (float) $tmp[9];
2037 $this->line->total_localtax2 = (float) $tmp[10];
2038
2039 $this->line->fk_expensereport = $this->id;
2040 $this->line->qty = $qty;
2041 $this->line->date = $date;
2042 $this->line->fk_c_type_fees = $fk_c_type_fees;
2043 $this->line->fk_c_exp_tax_cat = $fk_c_exp_tax_cat;
2044 $this->line->comments = $comments;
2045 $this->line->fk_projet = $fk_project; // deprecated
2046 $this->line->fk_project = $fk_project;
2047
2048 $this->line->fk_ecm_files = $fk_ecm_files;
2049
2050 $this->applyOffset();
2051 $this->checkRules($type, $seller);
2052
2053 $result = $this->line->insert(0, true);
2054 if ($result > 0) {
2055 $result = $this->update_price(1); // This method is designed to add line from user input so total calculation must be done using 'auto' mode.
2056 if ($result > 0) {
2057 $this->db->commit();
2058 return $this->line->id;
2059 } else {
2060 $this->db->rollback();
2061 return -1;
2062 }
2063 } else {
2064 $this->error = $this->line->error;
2065 dol_syslog(get_class($this)."::addline error=".$this->error, LOG_ERR);
2066 $this->db->rollback();
2067 return -2;
2068 }
2069 } else {
2070 dol_syslog(get_class($this)."::addline status of expense report must be Draft to allow use of ->addline()", LOG_ERR);
2071 $this->error = 'ErrorExpenseNotDraftAndNotRefused';
2072 return -3;
2073 }
2074 }
2075
2083 public function checkRules($type = 0, $seller = '')
2084 {
2085 global $conf, $langs, $mysoc;
2086
2087 $langs->load('trips');
2088
2089 // We don't know seller and buyer for expense reports
2090 if (!is_object($seller)) {
2091 $seller = $mysoc; // We use same than current company (expense report are often done in same country)
2092 $seller->tva_assuj = 1; // Most seller uses vat
2093 }
2094
2095 $expensereportrule = new ExpenseReportRule($this->db);
2096 $rulestocheck = $expensereportrule->getAllRule($this->line->fk_c_type_fees, $this->line->date, $this->fk_user_author);
2097
2098 $violation = 0;
2099 $rule_warning_message_tab = array();
2100
2101 $current_total_ttc = $this->line->total_ttc;
2102 $new_current_total_ttc = $this->line->total_ttc;
2103
2104 // check if one is violated
2105 foreach ($rulestocheck as $rule) {
2106 if (in_array($rule->code_expense_rules_type, array('EX_DAY', 'EX_MON', 'EX_YEA'))) {
2107 $amount_to_test = $this->line->getExpAmount($rule, $this->fk_user_author, $rule->code_expense_rules_type);
2108 } else {
2109 $amount_to_test = $current_total_ttc; // EX_EXP
2110 }
2111
2112 $amount_to_test = $amount_to_test - $current_total_ttc + $new_current_total_ttc; // if amount as been modified by a previous rule
2113
2114 if ($amount_to_test > $rule->amount) {
2115 $violation++;
2116
2117 if ($rule->restrictive) {
2118 $this->error = 'ExpenseReportConstraintViolationError';
2119 $this->errors[] = $this->error;
2120
2121 $new_current_total_ttc -= $amount_to_test - $rule->amount; // ex: entered 16 EUR, limit 12 EUR, subtracts 4 EUR
2122 $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));
2123 } else {
2124 $this->error = 'ExpenseReportConstraintViolationWarning';
2125 $this->errors[] = $this->error;
2126
2127 $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));
2128 }
2129
2130 // No break, we should test if another rule is violated
2131 }
2132 }
2133
2134 $this->line->rule_warning_message = implode('\n', $rule_warning_message_tab);
2135
2136 if ($violation > 0) {
2137 $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);
2138
2139 $this->line->value_unit = $tmp[5];
2140 $this->line->total_ttc = (float) $tmp[2];
2141 $this->line->total_ht = (float) $tmp[0];
2142 $this->line->total_tva = (float) $tmp[1];
2143 $this->line->total_localtax1 = (float) $tmp[9];
2144 $this->line->total_localtax2 = (float) $tmp[10];
2145
2146 return false;
2147 } else {
2148 return true;
2149 }
2150 }
2151
2159 public function applyOffset($type = 0, $seller = '')
2160 {
2161 global $mysoc;
2162
2163 if (!getDolGlobalString('MAIN_USE_EXPENSE_IK')) {
2164 return false;
2165 }
2166
2167 $userauthor = new User($this->db);
2168 if ($userauthor->fetch($this->fk_user_author) <= 0) {
2169 $this->error = 'ErrorCantFetchUser';
2170 $this->errors[] = 'ErrorCantFetchUser';
2171 return false;
2172 }
2173
2174 // We don't know seller and buyer for expense reports
2175 if (!is_object($seller)) {
2176 $seller = $mysoc; // We use same than current company (expense report are often done in same country)
2177 $seller->tva_assuj = 1; // Most seller uses vat
2178 }
2179
2180 $expenseik = new ExpenseReportIk($this->db);
2181 $range = $expenseik->getRangeByUser($userauthor, $this->line->fk_c_exp_tax_cat);
2182
2183 if (empty($range)) {
2184 $this->error = 'ErrorNoRangeAvailable';
2185 $this->errors[] = 'ErrorNoRangeAvailable';
2186 return false;
2187 }
2188
2189 if (getDolGlobalString('MAIN_EXPENSE_APPLY_ENTIRE_OFFSET')) {
2190 $ikoffset = $range->ikoffset;
2191 } else {
2192 $ikoffset = $range->ikoffset / 12; // The amount of offset is a global value for the year
2193 }
2194
2195 // Test if ikoffset has been applied for the current month
2196 if (!$this->offsetAlreadyGiven()) {
2197 $new_up = $range->coef + ($ikoffset / $this->line->qty);
2198 $tmp = calcul_price_total($this->line->qty, $new_up, 0, $this->line->vatrate, 0, 0, 0, 'TTC', 0, $type, $seller);
2199
2200 $this->line->value_unit = $tmp[5];
2201 $this->line->total_ttc = (float) $tmp[2];
2202 $this->line->total_ht = (float) $tmp[0];
2203 $this->line->total_tva = (float) $tmp[1];
2204 $this->line->total_localtax1 = (float) $tmp[9];
2205 $this->line->total_localtax2 = (float) $tmp[10];
2206
2207 return true;
2208 }
2209
2210 return false;
2211 }
2212
2218 public function offsetAlreadyGiven()
2219 {
2220 $sql = 'SELECT e.rowid FROM '.MAIN_DB_PREFIX.'expensereport e';
2221 $sql .= " INNER JOIN ".MAIN_DB_PREFIX."expensereport_det d ON (e.rowid = d.fk_expensereport)";
2222 $sql .= " INNER JOIN ".MAIN_DB_PREFIX."c_type_fees f ON (d.fk_c_type_fees = f.id AND f.code = 'EX_KME')";
2223 $sql .= " WHERE e.fk_user_author = ".(int) $this->fk_user_author;
2224 $sql .= " AND YEAR(d.date) = '".dol_print_date($this->line->date, '%Y')."' AND MONTH(d.date) = '".dol_print_date($this->line->date, '%m')."'";
2225 if (!empty($this->line->id)) {
2226 $sql .= ' AND d.rowid <> '.((int) $this->line->id);
2227 }
2228
2229 dol_syslog(get_class($this)."::offsetAlreadyGiven");
2230 $resql = $this->db->query($sql);
2231 if ($resql) {
2232 $num = $this->db->num_rows($resql);
2233 if ($num > 0) {
2234 return true;
2235 }
2236 } else {
2237 dol_print_error($this->db);
2238 }
2239
2240 return false;
2241 }
2242
2260 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)
2261 {
2262 global $user, $mysoc;
2263
2264 if (!$this->isLineOfObject($rowid)) {
2265 $this->error = 'ErrorLineIDDoesNotMatchWithObjectID';
2266 return -1;
2267 }
2268
2269 if ($this->status == self::STATUS_DRAFT || $this->status == self::STATUS_REFUSED) {
2270 $this->db->begin();
2271
2272 $error = 0;
2273 $type = 0; // TODO What if type is service ?
2274
2275 // We don't know seller and buyer for expense reports
2276 $seller = $mysoc; // We use same than current company (expense report are often done in same country)
2277 $seller->tva_assuj = 1; // Most seller uses vat
2278 $seller->localtax1_assuj = $mysoc->localtax1_assuj; // We don't know, we reuse the state of company
2279 $seller->localtax2_assuj = $mysoc->localtax1_assuj; // We don't know, we reuse the state of company
2280 $buyer = new Societe($this->db);
2281
2282 $localtaxes_type = getLocalTaxesFromRate($vatrate, 0, $buyer, $seller);
2283
2284 // Clean vat code
2285 $reg = array();
2286 $vat_src_code = '';
2287 if (preg_match('/\‍((.*)\‍)/', (string) $vatrate, $reg)) {
2288 $vat_src_code = $reg[1];
2289 $vatrate = preg_replace('/\s*\‍(.*\‍)/', '', (string) $vatrate); // Remove code into vatrate.
2290 }
2291 $vatrate = preg_replace('/\*/', '', $vatrate);
2292
2293 $tmp = calcul_price_total($qty, $value_unit, 0, (float) price2num($vatrate), -1, -1, 0, 'TTC', 0, $type, $seller, $localtaxes_type);
2294 // calcul total of line
2295 // $total_ttc = price2num($qty*$value_unit, 'MT');
2296
2297 $tx_tva = 1 + (float) $vatrate / 100;
2298
2299 $this->line = new ExpenseReportLine($this->db);
2300 $this->line->comments = $comments;
2301 $this->line->qty = $qty;
2302 $this->line->value_unit = $value_unit;
2303 $this->line->date = $date;
2304
2305 $this->line->fk_expensereport = $expensereport_id;
2306 $this->line->fk_c_type_fees = $type_fees_id;
2307 $this->line->fk_c_exp_tax_cat = $fk_c_exp_tax_cat;
2308 $this->line->fk_projet = $projet_id; // deprecated
2309 $this->line->fk_project = $projet_id;
2310
2311 $this->line->vat_src_code = $vat_src_code;
2312 $this->line->vatrate = price2num($vatrate);
2313 $this->line->localtax1_tx = $localtaxes_type[1];
2314 $this->line->localtax2_tx = $localtaxes_type[3];
2315 $this->line->localtax1_type = $localtaxes_type[0];
2316 $this->line->localtax2_type = $localtaxes_type[2];
2317
2318 $this->line->total_ttc = (float) $tmp[2];
2319 $this->line->total_ht = (float) $tmp[0];
2320 $this->line->total_tva = (float) $tmp[1];
2321 $this->line->total_localtax1 = (float) $tmp[9];
2322 $this->line->total_localtax2 = (float) $tmp[10];
2323
2324 $this->line->fk_ecm_files = $fk_ecm_files;
2325
2326 $this->line->id = ((int) $rowid);
2327
2328 // Select info about the fee type
2329 $sql = "SELECT c.code as code_type_fees, c.label as label_type_fees";
2330 $sql .= " FROM ".MAIN_DB_PREFIX."c_type_fees as c";
2331 $sql .= " WHERE c.id = ".((int) $type_fees_id);
2332 $resql = $this->db->query($sql);
2333 if ($resql) {
2334 $objp_fees = $this->db->fetch_object($resql);
2335 $this->line->type_fees_code = $objp_fees->code_type_fees;
2336 $this->line->type_fees_libelle = $objp_fees->label_type_fees;
2337 $this->db->free($resql);
2338 }
2339
2340 if ($projet_id > 0) {
2341 // Select information about the project
2342 $sql = "SELECT p.ref as ref_projet, p.title as title_projet";
2343 $sql .= " FROM ".MAIN_DB_PREFIX."projet as p";
2344 $sql .= " WHERE p.rowid = ".((int) $projet_id);
2345 $resql = $this->db->query($sql);
2346 if ($resql) {
2347 if ($this->db->num_rows($resql) > 0) {
2348 $objp_projet = $this->db->fetch_object($resql);
2349 $this->line->projet_ref = $objp_projet->ref_projet;
2350 $this->line->projet_title = $objp_projet->title_projet;
2351 }
2352 $this->db->free($resql);
2353 }
2354 }
2355 $this->applyOffset();
2356 $this->checkRules();
2357
2358 $result = $this->line->update($user, $notrigger);
2359 if ($result < 0) {
2360 $error++;
2361 }
2362
2363 if (!$error) {
2364 $this->db->commit();
2365 return 1;
2366 } else {
2367 $this->error = $this->line->error;
2368 $this->errors = $this->line->errors;
2369 $this->db->rollback();
2370 return -2;
2371 }
2372 }
2373
2374 return 0;
2375 }
2376
2385 public function deleteLine($rowid, $fuser = null, $notrigger = 0)
2386 {
2387 $error = 0;
2388
2389 if ($this->id > 0) {
2390 // The line must belong to this expense report
2391 $sql = "SELECT rowid FROM ".$this->db->prefix().$this->table_element_line;
2392 $sql .= " WHERE rowid = ".((int) $rowid)." AND fk_expensereport = ".((int) $this->id);
2393 $resql = $this->db->query($sql);
2394 if (!$resql || !$this->db->num_rows($resql)) {
2395 $this->error = 'ErrorLineIDDoesNotMatchWithObjectID';
2396 return -1;
2397 }
2398 }
2399
2400 $this->db->begin();
2401
2402 if (!$notrigger) {
2403 // Call triggers
2404 $result = $this->call_trigger('EXPENSE_REPORT_DET_DELETE', $fuser);
2405 if ($result < 0) {
2406 $error++;
2407 }
2408 // End call triggers
2409 }
2410
2411 $sql = ' DELETE FROM '.MAIN_DB_PREFIX.$this->table_element_line;
2412 $sql .= ' WHERE rowid = '.((int) $rowid);
2413
2414 dol_syslog(get_class($this)."::deleteline sql=".$sql);
2415 $result = $this->db->query($sql);
2416
2417 if (!$result || $error > 0) {
2418 $this->error = $this->db->error();
2419 dol_syslog(get_class($this)."::deleteline Error ".$this->error, LOG_ERR);
2420 $this->db->rollback();
2421 return -1;
2422 }
2423
2424 $this->update_price(1);
2425
2426 $this->db->commit();
2427
2428 return 1;
2429 }
2430
2439 public function periodExists(User $fuser, $startDate, $endDate)
2440 {
2441 global $conf;
2442
2443 $sql = "SELECT rowid, date_debut, date_fin";
2444 $sql .= " FROM ".MAIN_DB_PREFIX.$this->table_element;
2445 $sql .= " WHERE entity = ".((int) $conf->entity); // not shared, only for the current entity
2446 $sql .= " AND fk_user_author = ".((int) $fuser->id);
2447 $sql .= " AND (date_fin >= '".$this->db->idate($startDate)."' AND date_debut <= '".$this->db->idate($endDate)."')";
2448
2449 $row = $this->db->getRow($sql);
2450
2451 if ($row === false) {
2452 $this->error = $this->db->lasterror();
2453 dol_syslog(__CLASS__."::". __METHOD__." Error ".$this->error, LOG_ERR);
2454 return -1;
2455 }
2456
2457 return $row->rowid ?? 0;
2458 }
2459
2460
2461 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2469 {
2470 // phpcs:enable
2471 $users_validator = array();
2472
2473 $sql = "SELECT DISTINCT ur.fk_user";
2474 $sql .= " FROM ".MAIN_DB_PREFIX."user_rights as ur, ".MAIN_DB_PREFIX."rights_def as rd";
2475 $sql .= " WHERE ur.fk_id = rd.id and rd.module = 'expensereport' AND rd.perms = 'approve'"; // Permission 'Approve';
2476 $sql .= " UNION";
2477 $sql .= " SELECT DISTINCT ugu.fk_user";
2478 $sql .= " FROM ".MAIN_DB_PREFIX."usergroup_user as ugu, ".MAIN_DB_PREFIX."usergroup_rights as ur, ".MAIN_DB_PREFIX."rights_def as rd";
2479 $sql .= " WHERE ugu.fk_usergroup = ur.fk_usergroup AND ur.fk_id = rd.id and rd.module = 'expensereport' AND rd.perms = 'approve'"; // Permission 'Approve';
2480 //print $sql;
2481
2482 dol_syslog(get_class($this)."::fetch_users_approver_expensereport sql=".$sql);
2483 $result = $this->db->query($sql);
2484 if ($result) {
2485 $num_rows = $this->db->num_rows($result);
2486 $i = 0;
2487 while ($i < $num_rows) {
2488 $objp = $this->db->fetch_object($result);
2489 array_push($users_validator, $objp->fk_user);
2490 $i++;
2491 }
2492 return $users_validator;
2493 } else {
2494 $this->error = $this->db->lasterror();
2495 dol_syslog(get_class($this)."::fetch_users_approver_expensereport Error ".$this->error, LOG_ERR);
2496 return -1;
2497 }
2498 }
2499
2511 public function generateDocument($modele, $outputlangs, $hidedetails = 0, $hidedesc = 0, $hideref = 0, $moreparams = null)
2512 {
2513 $outputlangs->load("trips");
2514
2515 if (!dol_strlen($modele)) {
2516 if (!empty($this->model_pdf)) {
2517 $modele = $this->model_pdf;
2518 } elseif (getDolGlobalString('EXPENSEREPORT_ADDON_PDF')) {
2519 $modele = getDolGlobalString('EXPENSEREPORT_ADDON_PDF');
2520 }
2521 }
2522
2523 if (!empty($modele)) {
2524 $modelpath = "core/modules/expensereport/doc/";
2525
2526 return $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams);
2527 } else {
2528 return 0;
2529 }
2530 }
2531
2538 public function listOfTypes($active = 1)
2539 {
2540 global $langs;
2541 $ret = array();
2542 $sql = "SELECT id, code, label";
2543 $sql .= " FROM ".MAIN_DB_PREFIX."c_type_fees";
2544 $sql .= " WHERE active = ".((int) $active);
2545 dol_syslog(get_class($this)."::listOfTypes", LOG_DEBUG);
2546 $result = $this->db->query($sql);
2547 if ($result) {
2548 $num = $this->db->num_rows($result);
2549 $i = 0;
2550 while ($i < $num) {
2551 $obj = $this->db->fetch_object($result);
2552 $ret[$obj->code] = (($langs->transnoentitiesnoconv($obj->code) != $obj->code) ? $langs->transnoentitiesnoconv($obj->code) : $obj->label);
2553 $i++;
2554 }
2555 } else {
2556 dol_print_error($this->db);
2557 }
2558 return $ret;
2559 }
2560
2566 public function loadStateBoard()
2567 {
2568 global $user;
2569
2570 $this->nb = array();
2571
2572 $sql = "SELECT count(ex.rowid) as nb";
2573 $sql .= " FROM ".MAIN_DB_PREFIX."expensereport as ex";
2574 $sql .= " WHERE ex.fk_statut > 0";
2575 $sql .= " AND ex.entity IN (".getEntity('expensereport').")";
2576 if (!$user->hasRight('expensereport', 'readall')) {
2577 $userchildids = $user->getAllChildIds(1);
2578 $sql .= " AND (ex.fk_user_author IN (".$this->db->sanitize(implode(',', $userchildids)).")";
2579 $sql .= " OR ex.fk_user_validator IN (".$this->db->sanitize(implode(',', $userchildids))."))";
2580 }
2581
2582 $resql = $this->db->query($sql);
2583 if ($resql) {
2584 while ($obj = $this->db->fetch_object($resql)) {
2585 $this->nb["expensereports"] = $obj->nb;
2586 }
2587 $this->db->free($resql);
2588 return 1;
2589 } else {
2590 dol_print_error($this->db);
2591 $this->error = $this->db->error();
2592 return -1;
2593 }
2594 }
2595
2596 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2604 public function load_board($user, $option = 'topay')
2605 {
2606 // phpcs:enable
2607 global $conf, $langs;
2608
2609 if ($user->socid) {
2610 return -1; // Protection to prevent calls by external users
2611 }
2612
2613 $now = dol_now();
2614 $warning_delay = ($option == 'toapprove' ? $conf->expensereport->approve->warning_delay : $conf->expensereport->payment->warning_delay);
2615
2616 // The count and the number of late expense reports are computed by the database instead of reading every report. A
2617 // report is late when its validation date is before now minus the warning delay (a report without validation date was
2618 // counted as late, this is kept).
2619 $sql = "SELECT COUNT(ex.rowid) as nb,";
2620 $sql .= " SUM(CASE WHEN ex.date_valid IS NULL OR ex.date_valid < '".$this->db->idate($now - $warning_delay)."' THEN 1 ELSE 0 END) as nblate";
2621 $sql .= " FROM ".MAIN_DB_PREFIX."expensereport as ex";
2622 if ($option == 'toapprove') {
2623 $sql .= " WHERE ex.fk_statut = ".self::STATUS_VALIDATED;
2624 } else {
2625 $sql .= " WHERE ex.fk_statut = ".self::STATUS_APPROVED;
2626 }
2627 $sql .= " AND ex.entity IN (".getEntity('expensereport').")";
2628 if (!$user->hasRight('expensereport', 'readall')) {
2629 $userchildids = $user->getAllChildIds(1);
2630 $sql .= " AND (ex.fk_user_author IN (".$this->db->sanitize(implode(',', $userchildids)).")";
2631 $sql .= " OR ex.fk_user_validator IN (".$this->db->sanitize(implode(',', $userchildids))."))";
2632 }
2633
2634 $resql = $this->db->query($sql);
2635 if ($resql) {
2636 $langs->load("trips");
2637
2638 $response = new WorkboardResponse();
2639 if ($option == 'toapprove') {
2640 $response->warning_delay = $conf->expensereport->approve->warning_delay / 60 / 60 / 24;
2641 $response->label = $langs->trans("ExpenseReportsToApprove");
2642 $response->labelShort = $langs->trans("ToApprove");
2643 $response->url = dolBuildUrl(DOL_URL_ROOT.'/expensereport/list.php', ['mainmenu' => 'hrm', 'statut' => self::STATUS_VALIDATED]);
2644 } else {
2645 $response->warning_delay = $conf->expensereport->payment->warning_delay / 60 / 60 / 24;
2646 $response->label = $langs->trans("ExpenseReportsToPay");
2647 $response->labelShort = $langs->trans("StatusToPay");
2648 $response->url = dolBuildUrl(DOL_URL_ROOT.'/expensereport/list.php', ['mainmenu' => 'hrm', 'statut' => self::STATUS_APPROVED]);
2649 }
2650 $response->img = img_object('', "trip");
2651
2652 $obj = $this->db->fetch_object($resql);
2653 if ($obj) {
2654 $response->nbtodo = (int) $obj->nb;
2655 $response->nbtodolate = (int) $obj->nblate;
2656 }
2657
2658 return $response;
2659 } else {
2660 dol_print_error($this->db);
2661 $this->error = $this->db->error();
2662 return -1;
2663 }
2664 }
2665
2672 public function hasDelay($option)
2673 {
2674 global $conf;
2675
2676 // Only valid expenses reports
2677 if ($option == 'toapprove' && $this->status != 2) {
2678 return false;
2679 }
2680 if ($option == 'topay' && $this->status != 5) {
2681 return false;
2682 }
2683
2684 $now = dol_now();
2685 $warning_delay = (int) ($option == 'toapprove' ? $conf->expensereport->approve->warning_delay : $conf->expensereport->payment->warning_delay);
2686 if ($warning_delay <= 0) {
2687 // No delay configured (MAIN_DELAY_EXPENSEREPORTS / MIN_DELAY_EXPENSEREPORTS_TO_PAY not set), so nothing is late.
2688 return false;
2689 }
2690 return (!empty($this->datevalid) ? $this->datevalid : $this->date_valid) < ($now - $warning_delay);
2691 }
2692
2699 public function getVentilExportCompta($mode = 0)
2700 {
2701 $alreadydispatched = 0;
2702
2703 $type = 'expense_report';
2704
2705 $sql = " SELECT ".($mode ? 'DISTINCT piece_num' : 'COUNT(ab.rowid)')." as nb";
2706 $sql .= " FROM ".MAIN_DB_PREFIX."accounting_bookkeeping as ab WHERE ab.doc_type = '".$this->db->escape($type)."' AND ab.fk_doc = ".((int) $this->id);
2707 $resql = $this->db->query($sql);
2708 if ($resql) {
2709 $obj = $this->db->fetch_object($resql);
2710 if ($obj) {
2711 $alreadydispatched = $obj->nb;
2712 }
2713 } else {
2714 $this->error = $this->db->lasterror();
2715 return -1;
2716 }
2717
2718 if ($alreadydispatched) {
2719 return $alreadydispatched;
2720 }
2721 return 0;
2722 }
2723
2729 public function getSumPayments()
2730 {
2731 $table = 'payment_expensereport';
2732 $field = 'fk_expensereport';
2733
2734 $sql = 'SELECT sum(amount) as amount';
2735 $sql .= ' FROM '.MAIN_DB_PREFIX.$table;
2736 $sql .= " WHERE ".$this->db->sanitize($field)." = ".((int) $this->id);
2737
2738 dol_syslog(get_class($this)."::getSumPayments", LOG_DEBUG);
2739 $resql = $this->db->query($sql);
2740 if ($resql) {
2741 $obj = $this->db->fetch_object($resql);
2742 $this->db->free($resql);
2743 return (empty($obj->amount) ? 0 : $obj->amount);
2744 } else {
2745 $this->error = $this->db->lasterror();
2746 return -1;
2747 }
2748 }
2749
2758 public function computeTotalKm($fk_cat, $qty, $tva)
2759 {
2760 global $langs, $conf;
2761
2762 $cumulYearQty = 0;
2763 $ranges = array();
2764 $coef = 0;
2765
2766
2767 if ($fk_cat < 0) {
2768 $this->error = $langs->trans('ErrorBadParameterCat');
2769 return -1;
2770 }
2771
2772 if ($qty <= 0) {
2773 $this->error = $langs->trans('ErrorBadParameterQty');
2774 return -1;
2775 }
2776
2777 $currentUser = new User($this->db);
2778 $currentUser->fetch($this->fk_user);
2779 $currentUser->loadRights('expensereport');
2780 //Clean
2781 $qty = (float) price2num($qty);
2782
2783 $sql = " SELECT r.range_ik, t.ikoffset, t.coef";
2784 $sql .= " FROM ".MAIN_DB_PREFIX."expensereport_ik t";
2785 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."c_exp_tax_range r ON r.rowid = t.fk_range";
2786 $sql .= " WHERE t.fk_c_exp_tax_cat = ".(int) $fk_cat;
2787 $sql .= " ORDER BY r.range_ik ASC";
2788
2789 dol_syslog("expenseReport::computeTotalkm sql=".$sql, LOG_DEBUG);
2790
2791 $result = $this->db->query($sql);
2792
2793 if ($result) {
2794 if (getDolGlobalInt('EXPENSEREPORT_CALCULATE_MILEAGE_EXPENSE_COEFFICIENT_ON_CURRENT_YEAR')) {
2795 $arrayDate = dol_getdate(dol_now());
2796 $sql = " SELECT count(n.qty) as cumul FROM ".MAIN_DB_PREFIX."expensereport_det n";
2797 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."expensereport e ON e.rowid = n.fk_expensereport";
2798 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."c_type_fees tf ON tf.id = n.fk_c_type_fees";
2799 $sql .= " WHERE e.fk_user_author = ".(int) $this->fk_user_author;
2800 $sql .= " AND YEAR(n.date) = ".(int) $arrayDate['year'];
2801 $sql .= " AND tf.code = 'EX_KME' ";
2802 $sql .= " AND e.fk_statut = ".(int) ExpenseReport::STATUS_VALIDATED;
2803
2804 $resql = $this->db->query($sql);
2805
2806 if ($resql) {
2807 $obj = $this->db->fetch_object($resql);
2808 $cumulYearQty = $obj->cumul;
2809 }
2810
2811 $qty += (float) $cumulYearQty;
2812 }
2813
2814 $num = $this->db->num_rows($result);
2815
2816 if ($num) {
2817 for ($i = 0; $i < $num; $i++) {
2818 $obj = $this->db->fetch_object($result);
2819
2820 $ranges[$i] = $obj;
2821 }
2822 '@phan-var-force Object[] $ranges';
2823
2824 for ($i = 0; $i < $num; $i++) {
2825 if ($i < ($num - 1)) {
2826 // @phan-suppress-next-line PhanTypeInvalidDimOffset
2827 if ($qty > $ranges[$i]->range_ik && $qty < $ranges[$i + 1]->range_ik) {
2828 $coef = $ranges[$i]->coef;
2829 $offset = $ranges[$i]->ikoffset;
2830 }
2831 } else {
2832 if ($qty > $ranges[$i]->range_ik) {
2833 $coef = $ranges[$i]->coef;
2834 $offset = $ranges[$i]->ikoffset;
2835 }
2836 }
2837 }
2838 $total_ht = $coef;
2839 return $total_ht;
2840 } else {
2841 $this->error = $langs->trans('TaxUndefinedForThisCategory');
2842 return 0;
2843 }
2844 } else {
2845 $this->error = $this->db->error()." sql=".$sql;
2846
2847 return -1;
2848 }
2849 }
2850
2858 public function getKanbanView($option = '', $arraydata = null)
2859 {
2860 global $langs;
2861
2862 if ($arraydata === null) {
2863 $arraydata = array();
2864 }
2865
2866 $selected = (empty($arraydata['selected']) ? 0 : $arraydata['selected']);
2867
2868 $return = '<div class="box-flex-item box-flex-grow-zero">';
2869 $return .= '<div class="info-box info-box-sm">';
2870 $return .= '<span class="info-box-icon bg-infobox-action">';
2871 $return .= img_picto('', $this->picto);
2872 $return .= '</span>';
2873 $return .= '<div class="info-box-content">';
2874 $return .= '<span class="info-box-ref inline-block tdoverflowmax150 valignmiddle">' . $this->getNomUrl(1) . '</span>';
2875 if ($selected >= 0) {
2876 $return .= '<input id="cb'.$this->id.'" class="flat checkforselect fright" type="checkbox" name="toselect[]" value="'.$this->id.'"'.($selected ? ' checked="checked"' : '').'>';
2877 }
2878 if (array_key_exists('userauthor', $arraydata) && $arraydata['userauthor'] instanceof User) {
2879 $return .= '<br><span class="info-box-label">'.$arraydata['userauthor']->getNomUrl(-1).'</span>';
2880 }
2881 if (isDolTms($this->date_debut) || isDolTms($this->date_fin)) {
2882 $return .= '<br><span class="info-box-label">'.dol_print_date($this->date_debut, 'day').'</span>';
2883 $return .= ' <span class="opacitymedium">'.$langs->trans("To").'</span> ';
2884 $return .= '<span class="info-box-label">'.dol_print_date($this->date_fin, 'day').'</span>';
2885 }
2886 $return .= '<br><div class="info-box-status">'.$this->getLibStatut(3).'</div>';
2887 $return .= '</div>';
2888 $return .= '</div>';
2889 $return .= '</div>';
2890 return $return;
2891 }
2892}
$object ref
Definition info.php:90
fetch_optionals($rowid=null, $optionsArray=null)
Function to get extra fields of an object into $this->array_options This method is in most cases call...
deleteEcmFiles($mode=0)
Delete related files of object in database.
update_price($exclspec=0, $roundingadjust='auto', $nodatabaseupdate=0, $seller=null)
Update total_ht, total_ttc, total_vat, total_localtax1, total_localtax2 for an object (sum of lines).
commonGenerateDocument($modelspath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams=null)
Common function for all objects extending CommonObject for generating documents.
deleteObjectLinked($sourceid=null, $sourcetype='', $targetid=null, $targettype='', $rowid=0, $f_user=null, $notrigger=0)
Delete all links between an object $this.
setErrorsFromObject($object)
setErrorsFromObject
isLineOfObject($lineid)
Check that a line belongs to this object, using $this->table_element_line and $this->fk_element.
deleteExtraFields()
Delete all extra fields values for the current object.
insertExtraFields($trigger='', $userused=null)
Add/Update all extra fields values for the current object.
delete_linked_contact($source='', $code='')
Delete all links between an object $this and all its contacts in llx_element_contact.
Class to manage Trips and Expenses.
setPaid($id, $fuser, $notrigger=0)
Classify the expense report as paid.
loadStateBoard()
Load the indicators this->nb for the state board.
__construct($db)
Constructor.
checkRules($type=0, $seller='')
Check constraint of rules and update price if needed.
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)
Update an expense report line.
getNextNumRef()
Return next reference of expense report not already used.
createFromClone(User $user, $fk_user_author)
Load an object from its id and create a new one in database.
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)
Add expense report line.
const STATUS_DRAFT
Draft status.
computeTotalKm($fk_cat, $qty, $tva)
Compute the cost of the kilometers expense based on the number of kilometers and the vehicle category...
offsetAlreadyGiven()
If the sql find any rows then the ikoffset is already given (ikoffset is applied at the first expense...
listOfTypes($active=1)
List of types.
const STATUS_APPROVED
Classified approved.
set_save_from_refuse($fuser)
set_save_from_refuse
setValidate($fuser, $notrigger=0)
Set to status validate.
getSumPayments()
Return amount of payments already done.
getLibStatut($mode=0)
Returns the label status.
deleteLine($rowid, $fuser=null, $notrigger=0)
deleteline
set_cancel($fuser, $detail, $notrigger=0)
set_cancel
getNomUrl($withpicto=0, $option='', $max=0, $short=0, $moretitle='', $notooltip=0, $save_lastsearch_value=-1)
Return clickable name (with picto eventually)
set_unpaid($fuser, $notrigger=0)
set_unpaid
getVentilExportCompta($mode=0)
Return if object was dispatched into bookkeeping.
info($id)
Load information on object.
getTooltipContentArray($params)
getTooltipContentArray
hasDelay($option)
Return if an expense report is late or not.
applyOffset($type=0, $seller='')
Method to apply the offset if needed.
const STATUS_CANCELED
Classified canceled.
const STATUS_CLOSED
Classified paid.
const STATUS_REFUSED
Classified refused.
periodExists(User $fuser, $startDate, $endDate)
periodExists
setDeny($fuser, $details, $notrigger=0)
setDeny
getKanbanView($option='', $arraydata=null)
Return clickable link of object (with optional picto)
update($user, $notrigger=0, $userofexpensereport=null)
update
const STATUS_VALIDATED
Validated (need to be paid)
create($user, $notrigger=0)
Create object in database.
load_board($user, $option='topay')
Load indicators for dashboard (this->nbtodo and this->nbtodolate)
set_paid($id, $fuser, $notrigger=0)
Classify the expense report as paid.
getSummary()
Return a short summary of the public note.
generateDocument($modele, $outputlangs, $hidedetails=0, $hidedesc=0, $hideref=0, $moreparams=null)
Create a document onto disk according to template module.
initAsSpecimen()
Initialise an instance with random values.
fetch_users_approver_expensereport()
Return list of people with permission to validate expense reports.
setApproved($fuser, $notrigger=0)
Set status to approved.
LibStatut($status, $mode=0)
Returns the label of a status.
setUnpaid($fuser, $notrigger=0)
set_unpaid
fetch_line_by_project($projectid, $user)
fetch_line_by_project
update_totaux_add($ligne_total_ht, $ligne_total_tva)
Update total of an expense report when you add a line.
Class to manage inventories.
Class of expense report details lines.
Class to manage inventories.
Class to manage third parties objects (customers, suppliers, prospects...)
Class to manage Dolibarr users.
print $langs trans("Ref").' m titre as m m statut as status
Or an array listing all the potential status of the object: array: int of the status => translated la...
Definition index.php:169
global $mysoc
if(!isModEnabled('ai')||!getDolGlobalString('AI_ASSISTANT_ENABLED')) global $conf
The main.inc.php has been included so the following variable are now defined:
dol_delete_file($file, $disableglob=0, $nophperrors=0, $nohook=0, $object=null, $allowdotdot=false, $indexdatabase=1, $nolog=0)
Remove a file or several files with a mask.
dol_delete_dir_recursive($dir, $count=0, $nophperrors=0, $onlysub=0, &$countdeleted=0, $indexdatabase=1, $nolog=0, $level=0)
Remove a directory $dir and its subdirectories (or only files and subdirectories)
dol_dir_list($utf8_path, $types="all", $recursive=0, $filter="", $excludefilter=null, $sortcriteria="name", $sortorder=SORT_ASC, $mode=0, $nohook=0, $relativename="", $donotfollowsymlinks=0, $nbsecondsold=0)
Scan a directory and return a list of files/directories.
Definition files.lib.php:65
dol_delete_preview($object)
Delete all preview files linked to object instance.
dol_now($mode='gmt')
Return date for now.
dolGetFirstLineOfText($text, $nboflines=1, $charset='UTF-8')
Return first line of text.
price2num($amount, $rounding='', $option=0)
Function that return a number with universal decimal format (decimal separator is '.
dolBuildUrl($url, $params=[], $addtoken=false, $anchor='')
Return path of url.
isDolTms($timestamp)
isDolTms check if a timestamp is valid.
dol_sanitizeFileName($str, $newstr='_', $unaccent=1, $includequotes=0, $allowdash=0)
Clean a string to use it as a file name.
dol_strlen($string, $stringencoding='UTF-8')
Make a strlen call.
price($amount, $form=0, $outlangs='', $trunc=1, $rounding=-1, $forcerounding=-1, $currency_code='')
Function to format a value into an amount for visual output Function used into PDF and HTML pages.
getDolGlobalInt($key, $default=0)
Return a Dolibarr global constant int value.
getLocalTaxesFromRate($vatrate, $local, $buyer, $seller, $firstparamisid=0)
Get type and rate of localtaxes for a particular vat rate/country of a thirdparty.
dolGetFirstLastname($firstname, $lastname, $nameorder=-1)
Return firstname and lastname in correct order.
GETPOSTINT($paramname, $method=0, $nodefault=0)
Return the value of a $_GET or $_POST supervariable, converted into integer.
dol_buildpath($path, $type=0, $returnemptyifnotfound=0)
Return path of url or filesystem.
dol_print_date($time, $format='', $tzoutput='auto', $outputlangs=null, $encodetooutput=false, $decorate=0)
Output date in a string format according to outputlangs (or langs if not defined).
dol_trunc($string, $size=40, $trunc='right', $stringencoding='UTF-8', $nodot=0, $display=0)
Truncate a string to a particular length adding '...' if string larger than length.
getDolGlobalString($key, $default='')
Return a Dolibarr global constant string value.
dol_syslog($message, $level=LOG_INFO, $ident=0, $suffixinfilename='', $restricttologhandler='', $logcontext=null)
Write log message into outputs.
dol_getdate($timestamp, $fast=false, $forcetimezone='')
Return an array with locale date info.
calcul_price_total($qty, $pu, $remise_percent_ligne, $txtva, $uselocaltax1_rate, $uselocaltax2_rate, $notused, $price_base_type, $info_bits, $type, $seller=null, $localtaxes_array=[], $progress=100, $multicurrency_tx=1, $pu_devise=0, $multicurrency_code='')
Calculate totals (net, vat, ...) of a line.
Definition price.lib.php:90