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