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->ref_customer = '';
1298 $object->close_code = '';
1299 $object->close_note = '';
1300 if (getDolGlobalInt('MAIN_DONT_KEEP_NOTE_ON_CLONING') == 1) {
1301 $object->note_private = '';
1302 $object->note_public = '';
1303 }
1304
1305 // Loop on each line of new invoice
1306 foreach ($object->lines as $i => $line) {
1307 if (($object->lines[$i]->info_bits & 0x02) == 0x02) { // We do not clone line of discounts
1308 unset($object->lines[$i]);
1309 continue;
1310 }
1311
1312 // Bloc to update dates of service (month by month only if previously filled and similare to start and end of month)
1313 // If it's a service with start and end dates
1314 if (!empty($conf->global->INVOICE_AUTO_NEXT_MONTH_ON_LINES) && !empty($line->date_start) && !empty($line->date_end)) {
1315 // Get the dates
1316 $start = dol_getdate($line->date_start);
1317 $end = dol_getdate($line->date_end);
1318
1319 // Get the first and last day of the month
1320 $first = dol_get_first_day($start['year'], $start['mon']);
1321 $last = dol_get_last_day($end['year'], $end['mon']);
1322
1323 //print dol_print_date(dol_mktime(0, 0, 0, $start['mon'], $start['mday'], $start['year'], 'gmt'), 'dayhour').' '.dol_print_date($first, 'dayhour').'<br>';
1324 //print dol_mktime(23, 59, 59, $end['mon'], $end['mday'], $end['year'], 'gmt').' '.$last.'<br>';exit;
1325 // If start date is first date of month and end date is last date of month
1326 if (dol_mktime(0, 0, 0, $start['mon'], $start['mday'], $start['year'], 'gmt') == $first
1327 && dol_mktime(23, 59, 59, $end['mon'], $end['mday'], $end['year'], 'gmt') == $last) {
1328 $nextMonth = dol_get_next_month($end['mon'], $end['year']);
1329 $newFirst = dol_get_first_day($nextMonth['year'], $nextMonth['month']);
1330 $newLast = dol_get_last_day($nextMonth['year'], $nextMonth['month']);
1331 $object->lines[$i]->date_start = $newFirst;
1332 $object->lines[$i]->date_end = $newLast;
1333 }
1334 }
1335
1336 $object->lines[$i]->ref_ext = ''; // Do not clone ref_ext
1337 }
1338
1339 // Create clone
1340 $object->context['createfromclone'] = 'createfromclone';
1341 $result = $object->create($user);
1342 if ($result < 0) {
1343 $error++;
1344 $this->error = $object->error;
1345 $this->errors = $object->errors;
1346 } else {
1347 // copy internal contacts
1348 if ($object->copy_linked_contact($objFrom, 'internal') < 0) {
1349 $error++;
1350 $this->error = $object->error;
1351 $this->errors = $object->errors;
1352 } elseif ($object->socid == $objFrom->socid) {
1353 // copy external contacts if same company
1354 if ($object->copy_linked_contact($objFrom, 'external') < 0) {
1355 $error++;
1356 $this->error = $object->error;
1357 $this->errors = $object->errors;
1358 }
1359 }
1360 }
1361
1362 if (!$error) {
1363 // Hook of thirdparty module
1364 if (is_object($hookmanager)) {
1365 $parameters = array('objFrom'=>$objFrom);
1366 $action = '';
1367 $reshook = $hookmanager->executeHooks('createFrom', $parameters, $object, $action); // Note that $action and $object may have been modified by some hooks
1368 if ($reshook < 0) {
1369 $this->setErrorsFromObject($hookmanager);
1370 $error++;
1371 }
1372 }
1373 }
1374
1375 unset($object->context['createfromclone']);
1376
1377 // End
1378 if (!$error) {
1379 $this->db->commit();
1380 return $object->id;
1381 } else {
1382 $this->db->rollback();
1383 return -1;
1384 }
1385 }
1386
1394 public function createFromOrder($object, User $user)
1395 {
1396 global $conf, $hookmanager;
1397
1398 $error = 0;
1399
1400 // Closed order
1401 $this->date = dol_now();
1402 $this->source = 0;
1403
1404 $num = count($object->lines);
1405 for ($i = 0; $i < $num; $i++) {
1406 $line = new FactureLigne($this->db);
1407
1408 $line->libelle = $object->lines[$i]->libelle; // deprecated
1409 $line->label = $object->lines[$i]->label;
1410 $line->desc = $object->lines[$i]->desc;
1411 $line->subprice = $object->lines[$i]->subprice;
1412 $line->total_ht = $object->lines[$i]->total_ht;
1413 $line->total_tva = $object->lines[$i]->total_tva;
1414 $line->total_localtax1 = $object->lines[$i]->total_localtax1;
1415 $line->total_localtax2 = $object->lines[$i]->total_localtax2;
1416 $line->total_ttc = $object->lines[$i]->total_ttc;
1417 $line->vat_src_code = $object->lines[$i]->vat_src_code;
1418 $line->tva_tx = $object->lines[$i]->tva_tx;
1419 $line->localtax1_tx = $object->lines[$i]->localtax1_tx;
1420 $line->localtax2_tx = $object->lines[$i]->localtax2_tx;
1421 $line->qty = $object->lines[$i]->qty;
1422 $line->fk_remise_except = $object->lines[$i]->fk_remise_except;
1423 $line->remise_percent = $object->lines[$i]->remise_percent;
1424 $line->fk_product = $object->lines[$i]->fk_product;
1425 $line->info_bits = $object->lines[$i]->info_bits;
1426 $line->product_type = $object->lines[$i]->product_type;
1427 $line->rang = $object->lines[$i]->rang;
1428 $line->special_code = $object->lines[$i]->special_code;
1429 $line->fk_parent_line = $object->lines[$i]->fk_parent_line;
1430 $line->fk_unit = $object->lines[$i]->fk_unit;
1431 $line->date_start = $object->lines[$i]->date_start;
1432 $line->date_end = $object->lines[$i]->date_end;
1433
1434 // Multicurrency
1435 $line->fk_multicurrency = $object->lines[$i]->fk_multicurrency;
1436 $line->multicurrency_code = $object->lines[$i]->multicurrency_code;
1437 $line->multicurrency_subprice = $object->lines[$i]->multicurrency_subprice;
1438 $line->multicurrency_total_ht = $object->lines[$i]->multicurrency_total_ht;
1439 $line->multicurrency_total_tva = $object->lines[$i]->multicurrency_total_tva;
1440 $line->multicurrency_total_ttc = $object->lines[$i]->multicurrency_total_ttc;
1441
1442 $line->fk_fournprice = $object->lines[$i]->fk_fournprice;
1443 $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);
1444 $line->pa_ht = $marginInfos[0];
1445
1446 // get extrafields from original line
1447 $object->lines[$i]->fetch_optionals();
1448 foreach ($object->lines[$i]->array_options as $options_key => $value) {
1449 $line->array_options[$options_key] = $value;
1450 }
1451
1452 $this->lines[$i] = $line;
1453 }
1454
1455 $this->socid = $object->socid;
1456 $this->fk_project = $object->fk_project;
1457 $this->fk_account = $object->fk_account;
1458 $this->cond_reglement_id = $object->cond_reglement_id;
1459 $this->mode_reglement_id = $object->mode_reglement_id;
1460 $this->availability_id = $object->availability_id;
1461 $this->demand_reason_id = $object->demand_reason_id;
1462 $this->delivery_date = (empty($object->delivery_date) ? $object->date_livraison : $object->delivery_date);
1463 $this->date_livraison = $object->delivery_date; // deprecated
1464 $this->fk_delivery_address = $object->fk_delivery_address; // deprecated
1465 $this->contact_id = $object->contact_id;
1466 $this->ref_client = $object->ref_client;
1467
1468 if (empty($conf->global->MAIN_DISABLE_PROPAGATE_NOTES_FROM_ORIGIN)) {
1469 $this->note_private = $object->note_private;
1470 $this->note_public = $object->note_public;
1471 }
1472
1473 $this->module_source = $object->module_source;
1474 $this->pos_source = $object->pos_source;
1475
1476 $this->origin = $object->element;
1477 $this->origin_id = $object->id;
1478
1479 $this->fk_user_author = $user->id;
1480
1481 // get extrafields from original line
1482 $object->fetch_optionals();
1483 foreach ($object->array_options as $options_key => $value) {
1484 $this->array_options[$options_key] = $value;
1485 }
1486
1487 // Possibility to add external linked objects with hooks
1488 $this->linked_objects[$this->origin] = $this->origin_id;
1489 if (!empty($object->other_linked_objects) && is_array($object->other_linked_objects)) {
1490 $this->linked_objects = array_merge($this->linked_objects, $object->other_linked_objects);
1491 }
1492
1493 $ret = $this->create($user);
1494
1495 if ($ret > 0) {
1496 // Actions hooked (by external module)
1497 $hookmanager->initHooks(array('invoicedao'));
1498
1499 $parameters = array('objFrom'=>$object);
1500 $action = '';
1501 $reshook = $hookmanager->executeHooks('createFrom', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
1502 if ($reshook < 0) {
1503 $this->setErrorsFromObject($hookmanager);
1504 $error++;
1505 }
1506
1507 if (!$error) {
1508 return 1;
1509 } else {
1510 return -1;
1511 }
1512 } else {
1513 return -1;
1514 }
1515 }
1516
1525 public function createFromContract($object, User $user, $lines = array())
1526 {
1527 global $conf, $hookmanager;
1528
1529 $error = 0;
1530
1531 // Closed order
1532 $this->date = dol_now();
1533 $this->source = 0;
1534
1535 $use_all_lines = empty($lines);
1536 $num = count($object->lines);
1537 for ($i = 0; $i < $num; $i++) {
1538 if (!$use_all_lines && !in_array($object->lines[$i]->id, $lines)) {
1539 continue;
1540 }
1541
1542 $line = new FactureLigne($this->db);
1543
1544 $line->libelle = $object->lines[$i]->libelle; // deprecated
1545 $line->label = $object->lines[$i]->label;
1546 $line->desc = $object->lines[$i]->desc;
1547 $line->subprice = $object->lines[$i]->subprice;
1548 $line->total_ht = $object->lines[$i]->total_ht;
1549 $line->total_tva = $object->lines[$i]->total_tva;
1550 $line->total_localtax1 = $object->lines[$i]->total_localtax1;
1551 $line->total_localtax2 = $object->lines[$i]->total_localtax2;
1552 $line->total_ttc = $object->lines[$i]->total_ttc;
1553 $line->vat_src_code = $object->lines[$i]->vat_src_code;
1554 $line->tva_tx = $object->lines[$i]->tva_tx;
1555 $line->localtax1_tx = $object->lines[$i]->localtax1_tx;
1556 $line->localtax2_tx = $object->lines[$i]->localtax2_tx;
1557 $line->qty = $object->lines[$i]->qty;
1558 $line->fk_remise_except = $object->lines[$i]->fk_remise_except;
1559 $line->remise_percent = $object->lines[$i]->remise_percent;
1560 $line->fk_product = $object->lines[$i]->fk_product;
1561 $line->info_bits = $object->lines[$i]->info_bits;
1562 $line->product_type = $object->lines[$i]->product_type;
1563 $line->rang = $object->lines[$i]->rang;
1564 $line->special_code = $object->lines[$i]->special_code;
1565 $line->fk_parent_line = $object->lines[$i]->fk_parent_line;
1566 $line->fk_unit = $object->lines[$i]->fk_unit;
1567 $line->date_start = $object->lines[$i]->date_start;
1568 $line->date_end = $object->lines[$i]->date_end;
1569
1570 // Multicurrency
1571 $line->fk_multicurrency = $object->lines[$i]->fk_multicurrency;
1572 $line->multicurrency_code = $object->lines[$i]->multicurrency_code;
1573 $line->multicurrency_subprice = $object->lines[$i]->multicurrency_subprice;
1574 $line->multicurrency_total_ht = $object->lines[$i]->multicurrency_total_ht;
1575 $line->multicurrency_total_tva = $object->lines[$i]->multicurrency_total_tva;
1576 $line->multicurrency_total_ttc = $object->lines[$i]->multicurrency_total_ttc;
1577
1578 $line->fk_fournprice = $object->lines[$i]->fk_fournprice;
1579 $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);
1580 $line->pa_ht = $marginInfos[0];
1581
1582 // get extrafields from original line
1583 $object->lines[$i]->fetch_optionals();
1584 foreach ($object->lines[$i]->array_options as $options_key => $value) {
1585 $line->array_options[$options_key] = $value;
1586 }
1587
1588 $this->lines[$i] = $line;
1589 }
1590
1591 $this->socid = $object->socid;
1592 $this->fk_project = $object->fk_project;
1593 $this->fk_account = $object->fk_account;
1594 $this->cond_reglement_id = $object->cond_reglement_id;
1595 $this->mode_reglement_id = $object->mode_reglement_id;
1596 $this->availability_id = $object->availability_id;
1597 $this->demand_reason_id = $object->demand_reason_id;
1598 $this->delivery_date = (empty($object->delivery_date) ? $object->date_livraison : $object->delivery_date);
1599 $this->date_livraison = $object->delivery_date; // deprecated
1600 $this->fk_delivery_address = $object->fk_delivery_address; // deprecated
1601 $this->contact_id = $object->contact_id;
1602 $this->ref_client = $object->ref_client;
1603
1604 if (empty($conf->global->MAIN_DISABLE_PROPAGATE_NOTES_FROM_ORIGIN)) {
1605 $this->note_private = $object->note_private;
1606 $this->note_public = $object->note_public;
1607 }
1608
1609 $this->module_source = $object->module_source;
1610 $this->pos_source = $object->pos_source;
1611
1612 $this->origin = $object->element;
1613 $this->origin_id = $object->id;
1614
1615 $this->fk_user_author = $user->id;
1616
1617 // get extrafields from original line
1618 $object->fetch_optionals();
1619 foreach ($object->array_options as $options_key => $value) {
1620 $this->array_options[$options_key] = $value;
1621 }
1622
1623 // Possibility to add external linked objects with hooks
1624 $this->linked_objects[$this->origin] = $this->origin_id;
1625 if (!empty($object->other_linked_objects) && is_array($object->other_linked_objects)) {
1626 $this->linked_objects = array_merge($this->linked_objects, $object->other_linked_objects);
1627 }
1628
1629 $ret = $this->create($user);
1630
1631 if ($ret > 0) {
1632 // Actions hooked (by external module)
1633 $hookmanager->initHooks(array('invoicedao'));
1634
1635 $parameters = array('objFrom'=>$object);
1636 $action = '';
1637 $reshook = $hookmanager->executeHooks('createFrom', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
1638 if ($reshook < 0) {
1639 $this->setErrorsFromObject($hookmanager);
1640 $error++;
1641 }
1642
1643 if (!$error) {
1644 return 1;
1645 } else {
1646 return -1;
1647 }
1648 } else {
1649 return -1;
1650 }
1651 }
1652
1665 static public function createDepositFromOrigin(CommonObject $origin, $date, $payment_terms_id, User $user, $notrigger = 0, $autoValidateDeposit = false, $overrideFields = array())
1666 {
1667 global $conf, $langs, $hookmanager, $action;
1668
1669 if (! in_array($origin->element, array('propal', 'commande'))) {
1670 $origin->error = 'ErrorCanOnlyAutomaticallyGenerateADepositFromProposalOrOrder';
1671 return null;
1672 }
1673
1674 if (empty($date)) {
1675 $origin->error = $langs->trans('ErrorFieldRequired', $langs->transnoentities('DateInvoice'));
1676 return null;
1677 }
1678
1679 require_once DOL_DOCUMENT_ROOT . '/core/lib/date.lib.php';
1680
1681 if ($date > (dol_get_last_hour(dol_now('tzuserrel')) + (empty($conf->global->INVOICE_MAX_FUTURE_DELAY) ? 0 : $conf->global->INVOICE_MAX_FUTURE_DELAY))) {
1682 $origin->error = 'ErrorDateIsInFuture';
1683 return null;
1684 }
1685
1686 if ($payment_terms_id <= 0) {
1687 $origin->error = $langs->trans('ErrorFieldRequired', $langs->transnoentities('PaymentConditionsShort'));
1688 return null;
1689 }
1690
1691 $payment_conditions_deposit_percent = getDictionaryValue('c_payment_term', 'deposit_percent', $origin->cond_reglement_id);
1692
1693 if (empty($payment_conditions_deposit_percent)) {
1694 $origin->error = 'ErrorPaymentConditionsNotEligibleToDepositCreation';
1695 return null;
1696 }
1697
1698 if (empty($origin->deposit_percent)) {
1699 $origin->error = $langs->trans('ErrorFieldRequired', $langs->transnoentities('DepositPercent'));
1700 return null;
1701 }
1702
1703 $deposit = new self($origin->db);
1704 $deposit->socid = $origin->socid;
1705 $deposit->type = self::TYPE_DEPOSIT;
1706 $deposit->fk_project = $origin->fk_project;
1707 $deposit->ref_client = $origin->ref_client;
1708 $deposit->date = $date;
1709 $deposit->mode_reglement_id = $origin->mode_reglement_id;
1710 $deposit->cond_reglement_id = $payment_terms_id;
1711 $deposit->availability_id = $origin->availability_id;
1712 $deposit->demand_reason_id = $origin->demand_reason_id;
1713 $deposit->fk_account = $origin->fk_account;
1714 $deposit->fk_incoterms = $origin->fk_incoterms;
1715 $deposit->location_incoterms = $origin->location_incoterms;
1716 $deposit->fk_multicurrency = $origin->fk_multicurrency;
1717 $deposit->multicurrency_code = $origin->multicurrency_code;
1718 $deposit->multicurrency_tx = $origin->multicurrency_tx;
1719 $deposit->module_source = $origin->module_source;
1720 $deposit->pos_source = $origin->pos_source;
1721 $deposit->model_pdf = 'crabe';
1722
1723 $modelByTypeConfName = 'FACTURE_ADDON_PDF_' . $deposit->type;
1724
1725 if (!empty($conf->global->$modelByTypeConfName)) {
1726 $deposit->model_pdf = $conf->global->$modelByTypeConfName;
1727 } elseif (!empty($conf->global->FACTURE_ADDON_PDF)) {
1728 $deposit->model_pdf = $conf->global->FACTURE_ADDON_PDF;
1729 }
1730
1731 if (empty($conf->global->MAIN_DISABLE_PROPAGATE_NOTES_FROM_ORIGIN)) {
1732 $deposit->note_private = $origin->note_private;
1733 $deposit->note_public = $origin->note_public;
1734 }
1735
1736 $deposit->origin = $origin->element;
1737 $deposit->origin_id = $origin->id;
1738
1739 $origin->fetch_optionals();
1740
1741 foreach ($origin->array_options as $extrakey => $value) {
1742 $deposit->array_options[$extrakey] = $value;
1743 }
1744
1745 $deposit->linked_objects[$deposit->origin] = $deposit->origin_id;
1746
1747 foreach ($overrideFields as $key => $value) {
1748 $deposit->$key = $value;
1749 }
1750
1751 $deposit->context['createdepositfromorigin'] = 'createdepositfromorigin';
1752
1753 $origin->db->begin();
1754
1755 // Facture::create() also imports contact from origin
1756 $createReturn = $deposit->create($user, $notrigger);
1757
1758 if ($createReturn <= 0) {
1759 $origin->db->rollback();
1760 $origin->error = $deposit->error;
1761 $origin->errors = $deposit->errors;
1762 return null;
1763 }
1764
1765 $amount_ttc_diff = 0;
1766 $amountdeposit = array();
1767 $descriptions = array();
1768
1769 if (!empty($conf->global->MAIN_DEPOSIT_MULTI_TVA)) {
1770 $amount = $origin->total_ttc * ($origin->deposit_percent / 100);
1771
1772 $TTotalByTva = array();
1773 foreach ($origin->lines as &$line) {
1774 if (!empty($line->special_code)) {
1775 continue;
1776 }
1777 $TTotalByTva[$line->tva_tx] += $line->total_ttc;
1778 $descriptions[$line->tva_tx] .= '<li>' . (!empty($line->product_ref) ? $line->product_ref . ' - ' : '');
1779 $descriptions[$line->tva_tx] .= (!empty($line->product_label) ? $line->product_label . ' - ' : '');
1780 $descriptions[$line->tva_tx] .= $langs->trans('Qty') . ' : ' . $line->qty;
1781 $descriptions[$line->tva_tx] .= ' - ' . $langs->trans('TotalHT') . ' : ' . price($line->total_ht) . '</li>';
1782 }
1783
1784 foreach ($TTotalByTva as $tva => &$total) {
1785 $coef = $total / $origin->total_ttc; // Calc coef
1786 $am = $amount * $coef;
1787 $amount_ttc_diff += $am;
1788 $amountdeposit[$tva] += $am / (1 + $tva / 100); // Convert into HT for the addline
1789 }
1790 } else {
1791 $totalamount = 0;
1792 $lines = $origin->lines;
1793 $numlines = count($lines);
1794 for ($i = 0; $i < $numlines; $i++) {
1795 if (empty($lines[$i]->qty)) {
1796 continue; // We discard qty=0, it is an option
1797 }
1798 if (!empty($lines[$i]->special_code)) {
1799 continue; // We discard special_code (frais port, ecotaxe, option, ...)
1800 }
1801
1802 $totalamount += $lines[$i]->total_ht; // Fixme : is it not for the customer ? Shouldn't we take total_ttc ?
1803 $tva_tx = $lines[$i]->tva_tx;
1804 $amountdeposit[$tva_tx] += ($lines[$i]->total_ht * $origin->deposit_percent) / 100;
1805 $descriptions[$tva_tx] .= '<li>' . (!empty($lines[$i]->product_ref) ? $lines[$i]->product_ref . ' - ' : '');
1806 $descriptions[$tva_tx] .= (!empty($lines[$i]->product_label) ? $lines[$i]->product_label . ' - ' : '');
1807 $descriptions[$tva_tx] .= $langs->trans('Qty') . ' : ' . $lines[$i]->qty;
1808 $descriptions[$tva_tx] .= ' - ' . $langs->trans('TotalHT') . ' : ' . price($lines[$i]->total_ht) . '</li>';
1809 }
1810
1811 if ($totalamount == 0) {
1812 $amountdeposit[0] = 0;
1813 }
1814
1815 $amount_ttc_diff = $amountdeposit[0];
1816 }
1817
1818 foreach ($amountdeposit as $tva => $amount) {
1819 if (empty($amount)) {
1820 continue;
1821 }
1822
1823 $descline = '(DEPOSIT) ('. $origin->deposit_percent .'%) - '.$origin->ref;
1824
1825 // Hidden conf
1826 if (!empty($conf->global->INVOICE_DEPOSIT_VARIABLE_MODE_DETAIL_LINES_IN_DESCRIPTION) && !empty($descriptions[$tva])) {
1827 $descline .= '<ul>' . $descriptions[$tva] . '</ul>';
1828 }
1829
1830 $addlineResult = $deposit->addline(
1831 $descline,
1832 $amount, // subprice
1833 1, // quantity
1834 $tva, // vat rate
1835 0, // localtax1_tx
1836 0, // localtax2_tx
1837 (empty($conf->global->INVOICE_PRODUCTID_DEPOSIT) ? 0 : $conf->global->INVOICE_PRODUCTID_DEPOSIT), // fk_product
1838 0, // remise_percent
1839 0, // date_start
1840 0, // date_end
1841 0,
1842 0, // info_bits
1843 0,
1844 'HT',
1845 0,
1846 0, // product_type
1847 1,
1848 0, // special_code
1849 $deposit->origin,
1850 0,
1851 0,
1852 0,
1853 0
1854 //,$langs->trans('Deposit') //Deprecated
1855 );
1856
1857 if ($addlineResult < 0) {
1858 $origin->db->rollback();
1859 $origin->error = $deposit->error;
1860 $origin->errors = $deposit->errors;
1861 return null;
1862 }
1863 }
1864
1865 $diff = $deposit->total_ttc - $amount_ttc_diff;
1866
1867 if (!empty($conf->global->MAIN_DEPOSIT_MULTI_TVA) && $diff != 0) {
1868 $deposit->fetch_lines();
1869 $subprice_diff = $deposit->lines[0]->subprice - $diff / (1 + $deposit->lines[0]->tva_tx / 100);
1870
1871 $updatelineResult = $deposit->updateline(
1872 $deposit->lines[0]->id,
1873 $deposit->lines[0]->desc,
1874 $subprice_diff,
1875 $deposit->lines[0]->qty,
1876 $deposit->lines[0]->remise_percent,
1877 $deposit->lines[0]->date_start,
1878 $deposit->lines[0]->date_end,
1879 $deposit->lines[0]->tva_tx,
1880 0,
1881 0,
1882 'HT',
1883 $deposit->lines[0]->info_bits,
1884 $deposit->lines[0]->product_type,
1885 0,
1886 0,
1887 0,
1888 $deposit->lines[0]->pa_ht,
1889 $deposit->lines[0]->label,
1890 0,
1891 array(),
1892 100
1893 );
1894
1895 if ($updatelineResult < 0) {
1896 $origin->db->rollback();
1897 $origin->error = $deposit->error;
1898 $origin->errors = $deposit->errors;
1899 return null;
1900 }
1901 }
1902
1903 $hookmanager->initHooks(array('invoicedao'));
1904
1905 $parameters = array('objFrom' => $origin);
1906 $reshook = $hookmanager->executeHooks('createFrom', $parameters, $deposit, $action); // Note that $action and $object may have been
1907 // modified by hook
1908 if ($reshook < 0) {
1909 $origin->db->rollback();
1910 $origin->error = $hookmanager->error;
1911 $origin->errors = $hookmanager->errors;
1912 return null;
1913 }
1914
1915 if (!empty($autoValidateDeposit)) {
1916 $validateReturn = $deposit->validate($user, '', 0, $notrigger);
1917
1918 if ($validateReturn < 0) {
1919 $origin->db->rollback();
1920 $origin->error = $deposit->error;
1921 $origin->errors = $deposit->errors;
1922 return null;
1923 }
1924 }
1925
1926 unset($deposit->context['createdepositfromorigin']);
1927
1928 $origin->db->commit();
1929
1930 return $deposit;
1931 }
1932
1940 public function getTooltipContentArray($params)
1941 {
1942 global $conf, $langs, $mysoc, $user;
1943
1944 $langs->load('bills');
1945
1946 $datas = [];
1947 $moretitle = $params['moretitle'] ?? '';
1948 $picto = $this->picto;
1949 if ($this->type == self::TYPE_REPLACEMENT) {
1950 $picto .= 'r'; // Replacement invoice
1951 }
1952 if ($this->type == self::TYPE_CREDIT_NOTE) {
1953 $picto .= 'a'; // Credit note
1954 }
1955 if ($this->type == self::TYPE_DEPOSIT) {
1956 $picto .= 'd'; // Deposit invoice
1957 }
1958
1959 if ($user->hasRight("facture", "read")) {
1960 $datas['picto'] = img_picto('', $picto).' <u class="paddingrightonly">'.$langs->trans("Invoice").'</u>';
1961
1962 $datas['picto'] .= '&nbsp;'.$this->getLibType(1);
1963
1964 // Complete datas
1965 if (!empty($params['fromajaxtooltip']) && !isset($this->alreadypaid)) {
1966 // Load the alreadypaid field
1967 $this->alreadypaid = $this->getSommePaiement(0);
1968 }
1969 if (isset($this->statut) && isset($this->alreadypaid)) {
1970 $datas['picto'] .= ' '.$this->getLibStatut(5, $this->alreadypaid);
1971 }
1972 if ($moretitle) {
1973 $datas['picto'] .= ' - '.$moretitle;
1974 }
1975 if (!empty($this->ref)) {
1976 $datas['ref'] = '<br><b>'.$langs->trans('Ref').':</b> '.$this->ref;
1977 }
1978 if (!empty($this->ref_customer)) {
1979 $datas['refcustomer'] = '<br><b>'.$langs->trans('RefCustomer').':</b> '.$this->ref_customer;
1980 }
1981 if (!empty($this->date)) {
1982 $datas['date'] = '<br><b>'.$langs->trans('Date').':</b> '.dol_print_date($this->date, 'day');
1983 }
1984 if (!empty($this->total_ht)) {
1985 $datas['amountht'] = '<br><b>'.$langs->trans('AmountHT').':</b> '.price($this->total_ht, 0, $langs, 0, -1, -1, $conf->currency);
1986 }
1987 if (!empty($this->total_tva)) {
1988 $datas['amountvat'] = '<br><b>'.$langs->trans('AmountVAT').':</b> '.price($this->total_tva, 0, $langs, 0, -1, -1, $conf->currency);
1989 }
1990 if (!empty($this->revenuestamp) && $this->revenuestamp != 0) {
1991 $datas['amountrevenustamp'] = '<br><b>'.$langs->trans('RevenueStamp').':</b> '.price($this->revenuestamp, 0, $langs, 0, -1, -1, $conf->currency);
1992 }
1993 if (!empty($this->total_localtax1) && $this->total_localtax1 != 0) {
1994 // We keep test != 0 because $this->total_localtax1 can be '0.00000000'
1995 $datas['amountlt1'] = '<br><b>'.$langs->transcountry('AmountLT1', $mysoc->country_code).':</b> '.price($this->total_localtax1, 0, $langs, 0, -1, -1, $conf->currency);
1996 }
1997 if (!empty($this->total_localtax2) && $this->total_localtax2 != 0) {
1998 $datas['amountlt2'] = '<br><b>'.$langs->transcountry('AmountLT2', $mysoc->country_code).':</b> '.price($this->total_localtax2, 0, $langs, 0, -1, -1, $conf->currency);
1999 }
2000 if (!empty($this->total_ttc)) {
2001 $datas['amountttc'] = '<br><b>'.$langs->trans('AmountTTC').':</b> '.price($this->total_ttc, 0, $langs, 0, -1, -1, $conf->currency);
2002 }
2003 }
2004
2005 return $datas;
2006 }
2007
2022 public function getNomUrl($withpicto = 0, $option = '', $max = 0, $short = 0, $moretitle = '', $notooltip = 0, $addlinktonotes = 0, $save_lastsearch_value = -1, $target = '')
2023 {
2024 global $langs, $conf, $user, $mysoc;
2025
2026 if (!empty($conf->dol_no_mouse_hover)) {
2027 $notooltip = 1; // Force disable tooltips
2028 }
2029
2030 $result = '';
2031
2032 if ($option == 'withdraw') {
2033 $url = DOL_URL_ROOT.'/compta/facture/prelevement.php?facid='.$this->id;
2034 } else {
2035 $url = DOL_URL_ROOT.'/compta/facture/card.php?facid='.$this->id;
2036 }
2037
2038 if (!$user->hasRight("facture", "read")) {
2039 $option = 'nolink';
2040 }
2041
2042 if ($option !== 'nolink') {
2043 // Add param to save lastsearch_values or not
2044 $add_save_lastsearch_values = ($save_lastsearch_value == 1 ? 1 : 0);
2045 if ($save_lastsearch_value == -1 && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) {
2046 $add_save_lastsearch_values = 1;
2047 }
2048 if ($add_save_lastsearch_values) {
2049 $url .= '&save_lastsearch_values=1';
2050 }
2051 }
2052
2053 if ($short) {
2054 return $url;
2055 }
2056
2057 $picto = $this->picto;
2058 if ($this->type == self::TYPE_REPLACEMENT) {
2059 $picto .= 'r'; // Replacement invoice
2060 }
2061 if ($this->type == self::TYPE_CREDIT_NOTE) {
2062 $picto .= 'a'; // Credit note
2063 }
2064 if ($this->type == self::TYPE_DEPOSIT) {
2065 $picto .= 'd'; // Deposit invoice
2066 }
2067 $params = [
2068 'id' => $this->id,
2069 'objecttype' => $this->element,
2070 'moretitle' => $moretitle,
2071 'option' => $option,
2072 ];
2073 $classfortooltip = 'classfortooltip';
2074 $dataparams = '';
2075 if (getDolGlobalInt('MAIN_ENABLE_AJAX_TOOLTIP')) {
2076 $classfortooltip = 'classforajaxtooltip';
2077 $dataparams = ' data-params="'.dol_escape_htmltag(json_encode($params)).'"';
2078 $label = '';
2079 } else {
2080 $label = implode($this->getTooltipContentArray($params));
2081 }
2082
2083 $linkclose = ($target ? ' target="'.$target.'"' : '');
2084 if (empty($notooltip) && $user->hasRight("facture", "read")) {
2085 if (!empty($conf->global->MAIN_OPTIMIZEFORTEXTBROWSER)) {
2086 $label = $langs->trans("Invoice");
2087 $linkclose .= ' alt="'.dol_escape_htmltag($label, 1).'"';
2088 }
2089 $linkclose .= ($label ? ' title="'.dol_escape_htmltag($label, 1).'"' : ' title="tocomplete"');
2090 $linkclose .= $dataparams.' class="'.$classfortooltip.'"';
2091 }
2092
2093 $linkstart = '<a href="'.$url.'"';
2094 $linkstart .= $linkclose.'>';
2095 $linkend = '</a>';
2096
2097 if ($option == 'nolink') {
2098 $linkstart = '';
2099 $linkend = '';
2100 }
2101
2102 $result .= $linkstart;
2103 if ($withpicto) {
2104 $result .= img_object(($notooltip ? '' : $label), ($this->picto ? $this->picto : 'generic'), ($notooltip ? (($withpicto != 2) ? 'class="paddingright"' : '') : 'class="'.(($withpicto != 2) ? 'paddingright ' : '').'"'), 0, 0, $notooltip ? 0 : 1);
2105 }
2106 if ($withpicto != 2) {
2107 $result .= ($max ?dol_trunc($this->ref, $max) : $this->ref);
2108 }
2109 $result .= $linkend;
2110
2111 if ($addlinktonotes) {
2112 $txttoshow = ($user->socid > 0 ? $this->note_public : $this->note_private);
2113 if ($txttoshow) {
2114 //$notetoshow = $langs->trans("ViewPrivateNote").':<br>'.dol_string_nohtmltag($txttoshow, 1);
2115 $notetoshow = $langs->trans("ViewPrivateNote").':<br>'.$txttoshow;
2116 $result .= ' <span class="note inline-block">';
2117 $result .= '<a href="'.DOL_URL_ROOT.'/compta/facture/note.php?id='.$this->id.'" class="classfortooltip" title="'.dol_escape_htmltag($notetoshow, 1, 1).'">';
2118 $result .= img_picto('', 'note');
2119 $result .= '</a>';
2120 //$result.=img_picto($langs->trans("ViewNote"),'object_generic');
2121 //$result.='</a>';
2122 $result .= '</span>';
2123 }
2124 }
2125
2126 global $action, $hookmanager;
2127 $hookmanager->initHooks(array('invoicedao'));
2128 $parameters = array('id'=>$this->id, 'getnomurl' => &$result, 'notooltip' => $notooltip, 'addlinktonotes' => $addlinktonotes, 'save_lastsearch_value'=> $save_lastsearch_value, 'target' => $target);
2129 $reshook = $hookmanager->executeHooks('getNomUrl', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
2130 if ($reshook > 0) {
2131 $result = $hookmanager->resPrint;
2132 } else {
2133 $result .= $hookmanager->resPrint;
2134 }
2135
2136 return $result;
2137 }
2138
2149 public function fetch($rowid, $ref = '', $ref_ext = '', $notused = '', $fetch_situation = false)
2150 {
2151 if (empty($rowid) && empty($ref) && empty($ref_ext)) {
2152 return -1;
2153 }
2154
2155 $sql = 'SELECT f.rowid, f.entity, f.ref, f.ref_client, f.ref_ext, f.type, f.fk_soc';
2156 $sql .= ', f.total_tva, f.localtax1, f.localtax2, f.total_ht, f.total_ttc, f.revenuestamp';
2157 $sql .= ', f.remise_percent, f.remise_absolue, f.remise';
2158 $sql .= ', f.datef as df, f.date_pointoftax';
2159 $sql .= ', f.date_lim_reglement as dlr';
2160 $sql .= ', f.datec as datec';
2161 $sql .= ', f.date_valid as datev';
2162 $sql .= ', f.tms as datem';
2163 $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';
2164 $sql .= ', f.fk_facture_source, f.fk_fac_rec_source';
2165 $sql .= ', f.fk_mode_reglement, f.fk_cond_reglement, f.fk_projet as fk_project, f.extraparams';
2166 $sql .= ', f.situation_cycle_ref, f.situation_counter, f.situation_final';
2167 $sql .= ', f.fk_account';
2168 $sql .= ", f.fk_multicurrency, f.multicurrency_code, f.multicurrency_tx, f.multicurrency_total_ht, f.multicurrency_total_tva, f.multicurrency_total_ttc";
2169 $sql .= ', p.code as mode_reglement_code, p.libelle as mode_reglement_libelle';
2170 $sql .= ', c.code as cond_reglement_code, c.libelle as cond_reglement_libelle, c.libelle_facture as cond_reglement_libelle_doc';
2171 $sql .= ', f.fk_incoterms, f.location_incoterms';
2172 $sql .= ', f.module_source, f.pos_source';
2173 $sql .= ", i.libelle as label_incoterms";
2174 $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";
2175 $sql .= ' FROM '.MAIN_DB_PREFIX.'facture as f';
2176 $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_payment_term as c ON f.fk_cond_reglement = c.rowid';
2177 $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_paiement as p ON f.fk_mode_reglement = p.id';
2178 $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_incoterms as i ON f.fk_incoterms = i.rowid';
2179
2180 if ($rowid) {
2181 $sql .= " WHERE f.rowid = ".((int) $rowid);
2182 } else {
2183 $sql .= ' WHERE f.entity IN ('.getEntity('invoice').')'; // Don't use entity if you use rowid
2184 if ($ref) {
2185 $sql .= " AND f.ref = '".$this->db->escape($ref)."'";
2186 }
2187 if ($ref_ext) {
2188 $sql .= " AND f.ref_ext = '".$this->db->escape($ref_ext)."'";
2189 }
2190 }
2191
2192 dol_syslog(get_class($this)."::fetch", LOG_DEBUG);
2193 $resql = $this->db->query($sql);
2194 if ($resql) {
2195 if ($this->db->num_rows($resql)) {
2196 $obj = $this->db->fetch_object($resql);
2197
2198 $this->id = $obj->rowid;
2199 $this->entity = $obj->entity;
2200
2201 $this->ref = $obj->ref;
2202 $this->ref_client = $obj->ref_client;
2203 $this->ref_customer = $obj->ref_client;
2204 $this->ref_ext = $obj->ref_ext;
2205 $this->type = $obj->type;
2206 $this->date = $this->db->jdate($obj->df);
2207 $this->date_pointoftax = $this->db->jdate($obj->date_pointoftax);
2208 $this->date_creation = $this->db->jdate($obj->datec);
2209 $this->date_validation = $this->db->jdate($obj->datev);
2210 $this->date_modification = $this->db->jdate($obj->datem);
2211 $this->datem = $this->db->jdate($obj->datem);
2212 $this->remise_percent = $obj->remise_percent; // TODO deprecated
2213 $this->remise_absolue = $obj->remise_absolue;
2214 $this->total_ht = $obj->total_ht;
2215 $this->total_tva = $obj->total_tva;
2216 $this->total_localtax1 = $obj->localtax1;
2217 $this->total_localtax2 = $obj->localtax2;
2218 $this->total_ttc = $obj->total_ttc;
2219 $this->revenuestamp = $obj->revenuestamp;
2220 $this->paye = $obj->paye;
2221 $this->close_code = $obj->close_code;
2222 $this->close_note = $obj->close_note;
2223
2224 $this->socid = $obj->fk_soc;
2225 $this->thirdparty = null; // Clear if another value was already set by fetch_thirdparty
2226
2227 $this->fk_project = $obj->fk_project;
2228 $this->project = null; // Clear if another value was already set by fetch_projet
2229
2230 $this->statut = $obj->fk_statut;
2231 $this->status = $obj->fk_statut;
2232
2233 $this->date_lim_reglement = $this->db->jdate($obj->dlr);
2234 $this->mode_reglement_id = $obj->fk_mode_reglement;
2235 $this->mode_reglement_code = $obj->mode_reglement_code;
2236 $this->mode_reglement = $obj->mode_reglement_libelle;
2237 $this->cond_reglement_id = $obj->fk_cond_reglement;
2238 $this->cond_reglement_code = $obj->cond_reglement_code;
2239 $this->cond_reglement = $obj->cond_reglement_libelle;
2240 $this->cond_reglement_doc = $obj->cond_reglement_libelle_doc;
2241 $this->fk_account = ($obj->fk_account > 0) ? $obj->fk_account : null;
2242 $this->fk_facture_source = $obj->fk_facture_source;
2243 $this->fk_fac_rec_source = $obj->fk_fac_rec_source;
2244 $this->note = $obj->note_private; // deprecated
2245 $this->note_private = $obj->note_private;
2246 $this->note_public = $obj->note_public;
2247 $this->user_author = $obj->fk_user_author; // deprecated
2248 $this->user_valid = $obj->fk_user_valid; // deprecated
2249 $this->user_modification = $obj->fk_user_modif; // deprecated
2250 $this->fk_user_author = $obj->fk_user_author;
2251 $this->fk_user_valid = $obj->fk_user_valid;
2252 $this->fk_user_modif = $obj->fk_user_modif;
2253 $this->model_pdf = $obj->model_pdf;
2254 $this->modelpdf = $obj->model_pdf; // deprecated
2255 $this->last_main_doc = $obj->last_main_doc;
2256 $this->situation_cycle_ref = $obj->situation_cycle_ref;
2257 $this->situation_counter = $obj->situation_counter;
2258 $this->situation_final = $obj->situation_final;
2259 $this->retained_warranty = $obj->retained_warranty;
2260 $this->retained_warranty_date_limit = $this->db->jdate($obj->retained_warranty_date_limit);
2261 $this->retained_warranty_fk_cond_reglement = $obj->retained_warranty_fk_cond_reglement;
2262
2263 $this->extraparams = !empty($obj->extraparams) ? (array) json_decode($obj->extraparams, true) : array();
2264
2265 //Incoterms
2266 $this->fk_incoterms = $obj->fk_incoterms;
2267 $this->location_incoterms = $obj->location_incoterms;
2268 $this->label_incoterms = $obj->label_incoterms;
2269
2270 $this->module_source = $obj->module_source;
2271 $this->pos_source = $obj->pos_source;
2272
2273 // Multicurrency
2274 $this->fk_multicurrency = $obj->fk_multicurrency;
2275 $this->multicurrency_code = $obj->multicurrency_code;
2276 $this->multicurrency_tx = $obj->multicurrency_tx;
2277 $this->multicurrency_total_ht = $obj->multicurrency_total_ht;
2278 $this->multicurrency_total_tva = $obj->multicurrency_total_tva;
2279 $this->multicurrency_total_ttc = $obj->multicurrency_total_ttc;
2280
2281 if (($this->type == self::TYPE_SITUATION || ($this->type == self::TYPE_CREDIT_NOTE && $this->situation_cycle_ref > 0)) && $fetch_situation) {
2283 }
2284
2285 if ($this->status == self::STATUS_DRAFT) {
2286 $this->brouillon = 1;
2287 }
2288
2289 // Retrieve all extrafield
2290 // fetch optionals attributes and labels
2291 $this->fetch_optionals();
2292
2293 // Lines
2294 $this->lines = array();
2295
2296 $result = $this->fetch_lines();
2297 if ($result < 0) {
2298 $this->error = $this->db->error();
2299 return -3;
2300 }
2301
2302 $this->db->free($resql);
2303
2304 return 1;
2305 } else {
2306 $this->error = 'Invoice with id='.$rowid.' or ref='.$ref.' or ref_ext='.$ref_ext.' not found';
2307
2308 dol_syslog(__METHOD__.$this->error, LOG_WARNING);
2309 return 0;
2310 }
2311 } else {
2312 $this->error = $this->db->lasterror();
2313 return -1;
2314 }
2315 }
2316
2317
2318 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2327 public function fetch_lines($only_product = 0, $loadalsotranslation = 0)
2328 {
2329 // phpcs:enable
2330 global $langs, $conf;
2331
2332 $this->lines = array();
2333
2334 $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,';
2335 $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,';
2336 $sql .= ' l.situation_percent, l.fk_prev_id,';
2337 $sql .= ' l.rang, l.special_code,';
2338 $sql .= ' l.date_start as date_start, l.date_end as date_end,';
2339 $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,';
2340 $sql .= ' l.fk_unit,';
2341 $sql .= ' l.fk_multicurrency, l.multicurrency_code, l.multicurrency_subprice, l.multicurrency_total_ht, l.multicurrency_total_tva, l.multicurrency_total_ttc,';
2342 $sql .= ' p.ref as product_ref, p.fk_product_type as fk_product_type, p.label as product_label, p.description as product_desc';
2343 $sql .= ' FROM '.MAIN_DB_PREFIX.'facturedet as l';
2344 $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'product as p ON l.fk_product = p.rowid';
2345 $sql .= ' WHERE l.fk_facture = '.((int) $this->id);
2346 $sql .= ' ORDER BY l.rang, l.rowid';
2347
2348 dol_syslog(get_class($this).'::fetch_lines', LOG_DEBUG);
2349 $result = $this->db->query($sql);
2350 if ($result) {
2351 $num = $this->db->num_rows($result);
2352 $i = 0;
2353 while ($i < $num) {
2354 $objp = $this->db->fetch_object($result);
2355 $line = new FactureLigne($this->db);
2356
2357 $line->id = $objp->rowid;
2358 $line->rowid = $objp->rowid; // deprecated
2359 $line->fk_facture = $objp->fk_facture;
2360 $line->label = $objp->custom_label; // deprecated
2361 $line->desc = $objp->description; // Description line
2362 $line->description = $objp->description; // Description line
2363 $line->product_type = $objp->product_type; // Type of line
2364 $line->ref = $objp->product_ref; // Ref product
2365 $line->product_ref = $objp->product_ref; // Ref product
2366 $line->libelle = $objp->product_label; // deprecated
2367 $line->product_label = $objp->product_label; // Label product
2368 $line->product_desc = $objp->product_desc; // Description product
2369 $line->fk_product_type = $objp->fk_product_type; // Type of product
2370 $line->qty = $objp->qty;
2371 $line->subprice = $objp->subprice;
2372 $line->ref_ext = $objp->ref_ext; // line external ref
2373
2374 $line->vat_src_code = $objp->vat_src_code;
2375 $line->tva_tx = $objp->tva_tx;
2376 $line->localtax1_tx = $objp->localtax1_tx;
2377 $line->localtax2_tx = $objp->localtax2_tx;
2378 $line->localtax1_type = $objp->localtax1_type;
2379 $line->localtax2_type = $objp->localtax2_type;
2380 $line->remise_percent = $objp->remise_percent;
2381 $line->fk_remise_except = $objp->fk_remise_except;
2382 $line->fk_product = $objp->fk_product;
2383 $line->date_start = $this->db->jdate($objp->date_start);
2384 $line->date_end = $this->db->jdate($objp->date_end);
2385 $line->info_bits = $objp->info_bits;
2386 $line->total_ht = $objp->total_ht;
2387 $line->total_tva = $objp->total_tva;
2388 $line->total_localtax1 = $objp->total_localtax1;
2389 $line->total_localtax2 = $objp->total_localtax2;
2390 $line->total_ttc = $objp->total_ttc;
2391 $line->code_ventilation = $objp->fk_code_ventilation;
2392 $line->fk_fournprice = $objp->fk_fournprice;
2393 $marginInfos = getMarginInfos($objp->subprice, $objp->remise_percent, $objp->tva_tx, $objp->localtax1_tx, $objp->localtax2_tx, $line->fk_fournprice, $objp->pa_ht);
2394 $line->pa_ht = $marginInfos[0];
2395 $line->marge_tx = $marginInfos[1];
2396 $line->marque_tx = $marginInfos[2];
2397 $line->rang = $objp->rang;
2398 $line->special_code = $objp->special_code;
2399 $line->fk_parent_line = $objp->fk_parent_line;
2400 $line->situation_percent = $objp->situation_percent;
2401 $line->fk_prev_id = $objp->fk_prev_id;
2402 $line->fk_unit = $objp->fk_unit;
2403
2404 // Accountancy
2405 $line->fk_accounting_account = $objp->fk_code_ventilation;
2406
2407 // Multicurrency
2408 $line->fk_multicurrency = $objp->fk_multicurrency;
2409 $line->multicurrency_code = $objp->multicurrency_code;
2410 $line->multicurrency_subprice = $objp->multicurrency_subprice;
2411 $line->multicurrency_total_ht = $objp->multicurrency_total_ht;
2412 $line->multicurrency_total_tva = $objp->multicurrency_total_tva;
2413 $line->multicurrency_total_ttc = $objp->multicurrency_total_ttc;
2414
2415 $line->fetch_optionals();
2416
2417 // multilangs
2418 if (getDolGlobalInt('MAIN_MULTILANGS') && !empty($objp->fk_product) && !empty($loadalsotranslation)) {
2419 $tmpproduct = new Product($this->db);
2420 $tmpproduct->fetch($objp->fk_product);
2421 $tmpproduct->getMultiLangs();
2422
2423 $line->multilangs = $tmpproduct->multilangs;
2424 }
2425
2426 $this->lines[$i] = $line;
2427
2428 $i++;
2429 }
2430 $this->db->free($result);
2431 return 1;
2432 } else {
2433 $this->error = $this->db->error();
2434 return -3;
2435 }
2436 }
2437
2445 {
2446 global $conf;
2447
2448 $this->tab_previous_situation_invoice = array();
2449 $this->tab_next_situation_invoice = array();
2450
2451 $sql = 'SELECT rowid, type, situation_cycle_ref, situation_counter FROM '.MAIN_DB_PREFIX.'facture';
2452 $sql .= " WHERE rowid <> ".((int) $this->id);
2453 $sql .= ' AND entity = '.((int) $this->entity);
2454 $sql .= ' AND situation_cycle_ref = '.(int) $this->situation_cycle_ref;
2455 $sql .= ' ORDER BY situation_counter ASC';
2456
2457 dol_syslog(get_class($this).'::fetchPreviousNextSituationInvoice ', LOG_DEBUG);
2458 $result = $this->db->query($sql);
2459 if ($result && $this->db->num_rows($result) > 0) {
2460 while ($objp = $this->db->fetch_object($result)) {
2461 $invoice = new Facture($this->db);
2462 if ($invoice->fetch($objp->rowid) > 0) {
2463 if ($objp->situation_counter < $this->situation_counter
2464 || ($objp->situation_counter == $this->situation_counter && $objp->rowid < $this->id) // This case appear when there are credit notes
2465 ) {
2466 $this->tab_previous_situation_invoice[] = $invoice;
2467 } else {
2468 $this->tab_next_situation_invoice[] = $invoice;
2469 }
2470 }
2471 }
2472 }
2473 }
2474
2482 public function update(User $user, $notrigger = 0)
2483 {
2484 $error = 0;
2485
2486 // Clean parameters
2487 if (empty($this->type)) {
2488 $this->type = self::TYPE_STANDARD;
2489 }
2490 if (isset($this->ref)) {
2491 $this->ref = trim($this->ref);
2492 }
2493 if (isset($this->ref_ext)) {
2494 $this->ref_ext = trim($this->ref_ext);
2495 }
2496 if (isset($this->ref_client)) {
2497 $this->ref_client = trim($this->ref_client);
2498 }
2499 if (isset($this->increment)) {
2500 $this->increment = trim($this->increment);
2501 }
2502 if (isset($this->close_code)) {
2503 $this->close_code = trim($this->close_code);
2504 }
2505 if (isset($this->close_note)) {
2506 $this->close_note = trim($this->close_note);
2507 }
2508 if (isset($this->note) || isset($this->note_private)) {
2509 $this->note = (isset($this->note) ? trim($this->note) : trim($this->note_private)); // deprecated
2510 }
2511 if (isset($this->note) || isset($this->note_private)) {
2512 $this->note_private = (isset($this->note_private) ? trim($this->note_private) : trim($this->note));
2513 }
2514 if (isset($this->note_public)) {
2515 $this->note_public = trim($this->note_public);
2516 }
2517 if (isset($this->model_pdf)) {
2518 $this->model_pdf = trim($this->model_pdf);
2519 }
2520 if (isset($this->import_key)) {
2521 $this->import_key = trim($this->import_key);
2522 }
2523 if (isset($this->retained_warranty)) {
2524 $this->retained_warranty = floatval($this->retained_warranty);
2525 }
2526 if (!isset($this->fk_user_author) && isset($this->user_author) ) {
2527 $this->fk_user_author = $this->user_author;
2528 }
2529
2530
2531 // Check parameters
2532 // Put here code to add control on parameters values
2533
2534 // Update request
2535 $sql = "UPDATE ".MAIN_DB_PREFIX."facture SET";
2536 $sql .= " ref=".(isset($this->ref) ? "'".$this->db->escape($this->ref)."'" : "null").",";
2537 $sql .= " ref_ext=".(isset($this->ref_ext) ? "'".$this->db->escape($this->ref_ext)."'" : "null").",";
2538 $sql .= " type=".(isset($this->type) ? $this->db->escape($this->type) : "null").",";
2539 $sql .= " ref_client=".(isset($this->ref_client) ? "'".$this->db->escape($this->ref_client)."'" : "null").",";
2540 $sql .= " increment=".(isset($this->increment) ? "'".$this->db->escape($this->increment)."'" : "null").",";
2541 $sql .= " fk_soc=".(isset($this->socid) ? $this->db->escape($this->socid) : "null").",";
2542 $sql .= " datec=".(strval($this->date_creation) != '' ? "'".$this->db->idate($this->date_creation)."'" : 'null').",";
2543 $sql .= " datef=".(strval($this->date) != '' ? "'".$this->db->idate($this->date)."'" : 'null').",";
2544 $sql .= " date_pointoftax=".(strval($this->date_pointoftax) != '' ? "'".$this->db->idate($this->date_pointoftax)."'" : 'null').",";
2545 $sql .= " date_valid=".(strval($this->date_validation) != '' ? "'".$this->db->idate($this->date_validation)."'" : 'null').",";
2546 $sql .= " paye=".(isset($this->paye) ? $this->db->escape($this->paye) : 0).",";
2547 $sql .= " remise_percent=".(isset($this->remise_percent) ? $this->db->escape($this->remise_percent) : "null").","; // TODO deprecated
2548 $sql .= " remise_absolue=".(isset($this->remise_absolue) ? $this->db->escape($this->remise_absolue) : "null").",";
2549 $sql .= " close_code=".(isset($this->close_code) ? "'".$this->db->escape($this->close_code)."'" : "null").",";
2550 $sql .= " close_note=".(isset($this->close_note) ? "'".$this->db->escape($this->close_note)."'" : "null").",";
2551 $sql .= " total_tva=".(isset($this->total_tva) ? $this->total_tva : "null").",";
2552 $sql .= " localtax1=".(isset($this->total_localtax1) ? $this->total_localtax1 : "null").",";
2553 $sql .= " localtax2=".(isset($this->total_localtax2) ? $this->total_localtax2 : "null").",";
2554 $sql .= " total_ht=".(isset($this->total_ht) ? $this->total_ht : "null").",";
2555 $sql .= " total_ttc=".(isset($this->total_ttc) ? $this->total_ttc : "null").",";
2556 $sql .= " revenuestamp=".((isset($this->revenuestamp) && $this->revenuestamp != '') ? $this->db->escape($this->revenuestamp) : "null").",";
2557 $sql .= " fk_statut=".(isset($this->statut) ? $this->db->escape($this->statut) : "null").",";
2558 $sql .= " fk_user_author=".(isset($this->fk_user_author) ? $this->db->escape($this->fk_user_author) : "null").",";
2559 $sql .= " fk_user_valid=".(isset($this->fk_user_valid) ? $this->db->escape($this->fk_user_valid) : "null").",";
2560 $sql .= " fk_facture_source=".(isset($this->fk_facture_source) ? $this->db->escape($this->fk_facture_source) : "null").",";
2561 $sql .= " fk_projet=".(isset($this->fk_project) ? $this->db->escape($this->fk_project) : "null").",";
2562 $sql .= " fk_cond_reglement=".(isset($this->cond_reglement_id) ? $this->db->escape($this->cond_reglement_id) : "null").",";
2563 $sql .= " fk_mode_reglement=".(isset($this->mode_reglement_id) ? $this->db->escape($this->mode_reglement_id) : "null").",";
2564 $sql .= " date_lim_reglement=".(strval($this->date_lim_reglement) != '' ? "'".$this->db->idate($this->date_lim_reglement)."'" : 'null').",";
2565 $sql .= " note_private=".(isset($this->note_private) ? "'".$this->db->escape($this->note_private)."'" : "null").",";
2566 $sql .= " note_public=".(isset($this->note_public) ? "'".$this->db->escape($this->note_public)."'" : "null").",";
2567 $sql .= " model_pdf=".(isset($this->model_pdf) ? "'".$this->db->escape($this->model_pdf)."'" : "null").",";
2568 $sql .= " import_key=".(isset($this->import_key) ? "'".$this->db->escape($this->import_key)."'" : "null").",";
2569 $sql .= " situation_cycle_ref=".(empty($this->situation_cycle_ref) ? "null" : $this->db->escape($this->situation_cycle_ref)).",";
2570 $sql .= " situation_counter=".(empty($this->situation_counter) ? "null" : $this->db->escape($this->situation_counter)).",";
2571 $sql .= " situation_final=".(empty($this->situation_final) ? "0" : $this->db->escape($this->situation_final)).",";
2572 $sql .= " retained_warranty=".(empty($this->retained_warranty) ? "0" : $this->db->escape($this->retained_warranty)).",";
2573 $sql .= " retained_warranty_date_limit=".(strval($this->retained_warranty_date_limit) != '' ? "'".$this->db->idate($this->retained_warranty_date_limit)."'" : 'null').",";
2574 $sql .= " retained_warranty_fk_cond_reglement=".(isset($this->retained_warranty_fk_cond_reglement) ?intval($this->retained_warranty_fk_cond_reglement) : "null");
2575 $sql .= " WHERE rowid=".((int) $this->id);
2576
2577 $this->db->begin();
2578
2579 dol_syslog(get_class($this)."::update", LOG_DEBUG);
2580 $resql = $this->db->query($sql);
2581 if (!$resql) {
2582 $error++;
2583 $this->errors[] = "Error ".$this->db->lasterror();
2584 }
2585
2586 if (!$error) {
2587 $result = $this->insertExtraFields();
2588 if ($result < 0) {
2589 $error++;
2590 }
2591 }
2592
2593 if (!$error && !$notrigger) {
2594 // Call trigger
2595 $result = $this->call_trigger('BILL_MODIFY', $user);
2596 if ($result < 0) {
2597 $error++;
2598 }
2599 // End call triggers
2600 }
2601
2602 // Commit or rollback
2603 if ($error) {
2604 foreach ($this->errors as $errmsg) {
2605 dol_syslog(get_class($this)."::update ".$errmsg, LOG_ERR);
2606 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2607 }
2608 $this->db->rollback();
2609 return -1 * $error;
2610 } else {
2611 $this->db->commit();
2612 return 1;
2613 }
2614 }
2615
2616
2617 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2624 public function insert_discount($idremise)
2625 {
2626 // phpcs:enable
2627 global $conf, $langs;
2628
2629 include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
2630 include_once DOL_DOCUMENT_ROOT.'/core/class/discount.class.php';
2631
2632 $this->db->begin();
2633
2634 $remise = new DiscountAbsolute($this->db);
2635 $result = $remise->fetch($idremise);
2636
2637 if ($result > 0) {
2638 if ($remise->fk_facture) { // Protection against multiple submission
2639 $this->error = $langs->trans("ErrorDiscountAlreadyUsed");
2640 $this->db->rollback();
2641 return -5;
2642 }
2643
2644 $facligne = new FactureLigne($this->db);
2645 $facligne->fk_facture = $this->id;
2646 $facligne->fk_remise_except = $remise->id;
2647 $facligne->desc = $remise->description; // Description ligne
2648 $facligne->vat_src_code = $remise->vat_src_code;
2649 $facligne->tva_tx = $remise->tva_tx;
2650 $facligne->subprice = -$remise->amount_ht;
2651 $facligne->fk_product = 0; // Id produit predefini
2652 $facligne->qty = 1;
2653 $facligne->remise_percent = 0;
2654 $facligne->rang = -1;
2655 $facligne->info_bits = 2;
2656
2657 if (!empty($conf->global->MAIN_ADD_LINE_AT_POSITION)) {
2658 $facligne->rang = 1;
2659 $linecount = count($this->lines);
2660 for ($ii = 1; $ii <= $linecount; $ii++) {
2661 $this->updateRangOfLine($this->lines[$ii - 1]->id, $ii+1);
2662 }
2663 }
2664
2665 // Get buy/cost price of invoice that is source of discount
2666 if ($remise->fk_facture_source > 0) {
2667 $srcinvoice = new Facture($this->db);
2668 $srcinvoice->fetch($remise->fk_facture_source);
2669 include_once DOL_DOCUMENT_ROOT.'/core/class/html.formmargin.class.php'; // TODO Move this into commonobject
2670 $formmargin = new FormMargin($this->db);
2671 $arraytmp = $formmargin->getMarginInfosArray($srcinvoice, false);
2672 $facligne->pa_ht = $arraytmp['pa_total'];
2673 }
2674
2675 $facligne->total_ht = -$remise->amount_ht;
2676 $facligne->total_tva = -$remise->amount_tva;
2677 $facligne->total_ttc = -$remise->amount_ttc;
2678
2679 $facligne->multicurrency_subprice = -$remise->multicurrency_subprice;
2680 $facligne->multicurrency_total_ht = -$remise->multicurrency_amount_ht;
2681 $facligne->multicurrency_total_tva = -$remise->multicurrency_amount_tva;
2682 $facligne->multicurrency_total_ttc = -$remise->multicurrency_amount_ttc;
2683
2684 $lineid = $facligne->insert();
2685 if ($lineid > 0) {
2686 $result = $this->update_price(1);
2687 if ($result > 0) {
2688 // Create link between discount and invoice line
2689 $result = $remise->link_to_invoice($lineid, 0);
2690 if ($result < 0) {
2691 $this->error = $remise->error;
2692 $this->db->rollback();
2693 return -4;
2694 }
2695
2696 $this->db->commit();
2697 return 1;
2698 } else {
2699 $this->error = $facligne->error;
2700 $this->db->rollback();
2701 return -1;
2702 }
2703 } else {
2704 $this->error = $facligne->error;
2705 $this->db->rollback();
2706 return -2;
2707 }
2708 } else {
2709 $this->db->rollback();
2710 return -3;
2711 }
2712 }
2713
2714 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2722 public function set_ref_client($ref_client, $notrigger = 0)
2723 {
2724 // phpcs:enable
2725 global $user;
2726
2727 $error = 0;
2728
2729 $this->db->begin();
2730
2731 $sql = 'UPDATE '.MAIN_DB_PREFIX.'facture';
2732 if (empty($ref_client)) {
2733 $sql .= ' SET ref_client = NULL';
2734 } else {
2735 $sql .= ' SET ref_client = \''.$this->db->escape($ref_client).'\'';
2736 }
2737 $sql .= " WHERE rowid = ".((int) $this->id);
2738
2739 dol_syslog(__METHOD__.' this->id='.$this->id.', ref_client='.$ref_client, LOG_DEBUG);
2740 $resql = $this->db->query($sql);
2741 if (!$resql) {
2742 $this->errors[] = $this->db->error();
2743 $error++;
2744 }
2745
2746 if (!$error) {
2747 $this->ref_client = $ref_client;
2748 }
2749
2750 if (!$notrigger && empty($error)) {
2751 // Call trigger
2752 $result = $this->call_trigger('BILL_MODIFY', $user);
2753 if ($result < 0) {
2754 $error++;
2755 }
2756 // End call triggers
2757 }
2758
2759 if (!$error) {
2760 $this->ref_client = $ref_client;
2761
2762 $this->db->commit();
2763 return 1;
2764 } else {
2765 foreach ($this->errors as $errmsg) {
2766 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2767 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2768 }
2769 $this->db->rollback();
2770 return -1 * $error;
2771 }
2772 }
2773
2782 public function delete($user, $notrigger = 0, $idwarehouse = -1)
2783 {
2784 global $langs, $conf;
2785 require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
2786
2787 $rowid = $this->id;
2788
2789 dol_syslog(get_class($this)."::delete rowid=".$rowid.", ref=".$this->ref.", thirdparty=".(empty($this->thirdparty) ? '' : $this->thirdparty->name), LOG_DEBUG);
2790
2791 // Test to avoid invoice deletion (allowed if draft)
2792 $result = $this->is_erasable();
2793
2794 if ($result <= 0) {
2795 return 0;
2796 }
2797
2798 $error = 0;
2799
2800 $this->db->begin();
2801
2802 if (!$error && !$notrigger) {
2803 // Call trigger
2804 $result = $this->call_trigger('BILL_DELETE', $user);
2805 if ($result < 0) {
2806 $error++;
2807 }
2808 // End call triggers
2809 }
2810
2811 // Removed extrafields
2812 if (!$error) {
2813 $result = $this->deleteExtraFields();
2814 if ($result < 0) {
2815 $error++;
2816 dol_syslog(get_class($this)."::delete error deleteExtraFields ".$this->error, LOG_ERR);
2817 }
2818 }
2819
2820 if (!$error) {
2821 // Delete linked object
2822 $res = $this->deleteObjectLinked();
2823 if ($res < 0) {
2824 $error++;
2825 }
2826 }
2827
2828 if (!$error) {
2829 // If invoice was converted into a discount not yet consumed, we remove discount
2830 $sql = 'DELETE FROM '.MAIN_DB_PREFIX.'societe_remise_except';
2831 $sql .= ' WHERE fk_facture_source = '.((int) $rowid);
2832 $sql .= ' AND fk_facture_line IS NULL';
2833 $resql = $this->db->query($sql);
2834
2835 // If invoice has consumed discounts
2836 $this->fetch_lines();
2837 $list_rowid_det = array();
2838 foreach ($this->lines as $key => $invoiceline) {
2839 $list_rowid_det[] = $invoiceline->id;
2840 }
2841
2842 // Consumed discounts are freed
2843 if (count($list_rowid_det)) {
2844 $sql = 'UPDATE '.MAIN_DB_PREFIX.'societe_remise_except';
2845 $sql .= ' SET fk_facture = NULL, fk_facture_line = NULL';
2846 $sql .= ' WHERE fk_facture_line IN ('.$this->db->sanitize(join(',', $list_rowid_det)).')';
2847
2848 if (!$this->db->query($sql)) {
2849 $this->error = $this->db->error()." sql=".$sql;
2850 $this->errors[] = $this->error;
2851 $this->db->rollback();
2852 return -5;
2853 }
2854 }
2855
2856 // Remove other links to the deleted invoice
2857
2858 $sql = 'UPDATE '.MAIN_DB_PREFIX.'eventorganization_conferenceorboothattendee';
2859 $sql .= ' SET fk_invoice = NULL';
2860 $sql .= ' WHERE fk_invoice = '.((int) $rowid);
2861
2862 if (!$this->db->query($sql)) {
2863 $this->error = $this->db->error()." sql=".$sql;
2864 $this->errors[] = $this->error;
2865 $this->db->rollback();
2866 return -5;
2867 }
2868
2869 $sql = 'UPDATE '.MAIN_DB_PREFIX.'element_time';
2870 $sql .= ' SET invoice_id = NULL, invoice_line_id = NULL';
2871 $sql .= ' WHERE invoice_id = '.((int) $rowid);
2872
2873 if (!$this->db->query($sql)) {
2874 $this->error = $this->db->error()." sql=".$sql;
2875 $this->errors[] = $this->error;
2876 $this->db->rollback();
2877 return -5;
2878 }
2879
2880 // If we decrease stock on invoice validation, we increase back if a warehouse id was provided
2881 if ($this->type != self::TYPE_DEPOSIT && $result >= 0 && isModEnabled('stock') && !empty($conf->global->STOCK_CALCULATE_ON_BILL) && $idwarehouse != -1) {
2882 require_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
2883 $langs->load("agenda");
2884
2885 $num = count($this->lines);
2886 for ($i = 0; $i < $num; $i++) {
2887 if ($this->lines[$i]->fk_product > 0) {
2888 $mouvP = new MouvementStock($this->db);
2889 $mouvP->origin = &$this;
2890 $mouvP->setOrigin($this->element, $this->id);
2891 // We decrease stock for product
2892 if ($this->type == self::TYPE_CREDIT_NOTE) {
2893 $result = $mouvP->livraison($user, $this->lines[$i]->fk_product, $idwarehouse, $this->lines[$i]->qty, $this->lines[$i]->subprice, $langs->trans("InvoiceDeleteDolibarr", $this->ref));
2894 } else {
2895 $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
2896 }
2897 }
2898 }
2899 }
2900
2901 // Invoice line extrafileds
2902 $main = MAIN_DB_PREFIX.'facturedet';
2903 $ef = $main."_extrafields";
2904 $sqlef = "DELETE FROM ".$ef." WHERE fk_object IN (SELECT rowid FROM ".$main." WHERE fk_facture = ".((int) $rowid).")";
2905 // Delete invoice line
2906 $sql = 'DELETE FROM '.MAIN_DB_PREFIX.'facturedet WHERE fk_facture = '.((int) $rowid);
2907
2908 if ($this->db->query($sqlef) && $this->db->query($sql) && $this->delete_linked_contact()) {
2909 $sql = 'DELETE FROM '.MAIN_DB_PREFIX.'facture WHERE rowid = '.((int) $rowid);
2910
2911 $resql = $this->db->query($sql);
2912 if ($resql) {
2913 // Delete record into ECM index (Note that delete is also done when deleting files with the dol_delete_dir_recursive
2914 $this->deleteEcmFiles();
2915
2916 // On efface le repertoire de pdf provisoire
2917 $ref = dol_sanitizeFileName($this->ref);
2918 if ($conf->facture->dir_output && !empty($this->ref)) {
2919 $dir = $conf->facture->dir_output."/".$ref;
2920 $file = $conf->facture->dir_output."/".$ref."/".$ref.".pdf";
2921 if (file_exists($file)) { // We must delete all files before deleting directory
2922 $ret = dol_delete_preview($this);
2923
2924 if (!dol_delete_file($file, 0, 0, 0, $this)) { // For triggers
2925 $langs->load("errors");
2926 $this->error = $langs->trans("ErrorFailToDeleteFile", $file);
2927 $this->errors[] = $this->error;
2928 $this->db->rollback();
2929 return 0;
2930 }
2931 }
2932 if (file_exists($dir)) {
2933 if (!dol_delete_dir_recursive($dir)) { // For remove dir and meta
2934 $langs->load("errors");
2935 $this->error = $langs->trans("ErrorFailToDeleteDir", $dir);
2936 $this->errors[] = $this->error;
2937 $this->db->rollback();
2938 return 0;
2939 }
2940 }
2941 }
2942
2943 $this->db->commit();
2944 return 1;
2945 } else {
2946 $this->error = $this->db->lasterror()." sql=".$sql;
2947 $this->errors[] = $this->error;
2948 $this->db->rollback();
2949 return -6;
2950 }
2951 } else {
2952 $this->error = $this->db->lasterror()." sql=".$sql;
2953 $this->errors[] = $this->error;
2954 $this->db->rollback();
2955 return -4;
2956 }
2957 } else {
2958 $this->db->rollback();
2959 return -2;
2960 }
2961 }
2962
2963 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2975 public function set_paid($user, $close_code = '', $close_note = '')
2976 {
2977 // phpcs:enable
2978 dol_syslog(get_class($this)."::set_paid is deprecated, use setPaid instead", LOG_NOTICE);
2979 return $this->setPaid($user, $close_code, $close_note);
2980 }
2981
2991 public function setPaid($user, $close_code = '', $close_note = '')
2992 {
2993 $error = 0;
2994
2995 if ($this->paye != 1) {
2996 $this->db->begin();
2997
2998 $now = dol_now();
2999
3000 dol_syslog(get_class($this)."::setPaid rowid=".((int) $this->id), LOG_DEBUG);
3001
3002 $sql = 'UPDATE '.MAIN_DB_PREFIX.'facture SET';
3003 $sql .= ' fk_statut='.self::STATUS_CLOSED;
3004 if (!$close_code) {
3005 $sql .= ', paye=1';
3006 }
3007 if ($close_code) {
3008 $sql .= ", close_code='".$this->db->escape($close_code)."'";
3009 }
3010 if ($close_note) {
3011 $sql .= ", close_note='".$this->db->escape($close_note)."'";
3012 }
3013 $sql .= ', fk_user_closing = '.((int) $user->id);
3014 $sql .= ", date_closing = '".$this->db->idate($now)."'";
3015 $sql .= " WHERE rowid = ".((int) $this->id);
3016
3017 $resql = $this->db->query($sql);
3018 if ($resql) {
3019 // Call trigger
3020 $result = $this->call_trigger('BILL_PAYED', $user);
3021 if ($result < 0) {
3022 $error++;
3023 }
3024 // End call triggers
3025 } else {
3026 $error++;
3027 $this->error = $this->db->lasterror();
3028 }
3029
3030 if (!$error) {
3031 $this->db->commit();
3032 return 1;
3033 } else {
3034 $this->db->rollback();
3035 return -1;
3036 }
3037 } else {
3038 return 0;
3039 }
3040 }
3041
3042
3043 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3054 public function set_unpaid($user)
3055 {
3056 // phpcs:enable
3057 dol_syslog(get_class($this)."::set_unpaid is deprecated, use setUnpaid instead", LOG_NOTICE);
3058 return $this->setUnpaid($user);
3059 }
3060
3069 public function setUnpaid($user)
3070 {
3071 $error = 0;
3072
3073 $this->db->begin();
3074
3075 $sql = 'UPDATE '.MAIN_DB_PREFIX.'facture';
3076 $sql .= ' SET paye=0, fk_statut='.self::STATUS_VALIDATED.', close_code=null, close_note=null,';
3077 $sql .= ' date_closing=null,';
3078 $sql .= ' fk_user_closing=null';
3079 $sql .= " WHERE rowid = ".((int) $this->id);
3080
3081 dol_syslog(get_class($this)."::setUnpaid", LOG_DEBUG);
3082 $resql = $this->db->query($sql);
3083 if ($resql) {
3084 // Call trigger
3085 $result = $this->call_trigger('BILL_UNPAYED', $user);
3086 if ($result < 0) {
3087 $error++;
3088 }
3089 // End call triggers
3090 } else {
3091 $error++;
3092 $this->error = $this->db->error();
3093 dol_print_error($this->db);
3094 }
3095
3096 if (!$error) {
3097 $this->db->commit();
3098 return 1;
3099 } else {
3100 $this->db->rollback();
3101 return -1;
3102 }
3103 }
3104
3105
3106 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3119 public function set_canceled($user, $close_code = '', $close_note = '')
3120 {
3121 // phpcs:enable
3122 dol_syslog(get_class($this)."::set_canceled is deprecated, use setCanceled instead", LOG_NOTICE);
3123 return $this->setCanceled($user, $close_code, $close_note);
3124 }
3125
3136 public function setCanceled($user, $close_code = '', $close_note = '')
3137 {
3138 dol_syslog(get_class($this)."::setCanceled rowid=".((int) $this->id), LOG_DEBUG);
3139
3140 $this->db->begin();
3141
3142 $sql = 'UPDATE '.MAIN_DB_PREFIX.'facture SET';
3143 $sql .= ' fk_statut='.self::STATUS_ABANDONED;
3144 if ($close_code) {
3145 $sql .= ", close_code='".$this->db->escape($close_code)."'";
3146 }
3147 if ($close_note) {
3148 $sql .= ", close_note='".$this->db->escape($close_note)."'";
3149 }
3150 $sql .= " WHERE rowid = ".((int) $this->id);
3151
3152 $resql = $this->db->query($sql);
3153 if ($resql) {
3154 // Bound discounts are deducted from the invoice
3155 // as they have not been used since the invoice is abandoned.
3156 $sql = 'UPDATE '.MAIN_DB_PREFIX.'societe_remise_except';
3157 $sql .= ' SET fk_facture = NULL';
3158 $sql .= ' WHERE fk_facture = '.((int) $this->id);
3159
3160 $resql = $this->db->query($sql);
3161 if ($resql) {
3162 // Call trigger
3163 $result = $this->call_trigger('BILL_CANCEL', $user);
3164 if ($result < 0) {
3165 $this->db->rollback();
3166 return -1;
3167 }
3168 // End call triggers
3169
3170 $this->db->commit();
3171 return 1;
3172 } else {
3173 $this->error = $this->db->error()." sql=".$sql;
3174 $this->db->rollback();
3175 return -1;
3176 }
3177 } else {
3178 $this->error = $this->db->error()." sql=".$sql;
3179 $this->db->rollback();
3180 return -2;
3181 }
3182 }
3183
3195 public function validate($user, $force_number = '', $idwarehouse = 0, $notrigger = 0, $batch_rule = 0)
3196 {
3197 global $conf, $langs, $mysoc;
3198 require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
3199
3200 $productStatic = null;
3201 $warehouseStatic = null;
3202 if ($batch_rule > 0) {
3203 require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
3204 require_once DOL_DOCUMENT_ROOT.'/product/class/productbatch.class.php';
3205 require_once DOL_DOCUMENT_ROOT.'/product/stock/class/entrepot.class.php';
3206 $productStatic = new Product($this->db);
3207 $warehouseStatic = new Entrepot($this->db);
3208 $productbatch = new Productbatch($this->db);
3209 }
3210
3211 $now = dol_now();
3212
3213 $error = 0;
3214 dol_syslog(get_class($this).'::validate user='.$user->id.', force_number='.$force_number.', idwarehouse='.$idwarehouse);
3215
3216 // Force to have object complete for checks
3217 $this->fetch_thirdparty();
3218 $this->fetch_lines();
3219
3220 // Check parameters
3221 if ($this->statut != self::STATUS_DRAFT) {
3222 dol_syslog(get_class($this)."::validate status is not draft. operation canceled.", LOG_WARNING);
3223 return 0;
3224 }
3225 if (count($this->lines) <= 0) {
3226 $langs->load("errors");
3227 $this->error = $langs->trans("ErrorObjectMustHaveLinesToBeValidated", $this->ref);
3228 return -1;
3229 }
3230 if ((empty($conf->global->MAIN_USE_ADVANCED_PERMS) && empty($user->rights->facture->creer))
3231 || (!empty($conf->global->MAIN_USE_ADVANCED_PERMS) && empty($user->rights->facture->invoice_advance->validate))) {
3232 $this->error = 'Permission denied';
3233 dol_syslog(get_class($this)."::validate ".$this->error.' MAIN_USE_ADVANCED_PERMS='.$conf->global->MAIN_USE_ADVANCED_PERMS, LOG_ERR);
3234 return -1;
3235 }
3236 if ((preg_match('/^[\‍(]?PROV/i', $this->ref) || empty($this->ref)) && // empty should not happened, but when it occurs, the test save life
3237 !empty($conf->global->FAC_FORCE_DATE_VALIDATION) // If option enabled, we force invoice date
3238 ) {
3239 $this->date = dol_now();
3240 $this->date_lim_reglement = $this->calculate_date_lim_reglement();
3241 }
3242 if (!empty($conf->global-> INVOICE_CHECK_POSTERIOR_DATE)) {
3243 $last_of_type = $this->willBeLastOfSameType(true);
3244 if (!$last_of_type[0]) {
3245 $this->error = $langs->transnoentities("ErrorInvoiceIsNotLastOfSameType", $this->ref, dol_print_date($this->date, 'day'), dol_print_date($last_of_type[1], 'day'));
3246 return -1;
3247 }
3248 }
3249
3250 // Check for mandatory fields in thirdparty (defined into setup)
3251 if (!empty($this->thirdparty) && is_object($this->thirdparty)) {
3252 $array_to_check = array('IDPROF1', 'IDPROF2', 'IDPROF3', 'IDPROF4', 'IDPROF5', 'IDPROF6', 'EMAIL', 'ACCOUNTANCY_CODE_CUSTOMER');
3253 foreach ($array_to_check as $key) {
3254 $keymin = strtolower($key);
3255 if (!property_exists($this->thirdparty, $keymin)) {
3256 continue;
3257 }
3258 $vallabel = $this->thirdparty->$keymin;
3259
3260 $i = (int) preg_replace('/[^0-9]/', '', $key);
3261 if ($i > 0) {
3262 if ($this->thirdparty->isACompany()) {
3263 // Check for mandatory prof id (but only if country is other than ours)
3264 if ($mysoc->country_id > 0 && $this->thirdparty->country_id == $mysoc->country_id) {
3265 $idprof_mandatory = 'SOCIETE_'.$key.'_INVOICE_MANDATORY';
3266 if (!$vallabel && !empty($conf->global->$idprof_mandatory)) {
3267 $langs->load("errors");
3268 $this->error = $langs->trans('ErrorProdIdIsMandatory', $langs->transcountry('ProfId'.$i, $this->thirdparty->country_code)).' ('.$langs->trans("ForbiddenBySetupRules").') ['.$langs->trans('Company').' : '.$this->thirdparty->name.']';
3269 dol_syslog(__METHOD__.' '.$this->error, LOG_ERR);
3270 return -1;
3271 }
3272 }
3273 }
3274 } else {
3275 if ($key == 'EMAIL') {
3276 // Check for mandatory
3277 if (!empty($conf->global->SOCIETE_EMAIL_INVOICE_MANDATORY) && !isValidEMail($this->thirdparty->email)) {
3278 $langs->load("errors");
3279 $this->error = $langs->trans("ErrorBadEMail", $this->thirdparty->email).' ('.$langs->trans("ForbiddenBySetupRules").') ['.$langs->trans('Company').' : '.$this->thirdparty->name.']';
3280 dol_syslog(__METHOD__.' '.$this->error, LOG_ERR);
3281 return -1;
3282 }
3283 }
3284 if ($key == 'ACCOUNTANCY_CODE_CUSTOMER') {
3285 // Check for mandatory
3286 if (!empty($conf->global->SOCIETE_ACCOUNTANCY_CODE_CUSTOMER_INVOICE_MANDATORY) && empty($this->thirdparty->code_compta)) {
3287 $langs->load("errors");
3288 $this->error = $langs->trans("ErrorAccountancyCodeCustomerIsMandatory", $this->thirdparty->name).' ('.$langs->trans("ForbiddenBySetupRules").')';
3289 dol_syslog(__METHOD__.' '.$this->error, LOG_ERR);
3290 return -1;
3291 }
3292 }
3293 }
3294 }
3295 }
3296
3297 // Check for mandatory fields in $this
3298 $array_to_check = array('REF_CLIENT'=>'RefCustomer');
3299 foreach ($array_to_check as $key => $val) {
3300 $keymin = strtolower($key);
3301 $vallabel = $this->$keymin;
3302
3303 // Check for mandatory
3304 $keymandatory = 'INVOICE_'.$key.'_MANDATORY_FOR_VALIDATION';
3305 if (!$vallabel && !empty($conf->global->$keymandatory)) {
3306 $langs->load("errors");
3307 $error++;
3308 setEventMessages($langs->trans("ErrorFieldRequired", $langs->transnoentitiesnoconv($val)), null, 'errors');
3309 }
3310 }
3311
3312 $this->db->begin();
3313
3314 // Check parameters
3315 if ($this->type == self::TYPE_REPLACEMENT) { // if this is a replacement invoice
3316 // Check that source invoice is known
3317 if ($this->fk_facture_source <= 0) {
3318 $this->error = $langs->trans("ErrorFieldRequired", $langs->transnoentitiesnoconv("InvoiceReplacement"));
3319 $this->db->rollback();
3320 return -10;
3321 }
3322
3323 // Load source invoice that has been replaced
3324 $facreplaced = new Facture($this->db);
3325 $result = $facreplaced->fetch($this->fk_facture_source);
3326 if ($result <= 0) {
3327 $this->error = $langs->trans("ErrorBadInvoice");
3328 $this->db->rollback();
3329 return -11;
3330 }
3331
3332 // Check that source invoice not already replaced by another one.
3333 $idreplacement = $facreplaced->getIdReplacingInvoice('validated');
3334 if ($idreplacement && $idreplacement != $this->id) {
3335 $facreplacement = new Facture($this->db);
3336 $facreplacement->fetch($idreplacement);
3337 $this->error = $langs->trans("ErrorInvoiceAlreadyReplaced", $facreplaced->ref, $facreplacement->ref);
3338 $this->db->rollback();
3339 return -12;
3340 }
3341
3342 $result = $facreplaced->setCanceled($user, self::CLOSECODE_REPLACED, '');
3343 if ($result < 0) {
3344 $this->error = $facreplaced->error;
3345 $this->db->rollback();
3346 return -13;
3347 }
3348 }
3349
3350 // Define new ref
3351 if ($force_number) {
3352 $num = $force_number;
3353 } elseif (preg_match('/^[\‍(]?PROV/i', $this->ref) || empty($this->ref)) { // empty should not happened, but when it occurs, the test save life
3354 $num = $this->getNextNumRef($this->thirdparty);
3355 } else {
3356 $num = $this->ref;
3357 }
3358
3359 $this->newref = dol_sanitizeFileName($num);
3360
3361 if ($num) {
3362 $this->update_price(1);
3363
3364 // Validate
3365 $sql = 'UPDATE '.MAIN_DB_PREFIX.'facture';
3366 $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)."'";
3367 if (!empty($conf->global->FAC_FORCE_DATE_VALIDATION)) { // If option enabled, we force invoice date
3368 $sql .= ", datef='".$this->db->idate($this->date)."'";
3369 $sql .= ", date_lim_reglement='".$this->db->idate($this->date_lim_reglement)."'";
3370 }
3371 $sql .= " WHERE rowid = ".((int) $this->id);
3372
3373 dol_syslog(get_class($this)."::validate", LOG_DEBUG);
3374 $resql = $this->db->query($sql);
3375 if (!$resql) {
3376 dol_print_error($this->db);
3377 $error++;
3378 }
3379
3380 // We check if the invoice was provisional
3381 if (!$error && (preg_match('/^[\‍(]?PROV/i', $this->ref))) {
3382 // La verif qu'une remise n'est pas utilisee 2 fois est faite au moment de l'insertion de ligne
3383 }
3384
3385 if (!$error) {
3386 // Define third party as a customer
3387 $result = $this->thirdparty->set_as_client();
3388
3389 // If active we decrement the main product and its components at invoice validation
3390 if ($this->type != self::TYPE_DEPOSIT && $result >= 0 && isModEnabled('stock') && !empty($conf->global->STOCK_CALCULATE_ON_BILL) && $idwarehouse > 0) {
3391 require_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
3392 $langs->load("agenda");
3393
3394 // Loop on each line
3395 $cpt = count($this->lines);
3396 for ($i = 0; $i < $cpt; $i++) {
3397 if ($this->lines[$i]->fk_product > 0) {
3398 $mouvP = new MouvementStock($this->db);
3399 $mouvP->origin = &$this;
3400 $mouvP->setOrigin($this->element, $this->id);
3401 // We decrease stock for product
3402 if ($this->type == self::TYPE_CREDIT_NOTE) {
3403 $result = $mouvP->reception($user, $this->lines[$i]->fk_product, $idwarehouse, $this->lines[$i]->qty, 0, $langs->trans("InvoiceValidatedInDolibarr", $num));
3404 if ($result < 0) {
3405 $error++;
3406 $this->error = $mouvP->error;
3407 }
3408 } else {
3409 $is_batch_line = false;
3410 if ($batch_rule > 0) {
3411 $productStatic->fetch($this->lines[$i]->fk_product);
3412 if ($productStatic->hasbatch()) {
3413 $is_batch_line = true;
3414 $product_qty_remain = $this->lines[$i]->qty;
3415
3416 $sortfield = null;
3417 $sortorder = null;
3418 // find all batch order by sellby (DLC) and eatby dates (DLUO) first
3420 $sortfield = 'pl.sellby,pl.eatby,pb.qty,pl.rowid';
3421 $sortorder = 'ASC,ASC,ASC,ASC';
3422 }
3423
3424 $resBatchList = $productbatch->findAllForProduct($productStatic->id, $idwarehouse, (getDolGlobalInt('STOCK_ALLOW_NEGATIVE_TRANSFER') ? null : 0), $sortfield, $sortorder);
3425 if (!is_array($resBatchList)) {
3426 $error++;
3427 $this->error = $this->db->lasterror();
3428 }
3429
3430 if (!$error) {
3431 $batchList = $resBatchList;
3432 if (empty($batchList)) {
3433 $error++;
3434 $langs->load('errors');
3435 $warehouseStatic->fetch($idwarehouse);
3436 $this->error = $langs->trans('ErrorBatchNoFoundForProductInWarehouse', $productStatic->label, $warehouseStatic->ref);
3437 dol_syslog(__METHOD__.' Error: '.$langs->transnoentitiesnoconv('ErrorBatchNoFoundForProductInWarehouse', $productStatic->label, $warehouseStatic->ref), LOG_ERR);
3438 }
3439
3440 foreach ($batchList as $batch) {
3441 if ($batch->qty <= 0) {
3442 continue; // try to decrement only batches have positive quantity first
3443 }
3444
3445 // enough quantity in this batch
3446 if ($batch->qty >= $product_qty_remain) {
3447 $product_batch_qty = $product_qty_remain;
3448 } else {
3449 // not enough (take all in batch)
3450 $product_batch_qty = $batch->qty;
3451 }
3452 $result = $mouvP->livraison($user, $productStatic->id, $idwarehouse, $product_batch_qty, $this->lines[$i]->subprice, $langs->trans('InvoiceValidatedInDolibarr', $num), '', '', '', $batch->batch);
3453 if ($result < 0) {
3454 $error++;
3455 $this->error = $mouvP->error;
3456 $this->errors = $mouvP->errors;
3457 break;
3458 }
3459
3460 $product_qty_remain -= $product_batch_qty;
3461 // all product quantity was decremented
3462 if ($product_qty_remain <= 0) {
3463 break;
3464 }
3465 }
3466
3467 if (!$error && $product_qty_remain > 0) {
3468 if (getDolGlobalInt('STOCK_ALLOW_NEGATIVE_TRANSFER')) {
3469 // take in the first batch
3470 $batch = $batchList[0];
3471 $result = $mouvP->livraison($user, $productStatic->id, $idwarehouse, $product_qty_remain, $this->lines[$i]->subprice, $langs->trans('InvoiceValidatedInDolibarr', $num), '', '', '', $batch->batch);
3472 if ($result < 0) {
3473 $error++;
3474 $this->error = $mouvP->error;
3475 $this->errors = $mouvP->errors;
3476 }
3477 } else {
3478 $error++;
3479 $langs->load('errors');
3480 $warehouseStatic->fetch($idwarehouse);
3481 $this->error = $langs->trans('ErrorBatchNoFoundEnoughQuantityForProductInWarehouse', $productStatic->label, $warehouseStatic->ref);
3482 dol_syslog(__METHOD__.' Error: '.$langs->transnoentitiesnoconv('ErrorBatchNoFoundEnoughQuantityForProductInWarehouse', $productStatic->label, $warehouseStatic->ref), LOG_ERR);
3483 }
3484 }
3485 }
3486 }
3487 }
3488
3489 if (!$is_batch_line) {
3490 $result = $mouvP->livraison($user, $this->lines[$i]->fk_product, $idwarehouse, $this->lines[$i]->qty, $this->lines[$i]->subprice, $langs->trans("InvoiceValidatedInDolibarr", $num));
3491 if ($result < 0) {
3492 $error++;
3493 $this->error = $mouvP->error;
3494 $this->errors = $mouvP->errors;
3495 }
3496 }
3497 }
3498 }
3499 }
3500 }
3501 }
3502
3503 /*
3504 * 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%)
3505 * So we can continue to create new invoice situation
3506 */
3507 if (!$error && $this->type == self::TYPE_CREDIT_NOTE && $this->fk_facture_source > 0) {
3508 $invoice_situation = new Facture($this->db);
3509 $result = $invoice_situation->fetch($this->fk_facture_source);
3510 if ($result > 0 && $invoice_situation->type == self::TYPE_SITUATION && $invoice_situation->situation_final == 1) {
3511 $invoice_situation->situation_final = 0;
3512 // Disable triggers because module can force situation_final to 1 by triggers (ex: SubTotal)
3513 $result = $invoice_situation->setFinal($user, 1);
3514 }
3515 if ($result < 0) {
3516 $this->error = $invoice_situation->error;
3517 $this->errors = $invoice_situation->errors;
3518 $error++;
3519 }
3520 }
3521
3522 // Trigger calls
3523 if (!$error && !$notrigger) {
3524 // Call trigger
3525 $result = $this->call_trigger('BILL_VALIDATE', $user);
3526 if ($result < 0) {
3527 $error++;
3528 }
3529 // End call triggers
3530 }
3531
3532 if (!$error) {
3533 $this->oldref = $this->ref;
3534
3535 // Rename directory if dir was a temporary ref
3536 if (preg_match('/^[\‍(]?PROV/i', $this->ref)) {
3537 // Now we rename also files into index
3538 $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)."'";
3539 $sql .= " WHERE filename LIKE '".$this->db->escape($this->ref)."%' AND filepath = 'facture/".$this->db->escape($this->ref)."' and entity = ".$conf->entity;
3540 $resql = $this->db->query($sql);
3541 if (!$resql) {
3542 $error++;
3543 $this->error = $this->db->lasterror();
3544 }
3545 $sql = 'UPDATE '.MAIN_DB_PREFIX."ecm_files set filepath = 'facture/".$this->db->escape($this->newref)."'";
3546 $sql .= " WHERE filepath = 'facture/".$this->db->escape($this->ref)."' and entity = ".$conf->entity;
3547 $resql = $this->db->query($sql);
3548 if (!$resql) {
3549 $error++; $this->error = $this->db->lasterror();
3550 }
3551
3552 // We rename directory ($this->ref = old ref, $num = new ref) in order not to lose the attachments
3553 $oldref = dol_sanitizeFileName($this->ref);
3554 $newref = dol_sanitizeFileName($num);
3555 $dirsource = $conf->facture->dir_output.'/'.$oldref;
3556 $dirdest = $conf->facture->dir_output.'/'.$newref;
3557 if (!$error && file_exists($dirsource)) {
3558 dol_syslog(get_class($this)."::validate rename dir ".$dirsource." into ".$dirdest);
3559
3560 if (@rename($dirsource, $dirdest)) {
3561 dol_syslog("Rename ok");
3562 // Rename docs starting with $oldref with $newref
3563 $listoffiles = dol_dir_list($conf->facture->dir_output.'/'.$newref, 'files', 1, '^'.preg_quote($oldref, '/'));
3564 foreach ($listoffiles as $fileentry) {
3565 $dirsource = $fileentry['name'];
3566 $dirdest = preg_replace('/^'.preg_quote($oldref, '/').'/', $newref, $dirsource);
3567 $dirsource = $fileentry['path'].'/'.$dirsource;
3568 $dirdest = $fileentry['path'].'/'.$dirdest;
3569 @rename($dirsource, $dirdest);
3570 }
3571 }
3572 }
3573 }
3574 }
3575
3576 if (!$error && !$this->is_last_in_cycle()) {
3577 if (!$this->updatePriceNextInvoice($langs)) {
3578 $error++;
3579 }
3580 }
3581
3582 // Set new ref and define current status
3583 if (!$error) {
3584 $this->ref = $num;
3586 $this->status = self::STATUS_VALIDATED;
3587 $this->brouillon = 0;
3588 $this->date_validation = $now;
3589 $i = 0;
3590
3591 if (!empty($conf->global->INVOICE_USE_SITUATION)) {
3592 $final = true;
3593 $nboflines = count($this->lines);
3594 while (($i < $nboflines) && $final) {
3595 $final = ($this->lines[$i]->situation_percent == 100);
3596 $i++;
3597 }
3598
3599 if (empty($final)) {
3600 $this->situation_final = 0;
3601 } else {
3602 $this->situation_final = 1;
3603 }
3604
3605 $this->setFinal($user);
3606 }
3607 }
3608 } else {
3609 $error++;
3610 }
3611
3612 if (!$error) {
3613 $this->db->commit();
3614 return 1;
3615 } else {
3616 $this->db->rollback();
3617 return -1;
3618 }
3619 }
3620
3627 public function updatePriceNextInvoice(&$langs)
3628 {
3629 foreach ($this->tab_next_situation_invoice as $next_invoice) {
3630 $is_last = $next_invoice->is_last_in_cycle();
3631
3632 if ($next_invoice->statut == self::STATUS_DRAFT && $is_last != 1) {
3633 $this->error = $langs->trans('updatePriceNextInvoiceErrorUpdateline', $next_invoice->ref);
3634 return false;
3635 }
3636
3637 $next_invoice->brouillon = 1;
3638
3639 foreach ($next_invoice->lines as $line) {
3640 $result = $next_invoice->updateline(
3641 $line->id,
3642 $line->desc,
3643 $line->subprice,
3644 $line->qty,
3645 $line->remise_percent,
3646 $line->date_start,
3647 $line->date_end,
3648 $line->tva_tx,
3649 $line->localtax1_tx,
3650 $line->localtax2_tx,
3651 'HT',
3652 $line->info_bits,
3653 $line->product_type,
3654 $line->fk_parent_line,
3655 0,
3656 $line->fk_fournprice,
3657 $line->pa_ht,
3658 $line->label,
3659 $line->special_code,
3660 $line->array_options,
3661 $line->situation_percent,
3662 $line->fk_unit
3663 );
3664
3665 if ($result < 0) {
3666 $this->error = $langs->trans('updatePriceNextInvoiceErrorUpdateline', $next_invoice->ref);
3667 return false;
3668 }
3669 }
3670
3671 break; // Only the next invoice and not each next invoice
3672 }
3673
3674 return true;
3675 }
3676
3684 public function setDraft($user, $idwarehouse = -1)
3685 {
3686 // phpcs:enable
3687 global $conf, $langs;
3688
3689 $error = 0;
3690
3691 if ($this->statut == self::STATUS_DRAFT) {
3692 dol_syslog(__METHOD__." already draft status", LOG_WARNING);
3693 return 0;
3694 }
3695
3696 dol_syslog(__METHOD__, LOG_DEBUG);
3697
3698 $this->db->begin();
3699
3700 $sql = "UPDATE ".MAIN_DB_PREFIX."facture";
3701 $sql .= " SET fk_statut = ".self::STATUS_DRAFT;
3702 $sql .= " WHERE rowid = ".((int) $this->id);
3703
3704 $result = $this->db->query($sql);
3705 if ($result) {
3706 if (!$error) {
3707 $this->oldcopy = clone $this;
3708 }
3709
3710 // If we decrease stock on invoice validation, we increase back
3711 if ($this->type != self::TYPE_DEPOSIT && $result >= 0 && isModEnabled('stock') && !empty($conf->global->STOCK_CALCULATE_ON_BILL)) {
3712 require_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
3713 $langs->load("agenda");
3714
3715 $num = count($this->lines);
3716 for ($i = 0; $i < $num; $i++) {
3717 if ($this->lines[$i]->fk_product > 0) {
3718 $mouvP = new MouvementStock($this->db);
3719 $mouvP->origin = &$this;
3720 $mouvP->setOrigin($this->element, $this->id);
3721 // We decrease stock for product
3722 if ($this->type == self::TYPE_CREDIT_NOTE) {
3723 $result = $mouvP->livraison($user, $this->lines[$i]->fk_product, $idwarehouse, $this->lines[$i]->qty, $this->lines[$i]->subprice, $langs->trans("InvoiceBackToDraftInDolibarr", $this->ref));
3724 } else {
3725 $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
3726 }
3727 }
3728 }
3729 }
3730
3731 if ($error == 0) {
3732 $old_statut = $this->statut;
3733 $this->brouillon = 1;
3734 $this->statut = self::STATUS_DRAFT;
3735 $this->status = self::STATUS_DRAFT;
3736
3737 // Call trigger
3738 $result = $this->call_trigger('BILL_UNVALIDATE', $user);
3739 if ($result < 0) {
3740 $error++;
3741 $this->statut = $old_statut;
3742 $this->status = $old_statut;
3743 $this->brouillon = 0;
3744 }
3745 // End call triggers
3746 } else {
3747 $this->db->rollback();
3748 return -1;
3749 }
3750
3751 if ($error == 0) {
3752 $this->db->commit();
3753 return 1;
3754 } else {
3755 $this->db->rollback();
3756 return -1;
3757 }
3758 } else {
3759 $this->error = $this->db->error();
3760 $this->db->rollback();
3761 return -1;
3762 }
3763 }
3764
3765
3807 public function addline(
3808 $desc,
3809 $pu_ht,
3810 $qty,
3811 $txtva,
3812 $txlocaltax1 = 0,
3813 $txlocaltax2 = 0,
3814 $fk_product = 0,
3815 $remise_percent = 0,
3816 $date_start = '',
3817 $date_end = '',
3818 $ventil = 0,
3819 $info_bits = 0,
3820 $fk_remise_except = '',
3821 $price_base_type = 'HT',
3822 $pu_ttc = 0,
3823 $type = 0,
3824 $rang = -1,
3825 $special_code = 0,
3826 $origin = '',
3827 $origin_id = 0,
3828 $fk_parent_line = 0,
3829 $fk_fournprice = null,
3830 $pa_ht = 0,
3831 $label = '',
3832 $array_options = 0,
3833 $situation_percent = 100,
3834 $fk_prev_id = 0,
3835 $fk_unit = null,
3836 $pu_ht_devise = 0,
3837 $ref_ext = '',
3838 $noupdateafterinsertline = 0
3839 ) {
3840 // Deprecation warning
3841 if ($label) {
3842 dol_syslog(__METHOD__.": using line label is deprecated", LOG_WARNING);
3843 //var_dump(debug_backtrace(false));exit;
3844 }
3845
3846 global $mysoc, $conf, $langs;
3847
3848 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);
3849
3850 if ($this->statut == self::STATUS_DRAFT) {
3851 include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
3852
3853 // Clean parameters
3854 if (empty($remise_percent)) {
3855 $remise_percent = 0;
3856 }
3857 if (empty($qty)) {
3858 $qty = 0;
3859 }
3860 if (empty($info_bits)) {
3861 $info_bits = 0;
3862 }
3863 if (empty($rang)) {
3864 $rang = 0;
3865 }
3866 if (empty($ventil)) {
3867 $ventil = 0;
3868 }
3869 if (empty($txtva)) {
3870 $txtva = 0;
3871 }
3872 if (empty($txlocaltax1)) {
3873 $txlocaltax1 = 0;
3874 }
3875 if (empty($txlocaltax2)) {
3876 $txlocaltax2 = 0;
3877 }
3878 if (empty($fk_parent_line) || $fk_parent_line < 0) {
3879 $fk_parent_line = 0;
3880 }
3881 if (empty($fk_prev_id)) {
3882 $fk_prev_id = 'null';
3883 }
3884 if (!isset($situation_percent) || $situation_percent > 100 || (string) $situation_percent == '') {
3885 $situation_percent = 100;
3886 }
3887 if (empty($ref_ext)) {
3888 $ref_ext = '';
3889 }
3890
3892 $qty = price2num($qty);
3893 $pu_ht = price2num($pu_ht);
3894 $pu_ht_devise = price2num($pu_ht_devise);
3895 $pu_ttc = price2num($pu_ttc);
3896 $pa_ht = price2num($pa_ht);
3897 if (!preg_match('/\‍((.*)\‍)/', $txtva)) {
3898 $txtva = price2num($txtva); // $txtva can have format '5.0(XXX)' or '5'
3899 }
3900 $txlocaltax1 = price2num($txlocaltax1);
3901 $txlocaltax2 = price2num($txlocaltax2);
3902
3903 if ($price_base_type == 'HT') {
3904 $pu = $pu_ht;
3905 } else {
3906 $pu = $pu_ttc;
3907 }
3908
3909 // Check parameters
3910 if ($type < 0) {
3911 return -1;
3912 }
3913
3914 if ($date_start && $date_end && $date_start > $date_end) {
3915 $langs->load("errors");
3916 $this->error = $langs->trans('ErrorStartDateGreaterEnd');
3917 return -1;
3918 }
3919
3920 $this->db->begin();
3921
3922 $product_type = $type;
3923 if (!empty($fk_product) && $fk_product > 0) {
3924 $product = new Product($this->db);
3925 $result = $product->fetch($fk_product);
3926 $product_type = $product->type;
3927
3928 if (!empty($conf->global->STOCK_MUST_BE_ENOUGH_FOR_INVOICE) && $product_type == 0 && $product->stock_reel < $qty) {
3929 $langs->load("errors");
3930 $this->error = $langs->trans('ErrorStockIsNotEnoughToAddProductOnInvoice', $product->ref);
3931 $this->db->rollback();
3932 return -3;
3933 }
3934 }
3935
3936 $localtaxes_type = getLocalTaxesFromRate($txtva, 0, $this->thirdparty, $mysoc);
3937
3938 // Clean vat code
3939 $reg = array();
3940 $vat_src_code = '';
3941 if (preg_match('/\‍((.*)\‍)/', $txtva, $reg)) {
3942 $vat_src_code = $reg[1];
3943 $txtva = preg_replace('/\s*\‍(.*\‍)/', '', $txtva); // Remove code into vatrate.
3944 }
3945
3946 // Calcul du total TTC et de la TVA pour la ligne a partir de
3947 // qty, pu, remise_percent et txtva
3948 // TRES IMPORTANT: C'est au moment de l'insertion ligne qu'on doit stocker
3949 // la part ht, tva et ttc, et ce au niveau de la ligne qui a son propre taux tva.
3950
3951 $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);
3952
3953 $total_ht = $tabprice[0];
3954 $total_tva = $tabprice[1];
3955 $total_ttc = $tabprice[2];
3956 $total_localtax1 = $tabprice[9];
3957 $total_localtax2 = $tabprice[10];
3958 $pu_ht = $tabprice[3];
3959
3960 // MultiCurrency
3961 $multicurrency_total_ht = $tabprice[16];
3962 $multicurrency_total_tva = $tabprice[17];
3963 $multicurrency_total_ttc = $tabprice[18];
3964 $pu_ht_devise = $tabprice[19];
3965
3966 // Rank to use
3967 $ranktouse = $rang;
3968 if ($ranktouse == -1) {
3969 $rangmax = $this->line_max($fk_parent_line);
3970 $ranktouse = $rangmax + 1;
3971 }
3972
3973 // Insert line
3974 $this->line = new FactureLigne($this->db);
3975
3976 $this->line->context = $this->context;
3977
3978 $this->line->fk_facture = $this->id;
3979 $this->line->label = $label; // deprecated
3980 $this->line->desc = $desc;
3981 $this->line->ref_ext = $ref_ext;
3982
3983 $this->line->qty = ($this->type == self::TYPE_CREDIT_NOTE ? abs($qty) : $qty); // For credit note, quantity is always positive and unit price negative
3984 $this->line->subprice = ($this->type == self::TYPE_CREDIT_NOTE ? -abs($pu_ht) : $pu_ht); // For credit note, unit price always negative, always positive otherwise
3985
3986 $this->line->vat_src_code = $vat_src_code;
3987 $this->line->tva_tx = $txtva;
3988 $this->line->localtax1_tx = ($total_localtax1 ? $localtaxes_type[1] : 0);
3989 $this->line->localtax2_tx = ($total_localtax2 ? $localtaxes_type[3] : 0);
3990 $this->line->localtax1_type = empty($localtaxes_type[0]) ? '' : $localtaxes_type[0];
3991 $this->line->localtax2_type = empty($localtaxes_type[2]) ? '' : $localtaxes_type[2];
3992
3993 $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
3994 $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
3995 $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
3996 $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
3997 $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
3998
3999 $this->line->fk_product = $fk_product;
4000 $this->line->product_type = $product_type;
4001 $this->line->remise_percent = $remise_percent;
4002 $this->line->date_start = $date_start;
4003 $this->line->date_end = $date_end;
4004 $this->line->ventil = $ventil;
4005 $this->line->rang = $ranktouse;
4006 $this->line->info_bits = $info_bits;
4007 $this->line->fk_remise_except = $fk_remise_except;
4008
4009 $this->line->special_code = $special_code;
4010 $this->line->fk_parent_line = $fk_parent_line;
4011 $this->line->origin = $origin;
4012 $this->line->origin_id = $origin_id;
4013 $this->line->situation_percent = $situation_percent;
4014 $this->line->fk_prev_id = $fk_prev_id;
4015 $this->line->fk_unit = $fk_unit;
4016
4017 // infos marge
4018 $this->line->fk_fournprice = $fk_fournprice;
4019 $this->line->pa_ht = $pa_ht;
4020
4021 // Multicurrency
4022 $this->line->fk_multicurrency = $this->fk_multicurrency;
4023 $this->line->multicurrency_code = $this->multicurrency_code;
4024 $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
4025
4026 $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
4027 $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
4028 $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
4029
4030 if (is_array($array_options) && count($array_options) > 0) {
4031 $this->line->array_options = $array_options;
4032 }
4033
4034 $result = $this->line->insert();
4035 if ($result > 0) {
4036 // Reorder if child line
4037 if (!empty($fk_parent_line)) {
4038 $this->line_order(true, 'DESC');
4039 } elseif ($ranktouse > 0 && $ranktouse <= count($this->lines)) { // Update all rank of all other lines
4040 $linecount = count($this->lines);
4041 for ($ii = $ranktouse; $ii <= $linecount; $ii++) {
4042 $this->updateRangOfLine($this->lines[$ii - 1]->id, $ii + 1);
4043 }
4044 }
4045
4046 // Mise a jour informations denormalisees au niveau de la facture meme
4047 if (empty($noupdateafterinsertline)) {
4048 $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.
4049 }
4050
4051 if ($result > 0) {
4052 $this->db->commit();
4053 return $this->line->id;
4054 } else {
4055 $this->error = $this->db->lasterror();
4056 $this->db->rollback();
4057 return -1;
4058 }
4059 } else {
4060 $this->error = $this->line->error;
4061 $this->errors = $this->line->errors;
4062 $this->db->rollback();
4063 return -2;
4064 }
4065 } else {
4066 $this->errors[]='status of invoice must be Draft to allow use of ->addline()';
4067 dol_syslog(get_class($this)."::addline status of invoice must be Draft to allow use of ->addline()", LOG_ERR);
4068 return -3;
4069 }
4070 }
4071
4103 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)
4104 {
4105 global $conf, $user;
4106 // Deprecation warning
4107 if ($label) {
4108 dol_syslog(__METHOD__.": using line label is deprecated", LOG_WARNING);
4109 }
4110
4111 include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
4112
4113 global $mysoc, $langs;
4114
4115 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);
4116
4117 if ($this->statut == self::STATUS_DRAFT) {
4118 if (!$this->is_last_in_cycle() && empty($this->error)) {
4119 if (!$this->checkProgressLine($rowid, $situation_percent)) {
4120 if (!$this->error) {
4121 $this->error = $langs->trans('invoiceLineProgressError');
4122 }
4123 return -3;
4124 }
4125 }
4126
4127 if ($date_start && $date_end && $date_start > $date_end) {
4128 $langs->load("errors");
4129 $this->error = $langs->trans('ErrorStartDateGreaterEnd');
4130 return -1;
4131 }
4132
4133 $this->db->begin();
4134
4135 // Clean parameters
4136 if (empty($qty)) {
4137 $qty = 0;
4138 }
4139 if (empty($fk_parent_line) || $fk_parent_line < 0) {
4140 $fk_parent_line = 0;
4141 }
4142 if (empty($special_code) || $special_code == 3) {
4143 $special_code = 0;
4144 }
4145 if (!isset($situation_percent) || $situation_percent > 100 || (string) $situation_percent == '') {
4146 $situation_percent = 100;
4147 }
4148 if (empty($ref_ext)) {
4149 $ref_ext = '';
4150 }
4151
4153 $qty = price2num($qty);
4154 $pu = price2num($pu);
4155 $pu_ht_devise = price2num($pu_ht_devise);
4156 $pa_ht = price2num($pa_ht);
4157 if (!preg_match('/\‍((.*)\‍)/', $txtva)) {
4158 $txtva = price2num($txtva); // $txtva can have format '5.0(XXX)' or '5'
4159 }
4160 $txlocaltax1 = price2num($txlocaltax1);
4161 $txlocaltax2 = price2num($txlocaltax2);
4162
4163 // Check parameters
4164 if ($type < 0) {
4165 return -1;
4166 }
4167
4168 // Calculate total with, without tax and tax from qty, pu, remise_percent and txtva
4169 // TRES IMPORTANT: C'est au moment de l'insertion ligne qu'on doit stocker
4170 // la part ht, tva et ttc, et ce au niveau de la ligne qui a son propre taux tva.
4171
4172 $localtaxes_type = getLocalTaxesFromRate($txtva, 0, $this->thirdparty, $mysoc);
4173
4174 // Clean vat code
4175 $reg = array();
4176 $vat_src_code = '';
4177 if (preg_match('/\‍((.*)\‍)/', $txtva, $reg)) {
4178 $vat_src_code = $reg[1];
4179 $txtva = preg_replace('/\s*\‍(.*\‍)/', '', $txtva); // Remove code into vatrate.
4180 }
4181
4182 $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);
4183
4184 $total_ht = $tabprice[0];
4185 $total_tva = $tabprice[1];
4186 $total_ttc = $tabprice[2];
4187 $total_localtax1 = $tabprice[9];
4188 $total_localtax2 = $tabprice[10];
4189 $pu_ht = $tabprice[3];
4190 $pu_tva = $tabprice[4];
4191 $pu_ttc = $tabprice[5];
4192
4193 // MultiCurrency
4194 $multicurrency_total_ht = $tabprice[16];
4195 $multicurrency_total_tva = $tabprice[17];
4196 $multicurrency_total_ttc = $tabprice[18];
4197 $pu_ht_devise = $tabprice[19];
4198
4199 // Old properties: $price, $remise (deprecated)
4200 $price = $pu;
4201 $remise = 0;
4202 if ($remise_percent > 0) {
4203 $remise = round(($pu * $remise_percent / 100), 2);
4204 $price = ($pu - $remise);
4205 }
4206 $price = price2num($price);
4207
4208 //Fetch current line from the database and then clone the object and set it in $oldline property
4209 $line = new FactureLigne($this->db);
4210 $line->fetch($rowid);
4211 $line->fetch_optionals();
4212
4213 if (!empty($line->fk_product)) {
4214 $product = new Product($this->db);
4215 $result = $product->fetch($line->fk_product);
4216 $product_type = $product->type;
4217
4218 if (!empty($conf->global->STOCK_MUST_BE_ENOUGH_FOR_INVOICE) && $product_type == 0 && $product->stock_reel < $qty) {
4219 $langs->load("errors");
4220 $this->error = $langs->trans('ErrorStockIsNotEnoughToAddProductOnInvoice', $product->ref);
4221 $this->db->rollback();
4222 return -3;
4223 }
4224 }
4225
4226 $staticline = clone $line;
4227
4228 $line->oldline = $staticline;
4229 $this->line = $line;
4230 $this->line->context = $this->context;
4231 $this->line->rang = $rang;
4232
4233 // Reorder if fk_parent_line change
4234 if (!empty($fk_parent_line) && !empty($staticline->fk_parent_line) && $fk_parent_line != $staticline->fk_parent_line) {
4235 $rangmax = $this->line_max($fk_parent_line);
4236 $this->line->rang = $rangmax + 1;
4237 }
4238 $apply_abs_price_on_credit_note=false;
4239 if ($this->type == self::TYPE_CREDIT_NOTE && !getDolGlobalInt('FACTURE_ENABLE_NEGATIVE_LINES') && !getDolGlobalInt('INVOICE_KEEP_DISCOUNT_LINES_AS_IN_ORIGIN')) {
4240 $apply_abs_price_on_credit_note = true;
4241 }
4242
4243
4244 $this->line->id = $rowid;
4245 $this->line->rowid = $rowid;
4246 $this->line->label = $label;
4247 $this->line->desc = $desc;
4248 $this->line->ref_ext = $ref_ext;
4249 $this->line->qty = ($this->type == self::TYPE_CREDIT_NOTE ?abs($qty) : $qty); // For credit note, quantity is always positive and unit price negative
4250
4251 $this->line->vat_src_code = $vat_src_code;
4252 $this->line->tva_tx = $txtva;
4253 $this->line->localtax1_tx = $txlocaltax1;
4254 $this->line->localtax2_tx = $txlocaltax2;
4255 $this->line->localtax1_type = empty($localtaxes_type[0]) ? '' : $localtaxes_type[0];
4256 $this->line->localtax2_type = empty($localtaxes_type[2]) ? '' : $localtaxes_type[2];
4257
4258 $this->line->remise_percent = $remise_percent;
4259 $this->line->subprice = ($apply_abs_price_on_credit_note?-abs($pu_ht) : $pu_ht); // For credit note, unit price always negative, always positive otherwise
4260 $this->line->date_start = $date_start;
4261 $this->line->date_end = $date_end;
4262 $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
4263 $this->line->total_tva = (($apply_abs_price_on_credit_note || $qty < 0) ?-abs($total_tva) : $total_tva);
4264 $this->line->total_localtax1 = $total_localtax1;
4265 $this->line->total_localtax2 = $total_localtax2;
4266 $this->line->total_ttc = (($apply_abs_price_on_credit_note || $qty < 0) ?-abs($total_ttc) : $total_ttc);
4267 $this->line->info_bits = $info_bits;
4268 $this->line->special_code = $special_code;
4269 $this->line->product_type = $type;
4270 $this->line->fk_parent_line = $fk_parent_line;
4271 $this->line->skip_update_total = $skip_update_total;
4272 $this->line->situation_percent = $situation_percent;
4273 $this->line->fk_unit = $fk_unit;
4274
4275 $this->line->fk_fournprice = $fk_fournprice;
4276 $this->line->pa_ht = $pa_ht;
4277
4278 // Multicurrency
4279 $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
4280 $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
4281 $this->line->multicurrency_total_tva = (($this->type == self::TYPE_CREDIT_NOTE || $qty < 0) ?-abs($multicurrency_total_tva) : $multicurrency_total_tva);
4282 $this->line->multicurrency_total_ttc = (($this->type == self::TYPE_CREDIT_NOTE || $qty < 0) ?-abs($multicurrency_total_ttc) : $multicurrency_total_ttc);
4283
4284 if (is_array($array_options) && count($array_options) > 0) {
4285 // We replace values in this->line->array_options only for entries defined into $array_options
4286 foreach ($array_options as $key => $value) {
4287 $this->line->array_options[$key] = $array_options[$key];
4288 }
4289 }
4290
4291 $result = $this->line->update($user, $notrigger);
4292 if ($result > 0) {
4293 // Reorder if child line
4294 if (!empty($fk_parent_line)) {
4295 $this->line_order(true, 'DESC');
4296 }
4297
4298 // Mise a jour info denormalisees au niveau facture
4299 $this->update_price(1, 'auto');
4300 $this->db->commit();
4301 return $result;
4302 } else {
4303 $this->error = $this->line->error;
4304 $this->db->rollback();
4305 return -1;
4306 }
4307 } else {
4308 $this->error = "Invoice statut makes operation forbidden";
4309 return -2;
4310 }
4311 }
4312
4320 public function checkProgressLine($idline, $situation_percent)
4321 {
4322 $sql = 'SELECT fd.situation_percent FROM '.MAIN_DB_PREFIX.'facturedet fd
4323 INNER JOIN '.MAIN_DB_PREFIX.'facture f ON (fd.fk_facture = f.rowid)
4324 WHERE fd.fk_prev_id = '.((int) $idline).' AND f.fk_statut <> 0';
4325
4326 $result = $this->db->query($sql);
4327 if (!$result) {
4328 $this->error = $this->db->error();
4329 return false;
4330 }
4331
4332 $obj = $this->db->fetch_object($result);
4333
4334 if ($obj === null) {
4335 return true;
4336 } else {
4337 return ($situation_percent < $obj->situation_percent);
4338 }
4339 }
4340
4341 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4350 public function update_percent($line, $percent, $update_price = true)
4351 {
4352 // phpcs:enable
4353 global $mysoc, $user;
4354
4355 // Progress should never be changed for discount lines
4356 if (($line->info_bits & 2) == 2) {
4357 return;
4358 }
4359
4360 include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
4361
4362 // Cap percentages to 100
4363 if ($percent > 100) {
4364 $percent = 100;
4365 }
4366 $line->situation_percent = $percent;
4367 $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);
4368 $line->total_ht = $tabprice[0];
4369 $line->total_tva = $tabprice[1];
4370 $line->total_ttc = $tabprice[2];
4371 $line->total_localtax1 = $tabprice[9];
4372 $line->total_localtax2 = $tabprice[10];
4373 $line->multicurrency_total_ht = $tabprice[16];
4374 $line->multicurrency_total_tva = $tabprice[17];
4375 $line->multicurrency_total_ttc = $tabprice[18];
4376 $line->update($user);
4377
4378 // sometimes it is better to not update price for each line, ie when updating situation on all lines
4379 if ($update_price) {
4380 $this->update_price(1);
4381 }
4382 }
4383
4391 public function deleteline($rowid, $id = 0)
4392 {
4393 global $user;
4394
4395 dol_syslog(get_class($this)."::deleteline rowid=".((int) $rowid), LOG_DEBUG);
4396
4397 if ($this->statut != self::STATUS_DRAFT) {
4398 $this->error = 'ErrorDeleteLineNotAllowedByObjectStatus';
4399 return -1;
4400 }
4401
4402 $line = new FactureLigne($this->db);
4403
4404 $line->context = $this->context;
4405
4406 // Load line
4407 $result = $line->fetch($rowid);
4408 if (!($result > 0)) {
4409 dol_print_error($this->db, $line->error, $line->errors);
4410 return -1;
4411 }
4412
4413 if ($id > 0 && $line->fk_facture != $id) {
4414 $this->error = 'ErrorLineIDDoesNotMatchWithObjectID';
4415 return -1;
4416 }
4417
4418 $this->db->begin();
4419
4420 // Memorize previous line for triggers
4421 $staticline = clone $line;
4422 $line->oldline = $staticline;
4423
4424 if ($line->delete($user) > 0) {
4425 $result = $this->update_price(1);
4426
4427 if ($result > 0) {
4428 $this->db->commit();
4429 return 1;
4430 } else {
4431 $this->db->rollback();
4432 $this->error = $this->db->lasterror();
4433 return -1;
4434 }
4435 } else {
4436 $this->db->rollback();
4437 $this->error = $line->error;
4438 return -1;
4439 }
4440 }
4441
4442 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4453 public function set_remise($user, $remise, $notrigger = 0)
4454 {
4455 // phpcs:enable
4456 dol_syslog(get_class($this)."::set_remise is deprecated, use setDiscount instead", LOG_NOTICE);
4457 return $this->setDiscount($user, $remise, $notrigger);
4458 }
4459
4469 public function setDiscount($user, $remise, $notrigger = 0)
4470 {
4471 // Clean parameters
4472 if (empty($remise)) {
4473 $remise = 0;
4474 }
4475
4476 if ($user->hasRight('facture', 'creer')) {
4477 $remise = price2num($remise, 2);
4478
4479 $error = 0;
4480
4481 $this->db->begin();
4482
4483 $sql = 'UPDATE '.MAIN_DB_PREFIX.'facture';
4484 $sql .= ' SET remise_percent = '.((float) $remise);
4485 $sql .= " WHERE rowid = ".((int) $this->id);
4486 $sql .= ' AND fk_statut = '.self::STATUS_DRAFT;
4487
4488 dol_syslog(__METHOD__, LOG_DEBUG);
4489 $resql = $this->db->query($sql);
4490 if (!$resql) {
4491 $this->errors[] = $this->db->error();
4492 $error++;
4493 }
4494
4495 if (!$notrigger && empty($error)) {
4496 // Call trigger
4497 $result = $this->call_trigger('BILL_MODIFY', $user);
4498 if ($result < 0) {
4499 $error++;
4500 }
4501 // End call triggers
4502 }
4503
4504 if (!$error) {
4505 $this->remise_percent = $remise;
4506 $this->update_price(1);
4507
4508 $this->db->commit();
4509 return 1;
4510 } else {
4511 foreach ($this->errors as $errmsg) {
4512 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
4513 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
4514 }
4515 $this->db->rollback();
4516 return -1 * $error;
4517 }
4518 }
4519
4520 return 0;
4521 }
4522
4523
4524 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4533 public function set_remise_absolue($user, $remise, $notrigger = 0)
4534 {
4535 // phpcs:enable
4536 if (empty($remise)) {
4537 $remise = 0;
4538 }
4539
4540 if ($user->hasRight('facture', 'creer')) {
4541 $error = 0;
4542
4543 $this->db->begin();
4544
4545 $remise = price2num($remise);
4546
4547 $sql = 'UPDATE '.MAIN_DB_PREFIX.'facture';
4548 $sql .= ' SET remise_absolue = '.((float) $remise);
4549 $sql .= " WHERE rowid = ".((int) $this->id);
4550 $sql .= ' AND fk_statut = '.self::STATUS_DRAFT;
4551
4552 dol_syslog(__METHOD__, LOG_DEBUG);
4553 $resql = $this->db->query($sql);
4554 if (!$resql) {
4555 $this->errors[] = $this->db->error();
4556 $error++;
4557 }
4558
4559 if (!$error) {
4560 $this->oldcopy = clone $this;
4561 $this->remise_absolue = $remise;
4562 $this->update_price(1);
4563 }
4564
4565 if (!$notrigger && empty($error)) {
4566 // Call trigger
4567 $result = $this->call_trigger('BILL_MODIFY', $user);
4568 if ($result < 0) {
4569 $error++;
4570 }
4571 // End call triggers
4572 }
4573
4574 if (!$error) {
4575 $this->db->commit();
4576 return 1;
4577 } else {
4578 foreach ($this->errors as $errmsg) {
4579 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
4580 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
4581 }
4582 $this->db->rollback();
4583 return -1 * $error;
4584 }
4585 }
4586
4587 return 0;
4588 }
4589
4598 public function getNextNumRef($soc, $mode = 'next')
4599 {
4600 global $conf, $langs;
4601
4602 if ($this->module_source == 'takepos') {
4603 $langs->load('cashdesk');
4604
4605 $moduleName = 'takepos';
4606 $moduleSourceName = 'Takepos';
4607 $addonConstName = 'TAKEPOS_REF_ADDON';
4608
4609 // Clean parameters (if not defined or using deprecated value)
4610 if (empty($conf->global->TAKEPOS_REF_ADDON)) {
4611 $conf->global->TAKEPOS_REF_ADDON = 'mod_takepos_ref_simple';
4612 }
4613
4614 $addon = $conf->global->TAKEPOS_REF_ADDON;
4615 } else {
4616 $langs->load('bills');
4617
4618 $moduleName = 'facture';
4619 $moduleSourceName = 'Invoice';
4620 $addonConstName = 'FACTURE_ADDON';
4621
4622 // Clean parameters (if not defined or using deprecated value)
4623 if (empty($conf->global->FACTURE_ADDON)) {
4624 $conf->global->FACTURE_ADDON = 'mod_facture_terre';
4625 } elseif ($conf->global->FACTURE_ADDON == 'terre') {
4626 $conf->global->FACTURE_ADDON = 'mod_facture_terre';
4627 } elseif ($conf->global->FACTURE_ADDON == 'mercure') {
4628 $conf->global->FACTURE_ADDON = 'mod_facture_mercure';
4629 }
4630
4631 $addon = $conf->global->FACTURE_ADDON;
4632 }
4633
4634 if (!empty($addon)) {
4635 dol_syslog("Call getNextNumRef with ".$addonConstName." = ".$conf->global->FACTURE_ADDON.", thirdparty=".$soc->name.", type=".$soc->typent_code.", mode=".$mode, LOG_DEBUG);
4636
4637 $mybool = false;
4638
4639 $file = $addon.'.php';
4640 $classname = $addon;
4641
4642
4643 // Include file with class
4644 $dirmodels = array_merge(array('/'), (array) $conf->modules_parts['models']);
4645 foreach ($dirmodels as $reldir) {
4646 $dir = dol_buildpath($reldir.'core/modules/'.$moduleName.'/');
4647
4648 // Load file with numbering class (if found)
4649 if (is_file($dir.$file) && is_readable($dir.$file)) {
4650 $mybool |= include_once $dir.$file;
4651 }
4652 }
4653
4654 // For compatibility
4655 if (!$mybool) {
4656 $file = $addon.'/'.$addon.'.modules.php';
4657 $classname = 'mod_'.$moduleName.'_'.$addon;
4658 $classname = preg_replace('/\-.*$/', '', $classname);
4659 // Include file with class
4660 foreach ($conf->file->dol_document_root as $dirroot) {
4661 $dir = $dirroot.'/core/modules/'.$moduleName.'/';
4662
4663 // Load file with numbering class (if found)
4664 if (is_file($dir.$file) && is_readable($dir.$file)) {
4665 $mybool |= include_once $dir.$file;
4666 }
4667 }
4668 }
4669
4670 if (!$mybool) {
4671 dol_print_error('', 'Failed to include file '.$file);
4672 return '';
4673 }
4674
4675 $obj = new $classname();
4676
4677 $numref = $obj->getNextValue($soc, $this, $mode);
4678
4679
4684 if ($mode != 'last' && !$numref) {
4685 $this->error = $obj->error;
4686 return '';
4687 }
4688
4689 return $numref;
4690 } else {
4691 $langs->load('errors');
4692 print $langs->trans('Error').' '.$langs->trans('ErrorModuleSetupNotComplete', $langs->transnoentitiesnoconv($moduleSourceName));
4693 return '';
4694 }
4695 }
4696
4703 public function info($id)
4704 {
4705 $sql = 'SELECT c.rowid, datec, date_valid as datev, tms as datem,';
4706 $sql .= ' date_closing as dateclosing,';
4707 $sql .= ' fk_user_author, fk_user_valid, fk_user_closing';
4708 $sql .= ' FROM '.MAIN_DB_PREFIX.'facture as c';
4709 $sql .= ' WHERE c.rowid = '.((int) $id);
4710
4711 $result = $this->db->query($sql);
4712 if ($result) {
4713 if ($this->db->num_rows($result)) {
4714 $obj = $this->db->fetch_object($result);
4715 $this->id = $obj->rowid;
4716 if ($obj->fk_user_author) {
4717 $cuser = new User($this->db);
4718 $cuser->fetch($obj->fk_user_author);
4719 $this->user_creation = $cuser;
4720 }
4721 if ($obj->fk_user_valid) {
4722 $vuser = new User($this->db);
4723 $vuser->fetch($obj->fk_user_valid);
4724 $this->user_validation = $vuser;
4725 }
4726 if ($obj->fk_user_closing) {
4727 $cluser = new User($this->db);
4728 $cluser->fetch($obj->fk_user_closing);
4729 $this->user_closing = $cluser;
4730 }
4731
4732 $this->date_creation = $this->db->jdate($obj->datec);
4733 $this->date_modification = $this->db->jdate($obj->datem);
4734 $this->date_validation = $this->db->jdate($obj->datev);
4735 $this->date_closing = $this->db->jdate($obj->dateclosing);
4736 }
4737 $this->db->free($result);
4738 } else {
4739 dol_print_error($this->db);
4740 }
4741 }
4742
4743
4744 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4758 public function liste_array($shortlist = 0, $draft = 0, $excluser = '', $socid = 0, $limit = 0, $offset = 0, $sortfield = 'f.datef,f.rowid', $sortorder = 'DESC')
4759 {
4760 // phpcs:enable
4761 global $conf, $user;
4762
4763 $ga = array();
4764
4765 $sql = "SELECT s.rowid, s.nom as name, s.client,";
4766 $sql .= " f.rowid as fid, f.ref as ref, f.datef as df";
4767 if (empty($user->rights->societe->client->voir) && !$socid) {
4768 $sql .= ", sc.fk_soc, sc.fk_user";
4769 }
4770 $sql .= " FROM ".MAIN_DB_PREFIX."societe as s, ".MAIN_DB_PREFIX."facture as f";
4771 if (empty($user->rights->societe->client->voir) && !$socid) {
4772 $sql .= ", ".MAIN_DB_PREFIX."societe_commerciaux as sc";
4773 }
4774 $sql .= " WHERE f.entity IN (".getEntity('invoice').")";
4775 $sql .= " AND f.fk_soc = s.rowid";
4776 if (empty($user->rights->societe->client->voir) && !$socid) { //restriction
4777 $sql .= " AND s.rowid = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
4778 }
4779 if ($socid) {
4780 $sql .= " AND s.rowid = ".((int) $socid);
4781 }
4782 if ($draft) {
4783 $sql .= " AND f.fk_statut = ".self::STATUS_DRAFT;
4784 }
4785 if (is_object($excluser)) {
4786 $sql .= " AND f.fk_user_author <> ".((int) $excluser->id);
4787 }
4788 $sql .= $this->db->order($sortfield, $sortorder);
4789 $sql .= $this->db->plimit($limit, $offset);
4790
4791 $result = $this->db->query($sql);
4792 if ($result) {
4793 $numc = $this->db->num_rows($result);
4794 if ($numc) {
4795 $i = 0;
4796 while ($i < $numc) {
4797 $obj = $this->db->fetch_object($result);
4798
4799 if ($shortlist == 1) {
4800 $ga[$obj->fid] = $obj->ref;
4801 } elseif ($shortlist == 2) {
4802 $ga[$obj->fid] = $obj->ref.' ('.$obj->name.')';
4803 } else {
4804 $ga[$i]['id'] = $obj->fid;
4805 $ga[$i]['ref'] = $obj->ref;
4806 $ga[$i]['name'] = $obj->name;
4807 }
4808 $i++;
4809 }
4810 }
4811 return $ga;
4812 } else {
4813 dol_print_error($this->db);
4814 return -1;
4815 }
4816 }
4817
4818
4819 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4828 public function list_replacable_invoices($socid = 0)
4829 {
4830 // phpcs:enable
4831 global $conf;
4832
4833 $return = array();
4834
4835 $sql = "SELECT f.rowid as rowid, f.ref, f.fk_statut as status, f.paye as paid,";
4836 $sql .= " ff.rowid as rowidnext";
4837 //$sql .= ", SUM(pf.amount) as alreadypaid";
4838 $sql .= " FROM ".MAIN_DB_PREFIX."facture as f";
4839 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."paiement_facture as pf ON f.rowid = pf.fk_facture";
4840 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."facture as ff ON f.rowid = ff.fk_facture_source";
4841 $sql .= " WHERE (f.fk_statut = ".self::STATUS_VALIDATED." OR (f.fk_statut = ".self::STATUS_ABANDONED." AND f.close_code = '".self::CLOSECODE_ABANDONED."'))";
4842 $sql .= " AND f.entity IN (".getEntity('invoice').")";
4843 $sql .= " AND f.paye = 0"; // Not paid completely
4844 $sql .= " AND pf.fk_paiement IS NULL"; // No payment already done
4845 $sql .= " AND ff.fk_statut IS NULL"; // Return true if it is not a replacement invoice
4846 if ($socid > 0) {
4847 $sql .= " AND f.fk_soc = ".((int) $socid);
4848 }
4849 //$sql .= " GROUP BY f.rowid, f.ref, f.fk_statut, f.paye, ff.rowid";
4850 $sql .= " ORDER BY f.ref";
4851
4852 dol_syslog(get_class($this)."::list_replacable_invoices", LOG_DEBUG);
4853 $resql = $this->db->query($sql);
4854 if ($resql) {
4855 while ($obj = $this->db->fetch_object($resql)) {
4856 $return[$obj->rowid] = array(
4857 'id' => $obj->rowid,
4858 'ref' => $obj->ref,
4859 'status' => $obj->status,
4860 'paid' => $obj->paid,
4861 'alreadypaid' => 0
4862 );
4863 }
4864 //print_r($return);
4865 return $return;
4866 } else {
4867 $this->error = $this->db->error();
4868 return -1;
4869 }
4870 }
4871
4872
4873 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4882 public function list_qualified_avoir_invoices($socid = 0)
4883 {
4884 // phpcs:enable
4885 global $conf;
4886
4887 $return = array();
4888
4889
4890 $sql = "SELECT f.rowid as rowid, f.ref, f.fk_statut, f.type, f.paye, pf.fk_paiement";
4891 $sql .= " FROM ".MAIN_DB_PREFIX."facture as f";
4892 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."paiement_facture as pf ON f.rowid = pf.fk_facture";
4893 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."facture as ff ON (f.rowid = ff.fk_facture_source AND ff.type=".self::TYPE_REPLACEMENT.")";
4894 $sql .= " WHERE f.entity IN (".getEntity('invoice').")";
4895 $sql .= " AND f.fk_statut in (".self::STATUS_VALIDATED.",".self::STATUS_CLOSED.")";
4896 // $sql.= " WHERE f.fk_statut >= 1";
4897 // $sql.= " AND (f.paye = 1"; // Classee payee completement
4898 // $sql.= " OR f.close_code IS NOT NULL)"; // Classee payee partiellement
4899 $sql .= " AND ff.type IS NULL"; // Renvoi vrai si pas facture de remplacement
4900 $sql .= " AND f.type <> ".self::TYPE_CREDIT_NOTE; // Exclude credit note invoices from selection
4901
4902 if (!empty($conf->global->INVOICE_USE_SITUATION_CREDIT_NOTE)) {
4903 // Keep invoices that are not situation invoices or that are the last in serie if it is a situation invoice
4904 $sql .= " AND (f.type <> ".self::TYPE_SITUATION." OR f.rowid IN ";
4905 $sql .= '(SELECT MAX(fs.rowid)'; // This select returns several ID becasue of the group by later
4906 $sql .= " FROM ".MAIN_DB_PREFIX."facture as fs";
4907 $sql .= " WHERE fs.entity IN (".getEntity('invoice').")";
4908 $sql .= " AND fs.type = ".self::TYPE_SITUATION;
4909 $sql .= " AND fs.fk_statut IN (".self::STATUS_VALIDATED.",".self::STATUS_CLOSED.")";
4910 if ($socid > 0) {
4911 $sql .= " AND fs.fk_soc = ".((int) $socid);
4912 }
4913 $sql .= " GROUP BY fs.situation_cycle_ref)"; // For each situation_cycle_ref, we take the higher rowid
4914 $sql .= ")";
4915 } else {
4916 $sql .= " AND f.type <> ".self::TYPE_SITUATION; // Keep invoices that are not situation invoices
4917 }
4918
4919 if ($socid > 0) {
4920 $sql .= " AND f.fk_soc = ".((int) $socid);
4921 }
4922 $sql .= " ORDER BY f.ref";
4923
4924 dol_syslog(get_class($this)."::list_qualified_avoir_invoices", LOG_DEBUG);
4925 $resql = $this->db->query($sql);
4926 if ($resql) {
4927 while ($obj = $this->db->fetch_object($resql)) {
4928 $qualified = 0;
4929 if ($obj->fk_statut == self::STATUS_VALIDATED) {
4930 $qualified = 1;
4931 }
4932 if ($obj->fk_statut == self::STATUS_CLOSED) {
4933 $qualified = 1;
4934 }
4935 if ($qualified) {
4936 //$ref=$obj->ref;
4937 $paymentornot = ($obj->fk_paiement ? 1 : 0);
4938 $return[$obj->rowid] = array('ref'=>$obj->ref, 'status'=>$obj->fk_statut, 'type'=>$obj->type, 'paye'=>$obj->paye, 'paymentornot'=>$paymentornot);
4939 }
4940 }
4941
4942 return $return;
4943 } else {
4944 $this->error = $this->db->error();
4945 return -1;
4946 }
4947 }
4948
4949
4950 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4957 public function load_board($user)
4958 {
4959 // phpcs:enable
4960 global $conf, $langs;
4961
4962 $clause = " WHERE";
4963
4964 $sql = "SELECT f.rowid, f.date_lim_reglement as datefin, f.fk_statut, f.total_ht";
4965 $sql .= " FROM ".MAIN_DB_PREFIX."facture as f";
4966 if (empty($user->rights->societe->client->voir) && !$user->socid) {
4967 $sql .= " JOIN ".MAIN_DB_PREFIX."societe_commerciaux as sc ON f.fk_soc = sc.fk_soc";
4968 $sql .= " WHERE sc.fk_user = ".((int) $user->id);
4969 $clause = " AND";
4970 }
4971 $sql .= $clause." f.paye=0";
4972 $sql .= " AND f.entity IN (".getEntity('invoice').")";
4973 $sql .= " AND f.fk_statut = ".self::STATUS_VALIDATED;
4974 if ($user->socid) {
4975 $sql .= " AND f.fk_soc = ".((int) $user->socid);
4976 }
4977
4978 $resql = $this->db->query($sql);
4979 if ($resql) {
4980 $langs->load("bills");
4981 $now = dol_now();
4982
4983 $response = new WorkboardResponse();
4984 $response->warning_delay = $conf->facture->client->warning_delay / 60 / 60 / 24;
4985 $response->label = $langs->trans("CustomerBillsUnpaid");
4986 $response->labelShort = $langs->trans("Unpaid");
4987 $response->url = DOL_URL_ROOT.'/compta/facture/list.php?search_status=1&mainmenu=billing&leftmenu=customers_bills';
4988 $response->img = img_object('', "bill");
4989
4990 $generic_facture = new Facture($this->db);
4991
4992 while ($obj = $this->db->fetch_object($resql)) {
4993 $generic_facture->date_lim_reglement = $this->db->jdate($obj->datefin);
4994 $generic_facture->statut = $obj->fk_statut;
4995
4996 $response->nbtodo++;
4997 $response->total += $obj->total_ht;
4998
4999 if ($generic_facture->hasDelay()) {
5000 $response->nbtodolate++;
5001 $response->url_late = DOL_URL_ROOT.'/compta/facture/list.php?search_option=late&mainmenu=billing&leftmenu=customers_bills';
5002 }
5003 }
5004
5005 $this->db->free($resql);
5006 return $response;
5007 } else {
5008 dol_print_error($this->db);
5009 $this->error = $this->db->error();
5010 return -1;
5011 }
5012 }
5013
5014
5015 /* gestion des contacts d'une facture */
5016
5022 public function getIdBillingContact()
5023 {
5024 return $this->getIdContact('external', 'BILLING');
5025 }
5026
5032 public function getIdShippingContact()
5033 {
5034 return $this->getIdContact('external', 'SHIPPING');
5035 }
5036
5037
5046 public function initAsSpecimen($option = '')
5047 {
5048 global $conf, $langs, $user;
5049
5050 $now = dol_now();
5051 $arraynow = dol_getdate($now);
5052 $nownotime = dol_mktime(0, 0, 0, $arraynow['mon'], $arraynow['mday'], $arraynow['year']);
5053
5054 // Load array of products prodids
5055 $num_prods = 0;
5056 $prodids = array();
5057 $sql = "SELECT rowid";
5058 $sql .= " FROM ".MAIN_DB_PREFIX."product";
5059 $sql .= " WHERE entity IN (".getEntity('product').")";
5060 $sql .= $this->db->plimit(100);
5061
5062 $resql = $this->db->query($sql);
5063 if ($resql) {
5064 $num_prods = $this->db->num_rows($resql);
5065 $i = 0;
5066 while ($i < $num_prods) {
5067 $i++;
5068 $row = $this->db->fetch_row($resql);
5069 $prodids[$i] = $row[0];
5070 }
5071 }
5072 //Avoid php warning Warning: mt_rand(): max(0) is smaller than min(1) when no product exists
5073 if (empty($num_prods)) {
5074 $num_prods = 1;
5075 }
5076
5077 // Initialize parameters
5078 $this->id = 0;
5079 $this->entity = 1;
5080 $this->ref = 'SPECIMEN';
5081 $this->specimen = 1;
5082 $this->socid = 1;
5083 $this->date = $nownotime;
5084 $this->date_lim_reglement = $nownotime + 3600 * 24 * 30;
5085 $this->cond_reglement_id = 1;
5086 $this->cond_reglement_code = 'RECEP';
5087 $this->date_lim_reglement = $this->calculate_date_lim_reglement();
5088 $this->mode_reglement_id = 0; // Not forced to show payment mode CHQ + VIR
5089 $this->mode_reglement_code = ''; // Not forced to show payment mode CHQ + VIR
5090
5091 $this->note_public = 'This is a comment (public)';
5092 $this->note_private = 'This is a comment (private)';
5093 $this->note = 'This is a comment (private)';
5094
5095 $this->fk_user_author = $user->id;
5096
5097 $this->multicurrency_tx = 1;
5098 $this->multicurrency_code = $conf->currency;
5099
5100 $this->fk_incoterms = 0;
5101 $this->location_incoterms = '';
5102
5103 if (empty($option) || $option != 'nolines') {
5104 // Lines
5105 $nbp = 5;
5106 $xnbp = 0;
5107 while ($xnbp < $nbp) {
5108 $line = new FactureLigne($this->db);
5109 $line->desc = $langs->trans("Description")." ".$xnbp;
5110 $line->qty = 1;
5111 $line->subprice = 100;
5112 $line->tva_tx = 19.6;
5113 $line->localtax1_tx = 0;
5114 $line->localtax2_tx = 0;
5115 $line->remise_percent = 0;
5116 if ($xnbp == 1) { // Qty is negative (product line)
5117 $prodid = mt_rand(1, $num_prods);
5118 $line->fk_product = $prodids[$prodid];
5119 $line->qty = -1;
5120 $line->total_ht = -100;
5121 $line->total_ttc = -119.6;
5122 $line->total_tva = -19.6;
5123 $line->multicurrency_total_ht = -200;
5124 $line->multicurrency_total_ttc = -239.2;
5125 $line->multicurrency_total_tva = -39.2;
5126 } elseif ($xnbp == 2) { // UP is negative (free line)
5127 $line->subprice = -100;
5128 $line->total_ht = -100;
5129 $line->total_ttc = -119.6;
5130 $line->total_tva = -19.6;
5131 $line->remise_percent = 0;
5132 $line->multicurrency_total_ht = -200;
5133 $line->multicurrency_total_ttc = -239.2;
5134 $line->multicurrency_total_tva = -39.2;
5135 } elseif ($xnbp == 3) { // Discount is 50% (product line)
5136 $prodid = mt_rand(1, $num_prods);
5137 $line->fk_product = $prodids[$prodid];
5138 $line->total_ht = 50;
5139 $line->total_ttc = 59.8;
5140 $line->total_tva = 9.8;
5141 $line->multicurrency_total_ht = 100;
5142 $line->multicurrency_total_ttc = 119.6;
5143 $line->multicurrency_total_tva = 19.6;
5144 $line->remise_percent = 50;
5145 } else // (product line)
5146 {
5147 $prodid = mt_rand(1, $num_prods);
5148 $line->fk_product = $prodids[$prodid];
5149 $line->total_ht = 100;
5150 $line->total_ttc = 119.6;
5151 $line->total_tva = 19.6;
5152 $line->multicurrency_total_ht = 200;
5153 $line->multicurrency_total_ttc = 239.2;
5154 $line->multicurrency_total_tva = 39.2;
5155 $line->remise_percent = 0;
5156 }
5157
5158 $this->lines[$xnbp] = $line;
5159
5160
5161 $this->total_ht += $line->total_ht;
5162 $this->total_tva += $line->total_tva;
5163 $this->total_ttc += $line->total_ttc;
5164
5165 $this->multicurrency_total_ht += $line->multicurrency_total_ht;
5166 $this->multicurrency_total_tva += $line->multicurrency_total_tva;
5167 $this->multicurrency_total_ttc += $line->multicurrency_total_ttc;
5168
5169 $xnbp++;
5170 }
5171 $this->revenuestamp = 0;
5172
5173 // Add a line "offered"
5174 $line = new FactureLigne($this->db);
5175 $line->desc = $langs->trans("Description")." (offered line)";
5176 $line->qty = 1;
5177 $line->subprice = 100;
5178 $line->tva_tx = 19.6;
5179 $line->localtax1_tx = 0;
5180 $line->localtax2_tx = 0;
5181 $line->remise_percent = 100;
5182 $line->total_ht = 0;
5183 $line->total_ttc = 0; // 90 * 1.196
5184 $line->total_tva = 0;
5185 $line->multicurrency_total_ht = 0;
5186 $line->multicurrency_total_ttc = 0;
5187 $line->multicurrency_total_tva = 0;
5188 $prodid = mt_rand(1, $num_prods);
5189 $line->fk_product = $prodids[$prodid];
5190
5191 $this->lines[$xnbp] = $line;
5192 $xnbp++;
5193 }
5194 }
5195
5196 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5202 public function load_state_board()
5203 {
5204 // phpcs:enable
5205 global $conf, $user;
5206
5207 $this->nb = array();
5208
5209 $clause = "WHERE";
5210
5211 $sql = "SELECT count(f.rowid) as nb";
5212 $sql .= " FROM ".MAIN_DB_PREFIX."facture as f";
5213 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe as s ON f.fk_soc = s.rowid";
5214 if (empty($user->rights->societe->client->voir) && !$user->socid) {
5215 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe_commerciaux as sc ON s.rowid = sc.fk_soc";
5216 $sql .= " WHERE sc.fk_user = ".((int) $user->id);
5217 $clause = "AND";
5218 }
5219 $sql .= " ".$clause." f.entity IN (".getEntity('invoice').")";
5220
5221 $resql = $this->db->query($sql);
5222 if ($resql) {
5223 while ($obj = $this->db->fetch_object($resql)) {
5224 $this->nb["invoices"] = $obj->nb;
5225 }
5226 $this->db->free($resql);
5227 return 1;
5228 } else {
5229 dol_print_error($this->db);
5230 $this->error = $this->db->error();
5231 return -1;
5232 }
5233 }
5234
5240 public function getLinesArray()
5241 {
5242 return $this->fetch_lines();
5243 }
5244
5256 public function generateDocument($modele, $outputlangs, $hidedetails = 0, $hidedesc = 0, $hideref = 0, $moreparams = null)
5257 {
5258 global $conf, $langs;
5259
5260 $outputlangs->loadLangs(array("bills", "products"));
5261
5262 if (!dol_strlen($modele)) {
5263 $modele = 'crabe';
5264 $thisTypeConfName = 'FACTURE_ADDON_PDF_'.$this->type;
5265
5266 if (!empty($this->model_pdf)) {
5267 $modele = $this->model_pdf;
5268 } elseif (!empty($this->modelpdf)) { // deprecated
5269 $modele = $this->modelpdf;
5270 } elseif (!empty($conf->global->$thisTypeConfName)) {
5271 $modele = $conf->global->$thisTypeConfName;
5272 } elseif (!empty($conf->global->FACTURE_ADDON_PDF)) {
5273 $modele = $conf->global->FACTURE_ADDON_PDF;
5274 }
5275 }
5276
5277 $modelpath = "core/modules/facture/doc/";
5278
5279 return $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams);
5280 }
5281
5287 public function newCycle()
5288 {
5289 $sql = 'SELECT max(situation_cycle_ref) FROM '.MAIN_DB_PREFIX.'facture as f';
5290 $sql .= " WHERE f.entity IN (".getEntity('invoice', 0).")";
5291 $resql = $this->db->query($sql);
5292 if ($resql) {
5293 if ($this->db->num_rows($resql) > 0) {
5294 $res = $this->db->fetch_array($resql);
5295 $ref = $res['max(situation_cycle_ref)'];
5296 $ref++;
5297 } else {
5298 $ref = 1;
5299 }
5300 $this->db->free($resql);
5301 return $ref;
5302 } else {
5303 $this->error = $this->db->lasterror();
5304 dol_syslog("Error sql=".$sql.", error=".$this->error, LOG_ERR);
5305 return -1;
5306 }
5307 }
5308
5309 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5315 public function is_first()
5316 {
5317 // phpcs:enable
5318 return ($this->situation_counter == 1);
5319 }
5320
5321 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5327 public function get_prev_sits()
5328 {
5329 // phpcs:enable
5330 global $conf;
5331
5332 $sql = 'SELECT rowid FROM '.MAIN_DB_PREFIX.'facture';
5333 $sql .= ' WHERE situation_cycle_ref = '.((int) $this->situation_cycle_ref);
5334 $sql .= ' AND situation_counter < '.((int) $this->situation_counter);
5335 $sql .= ' AND entity = '.($this->entity > 0 ? $this->entity : $conf->entity);
5336 $resql = $this->db->query($sql);
5337 $res = array();
5338 if ($resql && $this->db->num_rows($resql) > 0) {
5339 while ($row = $this->db->fetch_object($resql)) {
5340 $id = $row->rowid;
5341 $situation = new Facture($this->db);
5342 $situation->fetch($id);
5343 $res[] = $situation;
5344 }
5345 } else {
5346 $this->error = $this->db->error();
5347 dol_syslog("Error sql=".$sql.", error=".$this->error, LOG_ERR);
5348 return -1;
5349 }
5350
5351 return $res;
5352 }
5353
5361 public function setFinal(User $user, $notrigger = 0)
5362 {
5363 $error = 0;
5364
5365 $this->db->begin();
5366
5367 $sql = 'UPDATE '.MAIN_DB_PREFIX.'facture SET situation_final = '.((int) $this->situation_final).' WHERE rowid = '.((int) $this->id);
5368
5369 dol_syslog(__METHOD__, LOG_DEBUG);
5370 $resql = $this->db->query($sql);
5371 if (!$resql) {
5372 $this->errors[] = $this->db->error();
5373 $error++;
5374 }
5375
5376 if (!$notrigger && empty($error)) {
5377 // Call trigger
5378 $result = $this->call_trigger('BILL_MODIFY', $user);
5379 if ($result < 0) {
5380 $error++;
5381 }
5382 // End call triggers
5383 }
5384
5385 if (!$error) {
5386 $this->db->commit();
5387 return 1;
5388 } else {
5389 foreach ($this->errors as $errmsg) {
5390 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
5391 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
5392 }
5393 $this->db->rollback();
5394 return -1 * $error;
5395 }
5396 }
5397
5398 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5404 public function is_last_in_cycle()
5405 {
5406 // phpcs:enable
5407 global $conf;
5408
5409 if (!empty($this->situation_cycle_ref)) {
5410 // No point in testing anything if we're not inside a cycle
5411 $sql = 'SELECT max(situation_counter) FROM '.MAIN_DB_PREFIX.'facture';
5412 $sql .= ' WHERE situation_cycle_ref = '.((int) $this->situation_cycle_ref);
5413 $sql .= ' AND entity = '.($this->entity > 0 ? $this->entity : $conf->entity);
5414 $resql = $this->db->query($sql);
5415
5416 if ($resql && $this->db->num_rows($resql) > 0) {
5417 $res = $this->db->fetch_array($resql);
5418 $last = $res['max(situation_counter)'];
5419 return ($last == $this->situation_counter);
5420 } else {
5421 $this->error = $this->db->lasterror();
5422 dol_syslog(get_class($this)."::select Error ".$this->error, LOG_ERR);
5423 return false;
5424 }
5425 } else {
5426 return true;
5427 }
5428 }
5429
5438 public static function replaceThirdparty(DoliDB $dbs, $origin_id, $dest_id)
5439 {
5440 $tables = array(
5441 'facture'
5442 );
5443
5444 return CommonObject::commonReplaceThirdparty($dbs, $origin_id, $dest_id, $tables);
5445 }
5446
5455 public static function replaceProduct(DoliDB $db, $origin_id, $dest_id)
5456 {
5457 $tables = array(
5458 'facturedet'
5459 );
5460
5461 return CommonObject::commonReplaceProduct($db, $origin_id, $dest_id, $tables);
5462 }
5463
5469 public function hasDelay()
5470 {
5471 global $conf;
5472
5473 $now = dol_now();
5474
5475 // Paid invoices have status STATUS_CLOSED
5476 if ($this->statut != Facture::STATUS_VALIDATED) {
5477 return false;
5478 }
5479
5480 $hasDelay = $this->date_lim_reglement < ($now - $conf->facture->client->warning_delay);
5481 if ($hasDelay && !empty($this->retained_warranty) && !empty($this->retained_warranty_date_limit)) {
5482 $totalpaid = $this->getSommePaiement();
5483 $totalpaid = floatval($totalpaid);
5484 $RetainedWarrantyAmount = $this->getRetainedWarrantyAmount();
5485 if ($totalpaid >= 0 && $RetainedWarrantyAmount >= 0) {
5486 if (($totalpaid < $this->total_ttc - $RetainedWarrantyAmount) && $this->date_lim_reglement < ($now - $conf->facture->client->warning_delay)) {
5487 $hasDelay = 1;
5488 } elseif ($totalpaid < $this->total_ttc && $this->retained_warranty_date_limit < ($now - $conf->facture->client->warning_delay)) {
5489 $hasDelay = 1;
5490 } else {
5491 $hasDelay = 0;
5492 }
5493 }
5494 }
5495
5496 return $hasDelay;
5497 }
5498
5503 public function displayRetainedWarranty()
5504 {
5505 global $conf;
5506
5507 // TODO : add a flag on invoices to store this conf : INVOICE_RETAINED_WARRANTY_LIMITED_TO_FINAL_SITUATION
5508
5509 // 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
5510
5511 $displayWarranty = false;
5512 if (!empty($this->retained_warranty)) {
5513 $displayWarranty = true;
5514
5515 if ($this->type == Facture::TYPE_SITUATION && !empty($conf->global->INVOICE_RETAINED_WARRANTY_LIMITED_TO_FINAL_SITUATION)) {
5516 // Check if this situation invoice is 100% for real
5517 $displayWarranty = false;
5518 if (!empty($this->situation_final)) {
5519 $displayWarranty = true;
5520 } elseif (!empty($this->lines) && $this->status == Facture::STATUS_DRAFT) {
5521 // $object->situation_final need validation to be done so this test is need for draft
5522 $displayWarranty = true;
5523
5524 foreach ($this->lines as $i => $line) {
5525 if ($line->product_type < 2 && $line->situation_percent < 100) {
5526 $displayWarranty = false;
5527 break;
5528 }
5529 }
5530 }
5531 }
5532 }
5533
5534 return $displayWarranty;
5535 }
5536
5541 public function getRetainedWarrantyAmount($rounding = -1)
5542 {
5543 global $conf;
5544 if (empty($this->retained_warranty)) {
5545 return -1;
5546 }
5547
5548 $retainedWarrantyAmount = 0;
5549
5550 // Billed - retained warranty
5551 if ($this->type == Facture::TYPE_SITUATION && !empty($conf->global->INVOICE_RETAINED_WARRANTY_LIMITED_TO_FINAL_SITUATION)) {
5552 $displayWarranty = true;
5553 // Check if this situation invoice is 100% for real
5554 if (!empty($this->lines)) {
5555 foreach ($this->lines as $i => $line) {
5556 if ($line->product_type < 2 && $line->situation_percent < 100) {
5557 $displayWarranty = false;
5558 break;
5559 }
5560 }
5561 }
5562
5563 if ($displayWarranty && !empty($this->situation_final)) {
5565 $TPreviousIncoice = $this->tab_previous_situation_invoice;
5566
5567 $total2BillWT = 0;
5568 foreach ($TPreviousIncoice as &$fac) {
5569 $total2BillWT += $fac->total_ttc;
5570 }
5571 $total2BillWT += $this->total_ttc;
5572
5573 $retainedWarrantyAmount = $total2BillWT * $this->retained_warranty / 100;
5574 } else {
5575 return -1;
5576 }
5577 } else {
5578 // Because one day retained warranty could be used on standard invoices
5579 $retainedWarrantyAmount = $this->total_ttc * $this->retained_warranty / 100;
5580 }
5581
5582 if ($rounding < 0) {
5583 $rounding = min($conf->global->MAIN_MAX_DECIMALS_UNIT, $conf->global->MAIN_MAX_DECIMALS_TOT);
5584 }
5585
5586 if ($rounding > 0) {
5587 return round($retainedWarrantyAmount, $rounding);
5588 }
5589
5590 return $retainedWarrantyAmount;
5591 }
5592
5599 public function setRetainedWarranty($value)
5600 {
5601 dol_syslog(get_class($this).'::setRetainedWarranty('.$value.')');
5602
5603 if ($this->statut >= 0) {
5604 $fieldname = 'retained_warranty';
5605 $sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element;
5606 $sql .= " SET ".$fieldname." = ".((float) $value);
5607 $sql .= ' WHERE rowid='.((int) $this->id);
5608
5609 if ($this->db->query($sql)) {
5610 $this->retained_warranty = floatval($value);
5611 return 1;
5612 } else {
5613 dol_syslog(get_class($this).'::setRetainedWarranty Erreur '.$sql.' - '.$this->db->error());
5614 $this->error = $this->db->error();
5615 return -1;
5616 }
5617 } else {
5618 dol_syslog(get_class($this).'::setRetainedWarranty, status of the object is incompatible');
5619 $this->error = 'Status of the object is incompatible '.$this->statut;
5620 return -2;
5621 }
5622 }
5623
5624
5632 public function setRetainedWarrantyDateLimit($timestamp, $dateYmd = false)
5633 {
5634 if (!$timestamp && $dateYmd) {
5635 $timestamp = $this->db->jdate($dateYmd);
5636 }
5637
5638
5639 dol_syslog(get_class($this).'::setRetainedWarrantyDateLimit('.$timestamp.')');
5640 if ($this->statut >= 0) {
5641 $fieldname = 'retained_warranty_date_limit';
5642 $sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element;
5643 $sql .= " SET ".$fieldname." = ".(strval($timestamp) != '' ? "'".$this->db->idate($timestamp)."'" : 'null');
5644 $sql .= ' WHERE rowid = '.((int) $this->id);
5645
5646 if ($this->db->query($sql)) {
5647 $this->retained_warranty_date_limit = $timestamp;
5648 return 1;
5649 } else {
5650 dol_syslog(get_class($this).'::setRetainedWarrantyDateLimit Erreur '.$sql.' - '.$this->db->error());
5651 $this->error = $this->db->error();
5652 return -1;
5653 }
5654 } else {
5655 dol_syslog(get_class($this).'::setRetainedWarrantyDateLimit, status of the object is incompatible');
5656 $this->error = 'Status of the object is incompatible '.$this->statut;
5657 return -2;
5658 }
5659 }
5660
5661
5673 public function sendEmailsRemindersOnInvoiceDueDate($nbdays = 0, $paymentmode = 'all', $template = '', $datetouse = 'duedate', $forcerecipient = '')
5674 {
5675 global $conf, $langs, $user;
5676
5677 $error = 0;
5678 $this->output = '';
5679 $this->error = '';
5680 $nbMailSend = 0;
5681 $errorsMsg = array();
5682
5683 $langs->load("bills");
5684
5685 if (!isModEnabled('facture')) { // Should not happen. If module disabled, cron job should not be visible.
5686 $this->output .= $langs->trans('ModuleNotEnabled', $langs->transnoentitiesnoconv("Facture"));
5687 return 0;
5688 }
5689 if (!in_array($datetouse, array('duedate', 'invoicedate'))) {
5690 $this->output .= 'Bad value for parameter datetouse. Must be "duedate" or "invoicedate"';
5691 return 0;
5692 }
5693 /*if (empty($conf->global->FACTURE_REMINDER_EMAIL)) {
5694 $langs->load("bills");
5695 $this->output .= $langs->trans('EventRemindersByEmailNotEnabled', $langs->transnoentitiesnoconv("Facture"));
5696 return 0;
5697 }
5698 */
5699
5700 require_once DOL_DOCUMENT_ROOT.'/core/lib/date.lib.php';
5701 require_once DOL_DOCUMENT_ROOT.'/core/class/html.formmail.class.php';
5702 require_once DOL_DOCUMENT_ROOT.'/core/class/CMailFile.class.php';
5703 $formmail = new FormMail($this->db);
5704
5705 $now = dol_now();
5706 $tmpidate = dol_get_first_hour(dol_time_plus_duree($now, $nbdays, 'd'), 'gmt');
5707
5708 $tmpinvoice = new Facture($this->db);
5709
5710 dol_syslog(__METHOD__." start", LOG_INFO);
5711
5712 // Select all action comm reminder
5713 $sql = "SELECT rowid as id FROM ".MAIN_DB_PREFIX."facture as f";
5714 if (!empty($paymentmode) && $paymentmode != 'all') {
5715 $sql .= ", ".MAIN_DB_PREFIX."c_paiement as cp";
5716 }
5717 $sql .= " WHERE f.paye = 0"; // Only unpaid
5718 $sql .= " AND f.fk_statut = ".self::STATUS_VALIDATED; // Only validated status
5719 if ($datetouse == 'invoicedate') {
5720 $sql .= " AND f.datef = '".$this->db->idate($tmpidate, 'gmt')."'";
5721 } else {
5722 $sql .= " AND f.date_lim_reglement = '".$this->db->idate($tmpidate, 'gmt')."'";
5723 }
5724 $sql .= " AND f.entity IN (".getEntity('facture', 0).")"; // One batch process only one company (no sharing)
5725 if (!empty($paymentmode) && $paymentmode != 'all') {
5726 $sql .= " AND f.fk_mode_reglement = cp.id AND cp.code = '".$this->db->escape($paymentmode)."'";
5727 }
5728 // TODO Add a filter to check there is no payment started yet
5729 if ($datetouse == 'invoicedate') {
5730 $sql .= $this->db->order("datef", "ASC");
5731 } else {
5732 $sql .= $this->db->order("date_lim_reglement", "ASC");
5733 }
5734
5735 $resql = $this->db->query($sql);
5736
5737 $stmpidate = dol_print_date($tmpidate, 'day', 'gmt');
5738 if ($datetouse == 'invoicedate') {
5739 $this->output .= $langs->transnoentitiesnoconv("SearchValidatedInvoicesWithDate", $stmpidate);
5740 } else {
5741 $this->output .= $langs->transnoentitiesnoconv("SearchUnpaidInvoicesWithDueDate", $stmpidate);
5742 }
5743 if (!empty($paymentmode) && $paymentmode != 'all') {
5744 $this->output .= ' ('.$langs->transnoentitiesnoconv("PaymentMode").' '.$paymentmode.')';
5745 }
5746 $this->output .= '<br>';
5747
5748 if ($resql) {
5749 while ($obj = $this->db->fetch_object($resql)) {
5750 if (!$error) {
5751 // Load event
5752 $res = $tmpinvoice->fetch($obj->id);
5753 if ($res > 0) {
5754 $tmpinvoice->fetch_thirdparty();
5755
5756 $outputlangs = new Translate('', $conf);
5757 if ($tmpinvoice->thirdparty->default_lang) {
5758 $outputlangs->setDefaultLang($tmpinvoice->thirdparty->default_lang);
5759 $outputlangs->loadLangs(array("main", "bills"));
5760 } else {
5761 $outputlangs = $langs;
5762 }
5763
5764 // Select email template according to language of recipient
5765 $arraymessage = $formmail->getEMailTemplate($this->db, 'facture_send', $user, $outputlangs, (is_numeric($template) ? $template : 0), 1, (is_numeric($template) ? '' : $template));
5766 if (is_numeric($arraymessage) && $arraymessage <= 0) {
5767 $langs->load("errors");
5768 $this->output .= $langs->trans('ErrorFailedToFindEmailTemplate', $template);
5769 return 0;
5770 }
5771
5772 // PREPARE EMAIL
5773 $errormesg = '';
5774
5775 // Make substitution in email content
5776 $substitutionarray = getCommonSubstitutionArray($outputlangs, 0, '', $tmpinvoice);
5777
5778 complete_substitutions_array($substitutionarray, $outputlangs, $tmpinvoice);
5779
5780 // Topic
5781 $sendTopic = make_substitutions(empty($arraymessage->topic) ? $outputlangs->transnoentitiesnoconv('InformationMessage') : $arraymessage->topic, $substitutionarray, $outputlangs, 1);
5782
5783 // Content
5784 $content = $outputlangs->transnoentitiesnoconv($arraymessage->content);
5785
5786 $sendContent = make_substitutions($content, $substitutionarray, $outputlangs, 1);
5787
5788 // Recipient
5789 $to = array();
5790 if ($forcerecipient) { // If a recipient was forced
5791 $to = array($forcerecipient);
5792 } else {
5793 $res = $tmpinvoice->fetch_thirdparty();
5794 $recipient = $tmpinvoice->thirdparty;
5795 if ($res > 0) {
5796 $tmparraycontact = $tmpinvoice->liste_contact(-1, 'external', 0, 'BILLING');
5797 if (is_array($tmparraycontact) && count($tmparraycontact) > 0) {
5798 foreach ($tmparraycontact as $data_email) {
5799 if (!empty($data_email['email'])) {
5800 $to[] = $tmpinvoice->thirdparty->contact_get_property($data_email['id'], 'email');
5801 }
5802 }
5803 }
5804 if (empty($to) && !empty($recipient->email)) {
5805 $to[] = $recipient->email;
5806 }
5807 if (empty($to)) {
5808 $errormesg = "Failed to send remind to thirdparty id=".$tmpinvoice->socid.". No email defined for invoice or customer.";
5809 $error++;
5810 }
5811 } else {
5812 $errormesg = "Failed to load recipient with thirdparty id=".$tmpinvoice->socid;
5813 $error++;
5814 }
5815 }
5816
5817 // Sender
5818 $from = getDolGlobalString('MAIN_MAIL_EMAIL_FROM');
5819 if (!empty($arraymessage->email_from)) { // If a sender is defined into template, we use it in priority
5820 $from = $arraymessage->email_from;
5821 }
5822 if (empty($from)) {
5823 $errormesg = "Failed to get sender into global setup MAIN_MAIL_EMAIL_FROM";
5824 $error++;
5825 }
5826
5827 if (!$error && !empty($to)) {
5828 $this->db->begin();
5829
5830 $to = implode(',', $to);
5831 if (!empty($arraymessage->email_to)) { // If a recipient is defined into template, we add it
5832 $to = $to.','.$arraymessage->email_to;
5833 }
5834
5835 // Errors Recipient
5836 $errors_to = $conf->global->MAIN_MAIL_ERRORS_TO;
5837
5838 $trackid = 'inv'.$tmpinvoice->id;
5839 $sendcontext = 'standard';
5840
5841 $email_tocc = '';
5842 if (!empty($arraymessage->email_tocc)) { // If a CC is defined into template, we use it
5843 $email_tocc = $arraymessage->email_tocc;
5844 }
5845
5846 $email_tobcc = '';
5847 if (!empty($arraymessage->email_tobcc)) { // If a BCC is defined into template, we use it
5848 $email_tobcc = $arraymessage->email_tobcc;
5849 }
5850
5851 //join file is asked
5852 $joinFile = [];
5853 $joinFileName = [];
5854 $joinFileMime = [];
5855 if ($arraymessage->joinfiles == 1 && !empty($tmpinvoice->last_main_doc)) {
5856 $joinFile[] = DOL_DATA_ROOT.'/'.$tmpinvoice->last_main_doc;
5857 $joinFileName[] = basename($tmpinvoice->last_main_doc);
5858 $joinFileMime[] = dol_mimetype(DOL_DATA_ROOT.'/'.$tmpinvoice->last_main_doc);
5859 }
5860
5861 // Mail Creation
5862 $cMailFile = new CMailFile($sendTopic, $to, $from, $sendContent, $joinFile, $joinFileMime, $joinFileName, $email_tocc, $email_tobcc, 0, 1, $errors_to, '', $trackid, '', $sendcontext, '');
5863
5864 // Sending Mail
5865 if ($cMailFile->sendfile()) {
5866 $nbMailSend++;
5867
5868 // Add a line into event table
5869 require_once DOL_DOCUMENT_ROOT.'/comm/action/class/actioncomm.class.php';
5870
5871 // Insert record of emails sent
5872 $actioncomm = new ActionComm($this->db);
5873
5874 $actioncomm->type_code = 'AC_OTH_AUTO'; // Event insert into agenda automatically
5875 $actioncomm->socid = $tmpinvoice->thirdparty->id; // To link to a company
5876 $actioncomm->contact_id = 0;
5877
5878 $actioncomm->code = 'AC_EMAIL';
5879 $actioncomm->label = 'sendEmailsRemindersOnInvoiceDueDateOK (nbdays='.$nbdays.' paymentmode='.$paymentmode.' template='.$template.' datetouse='.$datetouse.' forcerecipient='.$forcerecipient.')';
5880 $actioncomm->note_private = $sendContent;
5881 $actioncomm->fk_project = $tmpinvoice->fk_project;
5882 $actioncomm->datep = dol_now();
5883 $actioncomm->datef = $actioncomm->datep;
5884 $actioncomm->percentage = -1; // Not applicable
5885 $actioncomm->authorid = $user->id; // User saving action
5886 $actioncomm->userownerid = $user->id; // Owner of action
5887 // Fields when action is an email (content should be added into note)
5888 $actioncomm->email_msgid = $cMailFile->msgid;
5889 $actioncomm->email_subject = $sendTopic;
5890 $actioncomm->email_from = $from;
5891 $actioncomm->email_sender = '';
5892 $actioncomm->email_to = $to;
5893 //$actioncomm->email_tocc = $sendtocc;
5894 //$actioncomm->email_tobcc = $sendtobcc;
5895 //$actioncomm->email_subject = $subject;
5896 $actioncomm->errors_to = $errors_to;
5897
5898 $actioncomm->elementtype = 'invoice';
5899 $actioncomm->fk_element = $tmpinvoice->id;
5900
5901 //$actioncomm->extraparams = $extraparams;
5902
5903 $actioncomm->create($user);
5904 } else {
5905 $errormesg = $cMailFile->error.' : '.$to;
5906 $error++;
5907
5908 // Add a line into event table
5909 require_once DOL_DOCUMENT_ROOT.'/comm/action/class/actioncomm.class.php';
5910
5911 // Insert record of emails sent
5912 $actioncomm = new ActionComm($this->db);
5913
5914 $actioncomm->type_code = 'AC_OTH_AUTO'; // Event insert into agenda automatically
5915 $actioncomm->socid = $tmpinvoice->thirdparty->id; // To link to a company
5916 $actioncomm->contact_id = 0;
5917
5918 $actioncomm->code = 'AC_EMAIL';
5919 $actioncomm->label = 'sendEmailsRemindersOnInvoiceDueDateKO';
5920 $actioncomm->note_private = $errormesg;
5921 $actioncomm->fk_project = $tmpinvoice->fk_project;
5922 $actioncomm->datep = dol_now();
5923 $actioncomm->datef = $actioncomm->datep;
5924 $actioncomm->percentage = -1; // Not applicable
5925 $actioncomm->authorid = $user->id; // User saving action
5926 $actioncomm->userownerid = $user->id; // Owner of action
5927 // Fields when action is an email (content should be added into note)
5928 $actioncomm->email_msgid = $cMailFile->msgid;
5929 $actioncomm->email_from = $from;
5930 $actioncomm->email_sender = '';
5931 $actioncomm->email_to = $to;
5932 //$actioncomm->email_tocc = $sendtocc;
5933 //$actioncomm->email_tobcc = $sendtobcc;
5934 //$actioncomm->email_subject = $subject;
5935 $actioncomm->errors_to = $errors_to;
5936
5937 //$actioncomm->extraparams = $extraparams;
5938
5939 $actioncomm->create($user);
5940 }
5941
5942 $this->db->commit(); // We always commit
5943 }
5944
5945 if ($errormesg) {
5946 $errorsMsg[] = $errormesg;
5947 }
5948 } else {
5949 $errorsMsg[] = 'Failed to fetch record invoice with ID = '.$obj->id;
5950 $error++;
5951 }
5952 }
5953 }
5954 } else {
5955 $error++;
5956 }
5957
5958 if (!$error) {
5959 $this->output .= 'Nb of emails sent : '.$nbMailSend;
5960
5961 dol_syslog(__METHOD__." end - ".$this->output, LOG_INFO);
5962
5963 return 0;
5964 } else {
5965 $this->error = 'Nb of emails sent : '.$nbMailSend.', '.(!empty($errorsMsg)) ? join(', ', $errorsMsg) : $error;
5966
5967 dol_syslog(__METHOD__." end - ".$this->error, LOG_INFO);
5968
5969 return $error;
5970 }
5971 }
5972
5979 public function willBeLastOfSameType($allow_validated_drafts = false)
5980 {
5981 // get date of last validated invoices of same type
5982 $sql = "SELECT datef";
5983 $sql .= " FROM ".MAIN_DB_PREFIX."facture";
5984 $sql .= " WHERE type = " . (int) $this->type ;
5985 $sql .= " AND date_valid IS NOT NULL";
5986 $sql .= " AND entity IN (".getEntity('invoice').")";
5987 $sql .= " ORDER BY datef DESC LIMIT 1";
5988
5989 $result = $this->db->query($sql);
5990 if ($result) {
5991 // compare with current validation date
5992 if ($this->db->num_rows($result)) {
5993 $obj = $this->db->fetch_object($result);
5994 $last_date = $this->db->jdate($obj->datef);
5995 $invoice_date = $this->date;
5996
5997 $is_last_of_same_type = $invoice_date >= $last_date;
5998 if ($allow_validated_drafts) {
5999 $is_last_of_same_type = $is_last_of_same_type || (!strpos($this->ref, 'PROV') && $this->status == self::STATUS_DRAFT);
6000 }
6001
6002 return array($is_last_of_same_type, $last_date);
6003 } else {
6004 // element is first of type to be validated
6005 return array(true);
6006 }
6007 } else {
6008 dol_print_error($this->db);
6009 }
6010
6011 return array();
6012 }
6013
6021 public function getKanbanView($option = '', $arraydata = null)
6022 {
6023 $selected = (empty($arraydata['selected']) ? 0 : $arraydata['selected']);
6024
6025 $return = '<div class="box-flex-item box-flex-grow-zero">';
6026 $return .= '<div class="info-box info-box-sm">';
6027 $return .= '<span class="info-box-icon bg-infobox-action">';
6028 $return .= img_picto('', $this->picto);
6029 //$return .= '<i class="fa fa-dol-action"></i>'; // Can be image
6030 $return .= '</span>';
6031 $return .= '<div class="info-box-content">';
6032 $return .= '<span class="info-box-ref inline-block tdoverflowmax150 valignmiddle">'.(method_exists($this, 'getNomUrl') ? $this->getNomUrl(1) : $this->ref).'</span>';
6033 $return .= '<input id="cb'.$this->id.'" class="flat checkforselect fright" type="checkbox" name="toselect[]" value="'.$this->id.'"'.($selected ? ' checked="checked"' : '').'>';
6034 if (property_exists($this, 'socid')) {
6035 $return .= '<br><span class="info-box-label">'.$this->socid.'</span>';
6036 }
6037 if (property_exists($this, 'fk_user_author')) {
6038 $return .= '<br><span class="info-box-label">'.$this->fk_user_author.'</span>';
6039 }
6040 if (method_exists($this, 'getLibStatut')) {
6041 $return .= '<br><div class="info-box-status margintoponly">'.$this->getLibStatut(3).'</div>';
6042 }
6043 $return .= '</div>';
6044 $return .= '</div>';
6045 $return .= '</div>';
6046 return $return;
6047 }
6048}
6049
6055{
6059 public $element = 'facturedet';
6060
6064 public $table_element = 'facturedet';
6065
6069 public $oldline;
6070
6076
6078 public $desc;
6079 public $ref_ext; // External reference of the line
6080
6081 public $localtax1_type; // Local tax 1 type
6082 public $localtax2_type; // Local tax 2 type
6083 public $fk_remise_except; // Link to line into llx_remise_except
6084 public $rang = 0;
6085
6086 public $fk_fournprice;
6087 public $pa_ht;
6088 public $marge_tx;
6089 public $marque_tx;
6090
6091 public $remise_percent;
6092
6093 public $special_code; // Liste d'options non cumulabels:
6094 // 1: frais de port
6095 // 2: ecotaxe
6096 // 3: ??
6097
6098 public $origin;
6099 public $origin_id;
6100
6101 public $fk_code_ventilation = 0;
6102
6103 public $date_start;
6104 public $date_end;
6105
6106 public $skip_update_total; // Skip update price total for special lines
6107
6111 public $situation_percent;
6112
6116 public $fk_prev_id;
6117
6118 // Multicurrency
6119 public $fk_multicurrency;
6120 public $multicurrency_code;
6121 public $multicurrency_subprice;
6122 public $multicurrency_total_ht;
6123 public $multicurrency_total_tva;
6124 public $multicurrency_total_ttc;
6125
6131 public function __construct($db)
6132 {
6133 $this->db = $db;
6134 }
6135
6142 public function fetch($rowid)
6143 {
6144 $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,';
6145 $sql .= ' fd.localtax1_tx, fd. localtax2_tx, fd.remise, fd.remise_percent, fd.fk_remise_except, fd.subprice, fd.ref_ext,';
6146 $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,';
6147 $sql .= ' fd.info_bits, fd.special_code, fd.total_ht, fd.total_tva, fd.total_ttc, fd.total_localtax1, fd.total_localtax2, fd.rang,';
6148 $sql .= ' fd.fk_code_ventilation,';
6149 $sql .= ' fd.fk_unit, fd.fk_user_author, fd.fk_user_modif,';
6150 $sql .= ' fd.situation_percent, fd.fk_prev_id,';
6151 $sql .= ' fd.multicurrency_subprice,';
6152 $sql .= ' fd.multicurrency_total_ht,';
6153 $sql .= ' fd.multicurrency_total_tva,';
6154 $sql .= ' fd.multicurrency_total_ttc,';
6155 $sql .= ' p.ref as product_ref, p.label as product_label, p.description as product_desc';
6156 $sql .= ' FROM '.MAIN_DB_PREFIX.'facturedet as fd';
6157 $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'product as p ON fd.fk_product = p.rowid';
6158 $sql .= ' WHERE fd.rowid = '.((int) $rowid);
6159
6160 $result = $this->db->query($sql);
6161 if ($result) {
6162 $objp = $this->db->fetch_object($result);
6163
6164 if (!$objp) {
6165 $this->error = 'InvoiceLine with id '. $rowid .' not found sql='.$sql;
6166 return 0;
6167 }
6168
6169 $this->rowid = $objp->rowid;
6170 $this->id = $objp->rowid;
6171 $this->fk_facture = $objp->fk_facture;
6172 $this->fk_parent_line = $objp->fk_parent_line;
6173 $this->label = $objp->custom_label;
6174 $this->desc = $objp->description;
6175 $this->qty = $objp->qty;
6176 $this->subprice = $objp->subprice;
6177 $this->ref_ext = $objp->ref_ext;
6178 $this->vat_src_code = $objp->vat_src_code;
6179 $this->tva_tx = $objp->tva_tx;
6180 $this->localtax1_tx = $objp->localtax1_tx;
6181 $this->localtax2_tx = $objp->localtax2_tx;
6182 $this->remise_percent = $objp->remise_percent;
6183 $this->fk_remise_except = $objp->fk_remise_except;
6184 $this->fk_product = $objp->fk_product;
6185 $this->product_type = $objp->product_type;
6186 $this->date_start = $this->db->jdate($objp->date_start);
6187 $this->date_end = $this->db->jdate($objp->date_end);
6188 $this->info_bits = $objp->info_bits;
6189 $this->tva_npr = ($objp->info_bits & 1 == 1) ? 1 : 0;
6190 $this->special_code = $objp->special_code;
6191 $this->total_ht = $objp->total_ht;
6192 $this->total_tva = $objp->total_tva;
6193 $this->total_localtax1 = $objp->total_localtax1;
6194 $this->total_localtax2 = $objp->total_localtax2;
6195 $this->total_ttc = $objp->total_ttc;
6196 $this->fk_code_ventilation = $objp->fk_code_ventilation;
6197 $this->rang = $objp->rang;
6198 $this->fk_fournprice = $objp->fk_fournprice;
6199 $marginInfos = getMarginInfos($objp->subprice, $objp->remise_percent, $objp->tva_tx, $objp->localtax1_tx, $objp->localtax2_tx, $this->fk_fournprice, $objp->pa_ht);
6200 $this->pa_ht = $marginInfos[0];
6201 $this->marge_tx = $marginInfos[1];
6202 $this->marque_tx = $marginInfos[2];
6203
6204 $this->ref = $objp->product_ref; // deprecated
6205
6206 $this->product_ref = $objp->product_ref;
6207 $this->product_label = $objp->product_label;
6208 $this->product_desc = $objp->product_desc;
6209
6210 $this->fk_unit = $objp->fk_unit;
6211 $this->fk_user_modif = $objp->fk_user_modif;
6212 $this->fk_user_author = $objp->fk_user_author;
6213
6214 $this->situation_percent = $objp->situation_percent;
6215 $this->fk_prev_id = $objp->fk_prev_id;
6216
6217 $this->multicurrency_subprice = $objp->multicurrency_subprice;
6218 $this->multicurrency_total_ht = $objp->multicurrency_total_ht;
6219 $this->multicurrency_total_tva = $objp->multicurrency_total_tva;
6220 $this->multicurrency_total_ttc = $objp->multicurrency_total_ttc;
6221
6222 $this->fetch_optionals();
6223
6224 $this->db->free($result);
6225
6226 return 1;
6227 } else {
6228 $this->error = $this->db->lasterror();
6229 return -1;
6230 }
6231 }
6232
6240 public function insert($notrigger = 0, $noerrorifdiscountalreadylinked = 0)
6241 {
6242 global $langs, $user, $conf;
6243
6244 $error = 0;
6245
6246 $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'.
6247
6248 dol_syslog(get_class($this)."::insert rang=".$this->rang, LOG_DEBUG);
6249
6250 // Clean parameters
6251 $this->desc = trim($this->desc);
6252 if (empty($this->tva_tx)) {
6253 $this->tva_tx = 0;
6254 }
6255 if (empty($this->localtax1_tx)) {
6256 $this->localtax1_tx = 0;
6257 }
6258 if (empty($this->localtax2_tx)) {
6259 $this->localtax2_tx = 0;
6260 }
6261 if (empty($this->localtax1_type)) {
6262 $this->localtax1_type = 0;
6263 }
6264 if (empty($this->localtax2_type)) {
6265 $this->localtax2_type = 0;
6266 }
6267 if (empty($this->total_localtax1)) {
6268 $this->total_localtax1 = 0;
6269 }
6270 if (empty($this->total_localtax2)) {
6271 $this->total_localtax2 = 0;
6272 }
6273 if (empty($this->rang)) {
6274 $this->rang = 0;
6275 }
6276 if (empty($this->remise_percent)) {
6277 $this->remise_percent = 0;
6278 }
6279 if (empty($this->info_bits)) {
6280 $this->info_bits = 0;
6281 }
6282 if (empty($this->subprice)) {
6283 $this->subprice = 0;
6284 }
6285 if (empty($this->ref_ext)) {
6286 $this->ref_ext = '';
6287 }
6288 if (empty($this->special_code)) {
6289 $this->special_code = 0;
6290 }
6291 if (empty($this->fk_parent_line)) {
6292 $this->fk_parent_line = 0;
6293 }
6294 if (empty($this->fk_prev_id)) {
6295 $this->fk_prev_id = 0;
6296 }
6297 if (!isset($this->situation_percent) || $this->situation_percent > 100 || (string) $this->situation_percent == '') {
6298 $this->situation_percent = 100;
6299 }
6300
6301 if (empty($this->pa_ht)) {
6302 $this->pa_ht = 0;
6303 }
6304 if (empty($this->multicurrency_subprice)) {
6305 $this->multicurrency_subprice = 0;
6306 }
6307 if (empty($this->multicurrency_total_ht)) {
6308 $this->multicurrency_total_ht = 0;
6309 }
6310 if (empty($this->multicurrency_total_tva)) {
6311 $this->multicurrency_total_tva = 0;
6312 }
6313 if (empty($this->multicurrency_total_ttc)) {
6314 $this->multicurrency_total_ttc = 0;
6315 }
6316
6317 // if buy price not defined, define buyprice as configured in margin admin
6318 if ($this->pa_ht == 0 && $pa_ht_isemptystring) {
6319 if (($result = $this->defineBuyPrice($this->subprice, $this->remise_percent, $this->fk_product)) < 0) {
6320 return $result;
6321 } else {
6322 $this->pa_ht = $result;
6323 }
6324 }
6325
6326 // Check parameters
6327 if ($this->product_type < 0) {
6328 $this->error = 'ErrorProductTypeMustBe0orMore';
6329 return -1;
6330 }
6331 if (!empty($this->fk_product) && $this->fk_product > 0) {
6332 // Check product exists
6333 $result = Product::isExistingObject('product', $this->fk_product);
6334 if ($result <= 0) {
6335 $this->error = 'ErrorProductIdDoesNotExists';
6336 dol_syslog(get_class($this)."::insert Error ".$this->error, LOG_ERR);
6337 return -1;
6338 }
6339 }
6340
6341 $this->db->begin();
6342
6343 // Update line in database
6344 $sql = 'INSERT INTO '.MAIN_DB_PREFIX.'facturedet';
6345 $sql .= ' (fk_facture, fk_parent_line, label, description, qty,';
6346 $sql .= ' vat_src_code, tva_tx, localtax1_tx, localtax2_tx, localtax1_type, localtax2_type,';
6347 $sql .= ' fk_product, product_type, remise_percent, subprice, ref_ext, fk_remise_except,';
6348 $sql .= ' date_start, date_end, fk_code_ventilation, ';
6349 $sql .= ' rang, special_code, fk_product_fournisseur_price, buy_price_ht,';
6350 $sql .= ' info_bits, total_ht, total_tva, total_ttc, total_localtax1, total_localtax2,';
6351 $sql .= ' situation_percent, fk_prev_id,';
6352 $sql .= ' fk_unit, fk_user_author, fk_user_modif,';
6353 $sql .= ' fk_multicurrency, multicurrency_code, multicurrency_subprice, multicurrency_total_ht, multicurrency_total_tva, multicurrency_total_ttc';
6354 $sql .= ')';
6355 $sql .= " VALUES (".$this->fk_facture.",";
6356 $sql .= " ".($this->fk_parent_line > 0 ? $this->fk_parent_line : "null").",";
6357 $sql .= " ".(!empty($this->label) ? "'".$this->db->escape($this->label)."'" : "null").",";
6358 $sql .= " '".$this->db->escape($this->desc)."',";
6359 $sql .= " ".price2num($this->qty).",";
6360 $sql .= " ".(empty($this->vat_src_code) ? "''" : "'".$this->db->escape($this->vat_src_code)."'").",";
6361 $sql .= " ".price2num($this->tva_tx).",";
6362 $sql .= " ".price2num($this->localtax1_tx).",";
6363 $sql .= " ".price2num($this->localtax2_tx).",";
6364 $sql .= " '".$this->db->escape($this->localtax1_type)."',";
6365 $sql .= " '".$this->db->escape($this->localtax2_type)."',";
6366 $sql .= ' '.((!empty($this->fk_product) && $this->fk_product > 0) ? $this->fk_product : "null").',';
6367 $sql .= " ".((int) $this->product_type).",";
6368 $sql .= " ".price2num($this->remise_percent).",";
6369 $sql .= " ".price2num($this->subprice).",";
6370 $sql .= " '".$this->db->escape($this->ref_ext)."',";
6371 $sql .= ' '.(!empty($this->fk_remise_except) ? $this->fk_remise_except : "null").',';
6372 $sql .= " ".(!empty($this->date_start) ? "'".$this->db->idate($this->date_start)."'" : "null").",";
6373 $sql .= " ".(!empty($this->date_end) ? "'".$this->db->idate($this->date_end)."'" : "null").",";
6374 $sql .= ' '.((int) $this->fk_code_ventilation).',';
6375 $sql .= ' '.((int) $this->rang).',';
6376 $sql .= ' '.((int) $this->special_code).',';
6377 $sql .= ' '.(!empty($this->fk_fournprice) ? $this->fk_fournprice : "null").',';
6378 $sql .= ' '.price2num($this->pa_ht).',';
6379 $sql .= " '".$this->db->escape($this->info_bits)."',";
6380 $sql .= " ".price2num($this->total_ht).",";
6381 $sql .= " ".price2num($this->total_tva).",";
6382 $sql .= " ".price2num($this->total_ttc).",";
6383 $sql .= " ".price2num($this->total_localtax1).",";
6384 $sql .= " ".price2num($this->total_localtax2);
6385 $sql .= ", ".((float) $this->situation_percent);
6386 $sql .= ", ".(!empty($this->fk_prev_id) ? $this->fk_prev_id : "null");
6387 $sql .= ", ".(!$this->fk_unit ? 'NULL' : $this->fk_unit);
6388 $sql .= ", ".((int) $user->id);
6389 $sql .= ", ".((int) $user->id);
6390 $sql .= ", ".(int) $this->fk_multicurrency;
6391 $sql .= ", '".$this->db->escape($this->multicurrency_code)."'";
6392 $sql .= ", ".price2num($this->multicurrency_subprice);
6393 $sql .= ", ".price2num($this->multicurrency_total_ht);
6394 $sql .= ", ".price2num($this->multicurrency_total_tva);
6395 $sql .= ", ".price2num($this->multicurrency_total_ttc);
6396 $sql .= ')';
6397
6398 dol_syslog(get_class($this)."::insert", LOG_DEBUG);
6399 $resql = $this->db->query($sql);
6400 if ($resql) {
6401 $this->id = $this->db->last_insert_id(MAIN_DB_PREFIX.'facturedet');
6402 $this->rowid = $this->id; // For backward compatibility
6403
6404 if (!$error) {
6405 $result = $this->insertExtraFields();
6406 if ($result < 0) {
6407 $error++;
6408 }
6409 }
6410
6411 // If fk_remise_except is defined, the discount is linked to the invoice
6412 // which flags it as "consumed".
6413 if ($this->fk_remise_except && empty($error)) {
6414 $discount = new DiscountAbsolute($this->db);
6415 $result = $discount->fetch($this->fk_remise_except);
6416 if ($result >= 0) {
6417 // Check if discount was found
6418 if ($result > 0) {
6419 // Check if discount not already affected to another invoice
6420 if ($discount->fk_facture_line > 0) {
6421 if (empty($noerrorifdiscountalreadylinked)) {
6422 $this->error = $langs->trans("ErrorDiscountAlreadyUsed", $discount->id);
6423 dol_syslog(get_class($this)."::insert Error ".$this->error, LOG_ERR);
6424 $this->db->rollback();
6425 return -3;
6426 }
6427 } else {
6428 $result = $discount->link_to_invoice($this->rowid, 0);
6429 if ($result < 0) {
6430 $this->error = $discount->error;
6431 dol_syslog(get_class($this)."::insert Error ".$this->error, LOG_ERR);
6432 $this->db->rollback();
6433 return -3;
6434 }
6435 }
6436 } else {
6437 $this->error = $langs->trans("ErrorADiscountThatHasBeenRemovedIsIncluded");
6438 dol_syslog(get_class($this)."::insert Error ".$this->error, LOG_ERR);
6439 $this->db->rollback();
6440 return -3;
6441 }
6442 } else {
6443 $this->error = $discount->error;
6444 dol_syslog(get_class($this)."::insert Error ".$this->error, LOG_ERR);
6445 $this->db->rollback();
6446 return -3;
6447 }
6448 }
6449
6450 if (!$notrigger && empty($error)) {
6451 // Call trigger
6452 $result = $this->call_trigger('LINEBILL_INSERT', $user);
6453 if ($result < 0) {
6454 $this->db->rollback();
6455 return -2;
6456 }
6457 // End call triggers
6458 }
6459
6460 if (!$error) {
6461 $this->db->commit();
6462 return $this->id;
6463 }
6464
6465 foreach ($this->errors as $errmsg) {
6466 dol_syslog(get_class($this)."::insert ".$errmsg, LOG_ERR);
6467 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
6468 }
6469 $this->db->rollback();
6470 return -1 * $error;
6471 } else {
6472 $this->error = $this->db->lasterror();
6473 $this->db->rollback();
6474 return -2;
6475 }
6476 }
6477
6485 public function update($user = '', $notrigger = 0)
6486 {
6487 global $user, $conf;
6488
6489 $error = 0;
6490
6491 $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'.
6492
6493 // Clean parameters
6494 $this->desc = trim($this->desc);
6495 if (empty($this->ref_ext)) {
6496 $this->ref_ext = '';
6497 }
6498 if (empty($this->tva_tx)) {
6499 $this->tva_tx = 0;
6500 }
6501 if (empty($this->localtax1_tx)) {
6502 $this->localtax1_tx = 0;
6503 }
6504 if (empty($this->localtax2_tx)) {
6505 $this->localtax2_tx = 0;
6506 }
6507 if (empty($this->localtax1_type)) {
6508 $this->localtax1_type = 0;
6509 }
6510 if (empty($this->localtax2_type)) {
6511 $this->localtax2_type = 0;
6512 }
6513 if (empty($this->total_localtax1)) {
6514 $this->total_localtax1 = 0;
6515 }
6516 if (empty($this->total_localtax2)) {
6517 $this->total_localtax2 = 0;
6518 }
6519 if (empty($this->remise_percent)) {
6520 $this->remise_percent = 0;
6521 }
6522 if (empty($this->info_bits)) {
6523 $this->info_bits = 0;
6524 }
6525 if (empty($this->special_code)) {
6526 $this->special_code = 0;
6527 }
6528 if (empty($this->product_type)) {
6529 $this->product_type = 0;
6530 }
6531 if (empty($this->fk_parent_line)) {
6532 $this->fk_parent_line = 0;
6533 }
6534 if (!isset($this->situation_percent) || $this->situation_percent > 100 || (string) $this->situation_percent == '') {
6535 $this->situation_percent = 100;
6536 }
6537 if (empty($this->pa_ht)) {
6538 $this->pa_ht = 0;
6539 }
6540
6541 if (empty($this->multicurrency_subprice)) {
6542 $this->multicurrency_subprice = 0;
6543 }
6544 if (empty($this->multicurrency_total_ht)) {
6545 $this->multicurrency_total_ht = 0;
6546 }
6547 if (empty($this->multicurrency_total_tva)) {
6548 $this->multicurrency_total_tva = 0;
6549 }
6550 if (empty($this->multicurrency_total_ttc)) {
6551 $this->multicurrency_total_ttc = 0;
6552 }
6553
6554 // Check parameters
6555 if ($this->product_type < 0) {
6556 return -1;
6557 }
6558
6559 // if buy price not provided, define buyprice as configured in margin admin
6560 if ($this->pa_ht == 0 && $pa_ht_isemptystring) {
6561 // We call defineBuyPrice only if data was not provided (if input was '0', we will not go here and value will remaine '0')
6562 $result = $this->defineBuyPrice($this->subprice, $this->remise_percent, $this->fk_product);
6563 if ($result < 0) {
6564 return $result;
6565 } else {
6566 $this->pa_ht = $result;
6567 }
6568 }
6569
6570 $this->db->begin();
6571
6572 // Update line in database
6573 $sql = "UPDATE ".MAIN_DB_PREFIX."facturedet SET";
6574 $sql .= " description='".$this->db->escape($this->desc)."'";
6575 $sql .= ", ref_ext='".$this->db->escape($this->ref_ext)."'";
6576 $sql .= ", label=".(!empty($this->label) ? "'".$this->db->escape($this->label)."'" : "null");
6577 $sql .= ", subprice=".price2num($this->subprice);
6578 $sql .= ", remise_percent=".price2num($this->remise_percent);
6579 if ($this->fk_remise_except) {
6580 $sql .= ", fk_remise_except=".$this->fk_remise_except;
6581 } else {
6582 $sql .= ", fk_remise_except=null";
6583 }
6584 $sql .= ", vat_src_code = '".(empty($this->vat_src_code) ? '' : $this->db->escape($this->vat_src_code))."'";
6585 $sql .= ", tva_tx=".price2num($this->tva_tx);
6586 $sql .= ", localtax1_tx=".price2num($this->localtax1_tx);
6587 $sql .= ", localtax2_tx=".price2num($this->localtax2_tx);
6588 $sql .= ", localtax1_type='".$this->db->escape($this->localtax1_type)."'";
6589 $sql .= ", localtax2_type='".$this->db->escape($this->localtax2_type)."'";
6590 $sql .= ", qty=".price2num($this->qty);
6591 $sql .= ", date_start=".(!empty($this->date_start) ? "'".$this->db->idate($this->date_start)."'" : "null");
6592 $sql .= ", date_end=".(!empty($this->date_end) ? "'".$this->db->idate($this->date_end)."'" : "null");
6593 $sql .= ", product_type=".$this->product_type;
6594 $sql .= ", info_bits='".$this->db->escape($this->info_bits)."'";
6595 $sql .= ", special_code='".$this->db->escape($this->special_code)."'";
6596 if (empty($this->skip_update_total)) {
6597 $sql .= ", total_ht=".price2num($this->total_ht);
6598 $sql .= ", total_tva=".price2num($this->total_tva);
6599 $sql .= ", total_ttc=".price2num($this->total_ttc);
6600 $sql .= ", total_localtax1=".price2num($this->total_localtax1);
6601 $sql .= ", total_localtax2=".price2num($this->total_localtax2);
6602 }
6603 $sql .= ", fk_product_fournisseur_price=".(!empty($this->fk_fournprice) ? "'".$this->db->escape($this->fk_fournprice)."'" : "null");
6604 $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)
6605 $sql .= ", fk_parent_line=".($this->fk_parent_line > 0 ? $this->fk_parent_line : "null");
6606 if (!empty($this->rang)) {
6607 $sql .= ", rang=".((int) $this->rang);
6608 }
6609 $sql .= ", situation_percent = ".((float) $this->situation_percent);
6610 $sql .= ", fk_unit = ".(!$this->fk_unit ? 'NULL' : $this->fk_unit);
6611 $sql .= ", fk_user_modif = ".((int) $user->id);
6612
6613 // Multicurrency
6614 $sql .= ", multicurrency_subprice=".price2num($this->multicurrency_subprice);
6615 $sql .= ", multicurrency_total_ht=".price2num($this->multicurrency_total_ht);
6616 $sql .= ", multicurrency_total_tva=".price2num($this->multicurrency_total_tva);
6617 $sql .= ", multicurrency_total_ttc=".price2num($this->multicurrency_total_ttc);
6618
6619 $sql .= " WHERE rowid = ".((int) $this->rowid);
6620
6621 dol_syslog(get_class($this)."::update", LOG_DEBUG);
6622 $resql = $this->db->query($sql);
6623 if ($resql) {
6624 if (!$error) {
6625 $this->id = $this->rowid;
6626 $result = $this->insertExtraFields();
6627 if ($result < 0) {
6628 $error++;
6629 }
6630 }
6631
6632 if (!$error && !$notrigger) {
6633 // Call trigger
6634 $result = $this->call_trigger('LINEBILL_MODIFY', $user);
6635 if ($result < 0) {
6636 $this->db->rollback();
6637 return -2;
6638 }
6639 // End call triggers
6640 }
6641
6642 if (!$error) {
6643 $this->db->commit();
6644 return 1;
6645 }
6646
6647 foreach ($this->errors as $errmsg) {
6648 dol_syslog(get_class($this)."::update ".$errmsg, LOG_ERR);
6649 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
6650 }
6651 $this->db->rollback();
6652 return -1 * $error;
6653 } else {
6654 $this->error = $this->db->error();
6655 $this->db->rollback();
6656 return -2;
6657 }
6658 }
6659
6667 public function delete($tmpuser = null, $notrigger = false)
6668 {
6669 global $user;
6670
6671 $this->db->begin();
6672
6673 // Call trigger
6674 if (empty($notrigger)) {
6675 $result = $this->call_trigger('LINEBILL_DELETE', $user);
6676 if ($result < 0) {
6677 $this->db->rollback();
6678 return -1;
6679 }
6680 }
6681 // End call triggers
6682
6683 // extrafields
6684 $result = $this->deleteExtraFields();
6685 if ($result < 0) {
6686 $this->db->rollback();
6687 return -1;
6688 }
6689
6690 // Free discount linked to invoice line
6691 $sql = 'UPDATE '.MAIN_DB_PREFIX.'societe_remise_except';
6692 $sql .= ' SET fk_facture_line = NULL';
6693 $sql .= ' WHERE fk_facture_line = '.((int) $this->id);
6694
6695 dol_syslog(get_class($this)."::deleteline", LOG_DEBUG);
6696 $result = $this->db->query($sql);
6697 if (!$result) {
6698 $this->error = $this->db->error();
6699 $this->errors[] = $this->error;
6700 $this->db->rollback();
6701 return -1;
6702 }
6703
6704 $sql = 'UPDATE '.MAIN_DB_PREFIX.'element_time';
6705 $sql .= ' SET invoice_id = NULL, invoice_line_id = NULL';
6706 $sql .= ' WHERE invoice_line_id = '.((int) $this->id);
6707 if (!$this->db->query($sql)) {
6708 $this->error = $this->db->error()." sql=".$sql;
6709 $this->errors[] = $this->error;
6710 $this->db->rollback();
6711 return -1;
6712 }
6713
6714 $sql = "DELETE FROM ".MAIN_DB_PREFIX."facturedet WHERE rowid = ".((int) $this->id);
6715
6716 if ($this->db->query($sql)) {
6717 $this->db->commit();
6718 return 1;
6719 } else {
6720 $this->error = $this->db->error()." sql=".$sql;
6721 $this->errors[] = $this->error;
6722 $this->db->rollback();
6723 return -1;
6724 }
6725 }
6726
6727 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6734 public function update_total()
6735 {
6736 // phpcs:enable
6737 $this->db->begin();
6738 dol_syslog(get_class($this)."::update_total", LOG_DEBUG);
6739
6740 // Clean parameters
6741 if (empty($this->total_localtax1)) {
6742 $this->total_localtax1 = 0;
6743 }
6744 if (empty($this->total_localtax2)) {
6745 $this->total_localtax2 = 0;
6746 }
6747
6748 // Update line in database
6749 $sql = "UPDATE ".MAIN_DB_PREFIX."facturedet SET";
6750 $sql .= " total_ht=".price2num($this->total_ht);
6751 $sql .= ",total_tva=".price2num($this->total_tva);
6752 $sql .= ",total_localtax1=".price2num($this->total_localtax1);
6753 $sql .= ",total_localtax2=".price2num($this->total_localtax2);
6754 $sql .= ",total_ttc=".price2num($this->total_ttc);
6755 $sql .= " WHERE rowid = ".((int) $this->rowid);
6756
6757 dol_syslog(get_class($this)."::update_total", LOG_DEBUG);
6758
6759 $resql = $this->db->query($sql);
6760 if ($resql) {
6761 $this->db->commit();
6762 return 1;
6763 } else {
6764 $this->error = $this->db->error();
6765 $this->db->rollback();
6766 return -2;
6767 }
6768 }
6769
6770 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6779 public function get_prev_progress($invoiceid, $include_credit_note = true)
6780 {
6781 // phpcs:enable
6782 global $invoicecache;
6783
6784 if (is_null($this->fk_prev_id) || empty($this->fk_prev_id) || $this->fk_prev_id == "") {
6785 return 0;
6786 } else {
6787 // If invoice is not a situation invoice, this->fk_prev_id is used for something else
6788 if (!isset($invoicecache[$invoiceid])) {
6789 $invoicecache[$invoiceid] = new Facture($this->db);
6790 $invoicecache[$invoiceid]->fetch($invoiceid);
6791 }
6792 if ($invoicecache[$invoiceid]->type != Facture::TYPE_SITUATION) {
6793 return 0;
6794 }
6795
6796 $sql = "SELECT situation_percent FROM ".MAIN_DB_PREFIX."facturedet WHERE rowid = ".((int) $this->fk_prev_id);
6797 $resql = $this->db->query($sql);
6798 if ($resql && $this->db->num_rows($resql) > 0) {
6799 $res = $this->db->fetch_array($resql);
6800
6801 $returnPercent = floatval($res['situation_percent']);
6802
6803 if ($include_credit_note) {
6804 $sql = 'SELECT fd.situation_percent FROM '.MAIN_DB_PREFIX.'facturedet fd';
6805 $sql .= ' JOIN '.MAIN_DB_PREFIX.'facture f ON (f.rowid = fd.fk_facture) ';
6806 $sql .= " WHERE fd.fk_prev_id = ".((int) $this->fk_prev_id);
6807 $sql .= " AND f.situation_cycle_ref = ".((int) $invoicecache[$invoiceid]->situation_cycle_ref); // Prevent cycle outed
6808 $sql .= " AND f.type = ".Facture::TYPE_CREDIT_NOTE;
6809
6810 $res = $this->db->query($sql);
6811 if ($res) {
6812 while ($obj = $this->db->fetch_object($res)) {
6813 $returnPercent = $returnPercent + floatval($obj->situation_percent);
6814 }
6815 } else {
6816 dol_print_error($this->db);
6817 }
6818 }
6819
6820 return $returnPercent;
6821 } else {
6822 $this->error = $this->db->error();
6823 dol_syslog(get_class($this)."::select Error ".$this->error, LOG_ERR);
6824 $this->db->rollback();
6825 return -1;
6826 }
6827 }
6828 }
6829}
$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