dolibarr 23.0.3
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-2025 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 $sql .= " , date_debut = '".$this->db->idate($this->date_debut)."'";
635 $sql .= " , date_fin = '".$this->db->idate($this->date_fin)."'";
636 if ($userofexpensereport && is_object($userofexpensereport)) {
637 $sql .= " , fk_user_author = ".($userofexpensereport->id > 0 ? $userofexpensereport->id : "null"); // Note fk_user_author is not the 'author' but the guy the expense report is for.
638 }
639 $sql .= " , fk_user_validator = ".($this->fk_user_validator > 0 ? $this->fk_user_validator : "null");
640 $sql .= " , fk_user_valid = ".($this->fk_user_valid > 0 ? $this->fk_user_valid : "null");
641 $sql .= " , fk_user_approve = ".($this->fk_user_approve > 0 ? $this->fk_user_approve : "null");
642 $sql .= " , fk_user_modif = ".((int) $user->id);
643 $sql .= " , fk_statut = ".($this->fk_statut >= 0 ? $this->fk_statut : '0');
644 $sql .= " , fk_c_paiement = ".($this->fk_c_paiement > 0 ? $this->fk_c_paiement : "null");
645 $sql .= " , note_public = ".(!empty($this->note_public) ? "'".$this->db->escape($this->note_public)."'" : "''");
646 $sql .= " , note_private = ".(!empty($this->note_private) ? "'".$this->db->escape($this->note_private)."'" : "''");
647 $sql .= " , detail_refuse = ".(!empty($this->detail_refuse) ? "'".$this->db->escape($this->detail_refuse)."'" : "''");
648 $sql .= " WHERE rowid = ".((int) $this->id);
649
650 dol_syslog(get_class($this)."::update", LOG_DEBUG);
651 $result = $this->db->query($sql);
652 if ($result) {
653 $result = $this->insertExtraFields();
654 if ($result < 0) {
655 $error++;
656 }
657
658 if (!$error && !$notrigger) {
659 // Call trigger
660 $result = $this->call_trigger('EXPENSE_REPORT_MODIFY', $user);
661 if ($result < 0) {
662 $error++;
663 }
664 // End call triggers
665 }
666
667 if (!$error) {
668 $this->db->commit();
669 return 1;
670 } else {
671 $this->db->rollback();
672 $this->error = $this->db->error();
673 return -2;
674 }
675 } else {
676 $this->db->rollback();
677 $this->error = $this->db->error();
678 return -1;
679 }
680 }
681
689 public function fetch($id, $ref = '')
690 {
691 $sql = "SELECT d.rowid, d.entity, d.ref, d.note_public, d.note_private,"; // DEFAULT
692 $sql .= " d.detail_refuse, d.detail_cancel, d.fk_user_refuse, d.fk_user_cancel,"; // ACTIONS
693 $sql .= " d.date_refuse, d.date_cancel,"; // ACTIONS
694 $sql .= " d.total_ht, d.total_ttc, d.total_tva,";
695 $sql .= " d.localtax1 as total_localtax1, d.localtax2 as total_localtax2,";
696 $sql .= " d.date_debut, d.date_fin, d.date_create, d.tms as date_modif, d.date_valid, d.date_approve,"; // DATES (datetime)
697 $sql .= " d.fk_user_creat, d.fk_user_author, d.fk_user_modif, d.fk_user_validator,";
698 $sql .= " d.fk_user_valid, d.fk_user_approve,";
699 $sql .= " d.fk_statut as status, d.fk_c_paiement, d.paid";
700 $sql .= " FROM ".MAIN_DB_PREFIX.$this->table_element." as d";
701 if ($ref) {
702 $sql .= " WHERE d.ref = '".$this->db->escape($ref)."'";
703 $sql .= " AND d.entity IN (".getEntity('expensereport').")";
704 } else {
705 $sql .= " WHERE d.rowid = ".((int) $id);
706 }
707 //$sql.= $restrict;
708
709 dol_syslog(get_class($this)."::fetch", LOG_DEBUG);
710 $resql = $this->db->query($sql);
711 if ($resql) {
712 $obj = $this->db->fetch_object($resql);
713 if ($obj) {
714 $this->id = $obj->rowid;
715 $this->ref = $obj->ref;
716
717 $this->entity = $obj->entity;
718
719 $this->total_ht = $obj->total_ht;
720 $this->total_tva = $obj->total_tva;
721 $this->total_ttc = $obj->total_ttc;
722 $this->localtax1 = $obj->total_localtax1; // For backward compatibility
723 $this->localtax2 = $obj->total_localtax2; // For backward compatibility
724 $this->total_localtax1 = $obj->total_localtax1;
725 $this->total_localtax2 = $obj->total_localtax2;
726
727 $this->note_public = $obj->note_public;
728 $this->note_private = $obj->note_private;
729 $this->detail_refuse = $obj->detail_refuse;
730 $this->detail_cancel = $obj->detail_cancel;
731
732 $this->date_debut = $this->db->jdate($obj->date_debut);
733 $this->date_fin = $this->db->jdate($obj->date_fin);
734 $this->date_valid = $this->db->jdate($obj->date_valid);
735 $this->date_approve = $this->db->jdate($obj->date_approve);
736 $this->date_create = $this->db->jdate($obj->date_create);
737 $this->date_modif = $this->db->jdate($obj->date_modif);
738 $this->date_refuse = $this->db->jdate($obj->date_refuse);
739 $this->date_cancel = $this->db->jdate($obj->date_cancel);
740
741 $this->fk_user_creat = $obj->fk_user_creat;
742 $this->fk_user_author = $obj->fk_user_author; // Note fk_user_author is not the 'author' but the guy the expense report is for.
743 $this->fk_user_modif = $obj->fk_user_modif;
744 $this->fk_user_validator = $obj->fk_user_validator;
745 $this->fk_user_valid = $obj->fk_user_valid;
746 $this->fk_user_refuse = $obj->fk_user_refuse;
747 $this->fk_user_cancel = $obj->fk_user_cancel;
748 $this->fk_user_approve = $obj->fk_user_approve;
749
750 $user_author = new User($this->db);
751 if ($this->fk_user_author > 0) {
752 $user_author->fetch($this->fk_user_author);
753 }
754
755 $this->user_author_infos = dolGetFirstLastname($user_author->firstname, $user_author->lastname);
756
757 $user_approver = new User($this->db);
758 if ($this->fk_user_approve > 0) {
759 $user_approver->fetch($this->fk_user_approve);
760 } elseif ($this->fk_user_validator > 0) {
761 $user_approver->fetch($this->fk_user_validator); // For backward compatibility
762 }
763 $this->user_validator_infos = dolGetFirstLastname($user_approver->firstname, $user_approver->lastname);
764
765 $this->fk_statut = (int) $obj->status; // deprecated
766 $this->status = (int) $obj->status;
767 $this->fk_c_paiement = $obj->fk_c_paiement;
768 $this->paid = $obj->paid;
769
770 if ($this->status == self::STATUS_APPROVED || $this->status == self::STATUS_CLOSED) {
771 $user_valid = new User($this->db);
772 if ($this->fk_user_valid > 0) {
773 $user_valid->fetch($this->fk_user_valid);
774 }
775 $this->user_valid_infos = dolGetFirstLastname($user_valid->firstname, $user_valid->lastname);
776 }
777
778 $this->fetch_optionals();
779
780 $result = $this->fetch_lines();
781
782 return $result;
783 } else {
784 return 0;
785 }
786 } else {
787 $this->error = $this->db->lasterror();
788 return -1;
789 }
790 }
791
792 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
803 public function set_paid($id, $fuser, $notrigger = 0)
804 {
805 // phpcs:enable
806 dol_syslog(get_class($this)."::set_paid is deprecated, use setPaid instead", LOG_NOTICE);
807 return $this->setPaid($id, $fuser, $notrigger);
808 }
809
818 public function setPaid($id, $fuser, $notrigger = 0)
819 {
820 $error = 0;
821 $this->db->begin();
822
823 $sql = "UPDATE ".MAIN_DB_PREFIX."expensereport";
824 $sql .= " SET fk_statut = ".self::STATUS_CLOSED.", paid = 1";
825 $sql .= " WHERE rowid = ".((int) $id)." AND fk_statut = ".self::STATUS_APPROVED;
826
827 dol_syslog(get_class($this)."::setPaid", LOG_DEBUG);
828 $resql = $this->db->query($sql);
829 if ($resql) {
830 if ($this->db->affected_rows($resql)) {
831 if (!$notrigger) {
832 // Call trigger
833 $result = $this->call_trigger('EXPENSE_REPORT_PAID', $fuser);
834
835 if ($result < 0) {
836 $error++;
837 }
838 // End call triggers
839 }
840
841 if (empty($error)) {
842 $this->db->commit();
843
845 $this->paid = 1;
846
847 return 1;
848 } else {
849 $this->db->rollback();
850 $this->error = $this->db->error();
851 return -2;
852 }
853 } else {
854 $this->db->commit();
855 return 0;
856 }
857 } else {
858 $this->db->rollback();
859 dol_print_error($this->db);
860 return -1;
861 }
862 }
863
870 public function getLibStatut($mode = 0)
871 {
872 return $this->LibStatut($this->status, $mode);
873 }
874
875 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
883 public function LibStatut($status, $mode = 0)
884 {
885 // phpcs:enable
886 global $langs;
887
888 $labelStatus = $langs->transnoentitiesnoconv($this->labelStatus[$status]);
889 $labelStatusShort = $langs->transnoentitiesnoconv($this->labelStatusShort[$status]);
890
891 $statuslogo = array(0 => 'status0', 2 => 'status1', 4 => 'status6', 5 => 'status4', 6 => 'status6', 99 => 'status5');
892
893 $statusType = $statuslogo[$status];
894
895 return dolGetStatus($labelStatus, $labelStatusShort, '', $statusType, $mode);
896 }
897
898
905 public function info($id)
906 {
907 global $conf;
908
909 $sql = "SELECT f.rowid,";
910 $sql .= " f.date_create as datec,";
911 $sql .= " f.tms as date_modification,";
912 $sql .= " f.date_valid as datev,";
913 $sql .= " f.date_approve as datea,";
914 $sql .= " f.fk_user_creat as fk_user_creation,";
915 $sql .= " f.fk_user_author as fk_user_author,";
916 $sql .= " f.fk_user_modif as fk_user_modification,";
917 $sql .= " f.fk_user_valid,";
918 $sql .= " f.fk_user_approve";
919 $sql .= " FROM ".MAIN_DB_PREFIX."expensereport as f";
920 $sql .= " WHERE f.rowid = ".((int) $id);
921 $sql .= " AND f.entity = ".$conf->entity;
922
923
924
925 $resql = $this->db->query($sql);
926 if ($resql) {
927 if ($this->db->num_rows($resql)) {
928 $obj = $this->db->fetch_object($resql);
929
930 $this->id = $obj->rowid;
931
932 $this->date_creation = $this->db->jdate($obj->datec);
933 $this->date_modification = $this->db->jdate($obj->date_modification);
934 $this->date_validation = $this->db->jdate($obj->datev);
935 $this->date_approbation = $this->db->jdate($obj->datea);
936
937 $this->user_creation_id = $obj->fk_user_author;
938 $this->user_creation_id = $obj->fk_user_creation;
939 $this->user_validation_id = $obj->fk_user_valid;
940 $this->user_modification_id = $obj->fk_user_modification;
941 $this->user_approve_id = $obj->fk_user_approve;
942 }
943 $this->db->free($resql);
944 } else {
945 dol_print_error($this->db);
946 }
947 }
948
949
950
958 public function initAsSpecimen()
959 {
960 global $user, $langs;
961
962 $now = dol_now();
963
964 // Initialise parameters
965 $this->id = 0;
966 $this->ref = 'SPECIMEN';
967 $this->specimen = 1;
968 $this->entity = 1;
969 $this->date_create = $now;
970 $this->date_debut = $now;
971 $this->date_fin = $now;
972 $this->date_valid = $now;
973 $this->date_approve = $now;
974
975 $type_fees_id = 2; // TF_TRIP
976
977 $this->status = 5;
978
979 $this->fk_user_author = $user->id;
980 $this->fk_user_validator = $user->id;
981 $this->fk_user_valid = $user->id;
982 $this->fk_user_approve = $user->id;
983
984 $this->note_private = 'Private note';
985 $this->note_public = 'SPECIMEN';
986 $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)
987 $xnbp = 0;
988 while ($xnbp < $nbp) {
989 $line = new ExpenseReportLine($this->db);
990 $line->comments = $langs->trans("Comment")." ".$xnbp;
991 $line->date = ($now - 3600 * (1 + $xnbp));
992 $line->total_ht = 100;
993 $line->total_tva = 20;
994 $line->total_ttc = 120;
995 $line->qty = 1;
996 $line->vatrate = 20;
997 $line->value_unit = 120;
998 $line->fk_expensereport = 0;
999 $line->type_fees_code = 'TRA';
1000 $line->fk_c_type_fees = $type_fees_id;
1001
1002 $line->projet_ref = 'ABC';
1003
1004 $this->lines[$xnbp] = $line;
1005 $xnbp++;
1006
1007 $this->total_ht += $line->total_ht;
1008 $this->total_tva += $line->total_tva;
1009 $this->total_ttc += $line->total_ttc;
1010 }
1011
1012 return 1;
1013 }
1014
1015 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1023 public function fetch_line_by_project($projectid, $user)
1024 {
1025 // phpcs:enable
1026 global $langs;
1027
1028 $langs->load('trips');
1029
1030 if ($user->hasRight('expensereport', 'lire')) {
1031 $sql = "SELECT de.fk_expensereport, de.date, de.comments, de.total_ht, de.total_ttc";
1032 $sql .= " FROM ".MAIN_DB_PREFIX."expensereport_det as de";
1033 $sql .= " WHERE de.fk_projet = ".((int) $projectid);
1034
1035 dol_syslog(get_class($this)."::fetch", LOG_DEBUG);
1036 $result = $this->db->query($sql);
1037 if ($result) {
1038 $num = $this->db->num_rows($result);
1039 $i = 0;
1040 $total_HT = 0;
1041 $total_TTC = 0;
1042
1043 while ($i < $num) {
1044 $objp = $this->db->fetch_object($result);
1045
1046 $sql2 = "SELECT d.rowid, d.fk_user_author, d.ref, d.fk_statut as status";
1047 $sql2 .= " FROM ".MAIN_DB_PREFIX."expensereport as d";
1048 $sql2 .= " WHERE d.rowid = ".((int) $objp->fk_expensereport);
1049
1050 $result2 = $this->db->query($sql2);
1051 $obj = $this->db->fetch_object($result2);
1052
1053 $objp->fk_user_author = $obj->fk_user_author;
1054 $objp->ref = $obj->ref;
1055 $objp->fk_c_expensereport_status = $obj->status;
1056 $objp->rowid = $obj->rowid;
1057
1058 $total_HT += $objp->total_ht;
1059 $total_TTC += $objp->total_ttc;
1060 $author = new User($this->db);
1061 $author->fetch($objp->fk_user_author);
1062
1063 print '<tr>';
1064 print '<td>';
1065 print '<a href="'.dolBuildUrl(DOL_URL_ROOT.'/expensereport/card.php', ['id' => $objp->rowid]).'">'.$objp->ref_num.'</a>';
1066 print '</td>';
1067 print '<td class="center">'.dol_print_date($objp->date, 'day').'</td>';
1068 print '<td>'.$author->getNomUrl(1).'</td>';
1069 print '<td>'.$objp->comments.'</td>';
1070 print '<td class="right">'.price($objp->total_ht).'</td>';
1071 print '<td class="right">'.price($objp->total_ttc).'</td>';
1072 print '<td class="right">';
1073
1074 switch ($objp->fk_c_expensereport_status) {
1075 case 4:
1076 print img_picto($langs->trans('StatusOrderCanceled'), 'statut5');
1077 break;
1078 case 1:
1079 print $langs->trans('Draft').' '.img_picto($langs->trans('Draft'), 'statut0');
1080 break;
1081 case 2:
1082 print $langs->trans('TripForValid').' '.img_picto($langs->trans('TripForValid'), 'statut3');
1083 break;
1084 case 5:
1085 print $langs->trans('TripForPaid').' '.img_picto($langs->trans('TripForPaid'), 'statut3');
1086 break;
1087 case 6:
1088 print $langs->trans('TripPaid').' '.img_picto($langs->trans('TripPaid'), 'statut4');
1089 break;
1090 }
1091 /*
1092 if ($status==4) return img_picto($langs->trans('StatusOrderCanceled'),'statut5');
1093 if ($status==1) return img_picto($langs->trans('StatusOrderDraft'),'statut0');
1094 if ($status==2) return img_picto($langs->trans('StatusOrderValidated'),'statut1');
1095 if ($status==2) return img_picto($langs->trans('StatusOrderOnProcess'),'statut3');
1096 if ($status==5) return img_picto($langs->trans('StatusOrderToBill'),'statut4');
1097 if ($status==6) return img_picto($langs->trans('StatusOrderOnProcess'),'statut6');
1098 */
1099 print '</td>';
1100 print '</tr>';
1101
1102 $i++;
1103 }
1104
1105 print '<tr class="liste_total"><td colspan="4">'.$langs->trans("Number").': '.$i.'</td>';
1106 print '<td class="right" width="100">'.$langs->trans("TotalHT").' : '.price($total_HT).'</td>';
1107 print '<td class="right" width="100">'.$langs->trans("TotalTTC").' : '.price($total_TTC).'</td>';
1108 print '<td>&nbsp;</td>';
1109 print '</tr>';
1110 } else {
1111 $this->error = $this->db->lasterror();
1112 return -1;
1113 }
1114 }
1115
1116 return 0;
1117 }
1118
1119 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1125 public function fetch_lines()
1126 {
1127 // phpcs:enable
1128 $this->lines = array();
1129
1130 $sql = ' SELECT de.rowid, de.comments, de.qty, de.value_unit, de.date, de.rang,';
1131 $sql .= " de.".$this->fk_element.", de.fk_c_type_fees, de.fk_c_exp_tax_cat, de.fk_projet as fk_project,";
1132 $sql .= ' de.tva_tx, de.vat_src_code,';
1133 $sql .= ' de.localtax1_tx, de.localtax2_tx, de.localtax1_type, de.localtax2_type,';
1134 $sql .= ' de.fk_ecm_files,';
1135 $sql .= ' de.total_ht, de.total_tva, de.total_ttc,';
1136 $sql .= ' de.total_localtax1, de.total_localtax2, de.rule_warning_message,';
1137 $sql .= ' ctf.code as code_type_fees, ctf.label as label_type_fees, ctf.accountancy_code as accountancy_code_type_fees,';
1138 $sql .= ' p.ref as ref_projet, p.title as title_projet';
1139 $sql .= ' FROM '.MAIN_DB_PREFIX.$this->table_element_line.' as de';
1140 $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_type_fees as ctf ON de.fk_c_type_fees = ctf.id';
1141 $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'projet as p ON de.fk_projet = p.rowid';
1142 $sql .= " WHERE de.".$this->fk_element." = ".((int) $this->id);
1143 if (getDolGlobalString('EXPENSEREPORT_LINES_SORTED_BY_ROWID')) {
1144 $sql .= ' ORDER BY de.rang ASC, de.rowid ASC';
1145 } else {
1146 $sql .= ' ORDER BY de.rang ASC, de.date ASC';
1147 }
1148
1149 $resql = $this->db->query($sql);
1150 if ($resql) {
1151 $num = $this->db->num_rows($resql);
1152 $i = 0;
1153 while ($i < $num) {
1154 $objp = $this->db->fetch_object($resql);
1155
1156 $deplig = new ExpenseReportLine($this->db);
1157
1158 $deplig->rowid = $objp->rowid;
1159 $deplig->id = $objp->rowid;
1160 $deplig->comments = $objp->comments;
1161 $deplig->qty = $objp->qty;
1162 $deplig->value_unit = $objp->value_unit;
1163 $deplig->date = $objp->date;
1164 $deplig->dates = $this->db->jdate($objp->date);
1165
1166 $deplig->fk_expensereport = $objp->fk_expensereport;
1167 $deplig->fk_c_type_fees = $objp->fk_c_type_fees;
1168 $deplig->fk_c_exp_tax_cat = $objp->fk_c_exp_tax_cat;
1169 $deplig->fk_projet = $objp->fk_project; // deprecated
1170 $deplig->fk_project = $objp->fk_project;
1171 $deplig->fk_ecm_files = $objp->fk_ecm_files;
1172
1173 $deplig->total_ht = $objp->total_ht;
1174 $deplig->total_tva = $objp->total_tva;
1175 $deplig->total_ttc = $objp->total_ttc;
1176 $deplig->total_localtax1 = $objp->total_localtax1;
1177 $deplig->total_localtax2 = $objp->total_localtax2;
1178
1179 $deplig->type_fees_code = empty($objp->code_type_fees) ? 'TF_OTHER' : $objp->code_type_fees;
1180 $deplig->type_fees_libelle = $objp->label_type_fees;
1181 $deplig->type_fees_accountancy_code = $objp->accountancy_code_type_fees;
1182
1183 $deplig->tva_tx = $objp->tva_tx;
1184 $deplig->vatrate = $objp->tva_tx;
1185 $deplig->vat_src_code = $objp->vat_src_code;
1186 $deplig->localtax1_tx = $objp->localtax1_tx;
1187 $deplig->localtax2_tx = $objp->localtax2_tx;
1188 $deplig->localtax1_type = $objp->localtax1_type;
1189 $deplig->localtax2_type = $objp->localtax2_type;
1190
1191 $deplig->projet_ref = $objp->ref_projet;
1192 $deplig->projet_title = $objp->title_projet;
1193
1194 $deplig->rule_warning_message = $objp->rule_warning_message;
1195
1196 $deplig->rang = $objp->rang;
1197
1198 $this->lines[$i] = $deplig;
1199
1200 $i++;
1201 }
1202 $this->db->free($resql);
1203 return 1;
1204 } else {
1205 $this->error = $this->db->lasterror();
1206 dol_syslog(get_class($this)."::fetch_lines: Error ".$this->error, LOG_ERR);
1207 return -3;
1208 }
1209 }
1210
1211
1219 public function delete($user = null, $notrigger = 0)
1220 {
1221 global $conf;
1222 require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
1223
1224 $error = 0;
1225
1226 $this->db->begin();
1227
1228 if (!$notrigger) {
1229 // Call trigger
1230 $result = $this->call_trigger('EXPENSE_REPORT_DELETE', $user);
1231 if ($result < 0) {
1232 $error++;
1233 }
1234 // End call triggers
1235 }
1236
1237 // Delete extrafields of lines and lines
1238 if (!$error && !empty($this->table_element_line)) {
1239 $tabletodelete = $this->table_element_line;
1240 //$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).")";
1241 $sql = "DELETE FROM ".MAIN_DB_PREFIX.$tabletodelete." WHERE ".$this->fk_element." = ".((int) $this->id);
1242 if (!$this->db->query($sql)) {
1243 $error++;
1244 $this->error = $this->db->lasterror();
1245 $this->errors[] = $this->error;
1246 dol_syslog(get_class($this)."::delete error ".$this->error, LOG_ERR);
1247 }
1248 }
1249
1250 if (!$error) {
1251 // Delete linked object
1252 $res = $this->deleteObjectLinked();
1253 if ($res < 0) {
1254 $error++;
1255 }
1256 }
1257
1258 if (!$error) {
1259 // Delete linked contacts
1260 $res = $this->delete_linked_contact();
1261 if ($res < 0) {
1262 $error++;
1263 }
1264 }
1265
1266 // Removed extrafields of object
1267 if (!$error) {
1268 $result = $this->deleteExtraFields();
1269 if ($result < 0) {
1270 $error++;
1271 dol_syslog(get_class($this)."::delete error ".$this->error, LOG_ERR);
1272 }
1273 }
1274
1275 // Delete main record
1276 if (!$error) {
1277 $sql = "DELETE FROM ".MAIN_DB_PREFIX.$this->table_element." WHERE rowid = ".((int) $this->id);
1278 $res = $this->db->query($sql);
1279 if (!$res) {
1280 $error++;
1281 $this->error = $this->db->lasterror();
1282 $this->errors[] = $this->error;
1283 dol_syslog(get_class($this)."::delete error ".$this->error, LOG_ERR);
1284 }
1285 }
1286
1287 // Delete record into ECM index and physically
1288 if (!$error) {
1289 $res = $this->deleteEcmFiles(0); // Deleting files physically is done later with the dol_delete_dir_recursive
1290 $res = $this->deleteEcmFiles(1); // Deleting files physically is done later with the dol_delete_dir_recursive
1291 if (!$res) {
1292 $error++;
1293 }
1294 }
1295
1296 if (!$error) {
1297 // We remove directory
1298 $ref = dol_sanitizeFileName($this->ref);
1299 if ($conf->expensereport->multidir_output[$this->entity] && !empty($this->ref)) {
1300 $dir = $conf->expensereport->multidir_output[$this->entity]."/".$ref;
1301 $file = $dir."/".$ref.".pdf";
1302 if (file_exists($file)) {
1303 dol_delete_preview($this);
1304
1305 if (!dol_delete_file($file, 0, 0, 0, $this)) {
1306 $this->error = 'ErrorFailToDeleteFile';
1307 $this->errors[] = $this->error;
1308 $this->db->rollback();
1309 return 0;
1310 }
1311 }
1312 if (file_exists($dir)) {
1313 $res = @dol_delete_dir_recursive($dir);
1314 if (!$res) {
1315 $this->error = 'ErrorFailToDeleteDir';
1316 $this->errors[] = $this->error;
1317 $this->db->rollback();
1318 return 0;
1319 }
1320 }
1321 }
1322 }
1323
1324 if (!$error) {
1325 dol_syslog(get_class($this)."::delete ".$this->id." by ".$user->id, LOG_DEBUG);
1326 $this->db->commit();
1327 return 1;
1328 } else {
1329 $this->db->rollback();
1330 return -1;
1331 }
1332 }
1333
1341 public function setValidate($fuser, $notrigger = 0)
1342 {
1343 global $conf, $langs, $user;
1344
1345 $error = 0;
1346 $now = dol_now();
1347
1348 // Protection
1349 if ($this->status == self::STATUS_VALIDATED) {
1350 dol_syslog(get_class($this)."::valid action abandoned: already validated", LOG_WARNING);
1351 return 0;
1352 }
1353
1354 $this->date_valid = $now; // Required for the getNextNum later.
1355
1356 // Define new ref
1357 if (preg_match('/^[\‍(]?PROV/i', $this->ref) || empty($this->ref)) { // empty should not happened, but when it occurs, the test save life
1358 $num = $this->getNextNumRef();
1359 } else {
1360 $num = (string) $this->ref;
1361 }
1362 if (empty($num) || $num < 0) {
1363 return -1;
1364 }
1365
1366 $this->newref = dol_sanitizeFileName($num);
1367
1368 $this->db->begin();
1369
1370 // Validate
1371 $sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element;
1372 $sql .= " SET ref = '".$this->db->escape($num)."',";
1373 $sql .= " fk_statut = ".self::STATUS_VALIDATED.",";
1374 $sql .= " date_valid = '".$this->db->idate($this->date_valid)."',";
1375 $sql .= " fk_user_valid = ".((int) $user->id);
1376 $sql .= " WHERE rowid = ".((int) $this->id);
1377
1378 $resql = $this->db->query($sql);
1379 if ($resql) {
1380 if (!$notrigger) {
1381 // Call trigger
1382 $result = $this->call_trigger('EXPENSE_REPORT_VALIDATE', $fuser);
1383 if ($result < 0) {
1384 $error++;
1385 }
1386 // End call triggers
1387 }
1388
1389 if (!$error) {
1390 $this->oldref = $this->ref;
1391
1392 // Rename directory if dir was a temporary ref
1393 if (preg_match('/^[\‍(]?PROV/i', $this->ref)) {
1394 require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
1395
1396 // Now we rename also files into index
1397 $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)."'";
1398 $sql .= " WHERE filename LIKE '".$this->db->escape($this->ref)."%' AND filepath = 'expensereport/".$this->db->escape($this->ref)."' AND entity = ".((int) $this->entity);
1399 $resql = $this->db->query($sql);
1400 if (!$resql) {
1401 $error++;
1402 $this->error = $this->db->lasterror();
1403 }
1404 $sql = 'UPDATE '.MAIN_DB_PREFIX."ecm_files set filepath = 'expensereport/".$this->db->escape($this->newref)."'";
1405 $sql .= " WHERE filepath = 'expensereport/".$this->db->escape($this->ref)."' and entity = ".$conf->entity;
1406 $resql = $this->db->query($sql);
1407 if (!$resql) {
1408 $error++;
1409 $this->error = $this->db->lasterror();
1410 }
1411
1412 // We rename directory ($this->ref = old ref, $num = new ref) in order not to lose the attachments
1413 $oldref = dol_sanitizeFileName($this->ref);
1414 $newref = dol_sanitizeFileName($num);
1415 $dirsource = $conf->expensereport->multidir_output[$this->entity].'/'.$oldref;
1416 $dirdest = $conf->expensereport->multidir_output[$this->entity].'/'.$newref;
1417 if (!$error && file_exists($dirsource)) {
1418 dol_syslog(get_class($this)."::setValidate() rename dir ".$dirsource." into ".$dirdest);
1419
1420 if (@rename($dirsource, $dirdest)) {
1421 dol_syslog("Rename ok");
1422 // Rename docs starting with $oldref with $newref
1423 $listoffiles = dol_dir_list($dirdest, 'files', 1, '^'.preg_quote($oldref, '/'));
1424 foreach ($listoffiles as $fileentry) {
1425 $dirsource = $fileentry['name'];
1426 $dirdest = preg_replace('/^'.preg_quote($oldref, '/').'/', $newref, $dirsource);
1427 $dirsource = $fileentry['path'].'/'.$dirsource;
1428 $dirdest = $fileentry['path'].'/'.$dirdest;
1429 @rename($dirsource, $dirdest);
1430 }
1431 }
1432 }
1433 }
1434 }
1435
1436 // Set new ref and current status
1437 if (!$error) {
1438 $this->ref = $num;
1440 }
1441
1442 if (empty($error)) {
1443 $this->db->commit();
1444 return 1;
1445 } else {
1446 $this->db->rollback();
1447 $this->error = $this->db->error();
1448 return -2;
1449 }
1450 } else {
1451 $this->db->rollback();
1452 $this->error = $this->db->lasterror();
1453 return -1;
1454 }
1455 }
1456
1457 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1464 public function set_save_from_refuse($fuser)
1465 {
1466 // phpcs:enable
1467 // Sélection de la date de début de la NDF
1468 $sql = 'SELECT date_debut';
1469 $sql .= ' FROM '.MAIN_DB_PREFIX.$this->table_element;
1470 $sql .= " WHERE rowid = ".((int) $this->id);
1471
1472 $result = $this->db->query($sql);
1473
1474 $objp = $this->db->fetch_object($result);
1475
1476 $this->date_debut = $this->db->jdate($objp->date_debut);
1477
1478 if ($this->status != self::STATUS_VALIDATED) {
1479 $sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element;
1480 $sql .= " SET fk_statut = ".self::STATUS_VALIDATED;
1481 $sql .= " WHERE rowid = ".((int) $this->id);
1482
1483 dol_syslog(get_class($this)."::set_save_from_refuse", LOG_DEBUG);
1484
1485 if ($this->db->query($sql)) {
1486 return 1;
1487 } else {
1488 $this->error = $this->db->lasterror();
1489 return -1;
1490 }
1491 } else {
1492 dol_syslog(get_class($this)."::set_save_from_refuse expensereport already with save status", LOG_WARNING);
1493 }
1494
1495 return 0;
1496 }
1497
1505 public function setApproved($fuser, $notrigger = 0)
1506 {
1507 $now = dol_now();
1508 $error = 0;
1509
1510 if ($this->status != self::STATUS_APPROVED) {
1511 $this->db->begin();
1512 $sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element;
1513 $sql .= " SET ref = '".$this->db->escape($this->ref)."', fk_statut = ".self::STATUS_APPROVED.", fk_user_approve = ".((int) $fuser->id).",";
1514 $sql .= " date_approve='".$this->db->idate($now)."'";
1515 $sql .= " WHERE rowid = ".((int) $this->id);
1516 if ($this->db->query($sql)) {
1518 $this->date_approve = $now;
1519 $this->fk_user_approve = $fuser->id;
1520 if (!$notrigger) {
1521 // Call trigger
1522 $result = $this->call_trigger('EXPENSE_REPORT_APPROVE', $fuser);
1523
1524 if ($result < 0) {
1525 $error++;
1526 }
1527 // End call triggers
1528 }
1529
1530 if (empty($error)) {
1531 $this->db->commit();
1532 return 1;
1533 } else {
1534 $this->db->rollback();
1535 $this->error = $this->db->error();
1536 return -2;
1537 }
1538 } else {
1539 $this->db->rollback();
1540 $this->error = $this->db->lasterror();
1541 return -1;
1542 }
1543 } else {
1544 dol_syslog(get_class($this)."::setApproved expensereport already with approve status", LOG_WARNING);
1545 }
1546
1547 return 0;
1548 }
1549
1558 public function setDeny($fuser, $details, $notrigger = 0)
1559 {
1560 $now = dol_now();
1561 $error = 0;
1562
1563 // date de refus
1564 if ($this->status != self::STATUS_REFUSED) {
1565 $sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element;
1566 $sql .= " SET ref = '".$this->db->escape($this->ref)."', fk_statut = ".self::STATUS_REFUSED.", fk_user_refuse = ".((int) $fuser->id).",";
1567 $sql .= " date_refuse='".$this->db->idate($now)."',";
1568 $sql .= " detail_refuse='".$this->db->escape($details)."',";
1569 $sql .= " fk_user_approve = NULL";
1570 $sql .= " WHERE rowid = ".((int) $this->id);
1571 if ($this->db->query($sql)) {
1572 $this->fk_statut = 99; // deprecated
1573 $this->status = 99;
1574 $this->fk_user_refuse = $fuser->id;
1575 $this->detail_refuse = $details;
1576 $this->date_refuse = $now;
1577
1578 if (!$notrigger) {
1579 // Call trigger
1580 $result = $this->call_trigger('EXPENSE_REPORT_DENY', $fuser);
1581
1582 if ($result < 0) {
1583 $error++;
1584 }
1585 // End call triggers
1586 }
1587
1588 if (empty($error)) {
1589 $this->db->commit();
1590 return 1;
1591 } else {
1592 $this->db->rollback();
1593 $this->error = $this->db->error();
1594 return -2;
1595 }
1596 } else {
1597 $this->db->rollback();
1598 $this->error = $this->db->lasterror();
1599 return -1;
1600 }
1601 } else {
1602 dol_syslog(get_class($this)."::setDeny expensereport already with refuse status", LOG_WARNING);
1603 }
1604
1605 return 0;
1606 }
1607
1608 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1618 public function set_unpaid($fuser, $notrigger = 0)
1619 {
1620 // phpcs:enable
1621 dol_syslog(get_class($this)."::set_unpaid is deprecated, use setUnpaid instead", LOG_NOTICE);
1622 return $this->setUnpaid($fuser, $notrigger);
1623 }
1624
1632 public function setUnpaid($fuser, $notrigger = 0)
1633 {
1634 $error = 0;
1635
1636 if ($this->paid) {
1637 $this->db->begin();
1638
1639 $sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element;
1640 $sql .= " SET paid = 0, fk_statut = ".self::STATUS_APPROVED;
1641 $sql .= " WHERE rowid = ".((int) $this->id);
1642
1643 dol_syslog(get_class($this)."::set_unpaid", LOG_DEBUG);
1644
1645 if ($this->db->query($sql)) {
1646 if (!$notrigger) {
1647 // Call trigger
1648 $result = $this->call_trigger('EXPENSE_REPORT_UNPAID', $fuser);
1649
1650 if ($result < 0) {
1651 $error++;
1652 }
1653 // End call triggers
1654 }
1655
1656 if (empty($error)) {
1657 $this->db->commit();
1658 return 1;
1659 } else {
1660 $this->db->rollback();
1661 $this->error = $this->db->error();
1662 return -2;
1663 }
1664 } else {
1665 $this->db->rollback();
1666 $this->error = $this->db->error();
1667 return -1;
1668 }
1669 } else {
1670 dol_syslog(get_class($this)."::set_unpaid expensereport already with unpaid status", LOG_WARNING);
1671 }
1672
1673 return 0;
1674 }
1675
1676 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1685 public function set_cancel($fuser, $detail, $notrigger = 0)
1686 {
1687 // phpcs:enable
1688 $error = 0;
1689 $this->date_cancel = $this->db->idate(dol_now());
1690 if ($this->status != self::STATUS_CANCELED) {
1691 $this->db->begin();
1692
1693 $sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element;
1694 $sql .= " SET fk_statut = ".((int) self::STATUS_CANCELED).", fk_user_cancel = ".((int) $fuser->id);
1695 $sql .= ", date_cancel = '".$this->db->idate($this->date_cancel)."'";
1696 $sql .= ", detail_cancel = '".$this->db->escape($detail)."'";
1697 $sql .= " WHERE rowid = ".((int) $this->id);
1698
1699 dol_syslog(get_class($this)."::set_cancel", LOG_DEBUG);
1700
1701 if ($this->db->query($sql)) {
1702 if (!$notrigger) {
1703 // Call trigger
1704 $result = $this->call_trigger('EXPENSE_REPORT_CANCEL', $fuser);
1705
1706 if ($result < 0) {
1707 $error++;
1708 }
1709 // End call triggers
1710 }
1711
1712 if (empty($error)) {
1713 $this->db->commit();
1714 return 1;
1715 } else {
1716 $this->db->rollback();
1717 $this->error = $this->db->error();
1718 return -2;
1719 }
1720 } else {
1721 $this->db->rollback();
1722 $this->error = $this->db->error();
1723 return -1;
1724 }
1725 } else {
1726 dol_syslog(get_class($this)."::set_cancel expensereport already with cancel status", LOG_WARNING);
1727 }
1728 return 0;
1729 }
1730
1736 public function getNextNumRef()
1737 {
1738 global $langs, $conf;
1739 $langs->load("trips");
1740
1741 if (getDolGlobalString('EXPENSEREPORT_ADDON')) {
1742 $mybool = false;
1743
1744 $file = getDolGlobalString('EXPENSEREPORT_ADDON') . ".php";
1745 $classname = getDolGlobalString('EXPENSEREPORT_ADDON');
1746
1747 // Include file with class
1748 $dirmodels = array_merge(array('/'), (array) $conf->modules_parts['models']);
1749 foreach ($dirmodels as $reldir) {
1750 $dir = dol_buildpath($reldir."core/modules/expensereport/");
1751
1752 // Load file with numbering class (if found)
1753 $mybool = ((bool) @include_once $dir.$file) || $mybool;
1754 }
1755
1756 if (!$mybool) {
1757 dol_print_error(null, "Failed to include file ".$file);
1758 return '';
1759 }
1760
1761 $obj = new $classname();
1762 '@phan-var-force ModeleNumRefExpenseReport $obj';
1763 $numref = $obj->getNextValue($this);
1764
1765 if ($numref != "") {
1766 return $numref;
1767 } else {
1768 $this->error = $obj->error;
1769 $this->errors = $obj->errors;
1770 //dol_print_error($this->db,get_class($this)."::getNextNumRef ".$obj->error);
1771 return -1;
1772 }
1773 } else {
1774 $this->error = "Error_EXPENSEREPORT_ADDON_NotDefined";
1775 return -2;
1776 }
1777 }
1778
1785 public function getTooltipContentArray($params)
1786 {
1787 global $conf, $langs;
1788
1789 $langs->load('trips');
1790
1791 $nofetch = !empty($params['nofetch']);
1792 $moretitle = $params['moretitle'] ?? '';
1793
1794 $datas = array();
1795 $datas['picto'] = img_picto('', $this->picto).' <u class="paddingrightonly">'.$langs->trans("ExpenseReport").'</u>';
1796 if (isset($this->status)) {
1797 $datas['picto'] .= ' '.$this->getLibStatut(5);
1798 }
1799 if ($moretitle) {
1800 $datas['picto'] .= ' - '.$moretitle;
1801 }
1802 if (!empty($this->ref)) {
1803 $datas['ref'] = '<br><b>'.$langs->trans('Ref').':</b> '.$this->ref;
1804 }
1805 if (!empty($this->total_ht)) {
1806 $datas['total_ht'] = '<br><b>'.$langs->trans('AmountHT').':</b> '.price($this->total_ht, 0, $langs, 0, -1, -1, $conf->currency);
1807 }
1808 if (!empty($this->total_tva)) {
1809 $datas['total_tva'] = '<br><b>'.$langs->trans('VAT').':</b> '.price($this->total_tva, 0, $langs, 0, -1, -1, $conf->currency);
1810 }
1811 if (!empty($this->total_ttc)) {
1812 $datas['total_ttc'] = '<br><b>'.$langs->trans('AmountTTC').':</b> '.price($this->total_ttc, 0, $langs, 0, -1, -1, $conf->currency);
1813 }
1814
1815 return $datas;
1816 }
1817
1830 public function getNomUrl($withpicto = 0, $option = '', $max = 0, $short = 0, $moretitle = '', $notooltip = 0, $save_lastsearch_value = -1)
1831 {
1832 global $langs, $hookmanager;
1833
1834 $result = '';
1835
1836 $baseurl = DOL_URL_ROOT.'/expensereport/card.php';
1837 $query = ['id' => $this->id];
1838
1839 if ($short) {
1840 return dolBuildUrl($baseurl, $query);
1841 }
1842
1843 $params = [
1844 'id' => $this->id,
1845 'objecttype' => $this->element,
1846 'option' => $option,
1847 'moretitle' => $moretitle,
1848 'nofetch' => 1,
1849 ];
1850 $classfortooltip = 'classfortooltip';
1851 $dataparams = '';
1852 if (getDolGlobalInt('MAIN_ENABLE_AJAX_TOOLTIP')) {
1853 $classfortooltip = 'classforajaxtooltip';
1854 $dataparams = ' data-params="'.dol_escape_htmltag(json_encode($params)).'"';
1855 $label = '';
1856 } else {
1857 $label = implode($this->getTooltipContentArray($params));
1858 }
1859
1860 if ($option != 'nolink') {
1861 // Add param to save lastsearch_values or not
1862 $add_save_lastsearch_values = ($save_lastsearch_value == 1 ? 1 : 0);
1863 if ($save_lastsearch_value == -1 && isset($_SERVER["PHP_SELF"]) && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) {
1864 $add_save_lastsearch_values = 1;
1865 }
1866 if ($add_save_lastsearch_values) {
1867 $query += ['save_lastsearch_values' => 1];
1868 }
1869 }
1870 $url = dolBuildUrl($baseurl, $query);
1871
1872 $ref = $this->ref;
1873 if (empty($ref)) {
1874 $ref = $this->id;
1875 }
1876
1877 $linkclose = '';
1878 if (empty($notooltip)) {
1879 if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
1880 $label = $langs->trans("ShowExpenseReport");
1881 $linkclose .= ' alt="'.dolPrintHTMLForAttribute($label).'"';
1882 }
1883 $linkclose .= ($label ? ' title="'.dolPrintHTMLForAttribute($label).'"' : ' title="tocomplete"');
1884 $linkclose .= $dataparams.' class="'.$classfortooltip.'"';
1885 }
1886
1887 $linkstart = '<a href="'.$url.'"';
1888 $linkstart .= $linkclose.'>';
1889 $linkend = '</a>';
1890
1891 $result .= $linkstart;
1892 if ($withpicto) {
1893 $result .= img_object(($notooltip ? '' : $label), ($this->picto ? $this->picto : 'generic'), ($notooltip ? (($withpicto != 2) ? 'class="paddingright"' : '') : 'class="'.(($withpicto != 2) ? 'paddingright ' : '').'"'), 0, 0, $notooltip ? 0 : 1);
1894 }
1895 if ($withpicto != 2) {
1896 $result .= ($max ? dol_trunc($ref, $max) : $ref);
1897 }
1898 $result .= $linkend;
1899
1900 global $action;
1901 $hookmanager->initHooks(array($this->element . 'dao'));
1902 $parameters = array('id' => $this->id, 'getnomurl' => &$result);
1903 $reshook = $hookmanager->executeHooks('getNomUrl', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
1904 if ($reshook > 0) {
1905 $result = $hookmanager->resPrint;
1906 } else {
1907 $result .= $hookmanager->resPrint;
1908 }
1909 return $result;
1910 }
1911
1912 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1920 public function update_totaux_add($ligne_total_ht, $ligne_total_tva)
1921 {
1922 // phpcs:enable
1923 $this->total_ht += (float) $ligne_total_ht;
1924 $this->total_tva += (float) $ligne_total_tva;
1925 $this->total_ttc += $this->total_tva;
1926
1927 $sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element." SET";
1928 $sql .= " total_ht = ".((float) $this->total_ht);
1929 $sql .= " , total_ttc = ".((float) $this->total_ttc);
1930 $sql .= " , total_tva = ".((float) $this->total_tva);
1931 $sql .= " WHERE rowid = ".((int) $this->id);
1932
1933 $result = $this->db->query($sql);
1934 if ($result) {
1935 return 1;
1936 } else {
1937 $this->error = $this->db->error();
1938 return -1;
1939 }
1940 }
1941
1957 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)
1958 {
1959 global $langs, $mysoc;
1960
1961 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);
1962
1963 if ($this->status == self::STATUS_DRAFT || $this->status == self::STATUS_REFUSED) {
1964 if (empty($qty)) {
1965 $qty = 0;
1966 }
1967 if (empty($fk_c_type_fees) || $fk_c_type_fees < 0) {
1968 $fk_c_type_fees = 0;
1969 }
1970 if (empty($fk_c_exp_tax_cat) || $fk_c_exp_tax_cat < 0) {
1971 $fk_c_exp_tax_cat = 0;
1972 }
1973 if (empty($vatrate) || $vatrate < 0) {
1974 $vatrate = 0;
1975 }
1976 if (empty($date)) {
1977 $date = '';
1978 }
1979 if (empty($fk_project)) {
1980 $fk_project = 0;
1981 }
1982
1983 $qty = (float) price2num($qty);
1984 if (!preg_match('/\s*\‍((.*)\‍)/', $vatrate)) {
1985 $vatrate = price2num($vatrate); // $txtva can have format '5.0 (XXX)' or '5'
1986 }
1987 $up = price2num($up);
1988
1989 $this->db->begin();
1990
1991 $this->line = new ExpenseReportLine($this->db);
1992
1993 // We don't know seller and buyer for expense reports
1994 $seller = $mysoc; // We use same than current company (expense report are often done in same country)
1995 $seller->tva_assuj = 1; // Most seller uses vat
1996 $buyer = new Societe($this->db);
1997
1998 $localtaxes_type = getLocalTaxesFromRate($vatrate, 0, $buyer, $seller);
1999
2000 $vat_src_code = '';
2001 $reg = array();
2002 if (preg_match('/\s*\‍((.*)\‍)/', $vatrate, $reg)) {
2003 $vat_src_code = $reg[1];
2004 $vatrate = preg_replace('/\s*\‍(.*\‍)/', '', $vatrate); // Remove code into vatrate.
2005 }
2006 $vatrate = preg_replace('/\*/', '', $vatrate);
2007
2008 $tmp = calcul_price_total($qty, (float) $up, 0, (float) price2num($vatrate), -1, -1, 0, 'TTC', 0, $type, $seller, $localtaxes_type);
2009
2010 $this->line->value_unit = $up;
2011
2012 $this->line->vat_src_code = $vat_src_code;
2013 $this->line->vatrate = price2num($vatrate);
2014 $this->line->localtax1_tx = $localtaxes_type[1];
2015 $this->line->localtax2_tx = $localtaxes_type[3];
2016 $this->line->localtax1_type = $localtaxes_type[0];
2017 $this->line->localtax2_type = $localtaxes_type[2];
2018
2019 $this->line->total_ttc = (float) $tmp[2];
2020 $this->line->total_ht = (float) $tmp[0];
2021 $this->line->total_tva = (float) $tmp[1];
2022 $this->line->total_localtax1 = (float) $tmp[9];
2023 $this->line->total_localtax2 = (float) $tmp[10];
2024
2025 $this->line->fk_expensereport = $this->id;
2026 $this->line->qty = $qty;
2027 $this->line->date = $date;
2028 $this->line->fk_c_type_fees = $fk_c_type_fees;
2029 $this->line->fk_c_exp_tax_cat = $fk_c_exp_tax_cat;
2030 $this->line->comments = $comments;
2031 $this->line->fk_projet = $fk_project; // deprecated
2032 $this->line->fk_project = $fk_project;
2033
2034 $this->line->fk_ecm_files = $fk_ecm_files;
2035
2036 $this->applyOffset();
2037 $this->checkRules($type, $seller);
2038
2039 $result = $this->line->insert(0, true);
2040 if ($result > 0) {
2041 $result = $this->update_price(1); // This method is designed to add line from user input so total calculation must be done using 'auto' mode.
2042 if ($result > 0) {
2043 $this->db->commit();
2044 return $this->line->id;
2045 } else {
2046 $this->db->rollback();
2047 return -1;
2048 }
2049 } else {
2050 $this->error = $this->line->error;
2051 dol_syslog(get_class($this)."::addline error=".$this->error, LOG_ERR);
2052 $this->db->rollback();
2053 return -2;
2054 }
2055 } else {
2056 dol_syslog(get_class($this)."::addline status of expense report must be Draft to allow use of ->addline()", LOG_ERR);
2057 $this->error = 'ErrorExpenseNotDraftAndNotRefused';
2058 return -3;
2059 }
2060 }
2061
2069 public function checkRules($type = 0, $seller = '')
2070 {
2071 global $conf, $langs, $mysoc;
2072
2073 $langs->load('trips');
2074
2075 // We don't know seller and buyer for expense reports
2076 if (!is_object($seller)) {
2077 $seller = $mysoc; // We use same than current company (expense report are often done in same country)
2078 $seller->tva_assuj = 1; // Most seller uses vat
2079 }
2080
2081 $expensereportrule = new ExpenseReportRule($this->db);
2082 $rulestocheck = $expensereportrule->getAllRule($this->line->fk_c_type_fees, $this->line->date, $this->fk_user_author);
2083
2084 $violation = 0;
2085 $rule_warning_message_tab = array();
2086
2087 $current_total_ttc = $this->line->total_ttc;
2088 $new_current_total_ttc = $this->line->total_ttc;
2089
2090 // check if one is violated
2091 foreach ($rulestocheck as $rule) {
2092 if (in_array($rule->code_expense_rules_type, array('EX_DAY', 'EX_MON', 'EX_YEA'))) {
2093 $amount_to_test = $this->line->getExpAmount($rule, $this->fk_user_author, $rule->code_expense_rules_type);
2094 } else {
2095 $amount_to_test = $current_total_ttc; // EX_EXP
2096 }
2097
2098 $amount_to_test = $amount_to_test - $current_total_ttc + $new_current_total_ttc; // if amount as been modified by a previous rule
2099
2100 if ($amount_to_test > $rule->amount) {
2101 $violation++;
2102
2103 if ($rule->restrictive) {
2104 $this->error = 'ExpenseReportConstraintViolationError';
2105 $this->errors[] = $this->error;
2106
2107 $new_current_total_ttc -= $amount_to_test - $rule->amount; // ex, entered 16€, limit 12€, subtracts 4€;
2108 $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));
2109 } else {
2110 $this->error = 'ExpenseReportConstraintViolationWarning';
2111 $this->errors[] = $this->error;
2112
2113 $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));
2114 }
2115
2116 // No break, we should test if another rule is violated
2117 }
2118 }
2119
2120 $this->line->rule_warning_message = implode('\n', $rule_warning_message_tab);
2121
2122 if ($violation > 0) {
2123 $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);
2124
2125 $this->line->value_unit = $tmp[5];
2126 $this->line->total_ttc = (float) $tmp[2];
2127 $this->line->total_ht = (float) $tmp[0];
2128 $this->line->total_tva = (float) $tmp[1];
2129 $this->line->total_localtax1 = (float) $tmp[9];
2130 $this->line->total_localtax2 = (float) $tmp[10];
2131
2132 return false;
2133 } else {
2134 return true;
2135 }
2136 }
2137
2145 public function applyOffset($type = 0, $seller = '')
2146 {
2147 global $mysoc;
2148
2149 if (!getDolGlobalString('MAIN_USE_EXPENSE_IK')) {
2150 return false;
2151 }
2152
2153 $userauthor = new User($this->db);
2154 if ($userauthor->fetch($this->fk_user_author) <= 0) {
2155 $this->error = 'ErrorCantFetchUser';
2156 $this->errors[] = 'ErrorCantFetchUser';
2157 return false;
2158 }
2159
2160 // We don't know seller and buyer for expense reports
2161 if (!is_object($seller)) {
2162 $seller = $mysoc; // We use same than current company (expense report are often done in same country)
2163 $seller->tva_assuj = 1; // Most seller uses vat
2164 }
2165
2166 $expenseik = new ExpenseReportIk($this->db);
2167 $range = $expenseik->getRangeByUser($userauthor, $this->line->fk_c_exp_tax_cat);
2168
2169 if (empty($range)) {
2170 $this->error = 'ErrorNoRangeAvailable';
2171 $this->errors[] = 'ErrorNoRangeAvailable';
2172 return false;
2173 }
2174
2175 if (getDolGlobalString('MAIN_EXPENSE_APPLY_ENTIRE_OFFSET')) {
2176 $ikoffset = $range->ikoffset;
2177 } else {
2178 $ikoffset = $range->ikoffset / 12; // The amount of offset is a global value for the year
2179 }
2180
2181 // Test if ikoffset has been applied for the current month
2182 if (!$this->offsetAlreadyGiven()) {
2183 $new_up = $range->coef + ($ikoffset / $this->line->qty);
2184 $tmp = calcul_price_total($this->line->qty, $new_up, 0, $this->line->vatrate, 0, 0, 0, 'TTC', 0, $type, $seller);
2185
2186 $this->line->value_unit = $tmp[5];
2187 $this->line->total_ttc = (float) $tmp[2];
2188 $this->line->total_ht = (float) $tmp[0];
2189 $this->line->total_tva = (float) $tmp[1];
2190 $this->line->total_localtax1 = (float) $tmp[9];
2191 $this->line->total_localtax2 = (float) $tmp[10];
2192
2193 return true;
2194 }
2195
2196 return false;
2197 }
2198
2204 public function offsetAlreadyGiven()
2205 {
2206 $sql = 'SELECT e.rowid FROM '.MAIN_DB_PREFIX.'expensereport e';
2207 $sql .= " INNER JOIN ".MAIN_DB_PREFIX."expensereport_det d ON (e.rowid = d.fk_expensereport)";
2208 $sql .= " INNER JOIN ".MAIN_DB_PREFIX."c_type_fees f ON (d.fk_c_type_fees = f.id AND f.code = 'EX_KME')";
2209 $sql .= " WHERE e.fk_user_author = ".(int) $this->fk_user_author;
2210 $sql .= " AND YEAR(d.date) = '".dol_print_date($this->line->date, '%Y')."' AND MONTH(d.date) = '".dol_print_date($this->line->date, '%m')."'";
2211 if (!empty($this->line->id)) {
2212 $sql .= ' AND d.rowid <> '.((int) $this->line->id);
2213 }
2214
2215 dol_syslog(get_class($this)."::offsetAlreadyGiven");
2216 $resql = $this->db->query($sql);
2217 if ($resql) {
2218 $num = $this->db->num_rows($resql);
2219 if ($num > 0) {
2220 return true;
2221 }
2222 } else {
2223 dol_print_error($this->db);
2224 }
2225
2226 return false;
2227 }
2228
2246 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)
2247 {
2248 global $user, $mysoc;
2249
2250 if ($this->status == self::STATUS_DRAFT || $this->status == self::STATUS_REFUSED) {
2251 $this->db->begin();
2252
2253 $error = 0;
2254 $type = 0; // TODO What if type is service ?
2255
2256 // We don't know seller and buyer for expense reports
2257 $seller = $mysoc; // We use same than current company (expense report are often done in same country)
2258 $seller->tva_assuj = 1; // Most seller uses vat
2259 $seller->localtax1_assuj = $mysoc->localtax1_assuj; // We don't know, we reuse the state of company
2260 $seller->localtax2_assuj = $mysoc->localtax1_assuj; // We don't know, we reuse the state of company
2261 $buyer = new Societe($this->db);
2262
2263 $localtaxes_type = getLocalTaxesFromRate($vatrate, 0, $buyer, $seller);
2264
2265 // Clean vat code
2266 $reg = array();
2267 $vat_src_code = '';
2268 if (preg_match('/\‍((.*)\‍)/', (string) $vatrate, $reg)) {
2269 $vat_src_code = $reg[1];
2270 $vatrate = preg_replace('/\s*\‍(.*\‍)/', '', (string) $vatrate); // Remove code into vatrate.
2271 }
2272 $vatrate = preg_replace('/\*/', '', $vatrate);
2273
2274 $tmp = calcul_price_total($qty, $value_unit, 0, (float) price2num($vatrate), -1, -1, 0, 'TTC', 0, $type, $seller, $localtaxes_type);
2275 // calcul total of line
2276 // $total_ttc = price2num($qty*$value_unit, 'MT');
2277
2278 $tx_tva = 1 + (float) $vatrate / 100;
2279
2280 $this->line = new ExpenseReportLine($this->db);
2281 $this->line->comments = $comments;
2282 $this->line->qty = $qty;
2283 $this->line->value_unit = $value_unit;
2284 $this->line->date = $date;
2285
2286 $this->line->fk_expensereport = $expensereport_id;
2287 $this->line->fk_c_type_fees = $type_fees_id;
2288 $this->line->fk_c_exp_tax_cat = $fk_c_exp_tax_cat;
2289 $this->line->fk_projet = $projet_id; // deprecated
2290 $this->line->fk_project = $projet_id;
2291
2292 $this->line->vat_src_code = $vat_src_code;
2293 $this->line->vatrate = price2num($vatrate);
2294 $this->line->localtax1_tx = $localtaxes_type[1];
2295 $this->line->localtax2_tx = $localtaxes_type[3];
2296 $this->line->localtax1_type = $localtaxes_type[0];
2297 $this->line->localtax2_type = $localtaxes_type[2];
2298
2299 $this->line->total_ttc = (float) $tmp[2];
2300 $this->line->total_ht = (float) $tmp[0];
2301 $this->line->total_tva = (float) $tmp[1];
2302 $this->line->total_localtax1 = (float) $tmp[9];
2303 $this->line->total_localtax2 = (float) $tmp[10];
2304
2305 $this->line->fk_ecm_files = $fk_ecm_files;
2306
2307 $this->line->id = ((int) $rowid);
2308
2309 // Select des infos sur le type fees
2310 $sql = "SELECT c.code as code_type_fees, c.label as label_type_fees";
2311 $sql .= " FROM ".MAIN_DB_PREFIX."c_type_fees as c";
2312 $sql .= " WHERE c.id = ".((int) $type_fees_id);
2313 $resql = $this->db->query($sql);
2314 if ($resql) {
2315 $objp_fees = $this->db->fetch_object($resql);
2316 $this->line->type_fees_code = $objp_fees->code_type_fees;
2317 $this->line->type_fees_libelle = $objp_fees->label_type_fees;
2318 $this->db->free($resql);
2319 }
2320
2321 // Select des information du projet
2322 $sql = "SELECT p.ref as ref_projet, p.title as title_projet";
2323 $sql .= " FROM ".MAIN_DB_PREFIX."projet as p";
2324 $sql .= " WHERE p.rowid = ".((int) $projet_id);
2325 $resql = $this->db->query($sql);
2326 if ($resql) {
2327 $objp_projet = $this->db->fetch_object($resql);
2328 $this->line->projet_ref = $objp_projet->ref_projet;
2329 $this->line->projet_title = $objp_projet->title_projet;
2330 $this->db->free($resql);
2331 }
2332
2333 $this->applyOffset();
2334 $this->checkRules();
2335
2336 $result = $this->line->update($user, $notrigger);
2337 if ($result < 0) {
2338 $error++;
2339 }
2340
2341 if (!$error) {
2342 $this->db->commit();
2343 return 1;
2344 } else {
2345 $this->error = $this->line->error;
2346 $this->errors = $this->line->errors;
2347 $this->db->rollback();
2348 return -2;
2349 }
2350 }
2351
2352 return 0;
2353 }
2354
2363 public function deleteLine($rowid, $fuser = null, $notrigger = 0)
2364 {
2365 $error = 0;
2366
2367 $this->db->begin();
2368
2369 if (!$notrigger) {
2370 // Call triggers
2371 $result = $this->call_trigger('EXPENSE_REPORT_DET_DELETE', $fuser);
2372 if ($result < 0) {
2373 $error++;
2374 }
2375 // End call triggers
2376 }
2377
2378 $sql = ' DELETE FROM '.MAIN_DB_PREFIX.$this->table_element_line;
2379 $sql .= ' WHERE rowid = '.((int) $rowid);
2380
2381 dol_syslog(get_class($this)."::deleteline sql=".$sql);
2382 $result = $this->db->query($sql);
2383
2384 if (!$result || $error > 0) {
2385 $this->error = $this->db->error();
2386 dol_syslog(get_class($this)."::deleteline Error ".$this->error, LOG_ERR);
2387 $this->db->rollback();
2388 return -1;
2389 }
2390
2391 $this->update_price(1);
2392
2393 $this->db->commit();
2394
2395 return 1;
2396 }
2397
2406 public function periodExists(User $fuser, $startDate, $endDate)
2407 {
2408 global $conf;
2409
2410 $sql = "SELECT rowid, date_debut, date_fin";
2411 $sql .= " FROM ".MAIN_DB_PREFIX.$this->table_element;
2412 $sql .= " WHERE entity = ".((int) $conf->entity); // not shared, only for the current entity
2413 $sql .= " AND fk_user_author = ".((int) $fuser->id);
2414 $sql .= " AND (date_fin >= '".$this->db->idate($startDate)."' AND date_debut <= '".$this->db->idate($endDate)."')";
2415
2416 $row = $this->db->getRow($sql);
2417
2418 if ($row === false) {
2419 $this->error = $this->db->lasterror();
2420 dol_syslog(__CLASS__."::". __METHOD__." Error ".$this->error, LOG_ERR);
2421 return -1;
2422 }
2423
2424 return $row->rowid ?? 0;
2425 }
2426
2427
2428 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2436 {
2437 // phpcs:enable
2438 $users_validator = array();
2439
2440 $sql = "SELECT DISTINCT ur.fk_user";
2441 $sql .= " FROM ".MAIN_DB_PREFIX."user_rights as ur, ".MAIN_DB_PREFIX."rights_def as rd";
2442 $sql .= " WHERE ur.fk_id = rd.id and rd.module = 'expensereport' AND rd.perms = 'approve'"; // Permission 'Approve';
2443 $sql .= " UNION";
2444 $sql .= " SELECT DISTINCT ugu.fk_user";
2445 $sql .= " FROM ".MAIN_DB_PREFIX."usergroup_user as ugu, ".MAIN_DB_PREFIX."usergroup_rights as ur, ".MAIN_DB_PREFIX."rights_def as rd";
2446 $sql .= " WHERE ugu.fk_usergroup = ur.fk_usergroup AND ur.fk_id = rd.id and rd.module = 'expensereport' AND rd.perms = 'approve'"; // Permission 'Approve';
2447 //print $sql;
2448
2449 dol_syslog(get_class($this)."::fetch_users_approver_expensereport sql=".$sql);
2450 $result = $this->db->query($sql);
2451 if ($result) {
2452 $num_rows = $this->db->num_rows($result);
2453 $i = 0;
2454 while ($i < $num_rows) {
2455 $objp = $this->db->fetch_object($result);
2456 array_push($users_validator, $objp->fk_user);
2457 $i++;
2458 }
2459 return $users_validator;
2460 } else {
2461 $this->error = $this->db->lasterror();
2462 dol_syslog(get_class($this)."::fetch_users_approver_expensereport Error ".$this->error, LOG_ERR);
2463 return -1;
2464 }
2465 }
2466
2478 public function generateDocument($modele, $outputlangs, $hidedetails = 0, $hidedesc = 0, $hideref = 0, $moreparams = null)
2479 {
2480 $outputlangs->load("trips");
2481
2482 if (!dol_strlen($modele)) {
2483 if (!empty($this->model_pdf)) {
2484 $modele = $this->model_pdf;
2485 } elseif (getDolGlobalString('EXPENSEREPORT_ADDON_PDF')) {
2486 $modele = getDolGlobalString('EXPENSEREPORT_ADDON_PDF');
2487 }
2488 }
2489
2490 if (!empty($modele)) {
2491 $modelpath = "core/modules/expensereport/doc/";
2492
2493 return $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams);
2494 } else {
2495 return 0;
2496 }
2497 }
2498
2505 public function listOfTypes($active = 1)
2506 {
2507 global $langs;
2508 $ret = array();
2509 $sql = "SELECT id, code, label";
2510 $sql .= " FROM ".MAIN_DB_PREFIX."c_type_fees";
2511 $sql .= " WHERE active = ".((int) $active);
2512 dol_syslog(get_class($this)."::listOfTypes", LOG_DEBUG);
2513 $result = $this->db->query($sql);
2514 if ($result) {
2515 $num = $this->db->num_rows($result);
2516 $i = 0;
2517 while ($i < $num) {
2518 $obj = $this->db->fetch_object($result);
2519 $ret[$obj->code] = (($langs->transnoentitiesnoconv($obj->code) != $obj->code) ? $langs->transnoentitiesnoconv($obj->code) : $obj->label);
2520 $i++;
2521 }
2522 } else {
2523 dol_print_error($this->db);
2524 }
2525 return $ret;
2526 }
2527
2533 public function loadStateBoard()
2534 {
2535 global $user;
2536
2537 $this->nb = array();
2538
2539 $sql = "SELECT count(ex.rowid) as nb";
2540 $sql .= " FROM ".MAIN_DB_PREFIX."expensereport as ex";
2541 $sql .= " WHERE ex.fk_statut > 0";
2542 $sql .= " AND ex.entity IN (".getEntity('expensereport').")";
2543 if (!$user->hasRight('expensereport', 'readall')) {
2544 $userchildids = $user->getAllChildIds(1);
2545 $sql .= " AND (ex.fk_user_author IN (".$this->db->sanitize(implode(',', $userchildids)).")";
2546 $sql .= " OR ex.fk_user_validator IN (".$this->db->sanitize(implode(',', $userchildids))."))";
2547 }
2548
2549 $resql = $this->db->query($sql);
2550 if ($resql) {
2551 while ($obj = $this->db->fetch_object($resql)) {
2552 $this->nb["expensereports"] = $obj->nb;
2553 }
2554 $this->db->free($resql);
2555 return 1;
2556 } else {
2557 dol_print_error($this->db);
2558 $this->error = $this->db->error();
2559 return -1;
2560 }
2561 }
2562
2563 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2571 public function load_board($user, $option = 'topay')
2572 {
2573 // phpcs:enable
2574 global $conf, $langs;
2575
2576 if ($user->socid) {
2577 return -1; // protection pour eviter appel par utilisateur externe
2578 }
2579
2580 $now = dol_now();
2581
2582 $sql = "SELECT ex.rowid, ex.date_valid";
2583 $sql .= " FROM ".MAIN_DB_PREFIX."expensereport as ex";
2584 if ($option == 'toapprove') {
2585 $sql .= " WHERE ex.fk_statut = ".self::STATUS_VALIDATED;
2586 } else {
2587 $sql .= " WHERE ex.fk_statut = ".self::STATUS_APPROVED;
2588 }
2589 $sql .= " AND ex.entity IN (".getEntity('expensereport').")";
2590 if (!$user->hasRight('expensereport', 'readall')) {
2591 $userchildids = $user->getAllChildIds(1);
2592 $sql .= " AND (ex.fk_user_author IN (".$this->db->sanitize(implode(',', $userchildids)).")";
2593 $sql .= " OR ex.fk_user_validator IN (".$this->db->sanitize(implode(',', $userchildids))."))";
2594 }
2595
2596 $resql = $this->db->query($sql);
2597 if ($resql) {
2598 $langs->load("trips");
2599
2600 $response = new WorkboardResponse();
2601 if ($option == 'toapprove') {
2602 $response->warning_delay = $conf->expensereport->approve->warning_delay / 60 / 60 / 24;
2603 $response->label = $langs->trans("ExpenseReportsToApprove");
2604 $response->labelShort = $langs->trans("ToApprove");
2605 $response->url = dolBuildUrl(DOL_URL_ROOT.'/expensereport/list.php', ['mainmenu' => 'hrm', 'statut' => self::STATUS_VALIDATED]);
2606 } else {
2607 $response->warning_delay = $conf->expensereport->payment->warning_delay / 60 / 60 / 24;
2608 $response->label = $langs->trans("ExpenseReportsToPay");
2609 $response->labelShort = $langs->trans("StatusToPay");
2610 $response->url = dolBuildUrl(DOL_URL_ROOT.'/expensereport/list.php', ['mainmenu' => 'hrm', 'statut' => self::STATUS_APPROVED]);
2611 }
2612 $response->img = img_object('', "trip");
2613
2614 while ($obj = $this->db->fetch_object($resql)) {
2615 $response->nbtodo++;
2616
2617 if ($option == 'toapprove') {
2618 if ($this->db->jdate($obj->date_valid) < ($now - $conf->expensereport->approve->warning_delay)) {
2619 $response->nbtodolate++;
2620 }
2621 } else {
2622 if ($this->db->jdate($obj->date_valid) < ($now - $conf->expensereport->payment->warning_delay)) {
2623 $response->nbtodolate++;
2624 }
2625 }
2626 }
2627
2628 return $response;
2629 } else {
2630 dol_print_error($this->db);
2631 $this->error = $this->db->error();
2632 return -1;
2633 }
2634 }
2635
2642 public function hasDelay($option)
2643 {
2644 global $conf;
2645
2646 // Only valid expenses reports
2647 if ($option == 'toapprove' && $this->status != 2) {
2648 return false;
2649 }
2650 if ($option == 'topay' && $this->status != 5) {
2651 return false;
2652 }
2653
2654 $now = dol_now();
2655 $warning_delay = (int) ($option == 'toapprove' ? $conf->expensereport->approve->warning_delay : $conf->expensereport->payment->warning_delay);
2656 if ($warning_delay <= 0) {
2657 // No delay configured (MAIN_DELAY_EXPENSEREPORTS / MIN_DELAY_EXPENSEREPORTS_TO_PAY not set), so nothing is late.
2658 return false;
2659 }
2660 return (!empty($this->datevalid) ? $this->datevalid : $this->date_valid) < ($now - $warning_delay);
2661 }
2662
2669 public function getVentilExportCompta($mode = 0)
2670 {
2671 $alreadydispatched = 0;
2672
2673 $type = 'expense_report';
2674
2675 $sql = " SELECT ".($mode ? 'DISTINCT piece_num' : 'COUNT(ab.rowid)')." as nb";
2676 $sql .= " FROM ".MAIN_DB_PREFIX."accounting_bookkeeping as ab WHERE ab.doc_type = '".$this->db->escape($type)."' AND ab.fk_doc = ".((int) $this->id);
2677 $resql = $this->db->query($sql);
2678 if ($resql) {
2679 $obj = $this->db->fetch_object($resql);
2680 if ($obj) {
2681 $alreadydispatched = $obj->nb;
2682 }
2683 } else {
2684 $this->error = $this->db->lasterror();
2685 return -1;
2686 }
2687
2688 if ($alreadydispatched) {
2689 return $alreadydispatched;
2690 }
2691 return 0;
2692 }
2693
2699 public function getSumPayments()
2700 {
2701 $table = 'payment_expensereport';
2702 $field = 'fk_expensereport';
2703
2704 $sql = 'SELECT sum(amount) as amount';
2705 $sql .= ' FROM '.MAIN_DB_PREFIX.$table;
2706 $sql .= " WHERE ".$field." = ".((int) $this->id);
2707
2708 dol_syslog(get_class($this)."::getSumPayments", LOG_DEBUG);
2709 $resql = $this->db->query($sql);
2710 if ($resql) {
2711 $obj = $this->db->fetch_object($resql);
2712 $this->db->free($resql);
2713 return (empty($obj->amount) ? 0 : $obj->amount);
2714 } else {
2715 $this->error = $this->db->lasterror();
2716 return -1;
2717 }
2718 }
2719
2728 public function computeTotalKm($fk_cat, $qty, $tva)
2729 {
2730 global $langs, $conf;
2731
2732 $cumulYearQty = 0;
2733 $ranges = array();
2734 $coef = 0;
2735
2736
2737 if ($fk_cat < 0) {
2738 $this->error = $langs->trans('ErrorBadParameterCat');
2739 return -1;
2740 }
2741
2742 if ($qty <= 0) {
2743 $this->error = $langs->trans('ErrorBadParameterQty');
2744 return -1;
2745 }
2746
2747 $currentUser = new User($this->db);
2748 $currentUser->fetch($this->fk_user);
2749 $currentUser->loadRights('expensereport');
2750 //Clean
2751 $qty = (float) price2num($qty);
2752
2753 $sql = " SELECT r.range_ik, t.ikoffset, t.coef";
2754 $sql .= " FROM ".MAIN_DB_PREFIX."expensereport_ik t";
2755 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."c_exp_tax_range r ON r.rowid = t.fk_range";
2756 $sql .= " WHERE t.fk_c_exp_tax_cat = ".(int) $fk_cat;
2757 $sql .= " ORDER BY r.range_ik ASC";
2758
2759 dol_syslog("expenseReport::computeTotalkm sql=".$sql, LOG_DEBUG);
2760
2761 $result = $this->db->query($sql);
2762
2763 if ($result) {
2764 if (getDolGlobalInt('EXPENSEREPORT_CALCULATE_MILEAGE_EXPENSE_COEFFICIENT_ON_CURRENT_YEAR')) {
2765 $arrayDate = dol_getdate(dol_now());
2766 $sql = " SELECT count(n.qty) as cumul FROM ".MAIN_DB_PREFIX."expensereport_det n";
2767 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."expensereport e ON e.rowid = n.fk_expensereport";
2768 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."c_type_fees tf ON tf.id = n.fk_c_type_fees";
2769 $sql .= " WHERE e.fk_user_author = ".(int) $this->fk_user_author;
2770 $sql .= " AND YEAR(n.date) = ".(int) $arrayDate['year'];
2771 $sql .= " AND tf.code = 'EX_KME' ";
2772 $sql .= " AND e.fk_statut = ".(int) ExpenseReport::STATUS_VALIDATED;
2773
2774 $resql = $this->db->query($sql);
2775
2776 if ($resql) {
2777 $obj = $this->db->fetch_object($resql);
2778 $cumulYearQty = $obj->cumul;
2779 }
2780
2781 $qty += (float) $cumulYearQty;
2782 }
2783
2784 $num = $this->db->num_rows($result);
2785
2786 if ($num) {
2787 for ($i = 0; $i < $num; $i++) {
2788 $obj = $this->db->fetch_object($result);
2789
2790 $ranges[$i] = $obj;
2791 }
2792 '@phan-var-force Object[] $ranges';
2793
2794 for ($i = 0; $i < $num; $i++) {
2795 if ($i < ($num - 1)) {
2796 // @phan-suppress-next-line PhanTypeInvalidDimOffset
2797 if ($qty > $ranges[$i]->range_ik && $qty < $ranges[$i + 1]->range_ik) {
2798 $coef = $ranges[$i]->coef;
2799 $offset = $ranges[$i]->ikoffset;
2800 }
2801 } else {
2802 if ($qty > $ranges[$i]->range_ik) {
2803 $coef = $ranges[$i]->coef;
2804 $offset = $ranges[$i]->ikoffset;
2805 }
2806 }
2807 }
2808 $total_ht = $coef;
2809 return $total_ht;
2810 } else {
2811 $this->error = $langs->trans('TaxUndefinedForThisCategory');
2812 return 0;
2813 }
2814 } else {
2815 $this->error = $this->db->error()." sql=".$sql;
2816
2817 return -1;
2818 }
2819 }
2820
2828 public function getKanbanView($option = '', $arraydata = null)
2829 {
2830 global $langs;
2831
2832 if ($arraydata === null) {
2833 $arraydata = array();
2834 }
2835
2836 $selected = (empty($arraydata['selected']) ? 0 : $arraydata['selected']);
2837
2838 $return = '<div class="box-flex-item box-flex-grow-zero">';
2839 $return .= '<div class="info-box info-box-sm">';
2840 $return .= '<span class="info-box-icon bg-infobox-action">';
2841 $return .= img_picto('', $this->picto);
2842 $return .= '</span>';
2843 $return .= '<div class="info-box-content">';
2844 $return .= '<span class="info-box-ref inline-block tdoverflowmax150 valignmiddle">' . $this->getNomUrl(1) . '</span>';
2845 if ($selected >= 0) {
2846 $return .= '<input id="cb'.$this->id.'" class="flat checkforselect fright" type="checkbox" name="toselect[]" value="'.$this->id.'"'.($selected ? ' checked="checked"' : '').'>';
2847 }
2848 if (array_key_exists('userauthor', $arraydata) && $arraydata['userauthor'] instanceof User) {
2849 $return .= '<br><span class="info-box-label">'.$arraydata['userauthor']->getNomUrl(-1).'</span>';
2850 }
2851 if (isDolTms($this->date_debut) || isDolTms($this->date_fin)) {
2852 $return .= '<br><span class="info-box-label">'.dol_print_date($this->date_debut, 'day').'</span>';
2853 $return .= ' <span class="opacitymedium">'.$langs->trans("To").'</span> ';
2854 $return .= '<span class="info-box-label">'.dol_print_date($this->date_fin, 'day').'</span>';
2855 }
2856 $return .= '<br><div class="info-box-status">'.$this->getLibStatut(3).'</div>';
2857 $return .= '</div>';
2858 $return .= '</div>';
2859 $return .= '</div>';
2860 return $return;
2861 }
2862}
$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:171
global $mysoc
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.
dolBuildUrl($url, $params=[], $addtoken=false)
Return path of url.
price2num($amount, $rounding='', $option=0)
Function that return a number with universal decimal format (decimal separator is '.
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.
dolGetStatus($statusLabel='', $statusLabelShort='', $html='', $statusType='status0', $displayMode=0, $url='', $params=array())
Output the badge of a status.
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.
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