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