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