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 // For line from template invoice, we use data from template invoice
1017 /*
1018 if ($_facrec->lines[$i]->fk_product) {
1019 $prod = new Product($this->db);
1020 $res = $prod->fetch($_facrec->lines[$i]->fk_product);
1021 }
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 $apply_abs_price_on_credit_note=false;
4238 if ($this->type == self::TYPE_CREDIT_NOTE && !getDolGlobalInt('FACTURE_ENABLE_NEGATIVE_LINES') && !getDolGlobalInt('INVOICE_KEEP_DISCOUNT_LINES_AS_IN_ORIGIN')) {
4239 $apply_abs_price_on_credit_note = true;
4240 }
4241
4242
4243 $this->line->id = $rowid;
4244 $this->line->rowid = $rowid;
4245 $this->line->label = $label;
4246 $this->line->desc = $desc;
4247 $this->line->ref_ext = $ref_ext;
4248 $this->line->qty = ($this->type == self::TYPE_CREDIT_NOTE ?abs($qty) : $qty); // For credit note, quantity is always positive and unit price negative
4249
4250 $this->line->vat_src_code = $vat_src_code;
4251 $this->line->tva_tx = $txtva;
4252 $this->line->localtax1_tx = $txlocaltax1;
4253 $this->line->localtax2_tx = $txlocaltax2;
4254 $this->line->localtax1_type = empty($localtaxes_type[0]) ? '' : $localtaxes_type[0];
4255 $this->line->localtax2_type = empty($localtaxes_type[2]) ? '' : $localtaxes_type[2];
4256
4257 $this->line->remise_percent = $remise_percent;
4258 $this->line->subprice = ($apply_abs_price_on_credit_note?-abs($pu_ht) : $pu_ht); // For credit note, unit price always negative, always positive otherwise
4259 $this->line->date_start = $date_start;
4260 $this->line->date_end = $date_end;
4261 $this->line->total_ht = (($apply_abs_price_on_credit_note || $qty < 0) ?-abs($total_ht) : $total_ht); // For credit note and if qty is negative, total is negative
4262 $this->line->total_tva = (($apply_abs_price_on_credit_note || $qty < 0) ?-abs($total_tva) : $total_tva);
4263 $this->line->total_localtax1 = $total_localtax1;
4264 $this->line->total_localtax2 = $total_localtax2;
4265 $this->line->total_ttc = (($apply_abs_price_on_credit_note || $qty < 0) ?-abs($total_ttc) : $total_ttc);
4266 $this->line->info_bits = $info_bits;
4267 $this->line->special_code = $special_code;
4268 $this->line->product_type = $type;
4269 $this->line->fk_parent_line = $fk_parent_line;
4270 $this->line->skip_update_total = $skip_update_total;
4271 $this->line->situation_percent = $situation_percent;
4272 $this->line->fk_unit = $fk_unit;
4273
4274 $this->line->fk_fournprice = $fk_fournprice;
4275 $this->line->pa_ht = $pa_ht;
4276
4277 // Multicurrency
4278 $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
4279 $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
4280 $this->line->multicurrency_total_tva = (($this->type == self::TYPE_CREDIT_NOTE || $qty < 0) ?-abs($multicurrency_total_tva) : $multicurrency_total_tva);
4281 $this->line->multicurrency_total_ttc = (($this->type == self::TYPE_CREDIT_NOTE || $qty < 0) ?-abs($multicurrency_total_ttc) : $multicurrency_total_ttc);
4282
4283 if (is_array($array_options) && count($array_options) > 0) {
4284 // We replace values in this->line->array_options only for entries defined into $array_options
4285 foreach ($array_options as $key => $value) {
4286 $this->line->array_options[$key] = $array_options[$key];
4287 }
4288 }
4289
4290 $result = $this->line->update($user, $notrigger);
4291 if ($result > 0) {
4292 // Reorder if child line
4293 if (!empty($fk_parent_line)) {
4294 $this->line_order(true, 'DESC');
4295 }
4296
4297 // Mise a jour info denormalisees au niveau facture
4298 $this->update_price(1, 'auto');
4299 $this->db->commit();
4300 return $result;
4301 } else {
4302 $this->error = $this->line->error;
4303 $this->db->rollback();
4304 return -1;
4305 }
4306 } else {
4307 $this->error = "Invoice statut makes operation forbidden";
4308 return -2;
4309 }
4310 }
4311
4319 public function checkProgressLine($idline, $situation_percent)
4320 {
4321 $sql = 'SELECT fd.situation_percent FROM '.MAIN_DB_PREFIX.'facturedet fd
4322 INNER JOIN '.MAIN_DB_PREFIX.'facture f ON (fd.fk_facture = f.rowid)
4323 WHERE fd.fk_prev_id = '.((int) $idline).' AND f.fk_statut <> 0';
4324
4325 $result = $this->db->query($sql);
4326 if (!$result) {
4327 $this->error = $this->db->error();
4328 return false;
4329 }
4330
4331 $obj = $this->db->fetch_object($result);
4332
4333 if ($obj === null) {
4334 return true;
4335 } else {
4336 return ($situation_percent < $obj->situation_percent);
4337 }
4338 }
4339
4340 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4349 public function update_percent($line, $percent, $update_price = true)
4350 {
4351 // phpcs:enable
4352 global $mysoc, $user;
4353
4354 // Progress should never be changed for discount lines
4355 if (($line->info_bits & 2) == 2) {
4356 return;
4357 }
4358
4359 include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
4360
4361 // Cap percentages to 100
4362 if ($percent > 100) {
4363 $percent = 100;
4364 }
4365 $line->situation_percent = $percent;
4366 $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);
4367 $line->total_ht = $tabprice[0];
4368 $line->total_tva = $tabprice[1];
4369 $line->total_ttc = $tabprice[2];
4370 $line->total_localtax1 = $tabprice[9];
4371 $line->total_localtax2 = $tabprice[10];
4372 $line->multicurrency_total_ht = $tabprice[16];
4373 $line->multicurrency_total_tva = $tabprice[17];
4374 $line->multicurrency_total_ttc = $tabprice[18];
4375 $line->update($user);
4376
4377 // sometimes it is better to not update price for each line, ie when updating situation on all lines
4378 if ($update_price) {
4379 $this->update_price(1);
4380 }
4381 }
4382
4390 public function deleteline($rowid, $id = 0)
4391 {
4392 global $user;
4393
4394 dol_syslog(get_class($this)."::deleteline rowid=".((int) $rowid), LOG_DEBUG);
4395
4396 if ($this->statut != self::STATUS_DRAFT) {
4397 $this->error = 'ErrorDeleteLineNotAllowedByObjectStatus';
4398 return -1;
4399 }
4400
4401 $line = new FactureLigne($this->db);
4402
4403 $line->context = $this->context;
4404
4405 // Load line
4406 $result = $line->fetch($rowid);
4407 if (!($result > 0)) {
4408 dol_print_error($this->db, $line->error, $line->errors);
4409 return -1;
4410 }
4411
4412 if ($id > 0 && $line->fk_facture != $id) {
4413 $this->error = 'ErrorLineIDDoesNotMatchWithObjectID';
4414 return -1;
4415 }
4416
4417 $this->db->begin();
4418
4419 // Memorize previous line for triggers
4420 $staticline = clone $line;
4421 $line->oldline = $staticline;
4422
4423 if ($line->delete($user) > 0) {
4424 $result = $this->update_price(1);
4425
4426 if ($result > 0) {
4427 $this->db->commit();
4428 return 1;
4429 } else {
4430 $this->db->rollback();
4431 $this->error = $this->db->lasterror();
4432 return -1;
4433 }
4434 } else {
4435 $this->db->rollback();
4436 $this->error = $line->error;
4437 return -1;
4438 }
4439 }
4440
4441 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4452 public function set_remise($user, $remise, $notrigger = 0)
4453 {
4454 // phpcs:enable
4455 dol_syslog(get_class($this)."::set_remise is deprecated, use setDiscount instead", LOG_NOTICE);
4456 return $this->setDiscount($user, $remise, $notrigger);
4457 }
4458
4468 public function setDiscount($user, $remise, $notrigger = 0)
4469 {
4470 // Clean parameters
4471 if (empty($remise)) {
4472 $remise = 0;
4473 }
4474
4475 if ($user->hasRight('facture', 'creer')) {
4476 $remise = price2num($remise, 2);
4477
4478 $error = 0;
4479
4480 $this->db->begin();
4481
4482 $sql = 'UPDATE '.MAIN_DB_PREFIX.'facture';
4483 $sql .= ' SET remise_percent = '.((float) $remise);
4484 $sql .= " WHERE rowid = ".((int) $this->id);
4485 $sql .= ' AND fk_statut = '.self::STATUS_DRAFT;
4486
4487 dol_syslog(__METHOD__, LOG_DEBUG);
4488 $resql = $this->db->query($sql);
4489 if (!$resql) {
4490 $this->errors[] = $this->db->error();
4491 $error++;
4492 }
4493
4494 if (!$notrigger && empty($error)) {
4495 // Call trigger
4496 $result = $this->call_trigger('BILL_MODIFY', $user);
4497 if ($result < 0) {
4498 $error++;
4499 }
4500 // End call triggers
4501 }
4502
4503 if (!$error) {
4504 $this->remise_percent = $remise;
4505 $this->update_price(1);
4506
4507 $this->db->commit();
4508 return 1;
4509 } else {
4510 foreach ($this->errors as $errmsg) {
4511 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
4512 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
4513 }
4514 $this->db->rollback();
4515 return -1 * $error;
4516 }
4517 }
4518
4519 return 0;
4520 }
4521
4522
4523 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4532 public function set_remise_absolue($user, $remise, $notrigger = 0)
4533 {
4534 // phpcs:enable
4535 if (empty($remise)) {
4536 $remise = 0;
4537 }
4538
4539 if ($user->hasRight('facture', 'creer')) {
4540 $error = 0;
4541
4542 $this->db->begin();
4543
4544 $remise = price2num($remise);
4545
4546 $sql = 'UPDATE '.MAIN_DB_PREFIX.'facture';
4547 $sql .= ' SET remise_absolue = '.((float) $remise);
4548 $sql .= " WHERE rowid = ".((int) $this->id);
4549 $sql .= ' AND fk_statut = '.self::STATUS_DRAFT;
4550
4551 dol_syslog(__METHOD__, LOG_DEBUG);
4552 $resql = $this->db->query($sql);
4553 if (!$resql) {
4554 $this->errors[] = $this->db->error();
4555 $error++;
4556 }
4557
4558 if (!$error) {
4559 $this->oldcopy = clone $this;
4560 $this->remise_absolue = $remise;
4561 $this->update_price(1);
4562 }
4563
4564 if (!$notrigger && empty($error)) {
4565 // Call trigger
4566 $result = $this->call_trigger('BILL_MODIFY', $user);
4567 if ($result < 0) {
4568 $error++;
4569 }
4570 // End call triggers
4571 }
4572
4573 if (!$error) {
4574 $this->db->commit();
4575 return 1;
4576 } else {
4577 foreach ($this->errors as $errmsg) {
4578 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
4579 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
4580 }
4581 $this->db->rollback();
4582 return -1 * $error;
4583 }
4584 }
4585
4586 return 0;
4587 }
4588
4597 public function getNextNumRef($soc, $mode = 'next')
4598 {
4599 global $conf, $langs;
4600
4601 if ($this->module_source == 'takepos') {
4602 $langs->load('cashdesk');
4603
4604 $moduleName = 'takepos';
4605 $moduleSourceName = 'Takepos';
4606 $addonConstName = 'TAKEPOS_REF_ADDON';
4607
4608 // Clean parameters (if not defined or using deprecated value)
4609 if (empty($conf->global->TAKEPOS_REF_ADDON)) {
4610 $conf->global->TAKEPOS_REF_ADDON = 'mod_takepos_ref_simple';
4611 }
4612
4613 $addon = $conf->global->TAKEPOS_REF_ADDON;
4614 } else {
4615 $langs->load('bills');
4616
4617 $moduleName = 'facture';
4618 $moduleSourceName = 'Invoice';
4619 $addonConstName = 'FACTURE_ADDON';
4620
4621 // Clean parameters (if not defined or using deprecated value)
4622 if (empty($conf->global->FACTURE_ADDON)) {
4623 $conf->global->FACTURE_ADDON = 'mod_facture_terre';
4624 } elseif ($conf->global->FACTURE_ADDON == 'terre') {
4625 $conf->global->FACTURE_ADDON = 'mod_facture_terre';
4626 } elseif ($conf->global->FACTURE_ADDON == 'mercure') {
4627 $conf->global->FACTURE_ADDON = 'mod_facture_mercure';
4628 }
4629
4630 $addon = $conf->global->FACTURE_ADDON;
4631 }
4632
4633 if (!empty($addon)) {
4634 dol_syslog("Call getNextNumRef with ".$addonConstName." = ".$conf->global->FACTURE_ADDON.", thirdparty=".$soc->name.", type=".$soc->typent_code.", mode=".$mode, LOG_DEBUG);
4635
4636 $mybool = false;
4637
4638 $file = $addon.'.php';
4639 $classname = $addon;
4640
4641
4642 // Include file with class
4643 $dirmodels = array_merge(array('/'), (array) $conf->modules_parts['models']);
4644 foreach ($dirmodels as $reldir) {
4645 $dir = dol_buildpath($reldir.'core/modules/'.$moduleName.'/');
4646
4647 // Load file with numbering class (if found)
4648 if (is_file($dir.$file) && is_readable($dir.$file)) {
4649 $mybool |= include_once $dir.$file;
4650 }
4651 }
4652
4653 // For compatibility
4654 if (!$mybool) {
4655 $file = $addon.'/'.$addon.'.modules.php';
4656 $classname = 'mod_'.$moduleName.'_'.$addon;
4657 $classname = preg_replace('/\-.*$/', '', $classname);
4658 // Include file with class
4659 foreach ($conf->file->dol_document_root as $dirroot) {
4660 $dir = $dirroot.'/core/modules/'.$moduleName.'/';
4661
4662 // Load file with numbering class (if found)
4663 if (is_file($dir.$file) && is_readable($dir.$file)) {
4664 $mybool |= include_once $dir.$file;
4665 }
4666 }
4667 }
4668
4669 if (!$mybool) {
4670 dol_print_error('', 'Failed to include file '.$file);
4671 return '';
4672 }
4673
4674 $obj = new $classname();
4675
4676 $numref = $obj->getNextValue($soc, $this, $mode);
4677
4678
4683 if ($mode != 'last' && !$numref) {
4684 $this->error = $obj->error;
4685 return '';
4686 }
4687
4688 return $numref;
4689 } else {
4690 $langs->load('errors');
4691 print $langs->trans('Error').' '.$langs->trans('ErrorModuleSetupNotComplete', $langs->transnoentitiesnoconv($moduleSourceName));
4692 return '';
4693 }
4694 }
4695
4702 public function info($id)
4703 {
4704 $sql = 'SELECT c.rowid, datec, date_valid as datev, tms as datem,';
4705 $sql .= ' date_closing as dateclosing,';
4706 $sql .= ' fk_user_author, fk_user_valid, fk_user_closing';
4707 $sql .= ' FROM '.MAIN_DB_PREFIX.'facture as c';
4708 $sql .= ' WHERE c.rowid = '.((int) $id);
4709
4710 $result = $this->db->query($sql);
4711 if ($result) {
4712 if ($this->db->num_rows($result)) {
4713 $obj = $this->db->fetch_object($result);
4714 $this->id = $obj->rowid;
4715 if ($obj->fk_user_author) {
4716 $cuser = new User($this->db);
4717 $cuser->fetch($obj->fk_user_author);
4718 $this->user_creation = $cuser;
4719 }
4720 if ($obj->fk_user_valid) {
4721 $vuser = new User($this->db);
4722 $vuser->fetch($obj->fk_user_valid);
4723 $this->user_validation = $vuser;
4724 }
4725 if ($obj->fk_user_closing) {
4726 $cluser = new User($this->db);
4727 $cluser->fetch($obj->fk_user_closing);
4728 $this->user_closing = $cluser;
4729 }
4730
4731 $this->date_creation = $this->db->jdate($obj->datec);
4732 $this->date_modification = $this->db->jdate($obj->datem);
4733 $this->date_validation = $this->db->jdate($obj->datev);
4734 $this->date_closing = $this->db->jdate($obj->dateclosing);
4735 }
4736 $this->db->free($result);
4737 } else {
4738 dol_print_error($this->db);
4739 }
4740 }
4741
4742
4743 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4757 public function liste_array($shortlist = 0, $draft = 0, $excluser = '', $socid = 0, $limit = 0, $offset = 0, $sortfield = 'f.datef,f.rowid', $sortorder = 'DESC')
4758 {
4759 // phpcs:enable
4760 global $conf, $user;
4761
4762 $ga = array();
4763
4764 $sql = "SELECT s.rowid, s.nom as name, s.client,";
4765 $sql .= " f.rowid as fid, f.ref as ref, f.datef as df";
4766 if (empty($user->rights->societe->client->voir) && !$socid) {
4767 $sql .= ", sc.fk_soc, sc.fk_user";
4768 }
4769 $sql .= " FROM ".MAIN_DB_PREFIX."societe as s, ".MAIN_DB_PREFIX."facture as f";
4770 if (empty($user->rights->societe->client->voir) && !$socid) {
4771 $sql .= ", ".MAIN_DB_PREFIX."societe_commerciaux as sc";
4772 }
4773 $sql .= " WHERE f.entity IN (".getEntity('invoice').")";
4774 $sql .= " AND f.fk_soc = s.rowid";
4775 if (empty($user->rights->societe->client->voir) && !$socid) { //restriction
4776 $sql .= " AND s.rowid = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
4777 }
4778 if ($socid) {
4779 $sql .= " AND s.rowid = ".((int) $socid);
4780 }
4781 if ($draft) {
4782 $sql .= " AND f.fk_statut = ".self::STATUS_DRAFT;
4783 }
4784 if (is_object($excluser)) {
4785 $sql .= " AND f.fk_user_author <> ".((int) $excluser->id);
4786 }
4787 $sql .= $this->db->order($sortfield, $sortorder);
4788 $sql .= $this->db->plimit($limit, $offset);
4789
4790 $result = $this->db->query($sql);
4791 if ($result) {
4792 $numc = $this->db->num_rows($result);
4793 if ($numc) {
4794 $i = 0;
4795 while ($i < $numc) {
4796 $obj = $this->db->fetch_object($result);
4797
4798 if ($shortlist == 1) {
4799 $ga[$obj->fid] = $obj->ref;
4800 } elseif ($shortlist == 2) {
4801 $ga[$obj->fid] = $obj->ref.' ('.$obj->name.')';
4802 } else {
4803 $ga[$i]['id'] = $obj->fid;
4804 $ga[$i]['ref'] = $obj->ref;
4805 $ga[$i]['name'] = $obj->name;
4806 }
4807 $i++;
4808 }
4809 }
4810 return $ga;
4811 } else {
4812 dol_print_error($this->db);
4813 return -1;
4814 }
4815 }
4816
4817
4818 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4827 public function list_replacable_invoices($socid = 0)
4828 {
4829 // phpcs:enable
4830 global $conf;
4831
4832 $return = array();
4833
4834 $sql = "SELECT f.rowid as rowid, f.ref, f.fk_statut as status, f.paye as paid,";
4835 $sql .= " ff.rowid as rowidnext";
4836 //$sql .= ", SUM(pf.amount) as alreadypaid";
4837 $sql .= " FROM ".MAIN_DB_PREFIX."facture as f";
4838 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."paiement_facture as pf ON f.rowid = pf.fk_facture";
4839 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."facture as ff ON f.rowid = ff.fk_facture_source";
4840 $sql .= " WHERE (f.fk_statut = ".self::STATUS_VALIDATED." OR (f.fk_statut = ".self::STATUS_ABANDONED." AND f.close_code = '".self::CLOSECODE_ABANDONED."'))";
4841 $sql .= " AND f.entity IN (".getEntity('invoice').")";
4842 $sql .= " AND f.paye = 0"; // Not paid completely
4843 $sql .= " AND pf.fk_paiement IS NULL"; // No payment already done
4844 $sql .= " AND ff.fk_statut IS NULL"; // Return true if it is not a replacement invoice
4845 if ($socid > 0) {
4846 $sql .= " AND f.fk_soc = ".((int) $socid);
4847 }
4848 //$sql .= " GROUP BY f.rowid, f.ref, f.fk_statut, f.paye, ff.rowid";
4849 $sql .= " ORDER BY f.ref";
4850
4851 dol_syslog(get_class($this)."::list_replacable_invoices", LOG_DEBUG);
4852 $resql = $this->db->query($sql);
4853 if ($resql) {
4854 while ($obj = $this->db->fetch_object($resql)) {
4855 $return[$obj->rowid] = array(
4856 'id' => $obj->rowid,
4857 'ref' => $obj->ref,
4858 'status' => $obj->status,
4859 'paid' => $obj->paid,
4860 'alreadypaid' => 0
4861 );
4862 }
4863 //print_r($return);
4864 return $return;
4865 } else {
4866 $this->error = $this->db->error();
4867 return -1;
4868 }
4869 }
4870
4871
4872 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4881 public function list_qualified_avoir_invoices($socid = 0)
4882 {
4883 // phpcs:enable
4884 global $conf;
4885
4886 $return = array();
4887
4888
4889 $sql = "SELECT f.rowid as rowid, f.ref, f.fk_statut, f.type, f.paye, pf.fk_paiement";
4890 $sql .= " FROM ".MAIN_DB_PREFIX."facture as f";
4891 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."paiement_facture as pf ON f.rowid = pf.fk_facture";
4892 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."facture as ff ON (f.rowid = ff.fk_facture_source AND ff.type=".self::TYPE_REPLACEMENT.")";
4893 $sql .= " WHERE f.entity IN (".getEntity('invoice').")";
4894 $sql .= " AND f.fk_statut in (".self::STATUS_VALIDATED.",".self::STATUS_CLOSED.")";
4895 // $sql.= " WHERE f.fk_statut >= 1";
4896 // $sql.= " AND (f.paye = 1"; // Classee payee completement
4897 // $sql.= " OR f.close_code IS NOT NULL)"; // Classee payee partiellement
4898 $sql .= " AND ff.type IS NULL"; // Renvoi vrai si pas facture de remplacement
4899 $sql .= " AND f.type <> ".self::TYPE_CREDIT_NOTE; // Exclude credit note invoices from selection
4900
4901 if (!empty($conf->global->INVOICE_USE_SITUATION_CREDIT_NOTE)) {
4902 // Keep invoices that are not situation invoices or that are the last in serie if it is a situation invoice
4903 $sql .= " AND (f.type <> ".self::TYPE_SITUATION." OR f.rowid IN ";
4904 $sql .= '(SELECT MAX(fs.rowid)'; // This select returns several ID becasue of the group by later
4905 $sql .= " FROM ".MAIN_DB_PREFIX."facture as fs";
4906 $sql .= " WHERE fs.entity IN (".getEntity('invoice').")";
4907 $sql .= " AND fs.type = ".self::TYPE_SITUATION;
4908 $sql .= " AND fs.fk_statut IN (".self::STATUS_VALIDATED.",".self::STATUS_CLOSED.")";
4909 if ($socid > 0) {
4910 $sql .= " AND fs.fk_soc = ".((int) $socid);
4911 }
4912 $sql .= " GROUP BY fs.situation_cycle_ref)"; // For each situation_cycle_ref, we take the higher rowid
4913 $sql .= ")";
4914 } else {
4915 $sql .= " AND f.type <> ".self::TYPE_SITUATION; // Keep invoices that are not situation invoices
4916 }
4917
4918 if ($socid > 0) {
4919 $sql .= " AND f.fk_soc = ".((int) $socid);
4920 }
4921 $sql .= " ORDER BY f.ref";
4922
4923 dol_syslog(get_class($this)."::list_qualified_avoir_invoices", LOG_DEBUG);
4924 $resql = $this->db->query($sql);
4925 if ($resql) {
4926 while ($obj = $this->db->fetch_object($resql)) {
4927 $qualified = 0;
4928 if ($obj->fk_statut == self::STATUS_VALIDATED) {
4929 $qualified = 1;
4930 }
4931 if ($obj->fk_statut == self::STATUS_CLOSED) {
4932 $qualified = 1;
4933 }
4934 if ($qualified) {
4935 //$ref=$obj->ref;
4936 $paymentornot = ($obj->fk_paiement ? 1 : 0);
4937 $return[$obj->rowid] = array('ref'=>$obj->ref, 'status'=>$obj->fk_statut, 'type'=>$obj->type, 'paye'=>$obj->paye, 'paymentornot'=>$paymentornot);
4938 }
4939 }
4940
4941 return $return;
4942 } else {
4943 $this->error = $this->db->error();
4944 return -1;
4945 }
4946 }
4947
4948
4949 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4956 public function load_board($user)
4957 {
4958 // phpcs:enable
4959 global $conf, $langs;
4960
4961 $clause = " WHERE";
4962
4963 $sql = "SELECT f.rowid, f.date_lim_reglement as datefin, f.fk_statut, f.total_ht";
4964 $sql .= " FROM ".MAIN_DB_PREFIX."facture as f";
4965 if (empty($user->rights->societe->client->voir) && !$user->socid) {
4966 $sql .= " JOIN ".MAIN_DB_PREFIX."societe_commerciaux as sc ON f.fk_soc = sc.fk_soc";
4967 $sql .= " WHERE sc.fk_user = ".((int) $user->id);
4968 $clause = " AND";
4969 }
4970 $sql .= $clause." f.paye=0";
4971 $sql .= " AND f.entity IN (".getEntity('invoice').")";
4972 $sql .= " AND f.fk_statut = ".self::STATUS_VALIDATED;
4973 if ($user->socid) {
4974 $sql .= " AND f.fk_soc = ".((int) $user->socid);
4975 }
4976
4977 $resql = $this->db->query($sql);
4978 if ($resql) {
4979 $langs->load("bills");
4980 $now = dol_now();
4981
4982 $response = new WorkboardResponse();
4983 $response->warning_delay = $conf->facture->client->warning_delay / 60 / 60 / 24;
4984 $response->label = $langs->trans("CustomerBillsUnpaid");
4985 $response->labelShort = $langs->trans("Unpaid");
4986 $response->url = DOL_URL_ROOT.'/compta/facture/list.php?search_status=1&mainmenu=billing&leftmenu=customers_bills';
4987 $response->img = img_object('', "bill");
4988
4989 $generic_facture = new Facture($this->db);
4990
4991 while ($obj = $this->db->fetch_object($resql)) {
4992 $generic_facture->date_lim_reglement = $this->db->jdate($obj->datefin);
4993 $generic_facture->statut = $obj->fk_statut;
4994
4995 $response->nbtodo++;
4996 $response->total += $obj->total_ht;
4997
4998 if ($generic_facture->hasDelay()) {
4999 $response->nbtodolate++;
5000 $response->url_late = DOL_URL_ROOT.'/compta/facture/list.php?search_option=late&mainmenu=billing&leftmenu=customers_bills';
5001 }
5002 }
5003
5004 $this->db->free($resql);
5005 return $response;
5006 } else {
5007 dol_print_error($this->db);
5008 $this->error = $this->db->error();
5009 return -1;
5010 }
5011 }
5012
5013
5014 /* gestion des contacts d'une facture */
5015
5021 public function getIdBillingContact()
5022 {
5023 return $this->getIdContact('external', 'BILLING');
5024 }
5025
5031 public function getIdShippingContact()
5032 {
5033 return $this->getIdContact('external', 'SHIPPING');
5034 }
5035
5036
5045 public function initAsSpecimen($option = '')
5046 {
5047 global $conf, $langs, $user;
5048
5049 $now = dol_now();
5050 $arraynow = dol_getdate($now);
5051 $nownotime = dol_mktime(0, 0, 0, $arraynow['mon'], $arraynow['mday'], $arraynow['year']);
5052
5053 // Load array of products prodids
5054 $num_prods = 0;
5055 $prodids = array();
5056 $sql = "SELECT rowid";
5057 $sql .= " FROM ".MAIN_DB_PREFIX."product";
5058 $sql .= " WHERE entity IN (".getEntity('product').")";
5059 $sql .= $this->db->plimit(100);
5060
5061 $resql = $this->db->query($sql);
5062 if ($resql) {
5063 $num_prods = $this->db->num_rows($resql);
5064 $i = 0;
5065 while ($i < $num_prods) {
5066 $i++;
5067 $row = $this->db->fetch_row($resql);
5068 $prodids[$i] = $row[0];
5069 }
5070 }
5071 //Avoid php warning Warning: mt_rand(): max(0) is smaller than min(1) when no product exists
5072 if (empty($num_prods)) {
5073 $num_prods = 1;
5074 }
5075
5076 // Initialize parameters
5077 $this->id = 0;
5078 $this->entity = 1;
5079 $this->ref = 'SPECIMEN';
5080 $this->specimen = 1;
5081 $this->socid = 1;
5082 $this->date = $nownotime;
5083 $this->date_lim_reglement = $nownotime + 3600 * 24 * 30;
5084 $this->cond_reglement_id = 1;
5085 $this->cond_reglement_code = 'RECEP';
5086 $this->date_lim_reglement = $this->calculate_date_lim_reglement();
5087 $this->mode_reglement_id = 0; // Not forced to show payment mode CHQ + VIR
5088 $this->mode_reglement_code = ''; // Not forced to show payment mode CHQ + VIR
5089
5090 $this->note_public = 'This is a comment (public)';
5091 $this->note_private = 'This is a comment (private)';
5092 $this->note = 'This is a comment (private)';
5093
5094 $this->fk_user_author = $user->id;
5095
5096 $this->multicurrency_tx = 1;
5097 $this->multicurrency_code = $conf->currency;
5098
5099 $this->fk_incoterms = 0;
5100 $this->location_incoterms = '';
5101
5102 if (empty($option) || $option != 'nolines') {
5103 // Lines
5104 $nbp = 5;
5105 $xnbp = 0;
5106 while ($xnbp < $nbp) {
5107 $line = new FactureLigne($this->db);
5108 $line->desc = $langs->trans("Description")." ".$xnbp;
5109 $line->qty = 1;
5110 $line->subprice = 100;
5111 $line->tva_tx = 19.6;
5112 $line->localtax1_tx = 0;
5113 $line->localtax2_tx = 0;
5114 $line->remise_percent = 0;
5115 if ($xnbp == 1) { // Qty is negative (product line)
5116 $prodid = mt_rand(1, $num_prods);
5117 $line->fk_product = $prodids[$prodid];
5118 $line->qty = -1;
5119 $line->total_ht = -100;
5120 $line->total_ttc = -119.6;
5121 $line->total_tva = -19.6;
5122 $line->multicurrency_total_ht = -200;
5123 $line->multicurrency_total_ttc = -239.2;
5124 $line->multicurrency_total_tva = -39.2;
5125 } elseif ($xnbp == 2) { // UP is negative (free line)
5126 $line->subprice = -100;
5127 $line->total_ht = -100;
5128 $line->total_ttc = -119.6;
5129 $line->total_tva = -19.6;
5130 $line->remise_percent = 0;
5131 $line->multicurrency_total_ht = -200;
5132 $line->multicurrency_total_ttc = -239.2;
5133 $line->multicurrency_total_tva = -39.2;
5134 } elseif ($xnbp == 3) { // Discount is 50% (product line)
5135 $prodid = mt_rand(1, $num_prods);
5136 $line->fk_product = $prodids[$prodid];
5137 $line->total_ht = 50;
5138 $line->total_ttc = 59.8;
5139 $line->total_tva = 9.8;
5140 $line->multicurrency_total_ht = 100;
5141 $line->multicurrency_total_ttc = 119.6;
5142 $line->multicurrency_total_tva = 19.6;
5143 $line->remise_percent = 50;
5144 } else // (product line)
5145 {
5146 $prodid = mt_rand(1, $num_prods);
5147 $line->fk_product = $prodids[$prodid];
5148 $line->total_ht = 100;
5149 $line->total_ttc = 119.6;
5150 $line->total_tva = 19.6;
5151 $line->multicurrency_total_ht = 200;
5152 $line->multicurrency_total_ttc = 239.2;
5153 $line->multicurrency_total_tva = 39.2;
5154 $line->remise_percent = 0;
5155 }
5156
5157 $this->lines[$xnbp] = $line;
5158
5159
5160 $this->total_ht += $line->total_ht;
5161 $this->total_tva += $line->total_tva;
5162 $this->total_ttc += $line->total_ttc;
5163
5164 $this->multicurrency_total_ht += $line->multicurrency_total_ht;
5165 $this->multicurrency_total_tva += $line->multicurrency_total_tva;
5166 $this->multicurrency_total_ttc += $line->multicurrency_total_ttc;
5167
5168 $xnbp++;
5169 }
5170 $this->revenuestamp = 0;
5171
5172 // Add a line "offered"
5173 $line = new FactureLigne($this->db);
5174 $line->desc = $langs->trans("Description")." (offered line)";
5175 $line->qty = 1;
5176 $line->subprice = 100;
5177 $line->tva_tx = 19.6;
5178 $line->localtax1_tx = 0;
5179 $line->localtax2_tx = 0;
5180 $line->remise_percent = 100;
5181 $line->total_ht = 0;
5182 $line->total_ttc = 0; // 90 * 1.196
5183 $line->total_tva = 0;
5184 $line->multicurrency_total_ht = 0;
5185 $line->multicurrency_total_ttc = 0;
5186 $line->multicurrency_total_tva = 0;
5187 $prodid = mt_rand(1, $num_prods);
5188 $line->fk_product = $prodids[$prodid];
5189
5190 $this->lines[$xnbp] = $line;
5191 $xnbp++;
5192 }
5193 }
5194
5195 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5201 public function load_state_board()
5202 {
5203 // phpcs:enable
5204 global $conf, $user;
5205
5206 $this->nb = array();
5207
5208 $clause = "WHERE";
5209
5210 $sql = "SELECT count(f.rowid) as nb";
5211 $sql .= " FROM ".MAIN_DB_PREFIX."facture as f";
5212 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe as s ON f.fk_soc = s.rowid";
5213 if (empty($user->rights->societe->client->voir) && !$user->socid) {
5214 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe_commerciaux as sc ON s.rowid = sc.fk_soc";
5215 $sql .= " WHERE sc.fk_user = ".((int) $user->id);
5216 $clause = "AND";
5217 }
5218 $sql .= " ".$clause." f.entity IN (".getEntity('invoice').")";
5219
5220 $resql = $this->db->query($sql);
5221 if ($resql) {
5222 while ($obj = $this->db->fetch_object($resql)) {
5223 $this->nb["invoices"] = $obj->nb;
5224 }
5225 $this->db->free($resql);
5226 return 1;
5227 } else {
5228 dol_print_error($this->db);
5229 $this->error = $this->db->error();
5230 return -1;
5231 }
5232 }
5233
5239 public function getLinesArray()
5240 {
5241 return $this->fetch_lines();
5242 }
5243
5255 public function generateDocument($modele, $outputlangs, $hidedetails = 0, $hidedesc = 0, $hideref = 0, $moreparams = null)
5256 {
5257 global $conf, $langs;
5258
5259 $outputlangs->loadLangs(array("bills", "products"));
5260
5261 if (!dol_strlen($modele)) {
5262 $modele = 'crabe';
5263 $thisTypeConfName = 'FACTURE_ADDON_PDF_'.$this->type;
5264
5265 if (!empty($this->model_pdf)) {
5266 $modele = $this->model_pdf;
5267 } elseif (!empty($this->modelpdf)) { // deprecated
5268 $modele = $this->modelpdf;
5269 } elseif (!empty($conf->global->$thisTypeConfName)) {
5270 $modele = $conf->global->$thisTypeConfName;
5271 } elseif (!empty($conf->global->FACTURE_ADDON_PDF)) {
5272 $modele = $conf->global->FACTURE_ADDON_PDF;
5273 }
5274 }
5275
5276 $modelpath = "core/modules/facture/doc/";
5277
5278 return $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams);
5279 }
5280
5286 public function newCycle()
5287 {
5288 $sql = 'SELECT max(situation_cycle_ref) FROM '.MAIN_DB_PREFIX.'facture as f';
5289 $sql .= " WHERE f.entity IN (".getEntity('invoice', 0).")";
5290 $resql = $this->db->query($sql);
5291 if ($resql) {
5292 if ($this->db->num_rows($resql) > 0) {
5293 $res = $this->db->fetch_array($resql);
5294 $ref = $res['max(situation_cycle_ref)'];
5295 $ref++;
5296 } else {
5297 $ref = 1;
5298 }
5299 $this->db->free($resql);
5300 return $ref;
5301 } else {
5302 $this->error = $this->db->lasterror();
5303 dol_syslog("Error sql=".$sql.", error=".$this->error, LOG_ERR);
5304 return -1;
5305 }
5306 }
5307
5308 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5314 public function is_first()
5315 {
5316 // phpcs:enable
5317 return ($this->situation_counter == 1);
5318 }
5319
5320 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5326 public function get_prev_sits()
5327 {
5328 // phpcs:enable
5329 global $conf;
5330
5331 $sql = 'SELECT rowid FROM '.MAIN_DB_PREFIX.'facture';
5332 $sql .= ' WHERE situation_cycle_ref = '.((int) $this->situation_cycle_ref);
5333 $sql .= ' AND situation_counter < '.((int) $this->situation_counter);
5334 $sql .= ' AND entity = '.($this->entity > 0 ? $this->entity : $conf->entity);
5335 $resql = $this->db->query($sql);
5336 $res = array();
5337 if ($resql && $this->db->num_rows($resql) > 0) {
5338 while ($row = $this->db->fetch_object($resql)) {
5339 $id = $row->rowid;
5340 $situation = new Facture($this->db);
5341 $situation->fetch($id);
5342 $res[] = $situation;
5343 }
5344 } else {
5345 $this->error = $this->db->error();
5346 dol_syslog("Error sql=".$sql.", error=".$this->error, LOG_ERR);
5347 return -1;
5348 }
5349
5350 return $res;
5351 }
5352
5360 public function setFinal(User $user, $notrigger = 0)
5361 {
5362 $error = 0;
5363
5364 $this->db->begin();
5365
5366 $sql = 'UPDATE '.MAIN_DB_PREFIX.'facture SET situation_final = '.((int) $this->situation_final).' WHERE rowid = '.((int) $this->id);
5367
5368 dol_syslog(__METHOD__, LOG_DEBUG);
5369 $resql = $this->db->query($sql);
5370 if (!$resql) {
5371 $this->errors[] = $this->db->error();
5372 $error++;
5373 }
5374
5375 if (!$notrigger && empty($error)) {
5376 // Call trigger
5377 $result = $this->call_trigger('BILL_MODIFY', $user);
5378 if ($result < 0) {
5379 $error++;
5380 }
5381 // End call triggers
5382 }
5383
5384 if (!$error) {
5385 $this->db->commit();
5386 return 1;
5387 } else {
5388 foreach ($this->errors as $errmsg) {
5389 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
5390 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
5391 }
5392 $this->db->rollback();
5393 return -1 * $error;
5394 }
5395 }
5396
5397 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5403 public function is_last_in_cycle()
5404 {
5405 // phpcs:enable
5406 global $conf;
5407
5408 if (!empty($this->situation_cycle_ref)) {
5409 // No point in testing anything if we're not inside a cycle
5410 $sql = 'SELECT max(situation_counter) FROM '.MAIN_DB_PREFIX.'facture';
5411 $sql .= ' WHERE situation_cycle_ref = '.((int) $this->situation_cycle_ref);
5412 $sql .= ' AND entity = '.($this->entity > 0 ? $this->entity : $conf->entity);
5413 $resql = $this->db->query($sql);
5414
5415 if ($resql && $this->db->num_rows($resql) > 0) {
5416 $res = $this->db->fetch_array($resql);
5417 $last = $res['max(situation_counter)'];
5418 return ($last == $this->situation_counter);
5419 } else {
5420 $this->error = $this->db->lasterror();
5421 dol_syslog(get_class($this)."::select Error ".$this->error, LOG_ERR);
5422 return false;
5423 }
5424 } else {
5425 return true;
5426 }
5427 }
5428
5437 public static function replaceThirdparty(DoliDB $dbs, $origin_id, $dest_id)
5438 {
5439 $tables = array(
5440 'facture'
5441 );
5442
5443 return CommonObject::commonReplaceThirdparty($dbs, $origin_id, $dest_id, $tables);
5444 }
5445
5454 public static function replaceProduct(DoliDB $db, $origin_id, $dest_id)
5455 {
5456 $tables = array(
5457 'facturedet'
5458 );
5459
5460 return CommonObject::commonReplaceProduct($db, $origin_id, $dest_id, $tables);
5461 }
5462
5468 public function hasDelay()
5469 {
5470 global $conf;
5471
5472 $now = dol_now();
5473
5474 // Paid invoices have status STATUS_CLOSED
5475 if ($this->statut != Facture::STATUS_VALIDATED) {
5476 return false;
5477 }
5478
5479 $hasDelay = $this->date_lim_reglement < ($now - $conf->facture->client->warning_delay);
5480 if ($hasDelay && !empty($this->retained_warranty) && !empty($this->retained_warranty_date_limit)) {
5481 $totalpaid = $this->getSommePaiement();
5482 $totalpaid = floatval($totalpaid);
5483 $RetainedWarrantyAmount = $this->getRetainedWarrantyAmount();
5484 if ($totalpaid >= 0 && $RetainedWarrantyAmount >= 0) {
5485 if (($totalpaid < $this->total_ttc - $RetainedWarrantyAmount) && $this->date_lim_reglement < ($now - $conf->facture->client->warning_delay)) {
5486 $hasDelay = 1;
5487 } elseif ($totalpaid < $this->total_ttc && $this->retained_warranty_date_limit < ($now - $conf->facture->client->warning_delay)) {
5488 $hasDelay = 1;
5489 } else {
5490 $hasDelay = 0;
5491 }
5492 }
5493 }
5494
5495 return $hasDelay;
5496 }
5497
5502 public function displayRetainedWarranty()
5503 {
5504 global $conf;
5505
5506 // TODO : add a flag on invoices to store this conf : INVOICE_RETAINED_WARRANTY_LIMITED_TO_FINAL_SITUATION
5507
5508 // 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
5509
5510 $displayWarranty = false;
5511 if (!empty($this->retained_warranty)) {
5512 $displayWarranty = true;
5513
5514 if ($this->type == Facture::TYPE_SITUATION && !empty($conf->global->INVOICE_RETAINED_WARRANTY_LIMITED_TO_FINAL_SITUATION)) {
5515 // Check if this situation invoice is 100% for real
5516 $displayWarranty = false;
5517 if (!empty($this->situation_final)) {
5518 $displayWarranty = true;
5519 } elseif (!empty($this->lines) && $this->status == Facture::STATUS_DRAFT) {
5520 // $object->situation_final need validation to be done so this test is need for draft
5521 $displayWarranty = true;
5522
5523 foreach ($this->lines as $i => $line) {
5524 if ($line->product_type < 2 && $line->situation_percent < 100) {
5525 $displayWarranty = false;
5526 break;
5527 }
5528 }
5529 }
5530 }
5531 }
5532
5533 return $displayWarranty;
5534 }
5535
5540 public function getRetainedWarrantyAmount($rounding = -1)
5541 {
5542 global $conf;
5543 if (empty($this->retained_warranty)) {
5544 return -1;
5545 }
5546
5547 $retainedWarrantyAmount = 0;
5548
5549 // Billed - retained warranty
5550 if ($this->type == Facture::TYPE_SITUATION && !empty($conf->global->INVOICE_RETAINED_WARRANTY_LIMITED_TO_FINAL_SITUATION)) {
5551 $displayWarranty = true;
5552 // Check if this situation invoice is 100% for real
5553 if (!empty($this->lines)) {
5554 foreach ($this->lines as $i => $line) {
5555 if ($line->product_type < 2 && $line->situation_percent < 100) {
5556 $displayWarranty = false;
5557 break;
5558 }
5559 }
5560 }
5561
5562 if ($displayWarranty && !empty($this->situation_final)) {
5564 $TPreviousIncoice = $this->tab_previous_situation_invoice;
5565
5566 $total2BillWT = 0;
5567 foreach ($TPreviousIncoice as &$fac) {
5568 $total2BillWT += $fac->total_ttc;
5569 }
5570 $total2BillWT += $this->total_ttc;
5571
5572 $retainedWarrantyAmount = $total2BillWT * $this->retained_warranty / 100;
5573 } else {
5574 return -1;
5575 }
5576 } else {
5577 // Because one day retained warranty could be used on standard invoices
5578 $retainedWarrantyAmount = $this->total_ttc * $this->retained_warranty / 100;
5579 }
5580
5581 if ($rounding < 0) {
5582 $rounding = min($conf->global->MAIN_MAX_DECIMALS_UNIT, $conf->global->MAIN_MAX_DECIMALS_TOT);
5583 }
5584
5585 if ($rounding > 0) {
5586 return round($retainedWarrantyAmount, $rounding);
5587 }
5588
5589 return $retainedWarrantyAmount;
5590 }
5591
5598 public function setRetainedWarranty($value)
5599 {
5600 dol_syslog(get_class($this).'::setRetainedWarranty('.$value.')');
5601
5602 if ($this->statut >= 0) {
5603 $fieldname = 'retained_warranty';
5604 $sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element;
5605 $sql .= " SET ".$fieldname." = ".((float) $value);
5606 $sql .= ' WHERE rowid='.((int) $this->id);
5607
5608 if ($this->db->query($sql)) {
5609 $this->retained_warranty = floatval($value);
5610 return 1;
5611 } else {
5612 dol_syslog(get_class($this).'::setRetainedWarranty Erreur '.$sql.' - '.$this->db->error());
5613 $this->error = $this->db->error();
5614 return -1;
5615 }
5616 } else {
5617 dol_syslog(get_class($this).'::setRetainedWarranty, status of the object is incompatible');
5618 $this->error = 'Status of the object is incompatible '.$this->statut;
5619 return -2;
5620 }
5621 }
5622
5623
5631 public function setRetainedWarrantyDateLimit($timestamp, $dateYmd = false)
5632 {
5633 if (!$timestamp && $dateYmd) {
5634 $timestamp = $this->db->jdate($dateYmd);
5635 }
5636
5637
5638 dol_syslog(get_class($this).'::setRetainedWarrantyDateLimit('.$timestamp.')');
5639 if ($this->statut >= 0) {
5640 $fieldname = 'retained_warranty_date_limit';
5641 $sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element;
5642 $sql .= " SET ".$fieldname." = ".(strval($timestamp) != '' ? "'".$this->db->idate($timestamp)."'" : 'null');
5643 $sql .= ' WHERE rowid = '.((int) $this->id);
5644
5645 if ($this->db->query($sql)) {
5646 $this->retained_warranty_date_limit = $timestamp;
5647 return 1;
5648 } else {
5649 dol_syslog(get_class($this).'::setRetainedWarrantyDateLimit Erreur '.$sql.' - '.$this->db->error());
5650 $this->error = $this->db->error();
5651 return -1;
5652 }
5653 } else {
5654 dol_syslog(get_class($this).'::setRetainedWarrantyDateLimit, status of the object is incompatible');
5655 $this->error = 'Status of the object is incompatible '.$this->statut;
5656 return -2;
5657 }
5658 }
5659
5660
5672 public function sendEmailsRemindersOnInvoiceDueDate($nbdays = 0, $paymentmode = 'all', $template = '', $datetouse = 'duedate', $forcerecipient = '')
5673 {
5674 global $conf, $langs, $user;
5675
5676 $error = 0;
5677 $this->output = '';
5678 $this->error = '';
5679 $nbMailSend = 0;
5680 $errorsMsg = array();
5681
5682 $langs->load("bills");
5683
5684 if (!isModEnabled('facture')) { // Should not happen. If module disabled, cron job should not be visible.
5685 $this->output .= $langs->trans('ModuleNotEnabled', $langs->transnoentitiesnoconv("Facture"));
5686 return 0;
5687 }
5688 if (!in_array($datetouse, array('duedate', 'invoicedate'))) {
5689 $this->output .= 'Bad value for parameter datetouse. Must be "duedate" or "invoicedate"';
5690 return 0;
5691 }
5692 /*if (empty($conf->global->FACTURE_REMINDER_EMAIL)) {
5693 $langs->load("bills");
5694 $this->output .= $langs->trans('EventRemindersByEmailNotEnabled', $langs->transnoentitiesnoconv("Facture"));
5695 return 0;
5696 }
5697 */
5698
5699 require_once DOL_DOCUMENT_ROOT.'/core/lib/date.lib.php';
5700 require_once DOL_DOCUMENT_ROOT.'/core/class/html.formmail.class.php';
5701 require_once DOL_DOCUMENT_ROOT.'/core/class/CMailFile.class.php';
5702 $formmail = new FormMail($this->db);
5703
5704 $now = dol_now();
5705 $tmpidate = dol_get_first_hour(dol_time_plus_duree($now, $nbdays, 'd'), 'gmt');
5706
5707 $tmpinvoice = new Facture($this->db);
5708
5709 dol_syslog(__METHOD__." start", LOG_INFO);
5710
5711 // Select all action comm reminder
5712 $sql = "SELECT rowid as id FROM ".MAIN_DB_PREFIX."facture as f";
5713 if (!empty($paymentmode) && $paymentmode != 'all') {
5714 $sql .= ", ".MAIN_DB_PREFIX."c_paiement as cp";
5715 }
5716 $sql .= " WHERE f.paye = 0"; // Only unpaid
5717 $sql .= " AND f.fk_statut = ".self::STATUS_VALIDATED; // Only validated status
5718 if ($datetouse == 'invoicedate') {
5719 $sql .= " AND f.datef = '".$this->db->idate($tmpidate, 'gmt')."'";
5720 } else {
5721 $sql .= " AND f.date_lim_reglement = '".$this->db->idate($tmpidate, 'gmt')."'";
5722 }
5723 $sql .= " AND f.entity IN (".getEntity('facture', 0).")"; // One batch process only one company (no sharing)
5724 if (!empty($paymentmode) && $paymentmode != 'all') {
5725 $sql .= " AND f.fk_mode_reglement = cp.id AND cp.code = '".$this->db->escape($paymentmode)."'";
5726 }
5727 // TODO Add a filter to check there is no payment started yet
5728 if ($datetouse == 'invoicedate') {
5729 $sql .= $this->db->order("datef", "ASC");
5730 } else {
5731 $sql .= $this->db->order("date_lim_reglement", "ASC");
5732 }
5733
5734 $resql = $this->db->query($sql);
5735
5736 $stmpidate = dol_print_date($tmpidate, 'day', 'gmt');
5737 if ($datetouse == 'invoicedate') {
5738 $this->output .= $langs->transnoentitiesnoconv("SearchValidatedInvoicesWithDate", $stmpidate);
5739 } else {
5740 $this->output .= $langs->transnoentitiesnoconv("SearchUnpaidInvoicesWithDueDate", $stmpidate);
5741 }
5742 if (!empty($paymentmode) && $paymentmode != 'all') {
5743 $this->output .= ' ('.$langs->transnoentitiesnoconv("PaymentMode").' '.$paymentmode.')';
5744 }
5745 $this->output .= '<br>';
5746
5747 if ($resql) {
5748 while ($obj = $this->db->fetch_object($resql)) {
5749 if (!$error) {
5750 // Load event
5751 $res = $tmpinvoice->fetch($obj->id);
5752 if ($res > 0) {
5753 $tmpinvoice->fetch_thirdparty();
5754
5755 $outputlangs = new Translate('', $conf);
5756 if ($tmpinvoice->thirdparty->default_lang) {
5757 $outputlangs->setDefaultLang($tmpinvoice->thirdparty->default_lang);
5758 $outputlangs->loadLangs(array("main", "bills"));
5759 } else {
5760 $outputlangs = $langs;
5761 }
5762
5763 // Select email template according to language of recipient
5764 $arraymessage = $formmail->getEMailTemplate($this->db, 'facture_send', $user, $outputlangs, (is_numeric($template) ? $template : 0), 1, (is_numeric($template) ? '' : $template));
5765 if (is_numeric($arraymessage) && $arraymessage <= 0) {
5766 $langs->load("errors");
5767 $this->output .= $langs->trans('ErrorFailedToFindEmailTemplate', $template);
5768 return 0;
5769 }
5770
5771 // PREPARE EMAIL
5772 $errormesg = '';
5773
5774 // Make substitution in email content
5775 $substitutionarray = getCommonSubstitutionArray($outputlangs, 0, '', $tmpinvoice);
5776
5777 complete_substitutions_array($substitutionarray, $outputlangs, $tmpinvoice);
5778
5779 // Topic
5780 $sendTopic = make_substitutions(empty($arraymessage->topic) ? $outputlangs->transnoentitiesnoconv('InformationMessage') : $arraymessage->topic, $substitutionarray, $outputlangs, 1);
5781
5782 // Content
5783 $content = $outputlangs->transnoentitiesnoconv($arraymessage->content);
5784
5785 $sendContent = make_substitutions($content, $substitutionarray, $outputlangs, 1);
5786
5787 // Recipient
5788 $to = array();
5789 if ($forcerecipient) { // If a recipient was forced
5790 $to = array($forcerecipient);
5791 } else {
5792 $res = $tmpinvoice->fetch_thirdparty();
5793 $recipient = $tmpinvoice->thirdparty;
5794 if ($res > 0) {
5795 $tmparraycontact = $tmpinvoice->liste_contact(-1, 'external', 0, 'BILLING');
5796 if (is_array($tmparraycontact) && count($tmparraycontact) > 0) {
5797 foreach ($tmparraycontact as $data_email) {
5798 if (!empty($data_email['email'])) {
5799 $to[] = $tmpinvoice->thirdparty->contact_get_property($data_email['id'], 'email');
5800 }
5801 }
5802 }
5803 if (empty($to) && !empty($recipient->email)) {
5804 $to[] = $recipient->email;
5805 }
5806 if (empty($to)) {
5807 $errormesg = "Failed to send remind to thirdparty id=".$tmpinvoice->socid.". No email defined for invoice or customer.";
5808 $error++;
5809 }
5810 } else {
5811 $errormesg = "Failed to load recipient with thirdparty id=".$tmpinvoice->socid;
5812 $error++;
5813 }
5814 }
5815
5816 // Sender
5817 $from = getDolGlobalString('MAIN_MAIL_EMAIL_FROM');
5818 if (!empty($arraymessage->email_from)) { // If a sender is defined into template, we use it in priority
5819 $from = $arraymessage->email_from;
5820 }
5821 if (empty($from)) {
5822 $errormesg = "Failed to get sender into global setup MAIN_MAIL_EMAIL_FROM";
5823 $error++;
5824 }
5825
5826 if (!$error && !empty($to)) {
5827 $this->db->begin();
5828
5829 $to = implode(',', $to);
5830 if (!empty($arraymessage->email_to)) { // If a recipient is defined into template, we add it
5831 $to = $to.','.$arraymessage->email_to;
5832 }
5833
5834 // Errors Recipient
5835 $errors_to = $conf->global->MAIN_MAIL_ERRORS_TO;
5836
5837 $trackid = 'inv'.$tmpinvoice->id;
5838 $sendcontext = 'standard';
5839
5840 $email_tocc = '';
5841 if (!empty($arraymessage->email_tocc)) { // If a CC is defined into template, we use it
5842 $email_tocc = $arraymessage->email_tocc;
5843 }
5844
5845 $email_tobcc = '';
5846 if (!empty($arraymessage->email_tobcc)) { // If a BCC is defined into template, we use it
5847 $email_tobcc = $arraymessage->email_tobcc;
5848 }
5849
5850 //join file is asked
5851 $joinFile = [];
5852 $joinFileName = [];
5853 $joinFileMime = [];
5854 if ($arraymessage->joinfiles == 1 && !empty($tmpinvoice->last_main_doc)) {
5855 $joinFile[] = DOL_DATA_ROOT.'/'.$tmpinvoice->last_main_doc;
5856 $joinFileName[] = basename($tmpinvoice->last_main_doc);
5857 $joinFileMime[] = dol_mimetype(DOL_DATA_ROOT.'/'.$tmpinvoice->last_main_doc);
5858 }
5859
5860 // Mail Creation
5861 $cMailFile = new CMailFile($sendTopic, $to, $from, $sendContent, $joinFile, $joinFileMime, $joinFileName, $email_tocc, $email_tobcc, 0, 1, $errors_to, '', $trackid, '', $sendcontext, '');
5862
5863 // Sending Mail
5864 if ($cMailFile->sendfile()) {
5865 $nbMailSend++;
5866
5867 // Add a line into event table
5868 require_once DOL_DOCUMENT_ROOT.'/comm/action/class/actioncomm.class.php';
5869
5870 // Insert record of emails sent
5871 $actioncomm = new ActionComm($this->db);
5872
5873 $actioncomm->type_code = 'AC_OTH_AUTO'; // Event insert into agenda automatically
5874 $actioncomm->socid = $tmpinvoice->thirdparty->id; // To link to a company
5875 $actioncomm->contact_id = 0;
5876
5877 $actioncomm->code = 'AC_EMAIL';
5878 $actioncomm->label = 'sendEmailsRemindersOnInvoiceDueDateOK (nbdays='.$nbdays.' paymentmode='.$paymentmode.' template='.$template.' datetouse='.$datetouse.' forcerecipient='.$forcerecipient.')';
5879 $actioncomm->note_private = $sendContent;
5880 $actioncomm->fk_project = $tmpinvoice->fk_project;
5881 $actioncomm->datep = dol_now();
5882 $actioncomm->datef = $actioncomm->datep;
5883 $actioncomm->percentage = -1; // Not applicable
5884 $actioncomm->authorid = $user->id; // User saving action
5885 $actioncomm->userownerid = $user->id; // Owner of action
5886 // Fields when action is an email (content should be added into note)
5887 $actioncomm->email_msgid = $cMailFile->msgid;
5888 $actioncomm->email_subject = $sendTopic;
5889 $actioncomm->email_from = $from;
5890 $actioncomm->email_sender = '';
5891 $actioncomm->email_to = $to;
5892 //$actioncomm->email_tocc = $sendtocc;
5893 //$actioncomm->email_tobcc = $sendtobcc;
5894 //$actioncomm->email_subject = $subject;
5895 $actioncomm->errors_to = $errors_to;
5896
5897 $actioncomm->elementtype = 'invoice';
5898 $actioncomm->fk_element = $tmpinvoice->id;
5899
5900 //$actioncomm->extraparams = $extraparams;
5901
5902 $actioncomm->create($user);
5903 } else {
5904 $errormesg = $cMailFile->error.' : '.$to;
5905 $error++;
5906
5907 // Add a line into event table
5908 require_once DOL_DOCUMENT_ROOT.'/comm/action/class/actioncomm.class.php';
5909
5910 // Insert record of emails sent
5911 $actioncomm = new ActionComm($this->db);
5912
5913 $actioncomm->type_code = 'AC_OTH_AUTO'; // Event insert into agenda automatically
5914 $actioncomm->socid = $tmpinvoice->thirdparty->id; // To link to a company
5915 $actioncomm->contact_id = 0;
5916
5917 $actioncomm->code = 'AC_EMAIL';
5918 $actioncomm->label = 'sendEmailsRemindersOnInvoiceDueDateKO';
5919 $actioncomm->note_private = $errormesg;
5920 $actioncomm->fk_project = $tmpinvoice->fk_project;
5921 $actioncomm->datep = dol_now();
5922 $actioncomm->datef = $actioncomm->datep;
5923 $actioncomm->percentage = -1; // Not applicable
5924 $actioncomm->authorid = $user->id; // User saving action
5925 $actioncomm->userownerid = $user->id; // Owner of action
5926 // Fields when action is an email (content should be added into note)
5927 $actioncomm->email_msgid = $cMailFile->msgid;
5928 $actioncomm->email_from = $from;
5929 $actioncomm->email_sender = '';
5930 $actioncomm->email_to = $to;
5931 //$actioncomm->email_tocc = $sendtocc;
5932 //$actioncomm->email_tobcc = $sendtobcc;
5933 //$actioncomm->email_subject = $subject;
5934 $actioncomm->errors_to = $errors_to;
5935
5936 //$actioncomm->extraparams = $extraparams;
5937
5938 $actioncomm->create($user);
5939 }
5940
5941 $this->db->commit(); // We always commit
5942 }
5943
5944 if ($errormesg) {
5945 $errorsMsg[] = $errormesg;
5946 }
5947 } else {
5948 $errorsMsg[] = 'Failed to fetch record invoice with ID = '.$obj->id;
5949 $error++;
5950 }
5951 }
5952 }
5953 } else {
5954 $error++;
5955 }
5956
5957 if (!$error) {
5958 $this->output .= 'Nb of emails sent : '.$nbMailSend;
5959
5960 dol_syslog(__METHOD__." end - ".$this->output, LOG_INFO);
5961
5962 return 0;
5963 } else {
5964 $this->error = 'Nb of emails sent : '.$nbMailSend.', '.(!empty($errorsMsg)) ? join(', ', $errorsMsg) : $error;
5965
5966 dol_syslog(__METHOD__." end - ".$this->error, LOG_INFO);
5967
5968 return $error;
5969 }
5970 }
5971
5978 public function willBeLastOfSameType($allow_validated_drafts = false)
5979 {
5980 // get date of last validated invoices of same type
5981 $sql = "SELECT datef";
5982 $sql .= " FROM ".MAIN_DB_PREFIX."facture";
5983 $sql .= " WHERE type = " . (int) $this->type ;
5984 $sql .= " AND date_valid IS NOT NULL";
5985 $sql .= " AND entity IN (".getEntity('invoice').")";
5986 $sql .= " ORDER BY datef DESC LIMIT 1";
5987
5988 $result = $this->db->query($sql);
5989 if ($result) {
5990 // compare with current validation date
5991 if ($this->db->num_rows($result)) {
5992 $obj = $this->db->fetch_object($result);
5993 $last_date = $this->db->jdate($obj->datef);
5994 $invoice_date = $this->date;
5995
5996 $is_last_of_same_type = $invoice_date >= $last_date;
5997 if ($allow_validated_drafts) {
5998 $is_last_of_same_type = $is_last_of_same_type || (!strpos($this->ref, 'PROV') && $this->status == self::STATUS_DRAFT);
5999 }
6000
6001 return array($is_last_of_same_type, $last_date);
6002 } else {
6003 // element is first of type to be validated
6004 return array(true);
6005 }
6006 } else {
6007 dol_print_error($this->db);
6008 }
6009
6010 return array();
6011 }
6012
6020 public function getKanbanView($option = '', $arraydata = null)
6021 {
6022 $selected = (empty($arraydata['selected']) ? 0 : $arraydata['selected']);
6023
6024 $return = '<div class="box-flex-item box-flex-grow-zero">';
6025 $return .= '<div class="info-box info-box-sm">';
6026 $return .= '<span class="info-box-icon bg-infobox-action">';
6027 $return .= img_picto('', $this->picto);
6028 //$return .= '<i class="fa fa-dol-action"></i>'; // Can be image
6029 $return .= '</span>';
6030 $return .= '<div class="info-box-content">';
6031 $return .= '<span class="info-box-ref inline-block tdoverflowmax150 valignmiddle">'.(method_exists($this, 'getNomUrl') ? $this->getNomUrl(1) : $this->ref).'</span>';
6032 $return .= '<input id="cb'.$this->id.'" class="flat checkforselect fright" type="checkbox" name="toselect[]" value="'.$this->id.'"'.($selected ? ' checked="checked"' : '').'>';
6033 if (property_exists($this, 'socid')) {
6034 $return .= '<br><span class="info-box-label">'.$this->socid.'</span>';
6035 }
6036 if (property_exists($this, 'fk_user_author')) {
6037 $return .= '<br><span class="info-box-label">'.$this->fk_user_author.'</span>';
6038 }
6039 if (method_exists($this, 'getLibStatut')) {
6040 $return .= '<br><div class="info-box-status margintoponly">'.$this->getLibStatut(3).'</div>';
6041 }
6042 $return .= '</div>';
6043 $return .= '</div>';
6044 $return .= '</div>';
6045 return $return;
6046 }
6047}
6048
6054{
6058 public $element = 'facturedet';
6059
6063 public $table_element = 'facturedet';
6064
6068 public $oldline;
6069
6075
6077 public $desc;
6078 public $ref_ext; // External reference of the line
6079
6080 public $localtax1_type; // Local tax 1 type
6081 public $localtax2_type; // Local tax 2 type
6082 public $fk_remise_except; // Link to line into llx_remise_except
6083 public $rang = 0;
6084
6085 public $fk_fournprice;
6086 public $pa_ht;
6087 public $marge_tx;
6088 public $marque_tx;
6089
6090 public $remise_percent;
6091
6092 public $special_code; // Liste d'options non cumulabels:
6093 // 1: frais de port
6094 // 2: ecotaxe
6095 // 3: ??
6096
6097 public $origin;
6098 public $origin_id;
6099
6100 public $fk_code_ventilation = 0;
6101
6102 public $date_start;
6103 public $date_end;
6104
6105 public $skip_update_total; // Skip update price total for special lines
6106
6110 public $situation_percent;
6111
6115 public $fk_prev_id;
6116
6117 // Multicurrency
6118 public $fk_multicurrency;
6119 public $multicurrency_code;
6120 public $multicurrency_subprice;
6121 public $multicurrency_total_ht;
6122 public $multicurrency_total_tva;
6123 public $multicurrency_total_ttc;
6124
6130 public function __construct($db)
6131 {
6132 $this->db = $db;
6133 }
6134
6141 public function fetch($rowid)
6142 {
6143 $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,';
6144 $sql .= ' fd.localtax1_tx, fd. localtax2_tx, fd.remise, fd.remise_percent, fd.fk_remise_except, fd.subprice, fd.ref_ext,';
6145 $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,';
6146 $sql .= ' fd.info_bits, fd.special_code, fd.total_ht, fd.total_tva, fd.total_ttc, fd.total_localtax1, fd.total_localtax2, fd.rang,';
6147 $sql .= ' fd.fk_code_ventilation,';
6148 $sql .= ' fd.fk_unit, fd.fk_user_author, fd.fk_user_modif,';
6149 $sql .= ' fd.situation_percent, fd.fk_prev_id,';
6150 $sql .= ' fd.multicurrency_subprice,';
6151 $sql .= ' fd.multicurrency_total_ht,';
6152 $sql .= ' fd.multicurrency_total_tva,';
6153 $sql .= ' fd.multicurrency_total_ttc,';
6154 $sql .= ' p.ref as product_ref, p.label as product_label, p.description as product_desc';
6155 $sql .= ' FROM '.MAIN_DB_PREFIX.'facturedet as fd';
6156 $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'product as p ON fd.fk_product = p.rowid';
6157 $sql .= ' WHERE fd.rowid = '.((int) $rowid);
6158
6159 $result = $this->db->query($sql);
6160 if ($result) {
6161 $objp = $this->db->fetch_object($result);
6162
6163 if (!$objp) {
6164 $this->error = 'InvoiceLine with id '. $rowid .' not found sql='.$sql;
6165 return 0;
6166 }
6167
6168 $this->rowid = $objp->rowid;
6169 $this->id = $objp->rowid;
6170 $this->fk_facture = $objp->fk_facture;
6171 $this->fk_parent_line = $objp->fk_parent_line;
6172 $this->label = $objp->custom_label;
6173 $this->desc = $objp->description;
6174 $this->qty = $objp->qty;
6175 $this->subprice = $objp->subprice;
6176 $this->ref_ext = $objp->ref_ext;
6177 $this->vat_src_code = $objp->vat_src_code;
6178 $this->tva_tx = $objp->tva_tx;
6179 $this->localtax1_tx = $objp->localtax1_tx;
6180 $this->localtax2_tx = $objp->localtax2_tx;
6181 $this->remise_percent = $objp->remise_percent;
6182 $this->fk_remise_except = $objp->fk_remise_except;
6183 $this->fk_product = $objp->fk_product;
6184 $this->product_type = $objp->product_type;
6185 $this->date_start = $this->db->jdate($objp->date_start);
6186 $this->date_end = $this->db->jdate($objp->date_end);
6187 $this->info_bits = $objp->info_bits;
6188 $this->tva_npr = ($objp->info_bits & 1 == 1) ? 1 : 0;
6189 $this->special_code = $objp->special_code;
6190 $this->total_ht = $objp->total_ht;
6191 $this->total_tva = $objp->total_tva;
6192 $this->total_localtax1 = $objp->total_localtax1;
6193 $this->total_localtax2 = $objp->total_localtax2;
6194 $this->total_ttc = $objp->total_ttc;
6195 $this->fk_code_ventilation = $objp->fk_code_ventilation;
6196 $this->rang = $objp->rang;
6197 $this->fk_fournprice = $objp->fk_fournprice;
6198 $marginInfos = getMarginInfos($objp->subprice, $objp->remise_percent, $objp->tva_tx, $objp->localtax1_tx, $objp->localtax2_tx, $this->fk_fournprice, $objp->pa_ht);
6199 $this->pa_ht = $marginInfos[0];
6200 $this->marge_tx = $marginInfos[1];
6201 $this->marque_tx = $marginInfos[2];
6202
6203 $this->ref = $objp->product_ref; // deprecated
6204
6205 $this->product_ref = $objp->product_ref;
6206 $this->product_label = $objp->product_label;
6207 $this->product_desc = $objp->product_desc;
6208
6209 $this->fk_unit = $objp->fk_unit;
6210 $this->fk_user_modif = $objp->fk_user_modif;
6211 $this->fk_user_author = $objp->fk_user_author;
6212
6213 $this->situation_percent = $objp->situation_percent;
6214 $this->fk_prev_id = $objp->fk_prev_id;
6215
6216 $this->multicurrency_subprice = $objp->multicurrency_subprice;
6217 $this->multicurrency_total_ht = $objp->multicurrency_total_ht;
6218 $this->multicurrency_total_tva = $objp->multicurrency_total_tva;
6219 $this->multicurrency_total_ttc = $objp->multicurrency_total_ttc;
6220
6221 $this->fetch_optionals();
6222
6223 $this->db->free($result);
6224
6225 return 1;
6226 } else {
6227 $this->error = $this->db->lasterror();
6228 return -1;
6229 }
6230 }
6231
6239 public function insert($notrigger = 0, $noerrorifdiscountalreadylinked = 0)
6240 {
6241 global $langs, $user, $conf;
6242
6243 $error = 0;
6244
6245 $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'.
6246
6247 dol_syslog(get_class($this)."::insert rang=".$this->rang, LOG_DEBUG);
6248
6249 // Clean parameters
6250 $this->desc = trim($this->desc);
6251 if (empty($this->tva_tx)) {
6252 $this->tva_tx = 0;
6253 }
6254 if (empty($this->localtax1_tx)) {
6255 $this->localtax1_tx = 0;
6256 }
6257 if (empty($this->localtax2_tx)) {
6258 $this->localtax2_tx = 0;
6259 }
6260 if (empty($this->localtax1_type)) {
6261 $this->localtax1_type = 0;
6262 }
6263 if (empty($this->localtax2_type)) {
6264 $this->localtax2_type = 0;
6265 }
6266 if (empty($this->total_localtax1)) {
6267 $this->total_localtax1 = 0;
6268 }
6269 if (empty($this->total_localtax2)) {
6270 $this->total_localtax2 = 0;
6271 }
6272 if (empty($this->rang)) {
6273 $this->rang = 0;
6274 }
6275 if (empty($this->remise_percent)) {
6276 $this->remise_percent = 0;
6277 }
6278 if (empty($this->info_bits)) {
6279 $this->info_bits = 0;
6280 }
6281 if (empty($this->subprice)) {
6282 $this->subprice = 0;
6283 }
6284 if (empty($this->ref_ext)) {
6285 $this->ref_ext = '';
6286 }
6287 if (empty($this->special_code)) {
6288 $this->special_code = 0;
6289 }
6290 if (empty($this->fk_parent_line)) {
6291 $this->fk_parent_line = 0;
6292 }
6293 if (empty($this->fk_prev_id)) {
6294 $this->fk_prev_id = 0;
6295 }
6296 if (!isset($this->situation_percent) || $this->situation_percent > 100 || (string) $this->situation_percent == '') {
6297 $this->situation_percent = 100;
6298 }
6299
6300 if (empty($this->pa_ht)) {
6301 $this->pa_ht = 0;
6302 }
6303 if (empty($this->multicurrency_subprice)) {
6304 $this->multicurrency_subprice = 0;
6305 }
6306 if (empty($this->multicurrency_total_ht)) {
6307 $this->multicurrency_total_ht = 0;
6308 }
6309 if (empty($this->multicurrency_total_tva)) {
6310 $this->multicurrency_total_tva = 0;
6311 }
6312 if (empty($this->multicurrency_total_ttc)) {
6313 $this->multicurrency_total_ttc = 0;
6314 }
6315
6316 // if buy price not defined, define buyprice as configured in margin admin
6317 if ($this->pa_ht == 0 && $pa_ht_isemptystring) {
6318 if (($result = $this->defineBuyPrice($this->subprice, $this->remise_percent, $this->fk_product)) < 0) {
6319 return $result;
6320 } else {
6321 $this->pa_ht = $result;
6322 }
6323 }
6324
6325 // Check parameters
6326 if ($this->product_type < 0) {
6327 $this->error = 'ErrorProductTypeMustBe0orMore';
6328 return -1;
6329 }
6330 if (!empty($this->fk_product) && $this->fk_product > 0) {
6331 // Check product exists
6332 $result = Product::isExistingObject('product', $this->fk_product);
6333 if ($result <= 0) {
6334 $this->error = 'ErrorProductIdDoesNotExists';
6335 dol_syslog(get_class($this)."::insert Error ".$this->error, LOG_ERR);
6336 return -1;
6337 }
6338 }
6339
6340 $this->db->begin();
6341
6342 // Update line in database
6343 $sql = 'INSERT INTO '.MAIN_DB_PREFIX.'facturedet';
6344 $sql .= ' (fk_facture, fk_parent_line, label, description, qty,';
6345 $sql .= ' vat_src_code, tva_tx, localtax1_tx, localtax2_tx, localtax1_type, localtax2_type,';
6346 $sql .= ' fk_product, product_type, remise_percent, subprice, ref_ext, fk_remise_except,';
6347 $sql .= ' date_start, date_end, fk_code_ventilation, ';
6348 $sql .= ' rang, special_code, fk_product_fournisseur_price, buy_price_ht,';
6349 $sql .= ' info_bits, total_ht, total_tva, total_ttc, total_localtax1, total_localtax2,';
6350 $sql .= ' situation_percent, fk_prev_id,';
6351 $sql .= ' fk_unit, fk_user_author, fk_user_modif,';
6352 $sql .= ' fk_multicurrency, multicurrency_code, multicurrency_subprice, multicurrency_total_ht, multicurrency_total_tva, multicurrency_total_ttc';
6353 $sql .= ')';
6354 $sql .= " VALUES (".$this->fk_facture.",";
6355 $sql .= " ".($this->fk_parent_line > 0 ? $this->fk_parent_line : "null").",";
6356 $sql .= " ".(!empty($this->label) ? "'".$this->db->escape($this->label)."'" : "null").",";
6357 $sql .= " '".$this->db->escape($this->desc)."',";
6358 $sql .= " ".price2num($this->qty).",";
6359 $sql .= " ".(empty($this->vat_src_code) ? "''" : "'".$this->db->escape($this->vat_src_code)."'").",";
6360 $sql .= " ".price2num($this->tva_tx).",";
6361 $sql .= " ".price2num($this->localtax1_tx).",";
6362 $sql .= " ".price2num($this->localtax2_tx).",";
6363 $sql .= " '".$this->db->escape($this->localtax1_type)."',";
6364 $sql .= " '".$this->db->escape($this->localtax2_type)."',";
6365 $sql .= ' '.((!empty($this->fk_product) && $this->fk_product > 0) ? $this->fk_product : "null").',';
6366 $sql .= " ".((int) $this->product_type).",";
6367 $sql .= " ".price2num($this->remise_percent).",";
6368 $sql .= " ".price2num($this->subprice).",";
6369 $sql .= " '".$this->db->escape($this->ref_ext)."',";
6370 $sql .= ' '.(!empty($this->fk_remise_except) ? $this->fk_remise_except : "null").',';
6371 $sql .= " ".(!empty($this->date_start) ? "'".$this->db->idate($this->date_start)."'" : "null").",";
6372 $sql .= " ".(!empty($this->date_end) ? "'".$this->db->idate($this->date_end)."'" : "null").",";
6373 $sql .= ' '.((int) $this->fk_code_ventilation).',';
6374 $sql .= ' '.((int) $this->rang).',';
6375 $sql .= ' '.((int) $this->special_code).',';
6376 $sql .= ' '.(!empty($this->fk_fournprice) ? $this->fk_fournprice : "null").',';
6377 $sql .= ' '.price2num($this->pa_ht).',';
6378 $sql .= " '".$this->db->escape($this->info_bits)."',";
6379 $sql .= " ".price2num($this->total_ht).",";
6380 $sql .= " ".price2num($this->total_tva).",";
6381 $sql .= " ".price2num($this->total_ttc).",";
6382 $sql .= " ".price2num($this->total_localtax1).",";
6383 $sql .= " ".price2num($this->total_localtax2);
6384 $sql .= ", ".((float) $this->situation_percent);
6385 $sql .= ", ".(!empty($this->fk_prev_id) ? $this->fk_prev_id : "null");
6386 $sql .= ", ".(!$this->fk_unit ? 'NULL' : $this->fk_unit);
6387 $sql .= ", ".((int) $user->id);
6388 $sql .= ", ".((int) $user->id);
6389 $sql .= ", ".(int) $this->fk_multicurrency;
6390 $sql .= ", '".$this->db->escape($this->multicurrency_code)."'";
6391 $sql .= ", ".price2num($this->multicurrency_subprice);
6392 $sql .= ", ".price2num($this->multicurrency_total_ht);
6393 $sql .= ", ".price2num($this->multicurrency_total_tva);
6394 $sql .= ", ".price2num($this->multicurrency_total_ttc);
6395 $sql .= ')';
6396
6397 dol_syslog(get_class($this)."::insert", LOG_DEBUG);
6398 $resql = $this->db->query($sql);
6399 if ($resql) {
6400 $this->id = $this->db->last_insert_id(MAIN_DB_PREFIX.'facturedet');
6401 $this->rowid = $this->id; // For backward compatibility
6402
6403 if (!$error) {
6404 $result = $this->insertExtraFields();
6405 if ($result < 0) {
6406 $error++;
6407 }
6408 }
6409
6410 // If fk_remise_except is defined, the discount is linked to the invoice
6411 // which flags it as "consumed".
6412 if ($this->fk_remise_except) {
6413 $discount = new DiscountAbsolute($this->db);
6414 $result = $discount->fetch($this->fk_remise_except);
6415 if ($result >= 0) {
6416 // Check if discount was found
6417 if ($result > 0) {
6418 // Check if discount not already affected to another invoice
6419 if ($discount->fk_facture_line > 0) {
6420 if (empty($noerrorifdiscountalreadylinked)) {
6421 $this->error = $langs->trans("ErrorDiscountAlreadyUsed", $discount->id);
6422 dol_syslog(get_class($this)."::insert Error ".$this->error, LOG_ERR);
6423 $this->db->rollback();
6424 return -3;
6425 }
6426 } else {
6427 $result = $discount->link_to_invoice($this->rowid, 0);
6428 if ($result < 0) {
6429 $this->error = $discount->error;
6430 dol_syslog(get_class($this)."::insert Error ".$this->error, LOG_ERR);
6431 $this->db->rollback();
6432 return -3;
6433 }
6434 }
6435 } else {
6436 $this->error = $langs->trans("ErrorADiscountThatHasBeenRemovedIsIncluded");
6437 dol_syslog(get_class($this)."::insert Error ".$this->error, LOG_ERR);
6438 $this->db->rollback();
6439 return -3;
6440 }
6441 } else {
6442 $this->error = $discount->error;
6443 dol_syslog(get_class($this)."::insert Error ".$this->error, LOG_ERR);
6444 $this->db->rollback();
6445 return -3;
6446 }
6447 }
6448
6449 if (!$notrigger) {
6450 // Call trigger
6451 $result = $this->call_trigger('LINEBILL_INSERT', $user);
6452 if ($result < 0) {
6453 $this->db->rollback();
6454 return -2;
6455 }
6456 // End call triggers
6457 }
6458
6459 $this->db->commit();
6460 return $this->id;
6461 } else {
6462 $this->error = $this->db->lasterror();
6463 $this->db->rollback();
6464 return -2;
6465 }
6466 }
6467
6475 public function update($user = '', $notrigger = 0)
6476 {
6477 global $user, $conf;
6478
6479 $error = 0;
6480
6481 $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'.
6482
6483 // Clean parameters
6484 $this->desc = trim($this->desc);
6485 if (empty($this->ref_ext)) {
6486 $this->ref_ext = '';
6487 }
6488 if (empty($this->tva_tx)) {
6489 $this->tva_tx = 0;
6490 }
6491 if (empty($this->localtax1_tx)) {
6492 $this->localtax1_tx = 0;
6493 }
6494 if (empty($this->localtax2_tx)) {
6495 $this->localtax2_tx = 0;
6496 }
6497 if (empty($this->localtax1_type)) {
6498 $this->localtax1_type = 0;
6499 }
6500 if (empty($this->localtax2_type)) {
6501 $this->localtax2_type = 0;
6502 }
6503 if (empty($this->total_localtax1)) {
6504 $this->total_localtax1 = 0;
6505 }
6506 if (empty($this->total_localtax2)) {
6507 $this->total_localtax2 = 0;
6508 }
6509 if (empty($this->remise_percent)) {
6510 $this->remise_percent = 0;
6511 }
6512 if (empty($this->info_bits)) {
6513 $this->info_bits = 0;
6514 }
6515 if (empty($this->special_code)) {
6516 $this->special_code = 0;
6517 }
6518 if (empty($this->product_type)) {
6519 $this->product_type = 0;
6520 }
6521 if (empty($this->fk_parent_line)) {
6522 $this->fk_parent_line = 0;
6523 }
6524 if (!isset($this->situation_percent) || $this->situation_percent > 100 || (string) $this->situation_percent == '') {
6525 $this->situation_percent = 100;
6526 }
6527 if (empty($this->pa_ht)) {
6528 $this->pa_ht = 0;
6529 }
6530
6531 if (empty($this->multicurrency_subprice)) {
6532 $this->multicurrency_subprice = 0;
6533 }
6534 if (empty($this->multicurrency_total_ht)) {
6535 $this->multicurrency_total_ht = 0;
6536 }
6537 if (empty($this->multicurrency_total_tva)) {
6538 $this->multicurrency_total_tva = 0;
6539 }
6540 if (empty($this->multicurrency_total_ttc)) {
6541 $this->multicurrency_total_ttc = 0;
6542 }
6543
6544 // Check parameters
6545 if ($this->product_type < 0) {
6546 return -1;
6547 }
6548
6549 // if buy price not provided, define buyprice as configured in margin admin
6550 if ($this->pa_ht == 0 && $pa_ht_isemptystring) {
6551 // We call defineBuyPrice only if data was not provided (if input was '0', we will not go here and value will remaine '0')
6552 $result = $this->defineBuyPrice($this->subprice, $this->remise_percent, $this->fk_product);
6553 if ($result < 0) {
6554 return $result;
6555 } else {
6556 $this->pa_ht = $result;
6557 }
6558 }
6559
6560 $this->db->begin();
6561
6562 // Update line in database
6563 $sql = "UPDATE ".MAIN_DB_PREFIX."facturedet SET";
6564 $sql .= " description='".$this->db->escape($this->desc)."'";
6565 $sql .= ", ref_ext='".$this->db->escape($this->ref_ext)."'";
6566 $sql .= ", label=".(!empty($this->label) ? "'".$this->db->escape($this->label)."'" : "null");
6567 $sql .= ", subprice=".price2num($this->subprice);
6568 $sql .= ", remise_percent=".price2num($this->remise_percent);
6569 if ($this->fk_remise_except) {
6570 $sql .= ", fk_remise_except=".$this->fk_remise_except;
6571 } else {
6572 $sql .= ", fk_remise_except=null";
6573 }
6574 $sql .= ", vat_src_code = '".(empty($this->vat_src_code) ? '' : $this->db->escape($this->vat_src_code))."'";
6575 $sql .= ", tva_tx=".price2num($this->tva_tx);
6576 $sql .= ", localtax1_tx=".price2num($this->localtax1_tx);
6577 $sql .= ", localtax2_tx=".price2num($this->localtax2_tx);
6578 $sql .= ", localtax1_type='".$this->db->escape($this->localtax1_type)."'";
6579 $sql .= ", localtax2_type='".$this->db->escape($this->localtax2_type)."'";
6580 $sql .= ", qty=".price2num($this->qty);
6581 $sql .= ", date_start=".(!empty($this->date_start) ? "'".$this->db->idate($this->date_start)."'" : "null");
6582 $sql .= ", date_end=".(!empty($this->date_end) ? "'".$this->db->idate($this->date_end)."'" : "null");
6583 $sql .= ", product_type=".$this->product_type;
6584 $sql .= ", info_bits='".$this->db->escape($this->info_bits)."'";
6585 $sql .= ", special_code='".$this->db->escape($this->special_code)."'";
6586 if (empty($this->skip_update_total)) {
6587 $sql .= ", total_ht=".price2num($this->total_ht);
6588 $sql .= ", total_tva=".price2num($this->total_tva);
6589 $sql .= ", total_ttc=".price2num($this->total_ttc);
6590 $sql .= ", total_localtax1=".price2num($this->total_localtax1);
6591 $sql .= ", total_localtax2=".price2num($this->total_localtax2);
6592 }
6593 $sql .= ", fk_product_fournisseur_price=".(!empty($this->fk_fournprice) ? "'".$this->db->escape($this->fk_fournprice)."'" : "null");
6594 $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)
6595 $sql .= ", fk_parent_line=".($this->fk_parent_line > 0 ? $this->fk_parent_line : "null");
6596 if (!empty($this->rang)) {
6597 $sql .= ", rang=".((int) $this->rang);
6598 }
6599 $sql .= ", situation_percent = ".((float) $this->situation_percent);
6600 $sql .= ", fk_unit = ".(!$this->fk_unit ? 'NULL' : $this->fk_unit);
6601 $sql .= ", fk_user_modif = ".((int) $user->id);
6602
6603 // Multicurrency
6604 $sql .= ", multicurrency_subprice=".price2num($this->multicurrency_subprice);
6605 $sql .= ", multicurrency_total_ht=".price2num($this->multicurrency_total_ht);
6606 $sql .= ", multicurrency_total_tva=".price2num($this->multicurrency_total_tva);
6607 $sql .= ", multicurrency_total_ttc=".price2num($this->multicurrency_total_ttc);
6608
6609 $sql .= " WHERE rowid = ".((int) $this->rowid);
6610
6611 dol_syslog(get_class($this)."::update", LOG_DEBUG);
6612 $resql = $this->db->query($sql);
6613 if ($resql) {
6614 if (!$error) {
6615 $this->id = $this->rowid;
6616 $result = $this->insertExtraFields();
6617 if ($result < 0) {
6618 $error++;
6619 }
6620 }
6621
6622 if (!$error && !$notrigger) {
6623 // Call trigger
6624 $result = $this->call_trigger('LINEBILL_MODIFY', $user);
6625 if ($result < 0) {
6626 $this->db->rollback();
6627 return -2;
6628 }
6629 // End call triggers
6630 }
6631 $this->db->commit();
6632 return 1;
6633 } else {
6634 $this->error = $this->db->error();
6635 $this->db->rollback();
6636 return -2;
6637 }
6638 }
6639
6647 public function delete($tmpuser = null, $notrigger = false)
6648 {
6649 global $user;
6650
6651 $this->db->begin();
6652
6653 // Call trigger
6654 if (empty($notrigger)) {
6655 $result = $this->call_trigger('LINEBILL_DELETE', $user);
6656 if ($result < 0) {
6657 $this->db->rollback();
6658 return -1;
6659 }
6660 }
6661 // End call triggers
6662
6663 // extrafields
6664 $result = $this->deleteExtraFields();
6665 if ($result < 0) {
6666 $this->db->rollback();
6667 return -1;
6668 }
6669
6670 // Free discount linked to invoice line
6671 $sql = 'UPDATE '.MAIN_DB_PREFIX.'societe_remise_except';
6672 $sql .= ' SET fk_facture_line = NULL';
6673 $sql .= ' WHERE fk_facture_line = '.((int) $this->id);
6674
6675 dol_syslog(get_class($this)."::deleteline", LOG_DEBUG);
6676 $result = $this->db->query($sql);
6677 if (!$result) {
6678 $this->error = $this->db->error();
6679 $this->errors[] = $this->error;
6680 $this->db->rollback();
6681 return -1;
6682 }
6683
6684 $sql = 'UPDATE '.MAIN_DB_PREFIX.'element_time';
6685 $sql .= ' SET invoice_id = NULL, invoice_line_id = NULL';
6686 $sql .= ' WHERE invoice_line_id = '.((int) $this->id);
6687 if (!$this->db->query($sql)) {
6688 $this->error = $this->db->error()." sql=".$sql;
6689 $this->errors[] = $this->error;
6690 $this->db->rollback();
6691 return -1;
6692 }
6693
6694 $sql = "DELETE FROM ".MAIN_DB_PREFIX."facturedet WHERE rowid = ".((int) $this->id);
6695
6696 if ($this->db->query($sql)) {
6697 $this->db->commit();
6698 return 1;
6699 } else {
6700 $this->error = $this->db->error()." sql=".$sql;
6701 $this->errors[] = $this->error;
6702 $this->db->rollback();
6703 return -1;
6704 }
6705 }
6706
6707 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6714 public function update_total()
6715 {
6716 // phpcs:enable
6717 $this->db->begin();
6718 dol_syslog(get_class($this)."::update_total", LOG_DEBUG);
6719
6720 // Clean parameters
6721 if (empty($this->total_localtax1)) {
6722 $this->total_localtax1 = 0;
6723 }
6724 if (empty($this->total_localtax2)) {
6725 $this->total_localtax2 = 0;
6726 }
6727
6728 // Update line in database
6729 $sql = "UPDATE ".MAIN_DB_PREFIX."facturedet SET";
6730 $sql .= " total_ht=".price2num($this->total_ht);
6731 $sql .= ",total_tva=".price2num($this->total_tva);
6732 $sql .= ",total_localtax1=".price2num($this->total_localtax1);
6733 $sql .= ",total_localtax2=".price2num($this->total_localtax2);
6734 $sql .= ",total_ttc=".price2num($this->total_ttc);
6735 $sql .= " WHERE rowid = ".((int) $this->rowid);
6736
6737 dol_syslog(get_class($this)."::update_total", LOG_DEBUG);
6738
6739 $resql = $this->db->query($sql);
6740 if ($resql) {
6741 $this->db->commit();
6742 return 1;
6743 } else {
6744 $this->error = $this->db->error();
6745 $this->db->rollback();
6746 return -2;
6747 }
6748 }
6749
6750 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6759 public function get_prev_progress($invoiceid, $include_credit_note = true)
6760 {
6761 // phpcs:enable
6762 global $invoicecache;
6763
6764 if (is_null($this->fk_prev_id) || empty($this->fk_prev_id) || $this->fk_prev_id == "") {
6765 return 0;
6766 } else {
6767 // If invoice is not a situation invoice, this->fk_prev_id is used for something else
6768 if (!isset($invoicecache[$invoiceid])) {
6769 $invoicecache[$invoiceid] = new Facture($this->db);
6770 $invoicecache[$invoiceid]->fetch($invoiceid);
6771 }
6772 if ($invoicecache[$invoiceid]->type != Facture::TYPE_SITUATION) {
6773 return 0;
6774 }
6775
6776 $sql = "SELECT situation_percent FROM ".MAIN_DB_PREFIX."facturedet WHERE rowid = ".((int) $this->fk_prev_id);
6777 $resql = $this->db->query($sql);
6778 if ($resql && $this->db->num_rows($resql) > 0) {
6779 $res = $this->db->fetch_array($resql);
6780
6781 $returnPercent = floatval($res['situation_percent']);
6782
6783 if ($include_credit_note) {
6784 $sql = 'SELECT fd.situation_percent FROM '.MAIN_DB_PREFIX.'facturedet fd';
6785 $sql .= ' JOIN '.MAIN_DB_PREFIX.'facture f ON (f.rowid = fd.fk_facture) ';
6786 $sql .= " WHERE fd.fk_prev_id = ".((int) $this->fk_prev_id);
6787 $sql .= " AND f.situation_cycle_ref = ".((int) $invoicecache[$invoiceid]->situation_cycle_ref); // Prevent cycle outed
6788 $sql .= " AND f.type = ".Facture::TYPE_CREDIT_NOTE;
6789
6790 $res = $this->db->query($sql);
6791 if ($res) {
6792 while ($obj = $this->db->fetch_object($res)) {
6793 $returnPercent = $returnPercent + floatval($obj->situation_percent);
6794 }
6795 } else {
6796 dol_print_error($this->db);
6797 }
6798 }
6799
6800 return $returnPercent;
6801 } else {
6802 $this->error = $this->db->error();
6803 dol_syslog(get_class($this)."::select Error ".$this->error, LOG_ERR);
6804 $this->db->rollback();
6805 return -1;
6806 }
6807 }
6808 }
6809}
$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