dolibarr 24.0.0-beta
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-2025 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' => 'Fk user validator', 'enabled' => 1, 'visible' => -1, 'position' => 115),
321 'fk_user_approve' => array('type' => 'integer:User:user/class/user.class.php', 'label' => 'Fk user approve', 'enabled' => 1, 'visible' => -1, 'position' => 120),
322 'fk_user_refuse' => array('type' => 'integer:User:user/class/user.class.php', 'label' => 'Fk user refuse', 'enabled' => 1, 'visible' => -1, 'position' => 125),
323 'fk_user_cancel' => array('type' => 'integer:User:user/class/user.class.php', 'label' => 'Fk user cancel', 'enabled' => 1, 'visible' => -1, 'position' => 130),
324 'fk_c_paiement' => array('type' => 'integer', 'label' => 'Fk c paiement', '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' => 'Fk bank account', 'enabled' => 1, 'visible' => -1, 'position' => 175),
332 'fk_multicurrency' => array('type' => 'integer', 'label' => 'Fk multicurrency', '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
878 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
886 public function LibStatut($status, $mode = 0)
887 {
888 // phpcs:enable
889 global $langs;
890
891 $labelStatus = $langs->transnoentitiesnoconv($this->labelStatus[$status]);
892 $labelStatusShort = $langs->transnoentitiesnoconv($this->labelStatusShort[$status]);
893
894 $statuslogo = array(0 => 'status0', 2 => 'status1', 4 => 'status6', 5 => 'status4', 6 => 'status6', 99 => 'status5');
895
896 $statusType = $statuslogo[$status];
897
898 return dolGetStatus($labelStatus, $labelStatusShort, '', $statusType, $mode);
899 }
900
901
908 public function info($id)
909 {
910 global $conf;
911
912 $sql = "SELECT f.rowid,";
913 $sql .= " f.date_create as datec,";
914 $sql .= " f.tms as date_modification,";
915 $sql .= " f.date_valid as datev,";
916 $sql .= " f.date_approve as datea,";
917 $sql .= " f.fk_user_creat as fk_user_creation,";
918 $sql .= " f.fk_user_author as fk_user_author,";
919 $sql .= " f.fk_user_modif as fk_user_modification,";
920 $sql .= " f.fk_user_valid,";
921 $sql .= " f.fk_user_approve";
922 $sql .= " FROM ".MAIN_DB_PREFIX."expensereport as f";
923 $sql .= " WHERE f.rowid = ".((int) $id);
924 $sql .= " AND f.entity = ".$conf->entity;
925
926
927
928 $resql = $this->db->query($sql);
929 if ($resql) {
930 if ($this->db->num_rows($resql)) {
931 $obj = $this->db->fetch_object($resql);
932
933 $this->id = $obj->rowid;
934
935 $this->date_creation = $this->db->jdate($obj->datec);
936 $this->date_modification = $this->db->jdate($obj->date_modification);
937 $this->date_validation = $this->db->jdate($obj->datev);
938 $this->date_approbation = $this->db->jdate($obj->datea);
939
940 $this->user_creation_id = $obj->fk_user_author;
941 $this->user_creation_id = $obj->fk_user_creation;
942 $this->user_validation_id = $obj->fk_user_valid;
943 $this->user_modification_id = $obj->fk_user_modification;
944 $this->user_approve_id = $obj->fk_user_approve;
945 }
946 $this->db->free($resql);
947 } else {
948 dol_print_error($this->db);
949 }
950 }
951
952
953
961 public function initAsSpecimen()
962 {
963 global $user, $langs;
964
965 $now = dol_now();
966
967 // Initialise parameters
968 $this->id = 0;
969 $this->ref = 'SPECIMEN';
970 $this->specimen = 1;
971 $this->entity = 1;
972 $this->date_create = $now;
973 $this->date_debut = $now;
974 $this->date_fin = $now;
975 $this->date_valid = $now;
976 $this->date_approve = $now;
977
978 $type_fees_id = 2; // TF_TRIP
979
980 $this->status = 5;
981
982 $this->fk_user_author = $user->id;
983 $this->fk_user_validator = $user->id;
984 $this->fk_user_valid = $user->id;
985 $this->fk_user_approve = $user->id;
986
987 $this->note_private = 'Private note';
988 $this->note_public = 'SPECIMEN';
989 $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)
990 $xnbp = 0;
991 while ($xnbp < $nbp) {
992 $line = new ExpenseReportLine($this->db);
993 $line->comments = $langs->trans("Comment")." ".$xnbp;
994 $line->date = ($now - 3600 * (1 + $xnbp));
995 $line->total_ht = 100;
996 $line->total_tva = 20;
997 $line->total_ttc = 120;
998 $line->qty = 1;
999 $line->vatrate = 20;
1000 $line->value_unit = 120;
1001 $line->fk_expensereport = 0;
1002 $line->type_fees_code = 'TRA';
1003 $line->fk_c_type_fees = $type_fees_id;
1004
1005 $line->projet_ref = 'ABC';
1006
1007 $this->lines[$xnbp] = $line;
1008 $xnbp++;
1009
1010 $this->total_ht += $line->total_ht;
1011 $this->total_tva += $line->total_tva;
1012 $this->total_ttc += $line->total_ttc;
1013 }
1014
1015 return 1;
1016 }
1017
1018 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1026 public function fetch_line_by_project($projectid, $user)
1027 {
1028 // phpcs:enable
1029 global $langs;
1030
1031 $langs->load('trips');
1032
1033 if ($user->hasRight('expensereport', 'lire')) {
1034 $sql = "SELECT de.fk_expensereport, de.date, de.comments, de.total_ht, de.total_ttc";
1035 $sql .= " FROM ".MAIN_DB_PREFIX."expensereport_det as de";
1036 $sql .= " WHERE de.fk_projet = ".((int) $projectid);
1037
1038 dol_syslog(get_class($this)."::fetch", LOG_DEBUG);
1039 $result = $this->db->query($sql);
1040 if ($result) {
1041 $num = $this->db->num_rows($result);
1042 $i = 0;
1043 $total_HT = 0;
1044 $total_TTC = 0;
1045
1046 while ($i < $num) {
1047 $objp = $this->db->fetch_object($result);
1048
1049 $sql2 = "SELECT d.rowid, d.fk_user_author, d.ref, d.fk_statut as status";
1050 $sql2 .= " FROM ".MAIN_DB_PREFIX."expensereport as d";
1051 $sql2 .= " WHERE d.rowid = ".((int) $objp->fk_expensereport);
1052
1053 $result2 = $this->db->query($sql2);
1054 $obj = $this->db->fetch_object($result2);
1055
1056 $objp->fk_user_author = $obj->fk_user_author;
1057 $objp->ref = $obj->ref;
1058 $objp->fk_c_expensereport_status = $obj->status;
1059 $objp->rowid = $obj->rowid;
1060
1061 $total_HT += $objp->total_ht;
1062 $total_TTC += $objp->total_ttc;
1063 $author = new User($this->db);
1064 $author->fetch($objp->fk_user_author);
1065
1066 print '<tr>';
1067 print '<td>';
1068 print '<a href="'.dolBuildUrl(DOL_URL_ROOT.'/expensereport/card.php', ['id' => $objp->rowid]).'">'.$objp->ref_num.'</a>';
1069 print '</td>';
1070 print '<td class="center">'.dol_print_date($objp->date, 'day').'</td>';
1071 print '<td>'.$author->getNomUrl(1).'</td>';
1072 print '<td>'.$objp->comments.'</td>';
1073 print '<td class="right">'.price($objp->total_ht).'</td>';
1074 print '<td class="right">'.price($objp->total_ttc).'</td>';
1075 print '<td class="right">';
1076
1077 switch ($objp->fk_c_expensereport_status) {
1078 case 4:
1079 print img_picto($langs->trans('StatusOrderCanceled'), 'statut5');
1080 break;
1081 case 1:
1082 print $langs->trans('Draft').' '.img_picto($langs->trans('Draft'), 'statut0');
1083 break;
1084 case 2:
1085 print $langs->trans('TripForValid').' '.img_picto($langs->trans('TripForValid'), 'statut3');
1086 break;
1087 case 5:
1088 print $langs->trans('TripForPaid').' '.img_picto($langs->trans('TripForPaid'), 'statut3');
1089 break;
1090 case 6:
1091 print $langs->trans('TripPaid').' '.img_picto($langs->trans('TripPaid'), 'statut4');
1092 break;
1093 }
1094 /*
1095 if ($status==4) return img_picto($langs->trans('StatusOrderCanceled'),'statut5');
1096 if ($status==1) return img_picto($langs->trans('StatusOrderDraft'),'statut0');
1097 if ($status==2) return img_picto($langs->trans('StatusOrderValidated'),'statut1');
1098 if ($status==2) return img_picto($langs->trans('StatusOrderOnProcess'),'statut3');
1099 if ($status==5) return img_picto($langs->trans('StatusOrderToBill'),'statut4');
1100 if ($status==6) return img_picto($langs->trans('StatusOrderOnProcess'),'statut6');
1101 */
1102 print '</td>';
1103 print '</tr>';
1104
1105 $i++;
1106 }
1107
1108 print '<tr class="liste_total"><td colspan="4">'.$langs->trans("Number").': '.$i.'</td>';
1109 print '<td class="right" width="100">'.$langs->trans("TotalHT").' : '.price($total_HT).'</td>';
1110 print '<td class="right" width="100">'.$langs->trans("TotalTTC").' : '.price($total_TTC).'</td>';
1111 print '<td>&nbsp;</td>';
1112 print '</tr>';
1113 } else {
1114 $this->error = $this->db->lasterror();
1115 return -1;
1116 }
1117 }
1118
1119 return 0;
1120 }
1121
1122 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1128 public function fetch_lines()
1129 {
1130 // phpcs:enable
1131 $this->lines = array();
1132
1133 $sql = ' SELECT de.rowid, de.comments, de.qty, de.value_unit, de.date, de.rang,';
1134 $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,";
1135 $sql .= ' de.tva_tx, de.vat_src_code,';
1136 $sql .= ' de.localtax1_tx, de.localtax2_tx, de.localtax1_type, de.localtax2_type,';
1137 $sql .= ' de.fk_ecm_files,';
1138 $sql .= ' de.total_ht, de.total_tva, de.total_ttc,';
1139 $sql .= ' de.total_localtax1, de.total_localtax2, de.rule_warning_message,';
1140 $sql .= ' ctf.code as code_type_fees, ctf.label as label_type_fees, ctf.accountancy_code as accountancy_code_type_fees,';
1141 $sql .= ' p.ref as ref_projet, p.title as title_projet';
1142 $sql .= ' FROM '.MAIN_DB_PREFIX.$this->db->sanitize($this->table_element_line).' as de';
1143 $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_type_fees as ctf ON de.fk_c_type_fees = ctf.id';
1144 $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'projet as p ON de.fk_projet = p.rowid';
1145 $sql .= " WHERE de.".$this->db->sanitize($this->fk_element)." = ".((int) $this->id);
1146 if (getDolGlobalString('EXPENSEREPORT_LINES_SORTED_BY_ROWID')) {
1147 $sql .= ' ORDER BY de.rang ASC, de.rowid ASC';
1148 } else {
1149 $sql .= ' ORDER BY de.rang ASC, de.date ASC';
1150 }
1151
1152 $resql = $this->db->query($sql);
1153 if ($resql) {
1154 $num = $this->db->num_rows($resql);
1155 $i = 0;
1156 while ($i < $num) {
1157 $objp = $this->db->fetch_object($resql);
1158
1159 $deplig = new ExpenseReportLine($this->db);
1160
1161 $deplig->rowid = $objp->rowid;
1162 $deplig->id = $objp->rowid;
1163 $deplig->comments = $objp->comments;
1164 $deplig->qty = $objp->qty;
1165 $deplig->value_unit = $objp->value_unit;
1166 $deplig->date = $objp->date;
1167 $deplig->dates = $this->db->jdate($objp->date);
1168
1169 $deplig->fk_expensereport = $objp->fk_expensereport;
1170 $deplig->fk_c_type_fees = $objp->fk_c_type_fees;
1171 $deplig->fk_c_exp_tax_cat = $objp->fk_c_exp_tax_cat;
1172 $deplig->fk_projet = $objp->fk_project; // deprecated
1173 $deplig->fk_project = $objp->fk_project;
1174 $deplig->fk_ecm_files = $objp->fk_ecm_files;
1175
1176 $deplig->total_ht = $objp->total_ht;
1177 $deplig->total_tva = $objp->total_tva;
1178 $deplig->total_ttc = $objp->total_ttc;
1179 $deplig->total_localtax1 = $objp->total_localtax1;
1180 $deplig->total_localtax2 = $objp->total_localtax2;
1181
1182 $deplig->type_fees_code = empty($objp->code_type_fees) ? 'TF_OTHER' : $objp->code_type_fees;
1183 $deplig->type_fees_libelle = $objp->label_type_fees;
1184 $deplig->type_fees_accountancy_code = $objp->accountancy_code_type_fees;
1185
1186 $deplig->tva_tx = $objp->tva_tx;
1187 $deplig->vatrate = $objp->tva_tx;
1188 $deplig->vat_src_code = $objp->vat_src_code;
1189 $deplig->localtax1_tx = $objp->localtax1_tx;
1190 $deplig->localtax2_tx = $objp->localtax2_tx;
1191 $deplig->localtax1_type = $objp->localtax1_type;
1192 $deplig->localtax2_type = $objp->localtax2_type;
1193
1194 $deplig->projet_ref = $objp->ref_projet;
1195 $deplig->projet_title = $objp->title_projet;
1196
1197 $deplig->rule_warning_message = $objp->rule_warning_message;
1198
1199 $deplig->rang = $objp->rang;
1200
1201 $this->lines[$i] = $deplig;
1202
1203 $i++;
1204 }
1205 $this->db->free($resql);
1206 return 1;
1207 } else {
1208 $this->error = $this->db->lasterror();
1209 dol_syslog(get_class($this)."::fetch_lines: Error ".$this->error, LOG_ERR);
1210 return -3;
1211 }
1212 }
1213
1214
1222 public function delete($user = null, $notrigger = 0)
1223 {
1224 global $conf;
1225 require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
1226
1227 $error = 0;
1228
1229 $this->db->begin();
1230
1231 if (!$notrigger) {
1232 // Call trigger
1233 $result = $this->call_trigger('EXPENSE_REPORT_DELETE', $user);
1234 if ($result < 0) {
1235 $error++;
1236 }
1237 // End call triggers
1238 }
1239
1240 // Delete extrafields of lines and lines
1241 if (!$error && !empty($this->table_element_line)) {
1242 $tabletodelete = $this->table_element_line;
1243 //$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).")";
1244 $sql = "DELETE FROM ".MAIN_DB_PREFIX.$tabletodelete." WHERE ".$this->fk_element." = ".((int) $this->id);
1245 if (!$this->db->query($sql)) {
1246 $error++;
1247 $this->error = $this->db->lasterror();
1248 $this->errors[] = $this->error;
1249 dol_syslog(get_class($this)."::delete error ".$this->error, LOG_ERR);
1250 }
1251 }
1252
1253 if (!$error) {
1254 // Delete linked object
1255 $res = $this->deleteObjectLinked();
1256 if ($res < 0) {
1257 $error++;
1258 }
1259 }
1260
1261 if (!$error) {
1262 // Delete linked contacts
1263 $res = $this->delete_linked_contact();
1264 if ($res < 0) {
1265 $error++;
1266 }
1267 }
1268
1269 // Removed extrafields of object
1270 if (!$error) {
1271 $result = $this->deleteExtraFields();
1272 if ($result < 0) {
1273 $error++;
1274 dol_syslog(get_class($this)."::delete error ".$this->error, LOG_ERR);
1275 }
1276 }
1277
1278 // Delete main record
1279 if (!$error) {
1280 $sql = "DELETE FROM ".MAIN_DB_PREFIX.$this->table_element." WHERE rowid = ".((int) $this->id);
1281 $res = $this->db->query($sql);
1282 if (!$res) {
1283 $error++;
1284 $this->error = $this->db->lasterror();
1285 $this->errors[] = $this->error;
1286 dol_syslog(get_class($this)."::delete error ".$this->error, LOG_ERR);
1287 }
1288 }
1289
1290 // Delete record into ECM index and physically
1291 if (!$error) {
1292 $res = $this->deleteEcmFiles(0); // Deleting files physically is done later with the dol_delete_dir_recursive
1293 $res = $this->deleteEcmFiles(1); // Deleting files physically is done later with the dol_delete_dir_recursive
1294 if (!$res) {
1295 $error++;
1296 }
1297 }
1298
1299 if (!$error) {
1300 // We remove directory
1301 $ref = dol_sanitizeFileName($this->ref);
1302 if ($conf->expensereport->multidir_output[$this->entity] && !empty($this->ref)) {
1303 $dir = $conf->expensereport->multidir_output[$this->entity]."/".$ref;
1304 $file = $dir."/".$ref.".pdf";
1305 if (file_exists($file)) {
1306 dol_delete_preview($this);
1307
1308 if (!dol_delete_file($file, 0, 0, 0, $this)) {
1309 $this->error = 'ErrorFailToDeleteFile';
1310 $this->errors[] = $this->error;
1311 $this->db->rollback();
1312 return 0;
1313 }
1314 }
1315 if (file_exists($dir)) {
1316 $res = @dol_delete_dir_recursive($dir);
1317 if (!$res) {
1318 $this->error = 'ErrorFailToDeleteDir';
1319 $this->errors[] = $this->error;
1320 $this->db->rollback();
1321 return 0;
1322 }
1323 }
1324 }
1325 }
1326
1327 if (!$error) {
1328 dol_syslog(get_class($this)."::delete ".$this->id." by ".$user->id, LOG_DEBUG);
1329 $this->db->commit();
1330 return 1;
1331 } else {
1332 $this->db->rollback();
1333 return -1;
1334 }
1335 }
1336
1344 public function setValidate($fuser, $notrigger = 0)
1345 {
1346 global $conf, $langs, $user;
1347
1348 $error = 0;
1349 $now = dol_now();
1350
1351 // Protection
1352 if ($this->status == self::STATUS_VALIDATED) {
1353 dol_syslog(get_class($this)."::valid action abandoned: already validated", LOG_WARNING);
1354 return 0;
1355 }
1356
1357 $this->date_valid = $now; // Required for the getNextNum later.
1358
1359 // Define new ref
1360 if (preg_match('/^[\‍(]?PROV/i', $this->ref) || empty($this->ref)) { // empty should not happened, but when it occurs, the test save life
1361 $num = $this->getNextNumRef();
1362 } else {
1363 $num = (string) $this->ref;
1364 }
1365 if (empty($num) || $num < 0) {
1366 return -1;
1367 }
1368
1369 $this->newref = dol_sanitizeFileName($num);
1370
1371 $this->db->begin();
1372
1373 // Validate
1374 $sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element;
1375 $sql .= " SET ref = '".$this->db->escape($num)."',";
1376 $sql .= " fk_statut = ".self::STATUS_VALIDATED.",";
1377 $sql .= " date_valid = '".$this->db->idate($this->date_valid)."',";
1378 $sql .= " fk_user_valid = ".((int) $user->id);
1379 $sql .= " WHERE rowid = ".((int) $this->id);
1380
1381 $resql = $this->db->query($sql);
1382 if ($resql) {
1383 if (!$notrigger) {
1384 // Call trigger
1385 $result = $this->call_trigger('EXPENSE_REPORT_VALIDATE', $fuser);
1386 if ($result < 0) {
1387 $error++;
1388 }
1389 // End call triggers
1390 }
1391
1392 if (!$error) {
1393 $this->oldref = $this->ref;
1394
1395 // Rename directory if dir was a temporary ref
1396 if (preg_match('/^[\‍(]?PROV/i', $this->ref)) {
1397 require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
1398
1399 // Now we rename also files into index
1400 $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)."'";
1401 $sql .= " WHERE filename LIKE '".$this->db->escape($this->ref)."%' AND filepath = 'expensereport/".$this->db->escape($this->ref)."' AND entity = ".((int) $this->entity);
1402 $resql = $this->db->query($sql);
1403 if (!$resql) {
1404 $error++;
1405 $this->error = $this->db->lasterror();
1406 }
1407 $sql = 'UPDATE '.MAIN_DB_PREFIX."ecm_files set filepath = 'expensereport/".$this->db->escape($this->newref)."'";
1408 $sql .= " WHERE filepath = 'expensereport/".$this->db->escape($this->ref)."' and entity = ".$conf->entity;
1409 $resql = $this->db->query($sql);
1410 if (!$resql) {
1411 $error++;
1412 $this->error = $this->db->lasterror();
1413 }
1414
1415 // We rename directory ($this->ref = old ref, $num = new ref) in order not to lose the attachments
1416 $oldref = dol_sanitizeFileName($this->ref);
1417 $newref = dol_sanitizeFileName($num);
1418 $dirsource = $conf->expensereport->multidir_output[$this->entity].'/'.$oldref;
1419 $dirdest = $conf->expensereport->multidir_output[$this->entity].'/'.$newref;
1420 if (!$error && file_exists($dirsource)) {
1421 dol_syslog(get_class($this)."::setValidate() rename dir ".$dirsource." into ".$dirdest);
1422
1423 if (@rename($dirsource, $dirdest)) {
1424 dol_syslog("Rename ok");
1425 // Rename docs starting with $oldref with $newref
1426 $listoffiles = dol_dir_list($dirdest, 'files', 1, '^'.preg_quote($oldref, '/'));
1427 foreach ($listoffiles as $fileentry) {
1428 $dirsource = $fileentry['name'];
1429 $dirdest = preg_replace('/^'.preg_quote($oldref, '/').'/', $newref, $dirsource);
1430 $dirsource = $fileentry['path'].'/'.$dirsource;
1431 $dirdest = $fileentry['path'].'/'.$dirdest;
1432 @rename($dirsource, $dirdest);
1433 }
1434 }
1435 }
1436 }
1437 }
1438
1439 // Set new ref and current status
1440 if (!$error) {
1441 $this->ref = $num;
1443 }
1444
1445 if (empty($error)) {
1446 $this->db->commit();
1447 return 1;
1448 } else {
1449 $this->db->rollback();
1450 $this->error = $this->db->error();
1451 return -2;
1452 }
1453 } else {
1454 $this->db->rollback();
1455 $this->error = $this->db->lasterror();
1456 return -1;
1457 }
1458 }
1459
1460 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1467 public function set_save_from_refuse($fuser)
1468 {
1469 // phpcs:enable
1470 // Sélection de la date de début de la NDF
1471 $sql = 'SELECT date_debut';
1472 $sql .= ' FROM '.MAIN_DB_PREFIX.$this->table_element;
1473 $sql .= " WHERE rowid = ".((int) $this->id);
1474
1475 $result = $this->db->query($sql);
1476
1477 $objp = $this->db->fetch_object($result);
1478
1479 $this->date_debut = $this->db->jdate($objp->date_debut);
1480
1481 if ($this->status != self::STATUS_VALIDATED) {
1482 $sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element;
1483 $sql .= " SET fk_statut = ".self::STATUS_VALIDATED;
1484 $sql .= " WHERE rowid = ".((int) $this->id);
1485
1486 dol_syslog(get_class($this)."::set_save_from_refuse", LOG_DEBUG);
1487
1488 if ($this->db->query($sql)) {
1489 return 1;
1490 } else {
1491 $this->error = $this->db->lasterror();
1492 return -1;
1493 }
1494 } else {
1495 dol_syslog(get_class($this)."::set_save_from_refuse expensereport already with save status", LOG_WARNING);
1496 }
1497
1498 return 0;
1499 }
1500
1508 public function setApproved($fuser, $notrigger = 0)
1509 {
1510 $now = dol_now();
1511 $error = 0;
1512
1513 if ($this->status != self::STATUS_APPROVED) {
1514 $this->db->begin();
1515 $sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element;
1516 $sql .= " SET ref = '".$this->db->escape($this->ref)."', fk_statut = ".self::STATUS_APPROVED.", fk_user_approve = ".((int) $fuser->id).",";
1517 $sql .= " date_approve='".$this->db->idate($now)."'";
1518 $sql .= " WHERE rowid = ".((int) $this->id);
1519 if ($this->db->query($sql)) {
1521 $this->date_approve = $now;
1522 $this->fk_user_approve = $fuser->id;
1523 if (!$notrigger) {
1524 // Call trigger
1525 $result = $this->call_trigger('EXPENSE_REPORT_APPROVE', $fuser);
1526
1527 if ($result < 0) {
1528 $error++;
1529 }
1530 // End call triggers
1531 }
1532
1533 if (empty($error)) {
1534 $this->db->commit();
1535 return 1;
1536 } else {
1537 $this->db->rollback();
1538 $this->error = $this->db->error();
1539 return -2;
1540 }
1541 } else {
1542 $this->db->rollback();
1543 $this->error = $this->db->lasterror();
1544 return -1;
1545 }
1546 } else {
1547 dol_syslog(get_class($this)."::setApproved expensereport already with approve status", LOG_WARNING);
1548 }
1549
1550 return 0;
1551 }
1552
1561 public function setDeny($fuser, $details, $notrigger = 0)
1562 {
1563 $now = dol_now();
1564 $error = 0;
1565
1566 // date de refus
1567 if ($this->status != self::STATUS_REFUSED) {
1568 $sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element;
1569 $sql .= " SET ref = '".$this->db->escape($this->ref)."', fk_statut = ".self::STATUS_REFUSED.", fk_user_refuse = ".((int) $fuser->id).",";
1570 $sql .= " date_refuse='".$this->db->idate($now)."',";
1571 $sql .= " detail_refuse='".$this->db->escape($details)."',";
1572 $sql .= " fk_user_approve = NULL";
1573 $sql .= " WHERE rowid = ".((int) $this->id);
1574 if ($this->db->query($sql)) {
1575 $this->fk_statut = 99; // deprecated
1576 $this->status = 99;
1577 $this->fk_user_refuse = $fuser->id;
1578 $this->detail_refuse = $details;
1579 $this->date_refuse = $now;
1580
1581 if (!$notrigger) {
1582 // Call trigger
1583 $result = $this->call_trigger('EXPENSE_REPORT_DENY', $fuser);
1584
1585 if ($result < 0) {
1586 $error++;
1587 }
1588 // End call triggers
1589 }
1590
1591 if (empty($error)) {
1592 $this->db->commit();
1593 return 1;
1594 } else {
1595 $this->db->rollback();
1596 $this->error = $this->db->error();
1597 return -2;
1598 }
1599 } else {
1600 $this->db->rollback();
1601 $this->error = $this->db->lasterror();
1602 return -1;
1603 }
1604 } else {
1605 dol_syslog(get_class($this)."::setDeny expensereport already with refuse status", LOG_WARNING);
1606 }
1607
1608 return 0;
1609 }
1610
1611 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1621 public function set_unpaid($fuser, $notrigger = 0)
1622 {
1623 // phpcs:enable
1624 dol_syslog(get_class($this)."::set_unpaid is deprecated, use setUnpaid instead", LOG_NOTICE);
1625 return $this->setUnpaid($fuser, $notrigger);
1626 }
1627
1635 public function setUnpaid($fuser, $notrigger = 0)
1636 {
1637 $error = 0;
1638
1639 if ($this->paid) {
1640 $this->db->begin();
1641
1642 $sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element;
1643 $sql .= " SET paid = 0, fk_statut = ".self::STATUS_APPROVED;
1644 $sql .= " WHERE rowid = ".((int) $this->id);
1645
1646 dol_syslog(get_class($this)."::set_unpaid", LOG_DEBUG);
1647
1648 if ($this->db->query($sql)) {
1649 if (!$notrigger) {
1650 // Call trigger
1651 $result = $this->call_trigger('EXPENSE_REPORT_UNPAID', $fuser);
1652
1653 if ($result < 0) {
1654 $error++;
1655 }
1656 // End call triggers
1657 }
1658
1659 if (empty($error)) {
1660 $this->db->commit();
1661 return 1;
1662 } else {
1663 $this->db->rollback();
1664 $this->error = $this->db->error();
1665 return -2;
1666 }
1667 } else {
1668 $this->db->rollback();
1669 $this->error = $this->db->error();
1670 return -1;
1671 }
1672 } else {
1673 dol_syslog(get_class($this)."::set_unpaid expensereport already with unpaid status", LOG_WARNING);
1674 }
1675
1676 return 0;
1677 }
1678
1679 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1688 public function set_cancel($fuser, $detail, $notrigger = 0)
1689 {
1690 // phpcs:enable
1691 $error = 0;
1692 $this->date_cancel = dol_now();
1693 if ($this->status != self::STATUS_CANCELED) {
1694 $this->db->begin();
1695
1696 $sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element;
1697 $sql .= " SET fk_statut = ".((int) self::STATUS_CANCELED).", fk_user_cancel = ".((int) $fuser->id);
1698 $sql .= ", date_cancel = '".$this->db->idate($this->date_cancel)."'";
1699 $sql .= ", detail_cancel = '".$this->db->escape($detail)."'";
1700 $sql .= " WHERE rowid = ".((int) $this->id);
1701
1702 dol_syslog(get_class($this)."::set_cancel", LOG_DEBUG);
1703
1704 if ($this->db->query($sql)) {
1705 if (!$notrigger) {
1706 // Call trigger
1707 $result = $this->call_trigger('EXPENSE_REPORT_CANCEL', $fuser);
1708
1709 if ($result < 0) {
1710 $error++;
1711 }
1712 // End call triggers
1713 }
1714
1715 if (empty($error)) {
1716 $this->db->commit();
1717 return 1;
1718 } else {
1719 $this->db->rollback();
1720 $this->error = $this->db->error();
1721 return -2;
1722 }
1723 } else {
1724 $this->db->rollback();
1725 $this->error = $this->db->error();
1726 return -1;
1727 }
1728 } else {
1729 dol_syslog(get_class($this)."::set_cancel expensereport already with cancel status", LOG_WARNING);
1730 }
1731 return 0;
1732 }
1733
1739 public function getNextNumRef()
1740 {
1741 global $langs, $conf;
1742 $langs->load("trips");
1743
1744 if (getDolGlobalString('EXPENSEREPORT_ADDON')) {
1745 $mybool = false;
1746
1747 $file = getDolGlobalString('EXPENSEREPORT_ADDON') . ".php";
1748 $classname = getDolGlobalString('EXPENSEREPORT_ADDON');
1749
1750 // Include file with class
1751 $dirmodels = array_merge(array('/'), (array) $conf->modules_parts['models']);
1752 foreach ($dirmodels as $reldir) {
1753 $dir = dol_buildpath($reldir."core/modules/expensereport/");
1754
1755 // Load file with numbering class (if found)
1756 $mybool = ((bool) @include_once $dir.$file) || $mybool;
1757 }
1758
1759 if (!$mybool) {
1760 dol_print_error(null, "Failed to include file ".$file);
1761 return '';
1762 }
1763
1764 $obj = new $classname();
1765 '@phan-var-force ModeleNumRefExpenseReport $obj';
1766 $numref = $obj->getNextValue($this);
1767
1768 if ($numref != "") {
1769 return $numref;
1770 } else {
1771 $this->error = $obj->error;
1772 $this->errors = $obj->errors;
1773 //dol_print_error($this->db,get_class($this)."::getNextNumRef ".$obj->error);
1774 return -1;
1775 }
1776 } else {
1777 $this->error = "Error_EXPENSEREPORT_ADDON_NotDefined";
1778 return -2;
1779 }
1780 }
1781
1788 public function getTooltipContentArray($params)
1789 {
1790 global $conf, $langs;
1791
1792 $langs->load('trips');
1793
1794 $nofetch = !empty($params['nofetch']);
1795 $moretitle = $params['moretitle'] ?? '';
1796
1797 $datas = array();
1798 $datas['picto'] = img_picto('', $this->picto).' <u class="paddingrightonly">'.$langs->trans("ExpenseReport").'</u>';
1799 if (isset($this->status)) {
1800 $datas['picto'] .= ' '.$this->getLibStatut(5);
1801 }
1802 if ($moretitle) {
1803 $datas['picto'] .= ' - '.$moretitle;
1804 }
1805 if (!empty($this->ref)) {
1806 $datas['ref'] = '<br><b>'.$langs->trans('Ref').':</b> '.$this->ref;
1807 }
1808 if (!empty($this->total_ht)) {
1809 $datas['total_ht'] = '<br><b>'.$langs->trans('AmountHT').':</b> '.price($this->total_ht, 0, $langs, 0, -1, -1, $conf->currency);
1810 }
1811 if (!empty($this->total_tva)) {
1812 $datas['total_tva'] = '<br><b>'.$langs->trans('VAT').':</b> '.price($this->total_tva, 0, $langs, 0, -1, -1, $conf->currency);
1813 }
1814 if (!empty($this->total_ttc)) {
1815 $datas['total_ttc'] = '<br><b>'.$langs->trans('AmountTTC').':</b> '.price($this->total_ttc, 0, $langs, 0, -1, -1, $conf->currency);
1816 }
1817
1818 return $datas;
1819 }
1820
1833 public function getNomUrl($withpicto = 0, $option = '', $max = 0, $short = 0, $moretitle = '', $notooltip = 0, $save_lastsearch_value = -1)
1834 {
1835 global $langs, $hookmanager;
1836
1837 $result = '';
1838
1839 $baseurl = DOL_URL_ROOT.'/expensereport/card.php';
1840 $query = ['id' => $this->id];
1841
1842 if ($short) {
1843 return dolBuildUrl($baseurl, $query);
1844 }
1845
1846 $params = [
1847 'id' => $this->id,
1848 'objecttype' => $this->element,
1849 'option' => $option,
1850 'moretitle' => $moretitle,
1851 'nofetch' => 1,
1852 ];
1853 $classfortooltip = 'classfortooltip';
1854 $dataparams = '';
1855 if (getDolGlobalInt('MAIN_ENABLE_AJAX_TOOLTIP')) {
1856 $classfortooltip = 'classforajaxtooltip';
1857 $dataparams = ' data-params="'.dol_escape_htmltag(json_encode($params)).'"';
1858 $label = '';
1859 } else {
1860 $label = implode($this->getTooltipContentArray($params));
1861 }
1862
1863 if ($option != 'nolink') {
1864 // Add param to save lastsearch_values or not
1865 $add_save_lastsearch_values = ($save_lastsearch_value == 1 ? 1 : 0);
1866 if ($save_lastsearch_value == -1 && isset($_SERVER["PHP_SELF"]) && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) {
1867 $add_save_lastsearch_values = 1;
1868 }
1869 if ($add_save_lastsearch_values) {
1870 $query += ['save_lastsearch_values' => 1];
1871 }
1872 }
1873 $url = dolBuildUrl($baseurl, $query);
1874
1875 $ref = $this->ref;
1876 if (empty($ref)) {
1877 $ref = $this->id;
1878 }
1879
1880 $linkclose = '';
1881 if (empty($notooltip)) {
1882 if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
1883 $label = $langs->trans("ShowExpenseReport");
1884 $linkclose .= ' alt="'.dolPrintHTMLForAttribute($label).'"';
1885 }
1886 $linkclose .= ($label ? ' title="'.dolPrintHTMLForAttribute($label).'"' : ' title="tocomplete"');
1887 $linkclose .= $dataparams.' class="'.$classfortooltip.'"';
1888 }
1889
1890 $linkstart = '<a href="'.$url.'"';
1891 $linkstart .= $linkclose.'>';
1892 $linkend = '</a>';
1893
1894 $result .= $linkstart;
1895 if ($withpicto) {
1896 $result .= img_object(($notooltip ? '' : $label), ($this->picto ? $this->picto : 'generic'), ($notooltip ? (($withpicto != 2) ? 'class="paddingright"' : '') : 'class="'.(($withpicto != 2) ? 'paddingright ' : '').'"'), 0, 0, $notooltip ? 0 : 1);
1897 }
1898 if ($withpicto != 2) {
1899 $result .= ($max ? dol_trunc($ref, $max) : $ref);
1900 }
1901 $result .= $linkend;
1902
1903 global $action;
1904 $hookmanager->initHooks(array($this->element . 'dao'));
1905 $parameters = array('id' => $this->id, 'getnomurl' => &$result);
1906 $reshook = $hookmanager->executeHooks('getNomUrl', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
1907 if ($reshook > 0) {
1908 $result = $hookmanager->resPrint;
1909 } else {
1910 $result .= $hookmanager->resPrint;
1911 }
1912 return $result;
1913 }
1914
1915 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1923 public function update_totaux_add($ligne_total_ht, $ligne_total_tva)
1924 {
1925 // phpcs:enable
1926 $this->total_ht += (float) $ligne_total_ht;
1927 $this->total_tva += (float) $ligne_total_tva;
1928 $this->total_ttc += $this->total_tva;
1929
1930 $sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element." SET";
1931 $sql .= " total_ht = ".((float) $this->total_ht);
1932 $sql .= " , total_ttc = ".((float) $this->total_ttc);
1933 $sql .= " , total_tva = ".((float) $this->total_tva);
1934 $sql .= " WHERE rowid = ".((int) $this->id);
1935
1936 $result = $this->db->query($sql);
1937 if ($result) {
1938 return 1;
1939 } else {
1940 $this->error = $this->db->error();
1941 return -1;
1942 }
1943 }
1944
1960 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)
1961 {
1962 global $langs, $mysoc;
1963
1964 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);
1965
1966 if ($this->status == self::STATUS_DRAFT || $this->status == self::STATUS_REFUSED) {
1967 if (empty($qty)) {
1968 $qty = 0;
1969 }
1970 if (empty($fk_c_type_fees) || $fk_c_type_fees < 0) {
1971 $fk_c_type_fees = 0;
1972 }
1973 if (empty($fk_c_exp_tax_cat) || $fk_c_exp_tax_cat < 0) {
1974 $fk_c_exp_tax_cat = 0;
1975 }
1976 if (empty($vatrate) || $vatrate < 0) {
1977 $vatrate = 0;
1978 }
1979 if (empty($date)) {
1980 $date = '';
1981 }
1982 if (empty($fk_project)) {
1983 $fk_project = 0;
1984 }
1985
1986 $qty = (float) price2num($qty);
1987 if (!preg_match('/\s*\‍((.*)\‍)/', $vatrate)) {
1988 $vatrate = price2num($vatrate); // $txtva can have format '5.0 (XXX)' or '5'
1989 }
1990 $up = price2num($up);
1991
1992 $this->db->begin();
1993
1994 $this->line = new ExpenseReportLine($this->db);
1995
1996 // We don't know seller and buyer for expense reports
1997 $seller = $mysoc; // We use same than current company (expense report are often done in same country)
1998 $seller->tva_assuj = 1; // Most seller uses vat
1999 $buyer = new Societe($this->db);
2000
2001 $localtaxes_type = getLocalTaxesFromRate($vatrate, 0, $buyer, $seller);
2002
2003 $vat_src_code = '';
2004 $reg = array();
2005 if (preg_match('/\s*\‍((.*)\‍)/', $vatrate, $reg)) {
2006 $vat_src_code = $reg[1];
2007 $vatrate = preg_replace('/\s*\‍(.*\‍)/', '', $vatrate); // Remove code into vatrate.
2008 }
2009 $vatrate = preg_replace('/\*/', '', $vatrate);
2010
2011 $tmp = calcul_price_total($qty, (float) $up, 0, (float) price2num($vatrate), -1, -1, 0, 'TTC', 0, $type, $seller, $localtaxes_type);
2012
2013 $this->line->value_unit = $up;
2014
2015 $this->line->vat_src_code = $vat_src_code;
2016 $this->line->vatrate = price2num($vatrate);
2017 $this->line->localtax1_tx = $localtaxes_type[1];
2018 $this->line->localtax2_tx = $localtaxes_type[3];
2019 $this->line->localtax1_type = $localtaxes_type[0];
2020 $this->line->localtax2_type = $localtaxes_type[2];
2021
2022 $this->line->total_ttc = (float) $tmp[2];
2023 $this->line->total_ht = (float) $tmp[0];
2024 $this->line->total_tva = (float) $tmp[1];
2025 $this->line->total_localtax1 = (float) $tmp[9];
2026 $this->line->total_localtax2 = (float) $tmp[10];
2027
2028 $this->line->fk_expensereport = $this->id;
2029 $this->line->qty = $qty;
2030 $this->line->date = $date;
2031 $this->line->fk_c_type_fees = $fk_c_type_fees;
2032 $this->line->fk_c_exp_tax_cat = $fk_c_exp_tax_cat;
2033 $this->line->comments = $comments;
2034 $this->line->fk_projet = $fk_project; // deprecated
2035 $this->line->fk_project = $fk_project;
2036
2037 $this->line->fk_ecm_files = $fk_ecm_files;
2038
2039 $this->applyOffset();
2040 $this->checkRules($type, $seller);
2041
2042 $result = $this->line->insert(0, true);
2043 if ($result > 0) {
2044 $result = $this->update_price(1); // This method is designed to add line from user input so total calculation must be done using 'auto' mode.
2045 if ($result > 0) {
2046 $this->db->commit();
2047 return $this->line->id;
2048 } else {
2049 $this->db->rollback();
2050 return -1;
2051 }
2052 } else {
2053 $this->error = $this->line->error;
2054 dol_syslog(get_class($this)."::addline error=".$this->error, LOG_ERR);
2055 $this->db->rollback();
2056 return -2;
2057 }
2058 } else {
2059 dol_syslog(get_class($this)."::addline status of expense report must be Draft to allow use of ->addline()", LOG_ERR);
2060 $this->error = 'ErrorExpenseNotDraftAndNotRefused';
2061 return -3;
2062 }
2063 }
2064
2072 public function checkRules($type = 0, $seller = '')
2073 {
2074 global $conf, $langs, $mysoc;
2075
2076 $langs->load('trips');
2077
2078 // We don't know seller and buyer for expense reports
2079 if (!is_object($seller)) {
2080 $seller = $mysoc; // We use same than current company (expense report are often done in same country)
2081 $seller->tva_assuj = 1; // Most seller uses vat
2082 }
2083
2084 $expensereportrule = new ExpenseReportRule($this->db);
2085 $rulestocheck = $expensereportrule->getAllRule($this->line->fk_c_type_fees, $this->line->date, $this->fk_user_author);
2086
2087 $violation = 0;
2088 $rule_warning_message_tab = array();
2089
2090 $current_total_ttc = $this->line->total_ttc;
2091 $new_current_total_ttc = $this->line->total_ttc;
2092
2093 // check if one is violated
2094 foreach ($rulestocheck as $rule) {
2095 if (in_array($rule->code_expense_rules_type, array('EX_DAY', 'EX_MON', 'EX_YEA'))) {
2096 $amount_to_test = $this->line->getExpAmount($rule, $this->fk_user_author, $rule->code_expense_rules_type);
2097 } else {
2098 $amount_to_test = $current_total_ttc; // EX_EXP
2099 }
2100
2101 $amount_to_test = $amount_to_test - $current_total_ttc + $new_current_total_ttc; // if amount as been modified by a previous rule
2102
2103 if ($amount_to_test > $rule->amount) {
2104 $violation++;
2105
2106 if ($rule->restrictive) {
2107 $this->error = 'ExpenseReportConstraintViolationError';
2108 $this->errors[] = $this->error;
2109
2110 $new_current_total_ttc -= $amount_to_test - $rule->amount; // ex, entered 16€, limit 12€, subtracts 4€;
2111 $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));
2112 } else {
2113 $this->error = 'ExpenseReportConstraintViolationWarning';
2114 $this->errors[] = $this->error;
2115
2116 $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));
2117 }
2118
2119 // No break, we should test if another rule is violated
2120 }
2121 }
2122
2123 $this->line->rule_warning_message = implode('\n', $rule_warning_message_tab);
2124
2125 if ($violation > 0) {
2126 $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);
2127
2128 $this->line->value_unit = $tmp[5];
2129 $this->line->total_ttc = (float) $tmp[2];
2130 $this->line->total_ht = (float) $tmp[0];
2131 $this->line->total_tva = (float) $tmp[1];
2132 $this->line->total_localtax1 = (float) $tmp[9];
2133 $this->line->total_localtax2 = (float) $tmp[10];
2134
2135 return false;
2136 } else {
2137 return true;
2138 }
2139 }
2140
2148 public function applyOffset($type = 0, $seller = '')
2149 {
2150 global $mysoc;
2151
2152 if (!getDolGlobalString('MAIN_USE_EXPENSE_IK')) {
2153 return false;
2154 }
2155
2156 $userauthor = new User($this->db);
2157 if ($userauthor->fetch($this->fk_user_author) <= 0) {
2158 $this->error = 'ErrorCantFetchUser';
2159 $this->errors[] = 'ErrorCantFetchUser';
2160 return false;
2161 }
2162
2163 // We don't know seller and buyer for expense reports
2164 if (!is_object($seller)) {
2165 $seller = $mysoc; // We use same than current company (expense report are often done in same country)
2166 $seller->tva_assuj = 1; // Most seller uses vat
2167 }
2168
2169 $expenseik = new ExpenseReportIk($this->db);
2170 $range = $expenseik->getRangeByUser($userauthor, $this->line->fk_c_exp_tax_cat);
2171
2172 if (empty($range)) {
2173 $this->error = 'ErrorNoRangeAvailable';
2174 $this->errors[] = 'ErrorNoRangeAvailable';
2175 return false;
2176 }
2177
2178 if (getDolGlobalString('MAIN_EXPENSE_APPLY_ENTIRE_OFFSET')) {
2179 $ikoffset = $range->ikoffset;
2180 } else {
2181 $ikoffset = $range->ikoffset / 12; // The amount of offset is a global value for the year
2182 }
2183
2184 // Test if ikoffset has been applied for the current month
2185 if (!$this->offsetAlreadyGiven()) {
2186 $new_up = $range->coef + ($ikoffset / $this->line->qty);
2187 $tmp = calcul_price_total($this->line->qty, $new_up, 0, $this->line->vatrate, 0, 0, 0, 'TTC', 0, $type, $seller);
2188
2189 $this->line->value_unit = $tmp[5];
2190 $this->line->total_ttc = (float) $tmp[2];
2191 $this->line->total_ht = (float) $tmp[0];
2192 $this->line->total_tva = (float) $tmp[1];
2193 $this->line->total_localtax1 = (float) $tmp[9];
2194 $this->line->total_localtax2 = (float) $tmp[10];
2195
2196 return true;
2197 }
2198
2199 return false;
2200 }
2201
2207 public function offsetAlreadyGiven()
2208 {
2209 $sql = 'SELECT e.rowid FROM '.MAIN_DB_PREFIX.'expensereport e';
2210 $sql .= " INNER JOIN ".MAIN_DB_PREFIX."expensereport_det d ON (e.rowid = d.fk_expensereport)";
2211 $sql .= " INNER JOIN ".MAIN_DB_PREFIX."c_type_fees f ON (d.fk_c_type_fees = f.id AND f.code = 'EX_KME')";
2212 $sql .= " WHERE e.fk_user_author = ".(int) $this->fk_user_author;
2213 $sql .= " AND YEAR(d.date) = '".dol_print_date($this->line->date, '%Y')."' AND MONTH(d.date) = '".dol_print_date($this->line->date, '%m')."'";
2214 if (!empty($this->line->id)) {
2215 $sql .= ' AND d.rowid <> '.((int) $this->line->id);
2216 }
2217
2218 dol_syslog(get_class($this)."::offsetAlreadyGiven");
2219 $resql = $this->db->query($sql);
2220 if ($resql) {
2221 $num = $this->db->num_rows($resql);
2222 if ($num > 0) {
2223 return true;
2224 }
2225 } else {
2226 dol_print_error($this->db);
2227 }
2228
2229 return false;
2230 }
2231
2249 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)
2250 {
2251 global $user, $mysoc;
2252
2253 if ($this->status == self::STATUS_DRAFT || $this->status == self::STATUS_REFUSED) {
2254 $this->db->begin();
2255
2256 $error = 0;
2257 $type = 0; // TODO What if type is service ?
2258
2259 // We don't know seller and buyer for expense reports
2260 $seller = $mysoc; // We use same than current company (expense report are often done in same country)
2261 $seller->tva_assuj = 1; // Most seller uses vat
2262 $seller->localtax1_assuj = $mysoc->localtax1_assuj; // We don't know, we reuse the state of company
2263 $seller->localtax2_assuj = $mysoc->localtax1_assuj; // We don't know, we reuse the state of company
2264 $buyer = new Societe($this->db);
2265
2266 $localtaxes_type = getLocalTaxesFromRate($vatrate, 0, $buyer, $seller);
2267
2268 // Clean vat code
2269 $reg = array();
2270 $vat_src_code = '';
2271 if (preg_match('/\‍((.*)\‍)/', (string) $vatrate, $reg)) {
2272 $vat_src_code = $reg[1];
2273 $vatrate = preg_replace('/\s*\‍(.*\‍)/', '', (string) $vatrate); // Remove code into vatrate.
2274 }
2275 $vatrate = preg_replace('/\*/', '', $vatrate);
2276
2277 $tmp = calcul_price_total($qty, $value_unit, 0, (float) price2num($vatrate), -1, -1, 0, 'TTC', 0, $type, $seller, $localtaxes_type);
2278 // calcul total of line
2279 // $total_ttc = price2num($qty*$value_unit, 'MT');
2280
2281 $tx_tva = 1 + (float) $vatrate / 100;
2282
2283 $this->line = new ExpenseReportLine($this->db);
2284 $this->line->comments = $comments;
2285 $this->line->qty = $qty;
2286 $this->line->value_unit = $value_unit;
2287 $this->line->date = $date;
2288
2289 $this->line->fk_expensereport = $expensereport_id;
2290 $this->line->fk_c_type_fees = $type_fees_id;
2291 $this->line->fk_c_exp_tax_cat = $fk_c_exp_tax_cat;
2292 $this->line->fk_projet = $projet_id; // deprecated
2293 $this->line->fk_project = $projet_id;
2294
2295 $this->line->vat_src_code = $vat_src_code;
2296 $this->line->vatrate = price2num($vatrate);
2297 $this->line->localtax1_tx = $localtaxes_type[1];
2298 $this->line->localtax2_tx = $localtaxes_type[3];
2299 $this->line->localtax1_type = $localtaxes_type[0];
2300 $this->line->localtax2_type = $localtaxes_type[2];
2301
2302 $this->line->total_ttc = (float) $tmp[2];
2303 $this->line->total_ht = (float) $tmp[0];
2304 $this->line->total_tva = (float) $tmp[1];
2305 $this->line->total_localtax1 = (float) $tmp[9];
2306 $this->line->total_localtax2 = (float) $tmp[10];
2307
2308 $this->line->fk_ecm_files = $fk_ecm_files;
2309
2310 $this->line->id = ((int) $rowid);
2311
2312 // Select des infos sur le type fees
2313 $sql = "SELECT c.code as code_type_fees, c.label as label_type_fees";
2314 $sql .= " FROM ".MAIN_DB_PREFIX."c_type_fees as c";
2315 $sql .= " WHERE c.id = ".((int) $type_fees_id);
2316 $resql = $this->db->query($sql);
2317 if ($resql) {
2318 $objp_fees = $this->db->fetch_object($resql);
2319 $this->line->type_fees_code = $objp_fees->code_type_fees;
2320 $this->line->type_fees_libelle = $objp_fees->label_type_fees;
2321 $this->db->free($resql);
2322 }
2323
2324 if ($projet_id > 0) {
2325 // Select des information du projet
2326 $sql = "SELECT p.ref as ref_projet, p.title as title_projet";
2327 $sql .= " FROM ".MAIN_DB_PREFIX."projet as p";
2328 $sql .= " WHERE p.rowid = ".((int) $projet_id);
2329 $resql = $this->db->query($sql);
2330 if ($resql) {
2331 if ($this->db->num_rows($resql) > 0) {
2332 $objp_projet = $this->db->fetch_object($resql);
2333 $this->line->projet_ref = $objp_projet->ref_projet;
2334 $this->line->projet_title = $objp_projet->title_projet;
2335 }
2336 $this->db->free($resql);
2337 }
2338 }
2339 $this->applyOffset();
2340 $this->checkRules();
2341
2342 $result = $this->line->update($user, $notrigger);
2343 if ($result < 0) {
2344 $error++;
2345 }
2346
2347 if (!$error) {
2348 $this->db->commit();
2349 return 1;
2350 } else {
2351 $this->error = $this->line->error;
2352 $this->errors = $this->line->errors;
2353 $this->db->rollback();
2354 return -2;
2355 }
2356 }
2357
2358 return 0;
2359 }
2360
2369 public function deleteLine($rowid, $fuser = null, $notrigger = 0)
2370 {
2371 $error = 0;
2372
2373 $this->db->begin();
2374
2375 if (!$notrigger) {
2376 // Call triggers
2377 $result = $this->call_trigger('EXPENSE_REPORT_DET_DELETE', $fuser);
2378 if ($result < 0) {
2379 $error++;
2380 }
2381 // End call triggers
2382 }
2383
2384 $sql = ' DELETE FROM '.MAIN_DB_PREFIX.$this->table_element_line;
2385 $sql .= ' WHERE rowid = '.((int) $rowid);
2386
2387 dol_syslog(get_class($this)."::deleteline sql=".$sql);
2388 $result = $this->db->query($sql);
2389
2390 if (!$result || $error > 0) {
2391 $this->error = $this->db->error();
2392 dol_syslog(get_class($this)."::deleteline Error ".$this->error, LOG_ERR);
2393 $this->db->rollback();
2394 return -1;
2395 }
2396
2397 $this->update_price(1);
2398
2399 $this->db->commit();
2400
2401 return 1;
2402 }
2403
2412 public function periodExists(User $fuser, $startDate, $endDate)
2413 {
2414 global $conf;
2415
2416 $sql = "SELECT rowid, date_debut, date_fin";
2417 $sql .= " FROM ".MAIN_DB_PREFIX.$this->table_element;
2418 $sql .= " WHERE entity = ".((int) $conf->entity); // not shared, only for the current entity
2419 $sql .= " AND fk_user_author = ".((int) $fuser->id);
2420 $sql .= " AND (date_fin >= '".$this->db->idate($startDate)."' AND date_debut <= '".$this->db->idate($endDate)."')";
2421
2422 $row = $this->db->getRow($sql);
2423
2424 if ($row === false) {
2425 $this->error = $this->db->lasterror();
2426 dol_syslog(__CLASS__."::". __METHOD__." Error ".$this->error, LOG_ERR);
2427 return -1;
2428 }
2429
2430 return $row->rowid ?? 0;
2431 }
2432
2433
2434 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2442 {
2443 // phpcs:enable
2444 $users_validator = array();
2445
2446 $sql = "SELECT DISTINCT ur.fk_user";
2447 $sql .= " FROM ".MAIN_DB_PREFIX."user_rights as ur, ".MAIN_DB_PREFIX."rights_def as rd";
2448 $sql .= " WHERE ur.fk_id = rd.id and rd.module = 'expensereport' AND rd.perms = 'approve'"; // Permission 'Approve';
2449 $sql .= " UNION";
2450 $sql .= " SELECT DISTINCT ugu.fk_user";
2451 $sql .= " FROM ".MAIN_DB_PREFIX."usergroup_user as ugu, ".MAIN_DB_PREFIX."usergroup_rights as ur, ".MAIN_DB_PREFIX."rights_def as rd";
2452 $sql .= " WHERE ugu.fk_usergroup = ur.fk_usergroup AND ur.fk_id = rd.id and rd.module = 'expensereport' AND rd.perms = 'approve'"; // Permission 'Approve';
2453 //print $sql;
2454
2455 dol_syslog(get_class($this)."::fetch_users_approver_expensereport sql=".$sql);
2456 $result = $this->db->query($sql);
2457 if ($result) {
2458 $num_rows = $this->db->num_rows($result);
2459 $i = 0;
2460 while ($i < $num_rows) {
2461 $objp = $this->db->fetch_object($result);
2462 array_push($users_validator, $objp->fk_user);
2463 $i++;
2464 }
2465 return $users_validator;
2466 } else {
2467 $this->error = $this->db->lasterror();
2468 dol_syslog(get_class($this)."::fetch_users_approver_expensereport Error ".$this->error, LOG_ERR);
2469 return -1;
2470 }
2471 }
2472
2484 public function generateDocument($modele, $outputlangs, $hidedetails = 0, $hidedesc = 0, $hideref = 0, $moreparams = null)
2485 {
2486 $outputlangs->load("trips");
2487
2488 if (!dol_strlen($modele)) {
2489 if (!empty($this->model_pdf)) {
2490 $modele = $this->model_pdf;
2491 } elseif (getDolGlobalString('EXPENSEREPORT_ADDON_PDF')) {
2492 $modele = getDolGlobalString('EXPENSEREPORT_ADDON_PDF');
2493 }
2494 }
2495
2496 if (!empty($modele)) {
2497 $modelpath = "core/modules/expensereport/doc/";
2498
2499 return $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams);
2500 } else {
2501 return 0;
2502 }
2503 }
2504
2511 public function listOfTypes($active = 1)
2512 {
2513 global $langs;
2514 $ret = array();
2515 $sql = "SELECT id, code, label";
2516 $sql .= " FROM ".MAIN_DB_PREFIX."c_type_fees";
2517 $sql .= " WHERE active = ".((int) $active);
2518 dol_syslog(get_class($this)."::listOfTypes", LOG_DEBUG);
2519 $result = $this->db->query($sql);
2520 if ($result) {
2521 $num = $this->db->num_rows($result);
2522 $i = 0;
2523 while ($i < $num) {
2524 $obj = $this->db->fetch_object($result);
2525 $ret[$obj->code] = (($langs->transnoentitiesnoconv($obj->code) != $obj->code) ? $langs->transnoentitiesnoconv($obj->code) : $obj->label);
2526 $i++;
2527 }
2528 } else {
2529 dol_print_error($this->db);
2530 }
2531 return $ret;
2532 }
2533
2539 public function loadStateBoard()
2540 {
2541 global $user;
2542
2543 $this->nb = array();
2544
2545 $sql = "SELECT count(ex.rowid) as nb";
2546 $sql .= " FROM ".MAIN_DB_PREFIX."expensereport as ex";
2547 $sql .= " WHERE ex.fk_statut > 0";
2548 $sql .= " AND ex.entity IN (".getEntity('expensereport').")";
2549 if (!$user->hasRight('expensereport', 'readall')) {
2550 $userchildids = $user->getAllChildIds(1);
2551 $sql .= " AND (ex.fk_user_author IN (".$this->db->sanitize(implode(',', $userchildids)).")";
2552 $sql .= " OR ex.fk_user_validator IN (".$this->db->sanitize(implode(',', $userchildids))."))";
2553 }
2554
2555 $resql = $this->db->query($sql);
2556 if ($resql) {
2557 while ($obj = $this->db->fetch_object($resql)) {
2558 $this->nb["expensereports"] = $obj->nb;
2559 }
2560 $this->db->free($resql);
2561 return 1;
2562 } else {
2563 dol_print_error($this->db);
2564 $this->error = $this->db->error();
2565 return -1;
2566 }
2567 }
2568
2569 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2577 public function load_board($user, $option = 'topay')
2578 {
2579 // phpcs:enable
2580 global $conf, $langs;
2581
2582 if ($user->socid) {
2583 return -1; // Protection to prevent calls by external users
2584 }
2585
2586 $now = dol_now();
2587
2588 $sql = "SELECT ex.rowid, ex.date_valid";
2589 $sql .= " FROM ".MAIN_DB_PREFIX."expensereport as ex";
2590 if ($option == 'toapprove') {
2591 $sql .= " WHERE ex.fk_statut = ".self::STATUS_VALIDATED;
2592 } else {
2593 $sql .= " WHERE ex.fk_statut = ".self::STATUS_APPROVED;
2594 }
2595 $sql .= " AND ex.entity IN (".getEntity('expensereport').")";
2596 if (!$user->hasRight('expensereport', 'readall')) {
2597 $userchildids = $user->getAllChildIds(1);
2598 $sql .= " AND (ex.fk_user_author IN (".$this->db->sanitize(implode(',', $userchildids)).")";
2599 $sql .= " OR ex.fk_user_validator IN (".$this->db->sanitize(implode(',', $userchildids))."))";
2600 }
2601
2602 $resql = $this->db->query($sql);
2603 if ($resql) {
2604 $langs->load("trips");
2605
2606 $response = new WorkboardResponse();
2607 if ($option == 'toapprove') {
2608 $response->warning_delay = $conf->expensereport->approve->warning_delay / 60 / 60 / 24;
2609 $response->label = $langs->trans("ExpenseReportsToApprove");
2610 $response->labelShort = $langs->trans("ToApprove");
2611 $response->url = dolBuildUrl(DOL_URL_ROOT.'/expensereport/list.php', ['mainmenu' => 'hrm', 'statut' => self::STATUS_VALIDATED]);
2612 } else {
2613 $response->warning_delay = $conf->expensereport->payment->warning_delay / 60 / 60 / 24;
2614 $response->label = $langs->trans("ExpenseReportsToPay");
2615 $response->labelShort = $langs->trans("StatusToPay");
2616 $response->url = dolBuildUrl(DOL_URL_ROOT.'/expensereport/list.php', ['mainmenu' => 'hrm', 'statut' => self::STATUS_APPROVED]);
2617 }
2618 $response->img = img_object('', "trip");
2619
2620 while ($obj = $this->db->fetch_object($resql)) {
2621 $response->nbtodo++;
2622
2623 if ($option == 'toapprove') {
2624 if ($this->db->jdate($obj->date_valid) < ($now - $conf->expensereport->approve->warning_delay)) {
2625 $response->nbtodolate++;
2626 }
2627 } else {
2628 if ($this->db->jdate($obj->date_valid) < ($now - $conf->expensereport->payment->warning_delay)) {
2629 $response->nbtodolate++;
2630 }
2631 }
2632 }
2633
2634 return $response;
2635 } else {
2636 dol_print_error($this->db);
2637 $this->error = $this->db->error();
2638 return -1;
2639 }
2640 }
2641
2648 public function hasDelay($option)
2649 {
2650 global $conf;
2651
2652 // Only valid expenses reports
2653 if ($option == 'toapprove' && $this->status != 2) {
2654 return false;
2655 }
2656 if ($option == 'topay' && $this->status != 5) {
2657 return false;
2658 }
2659
2660 $now = dol_now();
2661 $warning_delay = (int) ($option == 'toapprove' ? $conf->expensereport->approve->warning_delay : $conf->expensereport->payment->warning_delay);
2662 if ($warning_delay <= 0) {
2663 // No delay configured (MAIN_DELAY_EXPENSEREPORTS / MIN_DELAY_EXPENSEREPORTS_TO_PAY not set), so nothing is late.
2664 return false;
2665 }
2666 return (!empty($this->datevalid) ? $this->datevalid : $this->date_valid) < ($now - $warning_delay);
2667 }
2668
2675 public function getVentilExportCompta($mode = 0)
2676 {
2677 $alreadydispatched = 0;
2678
2679 $type = 'expense_report';
2680
2681 $sql = " SELECT ".($mode ? 'DISTINCT piece_num' : 'COUNT(ab.rowid)')." as nb";
2682 $sql .= " FROM ".MAIN_DB_PREFIX."accounting_bookkeeping as ab WHERE ab.doc_type = '".$this->db->escape($type)."' AND ab.fk_doc = ".((int) $this->id);
2683 $resql = $this->db->query($sql);
2684 if ($resql) {
2685 $obj = $this->db->fetch_object($resql);
2686 if ($obj) {
2687 $alreadydispatched = $obj->nb;
2688 }
2689 } else {
2690 $this->error = $this->db->lasterror();
2691 return -1;
2692 }
2693
2694 if ($alreadydispatched) {
2695 return $alreadydispatched;
2696 }
2697 return 0;
2698 }
2699
2705 public function getSumPayments()
2706 {
2707 $table = 'payment_expensereport';
2708 $field = 'fk_expensereport';
2709
2710 $sql = 'SELECT sum(amount) as amount';
2711 $sql .= ' FROM '.MAIN_DB_PREFIX.$table;
2712 $sql .= " WHERE ".$this->db->sanitize($field)." = ".((int) $this->id);
2713
2714 dol_syslog(get_class($this)."::getSumPayments", LOG_DEBUG);
2715 $resql = $this->db->query($sql);
2716 if ($resql) {
2717 $obj = $this->db->fetch_object($resql);
2718 $this->db->free($resql);
2719 return (empty($obj->amount) ? 0 : $obj->amount);
2720 } else {
2721 $this->error = $this->db->lasterror();
2722 return -1;
2723 }
2724 }
2725
2734 public function computeTotalKm($fk_cat, $qty, $tva)
2735 {
2736 global $langs, $conf;
2737
2738 $cumulYearQty = 0;
2739 $ranges = array();
2740 $coef = 0;
2741
2742
2743 if ($fk_cat < 0) {
2744 $this->error = $langs->trans('ErrorBadParameterCat');
2745 return -1;
2746 }
2747
2748 if ($qty <= 0) {
2749 $this->error = $langs->trans('ErrorBadParameterQty');
2750 return -1;
2751 }
2752
2753 $currentUser = new User($this->db);
2754 $currentUser->fetch($this->fk_user);
2755 $currentUser->loadRights('expensereport');
2756 //Clean
2757 $qty = (float) price2num($qty);
2758
2759 $sql = " SELECT r.range_ik, t.ikoffset, t.coef";
2760 $sql .= " FROM ".MAIN_DB_PREFIX."expensereport_ik t";
2761 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."c_exp_tax_range r ON r.rowid = t.fk_range";
2762 $sql .= " WHERE t.fk_c_exp_tax_cat = ".(int) $fk_cat;
2763 $sql .= " ORDER BY r.range_ik ASC";
2764
2765 dol_syslog("expenseReport::computeTotalkm sql=".$sql, LOG_DEBUG);
2766
2767 $result = $this->db->query($sql);
2768
2769 if ($result) {
2770 if (getDolGlobalInt('EXPENSEREPORT_CALCULATE_MILEAGE_EXPENSE_COEFFICIENT_ON_CURRENT_YEAR')) {
2771 $arrayDate = dol_getdate(dol_now());
2772 $sql = " SELECT count(n.qty) as cumul FROM ".MAIN_DB_PREFIX."expensereport_det n";
2773 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."expensereport e ON e.rowid = n.fk_expensereport";
2774 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."c_type_fees tf ON tf.id = n.fk_c_type_fees";
2775 $sql .= " WHERE e.fk_user_author = ".(int) $this->fk_user_author;
2776 $sql .= " AND YEAR(n.date) = ".(int) $arrayDate['year'];
2777 $sql .= " AND tf.code = 'EX_KME' ";
2778 $sql .= " AND e.fk_statut = ".(int) ExpenseReport::STATUS_VALIDATED;
2779
2780 $resql = $this->db->query($sql);
2781
2782 if ($resql) {
2783 $obj = $this->db->fetch_object($resql);
2784 $cumulYearQty = $obj->cumul;
2785 }
2786
2787 $qty += (float) $cumulYearQty;
2788 }
2789
2790 $num = $this->db->num_rows($result);
2791
2792 if ($num) {
2793 for ($i = 0; $i < $num; $i++) {
2794 $obj = $this->db->fetch_object($result);
2795
2796 $ranges[$i] = $obj;
2797 }
2798 '@phan-var-force Object[] $ranges';
2799
2800 for ($i = 0; $i < $num; $i++) {
2801 if ($i < ($num - 1)) {
2802 // @phan-suppress-next-line PhanTypeInvalidDimOffset
2803 if ($qty > $ranges[$i]->range_ik && $qty < $ranges[$i + 1]->range_ik) {
2804 $coef = $ranges[$i]->coef;
2805 $offset = $ranges[$i]->ikoffset;
2806 }
2807 } else {
2808 if ($qty > $ranges[$i]->range_ik) {
2809 $coef = $ranges[$i]->coef;
2810 $offset = $ranges[$i]->ikoffset;
2811 }
2812 }
2813 }
2814 $total_ht = $coef;
2815 return $total_ht;
2816 } else {
2817 $this->error = $langs->trans('TaxUndefinedForThisCategory');
2818 return 0;
2819 }
2820 } else {
2821 $this->error = $this->db->error()." sql=".$sql;
2822
2823 return -1;
2824 }
2825 }
2826
2834 public function getKanbanView($option = '', $arraydata = null)
2835 {
2836 global $langs;
2837
2838 if ($arraydata === null) {
2839 $arraydata = array();
2840 }
2841
2842 $selected = (empty($arraydata['selected']) ? 0 : $arraydata['selected']);
2843
2844 $return = '<div class="box-flex-item box-flex-grow-zero">';
2845 $return .= '<div class="info-box info-box-sm">';
2846 $return .= '<span class="info-box-icon bg-infobox-action">';
2847 $return .= img_picto('', $this->picto);
2848 $return .= '</span>';
2849 $return .= '<div class="info-box-content">';
2850 $return .= '<span class="info-box-ref inline-block tdoverflowmax150 valignmiddle">' . $this->getNomUrl(1) . '</span>';
2851 if ($selected >= 0) {
2852 $return .= '<input id="cb'.$this->id.'" class="flat checkforselect fright" type="checkbox" name="toselect[]" value="'.$this->id.'"'.($selected ? ' checked="checked"' : '').'>';
2853 }
2854 if (array_key_exists('userauthor', $arraydata) && $arraydata['userauthor'] instanceof User) {
2855 $return .= '<br><span class="info-box-label">'.$arraydata['userauthor']->getNomUrl(-1).'</span>';
2856 }
2857 if (isDolTms($this->date_debut) || isDolTms($this->date_fin)) {
2858 $return .= '<br><span class="info-box-label">'.dol_print_date($this->date_debut, 'day').'</span>';
2859 $return .= ' <span class="opacitymedium">'.$langs->trans("To").'</span> ';
2860 $return .= '<span class="info-box-label">'.dol_print_date($this->date_fin, 'day').'</span>';
2861 }
2862 $return .= '<br><div class="info-box-status">'.$this->getLibStatut(3).'</div>';
2863 $return .= '</div>';
2864 $return .= '</div>';
2865 $return .= '</div>';
2866 return $return;
2867 }
2868}
$object ref
Definition info.php:90
Parent class of all other business classes (invoices, contracts, proposals, orders,...
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
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.
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:168
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:64
dol_delete_preview($object)
Delete all preview files linked to object instance.
dol_now($mode='gmt')
Return date for now.
img_picto($titlealt, $picto, $moreatt='', $pictoisfullpath=0, $srconly=0, $notitle=0, $alt='', $morecss='', $marginleftonlyshort=2, $allowothertags=array())
Show picto whatever it's its name (generic function)
GETPOSTINT($paramname, $method=0)
Return the value of a $_GET or $_POST supervariable, converted into integer.
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.
img_object($titlealt, $picto, $moreatt='', $pictoisfullpath=0, $srconly=0, $notitle=0, $allowothertags=array())
Show a picto called object_picto (generic function)
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.
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_print_error($db=null, $error='', $errors=null)
Displays error message system with all the information to facilitate the diagnosis and the escalation...
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.
print $langs trans("Show") . '< td style="' . $timeColor . '" align="center"> s</td > badge status0 badge status4 badge status3 Error badge status8< td align="center">< span class="badge ' . $badge . '"></span ></td >< td align="center">< a href="#" class="button button-small" onclick="openLogModal(this)" data-req="' . dol_escape_htmltag($reqSafe) . '" data-res="' . dol_escape_htmltag($resSafe) . '" data-err="' . dol_escape_htmltag($errSafe) . '">< span class="fa fa-search-plus"></span ></a ></td ></tr >< tr >< td colspan="' . $colspan . '" class="opacitymedium"></td ></tr ></table ></div ></form > logModal none logModal none s a JSON string
buildzip.php
calcul_price_total($qty, $pu, $remise_percent_ligne, $txtva, $uselocaltax1_rate, $uselocaltax2_rate, $remise_percent_global, $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