dolibarr 22.0.5
expensereport.class.php
Go to the documentation of this file.
1<?php
2/* Copyright (C) 2011 Dimitri Mouillard <dmouillard@teclib.com>
3 * Copyright (C) 2015 Laurent Destailleur <eldy@users.sourceforge.net>
4 * Copyright (C) 2015 Alexandre Spangaro <aspangaro@open-dsi.fr>
5 * Copyright (C) 2018 Nicolas ZABOURI <info@inovea-conseil.com>
6 * Copyright (c) 2018-2024 Frédéric France <frederic.france@free.fr>
7 * Copyright (C) 2016-2020 Ferran Marcet <fmarcet@2byte.es>
8 * Copyright (C) 2024-2025 MDW <mdeweerd@users.noreply.github.com>
9 * Copyright (C) 2024 Frédéric France <frederic.france@free.fr>
10 *
11 * This program is free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by
13 * the Free Software Foundation; either version 3 of the License, or
14 * (at your option) any later version.
15 *
16 * This program is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU General Public License for more details.
20 *
21 * You should have received a copy of the GNU General Public License
22 * along with this program. If not, see <https://www.gnu.org/licenses/>.
23 */
24
30require_once DOL_DOCUMENT_ROOT.'/core/class/commonobject.class.php';
31require_once DOL_DOCUMENT_ROOT.'/expensereport/class/expensereportline.class.php';
32require_once DOL_DOCUMENT_ROOT.'/expensereport/class/expensereport_ik.class.php';
33require_once DOL_DOCUMENT_ROOT.'/expensereport/class/expensereport_rule.class.php';
34
35
40{
44 public $element = 'expensereport';
45
49 public $table_element = 'expensereport';
50
54 public $table_element_line = 'expensereport_det';
55
59 public $fk_element = 'fk_expensereport';
60
64 public $picto = 'trip';
65
69 public $lines = array();
70
74 public $line;
75
79 public $date_debut;
80
84 public $date_fin;
85
89 public $date_approbation;
90
94 public $fk_user;
95
99 public $user_approve_id;
100
106 public $status;
107
114 public $fk_statut;
115
119 public $fk_c_paiement;
120
124 public $modepaymentid;
125
129 public $paid;
130
131 // Paiement
135 public $user_paid_infos;
136
140 public $user_author_infos;
141
145 public $user_validator_infos;
146
150 public $rule_warning_message;
151
152 // ACTIONS
153
154 // Create
158 public $date_create;
159
163 public $fk_user_creat;
164
168 public $fk_user_author; // Note fk_user_author is not the 'author' but the guy the expense report is for.
169
170 // Update
174 public $date_modif;
175
179 public $fk_user_modif;
180
181 // Refus
185 public $date_refuse;
186
190 public $detail_refuse;
191
195 public $fk_user_refuse;
196
197 // Annulation
201 public $date_cancel;
202
206 public $detail_cancel;
207
211 public $fk_user_cancel;
212
216 public $fk_user_validator;
217
224 public $datevalid;
225
230 public $date_valid;
231
235 public $fk_user_valid;
236
240 public $user_valid_infos;
241
242 // Approve
246 public $date_approve;
247
251 public $fk_user_approve;
252
257 public $localtax1; // for backward compatibility (real field should be total_localtax1 defined into CommonObject)
262 public $localtax2; // for backward compatibility (real field should be total_localtax2 defined into CommonObject)
263
267 const STATUS_DRAFT = 0;
268
273
278
283
287 const STATUS_CLOSED = 6;
288
292 const STATUS_REFUSED = 99;
293
294 public $fields = array(
295 'rowid' => array('type' => 'integer', 'label' => 'ID', 'enabled' => 1, 'visible' => -1, 'notnull' => 1, 'position' => 10),
296 'ref' => array('type' => 'varchar(50)', 'label' => 'Ref', 'enabled' => 1, 'visible' => -1, 'notnull' => 1, 'showoncombobox' => 1, 'position' => 15),
297 'entity' => array('type' => 'integer', 'label' => 'Entity', 'default' => '1', 'enabled' => 1, 'visible' => -2, 'notnull' => 1, 'position' => 20),
298 'ref_number_int' => array('type' => 'integer', 'label' => 'Ref number int', 'enabled' => 1, 'visible' => -1, 'position' => 25),
299 'ref_ext' => array('type' => 'integer', 'label' => 'RefExt', 'enabled' => 1, 'visible' => 0, 'position' => 30),
300 'total_ht' => array('type' => 'double(24,8)', 'label' => 'Total ht', 'enabled' => 1, 'visible' => -1, 'position' => 35),
301 'total_tva' => array('type' => 'double(24,8)', 'label' => 'Total tva', 'enabled' => 1, 'visible' => -1, 'position' => 40),
302 'localtax1' => array('type' => 'double(24,8)', 'label' => 'Localtax1', 'enabled' => 1, 'visible' => -1, 'position' => 45),
303 'localtax2' => array('type' => 'double(24,8)', 'label' => 'Localtax2', 'enabled' => 1, 'visible' => -1, 'position' => 50),
304 'total_ttc' => array('type' => 'double(24,8)', 'label' => 'Total ttc', 'enabled' => 1, 'visible' => -1, 'position' => 55),
305 'date_debut' => array('type' => 'date', 'label' => 'Date debut', 'enabled' => 1, 'visible' => -1, 'notnull' => 1, 'position' => 60),
306 'date_fin' => array('type' => 'date', 'label' => 'Date fin', 'enabled' => 1, 'visible' => -1, 'notnull' => 1, 'position' => 65),
307 'date_valid' => array('type' => 'datetime', 'label' => 'Date valid', 'enabled' => 1, 'visible' => -1, 'position' => 75),
308 'date_approve' => array('type' => 'datetime', 'label' => 'Date approve', 'enabled' => 1, 'visible' => -1, 'position' => 80),
309 'date_refuse' => array('type' => 'datetime', 'label' => 'Date refuse', 'enabled' => 1, 'visible' => -1, 'position' => 85),
310 'date_cancel' => array('type' => 'datetime', 'label' => 'Date cancel', 'enabled' => 1, 'visible' => -1, 'position' => 90),
311 'fk_user_author' => array('type' => 'integer:User:user/class/user.class.php', 'label' => 'Fk user author', 'enabled' => 1, 'visible' => -1, 'notnull' => 1, 'position' => 100),
312 'fk_user_modif' => array('type' => 'integer:User:user/class/user.class.php', 'label' => 'Fk user modif', 'enabled' => 1, 'visible' => -1, 'position' => 105),
313 'fk_user_valid' => array('type' => 'integer:User:user/class/user.class.php', 'label' => 'Fk user valid', 'enabled' => 1, 'visible' => -1, 'position' => 110),
314 'fk_user_validator' => array('type' => 'integer:User:user/class/user.class.php', 'label' => 'Fk user validator', 'enabled' => 1, 'visible' => -1, 'position' => 115),
315 'fk_user_approve' => array('type' => 'integer:User:user/class/user.class.php', 'label' => 'Fk user approve', 'enabled' => 1, 'visible' => -1, 'position' => 120),
316 'fk_user_refuse' => array('type' => 'integer:User:user/class/user.class.php', 'label' => 'Fk user refuse', 'enabled' => 1, 'visible' => -1, 'position' => 125),
317 'fk_user_cancel' => array('type' => 'integer:User:user/class/user.class.php', 'label' => 'Fk user cancel', 'enabled' => 1, 'visible' => -1, 'position' => 130),
318 'fk_c_paiement' => array('type' => 'integer', 'label' => 'Fk c paiement', 'enabled' => 1, 'visible' => -1, 'position' => 140),
319 'paid' => array('type' => 'integer', 'label' => 'Paid', 'enabled' => 1, 'visible' => -1, 'notnull' => 1, 'position' => 145),
320 'note_public' => array('type' => 'html', 'label' => 'Note public', 'enabled' => 1, 'visible' => 0, 'position' => 150),
321 'note_private' => array('type' => 'html', 'label' => 'Note private', 'enabled' => 1, 'visible' => 0, 'position' => 155),
322 'detail_refuse' => array('type' => 'varchar(255)', 'label' => 'Detail refuse', 'enabled' => 1, 'visible' => -1, 'position' => 160),
323 'detail_cancel' => array('type' => 'varchar(255)', 'label' => 'Detail cancel', 'enabled' => 1, 'visible' => -1, 'position' => 165),
324 'integration_compta' => array('type' => 'integer', 'label' => 'Integration compta', 'enabled' => 1, 'visible' => -1, 'position' => 170),
325 'fk_bank_account' => array('type' => 'integer', 'label' => 'Fk bank account', 'enabled' => 1, 'visible' => -1, 'position' => 175),
326 'fk_multicurrency' => array('type' => 'integer', 'label' => 'Fk multicurrency', 'enabled' => 1, 'visible' => -1, 'position' => 185),
327 'multicurrency_code' => array('type' => 'varchar(255)', 'label' => 'Multicurrency code', 'enabled' => 1, 'visible' => -1, 'position' => 190),
328 'multicurrency_tx' => array('type' => 'double(24,8)', 'label' => 'Multicurrency tx', 'enabled' => 1, 'visible' => -1, 'position' => 195),
329 'multicurrency_total_ht' => array('type' => 'double(24,8)', 'label' => 'Multicurrency total ht', 'enabled' => 1, 'visible' => -1, 'position' => 200),
330 'multicurrency_total_tva' => array('type' => 'double(24,8)', 'label' => 'Multicurrency total tva', 'enabled' => 1, 'visible' => -1, 'position' => 205),
331 'multicurrency_total_ttc' => array('type' => 'double(24,8)', 'label' => 'Multicurrency total ttc', 'enabled' => 1, 'visible' => -1, 'position' => 210),
332 'extraparams' => array('type' => 'varchar(255)', 'label' => 'Extraparams', 'enabled' => 1, 'visible' => -1, 'position' => 220),
333 'date_create' => array('type' => 'datetime', 'label' => 'Date create', 'enabled' => 1, 'visible' => -1, 'notnull' => 1, 'position' => 300),
334 'tms' => array('type' => 'timestamp', 'label' => 'Tms', 'enabled' => 1, 'visible' => -1, 'notnull' => 1, 'position' => 305),
335 'import_key' => array('type' => 'varchar(14)', 'label' => 'ImportId', 'enabled' => 1, 'visible' => -1, 'position' => 1000),
336 'model_pdf' => array('type' => 'varchar(255)', 'label' => 'Model pdf', 'enabled' => 1, 'visible' => 0, 'position' => 1010),
337 'fk_statut' => array('type' => 'integer', 'label' => 'Fk statut', 'enabled' => 1, 'visible' => -1, 'notnull' => 1, 'position' => 500),
338 );
339
345 public function __construct($db)
346 {
347 $this->db = $db;
348 $this->total_ht = 0;
349 $this->total_ttc = 0;
350 $this->total_tva = 0;
351 $this->total_localtax1 = 0;
352 $this->total_localtax2 = 0;
353 $this->localtax1 = 0; // For backward compatibility
354 $this->localtax2 = 0; // For backward compatibility
355 $this->modepaymentid = 0;
356
357 // List of language codes for status
358 $this->labelStatusShort = array(0 => 'Draft', 2 => 'Validated', 4 => 'Canceled', 5 => 'Approved', 6 => 'Paid', 99 => 'Refused');
359 $this->labelStatus = array(0 => 'Draft', 2 => 'ValidatedWaitingApproval', 4 => 'Canceled', 5 => 'Approved', 6 => 'Paid', 99 => 'Refused');
360
361 $this->fields['ref_ext']['visible'] = getDolGlobalInt('MAIN_LIST_SHOW_REF_EXT');
362 }
363
371 public function create($user, $notrigger = 0)
372 {
373 global $conf, $langs;
374
375 $now = dol_now();
376
377 $error = 0;
378
379 // Check parameters
380 if (empty($this->date_debut) || empty($this->date_fin)) {
381 $this->error = $langs->trans('ErrorFieldRequired', $langs->transnoentitiesnoconv('Date'));
382 return -1;
383 }
384
385 $fuserid = $this->fk_user_author; // Note fk_user_author is not the 'author' but the guy the expense report is for.
386 if (empty($fuserid)) {
387 $fuserid = $user->id;
388 }
389
390 $this->db->begin();
391
392 $sql = "INSERT INTO ".MAIN_DB_PREFIX.$this->table_element." (";
393 $sql .= "ref";
394 $sql .= ",total_ht";
395 $sql .= ",total_ttc";
396 $sql .= ",total_tva";
397 $sql .= ",date_debut";
398 $sql .= ",date_fin";
399 $sql .= ",date_create";
400 $sql .= ",fk_user_creat";
401 $sql .= ",fk_user_author";
402 $sql .= ",fk_user_validator";
403 $sql .= ",fk_user_approve";
404 $sql .= ",fk_user_modif";
405 $sql .= ",fk_statut";
406 $sql .= ",fk_c_paiement";
407 $sql .= ",paid";
408 $sql .= ",note_public";
409 $sql .= ",note_private";
410 $sql .= ",entity";
411 $sql .= ") VALUES(";
412 $sql .= "'(PROV)'";
413 $sql .= ", ".price2num($this->total_ht, 'MT');
414 $sql .= ", ".price2num($this->total_ttc, 'MT');
415 $sql .= ", ".price2num($this->total_tva, 'MT');
416 $sql .= ", '".$this->db->idate($this->date_debut)."'";
417 $sql .= ", '".$this->db->idate($this->date_fin)."'";
418 $sql .= ", '".$this->db->idate($now)."'";
419 $sql .= ", ".((int) $user->id);
420 $sql .= ", ".((int) $fuserid);
421 $sql .= ", ".($this->fk_user_validator > 0 ? ((int) $this->fk_user_validator) : "null");
422 $sql .= ", ".($this->fk_user_approve > 0 ? ((int) $this->fk_user_approve) : "null");
423 $sql .= ", ".($this->fk_user_modif > 0 ? ((int) $this->fk_user_modif) : "null");
424 $sql .= ", ".($this->fk_statut > 1 ? ((int) $this->fk_statut) : 0);
425 $sql .= ", ".($this->modepaymentid ? ((int) $this->modepaymentid) : "null");
426 $sql .= ", 0";
427 $sql .= ", ".($this->note_public ? "'".$this->db->escape($this->note_public)."'" : "null");
428 $sql .= ", ".($this->note_private ? "'".$this->db->escape($this->note_private)."'" : "null");
429 $sql .= ", ".((int) $conf->entity);
430 $sql .= ")";
431
432 $result = $this->db->query($sql);
433 if ($result) {
434 $this->id = $this->db->last_insert_id(MAIN_DB_PREFIX.$this->table_element);
435 $this->ref = '(PROV'.$this->id.')';
436
437 $sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element." SET ref='".$this->db->escape($this->ref)."' WHERE rowid=".((int) $this->id);
438 $resql = $this->db->query($sql);
439 if (!$resql) {
440 $this->error = $this->db->lasterror();
441 $error++;
442 }
443
444 if (!$error) {
445 if (is_array($this->lines) && count($this->lines) > 0) {
446 foreach ($this->lines as $line) {
447 // Test and convert into object this->lines[$i]. When coming from REST API, we may still have an array
448 //if (! is_object($line)) $line=json_decode(json_encode($line), false); // convert recursively array into object.
449 if (!is_object($line)) {
450 $line = (object) $line;
451 $newndfline = new ExpenseReportLine($this->db);
452 $newndfline->fk_expensereport = $line->fk_expensereport;
453 $newndfline->fk_c_type_fees = $line->fk_c_type_fees;
454 $newndfline->fk_project = $line->fk_project;
455 $newndfline->vatrate = $line->vatrate;
456 $newndfline->vat_src_code = $line->vat_src_code;
457 $newndfline->localtax1_tx = $line->localtax1_tx;
458 $newndfline->localtax2_tx = $line->localtax2_tx;
459 $newndfline->localtax1_type = $line->localtax1_type;
460 $newndfline->localtax2_type = $line->localtax2_type;
461 $newndfline->comments = $line->comments;
462 $newndfline->qty = $line->qty;
463 $newndfline->value_unit = $line->value_unit;
464 $newndfline->total_ht = $line->total_ht;
465 $newndfline->total_ttc = $line->total_ttc;
466 $newndfline->total_tva = $line->total_tva;
467 $newndfline->total_localtax1 = $line->total_localtax1;
468 $newndfline->total_localtax2 = $line->total_localtax2;
469 $newndfline->date = $line->date;
470 $newndfline->rule_warning_message = $line->rule_warning_message;
471 $newndfline->fk_c_exp_tax_cat = $line->fk_c_exp_tax_cat;
472 $newndfline->fk_ecm_files = $line->fk_ecm_files;
473 } else {
474 $newndfline = $line;
475 }
476 //$newndfline=new ExpenseReportLine($this->db);
477 $newndfline->fk_expensereport = $this->id;
478 $result = $newndfline->insert();
479 if ($result < 0) {
480 $this->error = $newndfline->error;
481 $this->errors = $newndfline->errors;
482 $error++;
483 break;
484 }
485 }
486 }
487 }
488
489 if (!$error) {
490 $result = $this->insertExtraFields();
491 if ($result < 0) {
492 $error++;
493 }
494 }
495
496 if (!$error) {
497 $result = $this->update_price(1);
498 if ($result > 0) {
499 if (!$notrigger) {
500 // Call trigger
501 $result = $this->call_trigger('EXPENSE_REPORT_CREATE', $user);
502
503 if ($result < 0) {
504 $error++;
505 }
506 // End call triggers
507 }
508
509 if (empty($error)) {
510 $this->db->commit();
511 return $this->id;
512 } else {
513 $this->db->rollback();
514 return -4;
515 }
516 } else {
517 $this->db->rollback();
518 return -3;
519 }
520 } else {
521 dol_syslog(get_class($this)."::create error ".$this->error, LOG_ERR);
522 $this->db->rollback();
523 return -2;
524 }
525 } else {
526 $this->error = $this->db->lasterror()." sql=".$sql;
527 $this->db->rollback();
528 return -1;
529 }
530 }
531
539 public function createFromClone(User $user, $fk_user_author)
540 {
541 global $hookmanager;
542
543 $error = 0;
544
545 if (empty($fk_user_author)) {
546 $fk_user_author = $user->id;
547 }
548
549 $this->db->begin();
550
551 // get extrafields so they will be clone
552 //foreach($this->lines as $line)
553 //$line->fetch_optionals();
554
555 // Load source object
556 $objFrom = clone $this;
557
558 $this->id = 0;
559 $this->ref = '';
560 $this->status = 0;
561 $this->fk_statut = 0; // deprecated
562
563 // Clear fields
564 $this->fk_user_creat = $user->id;
565 $this->fk_user_author = $fk_user_author; // Note fk_user_author is not the 'author' but the guy the expense report is for.
566 $this->fk_user_valid = 0;
567 $this->date_create = '';
568 $this->date_creation = '';
569 $this->date_validation = '';
570
571 // Remove link on lines to a joined file
572 if (is_array($this->lines) && count($this->lines) > 0) {
573 foreach ($this->lines as $key => $line) {
574 $this->lines[$key]->fk_ecm_files = 0;
575 }
576 }
577
578 // Create clone
579 $this->context['createfromclone'] = 'createfromclone';
580 $result = $this->create($user);
581 if ($result < 0) {
582 $error++;
583 }
584
585 if (!$error) {
586 // Hook of thirdparty module
587 if (is_object($hookmanager)) {
588 $parameters = array('objFrom' => $objFrom);
589 $action = '';
590 $reshook = $hookmanager->executeHooks('createFrom', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
591 if ($reshook < 0) {
592 $this->setErrorsFromObject($hookmanager);
593 $error++;
594 }
595 }
596 }
597
598 unset($this->context['createfromclone']);
599
600 // End
601 if (!$error) {
602 $this->db->commit();
603 return $this->id;
604 } else {
605 $this->db->rollback();
606 return -1;
607 }
608 }
609
610
619 public function update($user, $notrigger = 0, $userofexpensereport = null)
620 {
621 $error = 0;
622 $this->db->begin();
623
624 $sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element." SET";
625 $sql .= " total_ht = ".((float) $this->total_ht);
626 $sql .= " , total_ttc = ".((float) $this->total_ttc);
627 $sql .= " , total_tva = ".((float) $this->total_tva);
628 $sql .= " , date_debut = '".$this->db->idate($this->date_debut)."'";
629 $sql .= " , date_fin = '".$this->db->idate($this->date_fin)."'";
630 if ($userofexpensereport && is_object($userofexpensereport)) {
631 $sql .= " , fk_user_author = ".($userofexpensereport->id > 0 ? $userofexpensereport->id : "null"); // Note fk_user_author is not the 'author' but the guy the expense report is for.
632 }
633 $sql .= " , fk_user_validator = ".($this->fk_user_validator > 0 ? $this->fk_user_validator : "null");
634 $sql .= " , fk_user_valid = ".($this->fk_user_valid > 0 ? $this->fk_user_valid : "null");
635 $sql .= " , fk_user_approve = ".($this->fk_user_approve > 0 ? $this->fk_user_approve : "null");
636 $sql .= " , fk_user_modif = ".((int) $user->id);
637 $sql .= " , fk_statut = ".($this->fk_statut >= 0 ? $this->fk_statut : '0');
638 $sql .= " , fk_c_paiement = ".($this->fk_c_paiement > 0 ? $this->fk_c_paiement : "null");
639 $sql .= " , note_public = ".(!empty($this->note_public) ? "'".$this->db->escape($this->note_public)."'" : "''");
640 $sql .= " , note_private = ".(!empty($this->note_private) ? "'".$this->db->escape($this->note_private)."'" : "''");
641 $sql .= " , detail_refuse = ".(!empty($this->detail_refuse) ? "'".$this->db->escape($this->detail_refuse)."'" : "''");
642 $sql .= " WHERE rowid = ".((int) $this->id);
643
644 dol_syslog(get_class($this)."::update", LOG_DEBUG);
645 $result = $this->db->query($sql);
646 if ($result) {
647 $result = $this->insertExtraFields();
648 if ($result < 0) {
649 $error++;
650 }
651
652 if (!$error && !$notrigger) {
653 // Call trigger
654 $result = $this->call_trigger('EXPENSE_REPORT_MODIFY', $user);
655 if ($result < 0) {
656 $error++;
657 }
658 // End call triggers
659 }
660
661 if (!$error) {
662 $this->db->commit();
663 return 1;
664 } else {
665 $this->db->rollback();
666 $this->error = $this->db->error();
667 return -2;
668 }
669 } else {
670 $this->db->rollback();
671 $this->error = $this->db->error();
672 return -1;
673 }
674 }
675
683 public function fetch($id, $ref = '')
684 {
685 $sql = "SELECT d.rowid, d.entity, d.ref, d.note_public, d.note_private,"; // DEFAULT
686 $sql .= " d.detail_refuse, d.detail_cancel, d.fk_user_refuse, d.fk_user_cancel,"; // ACTIONS
687 $sql .= " d.date_refuse, d.date_cancel,"; // ACTIONS
688 $sql .= " d.total_ht, d.total_ttc, d.total_tva,";
689 $sql .= " d.localtax1 as total_localtax1, d.localtax2 as total_localtax2,";
690 $sql .= " d.date_debut, d.date_fin, d.date_create, d.tms as date_modif, d.date_valid, d.date_approve,"; // DATES (datetime)
691 $sql .= " d.fk_user_creat, d.fk_user_author, d.fk_user_modif, d.fk_user_validator,";
692 $sql .= " d.fk_user_valid, d.fk_user_approve,";
693 $sql .= " d.fk_statut as status, d.fk_c_paiement, d.paid";
694 $sql .= " FROM ".MAIN_DB_PREFIX.$this->table_element." as d";
695 if ($ref) {
696 $sql .= " WHERE d.ref = '".$this->db->escape($ref)."'";
697 $sql .= " AND d.entity IN (".getEntity('expensereport').")";
698 } else {
699 $sql .= " WHERE d.rowid = ".((int) $id);
700 }
701 //$sql.= $restrict;
702
703 dol_syslog(get_class($this)."::fetch", LOG_DEBUG);
704 $resql = $this->db->query($sql);
705 if ($resql) {
706 $obj = $this->db->fetch_object($resql);
707 if ($obj) {
708 $this->id = $obj->rowid;
709 $this->ref = $obj->ref;
710
711 $this->entity = $obj->entity;
712
713 $this->total_ht = $obj->total_ht;
714 $this->total_tva = $obj->total_tva;
715 $this->total_ttc = $obj->total_ttc;
716 $this->localtax1 = $obj->total_localtax1; // For backward compatibility
717 $this->localtax2 = $obj->total_localtax2; // For backward compatibility
718 $this->total_localtax1 = $obj->total_localtax1;
719 $this->total_localtax2 = $obj->total_localtax2;
720
721 $this->note_public = $obj->note_public;
722 $this->note_private = $obj->note_private;
723 $this->detail_refuse = $obj->detail_refuse;
724 $this->detail_cancel = $obj->detail_cancel;
725
726 $this->date_debut = $this->db->jdate($obj->date_debut);
727 $this->date_fin = $this->db->jdate($obj->date_fin);
728 $this->date_valid = $this->db->jdate($obj->date_valid);
729 $this->date_approve = $this->db->jdate($obj->date_approve);
730 $this->date_create = $this->db->jdate($obj->date_create);
731 $this->date_modif = $this->db->jdate($obj->date_modif);
732 $this->date_refuse = $this->db->jdate($obj->date_refuse);
733 $this->date_cancel = $this->db->jdate($obj->date_cancel);
734
735 $this->fk_user_creat = $obj->fk_user_creat;
736 $this->fk_user_author = $obj->fk_user_author; // Note fk_user_author is not the 'author' but the guy the expense report is for.
737 $this->fk_user_modif = $obj->fk_user_modif;
738 $this->fk_user_validator = $obj->fk_user_validator;
739 $this->fk_user_valid = $obj->fk_user_valid;
740 $this->fk_user_refuse = $obj->fk_user_refuse;
741 $this->fk_user_cancel = $obj->fk_user_cancel;
742 $this->fk_user_approve = $obj->fk_user_approve;
743
744 $user_author = new User($this->db);
745 if ($this->fk_user_author > 0) {
746 $user_author->fetch($this->fk_user_author);
747 }
748
749 $this->user_author_infos = dolGetFirstLastname($user_author->firstname, $user_author->lastname);
750
751 $user_approver = new User($this->db);
752 if ($this->fk_user_approve > 0) {
753 $user_approver->fetch($this->fk_user_approve);
754 } elseif ($this->fk_user_validator > 0) {
755 $user_approver->fetch($this->fk_user_validator); // For backward compatibility
756 }
757 $this->user_validator_infos = dolGetFirstLastname($user_approver->firstname, $user_approver->lastname);
758
759 $this->fk_statut = (int) $obj->status; // deprecated
760 $this->status = (int) $obj->status;
761 $this->fk_c_paiement = $obj->fk_c_paiement;
762 $this->paid = $obj->paid;
763
764 if ($this->status == self::STATUS_APPROVED || $this->status == self::STATUS_CLOSED) {
765 $user_valid = new User($this->db);
766 if ($this->fk_user_valid > 0) {
767 $user_valid->fetch($this->fk_user_valid);
768 }
769 $this->user_valid_infos = dolGetFirstLastname($user_valid->firstname, $user_valid->lastname);
770 }
771
772 $this->fetch_optionals();
773
774 $result = $this->fetch_lines();
775
776 return $result;
777 } else {
778 return 0;
779 }
780 } else {
781 $this->error = $this->db->lasterror();
782 return -1;
783 }
784 }
785
786 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
797 public function set_paid($id, $fuser, $notrigger = 0)
798 {
799 // phpcs:enable
800 dol_syslog(get_class($this)."::set_paid is deprecated, use setPaid instead", LOG_NOTICE);
801 return $this->setPaid($id, $fuser, $notrigger);
802 }
803
812 public function setPaid($id, $fuser, $notrigger = 0)
813 {
814 $error = 0;
815 $this->db->begin();
816
817 $sql = "UPDATE ".MAIN_DB_PREFIX."expensereport";
818 $sql .= " SET fk_statut = ".self::STATUS_CLOSED.", paid = 1";
819 $sql .= " WHERE rowid = ".((int) $id)." AND fk_statut = ".self::STATUS_APPROVED;
820
821 dol_syslog(get_class($this)."::setPaid", LOG_DEBUG);
822 $resql = $this->db->query($sql);
823 if ($resql) {
824 if ($this->db->affected_rows($resql)) {
825 if (!$notrigger) {
826 // Call trigger
827 $result = $this->call_trigger('EXPENSE_REPORT_PAID', $fuser);
828
829 if ($result < 0) {
830 $error++;
831 }
832 // End call triggers
833 }
834
835 if (empty($error)) {
836 $this->db->commit();
837
839 $this->paid = 1;
840
841 return 1;
842 } else {
843 $this->db->rollback();
844 $this->error = $this->db->error();
845 return -2;
846 }
847 } else {
848 $this->db->commit();
849 return 0;
850 }
851 } else {
852 $this->db->rollback();
853 dol_print_error($this->db);
854 return -1;
855 }
856 }
857
864 public function getLibStatut($mode = 0)
865 {
866 return $this->LibStatut($this->status, $mode);
867 }
868
869 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
877 public function LibStatut($status, $mode = 0)
878 {
879 // phpcs:enable
880 global $langs;
881
882 $labelStatus = $langs->transnoentitiesnoconv($this->labelStatus[$status]);
883 $labelStatusShort = $langs->transnoentitiesnoconv($this->labelStatusShort[$status]);
884
885 $statuslogo = array(0 => 'status0', 2 => 'status1', 4 => 'status6', 5 => 'status4', 6 => 'status6', 99 => 'status5');
886
887 $statusType = $statuslogo[$status];
888
889 return dolGetStatus($labelStatus, $labelStatusShort, '', $statusType, $mode);
890 }
891
892
899 public function info($id)
900 {
901 global $conf;
902
903 $sql = "SELECT f.rowid,";
904 $sql .= " f.date_create as datec,";
905 $sql .= " f.tms as date_modification,";
906 $sql .= " f.date_valid as datev,";
907 $sql .= " f.date_approve as datea,";
908 $sql .= " f.fk_user_creat as fk_user_creation,";
909 $sql .= " f.fk_user_author as fk_user_author,";
910 $sql .= " f.fk_user_modif as fk_user_modification,";
911 $sql .= " f.fk_user_valid,";
912 $sql .= " f.fk_user_approve";
913 $sql .= " FROM ".MAIN_DB_PREFIX."expensereport as f";
914 $sql .= " WHERE f.rowid = ".((int) $id);
915 $sql .= " AND f.entity = ".$conf->entity;
916
917
918
919 $resql = $this->db->query($sql);
920 if ($resql) {
921 if ($this->db->num_rows($resql)) {
922 $obj = $this->db->fetch_object($resql);
923
924 $this->id = $obj->rowid;
925
926 $this->date_creation = $this->db->jdate($obj->datec);
927 $this->date_modification = $this->db->jdate($obj->date_modification);
928 $this->date_validation = $this->db->jdate($obj->datev);
929 $this->date_approbation = $this->db->jdate($obj->datea);
930
931 $this->user_creation_id = $obj->fk_user_author;
932 $this->user_creation_id = $obj->fk_user_creation;
933 $this->user_validation_id = $obj->fk_user_valid;
934 $this->user_modification_id = $obj->fk_user_modification;
935 $this->user_approve_id = $obj->fk_user_approve;
936 }
937 $this->db->free($resql);
938 } else {
939 dol_print_error($this->db);
940 }
941 }
942
943
944
952 public function initAsSpecimen()
953 {
954 global $user, $langs;
955
956 $now = dol_now();
957
958 // Initialise parameters
959 $this->id = 0;
960 $this->ref = 'SPECIMEN';
961 $this->specimen = 1;
962 $this->entity = 1;
963 $this->date_create = $now;
964 $this->date_debut = $now;
965 $this->date_fin = $now;
966 $this->date_valid = $now;
967 $this->date_approve = $now;
968
969 $type_fees_id = 2; // TF_TRIP
970
971 $this->status = 5;
972
973 $this->fk_user_author = $user->id;
974 $this->fk_user_validator = $user->id;
975 $this->fk_user_valid = $user->id;
976 $this->fk_user_approve = $user->id;
977
978 $this->note_private = 'Private note';
979 $this->note_public = 'SPECIMEN';
980 $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)
981 $xnbp = 0;
982 while ($xnbp < $nbp) {
983 $line = new ExpenseReportLine($this->db);
984 $line->comments = $langs->trans("Comment")." ".$xnbp;
985 $line->date = ($now - 3600 * (1 + $xnbp));
986 $line->total_ht = 100;
987 $line->total_tva = 20;
988 $line->total_ttc = 120;
989 $line->qty = 1;
990 $line->vatrate = 20;
991 $line->value_unit = 120;
992 $line->fk_expensereport = 0;
993 $line->type_fees_code = 'TRA';
994 $line->fk_c_type_fees = $type_fees_id;
995
996 $line->projet_ref = 'ABC';
997
998 $this->lines[$xnbp] = $line;
999 $xnbp++;
1000
1001 $this->total_ht += $line->total_ht;
1002 $this->total_tva += $line->total_tva;
1003 $this->total_ttc += $line->total_ttc;
1004 }
1005
1006 return 1;
1007 }
1008
1009 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1017 public function fetch_line_by_project($projectid, $user)
1018 {
1019 // phpcs:enable
1020 global $langs;
1021
1022 $langs->load('trips');
1023
1024 if ($user->hasRight('expensereport', 'lire')) {
1025 $sql = "SELECT de.fk_expensereport, de.date, de.comments, de.total_ht, de.total_ttc";
1026 $sql .= " FROM ".MAIN_DB_PREFIX."expensereport_det as de";
1027 $sql .= " WHERE de.fk_projet = ".((int) $projectid);
1028
1029 dol_syslog(get_class($this)."::fetch", LOG_DEBUG);
1030 $result = $this->db->query($sql);
1031 if ($result) {
1032 $num = $this->db->num_rows($result);
1033 $i = 0;
1034 $total_HT = 0;
1035 $total_TTC = 0;
1036
1037 while ($i < $num) {
1038 $objp = $this->db->fetch_object($result);
1039
1040 $sql2 = "SELECT d.rowid, d.fk_user_author, d.ref, d.fk_statut as status";
1041 $sql2 .= " FROM ".MAIN_DB_PREFIX."expensereport as d";
1042 $sql2 .= " WHERE d.rowid = ".((int) $objp->fk_expensereport);
1043
1044 $result2 = $this->db->query($sql2);
1045 $obj = $this->db->fetch_object($result2);
1046
1047 $objp->fk_user_author = $obj->fk_user_author;
1048 $objp->ref = $obj->ref;
1049 $objp->fk_c_expensereport_status = $obj->status;
1050 $objp->rowid = $obj->rowid;
1051
1052 $total_HT += $objp->total_ht;
1053 $total_TTC += $objp->total_ttc;
1054 $author = new User($this->db);
1055 $author->fetch($objp->fk_user_author);
1056
1057 print '<tr>';
1058 print '<td><a href="'.DOL_URL_ROOT.'/expensereport/card.php?id='.$objp->rowid.'">'.$objp->ref_num.'</a></td>';
1059 print '<td class="center">'.dol_print_date($objp->date, 'day').'</td>';
1060 print '<td>'.$author->getNomUrl(1).'</td>';
1061 print '<td>'.$objp->comments.'</td>';
1062 print '<td class="right">'.price($objp->total_ht).'</td>';
1063 print '<td class="right">'.price($objp->total_ttc).'</td>';
1064 print '<td class="right">';
1065
1066 switch ($objp->fk_c_expensereport_status) {
1067 case 4:
1068 print img_picto($langs->trans('StatusOrderCanceled'), 'statut5');
1069 break;
1070 case 1:
1071 print $langs->trans('Draft').' '.img_picto($langs->trans('Draft'), 'statut0');
1072 break;
1073 case 2:
1074 print $langs->trans('TripForValid').' '.img_picto($langs->trans('TripForValid'), 'statut3');
1075 break;
1076 case 5:
1077 print $langs->trans('TripForPaid').' '.img_picto($langs->trans('TripForPaid'), 'statut3');
1078 break;
1079 case 6:
1080 print $langs->trans('TripPaid').' '.img_picto($langs->trans('TripPaid'), 'statut4');
1081 break;
1082 }
1083 /*
1084 if ($status==4) return img_picto($langs->trans('StatusOrderCanceled'),'statut5');
1085 if ($status==1) return img_picto($langs->trans('StatusOrderDraft'),'statut0');
1086 if ($status==2) return img_picto($langs->trans('StatusOrderValidated'),'statut1');
1087 if ($status==2) return img_picto($langs->trans('StatusOrderOnProcess'),'statut3');
1088 if ($status==5) return img_picto($langs->trans('StatusOrderToBill'),'statut4');
1089 if ($status==6) return img_picto($langs->trans('StatusOrderOnProcess'),'statut6');
1090 */
1091 print '</td>';
1092 print '</tr>';
1093
1094 $i++;
1095 }
1096
1097 print '<tr class="liste_total"><td colspan="4">'.$langs->trans("Number").': '.$i.'</td>';
1098 print '<td class="right" width="100">'.$langs->trans("TotalHT").' : '.price($total_HT).'</td>';
1099 print '<td class="right" width="100">'.$langs->trans("TotalTTC").' : '.price($total_TTC).'</td>';
1100 print '<td>&nbsp;</td>';
1101 print '</tr>';
1102 } else {
1103 $this->error = $this->db->lasterror();
1104 return -1;
1105 }
1106 }
1107
1108 return 0;
1109 }
1110
1111 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1117 public function fetch_lines()
1118 {
1119 // phpcs:enable
1120 $this->lines = array();
1121
1122 $sql = ' SELECT de.rowid, de.comments, de.qty, de.value_unit, de.date, de.rang,';
1123 $sql .= " de.".$this->fk_element.", de.fk_c_type_fees, de.fk_c_exp_tax_cat, de.fk_projet as fk_project,";
1124 $sql .= ' de.tva_tx, de.vat_src_code,';
1125 $sql .= ' de.localtax1_tx, de.localtax2_tx, de.localtax1_type, de.localtax2_type,';
1126 $sql .= ' de.fk_ecm_files,';
1127 $sql .= ' de.total_ht, de.total_tva, de.total_ttc,';
1128 $sql .= ' de.total_localtax1, de.total_localtax2, de.rule_warning_message,';
1129 $sql .= ' ctf.code as code_type_fees, ctf.label as label_type_fees, ctf.accountancy_code as accountancy_code_type_fees,';
1130 $sql .= ' p.ref as ref_projet, p.title as title_projet';
1131 $sql .= ' FROM '.MAIN_DB_PREFIX.$this->table_element_line.' as de';
1132 $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_type_fees as ctf ON de.fk_c_type_fees = ctf.id';
1133 $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'projet as p ON de.fk_projet = p.rowid';
1134 $sql .= " WHERE de.".$this->fk_element." = ".((int) $this->id);
1135 if (getDolGlobalString('EXPENSEREPORT_LINES_SORTED_BY_ROWID')) {
1136 $sql .= ' ORDER BY de.rang ASC, de.rowid ASC';
1137 } else {
1138 $sql .= ' ORDER BY de.rang ASC, de.date ASC';
1139 }
1140
1141 $resql = $this->db->query($sql);
1142 if ($resql) {
1143 $num = $this->db->num_rows($resql);
1144 $i = 0;
1145 while ($i < $num) {
1146 $objp = $this->db->fetch_object($resql);
1147
1148 $deplig = new ExpenseReportLine($this->db);
1149
1150 $deplig->rowid = $objp->rowid;
1151 $deplig->id = $objp->rowid;
1152 $deplig->comments = $objp->comments;
1153 $deplig->qty = $objp->qty;
1154 $deplig->value_unit = $objp->value_unit;
1155 $deplig->date = $objp->date;
1156 $deplig->dates = $this->db->jdate($objp->date);
1157
1158 $deplig->fk_expensereport = $objp->fk_expensereport;
1159 $deplig->fk_c_type_fees = $objp->fk_c_type_fees;
1160 $deplig->fk_c_exp_tax_cat = $objp->fk_c_exp_tax_cat;
1161 $deplig->fk_projet = $objp->fk_project; // deprecated
1162 $deplig->fk_project = $objp->fk_project;
1163 $deplig->fk_ecm_files = $objp->fk_ecm_files;
1164
1165 $deplig->total_ht = $objp->total_ht;
1166 $deplig->total_tva = $objp->total_tva;
1167 $deplig->total_ttc = $objp->total_ttc;
1168 $deplig->total_localtax1 = $objp->total_localtax1;
1169 $deplig->total_localtax2 = $objp->total_localtax2;
1170
1171 $deplig->type_fees_code = empty($objp->code_type_fees) ? 'TF_OTHER' : $objp->code_type_fees;
1172 $deplig->type_fees_libelle = $objp->label_type_fees;
1173 $deplig->type_fees_accountancy_code = $objp->accountancy_code_type_fees;
1174
1175 $deplig->tva_tx = $objp->tva_tx;
1176 $deplig->vatrate = $objp->tva_tx;
1177 $deplig->vat_src_code = $objp->vat_src_code;
1178 $deplig->localtax1_tx = $objp->localtax1_tx;
1179 $deplig->localtax2_tx = $objp->localtax2_tx;
1180 $deplig->localtax1_type = $objp->localtax1_type;
1181 $deplig->localtax2_type = $objp->localtax2_type;
1182
1183 $deplig->projet_ref = $objp->ref_projet;
1184 $deplig->projet_title = $objp->title_projet;
1185
1186 $deplig->rule_warning_message = $objp->rule_warning_message;
1187
1188 $deplig->rang = $objp->rang;
1189
1190 $this->lines[$i] = $deplig;
1191
1192 $i++;
1193 }
1194 $this->db->free($resql);
1195 return 1;
1196 } else {
1197 $this->error = $this->db->lasterror();
1198 dol_syslog(get_class($this)."::fetch_lines: Error ".$this->error, LOG_ERR);
1199 return -3;
1200 }
1201 }
1202
1203
1211 public function delete($user = null, $notrigger = 0)
1212 {
1213 global $conf;
1214 require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
1215
1216 $error = 0;
1217
1218 $this->db->begin();
1219
1220 if (!$notrigger) {
1221 // Call trigger
1222 $result = $this->call_trigger('EXPENSE_REPORT_DELETE', $user);
1223 if ($result < 0) {
1224 $error++;
1225 }
1226 // End call triggers
1227 }
1228
1229 // Delete extrafields of lines and lines
1230 if (!$error && !empty($this->table_element_line)) {
1231 $tabletodelete = $this->table_element_line;
1232 //$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).")";
1233 $sql = "DELETE FROM ".MAIN_DB_PREFIX.$tabletodelete." WHERE ".$this->fk_element." = ".((int) $this->id);
1234 if (!$this->db->query($sql)) {
1235 $error++;
1236 $this->error = $this->db->lasterror();
1237 $this->errors[] = $this->error;
1238 dol_syslog(get_class($this)."::delete error ".$this->error, LOG_ERR);
1239 }
1240 }
1241
1242 if (!$error) {
1243 // Delete linked object
1244 $res = $this->deleteObjectLinked();
1245 if ($res < 0) {
1246 $error++;
1247 }
1248 }
1249
1250 if (!$error) {
1251 // Delete linked contacts
1252 $res = $this->delete_linked_contact();
1253 if ($res < 0) {
1254 $error++;
1255 }
1256 }
1257
1258 // Removed extrafields of object
1259 if (!$error) {
1260 $result = $this->deleteExtraFields();
1261 if ($result < 0) {
1262 $error++;
1263 dol_syslog(get_class($this)."::delete error ".$this->error, LOG_ERR);
1264 }
1265 }
1266
1267 // Delete main record
1268 if (!$error) {
1269 $sql = "DELETE FROM ".MAIN_DB_PREFIX.$this->table_element." WHERE rowid = ".((int) $this->id);
1270 $res = $this->db->query($sql);
1271 if (!$res) {
1272 $error++;
1273 $this->error = $this->db->lasterror();
1274 $this->errors[] = $this->error;
1275 dol_syslog(get_class($this)."::delete error ".$this->error, LOG_ERR);
1276 }
1277 }
1278
1279 // Delete record into ECM index and physically
1280 if (!$error) {
1281 $res = $this->deleteEcmFiles(0); // Deleting files physically is done later with the dol_delete_dir_recursive
1282 $res = $this->deleteEcmFiles(1); // Deleting files physically is done later with the dol_delete_dir_recursive
1283 if (!$res) {
1284 $error++;
1285 }
1286 }
1287
1288 if (!$error) {
1289 // We remove directory
1290 $ref = dol_sanitizeFileName($this->ref);
1291 if ($conf->expensereport->multidir_output[$this->entity] && !empty($this->ref)) {
1292 $dir = $conf->expensereport->multidir_output[$this->entity]."/".$ref;
1293 $file = $dir."/".$ref.".pdf";
1294 if (file_exists($file)) {
1295 dol_delete_preview($this);
1296
1297 if (!dol_delete_file($file, 0, 0, 0, $this)) {
1298 $this->error = 'ErrorFailToDeleteFile';
1299 $this->errors[] = $this->error;
1300 $this->db->rollback();
1301 return 0;
1302 }
1303 }
1304 if (file_exists($dir)) {
1305 $res = @dol_delete_dir_recursive($dir);
1306 if (!$res) {
1307 $this->error = 'ErrorFailToDeleteDir';
1308 $this->errors[] = $this->error;
1309 $this->db->rollback();
1310 return 0;
1311 }
1312 }
1313 }
1314 }
1315
1316 if (!$error) {
1317 dol_syslog(get_class($this)."::delete ".$this->id." by ".$user->id, LOG_DEBUG);
1318 $this->db->commit();
1319 return 1;
1320 } else {
1321 $this->db->rollback();
1322 return -1;
1323 }
1324 }
1325
1333 public function setValidate($fuser, $notrigger = 0)
1334 {
1335 global $conf, $langs, $user;
1336
1337 $error = 0;
1338 $now = dol_now();
1339
1340 // Protection
1341 if ($this->status == self::STATUS_VALIDATED) {
1342 dol_syslog(get_class($this)."::valid action abandoned: already validated", LOG_WARNING);
1343 return 0;
1344 }
1345
1346 $this->date_valid = $now; // Required for the getNextNum later.
1347
1348 // Define new ref
1349 if (!$error && (preg_match('/^[\‍(]?PROV/i', $this->ref) || empty($this->ref))) { // empty should not happened, but when it occurs, the test save life
1350 $num = $this->getNextNumRef();
1351 } else {
1352 $num = $this->ref;
1353 }
1354 if (empty($num) || $num < 0) {
1355 return -1;
1356 }
1357
1358 $this->newref = dol_sanitizeFileName($num);
1359
1360 $this->db->begin();
1361
1362 // Validate
1363 $sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element;
1364 $sql .= " SET ref = '".$this->db->escape($num)."',";
1365 $sql .= " fk_statut = ".self::STATUS_VALIDATED.",";
1366 $sql .= " date_valid = '".$this->db->idate($this->date_valid)."',";
1367 $sql .= " fk_user_valid = ".((int) $user->id);
1368 $sql .= " WHERE rowid = ".((int) $this->id);
1369
1370 $resql = $this->db->query($sql);
1371 if ($resql) {
1372 if (!$error && !$notrigger) {
1373 // Call trigger
1374 $result = $this->call_trigger('EXPENSE_REPORT_VALIDATE', $fuser);
1375 if ($result < 0) {
1376 $error++;
1377 }
1378 // End call triggers
1379 }
1380
1381 if (!$error) {
1382 $this->oldref = $this->ref;
1383
1384 // Rename directory if dir was a temporary ref
1385 if (preg_match('/^[\‍(]?PROV/i', $this->ref)) {
1386 require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
1387
1388 // Now we rename also files into index
1389 $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)."'";
1390 $sql .= " WHERE filename LIKE '".$this->db->escape($this->ref)."%' AND filepath = 'expensereport/".$this->db->escape($this->ref)."' AND entity = ".((int) $this->entity);
1391 $resql = $this->db->query($sql);
1392 if (!$resql) {
1393 $error++;
1394 $this->error = $this->db->lasterror();
1395 }
1396 $sql = 'UPDATE '.MAIN_DB_PREFIX."ecm_files set filepath = 'expensereport/".$this->db->escape($this->newref)."'";
1397 $sql .= " WHERE filepath = 'expensereport/".$this->db->escape($this->ref)."' and entity = ".$conf->entity;
1398 $resql = $this->db->query($sql);
1399 if (!$resql) {
1400 $error++;
1401 $this->error = $this->db->lasterror();
1402 }
1403
1404 // We rename directory ($this->ref = old ref, $num = new ref) in order not to lose the attachments
1405 $oldref = dol_sanitizeFileName($this->ref);
1406 $newref = dol_sanitizeFileName($num);
1407 $dirsource = $conf->expensereport->multidir_output[$this->entity].'/'.$oldref;
1408 $dirdest = $conf->expensereport->multidir_output[$this->entity].'/'.$newref;
1409 if (!$error && file_exists($dirsource)) {
1410 dol_syslog(get_class($this)."::setValidate() rename dir ".$dirsource." into ".$dirdest);
1411
1412 if (@rename($dirsource, $dirdest)) {
1413 dol_syslog("Rename ok");
1414 // Rename docs starting with $oldref with $newref
1415 $listoffiles = dol_dir_list($dirdest, 'files', 1, '^'.preg_quote($oldref, '/'));
1416 foreach ($listoffiles as $fileentry) {
1417 $dirsource = $fileentry['name'];
1418 $dirdest = preg_replace('/^'.preg_quote($oldref, '/').'/', $newref, $dirsource);
1419 $dirsource = $fileentry['path'].'/'.$dirsource;
1420 $dirdest = $fileentry['path'].'/'.$dirdest;
1421 @rename($dirsource, $dirdest);
1422 }
1423 }
1424 }
1425 }
1426 }
1427
1428 // Set new ref and current status
1429 if (!$error) {
1430 $this->ref = $num;
1432 }
1433
1434 if (empty($error)) {
1435 $this->db->commit();
1436 return 1;
1437 } else {
1438 $this->db->rollback();
1439 $this->error = $this->db->error();
1440 return -2;
1441 }
1442 } else {
1443 $this->db->rollback();
1444 $this->error = $this->db->lasterror();
1445 return -1;
1446 }
1447 }
1448
1449 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1456 public function set_save_from_refuse($fuser)
1457 {
1458 // phpcs:enable
1459 // Sélection de la date de début de la NDF
1460 $sql = 'SELECT date_debut';
1461 $sql .= ' FROM '.MAIN_DB_PREFIX.$this->table_element;
1462 $sql .= " WHERE rowid = ".((int) $this->id);
1463
1464 $result = $this->db->query($sql);
1465
1466 $objp = $this->db->fetch_object($result);
1467
1468 $this->date_debut = $this->db->jdate($objp->date_debut);
1469
1470 if ($this->status != self::STATUS_VALIDATED) {
1471 $sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element;
1472 $sql .= " SET fk_statut = ".self::STATUS_VALIDATED;
1473 $sql .= " WHERE rowid = ".((int) $this->id);
1474
1475 dol_syslog(get_class($this)."::set_save_from_refuse", LOG_DEBUG);
1476
1477 if ($this->db->query($sql)) {
1478 return 1;
1479 } else {
1480 $this->error = $this->db->lasterror();
1481 return -1;
1482 }
1483 } else {
1484 dol_syslog(get_class($this)."::set_save_from_refuse expensereport already with save status", LOG_WARNING);
1485 }
1486
1487 return 0;
1488 }
1489
1497 public function setApproved($fuser, $notrigger = 0)
1498 {
1499 $now = dol_now();
1500 $error = 0;
1501
1502 // date approval
1503 $this->date_approve = $now;
1504 if ($this->status != self::STATUS_APPROVED) {
1505 $this->db->begin();
1506
1507 $sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element;
1508 $sql .= " SET ref = '".$this->db->escape($this->ref)."', fk_statut = ".self::STATUS_APPROVED.", fk_user_approve = ".((int) $fuser->id).",";
1509 $sql .= " date_approve='".$this->db->idate($this->date_approve)."'";
1510 $sql .= " WHERE rowid = ".((int) $this->id);
1511 if ($this->db->query($sql)) {
1512 if (!$notrigger) {
1513 // Call trigger
1514 $result = $this->call_trigger('EXPENSE_REPORT_APPROVE', $fuser);
1515
1516 if ($result < 0) {
1517 $error++;
1518 }
1519 // End call triggers
1520 }
1521
1522 if (empty($error)) {
1523 $this->db->commit();
1524 return 1;
1525 } else {
1526 $this->db->rollback();
1527 $this->error = $this->db->error();
1528 return -2;
1529 }
1530 } else {
1531 $this->db->rollback();
1532 $this->error = $this->db->lasterror();
1533 return -1;
1534 }
1535 } else {
1536 dol_syslog(get_class($this)."::setApproved expensereport already with approve status", LOG_WARNING);
1537 }
1538
1539 return 0;
1540 }
1541
1550 public function setDeny($fuser, $details, $notrigger = 0)
1551 {
1552 $now = dol_now();
1553 $error = 0;
1554
1555 // date de refus
1556 if ($this->status != self::STATUS_REFUSED) {
1557 $sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element;
1558 $sql .= " SET ref = '".$this->db->escape($this->ref)."', fk_statut = ".self::STATUS_REFUSED.", fk_user_refuse = ".((int) $fuser->id).",";
1559 $sql .= " date_refuse='".$this->db->idate($now)."',";
1560 $sql .= " detail_refuse='".$this->db->escape($details)."',";
1561 $sql .= " fk_user_approve = NULL";
1562 $sql .= " WHERE rowid = ".((int) $this->id);
1563 if ($this->db->query($sql)) {
1564 $this->fk_statut = 99; // deprecated
1565 $this->status = 99;
1566 $this->fk_user_refuse = $fuser->id;
1567 $this->detail_refuse = $details;
1568 $this->date_refuse = $now;
1569
1570 if (!$notrigger) {
1571 // Call trigger
1572 $result = $this->call_trigger('EXPENSE_REPORT_DENY', $fuser);
1573
1574 if ($result < 0) {
1575 $error++;
1576 }
1577 // End call triggers
1578 }
1579
1580 if (empty($error)) {
1581 $this->db->commit();
1582 return 1;
1583 } else {
1584 $this->db->rollback();
1585 $this->error = $this->db->error();
1586 return -2;
1587 }
1588 } else {
1589 $this->db->rollback();
1590 $this->error = $this->db->lasterror();
1591 return -1;
1592 }
1593 } else {
1594 dol_syslog(get_class($this)."::setDeny expensereport already with refuse status", LOG_WARNING);
1595 }
1596
1597 return 0;
1598 }
1599
1600 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1610 public function set_unpaid($fuser, $notrigger = 0)
1611 {
1612 // phpcs:enable
1613 dol_syslog(get_class($this)."::set_unpaid is deprecated, use setUnpaid instead", LOG_NOTICE);
1614 return $this->setUnpaid($fuser, $notrigger);
1615 }
1616
1624 public function setUnpaid($fuser, $notrigger = 0)
1625 {
1626 $error = 0;
1627
1628 if ($this->paid) {
1629 $this->db->begin();
1630
1631 $sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element;
1632 $sql .= " SET paid = 0, fk_statut = ".self::STATUS_APPROVED;
1633 $sql .= " WHERE rowid = ".((int) $this->id);
1634
1635 dol_syslog(get_class($this)."::set_unpaid", LOG_DEBUG);
1636
1637 if ($this->db->query($sql)) {
1638 if (!$notrigger) {
1639 // Call trigger
1640 $result = $this->call_trigger('EXPENSE_REPORT_UNPAID', $fuser);
1641
1642 if ($result < 0) {
1643 $error++;
1644 }
1645 // End call triggers
1646 }
1647
1648 if (empty($error)) {
1649 $this->db->commit();
1650 return 1;
1651 } else {
1652 $this->db->rollback();
1653 $this->error = $this->db->error();
1654 return -2;
1655 }
1656 } else {
1657 $this->db->rollback();
1658 $this->error = $this->db->error();
1659 return -1;
1660 }
1661 } else {
1662 dol_syslog(get_class($this)."::set_unpaid expensereport already with unpaid status", LOG_WARNING);
1663 }
1664
1665 return 0;
1666 }
1667
1668 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1677 public function set_cancel($fuser, $detail, $notrigger = 0)
1678 {
1679 // phpcs:enable
1680 $error = 0;
1681 $this->date_cancel = $this->db->idate(dol_now());
1682 if ($this->status != self::STATUS_CANCELED) {
1683 $this->db->begin();
1684
1685 $sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element;
1686 $sql .= " SET fk_statut = ".self::STATUS_CANCELED.", fk_user_cancel = ".((int) $fuser->id);
1687 $sql .= ", date_cancel='".$this->db->idate($this->date_cancel)."'";
1688 $sql .= ", detail_cancel='".$this->db->escape($detail)."'";
1689 $sql .= " WHERE rowid = ".((int) $this->id);
1690
1691 dol_syslog(get_class($this)."::set_cancel", LOG_DEBUG);
1692
1693 if ($this->db->query($sql)) {
1694 if (!$notrigger) {
1695 // Call trigger
1696 $result = $this->call_trigger('EXPENSE_REPORT_CANCEL', $fuser);
1697
1698 if ($result < 0) {
1699 $error++;
1700 }
1701 // End call triggers
1702 }
1703
1704 if (empty($error)) {
1705 $this->db->commit();
1706 return 1;
1707 } else {
1708 $this->db->rollback();
1709 $this->error = $this->db->error();
1710 return -2;
1711 }
1712 } else {
1713 $this->db->rollback();
1714 $this->error = $this->db->error();
1715 return -1;
1716 }
1717 } else {
1718 dol_syslog(get_class($this)."::set_cancel expensereport already with cancel status", LOG_WARNING);
1719 }
1720 return 0;
1721 }
1722
1728 public function getNextNumRef()
1729 {
1730 global $langs, $conf;
1731 $langs->load("trips");
1732
1733 if (getDolGlobalString('EXPENSEREPORT_ADDON')) {
1734 $mybool = false;
1735
1736 $file = getDolGlobalString('EXPENSEREPORT_ADDON') . ".php";
1737 $classname = getDolGlobalString('EXPENSEREPORT_ADDON');
1738
1739 // Include file with class
1740 $dirmodels = array_merge(array('/'), (array) $conf->modules_parts['models']);
1741 foreach ($dirmodels as $reldir) {
1742 $dir = dol_buildpath($reldir."core/modules/expensereport/");
1743
1744 // Load file with numbering class (if found)
1745 $mybool = ((bool) @include_once $dir.$file) || $mybool;
1746 }
1747
1748 if (!$mybool) {
1749 dol_print_error(null, "Failed to include file ".$file);
1750 return '';
1751 }
1752
1753 $obj = new $classname();
1754 '@phan-var-force ModeleNumRefExpenseReport $obj';
1755 $numref = $obj->getNextValue($this);
1756
1757 if ($numref != "") {
1758 return $numref;
1759 } else {
1760 $this->error = $obj->error;
1761 $this->errors = $obj->errors;
1762 //dol_print_error($this->db,get_class($this)."::getNextNumRef ".$obj->error);
1763 return -1;
1764 }
1765 } else {
1766 $this->error = "Error_EXPENSEREPORT_ADDON_NotDefined";
1767 return -2;
1768 }
1769 }
1770
1777 public function getTooltipContentArray($params)
1778 {
1779 global $conf, $langs;
1780
1781 $langs->load('trips');
1782
1783 $nofetch = !empty($params['nofetch']);
1784 $moretitle = $params['moretitle'] ?? '';
1785
1786 $datas = array();
1787 $datas['picto'] = img_picto('', $this->picto).' <u class="paddingrightonly">'.$langs->trans("ExpenseReport").'</u>';
1788 if (isset($this->status)) {
1789 $datas['picto'] .= ' '.$this->getLibStatut(5);
1790 }
1791 if ($moretitle) {
1792 $datas['picto'] .= ' - '.$moretitle;
1793 }
1794 if (!empty($this->ref)) {
1795 $datas['ref'] = '<br><b>'.$langs->trans('Ref').':</b> '.$this->ref;
1796 }
1797 if (!empty($this->total_ht)) {
1798 $datas['total_ht'] = '<br><b>'.$langs->trans('AmountHT').':</b> '.price($this->total_ht, 0, $langs, 0, -1, -1, $conf->currency);
1799 }
1800 if (!empty($this->total_tva)) {
1801 $datas['total_tva'] = '<br><b>'.$langs->trans('VAT').':</b> '.price($this->total_tva, 0, $langs, 0, -1, -1, $conf->currency);
1802 }
1803 if (!empty($this->total_ttc)) {
1804 $datas['total_ttc'] = '<br><b>'.$langs->trans('AmountTTC').':</b> '.price($this->total_ttc, 0, $langs, 0, -1, -1, $conf->currency);
1805 }
1806
1807 return $datas;
1808 }
1809
1822 public function getNomUrl($withpicto = 0, $option = '', $max = 0, $short = 0, $moretitle = '', $notooltip = 0, $save_lastsearch_value = -1)
1823 {
1824 global $langs, $hookmanager;
1825
1826 $result = '';
1827
1828 $url = DOL_URL_ROOT.'/expensereport/card.php?id='.$this->id;
1829
1830 if ($short) {
1831 return $url;
1832 }
1833
1834 $params = [
1835 'id' => $this->id,
1836 'objecttype' => $this->element,
1837 'option' => $option,
1838 'moretitle' => $moretitle,
1839 'nofetch' => 1,
1840 ];
1841 $classfortooltip = 'classfortooltip';
1842 $dataparams = '';
1843 if (getDolGlobalInt('MAIN_ENABLE_AJAX_TOOLTIP')) {
1844 $classfortooltip = 'classforajaxtooltip';
1845 $dataparams = ' data-params="'.dol_escape_htmltag(json_encode($params)).'"';
1846 $label = '';
1847 } else {
1848 $label = implode($this->getTooltipContentArray($params));
1849 }
1850
1851 if ($option != 'nolink') {
1852 // Add param to save lastsearch_values or not
1853 $add_save_lastsearch_values = ($save_lastsearch_value == 1 ? 1 : 0);
1854 if ($save_lastsearch_value == -1 && isset($_SERVER["PHP_SELF"]) && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) {
1855 $add_save_lastsearch_values = 1;
1856 }
1857 if ($add_save_lastsearch_values) {
1858 $url .= '&save_lastsearch_values=1';
1859 }
1860 }
1861
1862 $ref = $this->ref;
1863 if (empty($ref)) {
1864 $ref = $this->id;
1865 }
1866
1867 $linkclose = '';
1868 if (empty($notooltip)) {
1869 if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
1870 $label = $langs->trans("ShowExpenseReport");
1871 $linkclose .= ' alt="'.dolPrintHTMLForAttribute($label).'"';
1872 }
1873 $linkclose .= ($label ? ' title="'.dolPrintHTMLForAttribute($label).'"' : ' title="tocomplete"');
1874 $linkclose .= $dataparams.' class="'.$classfortooltip.'"';
1875 }
1876
1877 $linkstart = '<a href="'.$url.'"';
1878 $linkstart .= $linkclose.'>';
1879 $linkend = '</a>';
1880
1881 $result .= $linkstart;
1882 if ($withpicto) {
1883 $result .= img_object(($notooltip ? '' : $label), ($this->picto ? $this->picto : 'generic'), ($notooltip ? (($withpicto != 2) ? 'class="paddingright"' : '') : 'class="'.(($withpicto != 2) ? 'paddingright ' : '').'"'), 0, 0, $notooltip ? 0 : 1);
1884 }
1885 if ($withpicto != 2) {
1886 $result .= ($max ? dol_trunc($ref, $max) : $ref);
1887 }
1888 $result .= $linkend;
1889
1890 global $action;
1891 $hookmanager->initHooks(array($this->element . 'dao'));
1892 $parameters = array('id' => $this->id, 'getnomurl' => &$result);
1893 $reshook = $hookmanager->executeHooks('getNomUrl', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
1894 if ($reshook > 0) {
1895 $result = $hookmanager->resPrint;
1896 } else {
1897 $result .= $hookmanager->resPrint;
1898 }
1899 return $result;
1900 }
1901
1902 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1910 public function update_totaux_add($ligne_total_ht, $ligne_total_tva)
1911 {
1912 // phpcs:enable
1913 $this->total_ht += (float) $ligne_total_ht;
1914 $this->total_tva += (float) $ligne_total_tva;
1915 $this->total_ttc += $this->total_tva;
1916
1917 $sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element." SET";
1918 $sql .= " total_ht = ".((float) $this->total_ht);
1919 $sql .= " , total_ttc = ".((float) $this->total_ttc);
1920 $sql .= " , total_tva = ".((float) $this->total_tva);
1921 $sql .= " WHERE rowid = ".((int) $this->id);
1922
1923 $result = $this->db->query($sql);
1924 if ($result) {
1925 return 1;
1926 } else {
1927 $this->error = $this->db->error();
1928 return -1;
1929 }
1930 }
1931
1947 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)
1948 {
1949 global $langs, $mysoc;
1950
1951 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);
1952
1953 if ($this->status == self::STATUS_DRAFT || $this->status == self::STATUS_REFUSED) {
1954 if (empty($qty)) {
1955 $qty = 0;
1956 }
1957 if (empty($fk_c_type_fees) || $fk_c_type_fees < 0) {
1958 $fk_c_type_fees = 0;
1959 }
1960 if (empty($fk_c_exp_tax_cat) || $fk_c_exp_tax_cat < 0) {
1961 $fk_c_exp_tax_cat = 0;
1962 }
1963 if (empty($vatrate) || $vatrate < 0) {
1964 $vatrate = 0;
1965 }
1966 if (empty($date)) {
1967 $date = '';
1968 }
1969 if (empty($fk_project)) {
1970 $fk_project = 0;
1971 }
1972
1973 $qty = (float) price2num($qty);
1974 if (!preg_match('/\s*\‍((.*)\‍)/', $vatrate)) {
1975 $vatrate = price2num($vatrate); // $txtva can have format '5.0 (XXX)' or '5'
1976 }
1977 $up = price2num($up);
1978
1979 $this->db->begin();
1980
1981 $this->line = new ExpenseReportLine($this->db);
1982
1983 // We don't know seller and buyer for expense reports
1984 $seller = $mysoc; // We use same than current company (expense report are often done in same country)
1985 $seller->tva_assuj = 1; // Most seller uses vat
1986 $buyer = new Societe($this->db);
1987
1988 $localtaxes_type = getLocalTaxesFromRate($vatrate, 0, $buyer, $seller);
1989
1990 $vat_src_code = '';
1991 $reg = array();
1992 if (preg_match('/\s*\‍((.*)\‍)/', $vatrate, $reg)) {
1993 $vat_src_code = $reg[1];
1994 $vatrate = preg_replace('/\s*\‍(.*\‍)/', '', $vatrate); // Remove code into vatrate.
1995 }
1996 $vatrate = preg_replace('/\*/', '', $vatrate);
1997
1998 $tmp = calcul_price_total($qty, (float) $up, 0, (float) price2num($vatrate), -1, -1, 0, 'TTC', 0, $type, $seller, $localtaxes_type);
1999
2000 $this->line->value_unit = $up;
2001
2002 $this->line->vat_src_code = $vat_src_code;
2003 $this->line->vatrate = price2num($vatrate);
2004 $this->line->localtax1_tx = $localtaxes_type[1];
2005 $this->line->localtax2_tx = $localtaxes_type[3];
2006 $this->line->localtax1_type = $localtaxes_type[0];
2007 $this->line->localtax2_type = $localtaxes_type[2];
2008
2009 $this->line->total_ttc = (float) $tmp[2];
2010 $this->line->total_ht = (float) $tmp[0];
2011 $this->line->total_tva = (float) $tmp[1];
2012 $this->line->total_localtax1 = (float) $tmp[9];
2013 $this->line->total_localtax2 = (float) $tmp[10];
2014
2015 $this->line->fk_expensereport = $this->id;
2016 $this->line->qty = $qty;
2017 $this->line->date = $date;
2018 $this->line->fk_c_type_fees = $fk_c_type_fees;
2019 $this->line->fk_c_exp_tax_cat = $fk_c_exp_tax_cat;
2020 $this->line->comments = $comments;
2021 $this->line->fk_projet = $fk_project; // deprecated
2022 $this->line->fk_project = $fk_project;
2023
2024 $this->line->fk_ecm_files = $fk_ecm_files;
2025
2026 $this->applyOffset();
2027 $this->checkRules($type, $seller);
2028
2029 $result = $this->line->insert(0, true);
2030 if ($result > 0) {
2031 $result = $this->update_price(1); // This method is designed to add line from user input so total calculation must be done using 'auto' mode.
2032 if ($result > 0) {
2033 $this->db->commit();
2034 return $this->line->id;
2035 } else {
2036 $this->db->rollback();
2037 return -1;
2038 }
2039 } else {
2040 $this->error = $this->line->error;
2041 dol_syslog(get_class($this)."::addline error=".$this->error, LOG_ERR);
2042 $this->db->rollback();
2043 return -2;
2044 }
2045 } else {
2046 dol_syslog(get_class($this)."::addline status of expense report must be Draft to allow use of ->addline()", LOG_ERR);
2047 $this->error = 'ErrorExpenseNotDraftAndNotRefused';
2048 return -3;
2049 }
2050 }
2051
2059 public function checkRules($type = 0, $seller = '')
2060 {
2061 global $conf, $db, $langs, $mysoc;
2062
2063 $langs->load('trips');
2064
2065 // We don't know seller and buyer for expense reports
2066 if (!is_object($seller)) {
2067 $seller = $mysoc; // We use same than current company (expense report are often done in same country)
2068 $seller->tva_assuj = 1; // Most seller uses vat
2069 }
2070
2071 $expensereportrule = new ExpenseReportRule($db);
2072 $rulestocheck = $expensereportrule->getAllRule($this->line->fk_c_type_fees, $this->line->date, $this->fk_user_author);
2073
2074 $violation = 0;
2075 $rule_warning_message_tab = array();
2076
2077 $current_total_ttc = $this->line->total_ttc;
2078 $new_current_total_ttc = $this->line->total_ttc;
2079
2080 // check if one is violated
2081 foreach ($rulestocheck as $rule) {
2082 if (in_array($rule->code_expense_rules_type, array('EX_DAY', 'EX_MON', 'EX_YEA'))) {
2083 $amount_to_test = $this->line->getExpAmount($rule, $this->fk_user_author, $rule->code_expense_rules_type);
2084 } else {
2085 $amount_to_test = $current_total_ttc; // EX_EXP
2086 }
2087
2088 $amount_to_test = $amount_to_test - $current_total_ttc + $new_current_total_ttc; // if amount as been modified by a previous rule
2089
2090 if ($amount_to_test > $rule->amount) {
2091 $violation++;
2092
2093 if ($rule->restrictive) {
2094 $this->error = 'ExpenseReportConstraintViolationError';
2095 $this->errors[] = $this->error;
2096
2097 $new_current_total_ttc -= $amount_to_test - $rule->amount; // ex, entered 16€, limit 12€, subtracts 4€;
2098 $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));
2099 } else {
2100 $this->error = 'ExpenseReportConstraintViolationWarning';
2101 $this->errors[] = $this->error;
2102
2103 $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));
2104 }
2105
2106 // No break, we should test if another rule is violated
2107 }
2108 }
2109
2110 $this->line->rule_warning_message = implode('\n', $rule_warning_message_tab);
2111
2112 if ($violation > 0) {
2113 $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);
2114
2115 $this->line->value_unit = $tmp[5];
2116 $this->line->total_ttc = (float) $tmp[2];
2117 $this->line->total_ht = (float) $tmp[0];
2118 $this->line->total_tva = (float) $tmp[1];
2119 $this->line->total_localtax1 = (float) $tmp[9];
2120 $this->line->total_localtax2 = (float) $tmp[10];
2121
2122 return false;
2123 } else {
2124 return true;
2125 }
2126 }
2127
2135 public function applyOffset($type = 0, $seller = '')
2136 {
2137 global $mysoc;
2138
2139 if (!getDolGlobalString('MAIN_USE_EXPENSE_IK')) {
2140 return false;
2141 }
2142
2143 $userauthor = new User($this->db);
2144 if ($userauthor->fetch($this->fk_user_author) <= 0) {
2145 $this->error = 'ErrorCantFetchUser';
2146 $this->errors[] = 'ErrorCantFetchUser';
2147 return false;
2148 }
2149
2150 // We don't know seller and buyer for expense reports
2151 if (!is_object($seller)) {
2152 $seller = $mysoc; // We use same than current company (expense report are often done in same country)
2153 $seller->tva_assuj = 1; // Most seller uses vat
2154 }
2155
2156 $expenseik = new ExpenseReportIk($this->db);
2157 $range = $expenseik->getRangeByUser($userauthor, $this->line->fk_c_exp_tax_cat);
2158
2159 if (empty($range)) {
2160 $this->error = 'ErrorNoRangeAvailable';
2161 $this->errors[] = 'ErrorNoRangeAvailable';
2162 return false;
2163 }
2164
2165 if (getDolGlobalString('MAIN_EXPENSE_APPLY_ENTIRE_OFFSET')) {
2166 $ikoffset = $range->ikoffset;
2167 } else {
2168 $ikoffset = $range->ikoffset / 12; // The amount of offset is a global value for the year
2169 }
2170
2171 // Test if ikoffset has been applied for the current month
2172 if (!$this->offsetAlreadyGiven()) {
2173 $new_up = $range->coef + ($ikoffset / $this->line->qty);
2174 $tmp = calcul_price_total($this->line->qty, $new_up, 0, $this->line->vatrate, 0, 0, 0, 'TTC', 0, $type, $seller);
2175
2176 $this->line->value_unit = $tmp[5];
2177 $this->line->total_ttc = (float) $tmp[2];
2178 $this->line->total_ht = (float) $tmp[0];
2179 $this->line->total_tva = (float) $tmp[1];
2180 $this->line->total_localtax1 = (float) $tmp[9];
2181 $this->line->total_localtax2 = (float) $tmp[10];
2182
2183 return true;
2184 }
2185
2186 return false;
2187 }
2188
2194 public function offsetAlreadyGiven()
2195 {
2196 $sql = 'SELECT e.rowid FROM '.MAIN_DB_PREFIX.'expensereport e';
2197 $sql .= " INNER JOIN ".MAIN_DB_PREFIX."expensereport_det d ON (e.rowid = d.fk_expensereport)";
2198 $sql .= " INNER JOIN ".MAIN_DB_PREFIX."c_type_fees f ON (d.fk_c_type_fees = f.id AND f.code = 'EX_KME')";
2199 $sql .= " WHERE e.fk_user_author = ".(int) $this->fk_user_author;
2200 $sql .= " AND YEAR(d.date) = '".dol_print_date($this->line->date, '%Y')."' AND MONTH(d.date) = '".dol_print_date($this->line->date, '%m')."'";
2201 if (!empty($this->line->id)) {
2202 $sql .= ' AND d.rowid <> '.((int) $this->line->id);
2203 }
2204
2205 dol_syslog(get_class($this)."::offsetAlreadyGiven");
2206 $resql = $this->db->query($sql);
2207 if ($resql) {
2208 $num = $this->db->num_rows($resql);
2209 if ($num > 0) {
2210 return true;
2211 }
2212 } else {
2213 dol_print_error($this->db);
2214 }
2215
2216 return false;
2217 }
2218
2236 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)
2237 {
2238 global $user, $mysoc;
2239
2240 if ($this->status == self::STATUS_DRAFT || $this->status == self::STATUS_REFUSED) {
2241 $this->db->begin();
2242
2243 $error = 0;
2244 $type = 0; // TODO What if type is service ?
2245
2246 // We don't know seller and buyer for expense reports
2247 $seller = $mysoc; // We use same than current company (expense report are often done in same country)
2248 $seller->tva_assuj = 1; // Most seller uses vat
2249 $seller->localtax1_assuj = $mysoc->localtax1_assuj; // We don't know, we reuse the state of company
2250 $seller->localtax2_assuj = $mysoc->localtax1_assuj; // We don't know, we reuse the state of company
2251 $buyer = new Societe($this->db);
2252
2253 $localtaxes_type = getLocalTaxesFromRate($vatrate, 0, $buyer, $seller);
2254
2255 // Clean vat code
2256 $reg = array();
2257 $vat_src_code = '';
2258 if (preg_match('/\‍((.*)\‍)/', (string) $vatrate, $reg)) {
2259 $vat_src_code = $reg[1];
2260 $vatrate = preg_replace('/\s*\‍(.*\‍)/', '', (string) $vatrate); // Remove code into vatrate.
2261 }
2262 $vatrate = preg_replace('/\*/', '', $vatrate);
2263
2264 $tmp = calcul_price_total($qty, $value_unit, 0, (float) price2num($vatrate), -1, -1, 0, 'TTC', 0, $type, $seller, $localtaxes_type);
2265 // calcul total of line
2266 // $total_ttc = price2num($qty*$value_unit, 'MT');
2267
2268 $tx_tva = 1 + (float) $vatrate / 100;
2269
2270 $this->line = new ExpenseReportLine($this->db);
2271 $this->line->comments = $comments;
2272 $this->line->qty = $qty;
2273 $this->line->value_unit = $value_unit;
2274 $this->line->date = $date;
2275
2276 $this->line->fk_expensereport = $expensereport_id;
2277 $this->line->fk_c_type_fees = $type_fees_id;
2278 $this->line->fk_c_exp_tax_cat = $fk_c_exp_tax_cat;
2279 $this->line->fk_projet = $projet_id; // deprecated
2280 $this->line->fk_project = $projet_id;
2281
2282 $this->line->vat_src_code = $vat_src_code;
2283 $this->line->vatrate = price2num($vatrate);
2284 $this->line->localtax1_tx = $localtaxes_type[1];
2285 $this->line->localtax2_tx = $localtaxes_type[3];
2286 $this->line->localtax1_type = $localtaxes_type[0];
2287 $this->line->localtax2_type = $localtaxes_type[2];
2288
2289 $this->line->total_ttc = (float) $tmp[2];
2290 $this->line->total_ht = (float) $tmp[0];
2291 $this->line->total_tva = (float) $tmp[1];
2292 $this->line->total_localtax1 = (float) $tmp[9];
2293 $this->line->total_localtax2 = (float) $tmp[10];
2294
2295 $this->line->fk_ecm_files = $fk_ecm_files;
2296
2297 $this->line->id = ((int) $rowid);
2298
2299 // Select des infos sur le type fees
2300 $sql = "SELECT c.code as code_type_fees, c.label as label_type_fees";
2301 $sql .= " FROM ".MAIN_DB_PREFIX."c_type_fees as c";
2302 $sql .= " WHERE c.id = ".((int) $type_fees_id);
2303 $resql = $this->db->query($sql);
2304 if ($resql) {
2305 $objp_fees = $this->db->fetch_object($resql);
2306 $this->line->type_fees_code = $objp_fees->code_type_fees;
2307 $this->line->type_fees_libelle = $objp_fees->label_type_fees;
2308 $this->db->free($resql);
2309 }
2310
2311 // Select des information du projet
2312 $sql = "SELECT p.ref as ref_projet, p.title as title_projet";
2313 $sql .= " FROM ".MAIN_DB_PREFIX."projet as p";
2314 $sql .= " WHERE p.rowid = ".((int) $projet_id);
2315 $resql = $this->db->query($sql);
2316 if ($resql) {
2317 $objp_projet = $this->db->fetch_object($resql);
2318 $this->line->projet_ref = $objp_projet->ref_projet;
2319 $this->line->projet_title = $objp_projet->title_projet;
2320 $this->db->free($resql);
2321 }
2322
2323 $this->applyOffset();
2324 $this->checkRules();
2325
2326 $result = $this->line->update($user, $notrigger);
2327 if ($result < 0) {
2328 $error++;
2329 }
2330
2331 if (!$error) {
2332 $this->db->commit();
2333 return 1;
2334 } else {
2335 $this->error = $this->line->error;
2336 $this->errors = $this->line->errors;
2337 $this->db->rollback();
2338 return -2;
2339 }
2340 }
2341
2342 return 0;
2343 }
2344
2353 public function deleteLine($rowid, $fuser = '', $notrigger = 0)
2354 {
2355 $error = 0;
2356
2357 $this->db->begin();
2358
2359 if (!$notrigger) {
2360 // Call triggers
2361 $result = $this->call_trigger('EXPENSE_REPORT_DET_DELETE', $fuser);
2362 if ($result < 0) {
2363 $error++;
2364 }
2365 // End call triggers
2366 }
2367
2368 $sql = ' DELETE FROM '.MAIN_DB_PREFIX.$this->table_element_line;
2369 $sql .= ' WHERE rowid = '.((int) $rowid);
2370
2371 dol_syslog(get_class($this)."::deleteline sql=".$sql);
2372 $result = $this->db->query($sql);
2373
2374 if (!$result || $error > 0) {
2375 $this->error = $this->db->error();
2376 dol_syslog(get_class($this)."::deleteline Error ".$this->error, LOG_ERR);
2377 $this->db->rollback();
2378 return -1;
2379 }
2380
2381 $this->update_price(1);
2382
2383 $this->db->commit();
2384
2385 return 1;
2386 }
2387
2396 public function periodExists($fuser, $date_debut, $date_fin)
2397 {
2398 global $conf;
2399
2400 $sql = "SELECT rowid, date_debut, date_fin";
2401 $sql .= " FROM ".MAIN_DB_PREFIX.$this->table_element;
2402 $sql .= " WHERE entity = ".((int) $conf->entity); // not shared, only for the current entity
2403 $sql .= " AND fk_user_author = ".((int) $fuser->id);
2404
2405 dol_syslog(get_class($this)."::periodExists sql=".$sql);
2406 $result = $this->db->query($sql);
2407 if ($result) {
2408 $num_rows = $this->db->num_rows($result);
2409 $i = 0;
2410
2411 if ($num_rows > 0) {
2412 $date_d_form = $date_debut;
2413 $date_f_form = $date_fin;
2414
2415 while ($i < $num_rows) {
2416 $objp = $this->db->fetch_object($result);
2417
2418 $date_d_req = $this->db->jdate($objp->date_debut); // 3
2419 $date_f_req = $this->db->jdate($objp->date_fin); // 4
2420
2421 if (!($date_f_form < $date_d_req || $date_d_form > $date_f_req)) {
2422 return $objp->rowid;
2423 }
2424
2425 $i++;
2426 }
2427
2428 return 0;
2429 } else {
2430 return 0;
2431 }
2432 } else {
2433 $this->error = $this->db->lasterror();
2434 dol_syslog(get_class($this)."::periodExists Error ".$this->error, LOG_ERR);
2435 return -1;
2436 }
2437 }
2438
2439
2440 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2448 {
2449 // phpcs:enable
2450 $users_validator = array();
2451
2452 $sql = "SELECT DISTINCT ur.fk_user";
2453 $sql .= " FROM ".MAIN_DB_PREFIX."user_rights as ur, ".MAIN_DB_PREFIX."rights_def as rd";
2454 $sql .= " WHERE ur.fk_id = rd.id and rd.module = 'expensereport' AND rd.perms = 'approve'"; // Permission 'Approve';
2455 $sql .= " UNION";
2456 $sql .= " SELECT DISTINCT ugu.fk_user";
2457 $sql .= " FROM ".MAIN_DB_PREFIX."usergroup_user as ugu, ".MAIN_DB_PREFIX."usergroup_rights as ur, ".MAIN_DB_PREFIX."rights_def as rd";
2458 $sql .= " WHERE ugu.fk_usergroup = ur.fk_usergroup AND ur.fk_id = rd.id and rd.module = 'expensereport' AND rd.perms = 'approve'"; // Permission 'Approve';
2459 //print $sql;
2460
2461 dol_syslog(get_class($this)."::fetch_users_approver_expensereport sql=".$sql);
2462 $result = $this->db->query($sql);
2463 if ($result) {
2464 $num_rows = $this->db->num_rows($result);
2465 $i = 0;
2466 while ($i < $num_rows) {
2467 $objp = $this->db->fetch_object($result);
2468 array_push($users_validator, $objp->fk_user);
2469 $i++;
2470 }
2471 return $users_validator;
2472 } else {
2473 $this->error = $this->db->lasterror();
2474 dol_syslog(get_class($this)."::fetch_users_approver_expensereport Error ".$this->error, LOG_ERR);
2475 return -1;
2476 }
2477 }
2478
2490 public function generateDocument($modele, $outputlangs, $hidedetails = 0, $hidedesc = 0, $hideref = 0, $moreparams = null)
2491 {
2492 $outputlangs->load("trips");
2493
2494 if (!dol_strlen($modele)) {
2495 if (!empty($this->model_pdf)) {
2496 $modele = $this->model_pdf;
2497 } elseif (getDolGlobalString('EXPENSEREPORT_ADDON_PDF')) {
2498 $modele = getDolGlobalString('EXPENSEREPORT_ADDON_PDF');
2499 }
2500 }
2501
2502 if (!empty($modele)) {
2503 $modelpath = "core/modules/expensereport/doc/";
2504
2505 return $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams);
2506 } else {
2507 return 0;
2508 }
2509 }
2510
2517 public function listOfTypes($active = 1)
2518 {
2519 global $langs;
2520 $ret = array();
2521 $sql = "SELECT id, code, label";
2522 $sql .= " FROM ".MAIN_DB_PREFIX."c_type_fees";
2523 $sql .= " WHERE active = ".((int) $active);
2524 dol_syslog(get_class($this)."::listOfTypes", LOG_DEBUG);
2525 $result = $this->db->query($sql);
2526 if ($result) {
2527 $num = $this->db->num_rows($result);
2528 $i = 0;
2529 while ($i < $num) {
2530 $obj = $this->db->fetch_object($result);
2531 $ret[$obj->code] = (($langs->transnoentitiesnoconv($obj->code) != $obj->code) ? $langs->transnoentitiesnoconv($obj->code) : $obj->label);
2532 $i++;
2533 }
2534 } else {
2535 dol_print_error($this->db);
2536 }
2537 return $ret;
2538 }
2539
2545 public function loadStateBoard()
2546 {
2547 global $user;
2548
2549 $this->nb = array();
2550
2551 $sql = "SELECT count(ex.rowid) as nb";
2552 $sql .= " FROM ".MAIN_DB_PREFIX."expensereport as ex";
2553 $sql .= " WHERE ex.fk_statut > 0";
2554 $sql .= " AND ex.entity IN (".getEntity('expensereport').")";
2555 if (!$user->hasRight('expensereport', 'readall')) {
2556 $userchildids = $user->getAllChildIds(1);
2557 $sql .= " AND (ex.fk_user_author IN (".$this->db->sanitize(implode(',', $userchildids)).")";
2558 $sql .= " OR ex.fk_user_validator IN (".$this->db->sanitize(implode(',', $userchildids))."))";
2559 }
2560
2561 $resql = $this->db->query($sql);
2562 if ($resql) {
2563 while ($obj = $this->db->fetch_object($resql)) {
2564 $this->nb["expensereports"] = $obj->nb;
2565 }
2566 $this->db->free($resql);
2567 return 1;
2568 } else {
2569 dol_print_error($this->db);
2570 $this->error = $this->db->error();
2571 return -1;
2572 }
2573 }
2574
2575 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2583 public function load_board($user, $option = 'topay')
2584 {
2585 // phpcs:enable
2586 global $conf, $langs;
2587
2588 if ($user->socid) {
2589 return -1; // protection pour eviter appel par utilisateur externe
2590 }
2591
2592 $now = dol_now();
2593
2594 $sql = "SELECT ex.rowid, ex.date_valid";
2595 $sql .= " FROM ".MAIN_DB_PREFIX."expensereport as ex";
2596 if ($option == 'toapprove') {
2597 $sql .= " WHERE ex.fk_statut = ".self::STATUS_VALIDATED;
2598 } else {
2599 $sql .= " WHERE ex.fk_statut = ".self::STATUS_APPROVED;
2600 }
2601 $sql .= " AND ex.entity IN (".getEntity('expensereport').")";
2602 if (!$user->hasRight('expensereport', 'readall')) {
2603 $userchildids = $user->getAllChildIds(1);
2604 $sql .= " AND (ex.fk_user_author IN (".$this->db->sanitize(implode(',', $userchildids)).")";
2605 $sql .= " OR ex.fk_user_validator IN (".$this->db->sanitize(implode(',', $userchildids))."))";
2606 }
2607
2608 $resql = $this->db->query($sql);
2609 if ($resql) {
2610 $langs->load("trips");
2611
2612 $response = new WorkboardResponse();
2613 if ($option == 'toapprove') {
2614 $response->warning_delay = $conf->expensereport->approve->warning_delay / 60 / 60 / 24;
2615 $response->label = $langs->trans("ExpenseReportsToApprove");
2616 $response->labelShort = $langs->trans("ToApprove");
2617 $response->url = DOL_URL_ROOT.'/expensereport/list.php?mainmenu=hrm&amp;statut='.self::STATUS_VALIDATED;
2618 } else {
2619 $response->warning_delay = $conf->expensereport->payment->warning_delay / 60 / 60 / 24;
2620 $response->label = $langs->trans("ExpenseReportsToPay");
2621 $response->labelShort = $langs->trans("StatusToPay");
2622 $response->url = DOL_URL_ROOT.'/expensereport/list.php?mainmenu=hrm&amp;statut='.self::STATUS_APPROVED;
2623 }
2624 $response->img = img_object('', "trip");
2625
2626 while ($obj = $this->db->fetch_object($resql)) {
2627 $response->nbtodo++;
2628
2629 if ($option == 'toapprove') {
2630 if ($this->db->jdate($obj->date_valid) < ($now - $conf->expensereport->approve->warning_delay)) {
2631 $response->nbtodolate++;
2632 }
2633 } else {
2634 if ($this->db->jdate($obj->date_valid) < ($now - $conf->expensereport->payment->warning_delay)) {
2635 $response->nbtodolate++;
2636 }
2637 }
2638 }
2639
2640 return $response;
2641 } else {
2642 dol_print_error($this->db);
2643 $this->error = $this->db->error();
2644 return -1;
2645 }
2646 }
2647
2654 public function hasDelay($option)
2655 {
2656 global $conf;
2657
2658 // Only valid expenses reports
2659 if ($option == 'toapprove' && $this->status != 2) {
2660 return false;
2661 }
2662 if ($option == 'topay' && $this->status != 5) {
2663 return false;
2664 }
2665
2666 $now = dol_now();
2667 if ($option == 'toapprove') {
2668 return (!empty($this->datevalid) ? $this->datevalid : $this->date_valid) < ($now - $conf->expensereport->approve->warning_delay);
2669 } else {
2670 return (!empty($this->datevalid) ? $this->datevalid : $this->date_valid) < ($now - $conf->expensereport->payment->warning_delay);
2671 }
2672 }
2673
2680 public function getVentilExportCompta($mode = 0)
2681 {
2682 $alreadydispatched = 0;
2683
2684 $type = 'expense_report';
2685
2686 $sql = " SELECT ".($mode ? 'DISTINCT piece_num' : 'COUNT(ab.rowid)')." as nb";
2687 $sql .= " FROM ".MAIN_DB_PREFIX."accounting_bookkeeping as ab WHERE ab.doc_type = '".$this->db->escape($type)."' AND ab.fk_doc = ".((int) $this->id);
2688 $resql = $this->db->query($sql);
2689 if ($resql) {
2690 $obj = $this->db->fetch_object($resql);
2691 if ($obj) {
2692 $alreadydispatched = $obj->nb;
2693 }
2694 } else {
2695 $this->error = $this->db->lasterror();
2696 return -1;
2697 }
2698
2699 if ($alreadydispatched) {
2700 return $alreadydispatched;
2701 }
2702 return 0;
2703 }
2704
2710 public function getSumPayments()
2711 {
2712 $table = 'payment_expensereport';
2713 $field = 'fk_expensereport';
2714
2715 $sql = 'SELECT sum(amount) as amount';
2716 $sql .= ' FROM '.MAIN_DB_PREFIX.$table;
2717 $sql .= " WHERE ".$field." = ".((int) $this->id);
2718
2719 dol_syslog(get_class($this)."::getSumPayments", LOG_DEBUG);
2720 $resql = $this->db->query($sql);
2721 if ($resql) {
2722 $obj = $this->db->fetch_object($resql);
2723 $this->db->free($resql);
2724 return (empty($obj->amount) ? 0 : $obj->amount);
2725 } else {
2726 $this->error = $this->db->lasterror();
2727 return -1;
2728 }
2729 }
2730
2739 public function computeTotalKm($fk_cat, $qty, $tva)
2740 {
2741 global $langs, $db, $conf;
2742
2743 $cumulYearQty = 0;
2744 $ranges = array();
2745 $coef = 0;
2746
2747
2748 if ($fk_cat < 0) {
2749 $this->error = $langs->trans('ErrorBadParameterCat');
2750 return -1;
2751 }
2752
2753 if ($qty <= 0) {
2754 $this->error = $langs->trans('ErrorBadParameterQty');
2755 return -1;
2756 }
2757
2758 $currentUser = new User($db);
2759 $currentUser->fetch($this->fk_user);
2760 $currentUser->loadRights('expensereport');
2761 //Clean
2762 $qty = (float) price2num($qty);
2763
2764 $sql = " SELECT r.range_ik, t.ikoffset, t.coef";
2765 $sql .= " FROM ".MAIN_DB_PREFIX."expensereport_ik t";
2766 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."c_exp_tax_range r ON r.rowid = t.fk_range";
2767 $sql .= " WHERE t.fk_c_exp_tax_cat = ".(int) $fk_cat;
2768 $sql .= " ORDER BY r.range_ik ASC";
2769
2770 dol_syslog("expenseReport::computeTotalkm sql=".$sql, LOG_DEBUG);
2771
2772 $result = $this->db->query($sql);
2773
2774 if ($result) {
2775 if ($conf->global->EXPENSEREPORT_CALCULATE_MILEAGE_EXPENSE_COEFFICIENT_ON_CURRENT_YEAR) {
2776 $arrayDate = dol_getdate(dol_now());
2777 $sql = " SELECT count(n.qty) as cumul FROM ".MAIN_DB_PREFIX."expensereport_det n";
2778 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."expensereport e ON e.rowid = n.fk_expensereport";
2779 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."c_type_fees tf ON tf.id = n.fk_c_type_fees";
2780 $sql .= " WHERE e.fk_user_author = ".(int) $this->fk_user_author;
2781 $sql .= " AND YEAR(n.date) = ".(int) $arrayDate['year'];
2782 $sql .= " AND tf.code = 'EX_KME' ";
2783 $sql .= " AND e.fk_statut = ".(int) ExpenseReport::STATUS_VALIDATED;
2784
2785 $resql = $this->db->query($sql);
2786
2787 if ($resql) {
2788 $obj = $this->db->fetch_object($resql);
2789 $cumulYearQty = $obj->cumul;
2790 }
2791
2792 $qty += (float) $cumulYearQty;
2793 }
2794
2795 $num = $this->db->num_rows($result);
2796
2797 if ($num) {
2798 for ($i = 0; $i < $num; $i++) {
2799 $obj = $this->db->fetch_object($result);
2800
2801 $ranges[$i] = $obj;
2802 }
2803 '@phan-var-force Object[] $ranges';
2804
2805 for ($i = 0; $i < $num; $i++) {
2806 if ($i < ($num - 1)) {
2807 // @phan-suppress-next-line PhanTypeInvalidDimOffset
2808 if ($qty > $ranges[$i]->range_ik && $qty < $ranges[$i + 1]->range_ik) {
2809 $coef = $ranges[$i]->coef;
2810 $offset = $ranges[$i]->ikoffset;
2811 }
2812 } else {
2813 if ($qty > $ranges[$i]->range_ik) {
2814 $coef = $ranges[$i]->coef;
2815 $offset = $ranges[$i]->ikoffset;
2816 }
2817 }
2818 }
2819 $total_ht = $coef;
2820 return $total_ht;
2821 } else {
2822 $this->error = $langs->trans('TaxUndefinedForThisCategory');
2823 return 0;
2824 }
2825 } else {
2826 $this->error = $this->db->error()." sql=".$sql;
2827
2828 return -1;
2829 }
2830 }
2831
2839 public function getKanbanView($option = '', $arraydata = null)
2840 {
2841 global $langs;
2842
2843 if ($arraydata === null) {
2844 $arraydata = array();
2845 }
2846
2847 $selected = (empty($arraydata['selected']) ? 0 : $arraydata['selected']);
2848
2849 $return = '<div class="box-flex-item box-flex-grow-zero">';
2850 $return .= '<div class="info-box info-box-sm">';
2851 $return .= '<span class="info-box-icon bg-infobox-action">';
2852 $return .= img_picto('', $this->picto);
2853 $return .= '</span>';
2854 $return .= '<div class="info-box-content">';
2855 $return .= '<span class="info-box-ref inline-block tdoverflowmax150 valignmiddle">'.(method_exists($this, 'getNomUrl') ? $this->getNomUrl(1) : $this->ref).'</span>';
2856 if ($selected >= 0) {
2857 $return .= '<input id="cb'.$this->id.'" class="flat checkforselect fright" type="checkbox" name="toselect[]" value="'.$this->id.'"'.($selected ? ' checked="checked"' : '').'>';
2858 }
2859 if (array_key_exists('userauthor', $arraydata) && $arraydata['userauthor'] instanceof User) {
2860 $return .= '<br><span class="info-box-label">'.$arraydata['userauthor']->getNomUrl(-1).'</span>';
2861 }
2862 if (property_exists($this, 'date_debut') && property_exists($this, 'date_fin')) {
2863 $return .= '<br><span class="info-box-label">'.dol_print_date($this->date_debut, 'day').'</span>';
2864 $return .= ' <span class="opacitymedium">'.$langs->trans("To").'</span> ';
2865 $return .= '<span class="info-box-label">'.dol_print_date($this->date_fin, 'day').'</span>';
2866 }
2867 if (method_exists($this, 'getLibStatut')) {
2868 $return .= '<br><div class="info-box-status">'.$this->getLibStatut(3).'</div>';
2869 }
2870 $return .= '</div>';
2871 $return .= '</div>';
2872 $return .= '</div>';
2873 return $return;
2874 }
2875}
$object ref
Definition info.php:90
Parent class of all other business classes (invoices, contracts, proposals, orders,...
fetch_optionals($rowid=null, $optionsArray=null)
Function to get extra fields of an object into $this->array_options This method is in most cases call...
deleteEcmFiles($mode=0)
Delete related files of object in database.
update_price($exclspec=0, $roundingadjust='auto', $nodatabaseupdate=0, $seller=null)
Update total_ht, total_ttc, total_vat, total_localtax1, total_localtax2 for an object (sum of lines).
commonGenerateDocument($modelspath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams=null)
Common function for all objects extending CommonObject for generating documents.
deleteObjectLinked($sourceid=null, $sourcetype='', $targetid=null, $targettype='', $rowid=0, $f_user=null, $notrigger=0)
Delete all links between an object $this.
setErrorsFromObject($object)
setErrorsFromObject
deleteExtraFields()
Delete all extra fields values for the current object.
insertExtraFields($trigger='', $userused=null)
Add/Update all extra fields values for the current object.
delete_linked_contact($source='', $code='')
Delete all links between an object $this and all its contacts in llx_element_contact.
call_trigger($triggerName, $user)
Call trigger based on this instance.
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...
periodExists($fuser, $date_debut, $date_fin)
periodExists
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.
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.
deleteLine($rowid, $fuser='', $notrigger=0)
deleteline
setDeny($fuser, $details, $notrigger=0)
setDeny
getKanbanView($option='', $arraydata=null)
Return clickable link of object (with optional picto)
update($user, $notrigger=0, $userofexpensereport=null)
update
const STATUS_VALIDATED
Validated (need to be paid)
create($user, $notrigger=0)
Create object in database.
load_board($user, $option='topay')
Load indicators for dashboard (this->nbtodo and this->nbtodolate)
set_paid($id, $fuser, $notrigger=0)
Classify the expense report as paid.
generateDocument($modele, $outputlangs, $hidedetails=0, $hidedesc=0, $hideref=0, $moreparams=null)
Create a document onto disk according to template module.
initAsSpecimen()
Initialise an instance with random values.
fetch_users_approver_expensereport()
Return list of people with permission to validate expense reports.
setApproved($fuser, $notrigger=0)
Set status to approved.
LibStatut($status, $mode=0)
Returns the label of a status.
setUnpaid($fuser, $notrigger=0)
set_unpaid
fetch_line_by_project($projectid, $user)
fetch_line_by_project
update_totaux_add($ligne_total_ht, $ligne_total_tva)
Update total of an expense report when you add a line.
Class to manage inventories.
Class of expense report details lines.
Class to manage inventories.
Class to manage third parties objects (customers, suppliers, prospects...)
Class to manage Dolibarr users.
print $langs trans("Ref").' m titre as m m statut as status
Or an array listing all the potential status of the object: array: int of the status => translated la...
Definition index.php:171
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:63
dol_delete_preview($object)
Delete all preview files linked to object instance.
img_picto($titlealt, $picto, $moreatt='', $pictoisfullpath=0, $srconly=0, $notitle=0, $alt='', $morecss='', $marginleftonlyshort=2, $allowothertags=array())
Show picto whatever it's its name (generic function)
GETPOSTINT($paramname, $method=0)
Return the value of a $_GET or $_POST supervariable, converted into integer.
price2num($amount, $rounding='', $option=0)
Function that return a number with universal decimal format (decimal separator is '.
img_object($titlealt, $picto, $moreatt='', $pictoisfullpath=0, $srconly=0, $notitle=0, $allowothertags=array())
Show a picto called object_picto (generic function)
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.
dol_now($mode='auto')
Return date for now.
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.
dol_print_date($time, $format='', $tzoutput='auto', $outputlangs=null, $encodetooutput=false)
Output date in a string format according to outputlangs (or langs if not defined).
dolGetFirstLastname($firstname, $lastname, $nameorder=-1)
Return firstname and lastname in correct order.
dolGetStatus($statusLabel='', $statusLabelShort='', $html='', $statusType='status0', $displayMode=0, $url='', $params=array())
Output the badge of a status.
dol_buildpath($path, $type=0, $returnemptyifnotfound=0)
Return path of url or filesystem.
dol_print_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.
dol_sanitizeFileName($str, $newstr='_', $unaccent=1, $includequotes=0)
Clean a string to use it as a file name.
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.
global $conf
The following vars must be defined: $type2label $form $conf, $lang, The following vars may also be de...
Definition member.php:79
calcul_price_total($qty, $pu, $remise_percent_ligne, $txtva, $uselocaltax1_rate, $uselocaltax2_rate, $remise_percent_global, $price_base_type, $info_bits, $type, $seller=null, $localtaxes_array=[], $progress=100, $multicurrency_tx=1, $pu_devise=0, $multicurrency_code='')
Calculate totals (net, vat, ...) of a line.
Definition price.lib.php:90