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