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