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