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