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