dolibarr 19.0.4
facture.class.php
Go to the documentation of this file.
1<?php
2/* Copyright (C) 2002-2007 Rodolphe Quiedeville <rodolphe@quiedeville.org>
3 * Copyright (C) 2004-2013 Laurent Destailleur <eldy@users.sourceforge.net>
4 * Copyright (C) 2004 Sebastien Di Cintio <sdicintio@ressource-toi.org>
5 * Copyright (C) 2004 Benoit Mortier <benoit.mortier@opensides.be>
6 * Copyright (C) 2005 Marc Barilley / Ocebo <marc@ocebo.com>
7 * Copyright (C) 2005-2014 Regis Houssin <regis.houssin@inodbox.com>
8 * Copyright (C) 2006 Andre Cianfarani <acianfa@free.fr>
9 * Copyright (C) 2007 Franky Van Liedekerke <franky.van.liedekerke@telenet.be>
10 * Copyright (C) 2010-2020 Juanjo Menent <jmenent@2byte.es>
11 * Copyright (C) 2012-2014 Christophe Battarel <christophe.battarel@altairis.fr>
12 * Copyright (C) 2012-2015 Marcos García <marcosgdf@gmail.com>
13 * Copyright (C) 2012 Cédric Salvador <csalvador@gpcsolutions.fr>
14 * Copyright (C) 2012-2014 Raphaël Doursenaud <rdoursenaud@gpcsolutions.fr>
15 * Copyright (C) 2013 Cedric Gross <c.gross@kreiz-it.fr>
16 * Copyright (C) 2013 Florian Henry <florian.henry@open-concept.pro>
17 * Copyright (C) 2016-2022 Ferran Marcet <fmarcet@2byte.es>
18 * Copyright (C) 2018-2022 Alexandre Spangaro <aspangaro@open-dsi.fr>
19 * Copyright (C) 2018 Nicolas ZABOURI <info@inovea-conseil.com>
20 * Copyright (C) 2022 Sylvain Legrand <contact@infras.fr>
21 * Copyright (C) 2023 Gauthier VERDOL <gauthier.verdol@atm-consulting.fr>
22 * Copyright (C) 2023 Nick Fragoulis
23 *
24 * This program is free software; you can redistribute it and/or modify
25 * it under the terms of the GNU General Public License as published by
26 * the Free Software Foundation; either version 3 of the License, or
27 * (at your option) any later version.
28 *
29 * This program is distributed in the hope that it will be useful,
30 * but WITHOUT ANY WARRANTY; without even the implied warranty of
31 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
32 * GNU General Public License for more details.
33 *
34 * You should have received a copy of the GNU General Public License
35 * along with this program. If not, see <https://www.gnu.org/licenses/>.
36 */
37
44require_once DOL_DOCUMENT_ROOT.'/core/class/commoninvoice.class.php';
45require_once DOL_DOCUMENT_ROOT.'/core/class/commonobjectline.class.php';
46require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
47require_once DOL_DOCUMENT_ROOT.'/societe/class/client.class.php';
48require_once DOL_DOCUMENT_ROOT.'/margin/lib/margins.lib.php';
49require_once DOL_DOCUMENT_ROOT.'/multicurrency/class/multicurrency.class.php';
50
51if (isModEnabled('accounting')) {
52 require_once DOL_DOCUMENT_ROOT.'/core/class/html.formaccounting.class.php';
53}
54if (isModEnabled('accounting')) {
55 require_once DOL_DOCUMENT_ROOT.'/accountancy/class/accountingaccount.class.php';
56}
57
62{
66 public $element = 'facture';
67
71 public $table_element = 'facture';
72
76 public $table_element_line = 'facturedet';
77
81 public $class_element_line = 'FactureLigne';
82
86 public $fk_element = 'fk_facture';
87
91 public $picto = 'bill';
92
97 public $ismultientitymanaged = 1;
98
102 public $isextrafieldmanaged = 1;
103
108 public $restrictiononfksoc = 1;
109
113 protected $table_ref_field = 'ref';
114
115 public $author;
116
122 public $user_author;
123
127 public $fk_user_author;
128
134 public $user_valid;
135
139 public $fk_user_valid;
140
144 public $fk_user_modif;
145
146 public $datem;
147
151 public $delivery_date; // Date expected of shipment (date of start of shipment, not the reception that occurs some days after)
152
158 public $ref_client;
159
163 public $ref_customer;
164
165 public $total_ht;
166 public $total_tva;
167 public $total_localtax1;
168 public $total_localtax2;
169 public $total_ttc;
170 public $revenuestamp;
171
172 public $resteapayer;
173
177 public $paye;
178
187 public $linked_objects = array();
188
192 public $fk_bank;
193
197 public $lines = array();
198
202 public $line;
203 public $extraparams = array();
204
208 public $fac_rec;
209
210 public $date_pointoftax;
211
212
216 public $situation_cycle_ref;
217
221 public $situation_counter;
222
226 public $situation_final;
227
231 public $tab_previous_situation_invoice = array();
232
236 public $tab_next_situation_invoice = array();
237
241 public $oldcopy;
242
246 public $retained_warranty;
247
251 public $retained_warranty_date_limit;
252
256 public $retained_warranty_fk_cond_reglement;
257
261 public $availability_id;
262
263 public $date_closing;
264
268 public $source;
269
273 public $remise_percent;
274
275
300 // BEGIN MODULEBUILDER PROPERTIES
304 public $fields = array(
305 'rowid' =>array('type'=>'integer', 'label'=>'TechnicalID', 'enabled'=>1, 'visible'=>-1, 'notnull'=>1, 'position'=>1),
306 'ref' =>array('type'=>'varchar(30)', 'label'=>'Ref', 'enabled'=>1, 'visible'=>1, 'notnull'=>1, 'showoncombobox'=>1, 'position'=>5),
307 'entity' =>array('type'=>'integer', 'label'=>'Entity', 'default'=>1, 'enabled'=>1, 'visible'=>-2, 'notnull'=>1, 'position'=>20, 'index'=>1),
308 'ref_client' =>array('type'=>'varchar(255)', 'label'=>'RefCustomer', 'enabled'=>1, 'visible'=>-1, 'position'=>10),
309 'ref_ext' =>array('type'=>'varchar(255)', 'label'=>'Ref ext', 'enabled'=>1, 'visible'=>0, 'position'=>12),
310 'type' =>array('type'=>'smallint(6)', 'label'=>'Type', 'enabled'=>1, 'visible'=>-1, 'notnull'=>1, 'position'=>15),
311 'subtype' =>array('type'=>'smallint(6)', 'label'=>'InvoiceSubtype', 'enabled'=>1, 'visible'=>-1, 'notnull'=>1, 'position'=>15),
312 //'increment' =>array('type'=>'varchar(10)', 'label'=>'Increment', 'enabled'=>1, 'visible'=>-1, 'position'=>45),
313 'fk_soc' =>array('type'=>'integer:Societe:societe/class/societe.class.php', 'label'=>'ThirdParty', 'enabled'=>1, 'visible'=>-1, 'notnull'=>1, 'position'=>50),
314 'datef' =>array('type'=>'date', 'label'=>'DateInvoice', 'enabled'=>1, 'visible'=>1, 'position'=>20),
315 'date_valid' =>array('type'=>'date', 'label'=>'DateValidation', 'enabled'=>1, 'visible'=>-1, 'position'=>22),
316 'date_lim_reglement' =>array('type'=>'date', 'label'=>'DateDue', 'enabled'=>1, 'visible'=>1, 'position'=>25),
317 'date_closing' =>array('type'=>'datetime', 'label'=>'Date closing', 'enabled'=>1, 'visible'=>-1, 'position'=>30),
318 'paye' =>array('type'=>'smallint(6)', 'label'=>'InvoicePaidCompletely', 'enabled'=>1, 'visible'=>-1, 'notnull'=>1, 'position'=>80),
319 //'amount' =>array('type'=>'double(24,8)', 'label'=>'Amount', 'enabled'=>1, 'visible'=>-1, 'notnull'=>1, 'position'=>85),
320 //'remise_percent' =>array('type'=>'double', 'label'=>'RelativeDiscount', 'enabled'=>1, 'visible'=>-1, 'position'=>90),
321 //'remise_absolue' =>array('type'=>'double', 'label'=>'CustomerRelativeDiscount', 'enabled'=>1, 'visible'=>-1, 'position'=>91),
322 //'remise' =>array('type'=>'double', 'label'=>'Remise', 'enabled'=>1, 'visible'=>-1, 'position'=>100),
323 'close_code' =>array('type'=>'varchar(16)', 'label'=>'EarlyClosingReason', 'enabled'=>1, 'visible'=>-1, 'position'=>92),
324 'close_note' =>array('type'=>'varchar(128)', 'label'=>'EarlyClosingComment', 'enabled'=>1, 'visible'=>-1, 'position'=>93),
325 'total_ht' =>array('type'=>'double(24,8)', 'label'=>'AmountHT', 'enabled'=>1, 'visible'=>1, 'position'=>95, 'isameasure'=>1),
326 'total_tva' =>array('type'=>'double(24,8)', 'label'=>'AmountVAT', 'enabled'=>1, 'visible'=>-1, 'position'=>100, 'isameasure'=>1),
327 'localtax1' =>array('type'=>'double(24,8)', 'label'=>'LT1', 'enabled'=>1, 'visible'=>-1, 'position'=>110, 'isameasure'=>1),
328 'localtax2' =>array('type'=>'double(24,8)', 'label'=>'LT2', 'enabled'=>1, 'visible'=>-1, 'position'=>120, 'isameasure'=>1),
329 'revenuestamp' =>array('type'=>'double(24,8)', 'label'=>'RevenueStamp', 'enabled'=>1, 'visible'=>-1, 'position'=>115, 'isameasure'=>1),
330 'total_ttc' =>array('type'=>'double(24,8)', 'label'=>'AmountTTC', 'enabled'=>1, 'visible'=>1, 'position'=>130, 'isameasure'=>1),
331 'fk_facture_source' =>array('type'=>'integer', 'label'=>'SourceInvoice', 'enabled'=>1, 'visible'=>-1, 'position'=>170),
332 'fk_projet' =>array('type'=>'integer:Project:projet/class/project.class.php:1:(fk_statut:=:1)', 'label'=>'Project', 'enabled'=>1, 'visible'=>-1, 'position'=>175),
333 'fk_account' =>array('type'=>'integer', 'label'=>'Fk account', 'enabled'=>1, 'visible'=>-1, 'position'=>180),
334 'fk_currency' =>array('type'=>'varchar(3)', 'label'=>'CurrencyCode', 'enabled'=>1, 'visible'=>-1, 'position'=>185),
335 'fk_cond_reglement' =>array('type'=>'integer', 'label'=>'PaymentTerm', 'enabled'=>1, 'visible'=>-1, 'notnull'=>1, 'position'=>190),
336 'fk_mode_reglement' =>array('type'=>'integer', 'label'=>'PaymentMode', 'enabled'=>1, 'visible'=>-1, 'position'=>195),
337 'note_private' =>array('type'=>'html', 'label'=>'NotePrivate', 'enabled'=>1, 'visible'=>0, 'position'=>205),
338 'note_public' =>array('type'=>'html', 'label'=>'NotePublic', 'enabled'=>1, 'visible'=>0, 'position'=>210),
339 'model_pdf' =>array('type'=>'varchar(255)', 'label'=>'Model pdf', 'enabled'=>1, 'visible'=>0, 'position'=>215),
340 'extraparams' =>array('type'=>'varchar(255)', 'label'=>'Extraparams', 'enabled'=>1, 'visible'=>-1, 'position'=>225),
341 'situation_cycle_ref' =>array('type'=>'smallint(6)', 'label'=>'Situation cycle ref', 'enabled'=>'$conf->global->INVOICE_USE_SITUATION', 'visible'=>-1, 'position'=>230),
342 'situation_counter' =>array('type'=>'smallint(6)', 'label'=>'Situation counter', 'enabled'=>'$conf->global->INVOICE_USE_SITUATION', 'visible'=>-1, 'position'=>235),
343 'situation_final' =>array('type'=>'smallint(6)', 'label'=>'Situation final', 'enabled'=>'empty($conf->global->INVOICE_USE_SITUATION) ? 0 : 1', 'visible'=>-1, 'position'=>240),
344 'retained_warranty' =>array('type'=>'double', 'label'=>'Retained warranty', 'enabled'=>'$conf->global->INVOICE_USE_RETAINED_WARRANTY', 'visible'=>-1, 'position'=>245),
345 'retained_warranty_date_limit' =>array('type'=>'date', 'label'=>'Retained warranty date limit', 'enabled'=>'$conf->global->INVOICE_USE_RETAINED_WARRANTY', 'visible'=>-1, 'position'=>250),
346 'retained_warranty_fk_cond_reglement' =>array('type'=>'integer', 'label'=>'Retained warranty fk cond reglement', 'enabled'=>'$conf->global->INVOICE_USE_RETAINED_WARRANTY', 'visible'=>-1, 'position'=>255),
347 'fk_incoterms' =>array('type'=>'integer', 'label'=>'IncotermCode', 'enabled'=>'$conf->incoterm->enabled', 'visible'=>-1, 'position'=>260),
348 'location_incoterms' =>array('type'=>'varchar(255)', 'label'=>'IncotermLabel', 'enabled'=>'$conf->incoterm->enabled', 'visible'=>-1, 'position'=>265),
349 'date_pointoftax' =>array('type'=>'date', 'label'=>'DatePointOfTax', 'enabled'=>'$conf->global->INVOICE_POINTOFTAX_DATE', 'visible'=>-1, 'position'=>270),
350 'fk_multicurrency' =>array('type'=>'integer', 'label'=>'MulticurrencyID', 'enabled'=>'isModEnabled("multicurrency")', 'visible'=>-1, 'position'=>275),
351 'multicurrency_code' =>array('type'=>'varchar(255)', 'label'=>'Currency', 'enabled'=>'isModEnabled("multicurrency")', 'visible'=>-1, 'position'=>280),
352 'multicurrency_tx' =>array('type'=>'double(24,8)', 'label'=>'CurrencyRate', 'enabled'=>'isModEnabled("multicurrency")', 'visible'=>-1, 'position'=>285, 'isameasure'=>1),
353 'multicurrency_total_ht' =>array('type'=>'double(24,8)', 'label'=>'MulticurrencyAmountHT', 'enabled'=>'isModEnabled("multicurrency")', 'visible'=>-1, 'position'=>290, 'isameasure'=>1),
354 'multicurrency_total_tva' =>array('type'=>'double(24,8)', 'label'=>'MulticurrencyAmountVAT', 'enabled'=>'isModEnabled("multicurrency")', 'visible'=>-1, 'position'=>291, 'isameasure'=>1),
355 'multicurrency_total_ttc' =>array('type'=>'double(24,8)', 'label'=>'MulticurrencyAmountTTC', 'enabled'=>'isModEnabled("multicurrency")', 'visible'=>-1, 'position'=>292, 'isameasure'=>1),
356 'fk_fac_rec_source' =>array('type'=>'integer', 'label'=>'RecurringInvoiceSource', 'enabled'=>1, 'visible'=>-1, 'position'=>305),
357 'last_main_doc' =>array('type'=>'varchar(255)', 'label'=>'LastMainDoc', 'enabled'=>1, 'visible'=>-1, 'position'=>310),
358 'module_source' =>array('type'=>'varchar(32)', 'label'=>'POSModule', 'enabled'=>"(isModEnabled('cashdesk') || isModEnabled('takepos') || getDolGlobalInt('INVOICE_SHOW_POS'))", 'visible'=>-1, 'position'=>315),
359 'pos_source' =>array('type'=>'varchar(32)', 'label'=>'POSTerminal', 'enabled'=>"(isModEnabled('cashdesk') || isModEnabled('takepos') || getDolGlobalInt('INVOICE_SHOW_POS'))", 'visible'=>-1, 'position'=>320),
360 'datec' =>array('type'=>'datetime', 'label'=>'DateCreation', 'enabled'=>1, 'visible'=>-1, 'position'=>500),
361 'tms' =>array('type'=>'timestamp', 'label'=>'DateModificationShort', 'enabled'=>1, 'visible'=>-1, 'notnull'=>1, 'position'=>502),
362 'fk_user_author' =>array('type'=>'integer:User:user/class/user.class.php', 'label'=>'UserAuthor', 'enabled'=>1, 'visible'=>-1, 'position'=>506),
363 'fk_user_modif' =>array('type'=>'integer:User:user/class/user.class.php', 'label'=>'UserModif', 'enabled'=>1, 'visible'=>-1, 'notnull'=>-1, 'position'=>508),
364 'fk_user_valid' =>array('type'=>'integer:User:user/class/user.class.php', 'label'=>'UserValidation', 'enabled'=>1, 'visible'=>-1, 'position'=>510),
365 'fk_user_closing' =>array('type'=>'integer:User:user/class/user.class.php', 'label'=>'UserClosing', 'enabled'=>1, 'visible'=>-1, 'position'=>512),
366 'import_key' =>array('type'=>'varchar(14)', 'label'=>'ImportId', 'enabled'=>1, 'visible'=>-2, 'position'=>900),
367 'fk_statut' =>array('type'=>'smallint(6)', 'label'=>'Status', 'enabled'=>1, 'visible'=>1, 'notnull'=>1, 'position'=>1000, 'arrayofkeyval'=>array(0=>'Draft', 1=>'Validated', 2=>'Paid', 3=>'Abandonned')),
368 );
369 // END MODULEBUILDER PROPERTIES
370
374 const TYPE_STANDARD = 0;
375
380
385
389 const TYPE_DEPOSIT = 3;
390
394 const TYPE_PROFORMA = 4;
395
399 const TYPE_SITUATION = 5;
400
404 const STATUS_DRAFT = 0;
405
410
418 const STATUS_CLOSED = 2;
419
428
429 const CLOSECODE_DISCOUNTVAT = 'discount_vat'; // Abandonned remain - escompte
430 const CLOSECODE_BADDEBT = 'badcustomer'; // Abandonned remain - bad customer
431 const CLOSECODE_BANKCHARGE = 'bankcharge'; // Abandonned remain - bank charge
432 const CLOSECODE_OTHER = 'other'; // Abandonned remain - other
433
434 const CLOSECODE_ABANDONED = 'abandon'; // Abandonned - other
435 const CLOSECODE_REPLACED = 'replaced'; // Closed after doing a replacement invoice
436
437
443 public function __construct(DoliDB $db)
444 {
445 $this->db = $db;
446 }
447
458 public function create(User $user, $notrigger = 0, $forceduedate = 0)
459 {
460 global $langs, $conf, $mysoc, $hookmanager;
461 $error = 0;
462
463 // Clean parameters
464 if (empty($this->type)) {
465 $this->type = self::TYPE_STANDARD;
466 }
467
468 $this->ref_client = trim($this->ref_client);
469
470 $this->note = (isset($this->note) ? trim($this->note) : trim($this->note_private)); // deprecated
471 $this->note_private = (isset($this->note_private) ? trim($this->note_private) : '');
472 $this->note_public = (isset($this->note_public) ? trim($this->note_public) : '');
473 if (!$this->cond_reglement_id) {
474 $this->cond_reglement_id = 0;
475 }
476 if (!$this->mode_reglement_id) {
477 $this->mode_reglement_id = 0;
478 }
479 $this->status = self::STATUS_DRAFT;
480 $this->statut = self::STATUS_DRAFT; // deprecated
481
482 if (!empty($this->multicurrency_code)) {
483 // Multicurrency (test on $this->multicurrency_tx because we should take the default rate of multicurrency_code only if not using original rate)
484 if (empty($this->multicurrency_tx)) {
485 // If original rate is not set, we take a default value from date
486 list($this->fk_multicurrency, $this->multicurrency_tx) = MultiCurrency::getIdAndTxFromCode($this->db, $this->multicurrency_code, $this->date);
487 } else {
488 // original rate multicurrency_tx and multicurrency_code are set, we use them
489 $this->fk_multicurrency = MultiCurrency::getIdFromCode($this->db, $this->multicurrency_code);
490 }
491 } else {
492 $this->fk_multicurrency = 0;
493 }
494 if (empty($this->fk_multicurrency)) {
495 $this->multicurrency_code = $conf->currency;
496 $this->fk_multicurrency = 0;
497 $this->multicurrency_tx = 1;
498 }
499
500 dol_syslog(get_class($this)."::create user=".$user->id." date=".$this->date);
501
502 // Check parameters
503 if (empty($this->date)) {
504 $this->error = "Try to create an invoice with an empty parameter (date)";
505 dol_syslog(get_class($this)."::create ".$this->error, LOG_ERR);
506 return -3;
507 }
508 $soc = new Societe($this->db);
509 $result = $soc->fetch($this->socid);
510 if ($result < 0) {
511 $this->error = "Failed to fetch company: ".$soc->error;
512 dol_syslog(get_class($this)."::create ".$this->error, LOG_ERR);
513 return -2;
514 }
515
516 $now = dol_now();
517 $this->date_creation = $now;
518
519 $this->db->begin();
520
521 $originaldatewhen = null;
522 $nextdatewhen = null;
523 $previousdaynextdatewhen = null;
524
525 // Erase some properties of the invoice to create with the one of the recurring invoice
526 if ($this->fac_rec > 0) {
527 $this->fk_fac_rec_source = $this->fac_rec;
528
529 require_once DOL_DOCUMENT_ROOT.'/compta/facture/class/facture-rec.class.php';
530 $_facrec = new FactureRec($this->db);
531 $result = $_facrec->fetch($this->fac_rec);
532 $result = $_facrec->fetchObjectLinked(null, '', null, '', 'OR', 1, 'sourcetype', 0); // This load $_facrec->linkedObjectsIds
533
534 // Define some dates
535 $originaldatewhen = $_facrec->date_when;
536 $nextdatewhen = null;
537 $previousdaynextdatewhen = null;
538 if ($originaldatewhen) {
539 $nextdatewhen = dol_time_plus_duree($originaldatewhen, $_facrec->frequency, $_facrec->unit_frequency);
540 $previousdaynextdatewhen = dol_time_plus_duree($nextdatewhen, -1, 'd');
541 }
542
543 if (!empty($_facrec->frequency)) { // Invoice are created on same thirdparty than template when there is a recurrence, but not necessarly when there is no recurrence.
544 $this->socid = $_facrec->socid;
545 }
546 $this->entity = $_facrec->entity; // Invoice created in same entity than template
547
548 // Fields coming from GUI (priority on template). TODO Value of template should be used as default value on GUI so we can use here always value from GUI
549 $this->fk_project = GETPOST('projectid', 'int') > 0 ? ((int) GETPOST('projectid', 'int')) : $_facrec->fk_project;
550 $this->note_public = GETPOSTISSET('note_public') ? GETPOST('note_public', 'restricthtml') : $_facrec->note_public;
551 $this->note_private = GETPOSTISSET('note_private') ? GETPOST('note_private', 'restricthtml') : $_facrec->note_private;
552 $this->model_pdf = GETPOSTISSET('model') ? GETPOST('model', 'alpha') : $_facrec->model_pdf;
553 $this->cond_reglement_id = GETPOST('cond_reglement_id', 'int') > 0 ? ((int) GETPOST('cond_reglement_id', 'int')) : $_facrec->cond_reglement_id;
554 $this->mode_reglement_id = GETPOST('mode_reglement_id', 'int') > 0 ? ((int) GETPOST('mode_reglement_id', 'int')) : $_facrec->mode_reglement_id;
555 $this->fk_account = GETPOST('fk_account') > 0 ? ((int) GETPOST('fk_account')) : $_facrec->fk_account;
556
557 // Set here to have this defined for substitution into notes, should be recalculated after adding lines to get same result
558 $this->total_ht = $_facrec->total_ht;
559 $this->total_ttc = $_facrec->total_ttc;
560
561 // Fields always coming from template
562 //$this->remise_absolue = $_facrec->remise_absolue;
563 //$this->remise_percent = $_facrec->remise_percent; // TODO deprecated
564 $this->fk_incoterms = $_facrec->fk_incoterms;
565 $this->location_incoterms = $_facrec->location_incoterms;
566
567 // Clean parameters
568 if (!$this->type) {
569 $this->type = self::TYPE_STANDARD;
570 }
571 $this->ref_client = trim($this->ref_client);
572 $this->ref_customer = trim($this->ref_customer);
573 $this->note_public = trim($this->note_public);
574 $this->note_private = trim($this->note_private);
575 $this->note_private = dol_concatdesc($this->note_private, $langs->trans("GeneratedFromRecurringInvoice", $_facrec->ref));
576
577 $this->array_options = $_facrec->array_options;
578
579 if (!$this->mode_reglement_id) {
580 $this->mode_reglement_id = 0;
581 }
582 $this->status = self::STATUS_DRAFT;
583 $this->statut = self::STATUS_DRAFT; // deprecated
584
585 $this->linked_objects = $_facrec->linkedObjectsIds;
586 // We do not add link to template invoice or next invoice will be linked to all generated invoices
587 //$this->linked_objects['facturerec'][0] = $this->fac_rec;
588
589 // For recurring invoices, update date and number of last generation of recurring template invoice, before inserting new invoice
590 if ($_facrec->frequency > 0) {
591 dol_syslog("This is a recurring invoice so we set date_last_gen and next date_when");
592 if (empty($_facrec->date_when)) {
593 $_facrec->date_when = $now;
594 }
595 $next_date = $_facrec->getNextDate(); // Calculate next date
596 $result = $_facrec->setValueFrom('date_last_gen', $now, '', null, 'date', '', $user, '');
597 //$_facrec->setValueFrom('nb_gen_done', $_facrec->nb_gen_done + 1); // Not required, +1 already included into setNextDate when second param is 1.
598 $result = $_facrec->setNextDate($next_date, 1);
599 }
600
601 // Define lang of customer
602 $outputlangs = $langs;
603 $newlang = '';
604
605 if (getDolGlobalInt('MAIN_MULTILANGS') && empty($newlang) && isset($this->thirdparty->default_lang)) {
606 $newlang = $this->thirdparty->default_lang; // for proposal, order, invoice, ...
607 }
608 if (getDolGlobalInt('MAIN_MULTILANGS') && empty($newlang) && isset($this->default_lang)) {
609 $newlang = $this->default_lang; // for thirdparty
610 }
611 if (!empty($newlang)) {
612 $outputlangs = new Translate("", $conf);
613 $outputlangs->setDefaultLang($newlang);
614 }
615
616 // Array of possible substitutions (See also file mailing-send.php that should manage same substitutions)
617 $substitutionarray = getCommonSubstitutionArray($outputlangs, 0, null, $this);
618 $substitutionarray['__INVOICE_PREVIOUS_MONTH__'] = dol_print_date(dol_time_plus_duree($this->date, -1, 'm'), '%m');
619 $substitutionarray['__INVOICE_MONTH__'] = dol_print_date($this->date, '%m');
620 $substitutionarray['__INVOICE_NEXT_MONTH__'] = dol_print_date(dol_time_plus_duree($this->date, 1, 'm'), '%m');
621 $substitutionarray['__INVOICE_PREVIOUS_MONTH_TEXT__'] = dol_print_date(dol_time_plus_duree($this->date, -1, 'm'), '%B');
622 $substitutionarray['__INVOICE_MONTH_TEXT__'] = dol_print_date($this->date, '%B');
623 $substitutionarray['__INVOICE_NEXT_MONTH_TEXT__'] = dol_print_date(dol_time_plus_duree($this->date, 1, 'm'), '%B');
624 $substitutionarray['__INVOICE_PREVIOUS_YEAR__'] = dol_print_date(dol_time_plus_duree($this->date, -1, 'y'), '%Y');
625 $substitutionarray['__INVOICE_YEAR__'] = dol_print_date($this->date, '%Y');
626 $substitutionarray['__INVOICE_NEXT_YEAR__'] = dol_print_date(dol_time_plus_duree($this->date, 1, 'y'), '%Y');
627 // Only for template invoice
628 $substitutionarray['__INVOICE_DATE_NEXT_INVOICE_BEFORE_GEN__'] = (isset($originaldatewhen) ? dol_print_date($originaldatewhen, 'dayhour') : '');
629 $substitutionarray['__INVOICE_DATE_NEXT_INVOICE_AFTER_GEN__'] = (isset($nextdatewhen) ? dol_print_date($nextdatewhen, 'dayhour') : '');
630 $substitutionarray['__INVOICE_PREVIOUS_DATE_NEXT_INVOICE_AFTER_GEN__'] = (isset($previousdaynextdatewhen) ? dol_print_date($previousdaynextdatewhen, 'dayhour') : '');
631 $substitutionarray['__INVOICE_COUNTER_CURRENT__'] = $_facrec->nb_gen_done;
632 $substitutionarray['__INVOICE_COUNTER_MAX__'] = $_facrec->nb_gen_max;
633
634 //var_dump($substitutionarray);exit;
635
636 complete_substitutions_array($substitutionarray, $outputlangs);
637
638 $this->note_public = make_substitutions($this->note_public, $substitutionarray);
639 $this->note_private = make_substitutions($this->note_private, $substitutionarray);
640 }
641
642 // Define due date if not already defined
643 if (empty($forceduedate)) {
644 $duedate = $this->calculate_date_lim_reglement();
645 /*if ($duedate < 0) { Regression, a date can be negative if before 1970.
646 dol_syslog(__METHOD__ . ' Error in calculate_date_lim_reglement. We got ' . $duedate, LOG_ERR);
647 return -1;
648 }*/
649 $this->date_lim_reglement = $duedate;
650 } else {
651 $this->date_lim_reglement = $forceduedate;
652 }
653
654 // Insert into database
655 $socid = $this->socid;
656
657 $sql = "INSERT INTO ".MAIN_DB_PREFIX."facture (";
658 $sql .= " ref";
659 $sql .= ", entity";
660 $sql .= ", ref_ext";
661 $sql .= ", type";
662 $sql .= ", subtype";
663 $sql .= ", fk_soc";
664 $sql .= ", datec";
665 $sql .= ", datef";
666 $sql .= ", date_pointoftax";
667 $sql .= ", note_private";
668 $sql .= ", note_public";
669 $sql .= ", ref_client";
670 $sql .= ", fk_account";
671 $sql .= ", module_source, pos_source, fk_fac_rec_source, fk_facture_source, fk_user_author, fk_projet";
672 $sql .= ", fk_cond_reglement, fk_mode_reglement, date_lim_reglement, model_pdf";
673 $sql .= ", situation_cycle_ref, situation_counter, situation_final";
674 $sql .= ", fk_incoterms, location_incoterms";
675 $sql .= ", fk_multicurrency";
676 $sql .= ", multicurrency_code";
677 $sql .= ", multicurrency_tx";
678 $sql .= ", retained_warranty";
679 $sql .= ", retained_warranty_date_limit";
680 $sql .= ", retained_warranty_fk_cond_reglement";
681 $sql .= ")";
682 $sql .= " VALUES (";
683 $sql .= "'(PROV)'";
684 $sql .= ", ".setEntity($this);
685 $sql .= ", ".($this->ref_ext ? "'".$this->db->escape($this->ref_ext)."'" : "null");
686 $sql .= ", '".$this->db->escape($this->type)."'";
687 $sql .= ", ".($this->subtype ? "'".$this->db->escape($this->subtype)."'" : "null");
688 $sql .= ", ".((int) $socid);
689 $sql .= ", '".$this->db->idate($this->date_creation)."'";
690 $sql .= ", '".$this->db->idate($this->date)."'";
691 $sql .= ", ".(empty($this->date_pointoftax) ? "null" : "'".$this->db->idate($this->date_pointoftax)."'");
692 $sql .= ", ".($this->note_private ? "'".$this->db->escape($this->note_private)."'" : "null");
693 $sql .= ", ".($this->note_public ? "'".$this->db->escape($this->note_public)."'" : "null");
694 $sql .= ", ".($this->ref_customer ? "'".$this->db->escape($this->ref_customer)."'" : ($this->ref_client ? "'".$this->db->escape($this->ref_client)."'" : "null"));
695 $sql .= ", ".($this->fk_account > 0 ? $this->fk_account : 'NULL');
696 $sql .= ", ".($this->module_source ? "'".$this->db->escape($this->module_source)."'" : "null");
697 $sql .= ", ".($this->pos_source != '' ? "'".$this->db->escape($this->pos_source)."'" : "null");
698 $sql .= ", ".($this->fk_fac_rec_source ? "'".$this->db->escape($this->fk_fac_rec_source)."'" : "null");
699 $sql .= ", ".($this->fk_facture_source ? "'".$this->db->escape($this->fk_facture_source)."'" : "null");
700 $sql .= ", ".($user->id > 0 ? (int) $user->id : "null");
701 $sql .= ", ".($this->fk_project ? $this->fk_project : "null");
702 $sql .= ", ".((int) $this->cond_reglement_id);
703 $sql .= ", ".((int) $this->mode_reglement_id);
704 $sql .= ", '".$this->db->idate($this->date_lim_reglement)."'";
705 $sql .= ", ".(isset($this->model_pdf) ? "'".$this->db->escape($this->model_pdf)."'" : "null");
706 $sql .= ", ".($this->situation_cycle_ref ? "'".$this->db->escape($this->situation_cycle_ref)."'" : "null");
707 $sql .= ", ".($this->situation_counter ? "'".$this->db->escape($this->situation_counter)."'" : "null");
708 $sql .= ", ".($this->situation_final ? $this->situation_final : 0);
709 $sql .= ", ".(int) $this->fk_incoterms;
710 $sql .= ", '".$this->db->escape($this->location_incoterms)."'";
711 $sql .= ", ".(int) $this->fk_multicurrency;
712 $sql .= ", '".$this->db->escape($this->multicurrency_code)."'";
713 $sql .= ", ".(float) $this->multicurrency_tx;
714 $sql .= ", ".(empty($this->retained_warranty) ? "0" : $this->db->escape($this->retained_warranty));
715 $sql .= ", ".(!empty($this->retained_warranty_date_limit) ? "'".$this->db->idate($this->retained_warranty_date_limit)."'" : 'NULL');
716 $sql .= ", ".(int) $this->retained_warranty_fk_cond_reglement;
717 $sql .= ")";
718
719 $resql = $this->db->query($sql);
720 if ($resql) {
721 $this->id = $this->db->last_insert_id(MAIN_DB_PREFIX.'facture');
722
723 // Update ref with new one
724 $this->ref = '(PROV'.$this->id.')';
725 $sql = 'UPDATE '.MAIN_DB_PREFIX."facture SET ref='".$this->db->escape($this->ref)."' WHERE rowid=".((int) $this->id);
726
727 $resql = $this->db->query($sql);
728 if (!$resql) {
729 $error++;
730 }
731
732 if (!empty($this->linkedObjectsIds) && empty($this->linked_objects)) { // To use new linkedObjectsIds instead of old linked_objects
733 $this->linked_objects = $this->linkedObjectsIds; // TODO Replace linked_objects with linkedObjectsIds
734 }
735
736 // Add object linked
737 if (!$error && $this->id && !empty($this->linked_objects) && is_array($this->linked_objects)) {
738 foreach ($this->linked_objects as $origin => $tmp_origin_id) {
739 if (is_array($tmp_origin_id)) { // New behaviour, if linked_object can have several links per type, so is something like array('contract'=>array(id1, id2, ...))
740 foreach ($tmp_origin_id as $origin_id) {
741 $ret = $this->add_object_linked($origin, $origin_id);
742 if (!$ret) {
743 $this->error = $this->db->lasterror();
744 $error++;
745 }
746 }
747 } else { // Old behaviour, if linked_object has only one link per type, so is something like array('contract'=>id1))
748 $origin_id = $tmp_origin_id;
749 $ret = $this->add_object_linked($origin, $origin_id);
750 if (!$ret) {
751 $this->error = $this->db->lasterror();
752 $error++;
753 }
754 }
755 }
756 }
757
758 // Propagate contacts
759 if (!$error && $this->id && getDolGlobalString('MAIN_PROPAGATE_CONTACTS_FROM_ORIGIN') && !empty($this->origin) && !empty($this->origin_id)) { // Get contact from origin object
760 $originforcontact = $this->origin;
761 $originidforcontact = $this->origin_id;
762 if ($originforcontact == 'shipping') { // shipment and order share the same contacts. If creating from shipment we take data of order
763 require_once DOL_DOCUMENT_ROOT.'/expedition/class/expedition.class.php';
764 $exp = new Expedition($this->db);
765 $exp->fetch($this->origin_id);
766 $exp->fetchObjectLinked(null, '', null, '', 'OR', 1, 'sourcetype', 0);
767 if (count($exp->linkedObjectsIds['commande']) > 0) {
768 foreach ($exp->linkedObjectsIds['commande'] as $key => $value) {
769 $originforcontact = 'commande';
770 if (is_object($value)) {
771 $originidforcontact = $value->id;
772 } else {
773 $originidforcontact = $value;
774 }
775 break; // We take first one
776 }
777 }
778 }
779
780 $sqlcontact = "SELECT ctc.code, ctc.source, ec.fk_socpeople FROM ".MAIN_DB_PREFIX."element_contact as ec, ".MAIN_DB_PREFIX."c_type_contact as ctc";
781 $sqlcontact .= " WHERE element_id = ".((int) $originidforcontact)." AND ec.fk_c_type_contact = ctc.rowid AND ctc.element = '".$this->db->escape($originforcontact)."'";
782
783 $resqlcontact = $this->db->query($sqlcontact);
784 if ($resqlcontact) {
785 while ($objcontact = $this->db->fetch_object($resqlcontact)) {
786 //print $objcontact->code.'-'.$objcontact->source.'-'.$objcontact->fk_socpeople."\n";
787 $this->add_contact($objcontact->fk_socpeople, $objcontact->code, $objcontact->source); // May failed because of duplicate key or because code of contact type does not exists for new object
788 }
789 } else {
790 dol_print_error($resqlcontact);
791 }
792 }
793
794 /*
795 * Insert lines of invoices, if not from template invoice, into database
796 */
797 if (!$error && empty($this->fac_rec) && count($this->lines) && is_object($this->lines[0])) { // If this->lines is array of InvoiceLines (preferred mode)
798 $fk_parent_line = 0;
799
800 dol_syslog("There is ".count($this->lines)." lines into ->lines that are InvoiceLines");
801 foreach ($this->lines as $i => $val) {
802 $newinvoiceline = $this->lines[$i];
803
804 $newinvoiceline->context = $this->context;
805
806 $newinvoiceline->fk_facture = $this->id;
807
808 $newinvoiceline->origin = $this->lines[$i]->element;
809 $newinvoiceline->origin_id = $this->lines[$i]->id;
810
811 // Auto set date of service ?
812 if ($this->lines[$i]->date_start_fill == 1 && $originaldatewhen) { // $originaldatewhen is defined when generating from recurring invoice only
813 $newinvoiceline->date_start = $originaldatewhen;
814 }
815 if ($this->lines[$i]->date_end_fill == 1 && $previousdaynextdatewhen) { // $previousdaynextdatewhen is defined when generating from recurring invoice only
816 $newinvoiceline->date_end = $previousdaynextdatewhen;
817 }
818
819 if ($result >= 0) {
820 // Reset fk_parent_line for no child products and special product
821 if (($newinvoiceline->product_type != 9 && empty($newinvoiceline->fk_parent_line)) || $newinvoiceline->product_type == 9) {
822 $fk_parent_line = 0;
823 }
824
825 // Complete vat rate with code
826 $vatrate = $newinvoiceline->tva_tx;
827 if ($newinvoiceline->vat_src_code && ! preg_match('/\‍(.*\‍)/', $vatrate)) {
828 $vatrate.=' ('.$newinvoiceline->vat_src_code.')';
829 }
830
831 $newinvoiceline->fk_parent_line = $fk_parent_line;
832
833 if ($this->type === Facture::TYPE_REPLACEMENT && $newinvoiceline->fk_remise_except) {
834 $discount = new DiscountAbsolute($this->db);
835 $discount->fetch($newinvoiceline->fk_remise_except);
836
837 $discountId = $soc->set_remise_except($discount->amount_ht, $user, $discount->description, $discount->tva_tx);
838 $newinvoiceline->fk_remise_except = $discountId;
839 }
840
841 $result = $this->addline(
842 $newinvoiceline->desc,
843 $newinvoiceline->subprice,
844 $newinvoiceline->qty,
845 $vatrate,
846 $newinvoiceline->localtax1_tx,
847 $newinvoiceline->localtax2_tx,
848 $newinvoiceline->fk_product,
849 $newinvoiceline->remise_percent,
850 $newinvoiceline->date_start,
851 $newinvoiceline->date_end,
852 $newinvoiceline->fk_code_ventilation,
853 $newinvoiceline->info_bits,
854 $newinvoiceline->fk_remise_except,
855 'HT',
856 0,
857 $newinvoiceline->product_type,
858 $newinvoiceline->rang,
859 $newinvoiceline->special_code,
860 $newinvoiceline->element,
861 $newinvoiceline->id,
862 $fk_parent_line,
863 $newinvoiceline->fk_fournprice,
864 $newinvoiceline->pa_ht,
865 $newinvoiceline->label,
866 $newinvoiceline->array_options,
867 $newinvoiceline->situation_percent,
868 $newinvoiceline->fk_prev_id,
869 $newinvoiceline->fk_unit,
870 $newinvoiceline->multicurrency_subprice,
871 $newinvoiceline->ref_ext,
872 1
873 );
874
875 // Defined the new fk_parent_line
876 if ($result > 0 && $newinvoiceline->product_type == 9) {
877 $fk_parent_line = $result;
878 }
879 }
880 if ($result < 0) {
881 $this->error = $newinvoiceline->error;
882 $this->errors = $newinvoiceline->errors;
883 $error++;
884 break;
885 }
886 }
887 } elseif (!$error && empty($this->fac_rec)) { // If this->lines is an array of invoice line arrays
888 $fk_parent_line = 0;
889
890 dol_syslog("There is ".count($this->lines)." lines into ->lines as a simple array");
891
892 foreach ($this->lines as $i => $val) {
893 $line = $this->lines[$i];
894
895 // Test and convert into object this->lines[$i]. When coming from REST API, we may still have an array
896 //if (! is_object($line)) $line=json_decode(json_encode($line), false); // convert recursively array into object.
897 if (!is_object($line)) {
898 $line = (object) $line;
899 }
900
901 if ($result >= 0) {
902 // Reset fk_parent_line for no child products and special product
903 if (($line->product_type != 9 && empty($line->fk_parent_line)) || $line->product_type == 9) {
904 $fk_parent_line = 0;
905 }
906
907 // Complete vat rate with code
908 $vatrate = $line->tva_tx;
909 if ($line->vat_src_code && !preg_match('/\‍(.*\‍)/', $vatrate)) {
910 $vatrate .= ' ('.$line->vat_src_code.')';
911 }
912
913 if (getDolGlobalString('MAIN_CREATEFROM_KEEP_LINE_ORIGIN_INFORMATION')) {
914 $originid = $line->origin_id;
915 $origintype = $line->origin;
916 } else {
917 $originid = $line->id;
918 $origintype = $this->element;
919 }
920
921 // init ref_ext
922 if (empty($line->ref_ext)) {
923 $line->ref_ext = '';
924 }
925
926 $result = $this->addline(
927 $line->desc,
928 $line->subprice,
929 $line->qty,
930 $vatrate,
931 $line->localtax1_tx,
932 $line->localtax2_tx,
933 $line->fk_product,
934 $line->remise_percent,
935 $line->date_start,
936 $line->date_end,
937 $line->fk_code_ventilation,
938 $line->info_bits,
939 $line->fk_remise_except,
940 'HT',
941 0,
942 $line->product_type,
943 $line->rang,
944 $line->special_code,
945 $origintype,
946 $originid,
947 $fk_parent_line,
948 $line->fk_fournprice,
949 $line->pa_ht,
950 $line->label,
951 $line->array_options,
952 $line->situation_percent,
953 $line->fk_prev_id,
954 $line->fk_unit,
955 $line->multicurrency_subprice,
956 $line->ref_ext,
957 1
958 );
959 if ($result < 0) {
960 $this->error = $this->db->lasterror();
961 dol_print_error($this->db);
962 $this->db->rollback();
963 return -1;
964 }
965
966 // Defined the new fk_parent_line
967 if ($result > 0 && $line->product_type == 9) {
968 $fk_parent_line = $result;
969 }
970 }
971 }
972 }
973
974 /*
975 * Insert lines coming from the template invoice
976 */
977 if (!$error && $this->fac_rec > 0) {
978 dol_syslog("There is ".count($_facrec->lines)." lines from recurring invoice");
979 $fk_parent_line = 0;
980
981 foreach ($_facrec->lines as $i => $val) {
982 // Reset fk_parent_line for no child products and special product
983 if (($_facrec->lines[$i]->product_type != 9 && empty($_facrec->lines[$i]->fk_parent_line)) || $_facrec->lines[$i]->product_type == 9) {
984 $fk_parent_line = 0;
985 }
986
987 // For line from template invoice, we use data from template invoice
988 /*
989 if ($_facrec->lines[$i]->fk_product) {
990 $prod = new Product($this->db);
991 $res = $prod->fetch($_facrec->lines[$i]->fk_product);
992 }
993
994 $tva_tx = get_default_tva($mysoc,$soc,$prod->id);
995 $tva_npr = get_default_npr($mysoc,$soc,$prod->id);
996 if (empty($tva_tx)) $tva_npr=0;
997 $localtax1_tx=get_localtax($tva_tx,1,$soc,$mysoc,$tva_npr);
998 $localtax2_tx=get_localtax($tva_tx,2,$soc,$mysoc,$tva_npr);
999 */
1000 $tva_tx = $_facrec->lines[$i]->tva_tx.($_facrec->lines[$i]->vat_src_code ? '('.$_facrec->lines[$i]->vat_src_code.')' : '');
1001 $tva_npr = $_facrec->lines[$i]->info_bits;
1002 if (empty($tva_tx)) {
1003 $tva_npr = 0;
1004 }
1005 $localtax1_tx = $_facrec->lines[$i]->localtax1_tx;
1006 $localtax2_tx = $_facrec->lines[$i]->localtax2_tx;
1007
1008 $fk_product_fournisseur_price = empty($_facrec->lines[$i]->fk_product_fournisseur_price) ? null : $_facrec->lines[$i]->fk_product_fournisseur_price;
1009 $buyprice = empty($_facrec->lines[$i]->buyprice) ? 0 : $_facrec->lines[$i]->buyprice;
1010
1011 // If buyprice not defined from template invoice, we try to guess the best value
1012 if (!$buyprice && $_facrec->lines[$i]->fk_product > 0) {
1013 require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.product.class.php';
1014 $producttmp = new ProductFournisseur($this->db);
1015 $producttmp->fetch($_facrec->lines[$i]->fk_product);
1016
1017 // If margin module defined on costprice, we try the costprice
1018 // If not defined or if module margin defined and pmp and stock module enabled, we try pmp price
1019 // else we get the best supplier price
1020 if (getDolGlobalString('MARGIN_TYPE') == 'costprice' && !empty($producttmp->cost_price)) {
1021 $buyprice = $producttmp->cost_price;
1022 } elseif (isModEnabled('stock') && (getDolGlobalString('MARGIN_TYPE') == 'costprice' || getDolGlobalString('MARGIN_TYPE') == 'pmp') && !empty($producttmp->pmp)) {
1023 $buyprice = $producttmp->pmp;
1024 } else {
1025 if ($producttmp->find_min_price_product_fournisseur($_facrec->lines[$i]->fk_product) > 0) {
1026 if ($producttmp->product_fourn_price_id > 0) {
1027 $buyprice = price2num($producttmp->fourn_unitprice * (1 - $producttmp->fourn_remise_percent / 100) + $producttmp->fourn_remise, 'MU');
1028 }
1029 }
1030 }
1031 }
1032
1033 $result_insert = $this->addline(
1034 $_facrec->lines[$i]->desc,
1035 $_facrec->lines[$i]->subprice,
1036 $_facrec->lines[$i]->qty,
1037 $tva_tx,
1038 $localtax1_tx,
1039 $localtax2_tx,
1040 $_facrec->lines[$i]->fk_product,
1041 $_facrec->lines[$i]->remise_percent,
1042 ($_facrec->lines[$i]->date_start_fill == 1 && $originaldatewhen) ? $originaldatewhen : '',
1043 ($_facrec->lines[$i]->date_end_fill == 1 && $previousdaynextdatewhen) ? $previousdaynextdatewhen : '',
1044 0,
1045 $tva_npr,
1046 '',
1047 'HT',
1048 0,
1049 $_facrec->lines[$i]->product_type,
1050 $_facrec->lines[$i]->rang,
1051 $_facrec->lines[$i]->special_code,
1052 '',
1053 0,
1054 $fk_parent_line,
1055 $fk_product_fournisseur_price,
1056 $buyprice,
1057 $_facrec->lines[$i]->label,
1058 empty($_facrec->lines[$i]->array_options) ? null : $_facrec->lines[$i]->array_options,
1059 100, // situation percent is undefined on recurring invoice lines
1060 '',
1061 $_facrec->lines[$i]->fk_unit,
1062 $_facrec->lines[$i]->multicurrency_subprice,
1063 $_facrec->lines[$i]->ref_ext,
1064 1
1065 );
1066
1067 // Defined the new fk_parent_line
1068 if ($result_insert > 0 && $_facrec->lines[$i]->product_type == 9) {
1069 $fk_parent_line = $result_insert;
1070 }
1071
1072 if ($result_insert < 0) {
1073 $error++;
1074 $this->error = $this->db->error();
1075 break;
1076 }
1077 }
1078 }
1079
1080 if (!$error) {
1081 $result = $this->update_price(1, 'auto', 0, $mysoc);
1082 if ($result > 0) {
1083 $action = 'create';
1084
1085 // Actions on extra fields
1086 if (!$error) {
1087 $result = $this->insertExtraFields();
1088 if ($result < 0) {
1089 $error++;
1090 }
1091 }
1092
1093 if (!$error && !$notrigger) {
1094 // Call trigger
1095 $result = $this->call_trigger('BILL_CREATE', $user);
1096 if ($result < 0) {
1097 $error++;
1098 }
1099 // End call triggers
1100 }
1101
1102 if (!$error) {
1103 $this->db->commit();
1104 return $this->id;
1105 } else {
1106 $this->db->rollback();
1107 return -4;
1108 }
1109 } else {
1110 $this->error = $langs->trans('FailedToUpdatePrice');
1111 $this->db->rollback();
1112 return -3;
1113 }
1114 } else {
1115 dol_syslog(get_class($this)."::create error ".$this->error, LOG_ERR);
1116 $this->db->rollback();
1117 return -2;
1118 }
1119 } else {
1120 $this->error = $this->db->error();
1121 $this->db->rollback();
1122 return -1;
1123 }
1124 }
1125
1126
1134 public function createFromCurrent(User $user, $invertdetail = 0)
1135 {
1136 global $conf;
1137
1138 // Source invoice load
1139 $facture = new Facture($this->db);
1140
1141 // Retrieve all extrafield
1142 // fetch optionals attributes and labels
1143 $this->fetch_optionals();
1144
1145 if (!empty($this->array_options)) {
1146 $facture->array_options = $this->array_options;
1147 }
1148
1149 foreach ($this->lines as &$line) {
1150 $line->fetch_optionals(); //fetch extrafields
1151 }
1152
1153 $facture->fk_facture_source = $this->fk_facture_source;
1154 $facture->type = $this->type;
1155 $facture->subtype = $this->subtype;
1156 $facture->socid = $this->socid;
1157 $facture->date = $this->date;
1158 $facture->date_pointoftax = $this->date_pointoftax;
1159 $facture->note_public = $this->note_public;
1160 $facture->note_private = $this->note_private;
1161 $facture->ref_client = $this->ref_client;
1162 $facture->model_pdf = $this->model_pdf;
1163 $facture->fk_project = $this->fk_project;
1164 $facture->cond_reglement_id = $this->cond_reglement_id;
1165 $facture->mode_reglement_id = $this->mode_reglement_id;
1166 //$facture->remise_absolue = $this->remise_absolue;
1167 //$facture->remise_percent = $this->remise_percent; // TODO deprecated
1168
1169 $facture->origin = $this->origin;
1170 $facture->origin_id = $this->origin_id;
1171 $facture->fk_account = $this->fk_account;
1172
1173 $facture->lines = $this->lines; // Array of lines of invoice
1174 $facture->situation_counter = $this->situation_counter;
1175 $facture->situation_cycle_ref = $this->situation_cycle_ref;
1176 $facture->situation_final = $this->situation_final;
1177
1178 $facture->retained_warranty = $this->retained_warranty;
1179 $facture->retained_warranty_fk_cond_reglement = $this->retained_warranty_fk_cond_reglement;
1180 $facture->retained_warranty_date_limit = $this->retained_warranty_date_limit;
1181
1182 $facture->fk_user_author = $user->id;
1183
1184
1185 // Loop on each line of new invoice
1186 foreach ($facture->lines as $i => $tmpline) {
1187 $facture->lines[$i]->fk_prev_id = $this->lines[$i]->rowid;
1188 if ($invertdetail) {
1189 $facture->lines[$i]->subprice = -$facture->lines[$i]->subprice;
1190 $facture->lines[$i]->total_ht = -$facture->lines[$i]->total_ht;
1191 $facture->lines[$i]->total_tva = -$facture->lines[$i]->total_tva;
1192 $facture->lines[$i]->total_localtax1 = -$facture->lines[$i]->total_localtax1;
1193 $facture->lines[$i]->total_localtax2 = -$facture->lines[$i]->total_localtax2;
1194 $facture->lines[$i]->total_ttc = -$facture->lines[$i]->total_ttc;
1195 $facture->lines[$i]->ref_ext = '';
1196 }
1197 }
1198
1199 dol_syslog(get_class($this)."::createFromCurrent invertdetail=".$invertdetail." socid=".$this->socid." nboflines=".count($facture->lines));
1200
1201 $facid = $facture->create($user);
1202 if ($facid <= 0) {
1203 $this->error = $facture->error;
1204 $this->errors = $facture->errors;
1205 } elseif ($this->type == self::TYPE_SITUATION && getDolGlobalString('INVOICE_USE_SITUATION')) {
1206 $this->fetchObjectLinked('', '', $this->id, 'facture');
1207
1208 foreach ($this->linkedObjectsIds as $typeObject => $Tfk_object) {
1209 foreach ($Tfk_object as $fk_object) {
1210 $facture->add_object_linked($typeObject, $fk_object);
1211 }
1212 }
1213
1214 $facture->add_object_linked('facture', $this->fk_facture_source);
1215 }
1216
1217 return $facid;
1218 }
1219
1220
1228 public function createFromClone(User $user, $fromid = 0)
1229 {
1230 global $conf, $hookmanager;
1231
1232 $error = 0;
1233
1234 $object = new Facture($this->db);
1235
1236 $this->db->begin();
1237
1238 $object->fetch($fromid);
1239
1240 // Load source object
1241 $objFrom = clone $object;
1242
1243 // Change socid if needed
1244 if (!empty($this->socid) && $this->socid != $object->socid) {
1245 $objsoc = new Societe($this->db);
1246
1247 if ($objsoc->fetch($this->socid) > 0) {
1248 $object->socid = $objsoc->id;
1249 $object->cond_reglement_id = (!empty($objsoc->cond_reglement_id) ? $objsoc->cond_reglement_id : 0);
1250 $object->mode_reglement_id = (!empty($objsoc->mode_reglement_id) ? $objsoc->mode_reglement_id : 0);
1251 $object->fk_project = null;
1252 $object->fk_delivery_address = '';
1253 }
1254
1255 // TODO Change product price if multi-prices
1256 }
1257
1258 $object->id = 0;
1259 $object->statut = self::STATUS_DRAFT;
1260 $object->status = self::STATUS_DRAFT;
1261
1262 // Clear fields
1263 $object->date = (empty($this->date) ? dol_now() : $this->date);
1264 $object->user_creation_id = $user->id;
1265 $object->user_validation_id = null;
1266 $object->fk_user_author = $user->id;
1267 $object->fk_user_valid = null;
1268 $object->fk_facture_source = 0;
1269 $object->fk_fac_rec_source = null;
1270 $object->date_creation = '';
1271 $object->date_modification = '';
1272 $object->date_validation = '';
1273 $object->ref_client = '';
1274 $object->ref_customer = '';
1275 $object->close_code = '';
1276 $object->close_note = '';
1277 if (getDolGlobalInt('MAIN_DONT_KEEP_NOTE_ON_CLONING') == 1) {
1278 $object->note_private = '';
1279 $object->note_public = '';
1280 }
1281
1282 // Loop on each line of new invoice
1283 foreach ($object->lines as $i => $line) {
1284 if (($object->lines[$i]->info_bits & 0x02) == 0x02) { // We do not clone line of discounts
1285 unset($object->lines[$i]);
1286 continue;
1287 }
1288
1289 // Bloc to update dates of service (month by month only if previously filled and similare to start and end of month)
1290 // If it's a service with start and end dates
1291 if (getDolGlobalString('INVOICE_AUTO_NEXT_MONTH_ON_LINES') && !empty($line->date_start) && !empty($line->date_end)) {
1292 // Get the dates
1293 $start = dol_getdate($line->date_start);
1294 $end = dol_getdate($line->date_end);
1295
1296 // Get the first and last day of the month
1297 $first = dol_get_first_day($start['year'], $start['mon']);
1298 $last = dol_get_last_day($end['year'], $end['mon']);
1299
1300 //print dol_print_date(dol_mktime(0, 0, 0, $start['mon'], $start['mday'], $start['year'], 'gmt'), 'dayhour').' '.dol_print_date($first, 'dayhour').'<br>';
1301 //print dol_mktime(23, 59, 59, $end['mon'], $end['mday'], $end['year'], 'gmt').' '.$last.'<br>';exit;
1302 // If start date is first date of month and end date is last date of month
1303 if (dol_mktime(0, 0, 0, $start['mon'], $start['mday'], $start['year'], 'gmt') == $first
1304 && dol_mktime(23, 59, 59, $end['mon'], $end['mday'], $end['year'], 'gmt') == $last) {
1305 $nextMonth = dol_get_next_month($end['mon'], $end['year']);
1306 $newFirst = dol_get_first_day($nextMonth['year'], $nextMonth['month']);
1307 $newLast = dol_get_last_day($nextMonth['year'], $nextMonth['month']);
1308 $object->lines[$i]->date_start = $newFirst;
1309 $object->lines[$i]->date_end = $newLast;
1310 }
1311 }
1312
1313 $object->lines[$i]->ref_ext = ''; // Do not clone ref_ext
1314 }
1315
1316 // Create clone
1317 $object->context['createfromclone'] = 'createfromclone';
1318 $result = $object->create($user);
1319 if ($result < 0) {
1320 $error++;
1321 $this->error = $object->error;
1322 $this->errors = $object->errors;
1323 } else {
1324 // copy internal contacts
1325 if ($object->copy_linked_contact($objFrom, 'internal') < 0) {
1326 $error++;
1327 $this->error = $object->error;
1328 $this->errors = $object->errors;
1329 } elseif ($object->socid == $objFrom->socid) {
1330 // copy external contacts if same company
1331 if ($object->copy_linked_contact($objFrom, 'external') < 0) {
1332 $error++;
1333 $this->error = $object->error;
1334 $this->errors = $object->errors;
1335 }
1336 }
1337 }
1338
1339 if (!$error) {
1340 // Hook of thirdparty module
1341 if (is_object($hookmanager)) {
1342 $parameters = array('objFrom'=>$objFrom);
1343 $action = '';
1344 $reshook = $hookmanager->executeHooks('createFrom', $parameters, $object, $action); // Note that $action and $object may have been modified by some hooks
1345 if ($reshook < 0) {
1346 $this->setErrorsFromObject($hookmanager);
1347 $error++;
1348 }
1349 }
1350 }
1351
1352 unset($object->context['createfromclone']);
1353
1354 // End
1355 if (!$error) {
1356 $this->db->commit();
1357 return $object->id;
1358 } else {
1359 $this->db->rollback();
1360 return -1;
1361 }
1362 }
1363
1371 public function createFromOrder($object, User $user)
1372 {
1373 global $conf, $hookmanager;
1374
1375 $error = 0;
1376
1377 // Closed order
1378 $this->date = dol_now();
1379 $this->source = 0;
1380
1381 $num = count($object->lines);
1382 for ($i = 0; $i < $num; $i++) {
1383 $line = new FactureLigne($this->db);
1384
1385 $line->libelle = $object->lines[$i]->libelle; // deprecated
1386 $line->label = $object->lines[$i]->label;
1387 $line->desc = $object->lines[$i]->desc;
1388 $line->subprice = $object->lines[$i]->subprice;
1389 $line->total_ht = $object->lines[$i]->total_ht;
1390 $line->total_tva = $object->lines[$i]->total_tva;
1391 $line->total_localtax1 = $object->lines[$i]->total_localtax1;
1392 $line->total_localtax2 = $object->lines[$i]->total_localtax2;
1393 $line->total_ttc = $object->lines[$i]->total_ttc;
1394 $line->vat_src_code = $object->lines[$i]->vat_src_code;
1395 $line->tva_tx = $object->lines[$i]->tva_tx;
1396 $line->localtax1_tx = $object->lines[$i]->localtax1_tx;
1397 $line->localtax2_tx = $object->lines[$i]->localtax2_tx;
1398 $line->qty = $object->lines[$i]->qty;
1399 $line->fk_remise_except = $object->lines[$i]->fk_remise_except;
1400 $line->remise_percent = $object->lines[$i]->remise_percent;
1401 $line->fk_product = $object->lines[$i]->fk_product;
1402 $line->info_bits = $object->lines[$i]->info_bits;
1403 $line->product_type = $object->lines[$i]->product_type;
1404 $line->rang = $object->lines[$i]->rang;
1405 $line->special_code = $object->lines[$i]->special_code;
1406 $line->fk_parent_line = $object->lines[$i]->fk_parent_line;
1407 $line->fk_unit = $object->lines[$i]->fk_unit;
1408 $line->date_start = $object->lines[$i]->date_start;
1409 $line->date_end = $object->lines[$i]->date_end;
1410
1411 // Multicurrency
1412 $line->fk_multicurrency = $object->lines[$i]->fk_multicurrency;
1413 $line->multicurrency_code = $object->lines[$i]->multicurrency_code;
1414 $line->multicurrency_subprice = $object->lines[$i]->multicurrency_subprice;
1415 $line->multicurrency_total_ht = $object->lines[$i]->multicurrency_total_ht;
1416 $line->multicurrency_total_tva = $object->lines[$i]->multicurrency_total_tva;
1417 $line->multicurrency_total_ttc = $object->lines[$i]->multicurrency_total_ttc;
1418
1419 $line->fk_fournprice = $object->lines[$i]->fk_fournprice;
1420 $marginInfos = getMarginInfos($object->lines[$i]->subprice, $object->lines[$i]->remise_percent, $object->lines[$i]->tva_tx, $object->lines[$i]->localtax1_tx, $object->lines[$i]->localtax2_tx, $object->lines[$i]->fk_fournprice, $object->lines[$i]->pa_ht);
1421 $line->pa_ht = $marginInfos[0];
1422
1423 // get extrafields from original line
1424 $object->lines[$i]->fetch_optionals();
1425 foreach ($object->lines[$i]->array_options as $options_key => $value) {
1426 $line->array_options[$options_key] = $value;
1427 }
1428
1429 $this->lines[$i] = $line;
1430 }
1431
1432 $this->socid = $object->socid;
1433 $this->fk_project = $object->fk_project;
1434 $this->fk_account = $object->fk_account;
1435 $this->cond_reglement_id = $object->cond_reglement_id;
1436 $this->mode_reglement_id = $object->mode_reglement_id;
1437 $this->availability_id = $object->availability_id;
1438 $this->demand_reason_id = $object->demand_reason_id;
1439 $this->delivery_date = $object->delivery_date;
1440 $this->fk_delivery_address = $object->fk_delivery_address; // deprecated
1441 $this->contact_id = $object->contact_id;
1442 $this->ref_client = $object->ref_client;
1443
1444 if (!getDolGlobalString('MAIN_DISABLE_PROPAGATE_NOTES_FROM_ORIGIN')) {
1445 $this->note_private = $object->note_private;
1446 $this->note_public = $object->note_public;
1447 }
1448
1449 $this->module_source = $object->module_source;
1450 $this->pos_source = $object->pos_source;
1451
1452 $this->origin = $object->element;
1453 $this->origin_id = $object->id;
1454
1455 $this->fk_user_author = $user->id;
1456
1457 // get extrafields from original line
1458 $object->fetch_optionals();
1459 foreach ($object->array_options as $options_key => $value) {
1460 $this->array_options[$options_key] = $value;
1461 }
1462
1463 // Possibility to add external linked objects with hooks
1464 $this->linked_objects[$this->origin] = $this->origin_id;
1465 if (!empty($object->other_linked_objects) && is_array($object->other_linked_objects)) {
1466 $this->linked_objects = array_merge($this->linked_objects, $object->other_linked_objects);
1467 }
1468
1469 $ret = $this->create($user);
1470
1471 if ($ret > 0) {
1472 // Actions hooked (by external module)
1473 $hookmanager->initHooks(array('invoicedao'));
1474
1475 $parameters = array('objFrom'=>$object);
1476 $action = '';
1477 $reshook = $hookmanager->executeHooks('createFrom', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
1478 if ($reshook < 0) {
1479 $this->setErrorsFromObject($hookmanager);
1480 $error++;
1481 }
1482
1483 if (!$error) {
1484 return 1;
1485 } else {
1486 return -1;
1487 }
1488 } else {
1489 return -1;
1490 }
1491 }
1492
1501 public function createFromContract($object, User $user, $lines = array())
1502 {
1503 global $conf, $hookmanager;
1504
1505 $error = 0;
1506
1507 // Closed order
1508 $this->date = dol_now();
1509 $this->source = 0;
1510
1511 $use_all_lines = empty($lines);
1512 $num = count($object->lines);
1513 for ($i = 0; $i < $num; $i++) {
1514 if (!$use_all_lines && !in_array($object->lines[$i]->id, $lines)) {
1515 continue;
1516 }
1517
1518 $line = new FactureLigne($this->db);
1519
1520 $line->libelle = $object->lines[$i]->libelle; // deprecated
1521 $line->label = $object->lines[$i]->label;
1522 $line->desc = $object->lines[$i]->desc;
1523 $line->subprice = $object->lines[$i]->subprice;
1524 $line->total_ht = $object->lines[$i]->total_ht;
1525 $line->total_tva = $object->lines[$i]->total_tva;
1526 $line->total_localtax1 = $object->lines[$i]->total_localtax1;
1527 $line->total_localtax2 = $object->lines[$i]->total_localtax2;
1528 $line->total_ttc = $object->lines[$i]->total_ttc;
1529 $line->vat_src_code = $object->lines[$i]->vat_src_code;
1530 $line->tva_tx = $object->lines[$i]->tva_tx;
1531 $line->localtax1_tx = $object->lines[$i]->localtax1_tx;
1532 $line->localtax2_tx = $object->lines[$i]->localtax2_tx;
1533 $line->qty = $object->lines[$i]->qty;
1534 $line->fk_remise_except = $object->lines[$i]->fk_remise_except;
1535 $line->remise_percent = $object->lines[$i]->remise_percent;
1536 $line->fk_product = $object->lines[$i]->fk_product;
1537 $line->info_bits = $object->lines[$i]->info_bits;
1538 $line->product_type = $object->lines[$i]->product_type;
1539 $line->rang = $object->lines[$i]->rang;
1540 $line->special_code = $object->lines[$i]->special_code;
1541 $line->fk_parent_line = $object->lines[$i]->fk_parent_line;
1542 $line->fk_unit = $object->lines[$i]->fk_unit;
1543 $line->date_start = $object->lines[$i]->date_start;
1544 $line->date_end = $object->lines[$i]->date_end;
1545
1546 // Multicurrency
1547 $line->fk_multicurrency = $object->lines[$i]->fk_multicurrency;
1548 $line->multicurrency_code = $object->lines[$i]->multicurrency_code;
1549 $line->multicurrency_subprice = $object->lines[$i]->multicurrency_subprice;
1550 $line->multicurrency_total_ht = $object->lines[$i]->multicurrency_total_ht;
1551 $line->multicurrency_total_tva = $object->lines[$i]->multicurrency_total_tva;
1552 $line->multicurrency_total_ttc = $object->lines[$i]->multicurrency_total_ttc;
1553
1554 $line->fk_fournprice = $object->lines[$i]->fk_fournprice;
1555 $marginInfos = getMarginInfos($object->lines[$i]->subprice, $object->lines[$i]->remise_percent, $object->lines[$i]->tva_tx, $object->lines[$i]->localtax1_tx, $object->lines[$i]->localtax2_tx, $object->lines[$i]->fk_fournprice, $object->lines[$i]->pa_ht);
1556 $line->pa_ht = $marginInfos[0];
1557
1558 // get extrafields from original line
1559 $object->lines[$i]->fetch_optionals();
1560 foreach ($object->lines[$i]->array_options as $options_key => $value) {
1561 $line->array_options[$options_key] = $value;
1562 }
1563
1564 $this->lines[$i] = $line;
1565 }
1566
1567 $this->socid = $object->socid;
1568 $this->fk_project = $object->fk_project;
1569 $this->fk_account = $object->fk_account;
1570 $this->cond_reglement_id = $object->cond_reglement_id;
1571 $this->mode_reglement_id = $object->mode_reglement_id;
1572 $this->availability_id = $object->availability_id;
1573 $this->demand_reason_id = $object->demand_reason_id;
1574 $this->delivery_date = $object->delivery_date;
1575 $this->fk_delivery_address = $object->fk_delivery_address; // deprecated
1576 $this->contact_id = $object->contact_id;
1577 $this->ref_client = $object->ref_client;
1578
1579 if (!getDolGlobalString('MAIN_DISABLE_PROPAGATE_NOTES_FROM_ORIGIN')) {
1580 $this->note_private = $object->note_private;
1581 $this->note_public = $object->note_public;
1582 }
1583
1584 $this->module_source = $object->module_source;
1585 $this->pos_source = $object->pos_source;
1586
1587 $this->origin = $object->element;
1588 $this->origin_id = $object->id;
1589
1590 $this->fk_user_author = $user->id;
1591
1592 // get extrafields from original line
1593 $object->fetch_optionals();
1594 foreach ($object->array_options as $options_key => $value) {
1595 $this->array_options[$options_key] = $value;
1596 }
1597
1598 // Possibility to add external linked objects with hooks
1599 $this->linked_objects[$this->origin] = $this->origin_id;
1600 if (!empty($object->other_linked_objects) && is_array($object->other_linked_objects)) {
1601 $this->linked_objects = array_merge($this->linked_objects, $object->other_linked_objects);
1602 }
1603
1604 $ret = $this->create($user);
1605
1606 if ($ret > 0) {
1607 // Actions hooked (by external module)
1608 $hookmanager->initHooks(array('invoicedao'));
1609
1610 $parameters = array('objFrom'=>$object);
1611 $action = '';
1612 $reshook = $hookmanager->executeHooks('createFrom', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
1613 if ($reshook < 0) {
1614 $this->setErrorsFromObject($hookmanager);
1615 $error++;
1616 }
1617
1618 if (!$error) {
1619 return 1;
1620 } else {
1621 return -1;
1622 }
1623 } else {
1624 return -1;
1625 }
1626 }
1627
1640 public static function createDepositFromOrigin(CommonObject $origin, $date, $payment_terms_id, User $user, $notrigger = 0, $autoValidateDeposit = false, $overrideFields = array())
1641 {
1642 global $conf, $langs, $hookmanager, $action;
1643
1644 if (! in_array($origin->element, array('propal', 'commande'))) {
1645 $origin->error = 'ErrorCanOnlyAutomaticallyGenerateADepositFromProposalOrOrder';
1646 return null;
1647 }
1648
1649 if (empty($date)) {
1650 $origin->error = $langs->trans('ErrorFieldRequired', $langs->transnoentities('DateInvoice'));
1651 return null;
1652 }
1653
1654 require_once DOL_DOCUMENT_ROOT . '/core/lib/date.lib.php';
1655
1656 if ($date > (dol_get_last_hour(dol_now('tzuserrel')) + (!getDolGlobalString('INVOICE_MAX_FUTURE_DELAY') ? 0 : $conf->global->INVOICE_MAX_FUTURE_DELAY))) {
1657 $origin->error = 'ErrorDateIsInFuture';
1658 return null;
1659 }
1660
1661 if ($payment_terms_id <= 0) {
1662 $origin->error = $langs->trans('ErrorFieldRequired', $langs->transnoentities('PaymentConditionsShort'));
1663 return null;
1664 }
1665
1666 $payment_conditions_deposit_percent = getDictionaryValue('c_payment_term', 'deposit_percent', $origin->cond_reglement_id);
1667
1668 if (empty($payment_conditions_deposit_percent)) {
1669 $origin->error = 'ErrorPaymentConditionsNotEligibleToDepositCreation';
1670 return null;
1671 }
1672
1673 if (empty($origin->deposit_percent)) {
1674 $origin->error = $langs->trans('ErrorFieldRequired', $langs->transnoentities('DepositPercent'));
1675 return null;
1676 }
1677
1678 $deposit = new self($origin->db);
1679 $deposit->socid = $origin->socid;
1680 $deposit->type = self::TYPE_DEPOSIT;
1681 $deposit->fk_project = $origin->fk_project;
1682 $deposit->ref_client = $origin->ref_client;
1683 $deposit->date = $date;
1684 $deposit->mode_reglement_id = $origin->mode_reglement_id;
1685 $deposit->cond_reglement_id = $payment_terms_id;
1686 $deposit->availability_id = $origin->availability_id;
1687 $deposit->demand_reason_id = $origin->demand_reason_id;
1688 $deposit->fk_account = $origin->fk_account;
1689 $deposit->fk_incoterms = $origin->fk_incoterms;
1690 $deposit->location_incoterms = $origin->location_incoterms;
1691 $deposit->fk_multicurrency = $origin->fk_multicurrency;
1692 $deposit->multicurrency_code = $origin->multicurrency_code;
1693 $deposit->multicurrency_tx = $origin->multicurrency_tx;
1694 $deposit->module_source = $origin->module_source;
1695 $deposit->pos_source = $origin->pos_source;
1696 $deposit->model_pdf = 'crabe';
1697
1698 $modelByTypeConfName = 'FACTURE_ADDON_PDF_' . $deposit->type;
1699
1700 if (getDolGlobalString($modelByTypeConfName)) {
1701 $deposit->model_pdf = getDolGlobalString($modelByTypeConfName);
1702 } elseif (getDolGlobalString('FACTURE_ADDON_PDF')) {
1703 $deposit->model_pdf = getDolGlobalString('FACTURE_ADDON_PDF');
1704 }
1705
1706 if (!getDolGlobalString('MAIN_DISABLE_PROPAGATE_NOTES_FROM_ORIGIN')) {
1707 $deposit->note_private = $origin->note_private;
1708 $deposit->note_public = $origin->note_public;
1709 }
1710
1711 $deposit->origin = $origin->element;
1712 $deposit->origin_id = $origin->id;
1713
1714 $origin->fetch_optionals();
1715
1716 foreach ($origin->array_options as $extrakey => $value) {
1717 $deposit->array_options[$extrakey] = $value;
1718 }
1719
1720 $deposit->linked_objects[$deposit->origin] = $deposit->origin_id;
1721
1722 foreach ($overrideFields as $key => $value) {
1723 $deposit->$key = $value;
1724 }
1725
1726 $deposit->context['createdepositfromorigin'] = 'createdepositfromorigin';
1727
1728 $origin->db->begin();
1729
1730 // Facture::create() also imports contact from origin
1731 $createReturn = $deposit->create($user, $notrigger);
1732
1733 if ($createReturn <= 0) {
1734 $origin->db->rollback();
1735 $origin->error = $deposit->error;
1736 $origin->errors = $deposit->errors;
1737 return null;
1738 }
1739
1740 $amount_ttc_diff = 0;
1741 $amountdeposit = array();
1742 $descriptions = array();
1743
1744 if (getDolGlobalString('MAIN_DEPOSIT_MULTI_TVA')) {
1745 $amount = $origin->total_ttc * ($origin->deposit_percent / 100);
1746
1747 $TTotalByTva = array();
1748 foreach ($origin->lines as &$line) {
1749 if (!empty($line->special_code)) {
1750 continue;
1751 }
1752 $TTotalByTva[$line->tva_tx] += $line->total_ttc;
1753 $descriptions[$line->tva_tx] .= '<li>' . (!empty($line->product_ref) ? $line->product_ref . ' - ' : '');
1754 $descriptions[$line->tva_tx] .= (!empty($line->product_label) ? $line->product_label . ' - ' : '');
1755 $descriptions[$line->tva_tx] .= $langs->trans('Qty') . ' : ' . $line->qty;
1756 $descriptions[$line->tva_tx] .= ' - ' . $langs->trans('TotalHT') . ' : ' . price($line->total_ht) . '</li>';
1757 }
1758
1759 foreach ($TTotalByTva as $tva => &$total) {
1760 $coef = $total / $origin->total_ttc; // Calc coef
1761 $am = $amount * $coef;
1762 $amount_ttc_diff += $am;
1763 $amountdeposit[$tva] += $am / (1 + $tva / 100); // Convert into HT for the addline
1764 }
1765 } else {
1766 $totalamount = 0;
1767 $lines = $origin->lines;
1768 $numlines = count($lines);
1769 for ($i = 0; $i < $numlines; $i++) {
1770 if (empty($lines[$i]->qty)) {
1771 continue; // We discard qty=0, it is an option
1772 }
1773 if (!empty($lines[$i]->special_code)) {
1774 continue; // We discard special_code (frais port, ecotaxe, option, ...)
1775 }
1776
1777 $totalamount += $lines[$i]->total_ht; // Fixme : is it not for the customer ? Shouldn't we take total_ttc ?
1778 $tva_tx = $lines[$i]->tva_tx;
1779 $amountdeposit[$tva_tx] += ($lines[$i]->total_ht * $origin->deposit_percent) / 100;
1780 $descriptions[$tva_tx] .= '<li>' . (!empty($lines[$i]->product_ref) ? $lines[$i]->product_ref . ' - ' : '');
1781 $descriptions[$tva_tx] .= (!empty($lines[$i]->product_label) ? $lines[$i]->product_label . ' - ' : '');
1782 $descriptions[$tva_tx] .= $langs->trans('Qty') . ' : ' . $lines[$i]->qty;
1783 $descriptions[$tva_tx] .= ' - ' . $langs->trans('TotalHT') . ' : ' . price($lines[$i]->total_ht) . '</li>';
1784 }
1785
1786 if ($totalamount == 0) {
1787 $amountdeposit[0] = 0;
1788 }
1789
1790 $amount_ttc_diff = $amountdeposit[0];
1791 }
1792
1793 foreach ($amountdeposit as $tva => $amount) {
1794 if (empty($amount)) {
1795 continue;
1796 }
1797
1798 $descline = '(DEPOSIT) ('. $origin->deposit_percent .'%) - '.$origin->ref;
1799
1800 // Hidden conf
1801 if (getDolGlobalString('INVOICE_DEPOSIT_VARIABLE_MODE_DETAIL_LINES_IN_DESCRIPTION') && !empty($descriptions[$tva])) {
1802 $descline .= '<ul>' . $descriptions[$tva] . '</ul>';
1803 }
1804
1805 $addlineResult = $deposit->addline(
1806 $descline,
1807 $amount, // subprice
1808 1, // quantity
1809 $tva, // vat rate
1810 0, // localtax1_tx
1811 0, // localtax2_tx
1812 (!getDolGlobalString('INVOICE_PRODUCTID_DEPOSIT') ? 0 : $conf->global->INVOICE_PRODUCTID_DEPOSIT), // fk_product
1813 0, // remise_percent
1814 0, // date_start
1815 0, // date_end
1816 0,
1817 0, // info_bits
1818 0,
1819 'HT',
1820 0,
1821 0, // product_type
1822 1,
1823 0, // special_code
1824 $deposit->origin,
1825 0,
1826 0,
1827 0,
1828 0
1829 //,$langs->trans('Deposit') //Deprecated
1830 );
1831
1832 if ($addlineResult < 0) {
1833 $origin->db->rollback();
1834 $origin->error = $deposit->error;
1835 $origin->errors = $deposit->errors;
1836 return null;
1837 }
1838 }
1839
1840 $diff = $deposit->total_ttc - $amount_ttc_diff;
1841
1842 if (getDolGlobalString('MAIN_DEPOSIT_MULTI_TVA') && $diff != 0) {
1843 $deposit->fetch_lines();
1844 $subprice_diff = $deposit->lines[0]->subprice - $diff / (1 + $deposit->lines[0]->tva_tx / 100);
1845
1846 $updatelineResult = $deposit->updateline(
1847 $deposit->lines[0]->id,
1848 $deposit->lines[0]->desc,
1849 $subprice_diff,
1850 $deposit->lines[0]->qty,
1851 $deposit->lines[0]->remise_percent,
1852 $deposit->lines[0]->date_start,
1853 $deposit->lines[0]->date_end,
1854 $deposit->lines[0]->tva_tx,
1855 0,
1856 0,
1857 'HT',
1858 $deposit->lines[0]->info_bits,
1859 $deposit->lines[0]->product_type,
1860 0,
1861 0,
1862 0,
1863 $deposit->lines[0]->pa_ht,
1864 $deposit->lines[0]->label,
1865 0,
1866 array(),
1867 100
1868 );
1869
1870 if ($updatelineResult < 0) {
1871 $origin->db->rollback();
1872 $origin->error = $deposit->error;
1873 $origin->errors = $deposit->errors;
1874 return null;
1875 }
1876 }
1877
1878 $hookmanager->initHooks(array('invoicedao'));
1879
1880 $parameters = array('objFrom' => $origin);
1881 $reshook = $hookmanager->executeHooks('createFrom', $parameters, $deposit, $action); // Note that $action and $object may have been
1882 // modified by hook
1883 if ($reshook < 0) {
1884 $origin->db->rollback();
1885 $origin->error = $hookmanager->error;
1886 $origin->errors = $hookmanager->errors;
1887 return null;
1888 }
1889
1890 if (!empty($autoValidateDeposit)) {
1891 $validateReturn = $deposit->validate($user, '', 0, $notrigger);
1892
1893 if ($validateReturn < 0) {
1894 $origin->db->rollback();
1895 $origin->error = $deposit->error;
1896 $origin->errors = $deposit->errors;
1897 return null;
1898 }
1899 }
1900
1901 unset($deposit->context['createdepositfromorigin']);
1902
1903 $origin->db->commit();
1904
1905 return $deposit;
1906 }
1907
1915 public function getTooltipContentArray($params)
1916 {
1917 global $conf, $langs, $mysoc, $user;
1918
1919 $langs->load('bills');
1920
1921 $datas = [];
1922 $moretitle = $params['moretitle'] ?? '';
1923 $picto = $this->picto;
1924 if ($this->type == self::TYPE_REPLACEMENT) {
1925 $picto .= 'r'; // Replacement invoice
1926 }
1927 if ($this->type == self::TYPE_CREDIT_NOTE) {
1928 $picto .= 'a'; // Credit note
1929 }
1930 if ($this->type == self::TYPE_DEPOSIT) {
1931 $picto .= 'd'; // Deposit invoice
1932 }
1933
1934 if ($user->hasRight("facture", "read")) {
1935 $datas['picto'] = img_picto('', $picto).' <u class="paddingrightonly">'.$langs->trans("Invoice").'</u>';
1936
1937 $datas['picto'] .= '&nbsp;'.$this->getLibType(1);
1938
1939 // Complete datas
1940 if (!empty($params['fromajaxtooltip']) && !isset($this->alreadypaid)) {
1941 // Load the alreadypaid field
1942 $this->alreadypaid = $this->getSommePaiement(0);
1943 }
1944 if (isset($this->status) && isset($this->alreadypaid)) {
1945 $datas['picto'] .= ' '.$this->getLibStatut(5, $this->alreadypaid);
1946 }
1947 if ($moretitle) {
1948 $datas['picto'] .= ' - '.$moretitle;
1949 }
1950 if (!empty($this->ref)) {
1951 $datas['ref'] = '<br><b>'.$langs->trans('Ref').':</b> '.$this->ref;
1952 }
1953 if (!empty($this->ref_customer)) {
1954 $datas['refcustomer'] = '<br><b>'.$langs->trans('RefCustomer').':</b> '.$this->ref_customer;
1955 }
1956 if (!empty($this->date)) {
1957 $datas['date'] = '<br><b>'.$langs->trans('Date').':</b> '.dol_print_date($this->date, 'day');
1958 }
1959 if (!empty($this->total_ht)) {
1960 $datas['amountht'] = '<br><b>'.$langs->trans('AmountHT').':</b> '.price($this->total_ht, 0, $langs, 0, -1, -1, $conf->currency);
1961 }
1962 if (!empty($this->total_tva)) {
1963 $datas['amountvat'] = '<br><b>'.$langs->trans('AmountVAT').':</b> '.price($this->total_tva, 0, $langs, 0, -1, -1, $conf->currency);
1964 }
1965 if (!empty($this->revenuestamp) && $this->revenuestamp != 0) {
1966 $datas['amountrevenustamp'] = '<br><b>'.$langs->trans('RevenueStamp').':</b> '.price($this->revenuestamp, 0, $langs, 0, -1, -1, $conf->currency);
1967 }
1968 if (!empty($this->total_localtax1) && $this->total_localtax1 != 0) {
1969 // We keep test != 0 because $this->total_localtax1 can be '0.00000000'
1970 $datas['amountlt1'] = '<br><b>'.$langs->transcountry('AmountLT1', $mysoc->country_code).':</b> '.price($this->total_localtax1, 0, $langs, 0, -1, -1, $conf->currency);
1971 }
1972 if (!empty($this->total_localtax2) && $this->total_localtax2 != 0) {
1973 $datas['amountlt2'] = '<br><b>'.$langs->transcountry('AmountLT2', $mysoc->country_code).':</b> '.price($this->total_localtax2, 0, $langs, 0, -1, -1, $conf->currency);
1974 }
1975 if (!empty($this->total_ttc)) {
1976 $datas['amountttc'] = '<br><b>'.$langs->trans('AmountTTC').':</b> '.price($this->total_ttc, 0, $langs, 0, -1, -1, $conf->currency);
1977 }
1978 }
1979
1980 return $datas;
1981 }
1982
1997 public function getNomUrl($withpicto = 0, $option = '', $max = 0, $short = 0, $moretitle = '', $notooltip = 0, $addlinktonotes = 0, $save_lastsearch_value = -1, $target = '')
1998 {
1999 global $langs, $conf, $user, $mysoc;
2000
2001 if (!empty($conf->dol_no_mouse_hover)) {
2002 $notooltip = 1; // Force disable tooltips
2003 }
2004
2005 $result = '';
2006
2007 if ($option == 'withdraw') {
2008 $url = DOL_URL_ROOT.'/compta/facture/prelevement.php?facid='.$this->id;
2009 } else {
2010 $url = DOL_URL_ROOT.'/compta/facture/card.php?facid='.$this->id;
2011 }
2012
2013 if (!$user->hasRight("facture", "read")) {
2014 $option = 'nolink';
2015 }
2016
2017 if ($option !== 'nolink') {
2018 // Add param to save lastsearch_values or not
2019 $add_save_lastsearch_values = ($save_lastsearch_value == 1 ? 1 : 0);
2020 if ($save_lastsearch_value == -1 && isset($_SERVER["PHP_SELF"]) && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) {
2021 $add_save_lastsearch_values = 1;
2022 }
2023 if ($add_save_lastsearch_values) {
2024 $url .= '&save_lastsearch_values=1';
2025 }
2026 }
2027
2028 if ($short) {
2029 return $url;
2030 }
2031
2032 $picto = $this->picto;
2033 if ($this->type == self::TYPE_REPLACEMENT) {
2034 $picto .= 'r'; // Replacement invoice
2035 }
2036 if ($this->type == self::TYPE_CREDIT_NOTE) {
2037 $picto .= 'a'; // Credit note
2038 }
2039 if ($this->type == self::TYPE_DEPOSIT) {
2040 $picto .= 'd'; // Deposit invoice
2041 }
2042 $params = [
2043 'id' => $this->id,
2044 'objecttype' => $this->element,
2045 'moretitle' => $moretitle,
2046 'option' => $option,
2047 ];
2048 $classfortooltip = 'classfortooltip';
2049 $dataparams = '';
2050 if (getDolGlobalInt('MAIN_ENABLE_AJAX_TOOLTIP')) {
2051 $classfortooltip = 'classforajaxtooltip';
2052 $dataparams = ' data-params="'.dol_escape_htmltag(json_encode($params)).'"';
2053 $label = '';
2054 } else {
2055 $label = implode($this->getTooltipContentArray($params));
2056 }
2057
2058 $linkclose = ($target ? ' target="'.$target.'"' : '');
2059 if (empty($notooltip) && $user->hasRight("facture", "read")) {
2060 if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
2061 $label = $langs->trans("Invoice");
2062 $linkclose .= ' alt="'.dol_escape_htmltag($label, 1).'"';
2063 }
2064 $linkclose .= ($label ? ' title="'.dol_escape_htmltag($label, 1).'"' : ' title="tocomplete"');
2065 $linkclose .= $dataparams.' class="'.$classfortooltip.'"';
2066 }
2067
2068 $linkstart = '<a href="'.$url.'"';
2069 $linkstart .= $linkclose.'>';
2070 $linkend = '</a>';
2071
2072 if ($option == 'nolink') {
2073 $linkstart = '';
2074 $linkend = '';
2075 }
2076
2077 $result .= $linkstart;
2078 if ($withpicto) {
2079 $result .= img_object(($notooltip ? '' : $label), ($this->picto ? $this->picto : 'generic'), ($notooltip ? (($withpicto != 2) ? 'class="paddingright"' : '') : 'class="'.(($withpicto != 2) ? 'paddingright ' : '').'"'), 0, 0, $notooltip ? 0 : 1);
2080 }
2081 if ($withpicto != 2) {
2082 $result .= ($max ? dol_trunc($this->ref, $max) : $this->ref);
2083 }
2084 $result .= $linkend;
2085
2086 if ($addlinktonotes) {
2087 $txttoshow = ($user->socid > 0 ? $this->note_public : $this->note_private);
2088 if ($txttoshow) {
2089 //$notetoshow = $langs->trans("ViewPrivateNote").':<br>'.dol_string_nohtmltag($txttoshow, 1);
2090 $notetoshow = $langs->trans("ViewPrivateNote").':<br>'.$txttoshow;
2091 $result .= ' <span class="note inline-block">';
2092 $result .= '<a href="'.DOL_URL_ROOT.'/compta/facture/note.php?id='.$this->id.'" class="classfortooltip" title="'.dol_escape_htmltag($notetoshow, 1, 1).'">';
2093 $result .= img_picto('', 'note');
2094 $result .= '</a>';
2095 //$result.=img_picto($langs->trans("ViewNote"),'object_generic');
2096 //$result.='</a>';
2097 $result .= '</span>';
2098 }
2099 }
2100
2101 global $action, $hookmanager;
2102 $hookmanager->initHooks(array('invoicedao'));
2103 $parameters = array('id'=>$this->id, 'getnomurl' => &$result, 'notooltip' => $notooltip, 'addlinktonotes' => $addlinktonotes, 'save_lastsearch_value'=> $save_lastsearch_value, 'target' => $target);
2104 $reshook = $hookmanager->executeHooks('getNomUrl', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
2105 if ($reshook > 0) {
2106 $result = $hookmanager->resPrint;
2107 } else {
2108 $result .= $hookmanager->resPrint;
2109 }
2110
2111 return $result;
2112 }
2113
2124 public function fetch($rowid, $ref = '', $ref_ext = '', $notused = 0, $fetch_situation = false)
2125 {
2126 if (empty($rowid) && empty($ref) && empty($ref_ext)) {
2127 return -1;
2128 }
2129
2130 $sql = 'SELECT f.rowid, f.entity, f.ref, f.ref_client, f.ref_ext, f.type, f.subtype, f.fk_soc';
2131 $sql .= ', f.total_tva, f.localtax1, f.localtax2, f.total_ht, f.total_ttc, f.revenuestamp';
2132 $sql .= ', f.datef as df, f.date_pointoftax';
2133 $sql .= ', f.date_lim_reglement as dlr';
2134 $sql .= ', f.datec as datec';
2135 $sql .= ', f.date_valid as datev';
2136 $sql .= ', f.tms as datem';
2137 $sql .= ', f.note_private, f.note_public, f.fk_statut as status, f.paye, f.close_code, f.close_note, f.fk_user_author, f.fk_user_valid, f.fk_user_modif, f.model_pdf, f.last_main_doc';
2138 $sql .= ', f.fk_facture_source, f.fk_fac_rec_source';
2139 $sql .= ', f.fk_mode_reglement, f.fk_cond_reglement, f.fk_projet as fk_project, f.extraparams';
2140 $sql .= ', f.situation_cycle_ref, f.situation_counter, f.situation_final';
2141 $sql .= ', f.fk_account';
2142 $sql .= ", f.fk_multicurrency, f.multicurrency_code, f.multicurrency_tx, f.multicurrency_total_ht, f.multicurrency_total_tva, f.multicurrency_total_ttc";
2143 $sql .= ', p.code as mode_reglement_code, p.libelle as mode_reglement_libelle';
2144 $sql .= ', c.code as cond_reglement_code, c.libelle as cond_reglement_libelle, c.libelle_facture as cond_reglement_libelle_doc';
2145 $sql .= ', f.fk_incoterms, f.location_incoterms';
2146 $sql .= ', f.module_source, f.pos_source';
2147 $sql .= ", i.libelle as label_incoterms";
2148 $sql .= ", f.retained_warranty as retained_warranty, f.retained_warranty_date_limit as retained_warranty_date_limit, f.retained_warranty_fk_cond_reglement as retained_warranty_fk_cond_reglement";
2149 $sql .= ' FROM '.MAIN_DB_PREFIX.'facture as f';
2150 $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_payment_term as c ON f.fk_cond_reglement = c.rowid';
2151 $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_paiement as p ON f.fk_mode_reglement = p.id';
2152 $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_incoterms as i ON f.fk_incoterms = i.rowid';
2153
2154 if ($rowid) {
2155 $sql .= " WHERE f.rowid = ".((int) $rowid);
2156 } else {
2157 $sql .= ' WHERE f.entity IN ('.getEntity('invoice').')'; // Don't use entity if you use rowid
2158 if ($ref) {
2159 $sql .= " AND f.ref = '".$this->db->escape($ref)."'";
2160 }
2161 if ($ref_ext) {
2162 $sql .= " AND f.ref_ext = '".$this->db->escape($ref_ext)."'";
2163 }
2164 }
2165
2166 dol_syslog(get_class($this)."::fetch", LOG_DEBUG);
2167 $resql = $this->db->query($sql);
2168 if ($resql) {
2169 if ($this->db->num_rows($resql)) {
2170 $obj = $this->db->fetch_object($resql);
2171
2172 $this->id = $obj->rowid;
2173 $this->entity = $obj->entity;
2174
2175 $this->ref = $obj->ref;
2176 $this->ref_client = $obj->ref_client;
2177 $this->ref_customer = $obj->ref_client;
2178 $this->ref_ext = $obj->ref_ext;
2179 $this->type = $obj->type;
2180 $this->subtype = $obj->subtype;
2181 $this->date = $this->db->jdate($obj->df);
2182 $this->date_pointoftax = $this->db->jdate($obj->date_pointoftax);
2183 $this->date_creation = $this->db->jdate($obj->datec);
2184 $this->date_validation = $this->db->jdate($obj->datev);
2185 $this->date_modification = $this->db->jdate($obj->datem);
2186 $this->datem = $this->db->jdate($obj->datem);
2187 $this->total_ht = $obj->total_ht;
2188 $this->total_tva = $obj->total_tva;
2189 $this->total_localtax1 = $obj->localtax1;
2190 $this->total_localtax2 = $obj->localtax2;
2191 $this->total_ttc = $obj->total_ttc;
2192 $this->revenuestamp = $obj->revenuestamp;
2193 $this->paye = $obj->paye;
2194 $this->close_code = $obj->close_code;
2195 $this->close_note = $obj->close_note;
2196
2197 $this->socid = $obj->fk_soc;
2198 $this->thirdparty = null; // Clear if another value was already set by fetch_thirdparty
2199
2200 $this->fk_project = $obj->fk_project;
2201 $this->project = null; // Clear if another value was already set by fetch_projet
2202
2203 $this->statut = $obj->status; // deprecated
2204 $this->status = $obj->status;
2205
2206 $this->date_lim_reglement = $this->db->jdate($obj->dlr);
2207 $this->mode_reglement_id = $obj->fk_mode_reglement;
2208 $this->mode_reglement_code = $obj->mode_reglement_code;
2209 $this->mode_reglement = $obj->mode_reglement_libelle;
2210 $this->cond_reglement_id = $obj->fk_cond_reglement;
2211 $this->cond_reglement_code = $obj->cond_reglement_code;
2212 $this->cond_reglement = $obj->cond_reglement_libelle;
2213 $this->cond_reglement_doc = $obj->cond_reglement_libelle_doc;
2214 $this->fk_account = ($obj->fk_account > 0) ? $obj->fk_account : null;
2215 $this->fk_facture_source = $obj->fk_facture_source;
2216 $this->fk_fac_rec_source = $obj->fk_fac_rec_source;
2217 $this->note = $obj->note_private; // deprecated
2218 $this->note_private = $obj->note_private;
2219 $this->note_public = $obj->note_public;
2220 $this->user_creation_id = $obj->fk_user_author;
2221 $this->user_validation_id = $obj->fk_user_valid;
2222 $this->user_modification_id = $obj->fk_user_modif;
2223 $this->fk_user_author = $obj->fk_user_author;
2224 $this->fk_user_valid = $obj->fk_user_valid;
2225 $this->fk_user_modif = $obj->fk_user_modif;
2226 $this->model_pdf = $obj->model_pdf;
2227 $this->last_main_doc = $obj->last_main_doc;
2228 $this->situation_cycle_ref = $obj->situation_cycle_ref;
2229 $this->situation_counter = $obj->situation_counter;
2230 $this->situation_final = $obj->situation_final;
2231 $this->retained_warranty = $obj->retained_warranty;
2232 $this->retained_warranty_date_limit = $this->db->jdate($obj->retained_warranty_date_limit);
2233 $this->retained_warranty_fk_cond_reglement = $obj->retained_warranty_fk_cond_reglement;
2234
2235 $this->extraparams = !empty($obj->extraparams) ? (array) json_decode($obj->extraparams, true) : array();
2236
2237 //Incoterms
2238 $this->fk_incoterms = $obj->fk_incoterms;
2239 $this->location_incoterms = $obj->location_incoterms;
2240 $this->label_incoterms = $obj->label_incoterms;
2241
2242 $this->module_source = $obj->module_source;
2243 $this->pos_source = $obj->pos_source;
2244
2245 // Multicurrency
2246 $this->fk_multicurrency = $obj->fk_multicurrency;
2247 $this->multicurrency_code = $obj->multicurrency_code;
2248 $this->multicurrency_tx = $obj->multicurrency_tx;
2249 $this->multicurrency_total_ht = $obj->multicurrency_total_ht;
2250 $this->multicurrency_total_tva = $obj->multicurrency_total_tva;
2251 $this->multicurrency_total_ttc = $obj->multicurrency_total_ttc;
2252
2253 if (($this->type == self::TYPE_SITUATION || ($this->type == self::TYPE_CREDIT_NOTE && $this->situation_cycle_ref > 0)) && $fetch_situation) {
2255 }
2256
2257 // Retrieve all extrafield
2258 // fetch optionals attributes and labels
2259 $this->fetch_optionals();
2260
2261 // Lines
2262 $this->lines = array();
2263
2264 $result = $this->fetch_lines();
2265 if ($result < 0) {
2266 $this->error = $this->db->error();
2267 return -3;
2268 }
2269
2270 $this->db->free($resql);
2271
2272 return 1;
2273 } else {
2274 $this->error = 'Invoice with id='.$rowid.' or ref='.$ref.' or ref_ext='.$ref_ext.' not found';
2275
2276 dol_syslog(__METHOD__.$this->error, LOG_WARNING);
2277 return 0;
2278 }
2279 } else {
2280 $this->error = $this->db->lasterror();
2281 return -1;
2282 }
2283 }
2284
2285
2286 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2295 public function fetch_lines($only_product = 0, $loadalsotranslation = 0)
2296 {
2297 // phpcs:enable
2298 $this->lines = array();
2299
2300 $sql = 'SELECT l.rowid, l.fk_facture, l.fk_product, l.fk_parent_line, l.label as custom_label, l.description, l.product_type, l.price, l.qty, l.vat_src_code, l.tva_tx,';
2301 $sql .= ' l.localtax1_tx, l.localtax2_tx, l.localtax1_type, l.localtax2_type, l.remise_percent, l.fk_remise_except, l.subprice, l.ref_ext,';
2302 $sql .= ' l.situation_percent, l.fk_prev_id,';
2303 $sql .= ' l.rang, l.special_code, l.batch, l.fk_warehouse,';
2304 $sql .= ' l.date_start as date_start, l.date_end as date_end,';
2305 $sql .= ' l.info_bits, l.total_ht, l.total_tva, l.total_localtax1, l.total_localtax2, l.total_ttc, l.fk_code_ventilation, l.fk_product_fournisseur_price as fk_fournprice, l.buy_price_ht as pa_ht,';
2306 $sql .= ' l.fk_unit,';
2307 $sql .= ' l.fk_multicurrency, l.multicurrency_code, l.multicurrency_subprice, l.multicurrency_total_ht, l.multicurrency_total_tva, l.multicurrency_total_ttc,';
2308 $sql .= ' p.ref as product_ref, p.fk_product_type as fk_product_type, p.label as product_label, p.description as product_desc, p.barcode as product_barcode';
2309 $sql .= ' FROM '.MAIN_DB_PREFIX.'facturedet as l';
2310 $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'product as p ON l.fk_product = p.rowid';
2311 $sql .= ' WHERE l.fk_facture = '.((int) $this->id);
2312 $sql .= ' ORDER BY l.rang, l.rowid';
2313
2314 dol_syslog(get_class($this).'::fetch_lines', LOG_DEBUG);
2315 $result = $this->db->query($sql);
2316 if ($result) {
2317 $num = $this->db->num_rows($result);
2318 $i = 0;
2319 while ($i < $num) {
2320 $objp = $this->db->fetch_object($result);
2321 $line = new FactureLigne($this->db);
2322
2323 $line->id = $objp->rowid;
2324 $line->rowid = $objp->rowid; // deprecated
2325 $line->fk_facture = $objp->fk_facture;
2326 $line->label = $objp->custom_label; // deprecated
2327 $line->desc = $objp->description; // Description line
2328 $line->description = $objp->description; // Description line
2329 $line->product_type = $objp->product_type; // Type of line
2330 $line->ref = $objp->product_ref; // Ref product
2331 $line->product_ref = $objp->product_ref; // Ref product
2332 $line->libelle = $objp->product_label; // deprecated
2333 $line->product_label = $objp->product_label; // Label product
2334 $line->product_barcode = $objp->product_barcode; // Barcode number product
2335 $line->product_desc = $objp->product_desc; // Description product
2336 $line->fk_product_type = $objp->fk_product_type; // Type of product
2337 $line->qty = $objp->qty;
2338 $line->subprice = $objp->subprice;
2339 $line->ref_ext = $objp->ref_ext; // line external ref
2340
2341 $line->vat_src_code = $objp->vat_src_code;
2342 $line->tva_tx = $objp->tva_tx;
2343 $line->localtax1_tx = $objp->localtax1_tx;
2344 $line->localtax2_tx = $objp->localtax2_tx;
2345 $line->localtax1_type = $objp->localtax1_type;
2346 $line->localtax2_type = $objp->localtax2_type;
2347 $line->remise_percent = $objp->remise_percent;
2348 $line->fk_remise_except = $objp->fk_remise_except;
2349 $line->fk_product = $objp->fk_product;
2350 $line->date_start = $this->db->jdate($objp->date_start);
2351 $line->date_end = $this->db->jdate($objp->date_end);
2352 $line->info_bits = $objp->info_bits;
2353 $line->total_ht = $objp->total_ht;
2354 $line->total_tva = $objp->total_tva;
2355 $line->total_localtax1 = $objp->total_localtax1;
2356 $line->total_localtax2 = $objp->total_localtax2;
2357 $line->total_ttc = $objp->total_ttc;
2358
2359 $line->code_ventilation = $objp->fk_code_ventilation;
2360 $line->fk_fournprice = $objp->fk_fournprice;
2361 $marginInfos = getMarginInfos($objp->subprice, $objp->remise_percent, $objp->tva_tx, $objp->localtax1_tx, $objp->localtax2_tx, $line->fk_fournprice, $objp->pa_ht);
2362 $line->pa_ht = $marginInfos[0];
2363 $line->marge_tx = $marginInfos[1];
2364 $line->marque_tx = $marginInfos[2];
2365 $line->rang = $objp->rang;
2366 $line->special_code = $objp->special_code;
2367 $line->fk_parent_line = $objp->fk_parent_line;
2368 $line->situation_percent = $objp->situation_percent;
2369 $line->fk_prev_id = $objp->fk_prev_id;
2370 $line->fk_unit = $objp->fk_unit;
2371
2372 $line->batch = $objp->batch;
2373 $line->fk_warehouse = $objp->fk_warehouse;
2374
2375 // Accountancy
2376 $line->fk_accounting_account = $objp->fk_code_ventilation;
2377
2378 // Multicurrency
2379 $line->fk_multicurrency = $objp->fk_multicurrency;
2380 $line->multicurrency_code = $objp->multicurrency_code;
2381 $line->multicurrency_subprice = $objp->multicurrency_subprice;
2382 $line->multicurrency_total_ht = $objp->multicurrency_total_ht;
2383 $line->multicurrency_total_tva = $objp->multicurrency_total_tva;
2384 $line->multicurrency_total_ttc = $objp->multicurrency_total_ttc;
2385
2386 $line->fetch_optionals();
2387
2388 // multilangs
2389 if (getDolGlobalInt('MAIN_MULTILANGS') && !empty($objp->fk_product) && !empty($loadalsotranslation)) {
2390 $tmpproduct = new Product($this->db);
2391 $tmpproduct->fetch($objp->fk_product);
2392 $tmpproduct->getMultiLangs();
2393
2394 $line->multilangs = $tmpproduct->multilangs;
2395 }
2396
2397 $this->lines[$i] = $line;
2398
2399 $i++;
2400 }
2401 $this->db->free($result);
2402 return 1;
2403 } else {
2404 $this->error = $this->db->error();
2405 return -3;
2406 }
2407 }
2408
2416 {
2417 global $conf;
2418
2419 $this->tab_previous_situation_invoice = array();
2420 $this->tab_next_situation_invoice = array();
2421
2422 $sql = 'SELECT rowid, type, situation_cycle_ref, situation_counter FROM '.MAIN_DB_PREFIX.'facture';
2423 $sql .= " WHERE rowid <> ".((int) $this->id);
2424 $sql .= ' AND entity = '.((int) $this->entity);
2425 $sql .= ' AND situation_cycle_ref = '.(int) $this->situation_cycle_ref;
2426 $sql .= ' ORDER BY situation_counter ASC';
2427
2428 dol_syslog(get_class($this).'::fetchPreviousNextSituationInvoice ', LOG_DEBUG);
2429 $result = $this->db->query($sql);
2430 if ($result && $this->db->num_rows($result) > 0) {
2431 while ($objp = $this->db->fetch_object($result)) {
2432 $invoice = new Facture($this->db);
2433 if ($invoice->fetch($objp->rowid) > 0) {
2434 if ($objp->situation_counter < $this->situation_counter
2435 || ($objp->situation_counter == $this->situation_counter && $objp->rowid < $this->id) // This case appear when there are credit notes
2436 ) {
2437 $this->tab_previous_situation_invoice[] = $invoice;
2438 } else {
2439 $this->tab_next_situation_invoice[] = $invoice;
2440 }
2441 }
2442 }
2443 }
2444 }
2445
2453 public function update(User $user, $notrigger = 0)
2454 {
2455 $error = 0;
2456
2457 // Clean parameters
2458 if (empty($this->type)) {
2459 $this->type = self::TYPE_STANDARD;
2460 }
2461 if (isset($this->ref)) {
2462 $this->ref = trim($this->ref);
2463 }
2464 if (isset($this->ref_ext)) {
2465 $this->ref_ext = trim($this->ref_ext);
2466 }
2467 if (isset($this->ref_client)) {
2468 $this->ref_client = trim($this->ref_client);
2469 }
2470 if (isset($this->increment)) {
2471 $this->increment = trim($this->increment);
2472 }
2473 if (isset($this->close_code)) {
2474 $this->close_code = trim($this->close_code);
2475 }
2476 if (isset($this->close_note)) {
2477 $this->close_note = trim($this->close_note);
2478 }
2479 if (isset($this->note) || isset($this->note_private)) {
2480 $this->note = (isset($this->note) ? trim($this->note) : trim($this->note_private)); // deprecated
2481 }
2482 if (isset($this->note) || isset($this->note_private)) {
2483 $this->note_private = (isset($this->note_private) ? trim($this->note_private) : trim($this->note));
2484 }
2485 if (isset($this->note_public)) {
2486 $this->note_public = trim($this->note_public);
2487 }
2488 if (isset($this->model_pdf)) {
2489 $this->model_pdf = trim($this->model_pdf);
2490 }
2491 if (isset($this->import_key)) {
2492 $this->import_key = trim($this->import_key);
2493 }
2494 if (isset($this->retained_warranty)) {
2495 $this->retained_warranty = (float) $this->retained_warranty;
2496 }
2497 if (!isset($this->fk_user_author) && isset($this->user_author) ) {
2498 $this->fk_user_author = $this->user_author;
2499 }
2500
2501
2502 // Check parameters
2503 // Put here code to add control on parameters values
2504
2505 // Update request
2506 $sql = "UPDATE ".MAIN_DB_PREFIX."facture SET";
2507 $sql .= " ref=".(isset($this->ref) ? "'".$this->db->escape($this->ref)."'" : "null").",";
2508 $sql .= " ref_ext=".(isset($this->ref_ext) ? "'".$this->db->escape($this->ref_ext)."'" : "null").",";
2509 $sql .= " type=".(isset($this->type) ? $this->db->escape($this->type) : "null").",";
2510 $sql .= " subtype=".(isset($this->subtype) ? $this->db->escape($this->subtype) : "null").",";
2511 $sql .= " ref_client=".(isset($this->ref_client) ? "'".$this->db->escape($this->ref_client)."'" : "null").",";
2512 $sql .= " increment=".(isset($this->increment) ? "'".$this->db->escape($this->increment)."'" : "null").",";
2513 $sql .= " fk_soc=".(isset($this->socid) ? $this->db->escape($this->socid) : "null").",";
2514 $sql .= " datec=".(strval($this->date_creation) != '' ? "'".$this->db->idate($this->date_creation)."'" : 'null').",";
2515 $sql .= " datef=".(strval($this->date) != '' ? "'".$this->db->idate($this->date)."'" : 'null').",";
2516 $sql .= " date_pointoftax=".(strval($this->date_pointoftax) != '' ? "'".$this->db->idate($this->date_pointoftax)."'" : 'null').",";
2517 $sql .= " date_valid=".(strval($this->date_validation) != '' ? "'".$this->db->idate($this->date_validation)."'" : 'null').",";
2518 $sql .= " paye=".(isset($this->paye) ? $this->db->escape($this->paye) : 0).",";
2519 $sql .= " close_code=".(isset($this->close_code) ? "'".$this->db->escape($this->close_code)."'" : "null").",";
2520 $sql .= " close_note=".(isset($this->close_note) ? "'".$this->db->escape($this->close_note)."'" : "null").",";
2521 $sql .= " total_tva=".(isset($this->total_tva) ? $this->total_tva : "null").",";
2522 $sql .= " localtax1=".(isset($this->total_localtax1) ? $this->total_localtax1 : "null").",";
2523 $sql .= " localtax2=".(isset($this->total_localtax2) ? $this->total_localtax2 : "null").",";
2524 $sql .= " total_ht=".(isset($this->total_ht) ? $this->total_ht : "null").",";
2525 $sql .= " total_ttc=".(isset($this->total_ttc) ? $this->total_ttc : "null").",";
2526 $sql .= " revenuestamp=".((isset($this->revenuestamp) && $this->revenuestamp != '') ? $this->db->escape($this->revenuestamp) : "null").",";
2527 $sql .= " fk_statut=".(isset($this->status) ? ((int) $this->status) : "null").",";
2528 $sql .= " fk_user_author=".(isset($this->fk_user_author) ? ((int) $this->fk_user_author) : "null").",";
2529 $sql .= " fk_user_valid=".(isset($this->fk_user_valid) ? $this->db->escape($this->fk_user_valid) : "null").",";
2530 $sql .= " fk_facture_source=".(isset($this->fk_facture_source) ? $this->db->escape($this->fk_facture_source) : "null").",";
2531 $sql .= " fk_projet=".(isset($this->fk_project) ? $this->db->escape($this->fk_project) : "null").",";
2532 $sql .= " fk_cond_reglement=".(isset($this->cond_reglement_id) ? $this->db->escape($this->cond_reglement_id) : "null").",";
2533 $sql .= " fk_mode_reglement=".(isset($this->mode_reglement_id) ? $this->db->escape($this->mode_reglement_id) : "null").",";
2534 $sql .= " date_lim_reglement=".(strval($this->date_lim_reglement) != '' ? "'".$this->db->idate($this->date_lim_reglement)."'" : 'null').",";
2535 $sql .= " note_private=".(isset($this->note_private) ? "'".$this->db->escape($this->note_private)."'" : "null").",";
2536 $sql .= " note_public=".(isset($this->note_public) ? "'".$this->db->escape($this->note_public)."'" : "null").",";
2537 $sql .= " model_pdf=".(isset($this->model_pdf) ? "'".$this->db->escape($this->model_pdf)."'" : "null").",";
2538 $sql .= " import_key=".(isset($this->import_key) ? "'".$this->db->escape($this->import_key)."'" : "null").",";
2539 $sql .= " situation_cycle_ref=".(empty($this->situation_cycle_ref) ? "null" : $this->db->escape($this->situation_cycle_ref)).",";
2540 $sql .= " situation_counter=".(empty($this->situation_counter) ? "null" : $this->db->escape($this->situation_counter)).",";
2541 $sql .= " situation_final=".(empty($this->situation_final) ? "0" : $this->db->escape($this->situation_final)).",";
2542 $sql .= " retained_warranty=".(empty($this->retained_warranty) ? "0" : $this->db->escape($this->retained_warranty)).",";
2543 $sql .= " retained_warranty_date_limit=".(strval($this->retained_warranty_date_limit) != '' ? "'".$this->db->idate($this->retained_warranty_date_limit)."'" : 'null').",";
2544 $sql .= " retained_warranty_fk_cond_reglement=".(isset($this->retained_warranty_fk_cond_reglement) ? intval($this->retained_warranty_fk_cond_reglement) : "null");
2545 $sql .= " WHERE rowid=".((int) $this->id);
2546
2547 $this->db->begin();
2548
2549 dol_syslog(get_class($this)."::update", LOG_DEBUG);
2550 $resql = $this->db->query($sql);
2551 if (!$resql) {
2552 $error++;
2553 $this->errors[] = "Error ".$this->db->lasterror();
2554 }
2555
2556 if (!$error) {
2557 $result = $this->insertExtraFields();
2558 if ($result < 0) {
2559 $error++;
2560 }
2561 }
2562
2563 if (!$error && !$notrigger) {
2564 // Call trigger
2565 $result = $this->call_trigger('BILL_MODIFY', $user);
2566 if ($result < 0) {
2567 $error++;
2568 }
2569 // End call triggers
2570 }
2571
2572 // Commit or rollback
2573 if ($error) {
2574 foreach ($this->errors as $errmsg) {
2575 dol_syslog(get_class($this)."::update ".$errmsg, LOG_ERR);
2576 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2577 }
2578 $this->db->rollback();
2579 return -1 * $error;
2580 } else {
2581 $this->db->commit();
2582 return 1;
2583 }
2584 }
2585
2586
2587 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2594 public function insert_discount($idremise)
2595 {
2596 // phpcs:enable
2597 global $conf, $langs;
2598
2599 include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
2600 include_once DOL_DOCUMENT_ROOT.'/core/class/discount.class.php';
2601
2602 $this->db->begin();
2603
2604 $remise = new DiscountAbsolute($this->db);
2605 $result = $remise->fetch($idremise);
2606
2607 if ($result > 0) {
2608 if ($remise->fk_facture) { // Protection against multiple submission
2609 $this->error = $langs->trans("ErrorDiscountAlreadyUsed");
2610 $this->db->rollback();
2611 return -5;
2612 }
2613
2614 $facligne = new FactureLigne($this->db);
2615 $facligne->fk_facture = $this->id;
2616 $facligne->fk_remise_except = $remise->id;
2617 $facligne->desc = $remise->description; // Description ligne
2618 $facligne->vat_src_code = $remise->vat_src_code;
2619 $facligne->tva_tx = $remise->tva_tx;
2620 $facligne->subprice = -$remise->amount_ht;
2621 $facligne->fk_product = 0; // Id produit predefini
2622 $facligne->qty = 1;
2623 $facligne->remise_percent = 0;
2624 $facligne->rang = -1;
2625 $facligne->info_bits = 2;
2626
2627 if (getDolGlobalString('MAIN_ADD_LINE_AT_POSITION')) {
2628 $facligne->rang = 1;
2629 $linecount = count($this->lines);
2630 for ($ii = 1; $ii <= $linecount; $ii++) {
2631 $this->updateRangOfLine($this->lines[$ii - 1]->id, $ii+1);
2632 }
2633 }
2634
2635 // Get buy/cost price of invoice that is source of discount
2636 if ($remise->fk_facture_source > 0) {
2637 $srcinvoice = new Facture($this->db);
2638 $srcinvoice->fetch($remise->fk_facture_source);
2639 include_once DOL_DOCUMENT_ROOT.'/core/class/html.formmargin.class.php'; // TODO Move this into commonobject
2640 $formmargin = new FormMargin($this->db);
2641 $arraytmp = $formmargin->getMarginInfosArray($srcinvoice, false);
2642 $facligne->pa_ht = $arraytmp['pa_total'];
2643 }
2644
2645 $facligne->total_ht = -$remise->amount_ht;
2646 $facligne->total_tva = -$remise->amount_tva;
2647 $facligne->total_ttc = -$remise->amount_ttc;
2648
2649 $facligne->multicurrency_subprice = -$remise->multicurrency_subprice;
2650 $facligne->multicurrency_total_ht = -$remise->multicurrency_amount_ht;
2651 $facligne->multicurrency_total_tva = -$remise->multicurrency_amount_tva;
2652 $facligne->multicurrency_total_ttc = -$remise->multicurrency_amount_ttc;
2653
2654 $lineid = $facligne->insert();
2655 if ($lineid > 0) {
2656 $result = $this->update_price(1);
2657 if ($result > 0) {
2658 // Create link between discount and invoice line
2659 $result = $remise->link_to_invoice($lineid, 0);
2660 if ($result < 0) {
2661 $this->error = $remise->error;
2662 $this->db->rollback();
2663 return -4;
2664 }
2665
2666 $this->db->commit();
2667 return 1;
2668 } else {
2669 $this->error = $facligne->error;
2670 $this->db->rollback();
2671 return -1;
2672 }
2673 } else {
2674 $this->error = $facligne->error;
2675 $this->db->rollback();
2676 return -2;
2677 }
2678 } else {
2679 $this->db->rollback();
2680 return -3;
2681 }
2682 }
2683
2684 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2692 public function set_ref_client($ref_client, $notrigger = 0)
2693 {
2694 // phpcs:enable
2695 global $user;
2696
2697 $error = 0;
2698
2699 $this->db->begin();
2700
2701 $sql = 'UPDATE '.MAIN_DB_PREFIX.'facture';
2702 if (empty($ref_client)) {
2703 $sql .= ' SET ref_client = NULL';
2704 } else {
2705 $sql .= ' SET ref_client = \''.$this->db->escape($ref_client).'\'';
2706 }
2707 $sql .= " WHERE rowid = ".((int) $this->id);
2708
2709 dol_syslog(__METHOD__.' this->id='.$this->id.', ref_client='.$ref_client, LOG_DEBUG);
2710 $resql = $this->db->query($sql);
2711 if (!$resql) {
2712 $this->errors[] = $this->db->error();
2713 $error++;
2714 }
2715
2716 if (!$error) {
2717 $this->ref_client = $ref_client;
2718 }
2719
2720 if (!$notrigger && empty($error)) {
2721 // Call trigger
2722 $result = $this->call_trigger('BILL_MODIFY', $user);
2723 if ($result < 0) {
2724 $error++;
2725 }
2726 // End call triggers
2727 }
2728
2729 if (!$error) {
2730 $this->ref_client = $ref_client;
2731
2732 $this->db->commit();
2733 return 1;
2734 } else {
2735 foreach ($this->errors as $errmsg) {
2736 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2737 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2738 }
2739 $this->db->rollback();
2740 return -1 * $error;
2741 }
2742 }
2743
2752 public function delete($user, $notrigger = 0, $idwarehouse = -1)
2753 {
2754 global $langs, $conf;
2755 require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
2756
2757 $rowid = $this->id;
2758
2759 dol_syslog(get_class($this)."::delete rowid=".$rowid.", ref=".$this->ref.", thirdparty=".(empty($this->thirdparty) ? '' : $this->thirdparty->name), LOG_DEBUG);
2760
2761 // Test to avoid invoice deletion (allowed if draft)
2762 $result = $this->is_erasable();
2763
2764 if ($result <= 0) {
2765 return 0;
2766 }
2767
2768 $error = 0;
2769
2770 $this->db->begin();
2771
2772 if (!$error && !$notrigger) {
2773 // Call trigger
2774 $result = $this->call_trigger('BILL_DELETE', $user);
2775 if ($result < 0) {
2776 $error++;
2777 }
2778 // End call triggers
2779 }
2780
2781 // Removed extrafields
2782 if (!$error) {
2783 $result = $this->deleteExtraFields();
2784 if ($result < 0) {
2785 $error++;
2786 dol_syslog(get_class($this)."::delete error deleteExtraFields ".$this->error, LOG_ERR);
2787 }
2788 }
2789
2790 if (!$error) {
2791 // Delete linked object
2792 $res = $this->deleteObjectLinked();
2793 if ($res < 0) {
2794 $error++;
2795 }
2796 }
2797
2798 if (!$error) {
2799 // If invoice was converted into a discount not yet consumed, we remove discount
2800 $sql = 'DELETE FROM '.MAIN_DB_PREFIX.'societe_remise_except';
2801 $sql .= ' WHERE fk_facture_source = '.((int) $rowid);
2802 $sql .= ' AND fk_facture_line IS NULL';
2803 $resql = $this->db->query($sql);
2804
2805 // If invoice has consumed discounts
2806 $this->fetch_lines();
2807 $list_rowid_det = array();
2808 foreach ($this->lines as $key => $invoiceline) {
2809 $list_rowid_det[] = $invoiceline->id;
2810 }
2811
2812 // Consumed discounts are freed
2813 if (count($list_rowid_det)) {
2814 $sql = 'UPDATE '.MAIN_DB_PREFIX.'societe_remise_except';
2815 $sql .= ' SET fk_facture = NULL, fk_facture_line = NULL';
2816 $sql .= ' WHERE fk_facture_line IN ('.$this->db->sanitize(join(',', $list_rowid_det)).')';
2817
2818 if (!$this->db->query($sql)) {
2819 $this->error = $this->db->error()." sql=".$sql;
2820 $this->errors[] = $this->error;
2821 $this->db->rollback();
2822 return -5;
2823 }
2824 }
2825
2826 // Remove other links to the deleted invoice
2827
2828 $sql = 'UPDATE '.MAIN_DB_PREFIX.'eventorganization_conferenceorboothattendee';
2829 $sql .= ' SET fk_invoice = NULL';
2830 $sql .= ' WHERE fk_invoice = '.((int) $rowid);
2831
2832 if (!$this->db->query($sql)) {
2833 $this->error = $this->db->error()." sql=".$sql;
2834 $this->errors[] = $this->error;
2835 $this->db->rollback();
2836 return -5;
2837 }
2838
2839 $sql = 'UPDATE '.MAIN_DB_PREFIX.'element_time';
2840 $sql .= ' SET invoice_id = NULL, invoice_line_id = NULL';
2841 $sql .= ' WHERE invoice_id = '.((int) $rowid);
2842
2843 if (!$this->db->query($sql)) {
2844 $this->error = $this->db->error()." sql=".$sql;
2845 $this->errors[] = $this->error;
2846 $this->db->rollback();
2847 return -5;
2848 }
2849
2850 // If we decrease stock on invoice validation, we increase back if a warehouse id was provided
2851 if ($this->type != self::TYPE_DEPOSIT && $result >= 0 && isModEnabled('stock') && getDolGlobalString('STOCK_CALCULATE_ON_BILL') && $idwarehouse != -1) {
2852 require_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
2853 $langs->load("agenda");
2854
2855 $num = count($this->lines);
2856 for ($i = 0; $i < $num; $i++) {
2857 if ($this->lines[$i]->fk_product > 0) {
2858 $mouvP = new MouvementStock($this->db);
2859 $mouvP->origin = &$this;
2860 $mouvP->setOrigin($this->element, $this->id);
2861 // We decrease stock for product
2862 if ($this->type == self::TYPE_CREDIT_NOTE) {
2863 $result = $mouvP->livraison($user, $this->lines[$i]->fk_product, $idwarehouse, $this->lines[$i]->qty, $this->lines[$i]->subprice, $langs->trans("InvoiceDeleteDolibarr", $this->ref));
2864 } else {
2865 $result = $mouvP->reception($user, $this->lines[$i]->fk_product, $idwarehouse, $this->lines[$i]->qty, 0, $langs->trans("InvoiceDeleteDolibarr", $this->ref)); // we use 0 for price, to not change the weighted average value
2866 }
2867 }
2868 }
2869 }
2870
2871 // Invoice line extrafileds
2872 $main = MAIN_DB_PREFIX.'facturedet';
2873 $ef = $main."_extrafields";
2874 $sqlef = "DELETE FROM ".$ef." WHERE fk_object IN (SELECT rowid FROM ".$main." WHERE fk_facture = ".((int) $rowid).")";
2875 // Delete invoice line
2876 $sql = 'DELETE FROM '.MAIN_DB_PREFIX.'facturedet WHERE fk_facture = '.((int) $rowid);
2877
2878 if ($this->db->query($sqlef) && $this->db->query($sql) && $this->delete_linked_contact()) {
2879 $sql = 'DELETE FROM '.MAIN_DB_PREFIX.'facture WHERE rowid = '.((int) $rowid);
2880
2881 $resql = $this->db->query($sql);
2882 if ($resql) {
2883 // Delete record into ECM index (Note that delete is also done when deleting files with the dol_delete_dir_recursive
2884 $this->deleteEcmFiles(0); // Deleting files physically is done later with the dol_delete_dir_recursive
2885 $this->deleteEcmFiles(1); // Deleting files physically is done later with the dol_delete_dir_recursive
2886
2887 // On efface le repertoire de pdf provisoire
2888 $ref = dol_sanitizeFileName($this->ref);
2889 if ($conf->facture->dir_output && !empty($this->ref)) {
2890 $dir = $conf->facture->dir_output."/".$ref;
2891 $file = $conf->facture->dir_output."/".$ref."/".$ref.".pdf";
2892 if (file_exists($file)) { // We must delete all files before deleting directory
2893 $ret = dol_delete_preview($this);
2894
2895 if (!dol_delete_file($file, 0, 0, 0, $this)) { // For triggers
2896 $langs->load("errors");
2897 $this->error = $langs->trans("ErrorFailToDeleteFile", $file);
2898 $this->errors[] = $this->error;
2899 $this->db->rollback();
2900 return 0;
2901 }
2902 }
2903 if (file_exists($dir)) {
2904 if (!dol_delete_dir_recursive($dir)) { // For remove dir and meta
2905 $langs->load("errors");
2906 $this->error = $langs->trans("ErrorFailToDeleteDir", $dir);
2907 $this->errors[] = $this->error;
2908 $this->db->rollback();
2909 return 0;
2910 }
2911 }
2912 }
2913
2914 $this->db->commit();
2915 return 1;
2916 } else {
2917 $this->error = $this->db->lasterror()." sql=".$sql;
2918 $this->errors[] = $this->error;
2919 $this->db->rollback();
2920 return -6;
2921 }
2922 } else {
2923 $this->error = $this->db->lasterror()." sql=".$sql;
2924 $this->errors[] = $this->error;
2925 $this->db->rollback();
2926 return -4;
2927 }
2928 } else {
2929 $this->db->rollback();
2930 return -2;
2931 }
2932 }
2933
2934 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2946 public function set_paid($user, $close_code = '', $close_note = '')
2947 {
2948 // phpcs:enable
2949 dol_syslog(get_class($this)."::set_paid is deprecated, use setPaid instead", LOG_NOTICE);
2950 return $this->setPaid($user, $close_code, $close_note);
2951 }
2952
2963 public function setPaid($user, $close_code = '', $close_note = '')
2964 {
2965 $error = 0;
2966
2967 if ($this->paye != 1) {
2968 $this->db->begin();
2969
2970 $now = dol_now();
2971
2972 dol_syslog(get_class($this)."::setPaid rowid=".((int) $this->id), LOG_DEBUG);
2973
2974 $sql = 'UPDATE '.MAIN_DB_PREFIX.'facture SET';
2975 $sql .= ' fk_statut='.self::STATUS_CLOSED;
2976 if (!$close_code) {
2977 $sql .= ', paye=1';
2978 }
2979 if ($close_code) {
2980 $sql .= ", close_code='".$this->db->escape($close_code)."'";
2981 }
2982 if ($close_note) {
2983 $sql .= ", close_note='".$this->db->escape($close_note)."'";
2984 }
2985 $sql .= ', fk_user_closing = '.((int) $user->id);
2986 $sql .= ", date_closing = '".$this->db->idate($now)."'";
2987 $sql .= " WHERE rowid = ".((int) $this->id);
2988
2989 $resql = $this->db->query($sql);
2990 if ($resql) {
2991 // Call trigger
2992 $result = $this->call_trigger('BILL_PAYED', $user);
2993 if ($result < 0) {
2994 $error++;
2995 }
2996 // End call triggers
2997 } else {
2998 $error++;
2999 $this->error = $this->db->lasterror();
3000 }
3001
3002 if (!$error) {
3003 $this->db->commit();
3004 return 1;
3005 } else {
3006 $this->db->rollback();
3007 return -1;
3008 }
3009 } else {
3010 return 0;
3011 }
3012 }
3013
3014
3015 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3026 public function set_unpaid($user)
3027 {
3028 // phpcs:enable
3029 dol_syslog(get_class($this)."::set_unpaid is deprecated, use setUnpaid instead", LOG_NOTICE);
3030 return $this->setUnpaid($user);
3031 }
3032
3041 public function setUnpaid($user)
3042 {
3043 $error = 0;
3044
3045 $this->db->begin();
3046
3047 $sql = 'UPDATE '.MAIN_DB_PREFIX.'facture';
3048 $sql .= ' SET paye=0, fk_statut='.self::STATUS_VALIDATED.', close_code=null, close_note=null,';
3049 $sql .= ' date_closing=null,';
3050 $sql .= ' fk_user_closing=null';
3051 $sql .= " WHERE rowid = ".((int) $this->id);
3052
3053 dol_syslog(get_class($this)."::setUnpaid", LOG_DEBUG);
3054 $resql = $this->db->query($sql);
3055 if ($resql) {
3056 // Call trigger
3057 $result = $this->call_trigger('BILL_UNPAYED', $user);
3058 if ($result < 0) {
3059 $error++;
3060 }
3061 // End call triggers
3062 } else {
3063 $error++;
3064 $this->error = $this->db->error();
3065 dol_print_error($this->db);
3066 }
3067
3068 if (!$error) {
3069 $this->db->commit();
3070 return 1;
3071 } else {
3072 $this->db->rollback();
3073 return -1;
3074 }
3075 }
3076
3077
3078 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3091 public function set_canceled($user, $close_code = '', $close_note = '')
3092 {
3093 // phpcs:enable
3094 dol_syslog(get_class($this)."::set_canceled is deprecated, use setCanceled instead", LOG_NOTICE);
3095 return $this->setCanceled($user, $close_code, $close_note);
3096 }
3097
3108 public function setCanceled($user, $close_code = '', $close_note = '')
3109 {
3110 dol_syslog(get_class($this)."::setCanceled rowid=".((int) $this->id), LOG_DEBUG);
3111
3112 $this->db->begin();
3113 $now = dol_now();
3114
3115 $sql = 'UPDATE '.MAIN_DB_PREFIX.'facture SET';
3116 $sql .= ' fk_statut='.self::STATUS_ABANDONED;
3117 if ($close_code) {
3118 $sql .= ", close_code='".$this->db->escape($close_code)."'";
3119 }
3120 if ($close_note) {
3121 $sql .= ", close_note='".$this->db->escape($close_note)."'";
3122 }
3123 $sql .= ', fk_user_closing = '.((int) $user->id);
3124 $sql .= ", date_closing = '".$this->db->idate($now)."'";
3125 $sql .= " WHERE rowid = ".((int) $this->id);
3126
3127 $resql = $this->db->query($sql);
3128 if ($resql) {
3129 // Bound discounts are deducted from the invoice
3130 // as they have not been used since the invoice is abandoned.
3131 $sql = 'UPDATE '.MAIN_DB_PREFIX.'societe_remise_except';
3132 $sql .= ' SET fk_facture = NULL';
3133 $sql .= ' WHERE fk_facture = '.((int) $this->id);
3134
3135 $resql = $this->db->query($sql);
3136 if ($resql) {
3137 // Call trigger
3138 $result = $this->call_trigger('BILL_CANCEL', $user);
3139 if ($result < 0) {
3140 $this->db->rollback();
3141 return -1;
3142 }
3143 // End call triggers
3144
3145 $this->db->commit();
3146 return 1;
3147 } else {
3148 $this->error = $this->db->error()." sql=".$sql;
3149 $this->db->rollback();
3150 return -1;
3151 }
3152 } else {
3153 $this->error = $this->db->error()." sql=".$sql;
3154 $this->db->rollback();
3155 return -2;
3156 }
3157 }
3158
3170 public function validate($user, $force_number = '', $idwarehouse = 0, $notrigger = 0, $batch_rule = 0)
3171 {
3172 global $conf, $langs, $mysoc;
3173 require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
3174
3175 $productStatic = null;
3176 $warehouseStatic = null;
3177 if ($batch_rule > 0) {
3178 require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
3179 require_once DOL_DOCUMENT_ROOT.'/product/class/productbatch.class.php';
3180 require_once DOL_DOCUMENT_ROOT.'/product/stock/class/entrepot.class.php';
3181 $productStatic = new Product($this->db);
3182 $warehouseStatic = new Entrepot($this->db);
3183 $productbatch = new Productbatch($this->db);
3184 }
3185
3186 $now = dol_now();
3187
3188 $error = 0;
3189 dol_syslog(get_class($this).'::validate user='.$user->id.', force_number='.$force_number.', idwarehouse='.$idwarehouse);
3190
3191 // Force to have object complete for checks
3192 $this->fetch_thirdparty();
3193 $this->fetch_lines();
3194
3195 // Check parameters
3196 if ($this->status != self::STATUS_DRAFT) {
3197 dol_syslog(get_class($this)."::validate Current status is not draft. operation canceled.", LOG_WARNING);
3198 return 0;
3199 }
3200 if (count($this->lines) <= 0) {
3201 $langs->load("errors");
3202 $this->error = $langs->trans("ErrorObjectMustHaveLinesToBeValidated", $this->ref);
3203 return -1;
3204 }
3205 if ((!getDolGlobalString('MAIN_USE_ADVANCED_PERMS') && !$user->hasRight('facture', 'creer'))
3206 || (getDolGlobalString('MAIN_USE_ADVANCED_PERMS') && !$user->hasRight('facture', 'invoice_advance', 'validate'))) {
3207 $this->error = 'Permission denied';
3208 dol_syslog(get_class($this)."::validate ".$this->error.' MAIN_USE_ADVANCED_PERMS=' . getDolGlobalString('MAIN_USE_ADVANCED_PERMS'), LOG_ERR);
3209 return -1;
3210 }
3211 if ((preg_match('/^[\‍(]?PROV/i', $this->ref) || empty($this->ref)) && // empty should not happened, but when it occurs, the test save life
3212 getDolGlobalString('FAC_FORCE_DATE_VALIDATION') // If option enabled, we force invoice date
3213 ) {
3214 $this->date = dol_now();
3215 $this->date_lim_reglement = $this->calculate_date_lim_reglement();
3216 }
3217 if (getDolGlobalString('INVOICE_CHECK_POSTERIOR_DATE')) {
3218 $last_of_type = $this->willBeLastOfSameType(true);
3219 if (!$last_of_type[0]) {
3220 $this->error = $langs->transnoentities("ErrorInvoiceIsNotLastOfSameType", $this->ref, dol_print_date($this->date, 'day'), dol_print_date($last_of_type[1], 'day'));
3221 return -1;
3222 }
3223 }
3224
3225 // Check for mandatory fields in thirdparty (defined into setup)
3226 if (!empty($this->thirdparty) && is_object($this->thirdparty)) {
3227 $array_to_check = array('IDPROF1', 'IDPROF2', 'IDPROF3', 'IDPROF4', 'IDPROF5', 'IDPROF6', 'EMAIL', 'ACCOUNTANCY_CODE_CUSTOMER');
3228 foreach ($array_to_check as $key) {
3229 $keymin = strtolower($key);
3230 if (!property_exists($this->thirdparty, $keymin)) {
3231 continue;
3232 }
3233 $vallabel = $this->thirdparty->$keymin;
3234
3235 $i = (int) preg_replace('/[^0-9]/', '', $key);
3236 if ($i > 0) {
3237 if ($this->thirdparty->isACompany()) {
3238 // Check for mandatory prof id (but only if country is other than ours)
3239 if ($mysoc->country_id > 0 && $this->thirdparty->country_id == $mysoc->country_id) {
3240 $idprof_mandatory = 'SOCIETE_'.$key.'_INVOICE_MANDATORY';
3241 if (!$vallabel && !empty($conf->global->$idprof_mandatory)) {
3242 $langs->load("errors");
3243 $this->error = $langs->trans('ErrorProdIdIsMandatory', $langs->transcountry('ProfId'.$i, $this->thirdparty->country_code)).' ('.$langs->trans("ForbiddenBySetupRules").') ['.$langs->trans('Company').' : '.$this->thirdparty->name.']';
3244 dol_syslog(__METHOD__.' '.$this->error, LOG_ERR);
3245 return -1;
3246 }
3247 }
3248 }
3249 } else {
3250 if ($key == 'EMAIL') {
3251 // Check for mandatory
3252 if (getDolGlobalString('SOCIETE_EMAIL_INVOICE_MANDATORY') && !isValidEMail($this->thirdparty->email)) {
3253 $langs->load("errors");
3254 $this->error = $langs->trans("ErrorBadEMail", $this->thirdparty->email).' ('.$langs->trans("ForbiddenBySetupRules").') ['.$langs->trans('Company').' : '.$this->thirdparty->name.']';
3255 dol_syslog(__METHOD__.' '.$this->error, LOG_ERR);
3256 return -1;
3257 }
3258 }
3259 if ($key == 'ACCOUNTANCY_CODE_CUSTOMER') {
3260 // Check for mandatory
3261 if (getDolGlobalString('SOCIETE_ACCOUNTANCY_CODE_CUSTOMER_INVOICE_MANDATORY') && empty($this->thirdparty->code_compta)) {
3262 $langs->load("errors");
3263 $this->error = $langs->trans("ErrorAccountancyCodeCustomerIsMandatory", $this->thirdparty->name).' ('.$langs->trans("ForbiddenBySetupRules").')';
3264 dol_syslog(__METHOD__.' '.$this->error, LOG_ERR);
3265 return -1;
3266 }
3267 }
3268 }
3269 }
3270 }
3271
3272 // Check for mandatory fields in $this
3273 $array_to_check = array('REF_CLIENT'=>'RefCustomer');
3274 foreach ($array_to_check as $key => $val) {
3275 $keymin = strtolower($key);
3276 $vallabel = $this->$keymin;
3277
3278 // Check for mandatory
3279 $keymandatory = 'INVOICE_'.$key.'_MANDATORY_FOR_VALIDATION';
3280 if (!$vallabel && getDolGlobalString($keymandatory)) {
3281 $langs->load("errors");
3282 $error++;
3283 setEventMessages($langs->trans("ErrorFieldRequired", $langs->transnoentitiesnoconv($val)), null, 'errors');
3284 }
3285 }
3286
3287 $this->db->begin();
3288
3289 // Check parameters
3290 if ($this->type == self::TYPE_REPLACEMENT) { // if this is a replacement invoice
3291 // Check that source invoice is known
3292 if ($this->fk_facture_source <= 0) {
3293 $this->error = $langs->trans("ErrorFieldRequired", $langs->transnoentitiesnoconv("InvoiceReplacement"));
3294 $this->db->rollback();
3295 return -10;
3296 }
3297
3298 // Load source invoice that has been replaced
3299 $facreplaced = new Facture($this->db);
3300 $result = $facreplaced->fetch($this->fk_facture_source);
3301 if ($result <= 0) {
3302 $this->error = $langs->trans("ErrorBadInvoice");
3303 $this->db->rollback();
3304 return -11;
3305 }
3306
3307 // Check that source invoice not already replaced by another one.
3308 $idreplacement = $facreplaced->getIdReplacingInvoice('validated');
3309 if ($idreplacement && $idreplacement != $this->id) {
3310 $facreplacement = new Facture($this->db);
3311 $facreplacement->fetch($idreplacement);
3312 $this->error = $langs->trans("ErrorInvoiceAlreadyReplaced", $facreplaced->ref, $facreplacement->ref);
3313 $this->db->rollback();
3314 return -12;
3315 }
3316
3317 $result = $facreplaced->setCanceled($user, self::CLOSECODE_REPLACED, '');
3318 if ($result < 0) {
3319 $this->error = $facreplaced->error;
3320 $this->db->rollback();
3321 return -13;
3322 }
3323 }
3324
3325 // Define new ref
3326 if ($force_number) {
3327 $num = $force_number;
3328 } elseif (preg_match('/^[\‍(]?PROV/i', $this->ref) || empty($this->ref)) { // empty should not happened, but when it occurs, the test save life
3329 if (getDolGlobalString('FAC_FORCE_DATE_VALIDATION')) { // If option enabled, we force invoice date
3330 $this->date = dol_now();
3331 $this->date_lim_reglement = $this->calculate_date_lim_reglement();
3332 }
3333 $num = $this->getNextNumRef($this->thirdparty);
3334 } else {
3335 $num = $this->ref;
3336 }
3337
3338 $this->newref = dol_sanitizeFileName($num);
3339
3340 if ($num) {
3341 $this->update_price(1);
3342
3343 // Validate
3344 $sql = 'UPDATE '.MAIN_DB_PREFIX.'facture';
3345 $sql .= " SET ref = '".$this->db->escape($num)."', fk_statut = ".self::STATUS_VALIDATED.", fk_user_valid = ".($user->id > 0 ? $user->id : "null").", date_valid = '".$this->db->idate($now)."'";
3346 if (getDolGlobalString('FAC_FORCE_DATE_VALIDATION')) { // If option enabled, we force invoice date
3347 $sql .= ", datef='".$this->db->idate($this->date)."'";
3348 $sql .= ", date_lim_reglement='".$this->db->idate($this->date_lim_reglement)."'";
3349 }
3350 $sql .= " WHERE rowid = ".((int) $this->id);
3351
3352 dol_syslog(get_class($this)."::validate", LOG_DEBUG);
3353 $resql = $this->db->query($sql);
3354 if (!$resql) {
3355 dol_print_error($this->db);
3356 $error++;
3357 }
3358
3359 // We check if the invoice was provisional
3360 if (!$error && (preg_match('/^[\‍(]?PROV/i', $this->ref))) {
3361 // La verif qu'une remise n'est pas utilisee 2 fois est faite au moment de l'insertion de ligne
3362 }
3363
3364 if (!$error) {
3365 // Define third party as a customer
3366 $result = $this->thirdparty->setAsCustomer();
3367
3368 // If active we decrement the main product and its components at invoice validation
3369 if ($this->type != self::TYPE_DEPOSIT && $result >= 0 && isModEnabled('stock') && getDolGlobalString('STOCK_CALCULATE_ON_BILL') && $idwarehouse > 0) {
3370 require_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
3371 $langs->load("agenda");
3372
3373 // Loop on each line
3374 $cpt = count($this->lines);
3375 for ($i = 0; $i < $cpt; $i++) {
3376 if ($this->lines[$i]->fk_product > 0) {
3377 $mouvP = new MouvementStock($this->db);
3378 $mouvP->origin = &$this;
3379 $mouvP->setOrigin($this->element, $this->id);
3380
3381 // TODO If warehouseid has been set into invoice line, we should use this value in priority
3382 // $idwarehouse = $this->lines[$i]->fk_warehouse;
3383
3384 // We decrease stock for product
3385 if ($this->type == self::TYPE_CREDIT_NOTE) {
3386 $result = $mouvP->reception($user, $this->lines[$i]->fk_product, $idwarehouse, $this->lines[$i]->qty, 0, $langs->trans("InvoiceValidatedInDolibarr", $num));
3387 if ($result < 0) {
3388 $error++;
3389 $this->error = $mouvP->error;
3390 }
3391 } else {
3392 $is_batch_line = false;
3393 if ($batch_rule > 0) {
3394 $productStatic->fetch($this->lines[$i]->fk_product);
3395 if ($productStatic->hasbatch()) {
3396 $is_batch_line = true;
3397 $product_qty_remain = $this->lines[$i]->qty;
3398
3399 $sortfield = null;
3400 $sortorder = null;
3401 // find all batch order by sellby (DLC) and eatby dates (DLUO) first
3403 $sortfield = 'pl.sellby,pl.eatby,pb.qty,pl.rowid';
3404 $sortorder = 'ASC,ASC,ASC,ASC';
3405 }
3406
3407 $resBatchList = $productbatch->findAllForProduct($productStatic->id, $idwarehouse, (getDolGlobalInt('STOCK_ALLOW_NEGATIVE_TRANSFER') ? null : 0), $sortfield, $sortorder);
3408 if (!is_array($resBatchList)) {
3409 $error++;
3410 $this->error = $this->db->lasterror();
3411 }
3412
3413 if (!$error) {
3414 $batchList = $resBatchList;
3415 if (empty($batchList)) {
3416 $error++;
3417 $langs->load('errors');
3418 $warehouseStatic->fetch($idwarehouse);
3419 $this->error = $langs->trans('ErrorBatchNoFoundForProductInWarehouse', $productStatic->label, $warehouseStatic->ref);
3420 dol_syslog(__METHOD__.' Error: '.$langs->transnoentitiesnoconv('ErrorBatchNoFoundForProductInWarehouse', $productStatic->label, $warehouseStatic->ref), LOG_ERR);
3421 }
3422
3423 foreach ($batchList as $batch) {
3424 if ($batch->qty <= 0) {
3425 continue; // try to decrement only batches have positive quantity first
3426 }
3427
3428 // enough quantity in this batch
3429 if ($batch->qty >= $product_qty_remain) {
3430 $product_batch_qty = $product_qty_remain;
3431 } else {
3432 // not enough (take all in batch)
3433 $product_batch_qty = $batch->qty;
3434 }
3435 $result = $mouvP->livraison($user, $productStatic->id, $idwarehouse, $product_batch_qty, $this->lines[$i]->subprice, $langs->trans('InvoiceValidatedInDolibarr', $num), '', '', '', $batch->batch);
3436 if ($result < 0) {
3437 $error++;
3438 $this->error = $mouvP->error;
3439 $this->errors = $mouvP->errors;
3440 break;
3441 }
3442
3443 $product_qty_remain -= $product_batch_qty;
3444 // all product quantity was decremented
3445 if ($product_qty_remain <= 0) {
3446 break;
3447 }
3448 }
3449
3450 if (!$error && $product_qty_remain > 0) {
3451 if (getDolGlobalInt('STOCK_ALLOW_NEGATIVE_TRANSFER')) {
3452 // take in the first batch
3453 $batch = $batchList[0];
3454 $result = $mouvP->livraison($user, $productStatic->id, $idwarehouse, $product_qty_remain, $this->lines[$i]->subprice, $langs->trans('InvoiceValidatedInDolibarr', $num), '', '', '', $batch->batch);
3455 if ($result < 0) {
3456 $error++;
3457 $this->error = $mouvP->error;
3458 $this->errors = $mouvP->errors;
3459 }
3460 } else {
3461 $error++;
3462 $langs->load('errors');
3463 $warehouseStatic->fetch($idwarehouse);
3464 $this->error = $langs->trans('ErrorBatchNoFoundEnoughQuantityForProductInWarehouse', $productStatic->label, $warehouseStatic->ref);
3465 dol_syslog(__METHOD__.' Error: '.$langs->transnoentitiesnoconv('ErrorBatchNoFoundEnoughQuantityForProductInWarehouse', $productStatic->label, $warehouseStatic->ref), LOG_ERR);
3466 }
3467 }
3468 }
3469 }
3470 }
3471
3472 if (!$is_batch_line) {
3473 $result = $mouvP->livraison($user, $this->lines[$i]->fk_product, $idwarehouse, $this->lines[$i]->qty, $this->lines[$i]->subprice, $langs->trans("InvoiceValidatedInDolibarr", $num));
3474 if ($result < 0) {
3475 $error++;
3476 $this->error = $mouvP->error;
3477 $this->errors = $mouvP->errors;
3478 }
3479 }
3480 }
3481 }
3482 }
3483 }
3484 }
3485
3486 /*
3487 * Set situation_final to 0 if is a credit note and the invoice source is a invoice situation (case when invoice situation is at 100%)
3488 * So we can continue to create new invoice situation
3489 */
3490 if (!$error && $this->type == self::TYPE_CREDIT_NOTE && $this->fk_facture_source > 0) {
3491 $invoice_situation = new Facture($this->db);
3492 $result = $invoice_situation->fetch($this->fk_facture_source);
3493 if ($result > 0 && $invoice_situation->type == self::TYPE_SITUATION && $invoice_situation->situation_final == 1) {
3494 $invoice_situation->situation_final = 0;
3495 // Disable triggers because module can force situation_final to 1 by triggers (ex: SubTotal)
3496 $result = $invoice_situation->setFinal($user, 1);
3497 }
3498 if ($result < 0) {
3499 $this->error = $invoice_situation->error;
3500 $this->errors = $invoice_situation->errors;
3501 $error++;
3502 }
3503 }
3504
3505 // Trigger calls
3506 if (!$error && !$notrigger) {
3507 // Call trigger
3508 $result = $this->call_trigger('BILL_VALIDATE', $user);
3509 if ($result < 0) {
3510 $error++;
3511 }
3512 // End call triggers
3513 }
3514
3515 if (!$error) {
3516 $this->oldref = $this->ref;
3517
3518 // Rename directory if dir was a temporary ref
3519 if (preg_match('/^[\‍(]?PROV/i', $this->ref)) {
3520 // Now we rename also files into index
3521 $sql = 'UPDATE '.MAIN_DB_PREFIX."ecm_files set filename = CONCAT('".$this->db->escape($this->newref)."', SUBSTR(filename, ".(strlen($this->ref) + 1).")), filepath = 'facture/".$this->db->escape($this->newref)."'";
3522 $sql .= " WHERE filename LIKE '".$this->db->escape($this->ref)."%' AND filepath = 'facture/".$this->db->escape($this->ref)."' and entity = ".$conf->entity;
3523 $resql = $this->db->query($sql);
3524 if (!$resql) {
3525 $error++;
3526 $this->error = $this->db->lasterror();
3527 }
3528 $sql = 'UPDATE '.MAIN_DB_PREFIX."ecm_files set filepath = 'facture/".$this->db->escape($this->newref)."'";
3529 $sql .= " WHERE filepath = 'facture/".$this->db->escape($this->ref)."' and entity = ".$conf->entity;
3530 $resql = $this->db->query($sql);
3531 if (!$resql) {
3532 $error++;
3533 $this->error = $this->db->lasterror();
3534 }
3535
3536 // We rename directory ($this->ref = old ref, $num = new ref) in order not to lose the attachments
3537 $oldref = dol_sanitizeFileName($this->ref);
3538 $newref = dol_sanitizeFileName($num);
3539 $dirsource = $conf->facture->dir_output.'/'.$oldref;
3540 $dirdest = $conf->facture->dir_output.'/'.$newref;
3541 if (!$error && file_exists($dirsource)) {
3542 dol_syslog(get_class($this)."::validate rename dir ".$dirsource." into ".$dirdest);
3543
3544 if (@rename($dirsource, $dirdest)) {
3545 dol_syslog("Rename ok");
3546 // Rename docs starting with $oldref with $newref
3547 $listoffiles = dol_dir_list($conf->facture->dir_output.'/'.$newref, 'files', 1, '^'.preg_quote($oldref, '/'));
3548 foreach ($listoffiles as $fileentry) {
3549 $dirsource = $fileentry['name'];
3550 $dirdest = preg_replace('/^'.preg_quote($oldref, '/').'/', $newref, $dirsource);
3551 $dirsource = $fileentry['path'].'/'.$dirsource;
3552 $dirdest = $fileentry['path'].'/'.$dirdest;
3553 @rename($dirsource, $dirdest);
3554 }
3555 }
3556 }
3557 }
3558 }
3559
3560 if (!$error && !$this->is_last_in_cycle()) {
3561 if (!$this->updatePriceNextInvoice($langs)) {
3562 $error++;
3563 }
3564 }
3565
3566 // Set new ref and define current status
3567 if (!$error) {
3568 $this->ref = $num;
3569 $this->statut = self::STATUS_VALIDATED; // deprecated
3571 $this->date_validation = $now;
3572 $i = 0;
3573
3574 if (getDolGlobalString('INVOICE_USE_SITUATION')) {
3575 $final = true;
3576 $nboflines = count($this->lines);
3577 while (($i < $nboflines) && $final) {
3578 $final = ($this->lines[$i]->situation_percent == 100);
3579 $i++;
3580 }
3581
3582 if (empty($final)) {
3583 $this->situation_final = 0;
3584 } else {
3585 $this->situation_final = 1;
3586 }
3587
3588 $this->setFinal($user);
3589 }
3590 }
3591 } else {
3592 $error++;
3593 }
3594
3595 if (!$error) {
3596 $this->db->commit();
3597 return 1;
3598 } else {
3599 $this->db->rollback();
3600 return -1;
3601 }
3602 }
3603
3610 public function updatePriceNextInvoice(&$langs)
3611 {
3612 foreach ($this->tab_next_situation_invoice as $next_invoice) {
3613 $is_last = $next_invoice->is_last_in_cycle();
3614
3615 if ($next_invoice->status == self::STATUS_DRAFT && $is_last != 1) {
3616 $this->error = $langs->trans('updatePriceNextInvoiceErrorUpdateline', $next_invoice->ref);
3617 return false;
3618 }
3619
3620 foreach ($next_invoice->lines as $line) {
3621 $result = $next_invoice->updateline(
3622 $line->id,
3623 $line->desc,
3624 $line->subprice,
3625 $line->qty,
3626 $line->remise_percent,
3627 $line->date_start,
3628 $line->date_end,
3629 $line->tva_tx,
3630 $line->localtax1_tx,
3631 $line->localtax2_tx,
3632 'HT',
3633 $line->info_bits,
3634 $line->product_type,
3635 $line->fk_parent_line,
3636 0,
3637 $line->fk_fournprice,
3638 $line->pa_ht,
3639 $line->label,
3640 $line->special_code,
3641 $line->array_options,
3642 $line->situation_percent,
3643 $line->fk_unit
3644 );
3645
3646 if ($result < 0) {
3647 $this->error = $langs->trans('updatePriceNextInvoiceErrorUpdateline', $next_invoice->ref);
3648 return false;
3649 }
3650 }
3651
3652 break; // Only the next invoice and not each next invoice
3653 }
3654
3655 return true;
3656 }
3657
3665 public function setDraft($user, $idwarehouse = -1)
3666 {
3667 // phpcs:enable
3668 global $conf, $langs;
3669
3670 $error = 0;
3671
3672 if ($this->status == self::STATUS_DRAFT) {
3673 dol_syslog(__METHOD__." already draft status", LOG_WARNING);
3674 return 0;
3675 }
3676
3677 dol_syslog(__METHOD__, LOG_DEBUG);
3678
3679 $this->db->begin();
3680
3681 $sql = "UPDATE ".MAIN_DB_PREFIX."facture";
3682 $sql .= " SET fk_statut = ".self::STATUS_DRAFT;
3683 $sql .= " WHERE rowid = ".((int) $this->id);
3684
3685 $result = $this->db->query($sql);
3686 if ($result) {
3687 if (!$error) {
3688 $this->oldcopy = clone $this;
3689 }
3690
3691 // If we decrease stock on invoice validation, we increase back
3692 if ($this->type != self::TYPE_DEPOSIT && $result >= 0 && isModEnabled('stock') && getDolGlobalString('STOCK_CALCULATE_ON_BILL')) {
3693 require_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
3694 $langs->load("agenda");
3695
3696 $num = count($this->lines);
3697 for ($i = 0; $i < $num; $i++) {
3698 if ($this->lines[$i]->fk_product > 0) {
3699 $mouvP = new MouvementStock($this->db);
3700 $mouvP->origin = &$this;
3701 $mouvP->setOrigin($this->element, $this->id);
3702 // We decrease stock for product
3703 if ($this->type == self::TYPE_CREDIT_NOTE) {
3704 $result = $mouvP->livraison($user, $this->lines[$i]->fk_product, $idwarehouse, $this->lines[$i]->qty, $this->lines[$i]->subprice, $langs->trans("InvoiceBackToDraftInDolibarr", $this->ref));
3705 } else {
3706 $result = $mouvP->reception($user, $this->lines[$i]->fk_product, $idwarehouse, $this->lines[$i]->qty, 0, $langs->trans("InvoiceBackToDraftInDolibarr", $this->ref)); // we use 0 for price, to not change the weighted average value
3707 }
3708 }
3709 }
3710 }
3711
3712 if ($error == 0) {
3713 $old_statut = $this->status;
3714 $this->statut = self::STATUS_DRAFT; // deprecated
3715 $this->status = self::STATUS_DRAFT;
3716
3717 // Call trigger
3718 $result = $this->call_trigger('BILL_UNVALIDATE', $user);
3719 if ($result < 0) {
3720 $error++;
3721 $this->statut = $old_statut; // deprecated
3722 $this->status = $old_statut;
3723 }
3724 // End call triggers
3725 } else {
3726 $this->db->rollback();
3727 return -1;
3728 }
3729
3730 if ($error == 0) {
3731 $this->db->commit();
3732 return 1;
3733 } else {
3734 $this->db->rollback();
3735 return -1;
3736 }
3737 } else {
3738 $this->error = $this->db->error();
3739 $this->db->rollback();
3740 return -1;
3741 }
3742 }
3743
3744
3786 public function addline(
3787 $desc,
3788 $pu_ht,
3789 $qty,
3790 $txtva,
3791 $txlocaltax1 = 0,
3792 $txlocaltax2 = 0,
3793 $fk_product = 0,
3794 $remise_percent = 0,
3795 $date_start = '',
3796 $date_end = '',
3797 $ventil = 0,
3798 $info_bits = 0,
3799 $fk_remise_except = 0,
3800 $price_base_type = 'HT',
3801 $pu_ttc = 0,
3802 $type = 0,
3803 $rang = -1,
3804 $special_code = 0,
3805 $origin = '',
3806 $origin_id = 0,
3807 $fk_parent_line = 0,
3808 $fk_fournprice = null,
3809 $pa_ht = 0,
3810 $label = '',
3811 $array_options = array(),
3812 $situation_percent = 100,
3813 $fk_prev_id = 0,
3814 $fk_unit = null,
3815 $pu_ht_devise = 0,
3816 $ref_ext = '',
3817 $noupdateafterinsertline = 0
3818 ) {
3819 // Deprecation warning
3820 if ($label) {
3821 dol_syslog(__METHOD__.": using line label is deprecated", LOG_WARNING);
3822 //var_dump(debug_backtrace(false));exit;
3823 }
3824
3825 global $mysoc, $conf, $langs;
3826
3827 dol_syslog(get_class($this)."::addline id=$this->id, pu_ht=$pu_ht, qty=$qty, txtva=$txtva, txlocaltax1=$txlocaltax1, txlocaltax2=$txlocaltax2, fk_product=$fk_product, remise_percent=$remise_percent, date_start=$date_start, date_end=$date_end, ventil=$ventil, info_bits=$info_bits, fk_remise_except=$fk_remise_except, price_base_type=$price_base_type, pu_ttc=$pu_ttc, type=$type, fk_unit=$fk_unit, desc=".dol_trunc($desc, 25), LOG_DEBUG);
3828
3829 if ($this->status == self::STATUS_DRAFT) {
3830 include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
3831
3832 // Clean parameters
3833 if (empty($remise_percent)) {
3834 $remise_percent = 0;
3835 }
3836 if (empty($qty)) {
3837 $qty = 0;
3838 }
3839 if (empty($info_bits)) {
3840 $info_bits = 0;
3841 }
3842 if (empty($rang)) {
3843 $rang = 0;
3844 }
3845 if (empty($ventil)) {
3846 $ventil = 0;
3847 }
3848 if (empty($txtva)) {
3849 $txtva = 0;
3850 }
3851 if (empty($txlocaltax1)) {
3852 $txlocaltax1 = 0;
3853 }
3854 if (empty($txlocaltax2)) {
3855 $txlocaltax2 = 0;
3856 }
3857 if (empty($fk_parent_line) || $fk_parent_line < 0) {
3858 $fk_parent_line = 0;
3859 }
3860 if (empty($fk_prev_id)) {
3861 $fk_prev_id = 'null';
3862 }
3863 if (!isset($situation_percent) || $situation_percent > 100 || (string) $situation_percent == '') {
3864 $situation_percent = 100;
3865 }
3866 if (empty($ref_ext)) {
3867 $ref_ext = '';
3868 }
3869
3870 $remise_percent = price2num($remise_percent);
3871 $qty = price2num($qty);
3872 $pu_ht = price2num($pu_ht);
3873 $pu_ht_devise = price2num($pu_ht_devise);
3874 $pu_ttc = price2num($pu_ttc);
3875 $pa_ht = price2num($pa_ht);
3876 if (!preg_match('/\‍((.*)\‍)/', $txtva)) {
3877 $txtva = price2num($txtva); // $txtva can have format '5.0(XXX)' or '5'
3878 }
3879 $txlocaltax1 = price2num($txlocaltax1);
3880 $txlocaltax2 = price2num($txlocaltax2);
3881
3882 if ($price_base_type == 'HT') {
3883 $pu = $pu_ht;
3884 } else {
3885 $pu = $pu_ttc;
3886 }
3887
3888 // Check parameters
3889 if ($type < 0) {
3890 return -1;
3891 }
3892
3893 if ($date_start && $date_end && $date_start > $date_end) {
3894 $langs->load("errors");
3895 $this->error = $langs->trans('ErrorStartDateGreaterEnd');
3896 return -1;
3897 }
3898
3899 $this->db->begin();
3900
3901 $product_type = $type;
3902 if (!empty($fk_product) && $fk_product > 0) {
3903 $product = new Product($this->db);
3904 $result = $product->fetch($fk_product);
3905 $product_type = $product->type;
3906
3907 if (getDolGlobalString('STOCK_MUST_BE_ENOUGH_FOR_INVOICE') && $product_type == 0 && $product->stock_reel < $qty) {
3908 $langs->load("errors");
3909 $this->error = $langs->trans('ErrorStockIsNotEnoughToAddProductOnInvoice', $product->ref);
3910 $this->db->rollback();
3911 return -3;
3912 }
3913 }
3914
3915 $localtaxes_type = getLocalTaxesFromRate($txtva, 0, $this->thirdparty, $mysoc);
3916
3917 // Clean vat code
3918 $reg = array();
3919 $vat_src_code = '';
3920 if (preg_match('/\‍((.*)\‍)/', $txtva, $reg)) {
3921 $vat_src_code = $reg[1];
3922 $txtva = preg_replace('/\s*\‍(.*\‍)/', '', $txtva); // Remove code into vatrate.
3923 }
3924
3925 // Calcul du total TTC et de la TVA pour la ligne a partir de
3926 // qty, pu, remise_percent et txtva
3927 // TRES IMPORTANT: C'est au moment de l'insertion ligne qu'on doit stocker
3928 // la part ht, tva et ttc, et ce au niveau de la ligne qui a son propre taux tva.
3929
3930 $tabprice = calcul_price_total($qty, $pu, $remise_percent, $txtva, $txlocaltax1, $txlocaltax2, 0, $price_base_type, $info_bits, $product_type, $mysoc, $localtaxes_type, $situation_percent, $this->multicurrency_tx, $pu_ht_devise);
3931
3932 $total_ht = $tabprice[0];
3933 $total_tva = $tabprice[1];
3934 $total_ttc = $tabprice[2];
3935 $total_localtax1 = $tabprice[9];
3936 $total_localtax2 = $tabprice[10];
3937 $pu_ht = $tabprice[3];
3938
3939 // MultiCurrency
3940 $multicurrency_total_ht = $tabprice[16];
3941 $multicurrency_total_tva = $tabprice[17];
3942 $multicurrency_total_ttc = $tabprice[18];
3943 $pu_ht_devise = $tabprice[19];
3944
3945 // Rank to use
3946 $ranktouse = $rang;
3947 if ($ranktouse == -1) {
3948 $rangmax = $this->line_max($fk_parent_line);
3949 $ranktouse = $rangmax + 1;
3950 }
3951
3952 // Insert line
3953 $this->line = new FactureLigne($this->db);
3954
3955 $this->line->context = $this->context;
3956
3957 $this->line->fk_facture = $this->id;
3958 $this->line->label = $label; // deprecated
3959 $this->line->desc = $desc;
3960 $this->line->ref_ext = $ref_ext;
3961
3962 $this->line->qty = ($this->type == self::TYPE_CREDIT_NOTE ? abs($qty) : $qty); // For credit note, quantity is always positive and unit price negative
3963 $this->line->subprice = ($this->type == self::TYPE_CREDIT_NOTE ? -abs($pu_ht) : $pu_ht); // For credit note, unit price always negative, always positive otherwise
3964
3965 $this->line->vat_src_code = $vat_src_code;
3966 $this->line->tva_tx = $txtva;
3967 $this->line->localtax1_tx = ($total_localtax1 ? $localtaxes_type[1] : 0);
3968 $this->line->localtax2_tx = ($total_localtax2 ? $localtaxes_type[3] : 0);
3969 $this->line->localtax1_type = empty($localtaxes_type[0]) ? '' : $localtaxes_type[0];
3970 $this->line->localtax2_type = empty($localtaxes_type[2]) ? '' : $localtaxes_type[2];
3971
3972 $this->line->total_ht = (($this->type == self::TYPE_CREDIT_NOTE || $qty < 0) ? -abs($total_ht) : $total_ht); // For credit note and if qty is negative, total is negative
3973 $this->line->total_ttc = (($this->type == self::TYPE_CREDIT_NOTE || $qty < 0) ? -abs($total_ttc) : $total_ttc); // For credit note and if qty is negative, total is negative
3974 $this->line->total_tva = (($this->type == self::TYPE_CREDIT_NOTE || $qty < 0) ? -abs($total_tva) : $total_tva); // For credit note and if qty is negative, total is negative
3975 $this->line->total_localtax1 = (($this->type == self::TYPE_CREDIT_NOTE || $qty < 0) ? -abs($total_localtax1) : $total_localtax1); // For credit note and if qty is negative, total is negative
3976 $this->line->total_localtax2 = (($this->type == self::TYPE_CREDIT_NOTE || $qty < 0) ? -abs($total_localtax2) : $total_localtax2); // For credit note and if qty is negative, total is negative
3977
3978 $this->line->fk_product = $fk_product;
3979 $this->line->product_type = $product_type;
3980 $this->line->remise_percent = $remise_percent;
3981 $this->line->date_start = $date_start;
3982 $this->line->date_end = $date_end;
3983 $this->line->ventil = $ventil;
3984 $this->line->rang = $ranktouse;
3985 $this->line->info_bits = $info_bits;
3986 $this->line->fk_remise_except = $fk_remise_except;
3987
3988 $this->line->special_code = $special_code;
3989 $this->line->fk_parent_line = $fk_parent_line;
3990 $this->line->origin = $origin;
3991 $this->line->origin_id = $origin_id;
3992 $this->line->situation_percent = $situation_percent;
3993 $this->line->fk_prev_id = $fk_prev_id;
3994 $this->line->fk_unit = $fk_unit;
3995
3996 // infos marge
3997 $this->line->fk_fournprice = $fk_fournprice;
3998 $this->line->pa_ht = $pa_ht;
3999
4000 // Multicurrency
4001 $this->line->fk_multicurrency = $this->fk_multicurrency;
4002 $this->line->multicurrency_code = $this->multicurrency_code;
4003 $this->line->multicurrency_subprice = ($this->type == self::TYPE_CREDIT_NOTE ? -abs($pu_ht_devise) : $pu_ht_devise); // For credit note, unit price always negative, always positive otherwise
4004
4005 $this->line->multicurrency_total_ht = (($this->type == self::TYPE_CREDIT_NOTE || $qty < 0) ? -abs($multicurrency_total_ht) : $multicurrency_total_ht); // For credit note and if qty is negative, total is negative
4006 $this->line->multicurrency_total_tva = (($this->type == self::TYPE_CREDIT_NOTE || $qty < 0) ? -abs($multicurrency_total_tva) : $multicurrency_total_tva); // For credit note and if qty is negative, total is negative
4007 $this->line->multicurrency_total_ttc = (($this->type == self::TYPE_CREDIT_NOTE || $qty < 0) ? -abs($multicurrency_total_ttc) : $multicurrency_total_ttc); // For credit note and if qty is negative, total is negative
4008
4009 if (is_array($array_options) && count($array_options) > 0) {
4010 $this->line->array_options = $array_options;
4011 }
4012
4013 $result = $this->line->insert();
4014 if ($result > 0) {
4015 // Reorder if child line
4016 if (!empty($fk_parent_line)) {
4017 $this->line_order(true, 'DESC');
4018 } elseif ($ranktouse > 0 && $ranktouse <= count($this->lines)) { // Update all rank of all other lines
4019 $linecount = count($this->lines);
4020 for ($ii = $ranktouse; $ii <= $linecount; $ii++) {
4021 $this->updateRangOfLine($this->lines[$ii - 1]->id, $ii + 1);
4022 }
4023 }
4024
4025 // Mise a jour informations denormalisees au niveau de la facture meme
4026 if (empty($noupdateafterinsertline)) {
4027 $result = $this->update_price(1, 'auto', 0, $mysoc); // The addline method is designed to add line from user input so total calculation with update_price must be done using 'auto' mode.
4028 }
4029
4030 if ($result > 0) {
4031 $this->db->commit();
4032 return $this->line->id;
4033 } else {
4034 $this->error = $this->db->lasterror();
4035 $this->db->rollback();
4036 return -1;
4037 }
4038 } else {
4039 $this->error = $this->line->error;
4040 $this->errors = $this->line->errors;
4041 $this->db->rollback();
4042 return -2;
4043 }
4044 } else {
4045 $this->errors[] = 'status of invoice must be Draft to allow use of ->addline()';
4046 dol_syslog(get_class($this)."::addline status of invoice must be Draft to allow use of ->addline()", LOG_ERR);
4047 return -3;
4048 }
4049 }
4050
4082 public function updateline($rowid, $desc, $pu, $qty, $remise_percent, $date_start, $date_end, $txtva, $txlocaltax1 = 0, $txlocaltax2 = 0, $price_base_type = 'HT', $info_bits = 0, $type = self::TYPE_STANDARD, $fk_parent_line = 0, $skip_update_total = 0, $fk_fournprice = null, $pa_ht = 0, $label = '', $special_code = 0, $array_options = array(), $situation_percent = 100, $fk_unit = null, $pu_ht_devise = 0, $notrigger = 0, $ref_ext = '', $rang = 0)
4083 {
4084 global $conf, $user;
4085 // Deprecation warning
4086 if ($label) {
4087 dol_syslog(__METHOD__.": using line label is deprecated", LOG_WARNING);
4088 }
4089
4090 include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
4091
4092 global $mysoc, $langs;
4093
4094 dol_syslog(get_class($this)."::updateline rowid=$rowid, desc=$desc, pu=$pu, qty=$qty, remise_percent=$remise_percent, date_start=$date_start, date_end=$date_end, txtva=$txtva, txlocaltax1=$txlocaltax1, txlocaltax2=$txlocaltax2, price_base_type=$price_base_type, info_bits=$info_bits, type=$type, fk_parent_line=$fk_parent_line pa_ht=$pa_ht, special_code=$special_code, fk_unit=$fk_unit, pu_ht_devise=$pu_ht_devise", LOG_DEBUG);
4095
4096 if ($this->status == self::STATUS_DRAFT) {
4097 if (!$this->is_last_in_cycle() && empty($this->error)) {
4098 if (!$this->checkProgressLine($rowid, $situation_percent)) {
4099 if (!$this->error) {
4100 $this->error = $langs->trans('invoiceLineProgressError');
4101 }
4102 return -3;
4103 }
4104 }
4105
4106 if ($date_start && $date_end && $date_start > $date_end) {
4107 $langs->load("errors");
4108 $this->error = $langs->trans('ErrorStartDateGreaterEnd');
4109 return -1;
4110 }
4111
4112 $this->db->begin();
4113
4114 // Clean parameters
4115 if (empty($qty)) {
4116 $qty = 0;
4117 }
4118 if (empty($fk_parent_line) || $fk_parent_line < 0) {
4119 $fk_parent_line = 0;
4120 }
4121 if (empty($special_code) || $special_code == 3) {
4122 $special_code = 0;
4123 }
4124 if (!isset($situation_percent) || $situation_percent > 100 || (string) $situation_percent == '') {
4125 $situation_percent = 100;
4126 }
4127 if (empty($ref_ext)) {
4128 $ref_ext = '';
4129 }
4130
4131 $remise_percent = price2num($remise_percent);
4132 $qty = price2num($qty);
4133 $pu = price2num($pu);
4134 $pu_ht_devise = price2num($pu_ht_devise);
4135 $pa_ht = price2num($pa_ht);
4136 if (!preg_match('/\‍((.*)\‍)/', $txtva)) {
4137 $txtva = price2num($txtva); // $txtva can have format '5.0(XXX)' or '5'
4138 }
4139 $txlocaltax1 = price2num($txlocaltax1);
4140 $txlocaltax2 = price2num($txlocaltax2);
4141
4142 // Check parameters
4143 if ($type < 0) {
4144 return -1;
4145 }
4146
4147 // Calculate total with, without tax and tax from qty, pu, remise_percent and txtva
4148 // TRES IMPORTANT: C'est au moment de l'insertion ligne qu'on doit stocker
4149 // la part ht, tva et ttc, et ce au niveau de la ligne qui a son propre taux tva.
4150
4151 $localtaxes_type = getLocalTaxesFromRate($txtva, 0, $this->thirdparty, $mysoc);
4152
4153 // Clean vat code
4154 $reg = array();
4155 $vat_src_code = '';
4156 if (preg_match('/\‍((.*)\‍)/', $txtva, $reg)) {
4157 $vat_src_code = $reg[1];
4158 $txtva = preg_replace('/\s*\‍(.*\‍)/', '', $txtva); // Remove code into vatrate.
4159 }
4160
4161 $tabprice = calcul_price_total($qty, $pu, $remise_percent, $txtva, $txlocaltax1, $txlocaltax2, 0, $price_base_type, $info_bits, $type, $mysoc, $localtaxes_type, $situation_percent, $this->multicurrency_tx, $pu_ht_devise);
4162
4163 $total_ht = $tabprice[0];
4164 $total_tva = $tabprice[1];
4165 $total_ttc = $tabprice[2];
4166 $total_localtax1 = $tabprice[9];
4167 $total_localtax2 = $tabprice[10];
4168 $pu_ht = $tabprice[3];
4169 $pu_tva = $tabprice[4];
4170 $pu_ttc = $tabprice[5];
4171
4172 // MultiCurrency
4173 $multicurrency_total_ht = $tabprice[16];
4174 $multicurrency_total_tva = $tabprice[17];
4175 $multicurrency_total_ttc = $tabprice[18];
4176 $pu_ht_devise = $tabprice[19];
4177
4178 // Old properties: $price, $remise (deprecated)
4179 $price = $pu;
4180 $remise = 0;
4181 if ($remise_percent > 0) {
4182 $remise = round(((float) $pu * (float) $remise_percent / 100), 2);
4183 $price = ((float) $pu - $remise);
4184 }
4185 $price = price2num($price);
4186
4187 //Fetch current line from the database and then clone the object and set it in $oldline property
4188 $line = new FactureLigne($this->db);
4189 $line->fetch($rowid);
4190 $line->fetch_optionals();
4191
4192 if (!empty($line->fk_product)) {
4193 $product = new Product($this->db);
4194 $result = $product->fetch($line->fk_product);
4195 $product_type = $product->type;
4196
4197 if (getDolGlobalString('STOCK_MUST_BE_ENOUGH_FOR_INVOICE') && $product_type == 0 && $product->stock_reel < $qty) {
4198 $langs->load("errors");
4199 $this->error = $langs->trans('ErrorStockIsNotEnoughToAddProductOnInvoice', $product->ref);
4200 $this->db->rollback();
4201 return -3;
4202 }
4203 }
4204
4205 $staticline = clone $line;
4206
4207 $line->oldline = $staticline;
4208 $this->line = $line;
4209 $this->line->context = $this->context;
4210 $this->line->rang = $rang;
4211
4212 // Reorder if fk_parent_line change
4213 if (!empty($fk_parent_line) && !empty($staticline->fk_parent_line) && $fk_parent_line != $staticline->fk_parent_line) {
4214 $rangmax = $this->line_max($fk_parent_line);
4215 $this->line->rang = $rangmax + 1;
4216 }
4217 $apply_abs_price_on_credit_note=false;
4218 if ($this->type == self::TYPE_CREDIT_NOTE && !getDolGlobalInt('FACTURE_ENABLE_NEGATIVE_LINES') && !getDolGlobalInt('INVOICE_KEEP_DISCOUNT_LINES_AS_IN_ORIGIN')) {
4219 $apply_abs_price_on_credit_note = true;
4220 }
4221
4222
4223 $this->line->id = $rowid;
4224 $this->line->rowid = $rowid;
4225 $this->line->label = $label;
4226 $this->line->desc = $desc;
4227 $this->line->ref_ext = $ref_ext;
4228 $this->line->qty = ($this->type == self::TYPE_CREDIT_NOTE ? abs($qty) : $qty); // For credit note, quantity is always positive and unit price negative
4229
4230 $this->line->vat_src_code = $vat_src_code;
4231 $this->line->tva_tx = $txtva;
4232 $this->line->localtax1_tx = $txlocaltax1;
4233 $this->line->localtax2_tx = $txlocaltax2;
4234 $this->line->localtax1_type = empty($localtaxes_type[0]) ? '' : $localtaxes_type[0];
4235 $this->line->localtax2_type = empty($localtaxes_type[2]) ? '' : $localtaxes_type[2];
4236
4237 $this->line->remise_percent = $remise_percent;
4238 $this->line->subprice = ($apply_abs_price_on_credit_note ? -abs($pu_ht) : $pu_ht); // For credit note, unit price always negative, always positive otherwise
4239 $this->line->date_start = $date_start;
4240 $this->line->date_end = $date_end;
4241 $this->line->total_ht = (($apply_abs_price_on_credit_note || $qty < 0) ? -abs($total_ht) : $total_ht); // For credit note and if qty is negative, total is negative
4242 $this->line->total_tva = (($apply_abs_price_on_credit_note || $qty < 0) ? -abs($total_tva) : $total_tva);
4243 $this->line->total_localtax1 = $total_localtax1;
4244 $this->line->total_localtax2 = $total_localtax2;
4245 $this->line->total_ttc = (($apply_abs_price_on_credit_note || $qty < 0) ? -abs($total_ttc) : $total_ttc);
4246 $this->line->info_bits = $info_bits;
4247 $this->line->special_code = $special_code;
4248 $this->line->product_type = $type;
4249 $this->line->fk_parent_line = $fk_parent_line;
4250 $this->line->skip_update_total = $skip_update_total;
4251 $this->line->situation_percent = $situation_percent;
4252 $this->line->fk_unit = $fk_unit;
4253
4254 $this->line->fk_fournprice = $fk_fournprice;
4255 $this->line->pa_ht = $pa_ht;
4256
4257 // Multicurrency
4258 $this->line->multicurrency_subprice = ($apply_abs_price_on_credit_note ? -abs($pu_ht_devise) : $pu_ht_devise); // For credit note, unit price always negative, always positive otherwise
4259 $this->line->multicurrency_total_ht = (($apply_abs_price_on_credit_note || $qty < 0) ? -abs($multicurrency_total_ht) : $multicurrency_total_ht); // For credit note and if qty is negative, total is negative
4260 $this->line->multicurrency_total_tva = (($apply_abs_price_on_credit_note || $qty < 0) ? -abs($multicurrency_total_tva) : $multicurrency_total_tva);
4261 $this->line->multicurrency_total_ttc = (($apply_abs_price_on_credit_note || $qty < 0) ? -abs($multicurrency_total_ttc) : $multicurrency_total_ttc);
4262
4263 if (is_array($array_options) && count($array_options) > 0) {
4264 // We replace values in this->line->array_options only for entries defined into $array_options
4265 foreach ($array_options as $key => $value) {
4266 $this->line->array_options[$key] = $array_options[$key];
4267 }
4268 }
4269
4270 $result = $this->line->update($user, $notrigger);
4271 if ($result > 0) {
4272 // Reorder if child line
4273 if (!empty($fk_parent_line)) {
4274 $this->line_order(true, 'DESC');
4275 }
4276
4277 // Mise a jour info denormalisees au niveau facture
4278 $this->update_price(1, 'auto');
4279 $this->db->commit();
4280 return $result;
4281 } else {
4282 $this->error = $this->line->error;
4283 $this->db->rollback();
4284 return -1;
4285 }
4286 } else {
4287 $this->error = "Invoice statut makes operation forbidden";
4288 return -2;
4289 }
4290 }
4291
4299 public function checkProgressLine($idline, $situation_percent)
4300 {
4301 $sql = 'SELECT fd.situation_percent FROM '.MAIN_DB_PREFIX.'facturedet fd
4302 INNER JOIN '.MAIN_DB_PREFIX.'facture f ON (fd.fk_facture = f.rowid)
4303 WHERE fd.fk_prev_id = '.((int) $idline).' AND f.fk_statut <> 0';
4304
4305 $result = $this->db->query($sql);
4306 if (!$result) {
4307 $this->error = $this->db->error();
4308 return false;
4309 }
4310
4311 $obj = $this->db->fetch_object($result);
4312
4313 if ($obj === null) {
4314 return true;
4315 } else {
4316 return ($situation_percent < $obj->situation_percent);
4317 }
4318 }
4319
4320 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4329 public function update_percent($line, $percent, $update_price = true)
4330 {
4331 // phpcs:enable
4332 global $mysoc, $user;
4333
4334 // Progress should never be changed for discount lines
4335 if (($line->info_bits & 2) == 2) {
4336 return;
4337 }
4338
4339 include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
4340
4341 // Cap percentages to 100
4342 if ($percent > 100) {
4343 $percent = 100;
4344 }
4345 $line->situation_percent = $percent;
4346 $tabprice = calcul_price_total($line->qty, $line->subprice, $line->remise_percent, $line->tva_tx, $line->localtax1_tx, $line->localtax2_tx, 0, 'HT', 0, $line->product_type, $mysoc, '', $percent);
4347 $line->total_ht = $tabprice[0];
4348 $line->total_tva = $tabprice[1];
4349 $line->total_ttc = $tabprice[2];
4350 $line->total_localtax1 = $tabprice[9];
4351 $line->total_localtax2 = $tabprice[10];
4352 $line->multicurrency_total_ht = $tabprice[16];
4353 $line->multicurrency_total_tva = $tabprice[17];
4354 $line->multicurrency_total_ttc = $tabprice[18];
4355 $line->update($user);
4356
4357 // sometimes it is better to not update price for each line, ie when updating situation on all lines
4358 if ($update_price) {
4359 $this->update_price(1);
4360 }
4361 }
4362
4370 public function deleteline($rowid, $id = 0)
4371 {
4372 global $user;
4373
4374 dol_syslog(get_class($this)."::deleteline rowid=".((int) $rowid), LOG_DEBUG);
4375
4376 if ($this->status != self::STATUS_DRAFT) {
4377 $this->error = 'ErrorDeleteLineNotAllowedByObjectStatus';
4378 return -1;
4379 }
4380
4381 $line = new FactureLigne($this->db);
4382
4383 $line->context = $this->context;
4384
4385 // Load line
4386 $result = $line->fetch($rowid);
4387 if (!($result > 0)) {
4388 dol_print_error($this->db, $line->error, $line->errors);
4389 return -1;
4390 }
4391
4392 if ($id > 0 && $line->fk_facture != $id) {
4393 $this->error = 'ErrorLineIDDoesNotMatchWithObjectID';
4394 return -1;
4395 }
4396
4397 $this->db->begin();
4398
4399 // Memorize previous line for triggers
4400 $staticline = clone $line;
4401 $line->oldline = $staticline;
4402
4403 if ($line->delete($user) > 0) {
4404 $result = $this->update_price(1);
4405
4406 if ($result > 0) {
4407 $this->db->commit();
4408 return 1;
4409 } else {
4410 $this->db->rollback();
4411 $this->error = $this->db->lasterror();
4412 return -1;
4413 }
4414 } else {
4415 $this->db->rollback();
4416 $this->error = $line->error;
4417 return -1;
4418 }
4419 }
4420
4421 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4432 public function set_remise($user, $remise, $notrigger = 0)
4433 {
4434 // phpcs:enable
4435 dol_syslog(get_class($this)."::set_remise is deprecated, use setDiscount instead", LOG_NOTICE);
4436 return $this->setDiscount($user, $remise, $notrigger);
4437 }
4438
4448 public function setDiscount($user, $remise, $notrigger = 0)
4449 {
4450 // Clean parameters
4451 if (empty($remise)) {
4452 $remise = 0;
4453 }
4454
4455 if ($user->hasRight('facture', 'creer')) {
4456 $remise = price2num($remise, 2);
4457
4458 $error = 0;
4459
4460 $this->db->begin();
4461
4462 $sql = 'UPDATE '.MAIN_DB_PREFIX.'facture';
4463 $sql .= ' SET remise_percent = '.((float) $remise);
4464 $sql .= " WHERE rowid = ".((int) $this->id);
4465 $sql .= ' AND fk_statut = '.self::STATUS_DRAFT;
4466
4467 dol_syslog(__METHOD__, LOG_DEBUG);
4468 $resql = $this->db->query($sql);
4469 if (!$resql) {
4470 $this->errors[] = $this->db->error();
4471 $error++;
4472 }
4473
4474 if (!$notrigger && empty($error)) {
4475 // Call trigger
4476 $result = $this->call_trigger('BILL_MODIFY', $user);
4477 if ($result < 0) {
4478 $error++;
4479 }
4480 // End call triggers
4481 }
4482
4483 if (!$error) {
4484 $this->remise_percent = $remise;
4485 $this->update_price(1);
4486
4487 $this->db->commit();
4488 return 1;
4489 } else {
4490 foreach ($this->errors as $errmsg) {
4491 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
4492 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
4493 }
4494 $this->db->rollback();
4495 return -1 * $error;
4496 }
4497 }
4498
4499 return 0;
4500 }
4501
4502
4503 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4512 /*
4513 public function set_remise_absolue($user, $remise, $notrigger = 0)
4514 {
4515 // phpcs:enable
4516 if (empty($remise)) {
4517 $remise = 0;
4518 }
4519
4520 if ($user->hasRight('facture', 'creer')) {
4521 $error = 0;
4522
4523 $this->db->begin();
4524
4525 $remise = price2num($remise);
4526
4527 $sql = 'UPDATE '.MAIN_DB_PREFIX.'facture';
4528 $sql .= ' SET remise_absolue = '.((float) $remise);
4529 $sql .= " WHERE rowid = ".((int) $this->id);
4530 $sql .= ' AND fk_statut = '.self::STATUS_DRAFT;
4531
4532 dol_syslog(__METHOD__, LOG_DEBUG);
4533 $resql = $this->db->query($sql);
4534 if (!$resql) {
4535 $this->errors[] = $this->db->error();
4536 $error++;
4537 }
4538
4539 if (!$error) {
4540 $this->oldcopy = clone $this;
4541 $this->remise_absolue = $remise;
4542 $this->update_price(1);
4543 }
4544
4545 if (!$notrigger && empty($error)) {
4546 // Call trigger
4547 $result = $this->call_trigger('BILL_MODIFY', $user);
4548 if ($result < 0) {
4549 $error++;
4550 }
4551 // End call triggers
4552 }
4553
4554 if (!$error) {
4555 $this->db->commit();
4556 return 1;
4557 } else {
4558 foreach ($this->errors as $errmsg) {
4559 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
4560 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
4561 }
4562 $this->db->rollback();
4563 return -1 * $error;
4564 }
4565 }
4566
4567 return 0;
4568 }
4569 */
4570
4579 public function getNextNumRef($soc, $mode = 'next')
4580 {
4581 global $conf, $langs;
4582
4583 if ($this->module_source == 'takepos') {
4584 $langs->load('cashdesk');
4585
4586 $moduleName = 'takepos';
4587 $moduleSourceName = 'Takepos';
4588 $addonConstName = 'TAKEPOS_REF_ADDON';
4589
4590 // Clean parameters (if not defined or using deprecated value)
4591 if (!getDolGlobalString('TAKEPOS_REF_ADDON')) {
4592 $conf->global->TAKEPOS_REF_ADDON = 'mod_takepos_ref_simple';
4593 }
4594
4595 $addon = $conf->global->TAKEPOS_REF_ADDON;
4596 } else {
4597 $langs->load('bills');
4598
4599 $moduleName = 'facture';
4600 $moduleSourceName = 'Invoice';
4601 $addonConstName = 'FACTURE_ADDON';
4602
4603 // Clean parameters (if not defined or using deprecated value)
4604 if (!getDolGlobalString('FACTURE_ADDON')) {
4605 $conf->global->FACTURE_ADDON = 'mod_facture_terre';
4606 } elseif (getDolGlobalString('FACTURE_ADDON') == 'terre') {
4607 $conf->global->FACTURE_ADDON = 'mod_facture_terre';
4608 } elseif (getDolGlobalString('FACTURE_ADDON') == 'mercure') {
4609 $conf->global->FACTURE_ADDON = 'mod_facture_mercure';
4610 }
4611
4612 $addon = $conf->global->FACTURE_ADDON;
4613 }
4614
4615 if (!empty($addon)) {
4616 dol_syslog("Call getNextNumRef with ".$addonConstName." = " . getDolGlobalString('FACTURE_ADDON').", thirdparty=".$soc->name.", type=".$soc->typent_code.", mode=".$mode, LOG_DEBUG);
4617
4618 $mybool = false;
4619
4620 $file = $addon.'.php';
4621 $classname = $addon;
4622
4623
4624 // Include file with class
4625 $dirmodels = array_merge(array('/'), (array) $conf->modules_parts['models']);
4626 foreach ($dirmodels as $reldir) {
4627 $dir = dol_buildpath($reldir.'core/modules/'.$moduleName.'/');
4628
4629 // Load file with numbering class (if found)
4630 if (is_file($dir.$file) && is_readable($dir.$file)) {
4631 $mybool |= include_once $dir.$file;
4632 }
4633 }
4634
4635 // For compatibility
4636 if (!$mybool) {
4637 $file = $addon.'/'.$addon.'.modules.php';
4638 $classname = 'mod_'.$moduleName.'_'.$addon;
4639 $classname = preg_replace('/\-.*$/', '', $classname);
4640 // Include file with class
4641 foreach ($conf->file->dol_document_root as $dirroot) {
4642 $dir = $dirroot.'/core/modules/'.$moduleName.'/';
4643
4644 // Load file with numbering class (if found)
4645 if (is_file($dir.$file) && is_readable($dir.$file)) {
4646 $mybool |= include_once $dir.$file;
4647 }
4648 }
4649 }
4650
4651 if (!$mybool) {
4652 dol_print_error('', 'Failed to include file '.$file);
4653 return '';
4654 }
4655
4656 $obj = new $classname();
4657
4658 $numref = $obj->getNextValue($soc, $this, $mode);
4659
4660
4665 if ($mode != 'last' && !$numref) {
4666 $this->error = $obj->error;
4667 return '';
4668 }
4669
4670 return $numref;
4671 } else {
4672 $langs->load('errors');
4673 print $langs->trans('Error').' '.$langs->trans('ErrorModuleSetupNotComplete', $langs->transnoentitiesnoconv($moduleSourceName));
4674 return '';
4675 }
4676 }
4677
4684 public function info($id)
4685 {
4686 $sql = 'SELECT c.rowid, datec, date_valid as datev, tms as datem,';
4687 $sql .= ' date_closing as dateclosing,';
4688 $sql .= ' fk_user_author, fk_user_valid, fk_user_closing';
4689 $sql .= ' FROM '.MAIN_DB_PREFIX.'facture as c';
4690 $sql .= ' WHERE c.rowid = '.((int) $id);
4691
4692 $result = $this->db->query($sql);
4693 if ($result) {
4694 if ($this->db->num_rows($result)) {
4695 $obj = $this->db->fetch_object($result);
4696
4697 $this->id = $obj->rowid;
4698 $this->user_creation_id = $obj->fk_user_author;
4699 $this->user_validation_id = $obj->fk_user_valid;
4700 $this->user_closing_id = $obj->fk_user_closing;
4701
4702 $this->date_creation = $this->db->jdate($obj->datec);
4703 $this->date_modification = $this->db->jdate($obj->datem);
4704 $this->date_validation = $this->db->jdate($obj->datev);
4705 $this->date_closing = $this->db->jdate($obj->dateclosing);
4706 }
4707 $this->db->free($result);
4708 } else {
4709 dol_print_error($this->db);
4710 }
4711 }
4712
4713
4714 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4728 public function liste_array($shortlist = 0, $draft = 0, $excluser = null, $socid = 0, $limit = 0, $offset = 0, $sortfield = 'f.datef,f.rowid', $sortorder = 'DESC')
4729 {
4730 // phpcs:enable
4731 global $conf, $user;
4732
4733 $ga = array();
4734
4735 $sql = "SELECT s.rowid, s.nom as name, s.client,";
4736 $sql .= " f.rowid as fid, f.ref as ref, f.datef as df";
4737 if (!$user->hasRight('societe', 'client', 'voir') && !$socid) {
4738 $sql .= ", sc.fk_soc, sc.fk_user";
4739 }
4740 $sql .= " FROM ".MAIN_DB_PREFIX."societe as s, ".MAIN_DB_PREFIX."facture as f";
4741 if (!$user->hasRight('societe', 'client', 'voir') && !$socid) {
4742 $sql .= ", ".MAIN_DB_PREFIX."societe_commerciaux as sc";
4743 }
4744 $sql .= " WHERE f.entity IN (".getEntity('invoice').")";
4745 $sql .= " AND f.fk_soc = s.rowid";
4746 if (!$user->hasRight('societe', 'client', 'voir') && !$socid) { //restriction
4747 $sql .= " AND s.rowid = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
4748 }
4749 if ($socid) {
4750 $sql .= " AND s.rowid = ".((int) $socid);
4751 }
4752 if ($draft) {
4753 $sql .= " AND f.fk_statut = ".self::STATUS_DRAFT;
4754 }
4755 if (is_object($excluser)) {
4756 $sql .= " AND f.fk_user_author <> ".((int) $excluser->id);
4757 }
4758 $sql .= $this->db->order($sortfield, $sortorder);
4759 $sql .= $this->db->plimit($limit, $offset);
4760
4761 $result = $this->db->query($sql);
4762 if ($result) {
4763 $numc = $this->db->num_rows($result);
4764 if ($numc) {
4765 $i = 0;
4766 while ($i < $numc) {
4767 $obj = $this->db->fetch_object($result);
4768
4769 if ($shortlist == 1) {
4770 $ga[$obj->fid] = $obj->ref;
4771 } elseif ($shortlist == 2) {
4772 $ga[$obj->fid] = $obj->ref.' ('.$obj->name.')';
4773 } else {
4774 $ga[$i]['id'] = $obj->fid;
4775 $ga[$i]['ref'] = $obj->ref;
4776 $ga[$i]['name'] = $obj->name;
4777 }
4778 $i++;
4779 }
4780 }
4781 return $ga;
4782 } else {
4783 dol_print_error($this->db);
4784 return -1;
4785 }
4786 }
4787
4788
4789 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4798 public function list_replacable_invoices($socid = 0)
4799 {
4800 // phpcs:enable
4801 global $conf;
4802
4803 $return = array();
4804
4805 $sql = "SELECT f.rowid as rowid, f.ref, f.fk_statut as status, f.paye as paid,";
4806 $sql .= " ff.rowid as rowidnext";
4807 //$sql .= ", SUM(pf.amount) as alreadypaid";
4808 $sql .= " FROM ".MAIN_DB_PREFIX."facture as f";
4809 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."paiement_facture as pf ON f.rowid = pf.fk_facture";
4810 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."facture as ff ON f.rowid = ff.fk_facture_source";
4811 $sql .= " WHERE (f.fk_statut = ".self::STATUS_VALIDATED." OR (f.fk_statut = ".self::STATUS_ABANDONED." AND f.close_code = '".self::CLOSECODE_ABANDONED."'))";
4812 $sql .= " AND f.entity IN (".getEntity('invoice').")";
4813 $sql .= " AND f.paye = 0"; // Not paid completely
4814 $sql .= " AND pf.fk_paiement IS NULL"; // No payment already done
4815 $sql .= " AND ff.fk_statut IS NULL"; // Return true if it is not a replacement invoice
4816 if ($socid > 0) {
4817 $sql .= " AND f.fk_soc = ".((int) $socid);
4818 }
4819 //$sql .= " GROUP BY f.rowid, f.ref, f.fk_statut, f.paye, ff.rowid";
4820 $sql .= " ORDER BY f.ref";
4821
4822 dol_syslog(get_class($this)."::list_replacable_invoices", LOG_DEBUG);
4823 $resql = $this->db->query($sql);
4824 if ($resql) {
4825 while ($obj = $this->db->fetch_object($resql)) {
4826 $return[$obj->rowid] = array(
4827 'id' => $obj->rowid,
4828 'ref' => $obj->ref,
4829 'status' => $obj->status,
4830 'paid' => $obj->paid,
4831 'alreadypaid' => 0
4832 );
4833 }
4834 //print_r($return);
4835 return $return;
4836 } else {
4837 $this->error = $this->db->error();
4838 return -1;
4839 }
4840 }
4841
4842
4843 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4852 public function list_qualified_avoir_invoices($socid = 0)
4853 {
4854 // phpcs:enable
4855 global $conf;
4856
4857 $return = array();
4858
4859
4860 $sql = "SELECT f.rowid as rowid, f.ref, f.fk_statut, f.type, f.subtype, f.paye, pf.fk_paiement";
4861 $sql .= " FROM ".MAIN_DB_PREFIX."facture as f";
4862 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."paiement_facture as pf ON f.rowid = pf.fk_facture";
4863 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."facture as ff ON (f.rowid = ff.fk_facture_source AND ff.type=".self::TYPE_REPLACEMENT.")";
4864 $sql .= " WHERE f.entity IN (".getEntity('invoice').")";
4865 $sql .= " AND f.fk_statut in (".self::STATUS_VALIDATED.",".self::STATUS_CLOSED.")";
4866 // $sql.= " WHERE f.fk_statut >= 1";
4867 // $sql.= " AND (f.paye = 1"; // Classee payee completement
4868 // $sql.= " OR f.close_code IS NOT NULL)"; // Classee payee partiellement
4869 $sql .= " AND ff.type IS NULL"; // Renvoi vrai si pas facture de remplacement
4870 $sql .= " AND f.type <> ".self::TYPE_CREDIT_NOTE; // Exclude credit note invoices from selection
4871
4872 if (getDolGlobalString('INVOICE_USE_SITUATION_CREDIT_NOTE')) {
4873 // Keep invoices that are not situation invoices or that are the last in serie if it is a situation invoice
4874 $sql .= " AND (f.type <> ".self::TYPE_SITUATION." OR f.rowid IN ";
4875 $sql .= '(SELECT MAX(fs.rowid)'; // This select returns several ID becasue of the group by later
4876 $sql .= " FROM ".MAIN_DB_PREFIX."facture as fs";
4877 $sql .= " WHERE fs.entity IN (".getEntity('invoice').")";
4878 $sql .= " AND fs.type = ".self::TYPE_SITUATION;
4879 $sql .= " AND fs.fk_statut IN (".self::STATUS_VALIDATED.",".self::STATUS_CLOSED.")";
4880 if ($socid > 0) {
4881 $sql .= " AND fs.fk_soc = ".((int) $socid);
4882 }
4883 $sql .= " GROUP BY fs.situation_cycle_ref)"; // For each situation_cycle_ref, we take the higher rowid
4884 $sql .= ")";
4885 } else {
4886 $sql .= " AND f.type <> ".self::TYPE_SITUATION; // Keep invoices that are not situation invoices
4887 }
4888
4889 if ($socid > 0) {
4890 $sql .= " AND f.fk_soc = ".((int) $socid);
4891 }
4892 $sql .= " ORDER BY f.ref";
4893
4894 dol_syslog(get_class($this)."::list_qualified_avoir_invoices", LOG_DEBUG);
4895 $resql = $this->db->query($sql);
4896 if ($resql) {
4897 while ($obj = $this->db->fetch_object($resql)) {
4898 $qualified = 0;
4899 if ($obj->fk_statut == self::STATUS_VALIDATED) {
4900 $qualified = 1;
4901 }
4902 if ($obj->fk_statut == self::STATUS_CLOSED) {
4903 $qualified = 1;
4904 }
4905 if ($qualified) {
4906 //$ref=$obj->ref;
4907 $paymentornot = ($obj->fk_paiement ? 1 : 0);
4908 $return[$obj->rowid] = array('ref'=>$obj->ref, 'status'=>$obj->fk_statut, 'type'=>$obj->type, 'paye'=>$obj->paye, 'paymentornot'=>$paymentornot);
4909 }
4910 }
4911
4912 return $return;
4913 } else {
4914 $this->error = $this->db->error();
4915 return -1;
4916 }
4917 }
4918
4919
4920 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4927 public function load_board($user)
4928 {
4929 // phpcs:enable
4930 global $conf, $langs;
4931
4932 $clause = " WHERE";
4933
4934 $sql = "SELECT f.rowid, f.date_lim_reglement as datefin, f.fk_statut as status, f.total_ht";
4935 $sql .= " FROM ".MAIN_DB_PREFIX."facture as f";
4936 if (!$user->hasRight('societe', 'client', 'voir') && !$user->socid) {
4937 $sql .= " JOIN ".MAIN_DB_PREFIX."societe_commerciaux as sc ON f.fk_soc = sc.fk_soc";
4938 $sql .= " WHERE sc.fk_user = ".((int) $user->id);
4939 $clause = " AND";
4940 }
4941 $sql .= $clause." f.paye=0";
4942 $sql .= " AND f.entity IN (".getEntity('invoice').")";
4943 $sql .= " AND f.fk_statut = ".self::STATUS_VALIDATED;
4944 if ($user->socid) {
4945 $sql .= " AND f.fk_soc = ".((int) $user->socid);
4946 }
4947
4948 $resql = $this->db->query($sql);
4949 if ($resql) {
4950 $langs->load("bills");
4951 $now = dol_now();
4952
4953 $response = new WorkboardResponse();
4954 $response->warning_delay = $conf->facture->client->warning_delay / 60 / 60 / 24;
4955 $response->label = $langs->trans("CustomerBillsUnpaid");
4956 $response->labelShort = $langs->trans("Unpaid");
4957 $response->url = DOL_URL_ROOT.'/compta/facture/list.php?search_status=1&mainmenu=billing&leftmenu=customers_bills';
4958 $response->img = img_object('', "bill");
4959
4960 $generic_facture = new Facture($this->db);
4961
4962 while ($obj = $this->db->fetch_object($resql)) {
4963 $generic_facture->date_lim_reglement = $this->db->jdate($obj->datefin);
4964 $generic_facture->statut = $obj->status;
4965 $generic_facture->status = $obj->status;
4966
4967 $response->nbtodo++;
4968 $response->total += $obj->total_ht;
4969
4970 if ($generic_facture->hasDelay()) {
4971 $response->nbtodolate++;
4972 $response->url_late = DOL_URL_ROOT.'/compta/facture/list.php?search_option=late&mainmenu=billing&leftmenu=customers_bills';
4973 }
4974 }
4975
4976 $this->db->free($resql);
4977 return $response;
4978 } else {
4979 dol_print_error($this->db);
4980 $this->error = $this->db->error();
4981 return -1;
4982 }
4983 }
4984
4985
4986 /* gestion des contacts d'une facture */
4987
4993 public function getIdBillingContact()
4994 {
4995 return $this->getIdContact('external', 'BILLING');
4996 }
4997
5003 public function getIdShippingContact()
5004 {
5005 return $this->getIdContact('external', 'SHIPPING');
5006 }
5007
5008
5017 public function initAsSpecimen($option = '')
5018 {
5019 global $conf, $langs, $user;
5020
5021 $now = dol_now();
5022 $arraynow = dol_getdate($now);
5023 $nownotime = dol_mktime(0, 0, 0, $arraynow['mon'], $arraynow['mday'], $arraynow['year']);
5024
5025 // Load array of products prodids
5026 $num_prods = 0;
5027 $prodids = array();
5028 $sql = "SELECT rowid";
5029 $sql .= " FROM ".MAIN_DB_PREFIX."product";
5030 $sql .= " WHERE entity IN (".getEntity('product').")";
5031 $sql .= $this->db->plimit(100);
5032
5033 $resql = $this->db->query($sql);
5034 if ($resql) {
5035 $num_prods = $this->db->num_rows($resql);
5036 $i = 0;
5037 while ($i < $num_prods) {
5038 $i++;
5039 $row = $this->db->fetch_row($resql);
5040 $prodids[$i] = $row[0];
5041 }
5042 }
5043 //Avoid php warning Warning: mt_rand(): max(0) is smaller than min(1) when no product exists
5044 if (empty($num_prods)) {
5045 $num_prods = 1;
5046 }
5047
5048 // Initialize parameters
5049 $this->id = 0;
5050 $this->entity = 1;
5051 $this->ref = 'SPECIMEN';
5052 $this->specimen = 1;
5053 $this->socid = 1;
5054 $this->date = $nownotime;
5055 $this->date_lim_reglement = $nownotime + 3600 * 24 * 30;
5056 $this->cond_reglement_id = 1;
5057 $this->cond_reglement_code = 'RECEP';
5058 $this->date_lim_reglement = $this->calculate_date_lim_reglement();
5059 $this->mode_reglement_id = 0; // Not forced to show payment mode CHQ + VIR
5060 $this->mode_reglement_code = ''; // Not forced to show payment mode CHQ + VIR
5061
5062 $this->note_public = 'This is a comment (public)';
5063 $this->note_private = 'This is a comment (private)';
5064 $this->note = 'This is a comment (private)';
5065
5066 $this->fk_user_author = $user->id;
5067
5068 $this->multicurrency_tx = 1;
5069 $this->multicurrency_code = $conf->currency;
5070
5071 $this->fk_incoterms = 0;
5072 $this->location_incoterms = '';
5073
5074 if (empty($option) || $option != 'nolines') {
5075 // Lines
5076 $nbp = 5;
5077 $xnbp = 0;
5078 while ($xnbp < $nbp) {
5079 $line = new FactureLigne($this->db);
5080 $line->desc = $langs->trans("Description")." ".$xnbp;
5081 $line->qty = 1;
5082 $line->subprice = 100;
5083 $line->tva_tx = 19.6;
5084 $line->localtax1_tx = 0;
5085 $line->localtax2_tx = 0;
5086 $line->remise_percent = 0;
5087 if ($xnbp == 1) { // Qty is negative (product line)
5088 $prodid = mt_rand(1, $num_prods);
5089 $line->fk_product = $prodids[$prodid];
5090 $line->qty = -1;
5091 $line->total_ht = -100;
5092 $line->total_ttc = -119.6;
5093 $line->total_tva = -19.6;
5094 $line->multicurrency_total_ht = -200;
5095 $line->multicurrency_total_ttc = -239.2;
5096 $line->multicurrency_total_tva = -39.2;
5097 } elseif ($xnbp == 2) { // UP is negative (free line)
5098 $line->subprice = -100;
5099 $line->total_ht = -100;
5100 $line->total_ttc = -119.6;
5101 $line->total_tva = -19.6;
5102 $line->remise_percent = 0;
5103 $line->multicurrency_total_ht = -200;
5104 $line->multicurrency_total_ttc = -239.2;
5105 $line->multicurrency_total_tva = -39.2;
5106 } elseif ($xnbp == 3) { // Discount is 50% (product line)
5107 $prodid = mt_rand(1, $num_prods);
5108 $line->fk_product = $prodids[$prodid];
5109 $line->total_ht = 50;
5110 $line->total_ttc = 59.8;
5111 $line->total_tva = 9.8;
5112 $line->multicurrency_total_ht = 100;
5113 $line->multicurrency_total_ttc = 119.6;
5114 $line->multicurrency_total_tva = 19.6;
5115 $line->remise_percent = 50;
5116 } else { // (product line)
5117 $prodid = mt_rand(1, $num_prods);
5118 $line->fk_product = $prodids[$prodid];
5119 $line->total_ht = 100;
5120 $line->total_ttc = 119.6;
5121 $line->total_tva = 19.6;
5122 $line->multicurrency_total_ht = 200;
5123 $line->multicurrency_total_ttc = 239.2;
5124 $line->multicurrency_total_tva = 39.2;
5125 $line->remise_percent = 0;
5126 }
5127
5128 $this->lines[$xnbp] = $line;
5129
5130
5131 $this->total_ht += $line->total_ht;
5132 $this->total_tva += $line->total_tva;
5133 $this->total_ttc += $line->total_ttc;
5134
5135 $this->multicurrency_total_ht += $line->multicurrency_total_ht;
5136 $this->multicurrency_total_tva += $line->multicurrency_total_tva;
5137 $this->multicurrency_total_ttc += $line->multicurrency_total_ttc;
5138
5139 $xnbp++;
5140 }
5141 $this->revenuestamp = 0;
5142
5143 // Add a line "offered"
5144 $line = new FactureLigne($this->db);
5145 $line->desc = $langs->trans("Description")." (offered line)";
5146 $line->qty = 1;
5147 $line->subprice = 100;
5148 $line->tva_tx = 19.6;
5149 $line->localtax1_tx = 0;
5150 $line->localtax2_tx = 0;
5151 $line->remise_percent = 100;
5152 $line->total_ht = 0;
5153 $line->total_ttc = 0; // 90 * 1.196
5154 $line->total_tva = 0;
5155 $line->multicurrency_total_ht = 0;
5156 $line->multicurrency_total_ttc = 0;
5157 $line->multicurrency_total_tva = 0;
5158 $prodid = mt_rand(1, $num_prods);
5159 $line->fk_product = $prodids[$prodid];
5160
5161 $this->lines[$xnbp] = $line;
5162 $xnbp++;
5163 }
5164 }
5165
5166 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5172 public function load_state_board()
5173 {
5174 // phpcs:enable
5175 global $conf, $user;
5176
5177 $this->nb = array();
5178
5179 $clause = "WHERE";
5180
5181 $sql = "SELECT count(f.rowid) as nb";
5182 $sql .= " FROM ".MAIN_DB_PREFIX."facture as f";
5183 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe as s ON f.fk_soc = s.rowid";
5184 if (!$user->hasRight('societe', 'client', 'voir') && !$user->socid) {
5185 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe_commerciaux as sc ON s.rowid = sc.fk_soc";
5186 $sql .= " WHERE sc.fk_user = ".((int) $user->id);
5187 $clause = "AND";
5188 }
5189 $sql .= " ".$clause." f.entity IN (".getEntity('invoice').")";
5190
5191 $resql = $this->db->query($sql);
5192 if ($resql) {
5193 while ($obj = $this->db->fetch_object($resql)) {
5194 $this->nb["invoices"] = $obj->nb;
5195 }
5196 $this->db->free($resql);
5197 return 1;
5198 } else {
5199 dol_print_error($this->db);
5200 $this->error = $this->db->error();
5201 return -1;
5202 }
5203 }
5204
5210 public function getLinesArray()
5211 {
5212 return $this->fetch_lines();
5213 }
5214
5226 public function generateDocument($modele, $outputlangs, $hidedetails = 0, $hidedesc = 0, $hideref = 0, $moreparams = null)
5227 {
5228 global $conf, $langs;
5229
5230 $outputlangs->loadLangs(array("bills", "products"));
5231
5232 if (!dol_strlen($modele)) {
5233 $modele = 'crabe';
5234 $thisTypeConfName = 'FACTURE_ADDON_PDF_'.$this->type;
5235
5236 if (!empty($this->model_pdf)) {
5237 $modele = $this->model_pdf;
5238 } elseif (getDolGlobalString($thisTypeConfName)) {
5239 $modele = getDolGlobalString($thisTypeConfName);
5240 } elseif (getDolGlobalString('FACTURE_ADDON_PDF')) {
5241 $modele = $conf->global->FACTURE_ADDON_PDF;
5242 }
5243 }
5244
5245 $modelpath = "core/modules/facture/doc/";
5246
5247 return $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams);
5248 }
5249
5255 public function newCycle()
5256 {
5257 $sql = 'SELECT max(situation_cycle_ref) FROM '.MAIN_DB_PREFIX.'facture as f';
5258 $sql .= " WHERE f.entity IN (".getEntity('invoice', 0).")";
5259 $resql = $this->db->query($sql);
5260 if ($resql) {
5261 if ($this->db->num_rows($resql) > 0) {
5262 $res = $this->db->fetch_array($resql);
5263 $ref = $res['max(situation_cycle_ref)'];
5264 $ref++;
5265 } else {
5266 $ref = 1;
5267 }
5268 $this->db->free($resql);
5269 return $ref;
5270 } else {
5271 $this->error = $this->db->lasterror();
5272 dol_syslog("Error sql=".$sql.", error=".$this->error, LOG_ERR);
5273 return -1;
5274 }
5275 }
5276
5277 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5283 public function is_first()
5284 {
5285 // phpcs:enable
5286 return ($this->situation_counter == 1);
5287 }
5288
5289 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5295 public function get_prev_sits()
5296 {
5297 // phpcs:enable
5298 global $conf;
5299
5300 $sql = 'SELECT rowid FROM '.MAIN_DB_PREFIX.'facture';
5301 $sql .= ' WHERE situation_cycle_ref = '.((int) $this->situation_cycle_ref);
5302 $sql .= ' AND situation_counter < '.((int) $this->situation_counter);
5303 $sql .= ' AND entity = '.($this->entity > 0 ? $this->entity : $conf->entity);
5304 $resql = $this->db->query($sql);
5305 $res = array();
5306 if ($resql && $this->db->num_rows($resql) > 0) {
5307 while ($row = $this->db->fetch_object($resql)) {
5308 $id = $row->rowid;
5309 $situation = new Facture($this->db);
5310 $situation->fetch($id);
5311 $res[] = $situation;
5312 }
5313 } else {
5314 $this->error = $this->db->error();
5315 dol_syslog("Error sql=".$sql.", error=".$this->error, LOG_ERR);
5316 return -1;
5317 }
5318
5319 return $res;
5320 }
5321
5329 public function setFinal(User $user, $notrigger = 0)
5330 {
5331 $error = 0;
5332
5333 $this->db->begin();
5334
5335 $sql = 'UPDATE '.MAIN_DB_PREFIX.'facture SET situation_final = '.((int) $this->situation_final).' WHERE rowid = '.((int) $this->id);
5336
5337 dol_syslog(__METHOD__, LOG_DEBUG);
5338 $resql = $this->db->query($sql);
5339 if (!$resql) {
5340 $this->errors[] = $this->db->error();
5341 $error++;
5342 }
5343
5344 if (!$notrigger && empty($error)) {
5345 // Call trigger
5346 $result = $this->call_trigger('BILL_MODIFY', $user);
5347 if ($result < 0) {
5348 $error++;
5349 }
5350 // End call triggers
5351 }
5352
5353 if (!$error) {
5354 $this->db->commit();
5355 return 1;
5356 } else {
5357 foreach ($this->errors as $errmsg) {
5358 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
5359 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
5360 }
5361 $this->db->rollback();
5362 return -1 * $error;
5363 }
5364 }
5365
5366 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5372 public function is_last_in_cycle()
5373 {
5374 // phpcs:enable
5375 global $conf;
5376
5377 if (!empty($this->situation_cycle_ref)) {
5378 // No point in testing anything if we're not inside a cycle
5379 $sql = 'SELECT max(situation_counter) FROM '.MAIN_DB_PREFIX.'facture';
5380 $sql .= ' WHERE situation_cycle_ref = '.((int) $this->situation_cycle_ref);
5381 $sql .= ' AND entity = '.($this->entity > 0 ? $this->entity : $conf->entity);
5382 $resql = $this->db->query($sql);
5383
5384 if ($resql && $this->db->num_rows($resql) > 0) {
5385 $res = $this->db->fetch_array($resql);
5386 $last = $res['max(situation_counter)'];
5387 return ($last == $this->situation_counter);
5388 } else {
5389 $this->error = $this->db->lasterror();
5390 dol_syslog(get_class($this)."::select Error ".$this->error, LOG_ERR);
5391 return false;
5392 }
5393 } else {
5394 return true;
5395 }
5396 }
5397
5406 public static function replaceThirdparty(DoliDB $dbs, $origin_id, $dest_id)
5407 {
5408 $tables = array(
5409 'facture'
5410 );
5411
5412 return CommonObject::commonReplaceThirdparty($dbs, $origin_id, $dest_id, $tables);
5413 }
5414
5423 public static function replaceProduct(DoliDB $db, $origin_id, $dest_id)
5424 {
5425 $tables = array(
5426 'facturedet'
5427 );
5428
5429 return CommonObject::commonReplaceProduct($db, $origin_id, $dest_id, $tables);
5430 }
5431
5437 public function hasDelay()
5438 {
5439 global $conf;
5440
5441 $now = dol_now();
5442
5443 // Paid invoices have status STATUS_CLOSED
5444 if ($this->status != Facture::STATUS_VALIDATED) {
5445 return false;
5446 }
5447
5448 $hasDelay = $this->date_lim_reglement < ($now - $conf->facture->client->warning_delay);
5449 if ($hasDelay && !empty($this->retained_warranty) && !empty($this->retained_warranty_date_limit)) {
5450 $totalpaid = $this->getSommePaiement();
5451 $totalpaid = (float) $totalpaid;
5452 $RetainedWarrantyAmount = $this->getRetainedWarrantyAmount();
5453 if ($totalpaid >= 0 && $RetainedWarrantyAmount >= 0) {
5454 if (($totalpaid < $this->total_ttc - $RetainedWarrantyAmount) && $this->date_lim_reglement < ($now - $conf->facture->client->warning_delay)) {
5455 $hasDelay = 1;
5456 } elseif ($totalpaid < $this->total_ttc && $this->retained_warranty_date_limit < ($now - $conf->facture->client->warning_delay)) {
5457 $hasDelay = 1;
5458 } else {
5459 $hasDelay = 0;
5460 }
5461 }
5462 }
5463
5464 return $hasDelay;
5465 }
5466
5471 public function displayRetainedWarranty()
5472 {
5473 global $conf;
5474
5475 // TODO : add a flag on invoices to store this conf : INVOICE_RETAINED_WARRANTY_LIMITED_TO_FINAL_SITUATION
5476
5477 // note : we don't need to test INVOICE_USE_RETAINED_WARRANTY because if $this->retained_warranty is not empty it's because it was set when this conf was active
5478
5479 $displayWarranty = false;
5480 if (!empty($this->retained_warranty)) {
5481 $displayWarranty = true;
5482
5483 if ($this->type == Facture::TYPE_SITUATION && getDolGlobalString('INVOICE_RETAINED_WARRANTY_LIMITED_TO_FINAL_SITUATION')) {
5484 // Check if this situation invoice is 100% for real
5485 $displayWarranty = false;
5486 if (!empty($this->situation_final)) {
5487 $displayWarranty = true;
5488 } elseif (!empty($this->lines) && $this->status == Facture::STATUS_DRAFT) {
5489 // $object->situation_final need validation to be done so this test is need for draft
5490 $displayWarranty = true;
5491
5492 foreach ($this->lines as $i => $line) {
5493 if ($line->product_type < 2 && $line->situation_percent < 100) {
5494 $displayWarranty = false;
5495 break;
5496 }
5497 }
5498 }
5499 }
5500 }
5501
5502 return $displayWarranty;
5503 }
5504
5509 public function getRetainedWarrantyAmount($rounding = -1)
5510 {
5511 global $conf;
5512 if (empty($this->retained_warranty)) {
5513 return -1;
5514 }
5515
5516 $retainedWarrantyAmount = 0;
5517
5518 // Billed - retained warranty
5519 if ($this->type == Facture::TYPE_SITUATION && getDolGlobalString('INVOICE_RETAINED_WARRANTY_LIMITED_TO_FINAL_SITUATION')) {
5520 $displayWarranty = true;
5521 // Check if this situation invoice is 100% for real
5522 if (!empty($this->lines)) {
5523 foreach ($this->lines as $i => $line) {
5524 if ($line->product_type < 2 && $line->situation_percent < 100) {
5525 $displayWarranty = false;
5526 break;
5527 }
5528 }
5529 }
5530
5531 if ($displayWarranty && !empty($this->situation_final)) {
5533 $TPreviousIncoice = $this->tab_previous_situation_invoice;
5534
5535 $total2BillWT = 0;
5536 foreach ($TPreviousIncoice as &$fac) {
5537 $total2BillWT += $fac->total_ttc;
5538 }
5539 $total2BillWT += $this->total_ttc;
5540
5541 $retainedWarrantyAmount = $total2BillWT * $this->retained_warranty / 100;
5542 } else {
5543 return -1;
5544 }
5545 } else {
5546 // Because one day retained warranty could be used on standard invoices
5547 $retainedWarrantyAmount = $this->total_ttc * $this->retained_warranty / 100;
5548 }
5549
5550 if ($rounding < 0) {
5551 $rounding = min(getDolGlobalString('MAIN_MAX_DECIMALS_UNIT'), getDolGlobalString('MAIN_MAX_DECIMALS_TOT'));
5552 }
5553
5554 if ($rounding > 0) {
5555 return round($retainedWarrantyAmount, $rounding);
5556 }
5557
5558 return $retainedWarrantyAmount;
5559 }
5560
5567 public function setRetainedWarranty($value)
5568 {
5569 dol_syslog(get_class($this).'::setRetainedWarranty('.$value.')');
5570
5571 if ($this->status >= 0) {
5572 $fieldname = 'retained_warranty';
5573 $sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element;
5574 $sql .= " SET ".$fieldname." = ".((float) $value);
5575 $sql .= ' WHERE rowid='.((int) $this->id);
5576
5577 if ($this->db->query($sql)) {
5578 $this->retained_warranty = (float) $value;
5579 return 1;
5580 } else {
5581 dol_syslog(get_class($this).'::setRetainedWarranty Erreur '.$sql.' - '.$this->db->error());
5582 $this->error = $this->db->error();
5583 return -1;
5584 }
5585 } else {
5586 dol_syslog(get_class($this).'::setRetainedWarranty, status of the object is incompatible');
5587 $this->error = 'Status of the object is incompatible '.$this->status;
5588 return -2;
5589 }
5590 }
5591
5592
5600 public function setRetainedWarrantyDateLimit($timestamp, $dateYmd = '')
5601 {
5602 if (!$timestamp && $dateYmd) {
5603 $timestamp = $this->db->jdate($dateYmd);
5604 }
5605
5606
5607 dol_syslog(get_class($this).'::setRetainedWarrantyDateLimit('.$timestamp.')');
5608 if ($this->status >= 0) {
5609 $fieldname = 'retained_warranty_date_limit';
5610 $sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element;
5611 $sql .= " SET ".$fieldname." = ".(strval($timestamp) != '' ? "'".$this->db->idate($timestamp)."'" : 'null');
5612 $sql .= ' WHERE rowid = '.((int) $this->id);
5613
5614 if ($this->db->query($sql)) {
5615 $this->retained_warranty_date_limit = $timestamp;
5616 return 1;
5617 } else {
5618 dol_syslog(get_class($this).'::setRetainedWarrantyDateLimit Erreur '.$sql.' - '.$this->db->error());
5619 $this->error = $this->db->error();
5620 return -1;
5621 }
5622 } else {
5623 dol_syslog(get_class($this).'::setRetainedWarrantyDateLimit, status of the object is incompatible');
5624 $this->error = 'Status of the object is incompatible '.$this->status;
5625 return -2;
5626 }
5627 }
5628
5629
5641 public function sendEmailsRemindersOnInvoiceDueDate($nbdays = 0, $paymentmode = 'all', $template = '', $datetouse = 'duedate', $forcerecipient = '')
5642 {
5643 global $conf, $langs, $user;
5644
5645 $error = 0;
5646 $this->output = '';
5647 $this->error = '';
5648 $nbMailSend = 0;
5649 $errorsMsg = array();
5650
5651 $langs->load("bills");
5652
5653 if (!isModEnabled('facture')) { // Should not happen. If module disabled, cron job should not be visible.
5654 $this->output .= $langs->trans('ModuleNotEnabled', $langs->transnoentitiesnoconv("Facture"));
5655 return 0;
5656 }
5657 if (!in_array($datetouse, array('duedate', 'invoicedate'))) {
5658 $this->output .= 'Bad value for parameter datetouse. Must be "duedate" or "invoicedate"';
5659 return 0;
5660 }
5661 /*if (empty($conf->global->FACTURE_REMINDER_EMAIL)) {
5662 $langs->load("bills");
5663 $this->output .= $langs->trans('EventRemindersByEmailNotEnabled', $langs->transnoentitiesnoconv("Facture"));
5664 return 0;
5665 }
5666 */
5667
5668 require_once DOL_DOCUMENT_ROOT.'/core/lib/date.lib.php';
5669 require_once DOL_DOCUMENT_ROOT.'/core/class/html.formmail.class.php';
5670 require_once DOL_DOCUMENT_ROOT.'/core/class/CMailFile.class.php';
5671 $formmail = new FormMail($this->db);
5672
5673 $now = dol_now();
5674 $tmpidate = dol_get_first_hour(dol_time_plus_duree($now, $nbdays, 'd'), 'gmt');
5675
5676 $tmpinvoice = new Facture($this->db);
5677
5678 dol_syslog(__METHOD__." start", LOG_INFO);
5679
5680 // Select all action comm reminder
5681 $sql = "SELECT rowid as id FROM ".MAIN_DB_PREFIX."facture as f";
5682 if (!empty($paymentmode) && $paymentmode != 'all') {
5683 $sql .= ", ".MAIN_DB_PREFIX."c_paiement as cp";
5684 }
5685 $sql .= " WHERE f.paye = 0"; // Only unpaid
5686 $sql .= " AND f.fk_statut = ".self::STATUS_VALIDATED; // Only validated status
5687 if ($datetouse == 'invoicedate') {
5688 $sql .= " AND f.datef = '".$this->db->idate($tmpidate, 'gmt')."'";
5689 } else {
5690 $sql .= " AND f.date_lim_reglement = '".$this->db->idate($tmpidate, 'gmt')."'";
5691 }
5692 $sql .= " AND f.entity IN (".getEntity('facture', 0).")"; // One batch process only one company (no sharing)
5693 if (!empty($paymentmode) && $paymentmode != 'all') {
5694 $sql .= " AND f.fk_mode_reglement = cp.id AND cp.code = '".$this->db->escape($paymentmode)."'";
5695 }
5696 // TODO Add a filter to check there is no payment started yet
5697 if ($datetouse == 'invoicedate') {
5698 $sql .= $this->db->order("datef", "ASC");
5699 } else {
5700 $sql .= $this->db->order("date_lim_reglement", "ASC");
5701 }
5702
5703 $resql = $this->db->query($sql);
5704
5705 $stmpidate = dol_print_date($tmpidate, 'day', 'gmt');
5706 if ($datetouse == 'invoicedate') {
5707 $this->output .= $langs->transnoentitiesnoconv("SearchValidatedInvoicesWithDate", $stmpidate);
5708 } else {
5709 $this->output .= $langs->transnoentitiesnoconv("SearchUnpaidInvoicesWithDueDate", $stmpidate);
5710 }
5711 if (!empty($paymentmode) && $paymentmode != 'all') {
5712 $this->output .= ' ('.$langs->transnoentitiesnoconv("PaymentMode").' '.$paymentmode.')';
5713 }
5714 $this->output .= '<br>';
5715
5716 if ($resql) {
5717 while ($obj = $this->db->fetch_object($resql)) {
5718 // Create a loopError that is reset at each loop, this counter is added to the global counter at the end of loop
5719 $loopError = 0;
5720
5721 // Load event
5722 $res = $tmpinvoice->fetch($obj->id);
5723 if ($res > 0) {
5724 $tmpinvoice->fetch_thirdparty();
5725
5726 $outputlangs = new Translate('', $conf);
5727 if ($tmpinvoice->thirdparty->default_lang) {
5728 $outputlangs->setDefaultLang($tmpinvoice->thirdparty->default_lang);
5729 $outputlangs->loadLangs(array("main", "bills"));
5730 } else {
5731 $outputlangs = $langs;
5732 }
5733
5734 // Select email template according to language of recipient
5735 $arraymessage = $formmail->getEMailTemplate($this->db, 'facture_send', $user, $outputlangs, (is_numeric($template) ? $template : 0), 1, (is_numeric($template) ? '' : $template));
5736 if (is_numeric($arraymessage) && $arraymessage <= 0) {
5737 $langs->load("errors");
5738 $this->output .= $langs->trans('ErrorFailedToFindEmailTemplate', $template);
5739 return 0;
5740 }
5741
5742 // PREPARE EMAIL
5743 $errormesg = '';
5744
5745 // Make substitution in email content
5746 $substitutionarray = getCommonSubstitutionArray($outputlangs, 0, '', $tmpinvoice);
5747
5748 complete_substitutions_array($substitutionarray, $outputlangs, $tmpinvoice);
5749
5750 // Topic
5751 $sendTopic = make_substitutions(empty($arraymessage->topic) ? $outputlangs->transnoentitiesnoconv('InformationMessage') : $arraymessage->topic, $substitutionarray, $outputlangs, 1);
5752
5753 // Content
5754 $content = $outputlangs->transnoentitiesnoconv($arraymessage->content);
5755
5756 $sendContent = make_substitutions($content, $substitutionarray, $outputlangs, 1);
5757
5758 // Recipient
5759 $to = array();
5760 if ($forcerecipient) { // If a recipient was forced
5761 $to = array($forcerecipient);
5762 } else {
5763 $res = $tmpinvoice->fetch_thirdparty();
5764 $recipient = $tmpinvoice->thirdparty;
5765 if ($res > 0) {
5766 $tmparraycontact = $tmpinvoice->liste_contact(-1, 'external', 0, 'BILLING');
5767 if (is_array($tmparraycontact) && count($tmparraycontact) > 0) {
5768 foreach ($tmparraycontact as $data_email) {
5769 if (!empty($data_email['email'])) {
5770 $to[] = $tmpinvoice->thirdparty->contact_get_property($data_email['id'], 'email');
5771 }
5772 }
5773 }
5774 if (empty($to) && !empty($recipient->email)) {
5775 $to[] = $recipient->email;
5776 }
5777 if (empty($to)) {
5778 $errormesg = "Failed to send remind to thirdparty id=".$tmpinvoice->socid.". No email defined for invoice or customer.";
5779 $loopError++;
5780 }
5781 } else {
5782 $errormesg = "Failed to load recipient with thirdparty id=".$tmpinvoice->socid;
5783 $loopError++;
5784 }
5785 }
5786
5787 // Sender
5788 $from = getDolGlobalString('MAIN_MAIL_EMAIL_FROM');
5789 if (!empty($arraymessage->email_from)) { // If a sender is defined into template, we use it in priority
5790 $from = $arraymessage->email_from;
5791 }
5792 if (empty($from)) {
5793 $errormesg = "Failed to get sender into global setup MAIN_MAIL_EMAIL_FROM";
5794 $loopError++;
5795 }
5796
5797 if (!$loopError && !empty($to)) {
5798 $this->db->begin();
5799
5800 $to = implode(',', $to);
5801 if (!empty($arraymessage->email_to)) { // If a recipient is defined into template, we add it
5802 $to = $to.','.$arraymessage->email_to;
5803 }
5804
5805 // Errors Recipient
5806 $errors_to = $conf->global->MAIN_MAIL_ERRORS_TO;
5807
5808 $trackid = 'inv'.$tmpinvoice->id;
5809 $sendcontext = 'standard';
5810
5811 $email_tocc = '';
5812 if (!empty($arraymessage->email_tocc)) { // If a CC is defined into template, we use it
5813 $email_tocc = $arraymessage->email_tocc;
5814 }
5815
5816 $email_tobcc = '';
5817 if (!empty($arraymessage->email_tobcc)) { // If a BCC is defined into template, we use it
5818 $email_tobcc = $arraymessage->email_tobcc;
5819 }
5820
5821 //join file is asked
5822 $joinFile = [];
5823 $joinFileName = [];
5824 $joinFileMime = [];
5825 if ($arraymessage->joinfiles == 1 && !empty($tmpinvoice->last_main_doc)) {
5826 $joinFile[] = DOL_DATA_ROOT.'/'.$tmpinvoice->last_main_doc;
5827 $joinFileName[] = basename($tmpinvoice->last_main_doc);
5828 $joinFileMime[] = dol_mimetype(DOL_DATA_ROOT.'/'.$tmpinvoice->last_main_doc);
5829 }
5830
5831 // Mail Creation
5832 $cMailFile = new CMailFile($sendTopic, $to, $from, $sendContent, $joinFile, $joinFileMime, $joinFileName, $email_tocc, $email_tobcc, 0, 1, $errors_to, '', $trackid, '', $sendcontext, '');
5833
5834 // Sending Mail
5835 if ($cMailFile->sendfile()) {
5836 $nbMailSend++;
5837
5838 // Add a line into event table
5839 require_once DOL_DOCUMENT_ROOT.'/comm/action/class/actioncomm.class.php';
5840
5841 // Insert record of emails sent
5842 $actioncomm = new ActionComm($this->db);
5843
5844 $actioncomm->type_code = 'AC_OTH_AUTO'; // Event insert into agenda automatically
5845 $actioncomm->socid = $tmpinvoice->thirdparty->id; // To link to a company
5846 $actioncomm->contact_id = 0;
5847
5848 $actioncomm->code = 'AC_EMAIL';
5849 $actioncomm->label = 'sendEmailsRemindersOnInvoiceDueDateOK (nbdays='.$nbdays.' paymentmode='.$paymentmode.' template='.$template.' datetouse='.$datetouse.' forcerecipient='.$forcerecipient.')';
5850 $actioncomm->note_private = $sendContent;
5851 $actioncomm->fk_project = $tmpinvoice->fk_project;
5852 $actioncomm->datep = dol_now();
5853 $actioncomm->datef = $actioncomm->datep;
5854 $actioncomm->percentage = -1; // Not applicable
5855 $actioncomm->authorid = $user->id; // User saving action
5856 $actioncomm->userownerid = $user->id; // Owner of action
5857 // Fields when action is an email (content should be added into note)
5858 $actioncomm->email_msgid = $cMailFile->msgid;
5859 $actioncomm->email_subject = $sendTopic;
5860 $actioncomm->email_from = $from;
5861 $actioncomm->email_sender = '';
5862 $actioncomm->email_to = $to;
5863 //$actioncomm->email_tocc = $sendtocc;
5864 //$actioncomm->email_tobcc = $sendtobcc;
5865 //$actioncomm->email_subject = $subject;
5866 $actioncomm->errors_to = $errors_to;
5867
5868 $actioncomm->elementtype = 'invoice';
5869 $actioncomm->fk_element = $tmpinvoice->id;
5870
5871 //$actioncomm->extraparams = $extraparams;
5872
5873 $actioncomm->create($user);
5874 } else {
5875 $errormesg = $cMailFile->error.' : '.$to;
5876 $loopError++;
5877
5878 // Add a line into event table
5879 require_once DOL_DOCUMENT_ROOT.'/comm/action/class/actioncomm.class.php';
5880
5881 // Insert record of emails sent
5882 $actioncomm = new ActionComm($this->db);
5883
5884 $actioncomm->type_code = 'AC_OTH_AUTO'; // Event insert into agenda automatically
5885 $actioncomm->socid = $tmpinvoice->thirdparty->id; // To link to a company
5886 $actioncomm->contact_id = 0;
5887
5888 $actioncomm->code = 'AC_EMAIL';
5889 $actioncomm->label = 'sendEmailsRemindersOnInvoiceDueDateKO';
5890 $actioncomm->note_private = $errormesg;
5891 $actioncomm->fk_project = $tmpinvoice->fk_project;
5892 $actioncomm->datep = dol_now();
5893 $actioncomm->datef = $actioncomm->datep;
5894 $actioncomm->percentage = -1; // Not applicable
5895 $actioncomm->authorid = $user->id; // User saving action
5896 $actioncomm->userownerid = $user->id; // Owner of action
5897 // Fields when action is an email (content should be added into note)
5898 $actioncomm->email_msgid = $cMailFile->msgid;
5899 $actioncomm->email_from = $from;
5900 $actioncomm->email_sender = '';
5901 $actioncomm->email_to = $to;
5902 //$actioncomm->email_tocc = $sendtocc;
5903 //$actioncomm->email_tobcc = $sendtobcc;
5904 //$actioncomm->email_subject = $subject;
5905 $actioncomm->errors_to = $errors_to;
5906
5907 //$actioncomm->extraparams = $extraparams;
5908
5909 $actioncomm->create($user);
5910 }
5911
5912 $this->db->commit(); // We always commit
5913 }
5914
5915 if ($errormesg) {
5916 $errorsMsg[] = $errormesg;
5917 }
5918 } else {
5919 $errorsMsg[] = 'Failed to fetch record invoice with ID = '.$obj->id;
5920 $loopError++;
5921 }
5922
5923 $error += $loopError;
5924 }
5925 } else {
5926 $error++;
5927 }
5928
5929 if (!$error) {
5930 $this->output .= 'Nb of emails sent : '.$nbMailSend;
5931
5932 dol_syslog(__METHOD__." end - ".$this->output, LOG_INFO);
5933
5934 return 0;
5935 } else {
5936 $this->error = 'Nb of emails sent : '.$nbMailSend.', '.(!empty($errorsMsg)) ? join(', ', $errorsMsg) : $error;
5937
5938 dol_syslog(__METHOD__." end - ".$this->error, LOG_INFO);
5939
5940 return $error;
5941 }
5942 }
5943
5950 public function willBeLastOfSameType($allow_validated_drafts = false)
5951 {
5952 // get date of last validated invoices of same type
5953 $sql = "SELECT datef";
5954 $sql .= " FROM ".MAIN_DB_PREFIX."facture";
5955 $sql .= " WHERE type = " . (int) $this->type ;
5956 $sql .= " AND date_valid IS NOT NULL";
5957 $sql .= " AND entity IN (".getEntity('invoice').")";
5958 $sql .= " ORDER BY datef DESC LIMIT 1";
5959
5960 $result = $this->db->query($sql);
5961 if ($result) {
5962 // compare with current validation date
5963 if ($this->db->num_rows($result)) {
5964 $obj = $this->db->fetch_object($result);
5965 $last_date = $this->db->jdate($obj->datef);
5966 $invoice_date = $this->date;
5967
5968 $is_last_of_same_type = $invoice_date >= $last_date;
5969 if ($allow_validated_drafts) {
5970 $is_last_of_same_type = $is_last_of_same_type || (!strpos($this->ref, 'PROV') && $this->status == self::STATUS_DRAFT);
5971 }
5972
5973 return array($is_last_of_same_type, $last_date);
5974 } else {
5975 // element is first of type to be validated
5976 return array(true);
5977 }
5978 } else {
5979 dol_print_error($this->db);
5980 }
5981
5982 return array();
5983 }
5984
5992 public function getKanbanView($option = '', $arraydata = null)
5993 {
5994 global $langs;
5995
5996 $selected = (empty($arraydata['selected']) ? 0 : $arraydata['selected']);
5997
5998 $return = '<div class="box-flex-item box-flex-grow-zero">';
5999 $return .= '<div class="info-box info-box-sm">';
6000 $return .= '<span class="info-box-icon bg-infobox-action">';
6001 $return .= img_picto('', $this->picto);
6002 $return .= '</span>';
6003 $return .= '<div class="info-box-content">';
6004 $return .= '<span class="info-box-ref inline-block tdoverflowmax150 valignmiddle">'.(method_exists($this, 'getNomUrl') ? $this->getNomUrl(1) : $this->ref).'</span>';
6005 if ($selected >= 0) {
6006 $return .= '<input id="cb'.$this->id.'" class="flat checkforselect fright" type="checkbox" name="toselect[]" value="'.$this->id.'"'.($selected ? ' checked="checked"' : '').'>';
6007 }
6008 if (!empty($arraydata['thirdparty'])) {
6009 $return .= '<br><span class="info-box-label">'.$arraydata['thirdparty'].'</span>';
6010 }
6011 if (property_exists($this, 'date')) {
6012 $return .= '<br><span class="info-box-label">'.dol_print_date($this->date, 'day').'</span>';
6013 }
6014 if (property_exists($this, 'total_ht')) {
6015 $return .= ' &nbsp; <span class="info-box-label amount" title="'.dol_escape_htmltag($langs->trans("AmountHT")).'">'.price($this->total_ht);
6016 $return .= ' '.$langs->trans("HT");
6017 $return .= '</span>';
6018 }
6019 if (method_exists($this, 'getLibStatut')) {
6020 $alreadypaid = (empty($arraydata['alreadypaid']) ? 0 : $arraydata['alreadypaid']);
6021 $return .= '<br><div class="info-box-status">'.$this->getLibStatut(3, $alreadypaid).'</div>';
6022 }
6023 $return .= '</div>';
6024 $return .= '</div>';
6025 $return .= '</div>';
6026 return $return;
6027 }
6028}
6029
6035{
6039 public $element = 'facturedet';
6040
6044 public $table_element = 'facturedet';
6045
6049 public $oldline;
6050
6056
6058 public $desc;
6059 public $ref_ext; // External reference of the line
6060
6061 public $localtax1_type; // Local tax 1 type
6062 public $localtax2_type; // Local tax 2 type
6063 public $fk_remise_except; // Link to line into llx_remise_except
6064 public $rang = 0;
6065
6066 public $fk_fournprice;
6067 public $pa_ht;
6068 public $marge_tx;
6069 public $marque_tx;
6070
6074 public $tva_npr;
6075
6076 public $remise_percent;
6077
6086
6090 public $batch;
6094 public $fk_warehouse;
6095
6096
6097 public $origin;
6098 public $origin_id;
6099
6103 public $fk_code_ventilation = 0;
6104
6105
6106 public $date_start;
6107 public $date_end;
6108
6109 public $skip_update_total; // Skip update price total for special lines
6110
6114 public $situation_percent;
6115
6119 public $fk_prev_id;
6120
6126 public function __construct($db)
6127 {
6128 $this->db = $db;
6129 }
6130
6137 public function fetch($rowid)
6138 {
6139 $sql = 'SELECT fd.rowid, fd.fk_facture, fd.fk_parent_line, fd.fk_product, fd.product_type, fd.label as custom_label, fd.description, fd.price, fd.qty, fd.vat_src_code, fd.tva_tx,';
6140 $sql .= ' fd.localtax1_tx, fd. localtax2_tx, fd.remise, fd.remise_percent, fd.fk_remise_except, fd.subprice, fd.ref_ext,';
6141 $sql .= ' fd.date_start as date_start, fd.date_end as date_end, fd.fk_product_fournisseur_price as fk_fournprice, fd.buy_price_ht as pa_ht,';
6142 $sql .= ' fd.info_bits, fd.special_code, fd.total_ht, fd.total_tva, fd.total_ttc, fd.total_localtax1, fd.total_localtax2, fd.rang,';
6143 $sql .= ' fd.fk_code_ventilation,';
6144 $sql .= ' fd.fk_unit, fd.fk_user_author, fd.fk_user_modif,';
6145 $sql .= ' fd.situation_percent, fd.fk_prev_id,';
6146 $sql .= ' fd.multicurrency_subprice,';
6147 $sql .= ' fd.multicurrency_total_ht,';
6148 $sql .= ' fd.multicurrency_total_tva,';
6149 $sql .= ' fd.multicurrency_total_ttc,';
6150 $sql .= ' p.ref as product_ref, p.label as product_label, p.description as product_desc';
6151 $sql .= ' FROM '.MAIN_DB_PREFIX.'facturedet as fd';
6152 $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'product as p ON fd.fk_product = p.rowid';
6153 $sql .= ' WHERE fd.rowid = '.((int) $rowid);
6154
6155 $result = $this->db->query($sql);
6156 if ($result) {
6157 $objp = $this->db->fetch_object($result);
6158
6159 if (!$objp) {
6160 $this->error = 'InvoiceLine with id '. $rowid .' not found sql='.$sql;
6161 return 0;
6162 }
6163
6164 $this->rowid = $objp->rowid;
6165 $this->id = $objp->rowid;
6166 $this->fk_facture = $objp->fk_facture;
6167 $this->fk_parent_line = $objp->fk_parent_line;
6168 $this->label = $objp->custom_label;
6169 $this->desc = $objp->description;
6170 $this->qty = $objp->qty;
6171 $this->subprice = $objp->subprice;
6172 $this->ref_ext = $objp->ref_ext;
6173 $this->vat_src_code = $objp->vat_src_code;
6174 $this->tva_tx = $objp->tva_tx;
6175 $this->localtax1_tx = $objp->localtax1_tx;
6176 $this->localtax2_tx = $objp->localtax2_tx;
6177 $this->remise_percent = $objp->remise_percent;
6178 $this->fk_remise_except = $objp->fk_remise_except;
6179 $this->fk_product = $objp->fk_product;
6180 $this->product_type = $objp->product_type;
6181 $this->date_start = $this->db->jdate($objp->date_start);
6182 $this->date_end = $this->db->jdate($objp->date_end);
6183 $this->info_bits = $objp->info_bits;
6184 $this->tva_npr = ($objp->info_bits & 1 == 1) ? 1 : 0;
6185 $this->special_code = $objp->special_code;
6186 $this->total_ht = $objp->total_ht;
6187 $this->total_tva = $objp->total_tva;
6188 $this->total_localtax1 = $objp->total_localtax1;
6189 $this->total_localtax2 = $objp->total_localtax2;
6190 $this->total_ttc = $objp->total_ttc;
6191 $this->fk_code_ventilation = $objp->fk_code_ventilation;
6192 $this->rang = $objp->rang;
6193 $this->fk_fournprice = $objp->fk_fournprice;
6194 $marginInfos = getMarginInfos($objp->subprice, $objp->remise_percent, $objp->tva_tx, $objp->localtax1_tx, $objp->localtax2_tx, $this->fk_fournprice, $objp->pa_ht);
6195 $this->pa_ht = $marginInfos[0];
6196 $this->marge_tx = $marginInfos[1];
6197 $this->marque_tx = $marginInfos[2];
6198
6199 $this->ref = $objp->product_ref; // deprecated
6200
6201 $this->product_ref = $objp->product_ref;
6202 $this->product_label = $objp->product_label;
6203 $this->product_desc = $objp->product_desc;
6204
6205 $this->fk_unit = $objp->fk_unit;
6206 $this->fk_user_modif = $objp->fk_user_modif;
6207 $this->fk_user_author = $objp->fk_user_author;
6208
6209 $this->situation_percent = $objp->situation_percent;
6210 $this->fk_prev_id = $objp->fk_prev_id;
6211
6212 $this->multicurrency_subprice = $objp->multicurrency_subprice;
6213 $this->multicurrency_total_ht = $objp->multicurrency_total_ht;
6214 $this->multicurrency_total_tva = $objp->multicurrency_total_tva;
6215 $this->multicurrency_total_ttc = $objp->multicurrency_total_ttc;
6216
6217 $this->fetch_optionals();
6218
6219 $this->db->free($result);
6220
6221 return 1;
6222 } else {
6223 $this->error = $this->db->lasterror();
6224 return -1;
6225 }
6226 }
6227
6235 public function insert($notrigger = 0, $noerrorifdiscountalreadylinked = 0)
6236 {
6237 global $langs, $user, $conf;
6238
6239 $error = 0;
6240
6241 $pa_ht_isemptystring = (empty($this->pa_ht) && $this->pa_ht == ''); // If true, we can use a default value. If this->pa_ht = '0', we must use '0'.
6242
6243 dol_syslog(get_class($this)."::insert rang=".$this->rang, LOG_DEBUG);
6244
6245 // Clean parameters
6246 $this->desc = trim($this->desc);
6247 if (empty($this->tva_tx)) {
6248 $this->tva_tx = 0;
6249 }
6250 if (empty($this->localtax1_tx)) {
6251 $this->localtax1_tx = 0;
6252 }
6253 if (empty($this->localtax2_tx)) {
6254 $this->localtax2_tx = 0;
6255 }
6256 if (empty($this->localtax1_type)) {
6257 $this->localtax1_type = 0;
6258 }
6259 if (empty($this->localtax2_type)) {
6260 $this->localtax2_type = 0;
6261 }
6262 if (empty($this->total_localtax1)) {
6263 $this->total_localtax1 = 0;
6264 }
6265 if (empty($this->total_localtax2)) {
6266 $this->total_localtax2 = 0;
6267 }
6268 if (empty($this->rang)) {
6269 $this->rang = 0;
6270 }
6271 if (empty($this->remise_percent)) {
6272 $this->remise_percent = 0;
6273 }
6274 if (empty($this->info_bits)) {
6275 $this->info_bits = 0;
6276 }
6277 if (empty($this->subprice)) {
6278 $this->subprice = 0;
6279 }
6280 if (empty($this->ref_ext)) {
6281 $this->ref_ext = '';
6282 }
6283 if (empty($this->special_code)) {
6284 $this->special_code = 0;
6285 }
6286 if (empty($this->fk_parent_line)) {
6287 $this->fk_parent_line = 0;
6288 }
6289 if (empty($this->fk_prev_id)) {
6290 $this->fk_prev_id = 0;
6291 }
6292 if (!isset($this->situation_percent) || $this->situation_percent > 100 || (string) $this->situation_percent == '') {
6293 $this->situation_percent = 100;
6294 }
6295
6296 if (empty($this->pa_ht)) {
6297 $this->pa_ht = 0;
6298 }
6299 if (empty($this->multicurrency_subprice)) {
6300 $this->multicurrency_subprice = 0;
6301 }
6302 if (empty($this->multicurrency_total_ht)) {
6303 $this->multicurrency_total_ht = 0;
6304 }
6305 if (empty($this->multicurrency_total_tva)) {
6306 $this->multicurrency_total_tva = 0;
6307 }
6308 if (empty($this->multicurrency_total_ttc)) {
6309 $this->multicurrency_total_ttc = 0;
6310 }
6311
6312 // if buy price not defined, define buyprice as configured in margin admin
6313 if ($this->pa_ht == 0 && $pa_ht_isemptystring) {
6314 if (($result = $this->defineBuyPrice($this->subprice, $this->remise_percent, $this->fk_product)) < 0) {
6315 return $result;
6316 } else {
6317 $this->pa_ht = $result;
6318 }
6319 }
6320
6321 // Check parameters
6322 if ($this->product_type < 0) {
6323 $this->error = 'ErrorProductTypeMustBe0orMore';
6324 return -1;
6325 }
6326 if (!empty($this->fk_product) && $this->fk_product > 0) {
6327 // Check product exists
6328 $result = Product::isExistingObject('product', $this->fk_product);
6329 if ($result <= 0) {
6330 $this->error = 'ErrorProductIdDoesNotExists';
6331 dol_syslog(get_class($this)."::insert Error ".$this->error, LOG_ERR);
6332 return -1;
6333 }
6334 }
6335
6336 $this->db->begin();
6337
6338 // Update line in database
6339 $sql = 'INSERT INTO '.MAIN_DB_PREFIX.'facturedet';
6340 $sql .= ' (fk_facture, fk_parent_line, label, description, qty,';
6341 $sql .= ' vat_src_code, tva_tx, localtax1_tx, localtax2_tx, localtax1_type, localtax2_type,';
6342 $sql .= ' fk_product, product_type, remise_percent, subprice, ref_ext, fk_remise_except,';
6343 $sql .= ' date_start, date_end, fk_code_ventilation, ';
6344 $sql .= ' rang, special_code, fk_product_fournisseur_price, buy_price_ht,';
6345 $sql .= ' info_bits, total_ht, total_tva, total_ttc, total_localtax1, total_localtax2,';
6346 $sql .= ' situation_percent, fk_prev_id,';
6347 $sql .= ' fk_unit, fk_user_author, fk_user_modif,';
6348 $sql .= ' fk_multicurrency, multicurrency_code, multicurrency_subprice, multicurrency_total_ht, multicurrency_total_tva, multicurrency_total_ttc';
6349 $sql .= ')';
6350 $sql .= " VALUES (".$this->fk_facture.",";
6351 $sql .= " ".($this->fk_parent_line > 0 ? $this->fk_parent_line : "null").",";
6352 $sql .= " ".(!empty($this->label) ? "'".$this->db->escape($this->label)."'" : "null").",";
6353 $sql .= " '".$this->db->escape($this->desc)."',";
6354 $sql .= " ".price2num($this->qty).",";
6355 $sql .= " ".(empty($this->vat_src_code) ? "''" : "'".$this->db->escape($this->vat_src_code)."'").",";
6356 $sql .= " ".price2num($this->tva_tx).",";
6357 $sql .= " ".price2num($this->localtax1_tx).",";
6358 $sql .= " ".price2num($this->localtax2_tx).",";
6359 $sql .= " '".$this->db->escape($this->localtax1_type)."',";
6360 $sql .= " '".$this->db->escape($this->localtax2_type)."',";
6361 $sql .= ' '.((!empty($this->fk_product) && $this->fk_product > 0) ? $this->fk_product : "null").',';
6362 $sql .= " ".((int) $this->product_type).",";
6363 $sql .= " ".price2num($this->remise_percent).",";
6364 $sql .= " ".price2num($this->subprice).",";
6365 $sql .= " '".$this->db->escape($this->ref_ext)."',";
6366 $sql .= ' '.(!empty($this->fk_remise_except) ? $this->fk_remise_except : "null").',';
6367 $sql .= " ".(!empty($this->date_start) ? "'".$this->db->idate($this->date_start)."'" : "null").",";
6368 $sql .= " ".(!empty($this->date_end) ? "'".$this->db->idate($this->date_end)."'" : "null").",";
6369 $sql .= ' '.((int) $this->fk_code_ventilation).',';
6370 $sql .= ' '.((int) $this->rang).',';
6371 $sql .= ' '.((int) $this->special_code).',';
6372 $sql .= ' '.(!empty($this->fk_fournprice) ? $this->fk_fournprice : "null").',';
6373 $sql .= ' '.price2num($this->pa_ht).',';
6374 $sql .= " '".$this->db->escape($this->info_bits)."',";
6375 $sql .= " ".price2num($this->total_ht).",";
6376 $sql .= " ".price2num($this->total_tva).",";
6377 $sql .= " ".price2num($this->total_ttc).",";
6378 $sql .= " ".price2num($this->total_localtax1).",";
6379 $sql .= " ".price2num($this->total_localtax2);
6380 $sql .= ", ".((float) $this->situation_percent);
6381 $sql .= ", ".(!empty($this->fk_prev_id) ? $this->fk_prev_id : "null");
6382 $sql .= ", ".(!$this->fk_unit ? 'NULL' : $this->fk_unit);
6383 $sql .= ", ".((int) $user->id);
6384 $sql .= ", ".((int) $user->id);
6385 $sql .= ", ".(int) $this->fk_multicurrency;
6386 $sql .= ", '".$this->db->escape($this->multicurrency_code)."'";
6387 $sql .= ", ".price2num($this->multicurrency_subprice);
6388 $sql .= ", ".price2num($this->multicurrency_total_ht);
6389 $sql .= ", ".price2num($this->multicurrency_total_tva);
6390 $sql .= ", ".price2num($this->multicurrency_total_ttc);
6391 $sql .= ')';
6392
6393 dol_syslog(get_class($this)."::insert", LOG_DEBUG);
6394 $resql = $this->db->query($sql);
6395 if ($resql) {
6396 $this->id = $this->db->last_insert_id(MAIN_DB_PREFIX.'facturedet');
6397 $this->rowid = $this->id; // For backward compatibility
6398
6399 if (!$error) {
6400 $result = $this->insertExtraFields();
6401 if ($result < 0) {
6402 $error++;
6403 }
6404 }
6405
6406 // If fk_remise_except is defined, the discount is linked to the invoice
6407 // which flags it as "consumed".
6408 if ($this->fk_remise_except && empty($error)) {
6409 $discount = new DiscountAbsolute($this->db);
6410 $result = $discount->fetch($this->fk_remise_except);
6411 if ($result >= 0) {
6412 // Check if discount was found
6413 if ($result > 0) {
6414 // Check if discount not already affected to another invoice
6415 if ($discount->fk_facture_line > 0) {
6416 if (empty($noerrorifdiscountalreadylinked)) {
6417 $this->error = $langs->trans("ErrorDiscountAlreadyUsed", $discount->id);
6418 dol_syslog(get_class($this)."::insert Error ".$this->error, LOG_ERR);
6419 $this->db->rollback();
6420 return -3;
6421 }
6422 } else {
6423 $result = $discount->link_to_invoice($this->rowid, 0);
6424 if ($result < 0) {
6425 $this->error = $discount->error;
6426 dol_syslog(get_class($this)."::insert Error ".$this->error, LOG_ERR);
6427 $this->db->rollback();
6428 return -3;
6429 }
6430 }
6431 } else {
6432 $this->error = $langs->trans("ErrorADiscountThatHasBeenRemovedIsIncluded");
6433 dol_syslog(get_class($this)."::insert Error ".$this->error, LOG_ERR);
6434 $this->db->rollback();
6435 return -3;
6436 }
6437 } else {
6438 $this->error = $discount->error;
6439 dol_syslog(get_class($this)."::insert Error ".$this->error, LOG_ERR);
6440 $this->db->rollback();
6441 return -3;
6442 }
6443 }
6444
6445 if (!$notrigger && empty($error)) {
6446 // Call trigger
6447 $result = $this->call_trigger('LINEBILL_INSERT', $user);
6448 if ($result < 0) {
6449 $this->db->rollback();
6450 return -2;
6451 }
6452 // End call triggers
6453 }
6454
6455 if (!$error) {
6456 $this->db->commit();
6457 return $this->id;
6458 }
6459
6460 foreach ($this->errors as $errmsg) {
6461 dol_syslog(get_class($this)."::insert ".$errmsg, LOG_ERR);
6462 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
6463 }
6464 $this->db->rollback();
6465 return -1 * $error;
6466 } else {
6467 $this->error = $this->db->lasterror();
6468 $this->db->rollback();
6469 return -2;
6470 }
6471 }
6472
6480 public function update($user = null, $notrigger = 0)
6481 {
6482 global $user, $conf;
6483
6484 $error = 0;
6485
6486 $pa_ht_isemptystring = (empty($this->pa_ht) && $this->pa_ht == ''); // If true, we can use a default value. If this->pa_ht = '0', we must use '0'.
6487
6488 // Clean parameters
6489 $this->desc = trim($this->desc);
6490 if (empty($this->ref_ext)) {
6491 $this->ref_ext = '';
6492 }
6493 if (empty($this->tva_tx)) {
6494 $this->tva_tx = 0;
6495 }
6496 if (empty($this->localtax1_tx)) {
6497 $this->localtax1_tx = 0;
6498 }
6499 if (empty($this->localtax2_tx)) {
6500 $this->localtax2_tx = 0;
6501 }
6502 if (empty($this->localtax1_type)) {
6503 $this->localtax1_type = 0;
6504 }
6505 if (empty($this->localtax2_type)) {
6506 $this->localtax2_type = 0;
6507 }
6508 if (empty($this->total_localtax1)) {
6509 $this->total_localtax1 = 0;
6510 }
6511 if (empty($this->total_localtax2)) {
6512 $this->total_localtax2 = 0;
6513 }
6514 if (empty($this->remise_percent)) {
6515 $this->remise_percent = 0;
6516 }
6517 if (empty($this->info_bits)) {
6518 $this->info_bits = 0;
6519 }
6520 if (empty($this->special_code)) {
6521 $this->special_code = 0;
6522 }
6523 if (empty($this->product_type)) {
6524 $this->product_type = 0;
6525 }
6526 if (empty($this->fk_parent_line)) {
6527 $this->fk_parent_line = 0;
6528 }
6529 if (!isset($this->situation_percent) || $this->situation_percent > 100 || (string) $this->situation_percent == '') {
6530 $this->situation_percent = 100;
6531 }
6532 if (empty($this->pa_ht)) {
6533 $this->pa_ht = 0;
6534 }
6535
6536 if (empty($this->multicurrency_subprice)) {
6537 $this->multicurrency_subprice = 0;
6538 }
6539 if (empty($this->multicurrency_total_ht)) {
6540 $this->multicurrency_total_ht = 0;
6541 }
6542 if (empty($this->multicurrency_total_tva)) {
6543 $this->multicurrency_total_tva = 0;
6544 }
6545 if (empty($this->multicurrency_total_ttc)) {
6546 $this->multicurrency_total_ttc = 0;
6547 }
6548
6549 // Check parameters
6550 if ($this->product_type < 0) {
6551 return -1;
6552 }
6553
6554 // if buy price not provided, define buyprice as configured in margin admin
6555 if ($this->pa_ht == 0 && $pa_ht_isemptystring) {
6556 // We call defineBuyPrice only if data was not provided (if input was '0', we will not go here and value will remaine '0')
6557 $result = $this->defineBuyPrice($this->subprice, $this->remise_percent, $this->fk_product);
6558 if ($result < 0) {
6559 return $result;
6560 } else {
6561 $this->pa_ht = $result;
6562 }
6563 }
6564
6565 $this->db->begin();
6566
6567 // Update line in database
6568 $sql = "UPDATE ".MAIN_DB_PREFIX."facturedet SET";
6569 $sql .= " description='".$this->db->escape($this->desc)."'";
6570 $sql .= ", ref_ext='".$this->db->escape($this->ref_ext)."'";
6571 $sql .= ", label=".(!empty($this->label) ? "'".$this->db->escape($this->label)."'" : "null");
6572 $sql .= ", subprice=".price2num($this->subprice);
6573 $sql .= ", remise_percent=".price2num($this->remise_percent);
6574 if ($this->fk_remise_except) {
6575 $sql .= ", fk_remise_except=".$this->fk_remise_except;
6576 } else {
6577 $sql .= ", fk_remise_except=null";
6578 }
6579 $sql .= ", vat_src_code = '".(empty($this->vat_src_code) ? '' : $this->db->escape($this->vat_src_code))."'";
6580 $sql .= ", tva_tx=".price2num($this->tva_tx);
6581 $sql .= ", localtax1_tx=".price2num($this->localtax1_tx);
6582 $sql .= ", localtax2_tx=".price2num($this->localtax2_tx);
6583 $sql .= ", localtax1_type='".$this->db->escape($this->localtax1_type)."'";
6584 $sql .= ", localtax2_type='".$this->db->escape($this->localtax2_type)."'";
6585 $sql .= ", qty=".price2num($this->qty);
6586 $sql .= ", date_start=".(!empty($this->date_start) ? "'".$this->db->idate($this->date_start)."'" : "null");
6587 $sql .= ", date_end=".(!empty($this->date_end) ? "'".$this->db->idate($this->date_end)."'" : "null");
6588 $sql .= ", product_type=".$this->product_type;
6589 $sql .= ", info_bits='".$this->db->escape($this->info_bits)."'";
6590 $sql .= ", special_code='".$this->db->escape($this->special_code)."'";
6591 if (empty($this->skip_update_total)) {
6592 $sql .= ", total_ht=".price2num($this->total_ht);
6593 $sql .= ", total_tva=".price2num($this->total_tva);
6594 $sql .= ", total_ttc=".price2num($this->total_ttc);
6595 $sql .= ", total_localtax1=".price2num($this->total_localtax1);
6596 $sql .= ", total_localtax2=".price2num($this->total_localtax2);
6597 }
6598 $sql .= ", fk_product_fournisseur_price=".(!empty($this->fk_fournprice) ? "'".$this->db->escape($this->fk_fournprice)."'" : "null");
6599 $sql .= ", buy_price_ht=".(($this->pa_ht || (string) $this->pa_ht === '0') ? price2num($this->pa_ht) : "null"); // $this->pa_ht should always be defined (set to 0 or to sell price depending on option)
6600 $sql .= ", fk_parent_line=".($this->fk_parent_line > 0 ? $this->fk_parent_line : "null");
6601 if (!empty($this->rang)) {
6602 $sql .= ", rang=".((int) $this->rang);
6603 }
6604 $sql .= ", situation_percent = ".((float) $this->situation_percent);
6605 $sql .= ", fk_unit = ".(!$this->fk_unit ? 'NULL' : $this->fk_unit);
6606 $sql .= ", fk_user_modif = ".((int) $user->id);
6607
6608 // Multicurrency
6609 $sql .= ", multicurrency_subprice=".price2num($this->multicurrency_subprice);
6610 $sql .= ", multicurrency_total_ht=".price2num($this->multicurrency_total_ht);
6611 $sql .= ", multicurrency_total_tva=".price2num($this->multicurrency_total_tva);
6612 $sql .= ", multicurrency_total_ttc=".price2num($this->multicurrency_total_ttc);
6613
6614 $sql .= " WHERE rowid = ".((int) $this->rowid);
6615
6616 dol_syslog(get_class($this)."::update", LOG_DEBUG);
6617 $resql = $this->db->query($sql);
6618 if ($resql) {
6619 if (!$error) {
6620 $this->id = $this->rowid;
6621 $result = $this->insertExtraFields();
6622 if ($result < 0) {
6623 $error++;
6624 }
6625 }
6626
6627 if (!$error && !$notrigger) {
6628 // Call trigger
6629 $result = $this->call_trigger('LINEBILL_MODIFY', $user);
6630 if ($result < 0) {
6631 $this->db->rollback();
6632 return -2;
6633 }
6634 // End call triggers
6635 }
6636
6637 if (!$error) {
6638 $this->db->commit();
6639 return 1;
6640 }
6641
6642 foreach ($this->errors as $errmsg) {
6643 dol_syslog(get_class($this)."::update ".$errmsg, LOG_ERR);
6644 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
6645 }
6646 $this->db->rollback();
6647 return -1 * $error;
6648 } else {
6649 $this->error = $this->db->error();
6650 $this->db->rollback();
6651 return -2;
6652 }
6653 }
6654
6662 public function delete($tmpuser = null, $notrigger = false)
6663 {
6664 global $user;
6665
6666 $this->db->begin();
6667
6668 // Call trigger
6669 if (empty($notrigger)) {
6670 $result = $this->call_trigger('LINEBILL_DELETE', $user);
6671 if ($result < 0) {
6672 $this->db->rollback();
6673 return -1;
6674 }
6675 }
6676 // End call triggers
6677
6678 // extrafields
6679 $result = $this->deleteExtraFields();
6680 if ($result < 0) {
6681 $this->db->rollback();
6682 return -1;
6683 }
6684
6685 // Free discount linked to invoice line
6686 $sql = 'UPDATE '.MAIN_DB_PREFIX.'societe_remise_except';
6687 $sql .= ' SET fk_facture_line = NULL';
6688 $sql .= ' WHERE fk_facture_line = '.((int) $this->id);
6689
6690 dol_syslog(get_class($this)."::deleteline", LOG_DEBUG);
6691 $result = $this->db->query($sql);
6692 if (!$result) {
6693 $this->error = $this->db->error();
6694 $this->errors[] = $this->error;
6695 $this->db->rollback();
6696 return -1;
6697 }
6698
6699 $sql = 'UPDATE '.MAIN_DB_PREFIX.'element_time';
6700 $sql .= ' SET invoice_id = NULL, invoice_line_id = NULL';
6701 $sql .= ' WHERE invoice_line_id = '.((int) $this->id);
6702 if (!$this->db->query($sql)) {
6703 $this->error = $this->db->error()." sql=".$sql;
6704 $this->errors[] = $this->error;
6705 $this->db->rollback();
6706 return -1;
6707 }
6708
6709 $sql = "DELETE FROM ".MAIN_DB_PREFIX."facturedet WHERE rowid = ".((int) $this->id);
6710
6711 if ($this->db->query($sql)) {
6712 $this->db->commit();
6713 return 1;
6714 } else {
6715 $this->error = $this->db->error()." sql=".$sql;
6716 $this->errors[] = $this->error;
6717 $this->db->rollback();
6718 return -1;
6719 }
6720 }
6721
6722 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6729 public function update_total()
6730 {
6731 // phpcs:enable
6732 $this->db->begin();
6733 dol_syslog(get_class($this)."::update_total", LOG_DEBUG);
6734
6735 // Clean parameters
6736 if (empty($this->total_localtax1)) {
6737 $this->total_localtax1 = 0;
6738 }
6739 if (empty($this->total_localtax2)) {
6740 $this->total_localtax2 = 0;
6741 }
6742
6743 // Update line in database
6744 $sql = "UPDATE ".MAIN_DB_PREFIX."facturedet SET";
6745 $sql .= " total_ht=".price2num($this->total_ht);
6746 $sql .= ",total_tva=".price2num($this->total_tva);
6747 $sql .= ",total_localtax1=".price2num($this->total_localtax1);
6748 $sql .= ",total_localtax2=".price2num($this->total_localtax2);
6749 $sql .= ",total_ttc=".price2num($this->total_ttc);
6750 $sql .= " WHERE rowid = ".((int) $this->rowid);
6751
6752 dol_syslog(get_class($this)."::update_total", LOG_DEBUG);
6753
6754 $resql = $this->db->query($sql);
6755 if ($resql) {
6756 $this->db->commit();
6757 return 1;
6758 } else {
6759 $this->error = $this->db->error();
6760 $this->db->rollback();
6761 return -2;
6762 }
6763 }
6764
6765 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6774 public function get_prev_progress($invoiceid, $include_credit_note = true)
6775 {
6776 // phpcs:enable
6777 global $invoicecache;
6778
6779 if (is_null($this->fk_prev_id) || empty($this->fk_prev_id) || $this->fk_prev_id == "") {
6780 return 0;
6781 } else {
6782 // If invoice is not a situation invoice, this->fk_prev_id is used for something else
6783 if (!isset($invoicecache[$invoiceid])) {
6784 $invoicecache[$invoiceid] = new Facture($this->db);
6785 $invoicecache[$invoiceid]->fetch($invoiceid);
6786 }
6787 if ($invoicecache[$invoiceid]->type != Facture::TYPE_SITUATION) {
6788 return 0;
6789 }
6790
6791 $sql = "SELECT situation_percent FROM ".MAIN_DB_PREFIX."facturedet WHERE rowid = ".((int) $this->fk_prev_id);
6792 $resql = $this->db->query($sql);
6793 if ($resql && $this->db->num_rows($resql) > 0) {
6794 $res = $this->db->fetch_array($resql);
6795
6796 $returnPercent = (float) $res['situation_percent'];
6797
6798 if ($include_credit_note) {
6799 $sql = 'SELECT fd.situation_percent FROM '.MAIN_DB_PREFIX.'facturedet fd';
6800 $sql .= ' JOIN '.MAIN_DB_PREFIX.'facture f ON (f.rowid = fd.fk_facture) ';
6801 $sql .= " WHERE fd.fk_prev_id = ".((int) $this->fk_prev_id);
6802 $sql .= " AND f.situation_cycle_ref = ".((int) $invoicecache[$invoiceid]->situation_cycle_ref); // Prevent cycle outed
6803 $sql .= " AND f.type = ".Facture::TYPE_CREDIT_NOTE;
6804
6805 $res = $this->db->query($sql);
6806 if ($res) {
6807 while ($obj = $this->db->fetch_object($res)) {
6808 $returnPercent = $returnPercent + (float) $obj->situation_percent;
6809 }
6810 } else {
6811 dol_print_error($this->db);
6812 }
6813 }
6814
6815 return $returnPercent;
6816 } else {
6817 $this->error = $this->db->error();
6818 dol_syslog(get_class($this)."::select Error ".$this->error, LOG_ERR);
6819 $this->db->rollback();
6820 return -1;
6821 }
6822 }
6823 }
6824}
print $langs trans("AuditedSecurityEvents").'</strong >< span class="opacitymedium"></span >< br > status
Definition security.php:604
$object ref
Definition info.php:79
Class to manage agenda events (actions)
Class to send emails (with attachments or not) Usage: $mailfile = new CMailFile($subject,...
Superclass for invoices classes.
getSommePaiement($multicurrency=0)
Return amount of payments already done.
calculate_date_lim_reglement($cond_reglement=0)
Returns an invoice payment deadline based on the invoice settlement conditions and billing date.
is_erasable()
Return if an invoice can be deleted Rule is: If invoice is draft and has a temporary ref -> yes (1) I...
Parent class of all other business classes for details of elements (invoices, contracts,...
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...
line_order($renum=false, $rowidorder='ASC', $fk_parent_line=true)
Save a new position (field rang) for details lines.
deleteEcmFiles($mode=0)
Delete related files of object in database.
add_object_linked($origin=null, $origin_id=null, $f_user=null, $notrigger=0)
Add an object link into llx_element_element.
defineBuyPrice($unitPrice=0.0, $discountPercent=0.0, $fk_product=0)
Get buy price to use for margin calculation.
commonGenerateDocument($modelspath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams=null)
Common function for all objects extending CommonObject for generating documents.
fetch_thirdparty($force_thirdparty_id=0)
Load the third party of object, from id $this->socid or $this->fk_soc, into this->thirdparty.
getIdContact($source, $code, $status=0)
Return id of contacts for a source and a contact code.
deleteObjectLinked($sourceid=null, $sourcetype='', $targetid=null, $targettype='', $rowid=0, $f_user=null, $notrigger=0)
Delete all links between an object $this.
setErrorsFromObject($object)
setErrorsFromObject
static isExistingObject($element, $id, $ref='', $ref_ext='')
Check an object id/ref exists If you don't need/want to instantiate object and just need to know if o...
updateRangOfLine($rowid, $rang)
Update position of line (rang)
update_price($exclspec=0, $roundingadjust='none', $nodatabaseupdate=0, $seller=null)
Update total_ht, total_ttc, total_vat, total_localtax1, total_localtax2 for an object (sum of lines).
deleteExtraFields()
Delete all extra fields values for the current object.
fetchObjectLinked($sourceid=null, $sourcetype='', $targetid=null, $targettype='', $clause='OR', $alsosametype=1, $orderby='sourcetype', $loadalsoobjects=1)
Fetch array of objects linked to current object (object of enabled modules only).
static commonReplaceThirdparty(DoliDB $dbs, $origin_id, $dest_id, array $tables, $ignoreerrors=0)
Function used to replace a thirdparty id with another one.
static commonReplaceProduct(DoliDB $dbs, $origin_id, $dest_id, array $tables, $ignoreerrors=0)
Function used to replace a product id with another one.
line_max($fk_parent_line=0)
Get max value used for position of line (rang)
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.
add_contact($fk_socpeople, $type_contact, $source='external', $notrigger=0)
Add a link between element $this->element and a contact.
Class to manage absolute discounts.
Class to manage Dolibarr database access.
Class to manage warehouses.
Class to manage shipments.
Class to manage invoices.
createFromClone(User $user, $fromid=0)
Load an object from its id and create a new one in database.
setDraft($user, $idwarehouse=-1)
Set draft status.
$fk_facture_source
id of source invoice if replacement invoice or credit note
getIdShippingContact()
Retourne id des contacts clients de livraison.
setFinal(User $user, $notrigger=0)
Sets the invoice as a final situation.
setCanceled($user, $close_code='', $close_note='')
Tag invoice as canceled, with no payment on it (example for replacement invoice or payment never rece...
static createDepositFromOrigin(CommonObject $origin, $date, $payment_terms_id, User $user, $notrigger=0, $autoValidateDeposit=false, $overrideFields=array())
Creates a deposit from a proposal or an order by grouping lines by VAT rates.
static replaceThirdparty(DoliDB $dbs, $origin_id, $dest_id)
Function used to replace a thirdparty id with another one.
list_replacable_invoices($socid=0)
Return list of invoices qualified to be replaced by another invoice.
insert_discount($idremise)
Add a discount line into an invoice (as an invoice line) using an existing absolute discount (Consume...
createFromOrder($object, User $user)
Load an object from an order and create a new invoice into database.
load_state_board()
Load indicators for dashboard (this->nbtodo and this->nbtodolate)
willBeLastOfSameType($allow_validated_drafts=false)
See if current invoice date is posterior to the last invoice date among validated invoices of same ty...
update_percent($line, $percent, $update_price=true)
Update invoice line with percentage.
const TYPE_REPLACEMENT
Replacement invoice.
validate($user, $force_number='', $idwarehouse=0, $notrigger=0, $batch_rule=0)
Tag invoice as validated + call trigger BILL_VALIDATE Object must have lines loaded with fetch_lines.
deleteline($rowid, $id=0)
Delete line in database.
update(User $user, $notrigger=0)
Update database.
fetch_lines($only_product=0, $loadalsotranslation=0)
Load all detailed lines into this->lines.
$fk_fac_rec_source
id of template invoice when generated from a template invoice
liste_array($shortlist=0, $draft=0, $excluser=null, $socid=0, $limit=0, $offset=0, $sortfield='f.datef, f.rowid', $sortorder='DESC')
Return list of invoices (eventually filtered on a user) into an array.
getIdBillingContact()
Retourne id des contacts clients de facturation.
__construct(DoliDB $db)
Constructor.
const STATUS_DRAFT
Draft status.
updateline($rowid, $desc, $pu, $qty, $remise_percent, $date_start, $date_end, $txtva, $txlocaltax1=0, $txlocaltax2=0, $price_base_type='HT', $info_bits=0, $type=self::TYPE_STANDARD, $fk_parent_line=0, $skip_update_total=0, $fk_fournprice=null, $pa_ht=0, $label='', $special_code=0, $array_options=array(), $situation_percent=100, $fk_unit=null, $pu_ht_devise=0, $notrigger=0, $ref_ext='', $rang=0)
Update a detail line.
getNomUrl($withpicto=0, $option='', $max=0, $short=0, $moretitle='', $notooltip=0, $addlinktonotes=0, $save_lastsearch_value=-1, $target='')
Return clicable link of object (with eventually picto)
load_board($user)
Load indicators for dashboard (this->nbtodo and this->nbtodolate)
fetchPreviousNextSituationInvoice()
Fetch previous and next situations invoices.
fetch($rowid, $ref='', $ref_ext='', $notused=0, $fetch_situation=false)
Get object from database.
const TYPE_STANDARD
Standard invoice.
const TYPE_SITUATION
Situation invoice.
$paye
1 if invoice paid COMPLETELY, 0 otherwise (do not use it anymore, use statut and close_code)
updatePriceNextInvoice(&$langs)
Update price of next invoice.
static replaceProduct(DoliDB $db, $origin_id, $dest_id)
Function used to replace a product id with another one.
setRetainedWarrantyDateLimit($timestamp, $dateYmd='')
Change the retained_warranty_date_limit.
$pos_source
key of pos source ('0', '1', ...)
sendEmailsRemindersOnInvoiceDueDate($nbdays=0, $paymentmode='all', $template='', $datetouse='duedate', $forcerecipient='')
Send reminders by emails for invoices validated that are due.
info($id)
Load miscellaneous information for tab "Info".
const TYPE_PROFORMA
Proforma invoice (should not be used.
addline( $desc, $pu_ht, $qty, $txtva, $txlocaltax1=0, $txlocaltax2=0, $fk_product=0, $remise_percent=0, $date_start='', $date_end='', $ventil=0, $info_bits=0, $fk_remise_except=0, $price_base_type='HT', $pu_ttc=0, $type=0, $rang=-1, $special_code=0, $origin='', $origin_id=0, $fk_parent_line=0, $fk_fournprice=null, $pa_ht=0, $label='', $array_options=array(), $situation_percent=100, $fk_prev_id=0, $fk_unit=null, $pu_ht_devise=0, $ref_ext='', $noupdateafterinsertline=0)
Add an invoice line into database (linked to product/service or not).
getKanbanView($option='', $arraydata=null)
Return clicable link of object (with eventually picto)
generateDocument($modele, $outputlangs, $hidedetails=0, $hidedesc=0, $hideref=0, $moreparams=null)
Create a document onto disk according to template module.
setRetainedWarranty($value)
Change the retained warranty.
get_prev_sits()
Returns an array containing the previous situations as Facture objects.
list_qualified_avoir_invoices($socid=0)
Return list of invoices qualified to be corrected by a credit note.
set_canceled($user, $close_code='', $close_note='')
Tag invoice as canceled, with no payment on it (example for replacement invoice or payment never rece...
newCycle()
Gets the smallest reference available for a new cycle.
setDiscount($user, $remise, $notrigger=0)
Set percent discount.
set_ref_client($ref_client, $notrigger=0)
Set customer ref.
is_first()
Checks if the invoice is the first of a cycle.
set_remise($user, $remise, $notrigger=0)
Set percent discount.
getTooltipContentArray($params)
getTooltipContentArray
getNextNumRef($soc, $mode='next')
Set absolute discount.
checkProgressLine($idline, $situation_percent)
Check if the percent edited is lower of next invoice line.
const STATUS_VALIDATED
Validated (need to be paid)
hasDelay()
Is the customer invoice delayed?
getLinesArray()
Create an array of invoice lines.
set_unpaid($user)
Tag la facture comme non payee completement + appel trigger BILL_UNPAYED Fonction utilisee quand un p...
const TYPE_DEPOSIT
Deposit invoice.
set_paid($user, $close_code='', $close_note='')
Tag the invoice as paid completely (if close_code is filled) => this->fk_statut=2,...
const STATUS_ABANDONED
Classified abandoned and no payment done.
createFromCurrent(User $user, $invertdetail=0)
Create a new invoice in database from current invoice.
setPaid($user, $close_code='', $close_note='')
Tag the invoice as :
displayRetainedWarranty()
Currently used for documents generation : to know if retained warranty need to be displayed.
const TYPE_CREDIT_NOTE
Credit note invoice.
is_last_in_cycle()
Checks if the invoice is the last in its cycle.
initAsSpecimen($option='')
Initialise an instance with random values.
getRetainedWarrantyAmount($rounding=-1)
create(User $user, $notrigger=0, $forceduedate=0)
Create invoice in database.
$module_source
key of module source when invoice generated from a dedicated module ('cashdesk', 'takepos',...
setUnpaid($user)
Tag la facture comme non payee completement + appel trigger BILL_UNPAYED Fonction utilisee quand un p...
const STATUS_CLOSED
Classified paid.
createFromContract($object, User $user, $lines=array())
Load an object from an order and create a new invoice into database.
Class to manage invoice lines.
$desc
Description ligne.
$fk_parent_line
Id parent line.
$special_code
List of special options to define line: 1: shipment cost lines 2: ecotaxe 3: ?? idofmodule: a meaning...
insert($notrigger=0, $noerrorifdiscountalreadylinked=0)
Insert line into database.
get_prev_progress($invoiceid, $include_credit_note=true)
Returns situation_percent of the previous line.
fetch($rowid)
Load invoice line from database.
update($user=null, $notrigger=0)
Update line into database.
$fk_facture
From llx_facturedet Id facture.
__construct($db)
Constructor.
update_total()
Update DB line fields total_xxx Used by migration.
Class to manage invoice templates.
Classe permettant la generation du formulaire html d'envoi de mail unitaire Usage: $formail = new For...
Classe permettant la generation de composants html autre Only common components are here.
Class to manage stock movements.
static getIdFromCode($dbs, $code)
Get id of currency from code.
static getIdAndTxFromCode($dbs, $code, $date_document='')
Get id and rate of currency from code.
Class to manage predefined suppliers products.
Class to manage products or services.
Manage record for batch number management.
const BATCH_RULE_SELLBY_EATBY_DATES_FIRST
Batches rules.
Class to manage third parties objects (customers, suppliers, prospects...)
Class to manage translations.
Class to manage Dolibarr users.
hasRight($module, $permlevel1, $permlevel2='')
Return if a user has a permission.
dol_get_first_hour($date, $gm='tzserver')
Return GMT time for first hour of a given GMT date (it removes hours, min and second part)
Definition date.lib.php:654
dol_get_last_hour($date, $gm='tzserver')
Return GMT time for last hour of a given GMT date (it replaces hours, min and second part to 23:59:59...
Definition date.lib.php:640
dol_get_first_day($year, $month=1, $gm=false)
Return GMT time for first day of a month or year.
Definition date.lib.php:594
dol_get_next_month($month, $year)
Return next month.
Definition date.lib.php:532
dol_time_plus_duree($time, $duration_value, $duration_unit, $ruleforendofmonth=0)
Add a delay to a date.
Definition date.lib.php:125
dol_get_last_day($year, $month=12, $gm=false)
Return GMT time for last day of a month or year.
Definition date.lib.php:613
dol_delete_dir_recursive($dir, $count=0, $nophperrors=0, $onlysub=0, &$countdeleted=0, $indexdatabase=1, $nolog=0)
Remove a directory $dir and its subdirectories (or only files and subdirectories)
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_dir_list($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:62
dol_delete_preview($object)
Delete all preview files linked to object instance.
dol_mktime($hour, $minute, $second, $month, $day, $year, $gm='auto', $check=1)
Return a timestamp date built from detailed informations (by default a local PHP server timestamp) Re...
dol_mimetype($file, $default='application/octet-stream', $mode=0)
Return MIME type of a file from its name with extension.
price2num($amount, $rounding='', $option=0)
Function that return a number with universal decimal format (decimal separator is '.
dol_print_error($db='', $error='', $errors=null)
Displays error message system with all the information to facilitate the diagnosis and the escalation...
img_object($titlealt, $picto, $moreatt='', $pictoisfullpath=false, $srconly=0, $notitle=0)
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_print_date($time, $format='', $tzoutput='auto', $outputlangs='', $encodetooutput=false)
Output date in a string format according to outputlangs (or langs if not defined).
dol_now($mode='auto')
Return date for now.
getDolGlobalInt($key, $default=0)
Return a Dolibarr global constant int value.
img_picto($titlealt, $picto, $moreatt='', $pictoisfullpath=false, $srconly=0, $notitle=0, $alt='', $morecss='', $marginleftonlyshort=2)
Show picto whatever it's its name (generic function)
getLocalTaxesFromRate($vatrate, $local, $buyer, $seller, $firstparamisid=0)
Get type and rate of localtaxes for a particular vat rate/country of a thirdparty.
dol_concatdesc($text1, $text2, $forxml=false, $invert=false)
Concat 2 descriptions with a new line between them (second operand after first one with appropriate n...
complete_substitutions_array(&$substitutionarray, $outputlangs, $object=null, $parameters=null, $callfunc="completesubstitutionarray")
Complete the $substitutionarray with more entries coming from external module that had set the "subst...
make_substitutions($text, $substitutionarray, $outputlangs=null, $converttextinhtmlifnecessary=0)
Make substitution into a text string, replacing keys with vals from $substitutionarray (oldval=>newva...
GETPOST($paramname, $check='alphanohtml', $method=0, $filter=null, $options=null, $noreplace=0)
Return value of a param into GET or POST supervariable.
setEventMessages($mesg, $mesgs, $style='mesgs', $messagekey='', $noduplicate=0)
Set event messages in dol_events session object.
dol_buildpath($path, $type=0, $returnemptyifnotfound=0)
Return path of url or filesystem.
dol_sanitizeFileName($str, $newstr='_', $unaccent=1)
Clean a string to use it as a file name.
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.
getCommonSubstitutionArray($outputlangs, $onlykey=0, $exclude=null, $object=null, $include=null)
Return array of possible common substitutions.
getDolGlobalString($key, $default='')
Return dolibarr global constant string value.
getDictionaryValue($tablename, $field, $id, $checkentity=false, $rowidfield='rowid')
Return the value of a filed into a dictionary for the record $id.
dol_syslog($message, $level=LOG_INFO, $ident=0, $suffixinfilename='', $restricttologhandler='', $logcontext=null)
Write log message into outputs.
getEntity($element, $shared=1, $currentobject=null)
Get list of entity id to use.
dol_getdate($timestamp, $fast=false, $forcetimezone='')
Return an array with locale date info.
dol_escape_htmltag($stringtoescape, $keepb=0, $keepn=0, $noescapetags='', $escapeonlyhtmltags=0, $cleanalsojavascript=0)
Returns text escaped for inclusion in HTML alt or title or value tags, or into values of HTML input f...
publicphonebutton2 phonegreen basiclayout basiclayout TotalHT VATCode TotalVAT TotalLT1 TotalLT2 TotalTTC TotalHT clearboth nowraponall right right takeposterminal SELECT e rowid
Definition invoice.php:1931
publicphonebutton2 phonegreen basiclayout basiclayout TotalHT VATCode TotalVAT TotalLT1 TotalLT2 TotalTTC TotalHT clearboth nowraponall right right takeposterminal SELECT e e e e e statut
Definition invoice.php:1931
getMarginInfos($pvht, $remise_percent, $tva_tx, $localtax1_tx, $localtax2_tx, $fk_pa, $paht)
Return an array with margins information of a line.
calcul_price_total($qty, $pu, $remise_percent_ligne, $txtva, $uselocaltax1_rate, $uselocaltax2_rate, $remise_percent_global, $price_base_type, $info_bits, $type, $seller='', $localtaxes_array='', $progress=100, $multicurrency_tx=1, $pu_devise=0, $multicurrency_code='')
Calculate totals (net, vat, ...) of a line.
Definition price.lib.php:86
if(preg_match('/crypted:/i', $dolibarr_main_db_pass)||!empty($dolibarr_main_db_encrypted_pass)) $conf db type
Definition repair.php:121