Passed
Branch develop (a7390e)
by
unknown
25:38
created

Facture::getIdBillingContact()   A

Complexity

Conditions 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
c 0
b 0
f 0
nop 0
dl 0
loc 3
rs 10
1
<?php
2
/* Copyright (C) 2002-2007 Rodolphe Quiedeville  <[email protected]>
3
 * Copyright (C) 2004-2013 Laurent Destailleur   <[email protected]>
4
 * Copyright (C) 2004      Sebastien Di Cintio   <[email protected]>
5
 * Copyright (C) 2004      Benoit Mortier        <[email protected]>
6
 * Copyright (C) 2005      Marc Barilley / Ocebo <[email protected]>
7
 * Copyright (C) 2005-2014 Regis Houssin         <[email protected]>
8
 * Copyright (C) 2006      Andre Cianfarani      <[email protected]>
9
 * Copyright (C) 2007      Franky Van Liedekerke <[email protected]>
10
 * Copyright (C) 2010-2016 Juanjo Menent         <[email protected]>
11
 * Copyright (C) 2012-2014 Christophe Battarel   <[email protected]>
12
 * Copyright (C) 2012-2015 Marcos García         <[email protected]>
13
 * Copyright (C) 2012      Cédric Salvador       <[email protected]>
14
 * Copyright (C) 2012-2014 Raphaël Doursenaud    <[email protected]>
15
 * Copyright (C) 2013      Cedric Gross          <[email protected]>
16
 * Copyright (C) 2013      Florian Henry         <[email protected]>
17
 * Copyright (C) 2016      Ferran Marcet         <[email protected]>
18
 * Copyright (C) 2018      Alexandre Spangaro    <[email protected]>
19
 * Copyright (C) 2018      Nicolas ZABOURI        <[email protected]>
20
 *
21
 * This program is free software; you can redistribute it and/or modify
22
 * it under the terms of the GNU General Public License as published by
23
 * the Free Software Foundation; either version 3 of the License, or
24
 * (at your option) any later version.
25
 *
26
 * This program is distributed in the hope that it will be useful,
27
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
28
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
29
 * GNU General Public License for more details.
30
 *
31
 * You should have received a copy of the GNU General Public License
32
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
33
 */
34
35
/**
36
 *	\file       htdocs/compta/facture/class/facture.class.php
37
 *	\ingroup    facture
38
 *	\brief      File of class to manage invoices
39
 */
40
41
include_once DOL_DOCUMENT_ROOT.'/core/class/commoninvoice.class.php';
42
require_once DOL_DOCUMENT_ROOT.'/core/class/commonobjectline.class.php';
43
require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
44
require_once DOL_DOCUMENT_ROOT.'/societe/class/client.class.php';
45
require_once DOL_DOCUMENT_ROOT.'/margin/lib/margins.lib.php';
46
require_once DOL_DOCUMENT_ROOT.'/multicurrency/class/multicurrency.class.php';
47
48
if (! empty($conf->accounting->enabled)) require_once DOL_DOCUMENT_ROOT.'/core/class/html.formaccounting.class.php';
49
if (! empty($conf->accounting->enabled)) require_once DOL_DOCUMENT_ROOT.'/accountancy/class/accountingaccount.class.php';
50
51
/**
52
 *	Class to manage invoices
53
 */
54
class Facture extends CommonInvoice
55
{
56
	/**
57
	 * @var string ID to identify managed object
58
	 */
59
	public $element='facture';
60
61
	/**
62
	 * @var string Name of table without prefix where object is stored
63
	 */
64
	public $table_element='facture';
65
66
	/**
67
	 * @var int    Name of subtable line
68
	 */
69
	public $table_element_line = 'facturedet';
70
71
	/**
72
	 * @var int Field with ID of parent key if this field has a parent
73
	 */
74
	public $fk_element = 'fk_facture';
75
76
	/**
77
	 * @var string String with name of icon for myobject. Must be the part after the 'object_' into object_myobject.png
78
	 */
79
	public $picto='bill';
80
81
	/**
82
	 * 0=No test on entity, 1=Test with field entity, 2=Test with link by societe
83
	 * @var int
84
	 */
85
	public $ismultientitymanaged = 1;
86
87
	/**
88
	 * 0=Default, 1=View may be restricted to sales representative only if no permission to see all or to company of external user if external user
89
	 * @var integer
90
	 */
91
	public $restrictiononfksoc = 1;
92
93
	/**
94
	 * {@inheritdoc}
95
	 */
96
	protected $table_ref_field = 'ref';
97
98
	public $socid;
99
100
	public $author;
101
102
	/**
103
     * @var int ID
104
     */
105
	public $fk_user_author;
106
107
	/**
108
     * @var int ID
109
     */
110
	public $fk_user_valid;
111
112
	public $date;              // Date invoice
113
	public $datem;
114
	public $ref_client;
115
	public $ref_int;
116
	//Check constants for types
117
	public $type = self::TYPE_STANDARD;
118
119
	//var $amount;
120
	public $remise_absolue;
121
	public $remise_percent;
122
	public $total_ht=0;
123
	public $total_tva=0;
124
	public $total_localtax1=0;
125
	public $total_localtax2=0;
126
	public $total_ttc=0;
127
	public $revenuestamp;
128
129
	//! Fermeture apres paiement partiel: discount_vat, badcustomer, abandon
130
	//! Fermeture alors que aucun paiement: replaced (si remplace), abandon
131
	public $close_code;
132
	//! Commentaire si mis a paye sans paiement complet
133
	public $close_note;
134
	//! 1 if invoice paid COMPLETELY, 0 otherwise (do not use it anymore, use statut and close_code)
135
	public $paye;
136
	//! key of module source when invoice generated from a dedicated module ('cashdesk', 'takepos', ...)
137
	public $module_source;
138
	//! key of pos source ('0', '1', ...)
139
	public $pos_source;
140
	//! id of template invoice when generated from a template invoice
141
	public $fk_fac_rec_source;
142
	//! id of source invoice if replacement invoice or credit note
143
	public $fk_facture_source;
144
	public $linked_objects=array();
145
	public $date_lim_reglement;
146
	public $cond_reglement_code;		// Code in llx_c_paiement
147
	public $mode_reglement_code;		// Code in llx_c_paiement
148
149
	/**
150
     * @var int ID Field to store bank id to use when payment mode is withdraw
151
     */
152
	public $fk_bank;
153
154
	/**
155
	 * @deprecated
156
	 */
157
	public $products=array();
158
159
	/**
160
	 * @var FactureLigne[]
161
	 */
162
	public $lines=array();
163
164
	public $line;
165
	public $extraparams=array();
166
	public $specimen;
167
168
	public $fac_rec;
169
170
	// Multicurrency
171
	/**
172
     * @var int ID
173
     */
174
	public $fk_multicurrency;
175
176
	public $multicurrency_code;
177
	public $multicurrency_tx;
178
	public $multicurrency_total_ht;
179
	public $multicurrency_total_tva;
180
	public $multicurrency_total_ttc;
181
182
	/**
183
	 * @var int Situation cycle reference number
184
	 */
185
	public $situation_cycle_ref;
186
187
	/**
188
	 * @var int Situation counter inside the cycle
189
	 */
190
	public $situation_counter;
191
192
	/**
193
	 * @var int Final situation flag
194
	 */
195
	public $situation_final;
196
197
	/**
198
	 * @var array Table of previous situations
199
	 */
200
	public $tab_previous_situation_invoice=array();
201
202
	/**
203
	 * @var array Table of next situations
204
	 */
205
	public $tab_next_situation_invoice=array();
206
207
	public $oldcopy;
208
209
	/**
210
	 * @var double percentage of retainage
211
	 */
212
	public $retained_warranty;
213
	
214
	/**
215
	 * @var int timestamp of date limit of retainage
216
	 */
217
	public $retained_warranty_date_limit;
218
	
219
	/**
220
	 * @var int Code in llx_c_paiement
221
	 */
222
	public $retained_warranty_fk_cond_reglement;
223
224
    /**
225
     * Standard invoice
226
     */
227
    const TYPE_STANDARD = 0;
228
229
    /**
230
     * Replacement invoice
231
     */
232
    const TYPE_REPLACEMENT = 1;
233
234
    /**
235
     * Credit note invoice
236
     */
237
    const TYPE_CREDIT_NOTE = 2;
238
239
    /**
240
     * Deposit invoice
241
     */
242
    const TYPE_DEPOSIT = 3;
243
244
    /**
245
     * Proforma invoice (should not be used. a proforma is an order)
246
     */
247
    const TYPE_PROFORMA = 4;
248
249
	/**
250
	 * Situation invoice
251
	 */
252
	const TYPE_SITUATION = 5;
253
254
	/**
255
	 * Draft status
256
	 */
257
	const STATUS_DRAFT = 0;
258
259
	/**
260
	 * Validated (need to be paid)
261
	 */
262
	const STATUS_VALIDATED = 1;
263
264
	/**
265
	 * Classified paid.
266
	 * If paid partially, $this->close_code can be:
267
	 * - CLOSECODE_DISCOUNTVAT
268
	 * - CLOSECODE_BADDEBT
269
	 * If paid completely, this->close_code will be null
270
	 */
271
	const STATUS_CLOSED = 2;
272
273
	/**
274
	 * Classified abandoned and no payment done.
275
	 * $this->close_code can be:
276
	 * - CLOSECODE_BADDEBT
277
	 * - CLOSECODE_ABANDONED
278
	 * - CLOSECODE_REPLACED
279
	 */
280
	const STATUS_ABANDONED = 3;
281
282
	const CLOSECODE_DISCOUNTVAT = 'discount_vat';	// Abandonned remain - escompte
283
	const CLOSECODE_BADDEBT = 'badcustomer';		// Abandonned - bad
284
	const CLOSECODE_ABANDONED = 'abandon';			// Abandonned - other
285
	const CLOSECODE_REPLACED = 'replaced';			// Closed after doing a replacement invoice
286
287
	/**
288
	 * 	Constructor
289
	 *
290
	 * 	@param	DoliDB		$db			Database handler
291
	 */
292
	public function __construct($db)
293
	{
294
		$this->db = $db;
295
	}
296
297
	/**
298
	 *	Create invoice in database.
299
	 *  Note: this->ref can be set or empty. If empty, we will use "(PROV999)"
300
	 *  Note: this->fac_rec must be set to create invoice from a recurring invoice
301
	 *
302
	 *	@param	User	$user      		Object user that create
303
	 *	@param  int		$notrigger		1=Does not execute triggers, 0 otherwise
304
	 * 	@param	int		$forceduedate	If set, do not recalculate due date from payment condition but force it with value
305
	 *	@return	int						<0 if KO, >0 if OK
306
	 */
307
    public function create(User $user, $notrigger = 0, $forceduedate = 0)
308
	{
309
		global $langs,$conf,$mysoc,$hookmanager;
310
		$error=0;
311
312
		// Clean parameters
313
		if (empty($this->type)) $this->type = self::TYPE_STANDARD;
314
		$this->ref_client=trim($this->ref_client);
315
		$this->note=(isset($this->note) ? trim($this->note) : trim($this->note_private)); // deprecated
1 ignored issue
show
Deprecated Code introduced by
The property CommonObject::$note has been deprecated. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

315
		/** @scrutinizer ignore-deprecated */ $this->note=(isset($this->note) ? trim($this->note) : trim($this->note_private)); // deprecated
Loading history...
316
		$this->note_private=(isset($this->note_private) ? trim($this->note_private) : trim($this->note_private));
317
		$this->note_public=trim($this->note_public);
318
		if (! $this->cond_reglement_id) $this->cond_reglement_id = 0;
319
		if (! $this->mode_reglement_id) $this->mode_reglement_id = 0;
320
		$this->brouillon = 1;
321
        if (empty($this->entity)) $this->entity = $conf->entity;
322
323
		// Multicurrency (test on $this->multicurrency_tx because we should take the default rate only if not using origin rate)
324
		if (!empty($this->multicurrency_code) && empty($this->multicurrency_tx)) list($this->fk_multicurrency,$this->multicurrency_tx) = MultiCurrency::getIdAndTxFromCode($this->db, $this->multicurrency_code);
325
		else $this->fk_multicurrency = MultiCurrency::getIdFromCode($this->db, $this->multicurrency_code);
326
		if (empty($this->fk_multicurrency))
327
		{
328
			$this->multicurrency_code = $conf->currency;
329
			$this->fk_multicurrency = 0;
330
			$this->multicurrency_tx = 1;
331
		}
332
333
		dol_syslog(get_class($this)."::create user=".$user->id." date=".$this->date);
334
335
		// Check parameters
336
		if (empty($this->date))
337
		{
338
			$this->error="Try to create an invoice with an empty parameter (date)";
339
			dol_syslog(get_class($this)."::create ".$this->error, LOG_ERR);
340
			return -3;
341
		}
342
		$soc = new Societe($this->db);
343
		$result=$soc->fetch($this->socid);
344
		if ($result < 0)
345
		{
346
			$this->error="Failed to fetch company: ".$soc->error;
347
			dol_syslog(get_class($this)."::create ".$this->error, LOG_ERR);
348
			return -2;
349
		}
350
351
		$now=dol_now();
352
353
		$this->db->begin();
354
355
		$originaldatewhen=null;
356
		$nextdatewhen=null;
357
		$previousdaynextdatewhen=null;
358
359
		// Create invoice from a template invoice
360
		if ($this->fac_rec > 0)
361
		{
362
		    $this->fk_fac_rec_source = $this->fac_rec;
363
364
			require_once DOL_DOCUMENT_ROOT.'/compta/facture/class/facture-rec.class.php';
365
			$_facrec = new FactureRec($this->db);
366
			$result=$_facrec->fetch($this->fac_rec);
367
			$result=$_facrec->fetchObjectLinked(null, '', null, '', 'OR', 1, 'sourcetype', 0);       // This load $_facrec->linkedObjectsIds
368
369
			// Define some dates
370
			$originaldatewhen = $_facrec->date_when;
371
			$nextdatewhen=dol_time_plus_duree($originaldatewhen, $_facrec->frequency, $_facrec->unit_frequency);
372
			$previousdaynextdatewhen=dol_time_plus_duree($nextdatewhen, -1, 'd');
373
374
			$this->socid 		     = $_facrec->socid;  // Invoice created on same thirdparty than template
375
			$this->entity            = $_facrec->entity; // Invoice created in same entity than template
376
377
			// 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
378
			$this->fk_project        = GETPOST('projectid', 'int') > 0 ? ((int) GETPOST('projectid', 'int')) : $_facrec->fk_project;
379
			$this->note_public       = GETPOST('note_public', 'none') ? GETPOST('note_public', 'none') : $_facrec->note_public;
1 ignored issue
show
Documentation Bug introduced by
It seems like GETPOST('note_public', '...: $_facrec->note_public can also be of type string[]. However, the property $note_public is declared as type string. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
380
			$this->note_private      = GETPOST('note_private', 'none') ? GETPOST('note_private', 'none') : $_facrec->note_private;
1 ignored issue
show
Documentation Bug introduced by
It seems like GETPOST('note_private', ... $_facrec->note_private can also be of type string[]. However, the property $note_private is declared as type string. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
381
			$this->modelpdf          = GETPOST('model', 'alpha') ? GETPOST('model', 'apha') : $_facrec->modelpdf;
1 ignored issue
show
Documentation Bug introduced by
It seems like GETPOST('model', 'alpha'...') : $_facrec->modelpdf can also be of type string[]. However, the property $modelpdf is declared as type string. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
382
			$this->cond_reglement_id = GETPOST('cond_reglement_id', 'int') > 0 ? ((int) GETPOST('cond_reglement_id', 'int')) : $_facrec->cond_reglement_id;
383
			$this->mode_reglement_id = GETPOST('mode_reglement_id', 'int') > 0 ? ((int) GETPOST('mode_reglement_id', 'int')) : $_facrec->mode_reglement_id;
384
			$this->fk_account        = GETPOST('fk_account') > 0 ? ((int) GETPOST('fk_account')) : $_facrec->fk_account;
385
386
			// Set here to have this defined for substitution into notes, should be recalculated after adding lines to get same result
387
			$this->total_ht          = $_facrec->total_ht;
388
			$this->total_ttc         = $_facrec->total_ttc;
389
390
			// Fields always coming from template
391
			$this->remise_absolue    = $_facrec->remise_absolue;
392
			$this->remise_percent    = $_facrec->remise_percent;
393
			$this->fk_incoterms		 = $_facrec->fk_incoterms;
394
			$this->location_incoterms= $_facrec->location_incoterms;
395
396
			// Clean parameters
397
			if (! $this->type) $this->type = self::TYPE_STANDARD;
398
			$this->ref_client=trim($this->ref_client);
399
			$this->note_public=trim($this->note_public);
400
			$this->note_private=trim($this->note_private);
401
		    $this->note_private=dol_concatdesc($this->note_private, $langs->trans("GeneratedFromRecurringInvoice", $_facrec->ref));
402
403
		    $this->array_options=$_facrec->array_options;
404
405
			//if (! $this->remise) $this->remise = 0;
406
			if (! $this->mode_reglement_id) $this->mode_reglement_id = 0;
407
			$this->brouillon = 1;
408
409
			$this->linked_objects = $_facrec->linkedObjectsIds;
410
			// We do not add link to template invoice or next invoice will be linked to all generated invoices
411
			//$this->linked_objects['facturerec'][0] = $this->fac_rec;
412
413
			$forceduedate = $this->calculate_date_lim_reglement();
414
415
			// For recurring invoices, update date and number of last generation of recurring template invoice, before inserting new invoice
416
			if ($_facrec->frequency > 0)
417
			{
418
			    dol_syslog("This is a recurring invoice so we set date_last_gen and next date_when");
419
			    if (empty($_facrec->date_when)) $_facrec->date_when = $now;
420
                $next_date = $_facrec->getNextDate();   // Calculate next date
421
                $result = $_facrec->setValueFrom('date_last_gen', $now, '', null, 'date', '', $user, '');
422
                //$_facrec->setValueFrom('nb_gen_done', $_facrec->nb_gen_done + 1);		// Not required, +1 already included into setNextDate when second param is 1.
423
                $result = $_facrec->setNextDate($next_date, 1);
424
			}
425
426
			// Define lang of customer
427
			$outputlangs = $langs;
428
			$newlang='';
429
430
			if ($conf->global->MAIN_MULTILANGS && empty($newlang) && isset($this->thirdparty->default_lang)) $newlang=$this->thirdparty->default_lang;  // for proposal, order, invoice, ...
431
			if ($conf->global->MAIN_MULTILANGS && empty($newlang) && isset($this->default_lang)) $newlang=$this->default_lang;                  // for thirdparty
432
			if (! empty($newlang))
433
			{
434
			    $outputlangs = new Translate("", $conf);
435
			    $outputlangs->setDefaultLang($newlang);
436
			}
437
438
			// Array of possible substitutions (See also file mailing-send.php that should manage same substitutions)
439
			$substitutionarray=getCommonSubstitutionArray($outputlangs, 0, null, $this);
440
			$substitutionarray['__INVOICE_PREVIOUS_MONTH__'] = dol_print_date(dol_time_plus_duree($this->date, -1, 'm'), '%m');
441
			$substitutionarray['__INVOICE_MONTH__'] = dol_print_date($this->date, '%m');
442
			$substitutionarray['__INVOICE_NEXT_MONTH__'] = dol_print_date(dol_time_plus_duree($this->date, 1, 'm'), '%m');
443
			$substitutionarray['__INVOICE_PREVIOUS_MONTH_TEXT__'] = dol_print_date(dol_time_plus_duree($this->date, -1, 'm'), '%B');
444
			$substitutionarray['__INVOICE_MONTH_TEXT__'] = dol_print_date($this->date, '%B');
445
			$substitutionarray['__INVOICE_NEXT_MONTH_TEXT__'] = dol_print_date(dol_time_plus_duree($this->date, 1, 'm'), '%B');
446
			$substitutionarray['__INVOICE_PREVIOUS_YEAR__'] = dol_print_date(dol_time_plus_duree($this->date, -1, 'y'), '%Y');
447
			$substitutionarray['__INVOICE_YEAR__'] = dol_print_date($this->date, '%Y');
448
			$substitutionarray['__INVOICE_NEXT_YEAR__'] = dol_print_date(dol_time_plus_duree($this->date, 1, 'y'), '%Y');
449
			// Only for tempalte invoice
450
			$substitutionarray['__INVOICE_DATE_NEXT_INVOICE_BEFORE_GEN__'] = dol_print_date($originaldatewhen, 'dayhour');
451
			$substitutionarray['__INVOICE_DATE_NEXT_INVOICE_AFTER_GEN__'] = dol_print_date($nextdatewhen, 'dayhour');
452
			$substitutionarray['__INVOICE_PREVIOUS_DATE_NEXT_INVOICE_AFTER_GEN__'] = dol_print_date($previousdaynextdatewhen, 'dayhour');
453
454
			//var_dump($substitutionarray);exit;
455
456
			complete_substitutions_array($substitutionarray, $outputlangs);
457
458
			$this->note_public=make_substitutions($this->note_public, $substitutionarray);
459
			$this->note_private=make_substitutions($this->note_private, $substitutionarray);
460
		}
461
462
		// Define due date if not already defined
463
		$datelim=(empty($forceduedate)?$this->calculate_date_lim_reglement():$forceduedate);
464
465
		// Insert into database
466
		$socid  = $this->socid;
467
468
		$sql = "INSERT INTO ".MAIN_DB_PREFIX."facture (";
469
		$sql.= " ref";
470
		$sql.= ", entity";
471
		$sql.= ", ref_ext";
472
		$sql.= ", type";
473
		$sql.= ", fk_soc";
474
		$sql.= ", datec";
475
		$sql.= ", remise_absolue";
476
		$sql.= ", remise_percent";
477
		$sql.= ", datef";
478
		$sql.= ", date_pointoftax";
479
		$sql.= ", note_private";
480
		$sql.= ", note_public";
481
		$sql.= ", ref_client, ref_int";
482
        $sql.= ", fk_account";
483
		$sql.= ", module_source, pos_source, fk_fac_rec_source, fk_facture_source, fk_user_author, fk_projet";
484
		$sql.= ", fk_cond_reglement, fk_mode_reglement, date_lim_reglement, model_pdf";
485
		$sql.= ", situation_cycle_ref, situation_counter, situation_final";
486
		$sql.= ", fk_incoterms, location_incoterms";
487
        $sql.= ", fk_multicurrency";
488
        $sql.= ", multicurrency_code";
489
        $sql.= ", multicurrency_tx";
490
        $sql.= ", retained_warranty";
491
        $sql.= ", retained_warranty_date_limit";
492
        $sql.= ", retained_warranty_fk_cond_reglement";
493
		$sql.= ")";
494
		$sql.= " VALUES (";
495
		$sql.= "'(PROV)'";
496
		$sql.= ", ".$this->entity;
497
		$sql.= ", ".($this->ref_ext?"'".$this->db->escape($this->ref_ext)."'":"null");
498
		$sql.= ", '".$this->db->escape($this->type)."'";
499
		$sql.= ", '".$socid."'";
500
		$sql.= ", '".$this->db->idate($now)."'";
501
		$sql.= ", ".($this->remise_absolue>0?$this->remise_absolue:'NULL');
502
		$sql.= ", ".($this->remise_percent>0?$this->remise_percent:'NULL');
503
		$sql.= ", '".$this->db->idate($this->date)."'";
504
		$sql.= ", ".(strval($this->date_pointoftax)!='' ? "'".$this->db->idate($this->date_pointoftax)."'" : 'null');
505
		$sql.= ", ".($this->note_private?"'".$this->db->escape($this->note_private)."'":"null");
506
		$sql.= ", ".($this->note_public?"'".$this->db->escape($this->note_public)."'":"null");
507
		$sql.= ", ".($this->ref_client?"'".$this->db->escape($this->ref_client)."'":"null");
508
		$sql.= ", ".($this->ref_int?"'".$this->db->escape($this->ref_int)."'":"null");
509
		$sql.= ", ".($this->fk_account>0?$this->fk_account:'NULL');
510
		$sql.= ", ".($this->module_source ? "'".$this->db->escape($this->module_source)."'" : "null");
511
		$sql.= ", ".($this->pos_source != '' ? "'".$this->db->escape($this->pos_source)."'" : "null");
512
		$sql.= ", ".($this->fk_fac_rec_source?"'".$this->db->escape($this->fk_fac_rec_source)."'":"null");
513
		$sql.= ", ".($this->fk_facture_source?"'".$this->db->escape($this->fk_facture_source)."'":"null");
514
		$sql.= ", ".($user->id > 0 ? "'".$user->id."'":"null");
515
		$sql.= ", ".($this->fk_project?$this->fk_project:"null");
516
		$sql.= ", ".$this->cond_reglement_id;
517
		$sql.= ", ".$this->mode_reglement_id;
518
		$sql.= ", '".$this->db->idate($datelim)."', '".$this->db->escape($this->modelpdf)."'";
519
		$sql.= ", ".($this->situation_cycle_ref?"'".$this->db->escape($this->situation_cycle_ref)."'":"null");
520
		$sql.= ", ".($this->situation_counter?"'".$this->db->escape($this->situation_counter)."'":"null");
521
		$sql.= ", ".($this->situation_final?$this->situation_final:0);
522
		$sql.= ", ".(int) $this->fk_incoterms;
523
        $sql.= ", '".$this->db->escape($this->location_incoterms)."'";
524
		$sql.= ", ".(int) $this->fk_multicurrency;
525
		$sql.= ", '".$this->db->escape($this->multicurrency_code)."'";
526
		$sql.= ", ".(double) $this->multicurrency_tx;
527
		$sql.= ", ".(empty($this->retained_warranty)?"0":$this->db->escape($this->retained_warranty));
528
		$sql.= ", ".(!empty($this->retained_warranty_date_limit)?"'".$this->db->idate($this->retained_warranty_date_limit)."'":'NULL');
529
		$sql.= ", ".(int) $this->retained_warranty_fk_cond_reglement;
530
		
531
		$sql.=")";
532
533
		$resql=$this->db->query($sql);
534
		if ($resql)
535
		{
536
			$this->id = $this->db->last_insert_id(MAIN_DB_PREFIX.'facture');
537
538
			// Update ref with new one
539
			$this->ref='(PROV'.$this->id.')';
540
			$sql = 'UPDATE '.MAIN_DB_PREFIX."facture SET ref='".$this->db->escape($this->ref)."' WHERE rowid=".$this->id;
541
542
			$resql=$this->db->query($sql);
543
			if (! $resql) $error++;
544
545
			if (! empty($this->linkedObjectsIds) && empty($this->linked_objects))	// To use new linkedObjectsIds instead of old linked_objects
546
			{
547
				$this->linked_objects = $this->linkedObjectsIds;	// TODO Replace linked_objects with linkedObjectsIds
548
			}
549
550
			// Add object linked
551
			if (! $error && $this->id && is_array($this->linked_objects) && ! empty($this->linked_objects))
552
			{
553
				foreach($this->linked_objects as $origin => $tmp_origin_id)
554
				{
555
				    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, ...))
556
				    {
557
				        foreach($tmp_origin_id as $origin_id)
558
				        {
559
				            $ret = $this->add_object_linked($origin, $origin_id);
560
				            if (! $ret)
561
				            {
562
				                $this->error=$this->db->lasterror();
563
				                $error++;
564
				            }
565
				        }
566
				    }
567
				    else                                // Old behaviour, if linked_object has only one link per type, so is something like array('contract'=>id1))
568
				    {
569
				        $origin_id = $tmp_origin_id;
570
    					$ret = $this->add_object_linked($origin, $origin_id);
571
    					if (! $ret)
572
    					{
573
    						$this->error=$this->db->lasterror();
574
    						$error++;
575
    					}
576
				    }
577
				}
578
			}
579
580
			// Propagate contacts
581
			if (! $error && $this->id && ! empty($conf->global->MAIN_PROPAGATE_CONTACTS_FROM_ORIGIN) && ! empty($this->origin) && ! empty($this->origin_id))   // Get contact from origin object
582
			{
583
				$originforcontact = $this->origin;
584
				$originidforcontact = $this->origin_id;
585
				if ($originforcontact == 'shipping')     // shipment and order share the same contacts. If creating from shipment we take data of order
586
				{
587
				    require_once DOL_DOCUMENT_ROOT . '/expedition/class/expedition.class.php';
588
				    $exp = new Expedition($this->db);
589
				    $exp->fetch($this->origin_id);
590
				    $exp->fetchObjectLinked(null, '', null, '', 'OR', 1, 'sourcetype', 0);
591
				    if (count($exp->linkedObjectsIds['commande']) > 0)
592
				    {
593
				        foreach ($exp->linkedObjectsIds['commande'] as $key => $value)
594
				        {
595
				            $originforcontact = 'commande';
596
				            if (is_object($value)) $originidforcontact = $value->id;
597
				            else $originidforcontact = $value;
598
				            break; // We take first one
599
				        }
600
				    }
601
				}
602
603
				$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";
604
				$sqlcontact.= " WHERE element_id = ".$originidforcontact." AND ec.fk_c_type_contact = ctc.rowid AND ctc.element = '".$originforcontact."'";
605
606
				$resqlcontact = $this->db->query($sqlcontact);
607
				if ($resqlcontact)
608
				{
609
				    while($objcontact = $this->db->fetch_object($resqlcontact))
610
				    {
611
				        //print $objcontact->code.'-'.$objcontact->source.'-'.$objcontact->fk_socpeople."\n";
612
				        $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
613
				    }
614
				}
615
				else dol_print_error($resqlcontact);
616
			}
617
618
			/*
619
			 *  Insert lines of invoices, if not from template invoice, into database
620
			 */
621
			if (! $error && empty($this->fac_rec) && count($this->lines) && is_object($this->lines[0]))	// If this->lines is array of InvoiceLines (preferred mode)
622
			{
623
				$fk_parent_line = 0;
624
625
				dol_syslog("There is ".count($this->lines)." lines that are invoice lines objects");
626
				foreach ($this->lines as $i => $val)
627
				{
628
					$newinvoiceline=$this->lines[$i];
629
					$newinvoiceline->fk_facture=$this->id;
630
631
					$newinvoiceline->origin = $this->lines[$i]->element;
632
					$newinvoiceline->origin_id = $this->lines[$i]->id;
633
634
					// Auto set date of service ?
635
					if ($this->lines[$i]->date_start_fill == 1 && $originaldatewhen)			// $originaldatewhen is defined when generating from recurring invoice only
0 ignored issues
show
Bug introduced by
The property date_start_fill does not exist on FactureLigne. Did you mean date_start?
Loading history...
636
					{
637
						$newinvoiceline->date_start = $originaldatewhen;
638
					}
639
					if ($this->lines[$i]->date_end_fill == 1 && $previousdaynextdatewhen)	// $previousdaynextdatewhen is defined when generating from recurring invoice only
1 ignored issue
show
Bug introduced by
The property date_end_fill does not exist on FactureLigne. Did you mean date_end?
Loading history...
Bug Best Practice introduced by
The expression $previousdaynextdatewhen of type integer|null is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
640
					{
641
						$newinvoiceline->date_end = $previousdaynextdatewhen;
642
					}
643
644
					if ($result >= 0)
645
					{
646
						// Reset fk_parent_line for no child products and special product
647
						if (($newinvoiceline->product_type != 9 && empty($newinvoiceline->fk_parent_line)) || $newinvoiceline->product_type == 9) {
648
							$fk_parent_line = 0;
649
						}
650
651
						$newinvoiceline->fk_parent_line=$fk_parent_line;
652
653
						if($this->type === Facture::TYPE_REPLACEMENT && $newinvoiceline->fk_remise_except){
654
                            $discount = new DiscountAbsolute($this->db);
655
                            $discount->fetch($newinvoiceline->fk_remise_except);
656
657
						    $discountId = $soc->set_remise_except($discount->amount_ht, $user, $discount->description, $discount->tva_tx);
658
						    $newinvoiceline->fk_remise_except = $discountId;
659
                        }
660
661
						$result=$newinvoiceline->insert();
662
663
						// Defined the new fk_parent_line
664
						if ($result > 0 && $newinvoiceline->product_type == 9) {
665
							$fk_parent_line = $result;
666
						}
667
					}
668
					if ($result < 0)
669
					{
670
						$this->error=$newinvoiceline->error;
671
						$error++;
672
						break;
673
					}
674
				}
675
			}
676
			elseif (! $error && empty($this->fac_rec)) 		// If this->lines is an array of invoice line arrays
677
			{
678
				$fk_parent_line = 0;
679
680
				dol_syslog("There is ".count($this->lines)." lines that are array lines");
681
682
				foreach ($this->lines as $i => $val)
683
				{
684
                	$line = $this->lines[$i];
685
686
                	// Test and convert into object this->lines[$i]. When coming from REST API, we may still have an array
687
				    //if (! is_object($line)) $line=json_decode(json_encode($line), false);  // convert recursively array into object.
688
                	if (! is_object($line)) $line = (object) $line;
689
690
				    if ($result >= 0)
691
					{
692
						// Reset fk_parent_line for no child products and special product
693
						if (($line->product_type != 9 && empty($line->fk_parent_line)) || $line->product_type == 9) {
694
							$fk_parent_line = 0;
695
						}
696
697
						// Complete vat rate with code
698
						$vatrate = $line->tva_tx;
699
						if ($line->vat_src_code && ! preg_match('/\(.*\)/', $vatrate)) $vatrate.=' ('.$line->vat_src_code.')';
700
701
                        $result = $this->addline(
702
							$line->desc,
703
							$line->subprice,
704
							$line->qty,
705
							$vatrate,
706
							$line->localtax1_tx,
707
							$line->localtax2_tx,
708
							$line->fk_product,
709
							$line->remise_percent,
710
							$line->date_start,
711
							$line->date_end,
712
							$line->fk_code_ventilation,
713
							$line->info_bits,
714
							$line->fk_remise_except,
715
							'HT',
716
							0,
717
							$line->product_type,
718
							$line->rang,
719
							$line->special_code,
720
                            $this->element,
721
                            $line->id,
722
							$fk_parent_line,
723
							$line->fk_fournprice,
724
							$line->pa_ht,
725
							$line->label,
726
							$line->array_options,
727
							$line->situation_percent,
728
							$line->fk_prev_id,
729
							$line->fk_unit,
730
							$line->pu_ht_devise
0 ignored issues
show
Bug introduced by
The property pu_ht_devise does not seem to exist on FactureLigne.
Loading history...
731
						);
732
						if ($result < 0)
733
						{
734
							$this->error=$this->db->lasterror();
735
							dol_print_error($this->db);
736
							$this->db->rollback();
737
							return -1;
738
						}
739
740
						// Defined the new fk_parent_line
741
						if ($result > 0 && $line->product_type == 9) {
742
							$fk_parent_line = $result;
743
						}
744
					}
745
				}
746
			}
747
748
			/*
749
			 * Insert lines of template invoices
750
			 */
751
			if (! $error && $this->fac_rec > 0)
752
			{
753
				foreach ($_facrec->lines as $i => $val)
754
				{
755
					if ($_facrec->lines[$i]->fk_product)
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $_facrec does not seem to be defined for all execution paths leading up to this point.
Loading history...
756
					{
757
						$prod = new Product($this->db);
758
						$res=$prod->fetch($_facrec->lines[$i]->fk_product);
759
					}
760
761
					// For line from template invoice, we use data from template invoice
762
					/*
763
					$tva_tx = get_default_tva($mysoc,$soc,$prod->id);
764
					$tva_npr = get_default_npr($mysoc,$soc,$prod->id);
765
					if (empty($tva_tx)) $tva_npr=0;
766
					$localtax1_tx=get_localtax($tva_tx,1,$soc,$mysoc,$tva_npr);
767
					$localtax2_tx=get_localtax($tva_tx,2,$soc,$mysoc,$tva_npr);
768
					*/
769
					$tva_tx = $_facrec->lines[$i]->tva_tx.($_facrec->lines[$i]->vat_src_code ? '('.$_facrec->lines[$i]->vat_src_code.')' : '');
770
					$tva_npr = $_facrec->lines[$i]->info_bits;
771
					if (empty($tva_tx)) $tva_npr=0;
772
					$localtax1_tx = $_facrec->lines[$i]->localtax1_tx;
773
					$localtax2_tx = $_facrec->lines[$i]->localtax2_tx;
774
775
					$fk_product_fournisseur_price = empty($_facrec->lines[$i]->fk_product_fournisseur_price)?null:$_facrec->lines[$i]->fk_product_fournisseur_price;
776
					$buyprice = empty($_facrec->lines[$i]->buyprice)?0:$_facrec->lines[$i]->buyprice;
777
					// If buyprice not defined from template invoice, we try to guess the best value
778
					if (! $buyprice && $_facrec->lines[$i]->fk_product > 0)
779
                    {
780
                        require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.product.class.php';
781
                        $producttmp = new ProductFournisseur($this->db);
782
                        $producttmp->fetch($_facrec->lines[$i]->fk_product);
783
784
                        // If margin module defined on costprice, we try the costprice
785
                        // If not defined or if module margin defined and pmp and stock module enabled, we try pmp price
786
                        // else we get the best supplier price
787
                        if ($conf->global->MARGIN_TYPE == 'costprice' && ! empty($producttmp->cost_price)) $buyprice = $producttmp->cost_price;
788
                        elseif (! empty($conf->stock->enabled) && ($conf->global->MARGIN_TYPE == 'costprice' || $conf->global->MARGIN_TYPE == 'pmp') && ! empty($producttmp->pmp)) $buyprice = $producttmp->pmp;
789
                        else {
790
                            if ($producttmp->find_min_price_product_fournisseur($_facrec->lines[$i]->fk_product) > 0)
791
                            {
792
                                if ($producttmp->product_fourn_price_id > 0)
793
                                {
794
                                    $buyprice = price2num($producttmp->fourn_unitprice * (1 - $producttmp->fourn_remise_percent/100) + $producttmp->fourn_remise, 'MU');
795
                                }
796
                            }
797
                        }
798
                    }
799
800
					$result_insert = $this->addline(
801
						$_facrec->lines[$i]->desc,
802
						$_facrec->lines[$i]->subprice,
803
						$_facrec->lines[$i]->qty,
804
						$tva_tx,
805
						$localtax1_tx,
806
						$localtax2_tx,
807
						$_facrec->lines[$i]->fk_product,
808
						$_facrec->lines[$i]->remise_percent,
809
						($_facrec->lines[$i]->date_start_fill == 1 && $originaldatewhen)?$originaldatewhen:'',
810
						($_facrec->lines[$i]->date_end_fill == 1 && $previousdaynextdatewhen)?$previousdaynextdatewhen:'',
1 ignored issue
show
Bug Best Practice introduced by
The expression $previousdaynextdatewhen of type integer|null is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
811
						0,
812
						$tva_npr,
813
						'',
814
						'HT',
815
						0,
816
						$_facrec->lines[$i]->product_type,
817
						$_facrec->lines[$i]->rang,
818
						$_facrec->lines[$i]->special_code,
819
						'',
820
						0,
821
						0,
822
					    $fk_product_fournisseur_price,
823
						$buyprice,
824
						$_facrec->lines[$i]->label,
825
						empty($_facrec->lines[$i]->array_options)?null:$_facrec->lines[$i]->array_options,
826
						$_facrec->lines[$i]->situation_percent,
827
						'',
828
						$_facrec->lines[$i]->fk_unit,
829
						$_facrec->lines[$i]->pu_ht_devise
830
					);
831
832
					if ( $result_insert < 0)
833
					{
834
						$error++;
835
						$this->error=$this->db->error();
836
						break;
837
					}
838
				}
839
			}
840
841
			if (! $error)
842
			{
843
844
				$result=$this->update_price(1);
845
				if ($result > 0)
846
				{
847
					$action='create';
848
849
					// Actions on extra fields
850
					if (! $error)
851
					{
852
					    $result=$this->insertExtraFields();
853
					    if ($result < 0) $error++;
854
					}
855
856
			        if (! $error && ! $notrigger)
857
			        {
858
			           // Call trigger
859
			           $result=$this->call_trigger('BILL_CREATE', $user);
860
			           if ($result < 0) $error++;
861
			           // End call triggers
862
			        }
863
864
					if (! $error)
865
					{
866
						$this->db->commit();
867
						return $this->id;
868
					}
869
					else
870
					{
871
						$this->db->rollback();
872
						return -4;
873
					}
874
				}
875
				else
876
				{
877
					$this->error=$langs->trans('FailedToUpdatePrice');
878
					$this->db->rollback();
879
					return -3;
880
				}
881
			}
882
			else
883
			{
884
				dol_syslog(get_class($this)."::create error ".$this->error, LOG_ERR);
885
				$this->db->rollback();
886
				return -2;
887
			}
888
		}
889
		else
890
		{
891
			$this->error=$this->db->error();
892
			$this->db->rollback();
893
			return -1;
894
		}
895
	}
896
897
898
	/**
899
	 *	Create a new invoice in database from current invoice
900
	 *
901
	 *	@param      User	$user    		Object user that ask creation
902
	 *	@param		int		$invertdetail	Reverse sign of amounts for lines
903
	 *	@return		int						<0 if KO, >0 if OK
904
	 */
905
    public function createFromCurrent(User $user, $invertdetail = 0)
906
	{
907
		global $conf;
908
909
		// Charge facture source
910
		$facture=new Facture($this->db);
911
912
		// Retreive all extrafield
913
		// fetch optionals attributes and labels
914
		$this->fetch_optionals();
915
916
        if(!empty($this->array_options)){
917
                    $facture->array_options = $this->array_options;
918
        }
919
920
        foreach($this->lines as &$line){
921
                    $line->fetch_optionals();//fetch extrafields
922
        }
923
924
		$facture->fk_facture_source = $this->fk_facture_source;
925
		$facture->type 			    = $this->type;
926
		$facture->socid 		    = $this->socid;
927
		$facture->date              = $this->date;
928
		$facture->date_pointoftax   = $this->date_pointoftax;
929
		$facture->note_public       = $this->note_public;
930
		$facture->note_private      = $this->note_private;
931
		$facture->ref_client        = $this->ref_client;
932
		$facture->modelpdf          = $this->modelpdf;
933
		$facture->fk_project        = $this->fk_project;
934
		$facture->cond_reglement_id = $this->cond_reglement_id;
935
		$facture->mode_reglement_id = $this->mode_reglement_id;
936
		$facture->remise_absolue    = $this->remise_absolue;
937
		$facture->remise_percent    = $this->remise_percent;
938
939
		$facture->origin                        = $this->origin;
940
		$facture->origin_id                     = $this->origin_id;
941
942
		$facture->lines		    	= $this->lines;	// Array of lines of invoice
943
		$facture->products		    = $this->lines;	// Tant que products encore utilise
1 ignored issue
show
Deprecated Code introduced by
The property Facture::$products has been deprecated. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

943
		/** @scrutinizer ignore-deprecated */ $facture->products		    = $this->lines;	// Tant que products encore utilise
Loading history...
944
		$facture->situation_counter = $this->situation_counter;
945
		$facture->situation_cycle_ref=$this->situation_cycle_ref;
946
		$facture->situation_final  = $this->situation_final;
947
948
		// Loop on each line of new invoice
949
		foreach($facture->lines as $i => $tmpline)
950
		{
951
			$facture->lines[$i]->fk_prev_id = $this->lines[$i]->rowid;
1 ignored issue
show
Deprecated Code introduced by
The property CommonObjectLine::$rowid has been deprecated: Try to use id property as possible (even if field into database is still rowid) ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

951
			$facture->lines[$i]->fk_prev_id = /** @scrutinizer ignore-deprecated */ $this->lines[$i]->rowid;

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
952
			if ($invertdetail)
953
			{
954
				$facture->lines[$i]->subprice  = -$facture->lines[$i]->subprice;
955
				$facture->lines[$i]->total_ht  = -$facture->lines[$i]->total_ht;
956
				$facture->lines[$i]->total_tva = -$facture->lines[$i]->total_tva;
957
				$facture->lines[$i]->total_localtax1 = -$facture->lines[$i]->total_localtax1;
958
				$facture->lines[$i]->total_localtax2 = -$facture->lines[$i]->total_localtax2;
959
				$facture->lines[$i]->total_ttc = -$facture->lines[$i]->total_ttc;
960
			}
961
		}
962
963
		dol_syslog(get_class($this)."::createFromCurrent invertdetail=".$invertdetail." socid=".$this->socid." nboflines=".count($facture->lines));
964
965
		$facid = $facture->create($user);
966
		if ($facid <= 0)
967
		{
968
			$this->error=$facture->error;
969
			$this->errors=$facture->errors;
970
		}
971
		elseif ($this->type == self::TYPE_SITUATION && !empty($conf->global->INVOICE_USE_SITUATION))
972
		{
973
			$this->fetchObjectLinked('', '', $facture->id, 'facture');
974
975
			foreach ($this->linkedObjectsIds as $typeObject => $Tfk_object)
976
			{
977
				foreach ($Tfk_object as $fk_object)
978
				{
979
					$facture->add_object_linked($typeObject, $fk_object);
980
				}
981
			}
982
983
			$facture->add_object_linked('facture', $this->fk_facture_source);
984
		}
985
986
		return $facid;
987
	}
988
989
990
	/**
991
	 *	Load an object from its id and create a new one in database
992
	 *
993
     *	@param      User	$user        	User that clone
994
	 *  @param  	int 	$fromid         Id of object to clone
995
	 * 	@return		int					    New id of clone
996
	 */
997
	public function createFromClone(User $user, $fromid = 0)
998
	{
999
		global $hookmanager;
1000
1001
		$error=0;
1002
1003
		$object=new Facture($this->db);
1004
1005
		$this->db->begin();
1006
1007
		$object->fetch($fromid);
1008
1009
		// Change socid if needed
1010
		if (! empty($this->socid) && $this->socid != $object->socid)
1011
		{
1012
			$objsoc = new Societe($this->db);
1013
1014
			if ($objsoc->fetch($this->socid)>0)
1015
			{
1016
			    $object->socid 				= $objsoc->id;
1017
			    $object->cond_reglement_id	= (! empty($objsoc->cond_reglement_id) ? $objsoc->cond_reglement_id : 0);
1018
			    $object->mode_reglement_id	= (! empty($objsoc->mode_reglement_id) ? $objsoc->mode_reglement_id : 0);
1019
			    $object->fk_project			= '';
1 ignored issue
show
Documentation Bug introduced by
The property $fk_project was declared of type integer, but '' is of type string. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
1020
			    $object->fk_delivery_address	= '';
2 ignored issues
show
Deprecated Code introduced by
The property CommonObject::$fk_delivery_address has been deprecated. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

1020
			    /** @scrutinizer ignore-deprecated */ $object->fk_delivery_address	= '';
Loading history...
Documentation Bug introduced by
The property $fk_delivery_address was declared of type integer, but '' is of type string. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
1021
			}
1022
1023
			// TODO Change product price if multi-prices
1024
		}
1025
1026
		$object->id=0;
1027
		$object->statut= self::STATUS_DRAFT;
1028
1029
		// Clear fields
1030
		$object->date               = (empty($this->date) ? dol_now() : $this->date);
1031
		$object->user_author        = $user->id;
1032
		$object->user_valid         = '';
1033
		$object->fk_facture_source  = 0;
1034
		$object->date_creation      = '';
1035
		$object->date_modification = '';
1036
		$object->date_validation    = '';
1037
		$object->ref_client         = '';
1038
		$object->close_code         = '';
1039
		$object->close_note         = '';
1040
		$object->products = $object->lines;	        // For backward compatibility
1 ignored issue
show
Deprecated Code introduced by
The property Facture::$products has been deprecated. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

1040
		/** @scrutinizer ignore-deprecated */ $object->products = $object->lines;	        // For backward compatibility
Loading history...
1041
1042
		// Loop on each line of new invoice
1043
		foreach($object->lines as $i => $line)
1044
		{
1045
		    if (($object->lines[$i]->info_bits & 0x02) == 0x02)	// We do not clone line of discounts
1046
			{
1047
			    unset($object->lines[$i]);
1048
			    unset($object->products[$i]);	// Tant que products encore utilise
1 ignored issue
show
Deprecated Code introduced by
The property Facture::$products has been deprecated. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

1048
			    unset(/** @scrutinizer ignore-deprecated */ $object->products[$i]);	// Tant que products encore utilise
Loading history...
1049
			}
1050
		}
1051
1052
		// Create clone
1053
		$object->context['createfromclone'] = 'createfromclone';
1054
		$result=$object->create($user);
1055
		if ($result < 0) $error++;
1056
		else {
1057
			// copy internal contacts
1058
		    if ($object->copy_linked_contact($this, 'internal') < 0)
1059
				$error++;
1060
1061
			// copy external contacts if same company
1062
			elseif ($this->socid == $object->socid)
1063
			{
1064
			    if ($object->copy_linked_contact($this, 'external') < 0)
1065
					$error++;
1066
			}
1067
		}
1068
1069
		if (! $error)
1070
		{
1071
			// Hook of thirdparty module
1072
			if (is_object($hookmanager))
1073
			{
1074
				$parameters=array('objFrom'=>$this);
1075
				$action='';
1076
				$reshook=$hookmanager->executeHooks('createFrom', $parameters, $object, $action);    // Note that $action and $object may have been modified by some hooks
1077
				if ($reshook < 0) $error++;
1078
			}
1079
		}
1080
1081
		unset($object->context['createfromclone']);
1082
1083
		// End
1084
		if (! $error)
1085
		{
1086
			$this->db->commit();
1087
			return $object->id;
1088
		}
1089
		else
1090
		{
1091
			$this->db->rollback();
1092
			return -1;
1093
		}
1094
	}
1095
1096
	/**
1097
	 *  Load an object from an order and create a new invoice into database
1098
	 *
1099
	 *  @param      Object			$object         	Object source
1100
	 *  @param		User			$user				Object user
1101
	 *  @return     int             					<0 if KO, 0 if nothing done, 1 if OK
1102
	 */
1103
    public function createFromOrder($object, User $user)
1104
	{
1105
		global $conf, $hookmanager;
1106
1107
		$error=0;
1108
1109
		// Closed order
1110
		$this->date = dol_now();
1111
		$this->source = 0;
1112
1113
		$num=count($object->lines);
1114
		for ($i = 0; $i < $num; $i++)
1115
		{
1116
			$line = new FactureLigne($this->db);
1117
1118
			$line->libelle			= $object->lines[$i]->libelle;
1 ignored issue
show
Deprecated Code introduced by
The property FactureLigne::$libelle has been deprecated. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

1118
			/** @scrutinizer ignore-deprecated */ $line->libelle			= $object->lines[$i]->libelle;
Loading history...
1119
			$line->label			= $object->lines[$i]->label;
1 ignored issue
show
Deprecated Code introduced by
The property FactureLigne::$label has been deprecated. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

1119
			/** @scrutinizer ignore-deprecated */ $line->label			= $object->lines[$i]->label;
Loading history...
1120
			$line->desc				= $object->lines[$i]->desc;
1121
			$line->subprice			= $object->lines[$i]->subprice;
1122
			$line->total_ht			= $object->lines[$i]->total_ht;
1123
			$line->total_tva		= $object->lines[$i]->total_tva;
1124
			$line->total_localtax1	= $object->lines[$i]->total_localtax1;
1125
			$line->total_localtax2	= $object->lines[$i]->total_localtax2;
1126
			$line->total_ttc		= $object->lines[$i]->total_ttc;
1127
			$line->vat_src_code  	= $object->lines[$i]->vat_src_code;
1128
			$line->tva_tx			= $object->lines[$i]->tva_tx;
1129
			$line->localtax1_tx		= $object->lines[$i]->localtax1_tx;
1130
			$line->localtax2_tx		= $object->lines[$i]->localtax2_tx;
1131
			$line->qty				= $object->lines[$i]->qty;
1132
			$line->fk_remise_except	= $object->lines[$i]->fk_remise_except;
1133
			$line->remise_percent	= $object->lines[$i]->remise_percent;
1134
			$line->fk_product		= $object->lines[$i]->fk_product;
1135
			$line->info_bits		= $object->lines[$i]->info_bits;
1136
			$line->product_type		= $object->lines[$i]->product_type;
1137
			$line->rang				= $object->lines[$i]->rang;
1138
			$line->special_code		= $object->lines[$i]->special_code;
1139
			$line->fk_parent_line	= $object->lines[$i]->fk_parent_line;
1140
			$line->fk_unit			= $object->lines[$i]->fk_unit;
1141
			$line->date_start 		= $object->lines[$i]->date_start;
1142
			$line->date_end 		= $object->lines[$i]->date_end;
1143
1144
			$line->fk_fournprice	= $object->lines[$i]->fk_fournprice;
1145
			$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);
1146
			$line->pa_ht			= $marginInfos[0];
1147
1148
            // get extrafields from original line
1149
			$object->lines[$i]->fetch_optionals();
1150
			foreach($object->lines[$i]->array_options as $options_key => $value)
1151
				$line->array_options[$options_key] = $value;
1152
1153
			$this->lines[$i] = $line;
1154
		}
1155
1156
		$this->socid                = $object->socid;
1157
		$this->fk_project           = $object->fk_project;
1158
		$this->cond_reglement_id    = $object->cond_reglement_id;
1159
		$this->mode_reglement_id    = $object->mode_reglement_id;
1160
		$this->availability_id      = $object->availability_id;
1161
		$this->demand_reason_id     = $object->demand_reason_id;
1162
		$this->date_livraison       = $object->date_livraison;
1163
		$this->fk_delivery_address  = $object->fk_delivery_address;
1 ignored issue
show
Deprecated Code introduced by
The property CommonObject::$fk_delivery_address has been deprecated. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

1163
		/** @scrutinizer ignore-deprecated */ $this->fk_delivery_address  = $object->fk_delivery_address;
Loading history...
1164
		$this->contact_id           = $object->contactid;
1165
		$this->ref_client           = $object->ref_client;
1166
1167
		if (empty($conf->global->MAIN_DISABLE_PROPAGATE_NOTES_FROM_ORIGIN))
1168
		{
1169
		    $this->note_private         = $object->note_private;
1170
            $this->note_public          = $object->note_public;
1171
		}
1172
1173
        $this->module_source		= $object->module_source;
1174
		$this->pos_source			= $object->pos_source;
1175
1176
		$this->origin				= $object->element;
1177
		$this->origin_id			= $object->id;
1178
1179
        // get extrafields from original line
1180
		$object->fetch_optionals($object->id);
1181
		foreach($object->array_options as $options_key => $value)
1182
			$this->array_options[$options_key] = $value;
1183
1184
		// Possibility to add external linked objects with hooks
1185
		$this->linked_objects[$this->origin] = $this->origin_id;
1186
		if (! empty($object->other_linked_objects) && is_array($object->other_linked_objects))
1187
		{
1188
			$this->linked_objects = array_merge($this->linked_objects, $object->other_linked_objects);
1189
		}
1190
1191
		$ret = $this->create($user);
1192
1193
		if ($ret > 0)
1194
		{
1195
			// Actions hooked (by external module)
1196
			$hookmanager->initHooks(array('invoicedao'));
1197
1198
			$parameters=array('objFrom'=>$object);
1199
			$action='';
1200
			$reshook=$hookmanager->executeHooks('createFrom', $parameters, $this, $action);    // Note that $action and $object may have been modified by some hooks
1201
			if ($reshook < 0) $error++;
1202
1203
			if (! $error)
1204
			{
1205
				return 1;
1206
			}
1207
			else return -1;
1208
		}
1209
		else return -1;
1210
	}
1211
1212
	/**
1213
	 *  Return clicable link of object (with eventually picto)
1214
	 *
1215
	 *  @param	int		$withpicto       			Add picto into link
1216
	 *  @param  string	$option          			Where point the link
1217
	 *  @param  int		$max             			Maxlength of ref
1218
	 *  @param  int		$short           			1=Return just URL
1219
	 *  @param  string  $moretitle       			Add more text to title tooltip
1220
     *  @param	int  	$notooltip		 			1=Disable tooltip
1221
     *  @param  int     $addlinktonotes  			1=Add link to notes
1222
     *  @param  int     $save_lastsearch_value		-1=Auto, 0=No save of lastsearch_values when clicking, 1=Save lastsearch_values whenclicking
1223
     *  @param  string  $target                     Target of link ('', '_self', '_blank', '_parent', '_backoffice', ...)
1224
	 *  @return string 			         			String with URL
1225
	 */
1226
    public function getNomUrl($withpicto = 0, $option = '', $max = 0, $short = 0, $moretitle = '', $notooltip = 0, $addlinktonotes = 0, $save_lastsearch_value = -1, $target = '')
1227
	{
1228
		global $langs, $conf, $user;
1229
1230
		if (! empty($conf->dol_no_mouse_hover)) $notooltip=1;   // Force disable tooltips
1231
1232
		$result='';
1233
1234
		if ($option == 'withdraw') $url = DOL_URL_ROOT.'/compta/facture/prelevement.php?facid='.$this->id;
1235
		else $url = DOL_URL_ROOT.'/compta/facture/card.php?facid='.$this->id;
1236
1237
        if (!$user->rights->facture->lire)
1238
            $option = 'nolink';
1239
1240
		if ($option !== 'nolink')
1241
		{
1242
			// Add param to save lastsearch_values or not
1243
			$add_save_lastsearch_values=($save_lastsearch_value == 1 ? 1 : 0);
1244
			if ($save_lastsearch_value == -1 && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) $add_save_lastsearch_values=1;
1245
			if ($add_save_lastsearch_values) $url.='&save_lastsearch_values=1';
1246
		}
1247
1248
		if ($short) return $url;
1249
1250
		$picto=$this->picto;
1251
		if ($this->type == self::TYPE_REPLACEMENT) $picto.='r';	// Replacement invoice
1252
		if ($this->type == self::TYPE_CREDIT_NOTE) $picto.='a';	// Credit note
1253
		if ($this->type == self::TYPE_DEPOSIT) $picto.='d';	// Deposit invoice
1254
        $label='';
1255
1256
        if ($user->rights->facture->lire) {
1257
            $label = '<u>' . $langs->trans("ShowInvoice") . '</u>';
1258
            if ($this->type == self::TYPE_REPLACEMENT) $label='<u>' . $langs->transnoentitiesnoconv("ShowInvoiceReplace") . '</u>';
1259
            if ($this->type == self::TYPE_CREDIT_NOTE) $label='<u>' . $langs->transnoentitiesnoconv("ShowInvoiceAvoir") . '</u>';
1260
            if ($this->type == self::TYPE_DEPOSIT)     $label='<u>' . $langs->transnoentitiesnoconv("ShowInvoiceDeposit") . '</u>';
1261
            if ($this->type == self::TYPE_SITUATION)   $label='<u>' . $langs->transnoentitiesnoconv("ShowInvoiceSituation") . '</u>';
1262
            if (! empty($this->ref))
1263
                $label .= '<br><b>'.$langs->trans('Ref') . ':</b> ' . $this->ref;
1264
            if (! empty($this->ref_client))
1265
                $label .= '<br><b>' . $langs->trans('RefCustomer') . ':</b> ' . $this->ref_client;
1266
            if (! empty($this->date))
1267
              	$label .= '<br><b>' . $langs->trans('Date') . ':</b> ' . dol_print_date($this->date, 'day');
1268
            if (! empty($this->total_ht))
1269
                $label.= '<br><b>' . $langs->trans('AmountHT') . ':</b> ' . price($this->total_ht, 0, $langs, 0, -1, -1, $conf->currency);
1270
            if (! empty($this->total_tva))
1271
                $label.= '<br><b>' . $langs->trans('VAT') . ':</b> ' . price($this->total_tva, 0, $langs, 0, -1, -1, $conf->currency);
1272
            if (! empty($this->total_localtax1) && $this->total_localtax1 != 0)		// We keep test != 0 because $this->total_localtax1 can be '0.00000000'
1273
                $label.= '<br><b>' . $langs->trans('LT1') . ':</b> ' . price($this->total_localtax1, 0, $langs, 0, -1, -1, $conf->currency);
1274
            if (! empty($this->total_localtax2) && $this->total_localtax2 != 0)
1275
                $label.= '<br><b>' . $langs->trans('LT2') . ':</b> ' . price($this->total_localtax2, 0, $langs, 0, -1, -1, $conf->currency);
1276
            if (! empty($this->total_ttc))
1277
                $label.= '<br><b>' . $langs->trans('AmountTTC') . ':</b> ' . price($this->total_ttc, 0, $langs, 0, -1, -1, $conf->currency);
1278
    		if ($moretitle) $label.=' - '.$moretitle;
1279
        }
1280
1281
		$linkclose=($target?' target="'.$target.'"':'');
1282
		if (empty($notooltip) && $user->rights->facture->lire)
1283
		{
1284
		    if (! empty($conf->global->MAIN_OPTIMIZEFORTEXTBROWSER))
1285
		    {
1286
		        $label=$langs->trans("ShowInvoice");
1287
		        $linkclose.=' alt="'.dol_escape_htmltag($label, 1).'"';
1288
		    }
1289
		    $linkclose.= ' title="'.dol_escape_htmltag($label, 1).'"';
1290
		    $linkclose.=' class="classfortooltip"';
1291
		}
1292
1293
        $linkstart='<a href="'.$url.'"';
1294
        $linkstart.=$linkclose.'>';
1295
		$linkend='</a>';
1296
1297
        if ($option == 'nolink') {
1298
            $linkstart = '';
1299
            $linkend = '';
1300
        }
1301
1302
		$result .= $linkstart;
1303
		if ($withpicto) $result.=img_object(($notooltip?'':$label), $picto, ($notooltip?(($withpicto != 2) ? 'class="paddingright"' : ''):'class="'.(($withpicto != 2) ? 'paddingright ' : '').'classfortooltip"'), 0, 0, $notooltip?0:1);
1304
		if ($withpicto != 2) $result.= ($max?dol_trunc($this->ref, $max):$this->ref);
1305
		$result .= $linkend;
1306
1307
		if ($addlinktonotes)
1308
		{
1309
		    $txttoshow=($user->socid > 0 ? $this->note_public : $this->note_private);
1310
		    if ($txttoshow)
1311
		    {
1312
                $notetoshow=$langs->trans("ViewPrivateNote").':<br>'.dol_string_nohtmltag($txttoshow, 1);
1313
    		    $result.=' <span class="note inline-block">';
1314
    		    $result.='<a href="'.DOL_URL_ROOT.'/compta/facture/note.php?id='.$this->id.'" class="classfortooltip" title="'.dol_escape_htmltag($notetoshow).'">';
1315
    		    $result.=img_picto('', 'note');
1316
    		    $result.='</a>';
1317
    		    //$result.=img_picto($langs->trans("ViewNote"),'object_generic');
1318
    		    //$result.='</a>';
1319
    		    $result.='</span>';
1320
		    }
1321
		}
1322
1323
		return $result;
1324
	}
1325
1326
	/**
1327
	 *	Get object and lines from database
1328
	 *
1329
	 *	@param      int		$rowid       	Id of object to load
1330
	 * 	@param		string	$ref			Reference of invoice
1331
	 * 	@param		string	$ref_ext		External reference of invoice
1332
	 * 	@param		int		$ref_int		Internal reference of other object
1333
	 *  @param		bool	$fetch_situation	Fetch the previous and next situation in $tab_previous_situation_invoice and $tab_next_situation_invoice
1334
	 *	@return     int         			>0 if OK, <0 if KO, 0 if not found
1335
	 */
1336
    public function fetch($rowid, $ref = '', $ref_ext = '', $ref_int = '', $fetch_situation = false)
1337
	{
1338
		global $conf;
1339
1340
		if (empty($rowid) && empty($ref) && empty($ref_ext) && empty($ref_int)) return -1;
1341
1342
		$sql = 'SELECT f.rowid,f.entity,f.ref,f.ref_client,f.ref_ext,f.ref_int,f.type,f.fk_soc,f.amount';
1343
		$sql.= ', f.tva, f.localtax1, f.localtax2, f.total, f.total_ttc, f.revenuestamp';
1344
		$sql.= ', f.remise_percent, f.remise_absolue, f.remise';
1345
		$sql.= ', f.datef as df, f.date_pointoftax';
1346
		$sql.= ', f.date_lim_reglement as dlr';
1347
		$sql.= ', f.datec as datec';
1348
		$sql.= ', f.date_valid as datev';
1349
		$sql.= ', f.tms as datem';
1350
		$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.model_pdf, f.last_main_doc';
1351
		$sql.= ', f.fk_facture_source, f.fk_fac_rec_source';
1352
		$sql.= ', f.fk_mode_reglement, f.fk_cond_reglement, f.fk_projet as fk_project, f.extraparams';
1353
		$sql.= ', f.situation_cycle_ref, f.situation_counter, f.situation_final';
1354
		$sql.= ', f.fk_account';
1355
		$sql.= ", f.fk_multicurrency, f.multicurrency_code, f.multicurrency_tx, f.multicurrency_total_ht, f.multicurrency_total_tva, f.multicurrency_total_ttc";
1356
		$sql.= ', p.code as mode_reglement_code, p.libelle as mode_reglement_libelle';
1357
		$sql.= ', c.code as cond_reglement_code, c.libelle as cond_reglement_libelle, c.libelle_facture as cond_reglement_libelle_doc';
1358
        $sql.= ', f.fk_incoterms, f.location_incoterms';
1359
        $sql.= ', f.module_source, f.pos_source';
1360
        $sql.= ", i.libelle as label_incoterms";
1361
        $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";
1362
		$sql.= ' FROM '.MAIN_DB_PREFIX.'facture as f';
1363
		$sql.= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_payment_term as c ON f.fk_cond_reglement = c.rowid';
1364
		$sql.= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_paiement as p ON f.fk_mode_reglement = p.id';
1365
		$sql.= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_incoterms as i ON f.fk_incoterms = i.rowid';
1366
1367
		if ($rowid)   $sql.= " WHERE f.rowid=".$rowid;
1368
		else $sql.= ' WHERE f.entity IN ('.getEntity('invoice').')'; // Dont't use entity if you use rowid
1369
1370
		if ($ref)     $sql.= " AND f.ref='".$this->db->escape($ref)."'";
1371
		if ($ref_ext) $sql.= " AND f.ref_ext='".$this->db->escape($ref_ext)."'";
1372
		if ($ref_int) $sql.= " AND f.ref_int='".$this->db->escape($ref_int)."'";
1373
1374
		dol_syslog(get_class($this)."::fetch", LOG_DEBUG);
1375
		$result = $this->db->query($sql);
1376
		if ($result)
1377
		{
1378
			if ($this->db->num_rows($result))
1379
			{
1380
				$obj = $this->db->fetch_object($result);
1381
1382
				$this->id					= $obj->rowid;
1383
				$this->entity				= $obj->entity;
1384
1385
				$this->ref					= $obj->ref;
1386
				$this->ref_client			= $obj->ref_client;
1387
				$this->ref_ext				= $obj->ref_ext;
1388
				$this->ref_int				= $obj->ref_int;
1389
				$this->type					= $obj->type;
1390
				$this->date					= $this->db->jdate($obj->df);
1391
				$this->date_pointoftax		= $this->db->jdate($obj->date_pointoftax);
1392
				$this->date_creation		= $this->db->jdate($obj->datec);
1393
				$this->date_validation		= $this->db->jdate($obj->datev);
1394
				$this->date_modification	= $this->db->jdate($obj->datem);
1395
				$this->datem				= $this->db->jdate($obj->datem);
1396
				$this->remise_percent		= $obj->remise_percent;
1397
				$this->remise_absolue		= $obj->remise_absolue;
1398
				$this->total_ht				= $obj->total;
1399
				$this->total_tva			= $obj->tva;
1400
				$this->total_localtax1		= $obj->localtax1;
1401
				$this->total_localtax2		= $obj->localtax2;
1402
				$this->total_ttc			= $obj->total_ttc;
1403
				$this->revenuestamp         = $obj->revenuestamp;
1404
				$this->paye					= $obj->paye;
1405
				$this->close_code			= $obj->close_code;
1406
				$this->close_note			= $obj->close_note;
1407
				$this->socid				= $obj->fk_soc;
1408
				$this->statut				= $obj->fk_statut;
1409
				$this->date_lim_reglement	= $this->db->jdate($obj->dlr);
1410
				$this->mode_reglement_id	= $obj->fk_mode_reglement;
1411
				$this->mode_reglement_code	= $obj->mode_reglement_code;
1412
				$this->mode_reglement		= $obj->mode_reglement_libelle;
1413
				$this->cond_reglement_id	= $obj->fk_cond_reglement;
1414
				$this->cond_reglement_code	= $obj->cond_reglement_code;
1415
				$this->cond_reglement		= $obj->cond_reglement_libelle;
1416
				$this->cond_reglement_doc	= $obj->cond_reglement_libelle_doc;
1417
				$this->fk_account           = ($obj->fk_account>0)?$obj->fk_account:null;
1418
				$this->fk_project			= $obj->fk_project;
1419
				$this->fk_facture_source	= $obj->fk_facture_source;
1420
				$this->fk_fac_rec_source	= $obj->fk_fac_rec_source;
1421
				$this->note					= $obj->note_private;	// deprecated
1422
				$this->note_private			= $obj->note_private;
1423
				$this->note_public			= $obj->note_public;
1424
				$this->user_author			= $obj->fk_user_author;
1425
				$this->user_valid			= $obj->fk_user_valid;
1426
				$this->modelpdf				= $obj->model_pdf;
1427
				$this->last_main_doc		= $obj->last_main_doc;
1428
				$this->situation_cycle_ref  = $obj->situation_cycle_ref;
1429
				$this->situation_counter    = $obj->situation_counter;
1430
				$this->situation_final      = $obj->situation_final;
1431
				$this->retained_warranty    = $obj->retained_warranty;
1432
				$this->retained_warranty_date_limit         = $this->db->jdate($obj->retained_warranty_date_limit);
1433
				$this->retained_warranty_fk_cond_reglement  = $obj->retained_warranty_fk_cond_reglement;
1434
				
1435
				$this->extraparams			= (array) json_decode($obj->extraparams, true);
1436
1437
				//Incoterms
1438
				$this->fk_incoterms         = $obj->fk_incoterms;
1439
				$this->location_incoterms   = $obj->location_incoterms;
1440
				$this->label_incoterms    = $obj->label_incoterms;
1441
1442
  				$this->module_source        = $obj->module_source;
1443
				$this->pos_source           = $obj->pos_source;
1444
1445
				// Multicurrency
1446
				$this->fk_multicurrency 		= $obj->fk_multicurrency;
1447
				$this->multicurrency_code 		= $obj->multicurrency_code;
1448
				$this->multicurrency_tx 		= $obj->multicurrency_tx;
1449
				$this->multicurrency_total_ht 	= $obj->multicurrency_total_ht;
1450
				$this->multicurrency_total_tva 	= $obj->multicurrency_total_tva;
1451
				$this->multicurrency_total_ttc 	= $obj->multicurrency_total_ttc;
1452
1453
				if (($this->type == self::TYPE_SITUATION || ($this->type == self::TYPE_CREDIT_NOTE && $this->situation_cycle_ref > 0))  && $fetch_situation)
1 ignored issue
show
introduced by
Consider adding parentheses for clarity. Current Interpretation: ($this->type == self::TY... 0) && $fetch_situation, Probably Intended Meaning: $this->type == self::TYP... 0 && $fetch_situation)
Loading history...
1454
				{
1455
					$this->fetchPreviousNextSituationInvoice();
1456
				}
1457
1458
				if ($this->statut == self::STATUS_DRAFT)	$this->brouillon = 1;
1459
1460
				// Retreive all extrafield
1461
				// fetch optionals attributes and labels
1462
				$this->fetch_optionals();
1463
1464
				/*
1465
				 * Lines
1466
				 */
1467
1468
				$this->lines  = array();
1469
1470
				$result=$this->fetch_lines();
1471
				if ($result < 0)
1472
				{
1473
					$this->error=$this->db->error();
1474
					return -3;
1475
				}
1476
				return 1;
1477
			}
1478
			else
1479
			{
1480
				$this->error='Invoice with id='.$rowid.' or ref='.$ref.' or ref_ext='.$ref_ext.' not found';
1481
				dol_syslog(get_class($this)."::fetch Error ".$this->error, LOG_ERR);
1482
				return 0;
1483
			}
1484
		}
1485
		else
1486
		{
1487
			$this->error=$this->db->error();
1488
			return -1;
1489
		}
1490
	}
1491
1492
1493
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1494
	/**
1495
	 *	Load all detailed lines into this->lines
1496
	 *
1497
	 *	@param		int		$only_product	Return only physical products
1498
	 *	@param		int		$loadalsotranslation	Return translation for products
1499
	 *
1500
	 *	@return     int         1 if OK, < 0 if KO
1501
	 */
1502
	public function fetch_lines($only_product = 0, $loadalsotranslation = 0)
1503
	{
1504
		global $langs, $conf;
1505
        // phpcs:enable
1506
		$this->lines=array();
1507
1508
		$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,';
1509
		$sql.= ' l.situation_percent, l.fk_prev_id,';
1510
		$sql.= ' l.localtax1_tx, l.localtax2_tx, l.localtax1_type, l.localtax2_type, l.remise_percent, l.fk_remise_except, l.subprice,';
1511
		$sql.= ' l.rang, l.special_code,';
1512
		$sql.= ' l.date_start as date_start, l.date_end as date_end,';
1513
		$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,';
1514
		$sql.= ' l.fk_unit,';
1515
		$sql.= ' l.fk_multicurrency, l.multicurrency_code, l.multicurrency_subprice, l.multicurrency_total_ht, l.multicurrency_total_tva, l.multicurrency_total_ttc,';
1516
		$sql.= ' p.ref as product_ref, p.fk_product_type as fk_product_type, p.label as product_label, p.description as product_desc';
1517
		$sql.= ' FROM '.MAIN_DB_PREFIX.'facturedet as l';
1518
		$sql.= ' LEFT JOIN '.MAIN_DB_PREFIX.'product as p ON l.fk_product = p.rowid';
1519
		$sql.= ' WHERE l.fk_facture = '.$this->id;
1520
		$sql.= ' ORDER BY l.rang, l.rowid';
1521
1522
		dol_syslog(get_class($this).'::fetch_lines', LOG_DEBUG);
1523
		$result = $this->db->query($sql);
1524
		if ($result)
1525
		{
1526
			$num = $this->db->num_rows($result);
1527
			$i = 0;
1528
			while ($i < $num)
1529
			{
1530
				$objp = $this->db->fetch_object($result);
1531
				$line = new FactureLigne($this->db);
1532
1533
				$line->id               = $objp->rowid;
1534
				$line->rowid	        = $objp->rowid;             // deprecated
1535
				$line->fk_facture       = $objp->fk_facture;
1536
				$line->label            = $objp->custom_label;		// deprecated
1537
				$line->desc             = $objp->description;		// Description line
1538
				$line->description      = $objp->description;		// Description line
1539
				$line->product_type     = $objp->product_type;		// Type of line
1540
				$line->ref              = $objp->product_ref;		// Ref product
1541
				$line->product_ref      = $objp->product_ref;		// Ref product
1542
				$line->libelle          = $objp->product_label;		// TODO deprecated
1543
				$line->product_label	= $objp->product_label;		// Label product
1544
				$line->product_desc     = $objp->product_desc;		// Description product
1545
				$line->fk_product_type  = $objp->fk_product_type;	// Type of product
1546
				$line->qty              = $objp->qty;
1547
				$line->subprice         = $objp->subprice;
1548
1549
                $line->vat_src_code     = $objp->vat_src_code;
1550
				$line->tva_tx           = $objp->tva_tx;
1551
				$line->localtax1_tx     = $objp->localtax1_tx;
1552
				$line->localtax2_tx     = $objp->localtax2_tx;
1553
				$line->localtax1_type   = $objp->localtax1_type;
1554
				$line->localtax2_type   = $objp->localtax2_type;
1555
				$line->remise_percent   = $objp->remise_percent;
1556
				$line->fk_remise_except = $objp->fk_remise_except;
1557
				$line->fk_product       = $objp->fk_product;
1558
				$line->date_start       = $this->db->jdate($objp->date_start);
1559
				$line->date_end         = $this->db->jdate($objp->date_end);
1560
				$line->date_start       = $this->db->jdate($objp->date_start);
1561
				$line->date_end         = $this->db->jdate($objp->date_end);
1562
				$line->info_bits        = $objp->info_bits;
1563
				$line->total_ht         = $objp->total_ht;
1564
				$line->total_tva        = $objp->total_tva;
1565
				$line->total_localtax1  = $objp->total_localtax1;
1566
				$line->total_localtax2  = $objp->total_localtax2;
1567
				$line->total_ttc        = $objp->total_ttc;
1568
				$line->code_ventilation = $objp->fk_code_ventilation;
1569
				$line->fk_fournprice 	= $objp->fk_fournprice;
1570
				$marginInfos			= getMarginInfos($objp->subprice, $objp->remise_percent, $objp->tva_tx, $objp->localtax1_tx, $objp->localtax2_tx, $line->fk_fournprice, $objp->pa_ht);
1571
				$line->pa_ht 			= $marginInfos[0];
1572
				$line->marge_tx			= $marginInfos[1];
1573
				$line->marque_tx		= $marginInfos[2];
1574
				$line->rang				= $objp->rang;
1575
				$line->special_code		= $objp->special_code;
1576
				$line->fk_parent_line	= $objp->fk_parent_line;
1577
				$line->situation_percent= $objp->situation_percent;
1578
				$line->fk_prev_id       = $objp->fk_prev_id;
1579
				$line->fk_unit	        = $objp->fk_unit;
1580
1581
				// Accountancy
1582
				$line->fk_accounting_account	= $objp->fk_code_ventilation;
1583
1584
				// Multicurrency
1585
				$line->fk_multicurrency 		= $objp->fk_multicurrency;
1586
				$line->multicurrency_code 		= $objp->multicurrency_code;
1587
				$line->multicurrency_subprice 	= $objp->multicurrency_subprice;
1588
				$line->multicurrency_total_ht 	= $objp->multicurrency_total_ht;
1589
				$line->multicurrency_total_tva 	= $objp->multicurrency_total_tva;
1590
				$line->multicurrency_total_ttc 	= $objp->multicurrency_total_ttc;
1591
1592
                $line->fetch_optionals();
1593
1594
				// multilangs
1595
        		if (! empty($conf->global->MAIN_MULTILANGS) && ! empty($objp->fk_product) && ! empty($loadalsotranslation)) {
1596
        		$line = new Product($this->db);
1597
        		$line->fetch($objp->fk_product);
1598
        		$line->getMultiLangs();
1599
        		}
1600
1601
				$this->lines[$i] = $line;
1602
1603
				$i++;
1604
			}
1605
			$this->db->free($result);
1606
			return 1;
1607
		}
1608
		else
1609
		{
1610
			$this->error=$this->db->error();
1611
			return -3;
1612
		}
1613
	}
1614
1615
	/**
1616
	 * Fetch previous and next situations invoices
1617
	 *
1618
	 * @return	void
1619
	 */
1620
    public function fetchPreviousNextSituationInvoice()
1621
	{
1622
		global $conf;
1623
1624
		$this->tab_previous_situation_invoice = array();
1625
		$this->tab_next_situation_invoice = array();
1626
1627
		$sql = 'SELECT rowid, situation_counter FROM '.MAIN_DB_PREFIX.'facture WHERE rowid <> '.$this->id.' AND entity = '.$conf->entity.' AND situation_cycle_ref = '.(int) $this->situation_cycle_ref.' ORDER BY situation_counter ASC';
1628
1629
		dol_syslog(get_class($this).'::fetchPreviousNextSituationInvoice ', LOG_DEBUG);
1630
		$result = $this->db->query($sql);
1631
		if ($result && $this->db->num_rows($result) > 0)
1632
		{
1633
			while ($objp = $this->db->fetch_object($result))
1634
			{
1635
				$invoice = new Facture($this->db);
1636
				if ($invoice->fetch($objp->rowid) > 0)
1637
				{
1638
				    if ($objp->situation_counter < $this->situation_counter
1639
				        || ($objp->situation_counter == $this->situation_counter && $objp->rowid < $this->id) // This case appear when there are credit notes
1640
				       )
1641
					{
1642
					    $this->tab_previous_situation_invoice[] = $invoice;
1643
					}
1644
					else
1645
					{
1646
					    $this->tab_next_situation_invoice[] = $invoice;
1647
					}
1648
				}
1649
			}
1650
		}
1651
	}
1652
1653
	/**
1654
	 *      Update database
1655
	 *
1656
	 *      @param      User	$user        	User that modify
1657
	 *      @param      int		$notrigger	    0=launch triggers after, 1=disable triggers
1658
	 *      @return     int      			   	<0 if KO, >0 if OK
1659
	 */
1660
    public function update(User $user, $notrigger = 0)
1661
	{
1662
		global $conf;
1663
1664
		$error=0;
1665
1666
		// Clean parameters
1667
		if (empty($this->type)) $this->type= self::TYPE_STANDARD;
1668
		if (isset($this->ref)) $this->ref=trim($this->ref);
1669
		if (isset($this->ref_client)) $this->ref_client=trim($this->ref_client);
1670
		if (isset($this->increment)) $this->increment=trim($this->increment);
1671
		if (isset($this->close_code)) $this->close_code=trim($this->close_code);
1672
		if (isset($this->close_note)) $this->close_note=trim($this->close_note);
1673
		if (isset($this->note) || isset($this->note_private)) $this->note=(isset($this->note) ? trim($this->note) : trim($this->note_private));		// deprecated
1674
		if (isset($this->note) || isset($this->note_private)) $this->note_private=(isset($this->note_private) ? trim($this->note_private) : trim($this->note));
1675
		if (isset($this->note_public)) $this->note_public=trim($this->note_public);
1676
		if (isset($this->modelpdf)) $this->modelpdf=trim($this->modelpdf);
1677
		if (isset($this->import_key)) $this->import_key=trim($this->import_key);
1678
		if (isset($this->retained_warranty)) $this->retained_warranty = floatval($this->retained_warranty);
1679
		
1680
1681
		// Check parameters
1682
		// Put here code to add control on parameters values
1683
1684
		// Update request
1685
		$sql = "UPDATE ".MAIN_DB_PREFIX."facture SET";
1686
		$sql.= " ref=".(isset($this->ref)?"'".$this->db->escape($this->ref)."'":"null").",";
1687
		$sql.= " type=".(isset($this->type)?$this->db->escape($this->type):"null").",";
1688
		$sql.= " ref_client=".(isset($this->ref_client)?"'".$this->db->escape($this->ref_client)."'":"null").",";
1689
		$sql.= " increment=".(isset($this->increment)?"'".$this->db->escape($this->increment)."'":"null").",";
1690
		$sql.= " fk_soc=".(isset($this->socid)?$this->db->escape($this->socid):"null").",";
1691
		$sql.= " datec=".(strval($this->date_creation)!='' ? "'".$this->db->idate($this->date_creation)."'" : 'null').",";
1692
		$sql.= " datef=".(strval($this->date)!='' ? "'".$this->db->idate($this->date)."'" : 'null').",";
1693
		$sql.= " date_pointoftax=".(strval($this->date_pointoftax)!='' ? "'".$this->db->idate($this->date_pointoftax)."'" : 'null').",";
1694
		$sql.= " date_valid=".(strval($this->date_validation)!='' ? "'".$this->db->idate($this->date_validation)."'" : 'null').",";
1695
		$sql.= " paye=".(isset($this->paye)?$this->db->escape($this->paye):"null").",";
1696
		$sql.= " remise_percent=".(isset($this->remise_percent)?$this->db->escape($this->remise_percent):"null").",";
1697
		$sql.= " remise_absolue=".(isset($this->remise_absolue)?$this->db->escape($this->remise_absolue):"null").",";
1698
		$sql.= " close_code=".(isset($this->close_code)?"'".$this->db->escape($this->close_code)."'":"null").",";
1699
		$sql.= " close_note=".(isset($this->close_note)?"'".$this->db->escape($this->close_note)."'":"null").",";
1700
		$sql.= " tva=".(isset($this->total_tva)?$this->total_tva:"null").",";
1701
		$sql.= " localtax1=".(isset($this->total_localtax1)?$this->total_localtax1:"null").",";
1702
		$sql.= " localtax2=".(isset($this->total_localtax2)?$this->total_localtax2:"null").",";
1703
		$sql.= " total=".(isset($this->total_ht)?$this->total_ht:"null").",";
1704
		$sql.= " total_ttc=".(isset($this->total_ttc)?$this->total_ttc:"null").",";
1705
		$sql.= " revenuestamp=".((isset($this->revenuestamp) && $this->revenuestamp != '')?$this->db->escape($this->revenuestamp):"null").",";
1706
		$sql.= " fk_statut=".(isset($this->statut)?$this->db->escape($this->statut):"null").",";
1707
		$sql.= " fk_user_author=".(isset($this->user_author)?$this->db->escape($this->user_author):"null").",";
1708
		$sql.= " fk_user_valid=".(isset($this->fk_user_valid)?$this->db->escape($this->fk_user_valid):"null").",";
1709
		$sql.= " fk_facture_source=".(isset($this->fk_facture_source)?$this->db->escape($this->fk_facture_source):"null").",";
1710
		$sql.= " fk_projet=".(isset($this->fk_project)?$this->db->escape($this->fk_project):"null").",";
1711
		$sql.= " fk_cond_reglement=".(isset($this->cond_reglement_id)?$this->db->escape($this->cond_reglement_id):"null").",";
1712
		$sql.= " fk_mode_reglement=".(isset($this->mode_reglement_id)?$this->db->escape($this->mode_reglement_id):"null").",";
1713
		$sql.= " date_lim_reglement=".(strval($this->date_lim_reglement)!='' ? "'".$this->db->idate($this->date_lim_reglement)."'" : 'null').",";
1714
		$sql.= " note_private=".(isset($this->note_private)?"'".$this->db->escape($this->note_private)."'":"null").",";
1715
		$sql.= " note_public=".(isset($this->note_public)?"'".$this->db->escape($this->note_public)."'":"null").",";
1716
		$sql.= " model_pdf=".(isset($this->modelpdf)?"'".$this->db->escape($this->modelpdf)."'":"null").",";
1717
		$sql.= " import_key=".(isset($this->import_key)?"'".$this->db->escape($this->import_key)."'":"null").",";
1718
		$sql.= " situation_cycle_ref=".(empty($this->situation_cycle_ref)?"null":$this->db->escape($this->situation_cycle_ref)).",";
1719
		$sql.= " situation_counter=".(empty($this->situation_counter)?"null":$this->db->escape($this->situation_counter)).",";
1720
		$sql.= " situation_final=".(empty($this->situation_final)?"0":$this->db->escape($this->situation_final)).",";
1721
		$sql.= " retained_warranty=".(empty($this->retained_warranty)?"0":$this->db->escape($this->retained_warranty)).",";
1722
		$sql.= " retained_warranty_date_limit=".(strval($this->retained_warranty_date_limit)!='' ? "'".$this->db->idate($this->retained_warranty_date_limit)."'" : 'null').",";
1723
		$sql.= " retained_warranty_fk_cond_reglement=".(isset($this->retained_warranty_fk_cond_reglement)?intval($this->retained_warranty_fk_cond_reglement):"null");
1724
		$sql.= " WHERE rowid=".$this->id;
1725
1726
		$this->db->begin();
1727
1728
		dol_syslog(get_class($this)."::update", LOG_DEBUG);
1729
		$resql = $this->db->query($sql);
1730
		if (! $resql) {
1731
			$error++; $this->errors[]="Error ".$this->db->lasterror();
1732
		}
1733
1734
		if (! $error && empty($conf->global->MAIN_EXTRAFIELDS_DISABLED) && is_array($this->array_options) && count($this->array_options)>0)
1735
		{
1736
			$result=$this->insertExtraFields();
1737
			if ($result < 0)
1738
			{
1739
				$error++;
1740
			}
1741
		}
1742
1743
		if (! $error && ! $notrigger)
1744
		{
1745
			// Call trigger
1746
			$result=$this->call_trigger('BILL_MODIFY', $user);
1747
			if ($result < 0) $error++;
1748
			// End call triggers
1749
		}
1750
1751
		// Commit or rollback
1752
		if ($error)
1753
		{
1754
			foreach($this->errors as $errmsg)
1755
			{
1756
				dol_syslog(get_class($this)."::update ".$errmsg, LOG_ERR);
1757
				$this->error.=($this->error?', '.$errmsg:$errmsg);
1758
			}
1759
			$this->db->rollback();
1760
			return -1*$error;
1761
		}
1762
		else
1763
		{
1764
			$this->db->commit();
1765
			return 1;
1766
		}
1767
	}
1768
1769
1770
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1771
	/**
1772
	 *    Add a discount line into an invoice (as an invoice line) using an existing absolute discount (Consume the discount)
1773
	 *
1774
	 *    @param     int	$idremise	Id of absolute discount
1775
	 *    @return    int          		>0 if OK, <0 if KO
1776
	 */
1777
    public function insert_discount($idremise)
1778
	{
1779
        // phpcs:enable
1780
		global $langs;
1781
1782
		include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
1783
		include_once DOL_DOCUMENT_ROOT.'/core/class/discount.class.php';
1784
1785
		$this->db->begin();
1786
1787
		$remise=new DiscountAbsolute($this->db);
1788
		$result=$remise->fetch($idremise);
1789
1790
		if ($result > 0)
1791
		{
1792
			if ($remise->fk_facture)	// Protection against multiple submission
1793
			{
1794
				$this->error=$langs->trans("ErrorDiscountAlreadyUsed");
1795
				$this->db->rollback();
1796
				return -5;
1797
			}
1798
1799
			$facligne=new FactureLigne($this->db);
1800
			$facligne->fk_facture=$this->id;
1801
			$facligne->fk_remise_except=$remise->id;
1802
			$facligne->desc=$remise->description;   	// Description ligne
1803
			$facligne->vat_src_code=$remise->vat_src_code;
0 ignored issues
show
Bug introduced by
The property vat_src_code does not seem to exist on DiscountAbsolute.
Loading history...
1804
			$facligne->tva_tx=$remise->tva_tx;
1805
			$facligne->subprice = -$remise->amount_ht;
1806
			$facligne->fk_product=0;					// Id produit predefini
1807
			$facligne->qty=1;
1808
			$facligne->remise_percent=0;
1809
			$facligne->rang=-1;
1810
			$facligne->info_bits=2;
1811
1812
			// Get buy/cost price of invoice that is source of discount
1813
			if ($remise->fk_facture_source > 0)
1814
			{
1815
    			$srcinvoice=new Facture($this->db);
1816
    			$srcinvoice->fetch($remise->fk_facture_source);
1817
    			$totalcostpriceofinvoice=0;
1818
    			include_once DOL_DOCUMENT_ROOT.'/core/class/html.formmargin.class.php';  // TODO Move this into commonobject
1819
    			$formmargin=new FormMargin($this->db);
1820
    			$arraytmp=$formmargin->getMarginInfosArray($srcinvoice, false);
1821
        		$facligne->pa_ht = $arraytmp['pa_total'];
1822
			}
1823
1824
			$facligne->total_ht  = -$remise->amount_ht;
1825
			$facligne->total_tva = -$remise->amount_tva;
1826
			$facligne->total_ttc = -$remise->amount_ttc;
1827
1828
			$facligne->multicurrency_subprice = -$remise->multicurrency_subprice;
1829
			$facligne->multicurrency_total_ht = -$remise->multicurrency_amount_ht;
1830
			$facligne->multicurrency_total_tva = -$remise->multicurrency_amount_tva;
1831
			$facligne->multicurrency_total_ttc = -$remise->multicurrency_amount_ttc;
1832
1833
			$lineid=$facligne->insert();
1834
			if ($lineid > 0)
1835
			{
1836
				$result=$this->update_price(1);
1837
				if ($result > 0)
1838
				{
1839
					// Create link between discount and invoice line
1840
					$result=$remise->link_to_invoice($lineid, 0);
1841
					if ($result < 0)
1842
					{
1843
						$this->error=$remise->error;
1844
						$this->db->rollback();
1845
						return -4;
1846
					}
1847
1848
					$this->db->commit();
1849
					return 1;
1850
				}
1851
				else
1852
				{
1853
					$this->error=$facligne->error;
1854
					$this->db->rollback();
1855
					return -1;
1856
				}
1857
			}
1858
			else
1859
			{
1860
				$this->error=$facligne->error;
1861
				$this->db->rollback();
1862
				return -2;
1863
			}
1864
		}
1865
		else
1866
		{
1867
			$this->db->rollback();
1868
			return -3;
1869
		}
1870
	}
1871
1872
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1873
	/**
1874
	 *	Set customer ref
1875
	 *
1876
	 *	@param     	string	$ref_client		Customer ref
1877
	 *  @param     	int		$notrigger		1=Does not execute triggers, 0= execute triggers
1878
	 *	@return		int						<0 if KO, >0 if OK
1879
	 */
1880
    public function set_ref_client($ref_client, $notrigger = 0)
1881
	{
1882
        // phpcs:enable
1883
	    global $user;
1884
1885
		$error=0;
1886
1887
		$this->db->begin();
1888
1889
		$sql = 'UPDATE '.MAIN_DB_PREFIX.'facture';
1890
		if (empty($ref_client))
1891
			$sql .= ' SET ref_client = NULL';
1892
		else
1893
			$sql .= ' SET ref_client = \''.$this->db->escape($ref_client).'\'';
1894
		$sql .= ' WHERE rowid = '.$this->id;
1895
1896
		dol_syslog(__METHOD__.' this->id='.$this->id.', ref_client='.$ref_client, LOG_DEBUG);
1897
		$resql=$this->db->query($sql);
1898
		if (!$resql)
1899
		{
1900
			$this->errors[]=$this->db->error();
1901
			$error++;
1902
		}
1903
1904
		if (! $error)
1905
		{
1906
			$this->ref_client = $ref_client;
1907
		}
1908
1909
		if (! $notrigger && empty($error))
1910
		{
1911
			// Call trigger
1912
			$result=$this->call_trigger('BILL_MODIFY', $user);
1913
			if ($result < 0) $error++;
1914
			// End call triggers
1915
		}
1916
1917
		if (! $error)
1918
		{
1919
1920
			$this->ref_client = $ref_client;
1921
1922
			$this->db->commit();
1923
			return 1;
1924
		}
1925
		else
1926
		{
1927
			foreach($this->errors as $errmsg)
1928
			{
1929
				dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
1930
				$this->error.=($this->error?', '.$errmsg:$errmsg);
1931
			}
1932
			$this->db->rollback();
1933
			return -1*$error;
1934
		}
1935
	}
1936
1937
	/**
1938
	 *	Delete invoice
1939
	 *
1940
	 *	@param     	User	$user      	    User making the deletion.
1941
	 *	@param		int		$notrigger		1=Does not execute triggers, 0= execute triggers
1942
	 *	@param		int		$idwarehouse	Id warehouse to use for stock change.
1943
	 *	@return		int						<0 if KO, 0=Refused, >0 if OK
1944
	 */
1945
    public function delete($user, $notrigger = 0, $idwarehouse = -1)
1946
	{
1947
		global $langs,$conf;
1948
		require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
1949
1950
		$rowid=$this->id;
1951
1952
		dol_syslog(get_class($this)."::delete rowid=".$rowid.", ref=".$this->ref.", thirdparty=".$this->thirdparty->name, LOG_DEBUG);
1953
1954
		// Test to avoid invoice deletion (allowed if draft)
1955
		$result = $this->is_erasable();
1956
1957
		if ($result <= 0) return 0;
1958
1959
		$error=0;
1960
1961
		$this->db->begin();
1962
1963
		if (! $error && ! $notrigger)
1964
		{
1965
            // Call trigger
1966
            $result=$this->call_trigger('BILL_DELETE', $user);
1967
            if ($result < 0) $error++;
1968
            // End call triggers
1969
		}
1970
1971
		// Removed extrafields
1972
		if (! $error) {
1973
			$result=$this->deleteExtraFields();
1974
			if ($result < 0)
1975
			{
1976
				$error++;
1977
				dol_syslog(get_class($this)."::delete error deleteExtraFields ".$this->error, LOG_ERR);
1978
			}
1979
		}
1980
1981
		if (! $error)
1982
		{
1983
			// Delete linked object
1984
			$res = $this->deleteObjectLinked();
1985
			if ($res < 0) $error++;
1986
		}
1987
1988
		if (! $error)
1989
		{
1990
			// If invoice was converted into a discount not yet consumed, we remove discount
1991
			$sql = 'DELETE FROM '.MAIN_DB_PREFIX.'societe_remise_except';
1992
			$sql.= ' WHERE fk_facture_source = '.$rowid;
1993
			$sql.= ' AND fk_facture_line IS NULL';
1994
			$resql=$this->db->query($sql);
1995
1996
			// If invoice has consumned discounts
1997
			$this->fetch_lines();
1998
			$list_rowid_det=array();
1999
			foreach($this->lines as $key => $invoiceline)
2000
			{
2001
				$list_rowid_det[]=$invoiceline->rowid;
2002
			}
2003
2004
			// Consumned discounts are freed
2005
			if (count($list_rowid_det))
2006
			{
2007
				$sql = 'UPDATE '.MAIN_DB_PREFIX.'societe_remise_except';
2008
				$sql.= ' SET fk_facture = NULL, fk_facture_line = NULL';
2009
				$sql.= ' WHERE fk_facture_line IN ('.join(',', $list_rowid_det).')';
2010
2011
				dol_syslog(get_class($this)."::delete", LOG_DEBUG);
2012
				if (! $this->db->query($sql))
2013
				{
2014
					$this->error=$this->db->error()." sql=".$sql;
2015
					$this->db->rollback();
2016
					return -5;
2017
				}
2018
			}
2019
2020
			// If we decrement stock on invoice validation, we increment
2021
			if ($this->type != self::TYPE_DEPOSIT && $result >= 0 && ! empty($conf->stock->enabled) && ! empty($conf->global->STOCK_CALCULATE_ON_BILL) && $idwarehouse!=-1)
2022
			{
2023
				require_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
2024
				$langs->load("agenda");
2025
2026
				$num=count($this->lines);
2027
				for ($i = 0; $i < $num; $i++)
2028
				{
2029
					if ($this->lines[$i]->fk_product > 0)
2030
					{
2031
						$mouvP = new MouvementStock($this->db);
2032
						$mouvP->origin = &$this;
2033
						// We decrease stock for product
2034
						if ($this->type == self::TYPE_CREDIT_NOTE) $result=$mouvP->livraison($user, $this->lines[$i]->fk_product, $idwarehouse, $this->lines[$i]->qty, $this->lines[$i]->subprice, $langs->trans("InvoiceDeleteDolibarr", $this->ref));
2035
						else $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
2036
					}
2037
				}
2038
			}
2039
2040
2041
			// Delete invoice line
2042
			$sql = 'DELETE FROM '.MAIN_DB_PREFIX.'facturedet WHERE fk_facture = '.$rowid;
2043
2044
			dol_syslog(get_class($this)."::delete", LOG_DEBUG);
2045
2046
			if ($this->db->query($sql) && $this->delete_linked_contact())
2047
			{
2048
				$sql = 'DELETE FROM '.MAIN_DB_PREFIX.'facture WHERE rowid = '.$rowid;
2049
2050
				dol_syslog(get_class($this)."::delete", LOG_DEBUG);
2051
2052
				$resql=$this->db->query($sql);
2053
				if ($resql)
2054
				{
2055
					// On efface le repertoire de pdf provisoire
2056
					$ref = dol_sanitizeFileName($this->ref);
2057
					if ($conf->facture->dir_output && !empty($this->ref))
2058
					{
2059
						$dir = $conf->facture->dir_output . "/" . $ref;
2060
						$file = $conf->facture->dir_output . "/" . $ref . "/" . $ref . ".pdf";
2061
						if (file_exists($file))	// We must delete all files before deleting directory
2062
						{
2063
							$ret=dol_delete_preview($this);
2064
2065
							if (! dol_delete_file($file, 0, 0, 0, $this)) // For triggers
2066
							{
2067
								$langs->load("errors");
2068
								$this->error=$langs->trans("ErrorFailToDeleteFile", $file);
2069
								$this->db->rollback();
2070
								return 0;
2071
							}
2072
						}
2073
						if (file_exists($dir))
2074
						{
2075
							if (! dol_delete_dir_recursive($dir)) // For remove dir and meta
2076
							{
2077
								$langs->load("errors");
2078
								$this->error=$langs->trans("ErrorFailToDeleteDir", $dir);
2079
								$this->db->rollback();
2080
								return 0;
2081
							}
2082
						}
2083
					}
2084
2085
					$this->db->commit();
2086
					return 1;
2087
				}
2088
				else
2089
				{
2090
					$this->error=$this->db->lasterror()." sql=".$sql;
2091
					$this->db->rollback();
2092
					return -6;
2093
				}
2094
			}
2095
			else
2096
			{
2097
				$this->error=$this->db->lasterror()." sql=".$sql;
2098
				$this->db->rollback();
2099
				return -4;
2100
			}
2101
		}
2102
		else
2103
		{
2104
			$this->db->rollback();
2105
			return -2;
2106
		}
2107
	}
2108
2109
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2110
	/**
2111
	 *  Tag la facture comme paye completement (si close_code non renseigne) => this->fk_statut=2, this->paye=1
2112
	 *  ou partiellement (si close_code renseigne) + appel trigger BILL_PAYED => this->fk_statut=2, this->paye stay 0
2113
	 *
2114
	 *  @param	User	$user      	Objet utilisateur qui modifie
2115
	 *	@param  string	$close_code	Code renseigne si on classe a payee completement alors que paiement incomplet (cas escompte par exemple)
2116
	 *	@param  string	$close_note	Commentaire renseigne si on classe a payee alors que paiement incomplet (cas escompte par exemple)
2117
	 *  @return int         		<0 if KO, >0 if OK
2118
	 */
2119
    public function set_paid($user, $close_code = '', $close_note = '')
2120
	{
2121
        // phpcs:enable
2122
		$error=0;
2123
2124
		if ($this->paye != 1)
2125
		{
2126
			$this->db->begin();
2127
2128
			dol_syslog(get_class($this)."::set_paid rowid=".$this->id, LOG_DEBUG);
2129
2130
			$sql = 'UPDATE '.MAIN_DB_PREFIX.'facture SET';
2131
			$sql.= ' fk_statut='.self::STATUS_CLOSED;
2132
			if (! $close_code) $sql.= ', paye=1';
2133
			if ($close_code) $sql.= ", close_code='".$this->db->escape($close_code)."'";
2134
			if ($close_note) $sql.= ", close_note='".$this->db->escape($close_note)."'";
2135
			$sql.= ' WHERE rowid = '.$this->id;
2136
2137
			$resql = $this->db->query($sql);
2138
			if ($resql)
2139
			{
2140
	            // Call trigger
2141
	            $result=$this->call_trigger('BILL_PAYED', $user);
2142
	            if ($result < 0) $error++;
2143
	            // End call triggers
2144
			}
2145
			else
2146
			{
2147
				$error++;
2148
				$this->error=$this->db->lasterror();
2149
			}
2150
2151
			if (! $error)
2152
			{
2153
				$this->db->commit();
2154
				return 1;
2155
			}
2156
			else
2157
			{
2158
				$this->db->rollback();
2159
				return -1;
2160
			}
2161
		}
2162
		else
2163
		{
2164
			return 0;
2165
		}
2166
	}
2167
2168
2169
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2170
	/**
2171
	 *  Tag la facture comme non payee completement + appel trigger BILL_UNPAYED
2172
	 *	Fonction utilisee quand un paiement prelevement est refuse,
2173
	 * 	ou quand une facture annulee et reouverte.
2174
	 *
2175
	 *  @param	User	$user       Object user that change status
2176
	 *  @return int         		<0 if KO, >0 if OK
2177
	 */
2178
    public function set_unpaid($user)
2179
	{
2180
        // phpcs:enable
2181
		$error=0;
2182
2183
		$this->db->begin();
2184
2185
		$sql = 'UPDATE '.MAIN_DB_PREFIX.'facture';
2186
		$sql.= ' SET paye=0, fk_statut='.self::STATUS_VALIDATED.', close_code=null, close_note=null';
2187
		$sql.= ' WHERE rowid = '.$this->id;
2188
2189
		dol_syslog(get_class($this)."::set_unpaid", LOG_DEBUG);
2190
		$resql = $this->db->query($sql);
2191
		if ($resql)
2192
		{
2193
            // Call trigger
2194
            $result=$this->call_trigger('BILL_UNPAYED', $user);
2195
            if ($result < 0) $error++;
2196
            // End call triggers
2197
		}
2198
		else
2199
		{
2200
			$error++;
2201
			$this->error=$this->db->error();
2202
			dol_print_error($this->db);
2203
		}
2204
2205
		if (! $error)
2206
		{
2207
			$this->db->commit();
2208
			return 1;
2209
		}
2210
		else
2211
		{
2212
			$this->db->rollback();
2213
			return -1;
2214
		}
2215
	}
2216
2217
2218
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2219
	/**
2220
	 *	Tag invoice as canceled, with no payment on it (example for replacement invoice or payment never received) + call trigger BILL_CANCEL
2221
	 *	Warning, if option to decrease stock on invoice was set, this function does not change stock (it might be a cancel because
2222
	 *  of no payment even if merchandises were sent).
2223
	 *
2224
	 *	@param	User	$user        	Object user making change
2225
	 *	@param	string	$close_code		Code of closing invoice (CLOSECODE_REPLACED, CLOSECODE_...)
2226
	 *	@param	string	$close_note		Comment
2227
	 *	@return int         			<0 if KO, >0 if OK
2228
	 */
2229
    public function set_canceled($user, $close_code = '', $close_note = '')
2230
	{
2231
        // phpcs:enable
2232
2233
		dol_syslog(get_class($this)."::set_canceled rowid=".$this->id, LOG_DEBUG);
2234
2235
		$this->db->begin();
2236
2237
		$sql = 'UPDATE '.MAIN_DB_PREFIX.'facture SET';
2238
		$sql.= ' fk_statut='.self::STATUS_ABANDONED;
2239
		if ($close_code) $sql.= ", close_code='".$this->db->escape($close_code)."'";
2240
		if ($close_note) $sql.= ", close_note='".$this->db->escape($close_note)."'";
2241
		$sql.= ' WHERE rowid = '.$this->id;
2242
2243
		$resql = $this->db->query($sql);
2244
		if ($resql)
2245
		{
2246
			// On desaffecte de la facture les remises liees
2247
			// car elles n'ont pas ete utilisees vu que la facture est abandonnee.
2248
			$sql = 'UPDATE '.MAIN_DB_PREFIX.'societe_remise_except';
2249
			$sql.= ' SET fk_facture = NULL';
2250
			$sql.= ' WHERE fk_facture = '.$this->id;
2251
2252
			$resql=$this->db->query($sql);
2253
			if ($resql)
2254
			{
2255
	            // Call trigger
2256
	            $result=$this->call_trigger('BILL_CANCEL', $user);
2257
	            if ($result < 0)
2258
	            {
2259
					$this->db->rollback();
2260
					return -1;
2261
				}
2262
	            // End call triggers
2263
2264
				$this->db->commit();
2265
				return 1;
2266
			}
2267
			else
2268
			{
2269
				$this->error=$this->db->error()." sql=".$sql;
2270
				$this->db->rollback();
2271
				return -1;
2272
			}
2273
		}
2274
		else
2275
		{
2276
			$this->error=$this->db->error()." sql=".$sql;
2277
			$this->db->rollback();
2278
			return -2;
2279
		}
2280
	}
2281
2282
	/**
2283
	 * Tag invoice as validated + call trigger BILL_VALIDATE
2284
	 * Object must have lines loaded with fetch_lines
2285
	 *
2286
	 * @param	User	$user           Object user that validate
2287
	 * @param   string	$force_number	Reference to force on invoice
2288
	 * @param	int		$idwarehouse	Id of warehouse to use for stock decrease if option to decreasenon stock is on (0=no decrease)
2289
	 * @param	int		$notrigger		1=Does not execute triggers, 0= execute triggers
2290
     * @return	int						<0 if KO, 0=Nothing done because invoice is not a draft, >0 if OK
2291
	 */
2292
    public function validate($user, $force_number = '', $idwarehouse = 0, $notrigger = 0)
2293
	{
2294
		global $conf,$langs;
2295
		require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
2296
2297
		$now=dol_now();
2298
2299
		$error=0;
2300
		dol_syslog(get_class($this).'::validate user='.$user->id.', force_number='.$force_number.', idwarehouse='.$idwarehouse);
2301
2302
		// Force to have object complete for checks
2303
		$this->fetch_thirdparty();
2304
		$this->fetch_lines();
2305
2306
		// Check parameters
2307
		if (! $this->brouillon)
2308
		{
2309
			dol_syslog(get_class($this)."::validate no draft status", LOG_WARNING);
2310
			return 0;
2311
		}
2312
		if (count($this->lines) <= 0)
2313
		{
2314
        	$langs->load("errors");
2315
			$this->error=$langs->trans("ErrorObjectMustHaveLinesToBeValidated", $this->ref);
2316
			return -1;
2317
		}
2318
		if ((empty($conf->global->MAIN_USE_ADVANCED_PERMS) && empty($user->rights->facture->creer))
2319
       	|| (! empty($conf->global->MAIN_USE_ADVANCED_PERMS) && empty($user->rights->facture->invoice_advance->validate)))
2320
		{
2321
			$this->error='Permission denied';
2322
			dol_syslog(get_class($this)."::validate ".$this->error.' MAIN_USE_ADVANCED_PERMS='.$conf->global->MAIN_USE_ADVANCED_PERMS, LOG_ERR);
2323
			return -1;
2324
		}
2325
2326
		$this->db->begin();
2327
2328
		// Check parameters
2329
		if ($this->type == self::TYPE_REPLACEMENT)		// si facture de remplacement
2330
		{
2331
			// Controle que facture source connue
2332
			if ($this->fk_facture_source <= 0)
2333
			{
2334
				$this->error=$langs->trans("ErrorFieldRequired", $langs->transnoentitiesnoconv("InvoiceReplacement"));
2335
				$this->db->rollback();
2336
				return -10;
2337
			}
2338
2339
			// Charge la facture source a remplacer
2340
			$facreplaced=new Facture($this->db);
2341
			$result=$facreplaced->fetch($this->fk_facture_source);
2342
			if ($result <= 0)
2343
			{
2344
				$this->error=$langs->trans("ErrorBadInvoice");
2345
				$this->db->rollback();
2346
				return -11;
2347
			}
2348
2349
			// Controle que facture source non deja remplacee par une autre
2350
			$idreplacement=$facreplaced->getIdReplacingInvoice('validated');
2351
			if ($idreplacement && $idreplacement != $this->id)
2352
			{
2353
				$facreplacement=new Facture($this->db);
2354
				$facreplacement->fetch($idreplacement);
2355
				$this->error=$langs->trans("ErrorInvoiceAlreadyReplaced", $facreplaced->ref, $facreplacement->ref);
2356
				$this->db->rollback();
2357
				return -12;
2358
			}
2359
2360
			$result=$facreplaced->set_canceled($user, self::CLOSECODE_REPLACED, '');
2361
			if ($result < 0)
2362
			{
2363
				$this->error=$facreplaced->error;
2364
				$this->db->rollback();
2365
				return -13;
2366
			}
2367
		}
2368
2369
		// Define new ref
2370
		if ($force_number)
2371
		{
2372
			$num = $force_number;
2373
		}
2374
		elseif (preg_match('/^[\(]?PROV/i', $this->ref) || empty($this->ref)) // empty should not happened, but when it occurs, the test save life
2375
		{
2376
			if (! empty($conf->global->FAC_FORCE_DATE_VALIDATION))	// If option enabled, we force invoice date
2377
			{
2378
				$this->date=dol_now();
2379
				$this->date_lim_reglement=$this->calculate_date_lim_reglement();
2380
			}
2381
			$num = $this->getNextNumRef($this->thirdparty);
2382
		}
2383
		else
2384
		{
2385
			$num = $this->ref;
2386
		}
2387
		$this->newref = $num;
2388
2389
		if ($num)
2390
		{
2391
			$this->update_price(1);
2392
2393
			// Validate
2394
			$sql = 'UPDATE '.MAIN_DB_PREFIX.'facture';
2395
			$sql.= " SET ref='".$num."', fk_statut = ".self::STATUS_VALIDATED.", fk_user_valid = ".($user->id > 0 ? $user->id : "null").", date_valid = '".$this->db->idate($now)."'";
2396
			if (! empty($conf->global->FAC_FORCE_DATE_VALIDATION))	// If option enabled, we force invoice date
2397
			{
2398
				$sql.= ", datef='".$this->db->idate($this->date)."'";
2399
				$sql.= ", date_lim_reglement='".$this->db->idate($this->date_lim_reglement)."'";
2400
			}
2401
			$sql.= ' WHERE rowid = '.$this->id;
2402
2403
			dol_syslog(get_class($this)."::validate", LOG_DEBUG);
2404
			$resql=$this->db->query($sql);
2405
			if (! $resql)
2406
			{
2407
				dol_print_error($this->db);
2408
				$error++;
2409
			}
2410
2411
			// On verifie si la facture etait une provisoire
2412
			if (! $error && (preg_match('/^[\(]?PROV/i', $this->ref)))
2413
			{
2414
				// La verif qu'une remise n'est pas utilisee 2 fois est faite au moment de l'insertion de ligne
2415
			}
2416
2417
			if (! $error)
2418
			{
2419
				// Define third party as a customer
2420
				$result=$this->thirdparty->set_as_client();
2421
2422
				// Si active on decremente le produit principal et ses composants a la validation de facture
2423
				if ($this->type != self::TYPE_DEPOSIT && $result >= 0 && ! empty($conf->stock->enabled) && ! empty($conf->global->STOCK_CALCULATE_ON_BILL) && $idwarehouse > 0)
2424
				{
2425
					require_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
2426
					$langs->load("agenda");
2427
2428
					// Loop on each line
2429
					$cpt=count($this->lines);
2430
					for ($i = 0; $i < $cpt; $i++)
2431
					{
2432
						if ($this->lines[$i]->fk_product > 0)
2433
						{
2434
							$mouvP = new MouvementStock($this->db);
2435
							$mouvP->origin = &$this;
2436
							// We decrease stock for product
2437
							if ($this->type == self::TYPE_CREDIT_NOTE) $result=$mouvP->reception($user, $this->lines[$i]->fk_product, $idwarehouse, $this->lines[$i]->qty, 0, $langs->trans("InvoiceValidatedInDolibarr", $num));
2438
							else $result=$mouvP->livraison($user, $this->lines[$i]->fk_product, $idwarehouse, $this->lines[$i]->qty, $this->lines[$i]->subprice, $langs->trans("InvoiceValidatedInDolibarr", $num));
2439
							if ($result < 0) {
2440
								$error++;
2441
								$this->error = $mouvP->error;
2442
							}
2443
						}
2444
					}
2445
				}
2446
			}
2447
2448
			// Trigger calls
2449
			if (! $error && ! $notrigger)
2450
			{
2451
	            // Call trigger
2452
	            $result=$this->call_trigger('BILL_VALIDATE', $user);
2453
	            if ($result < 0) $error++;
2454
	            // End call triggers
2455
			}
2456
2457
			if (! $error)
2458
			{
2459
				$this->oldref = $this->ref;
2460
2461
				// Rename directory if dir was a temporary ref
2462
				if (preg_match('/^[\(]?PROV/i', $this->ref))
2463
				{
2464
					// Now we rename also files into index
2465
					$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)."'";
2466
					$sql.= " WHERE filename LIKE '".$this->db->escape($this->ref)."%' AND filepath = 'facture/".$this->db->escape($this->ref)."' and entity = ".$conf->entity;
2467
					$resql = $this->db->query($sql);
2468
					if (! $resql) { $error++; $this->error = $this->db->lasterror(); }
2469
2470
					// We rename directory ($this->ref = old ref, $num = new ref) in order not to lose the attachments
2471
					$oldref = dol_sanitizeFileName($this->ref);
2472
					$newref = dol_sanitizeFileName($num);
2473
					$dirsource = $conf->facture->dir_output.'/'.$oldref;
2474
					$dirdest = $conf->facture->dir_output.'/'.$newref;
2475
					if (! $error && file_exists($dirsource))
2476
					{
2477
						dol_syslog(get_class($this)."::validate rename dir ".$dirsource." into ".$dirdest);
2478
2479
						if (@rename($dirsource, $dirdest))
2480
						{
2481
							dol_syslog("Rename ok");
2482
	                        // Rename docs starting with $oldref with $newref
2483
	                        $listoffiles=dol_dir_list($conf->facture->dir_output.'/'.$newref, 'files', 1, '^'.preg_quote($oldref, '/'));
2484
	                        foreach($listoffiles as $fileentry)
2485
	                        {
2486
	                        	$dirsource=$fileentry['name'];
2487
	                        	$dirdest=preg_replace('/^'.preg_quote($oldref, '/').'/', $newref, $dirsource);
2488
	                        	$dirsource=$fileentry['path'].'/'.$dirsource;
2489
	                        	$dirdest=$fileentry['path'].'/'.$dirdest;
2490
	                        	@rename($dirsource, $dirdest);
1 ignored issue
show
Security Best Practice introduced by
It seems like you do not handle an error condition for rename(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unhandled  annotation

2490
	                        	/** @scrutinizer ignore-unhandled */ @rename($dirsource, $dirdest);

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
2491
	                        }
2492
						}
2493
					}
2494
				}
2495
			}
2496
2497
			if (! $error && !$this->is_last_in_cycle())
2498
			{
2499
				if (! $this->updatePriceNextInvoice($langs))
2500
				{
2501
					$error++;
2502
				}
2503
			}
2504
2505
			// Set new ref and define current status
2506
			if (! $error)
2507
			{
2508
				$this->ref = $num;
2509
				$this->ref=$num;
2510
				$this->statut= self::STATUS_VALIDATED;
2511
				$this->brouillon=0;
2512
				$this->date_validation=$now;
2513
				$i = 0;
2514
2515
                if (!empty($conf->global->INVOICE_USE_SITUATION))
2516
                {
2517
                	$final = true;
2518
    				$nboflines = count($this->lines);
2519
    				while (($i < $nboflines) && $final) {
2520
    					$final = ($this->lines[$i]->situation_percent == 100);
2521
    					$i++;
2522
    				}
2523
2524
    				if (empty($final)) $this->situation_final = 0;
2525
    				else $this->situation_final = 1;
2526
2527
				$this->setFinal($user);
2528
                }
2529
			}
2530
		}
2531
		else
2532
		{
2533
			$error++;
2534
		}
2535
2536
		if (! $error)
2537
		{
2538
			$this->db->commit();
2539
			return 1;
2540
		}
2541
		else
2542
		{
2543
			$this->db->rollback();
2544
			return -1;
2545
		}
2546
	}
2547
2548
	/**
2549
	 * Update price of next invoice
2550
	 *
2551
	 * @param	Translate	$langs	Translate object
2552
	 * @return bool		false if KO, true if OK
2553
	 */
2554
    public function updatePriceNextInvoice(&$langs)
2555
	{
2556
		foreach ($this->tab_next_situation_invoice as $next_invoice)
2557
		{
2558
			$is_last = $next_invoice->is_last_in_cycle();
2559
2560
			if ($next_invoice->statut == self::STATUS_DRAFT && $is_last != 1)
2561
			{
2562
				$this->error = $langs->trans('updatePriceNextInvoiceErrorUpdateline', $next_invoice->ref);
2563
				return false;
2564
			}
2565
2566
			$next_invoice->brouillon = 1;
2567
			foreach ($next_invoice->lines as $line)
2568
			{
2569
				$result = $next_invoice->updateline($line->id, $line->desc, $line->subprice, $line->qty, $line->remise_percent,
2570
														$line->date_start, $line->date_end, $line->tva_tx, $line->localtax1_tx, $line->localtax2_tx, 'HT', $line->info_bits, $line->product_type,
2571
														$line->fk_parent_line, 0, $line->fk_fournprice, $line->pa_ht, $line->label, $line->special_code, $line->array_options, $line->situation_percent,
2572
														$line->fk_unit);
2573
2574
				if ($result < 0)
2575
				{
2576
					$this->error = $langs->trans('updatePriceNextInvoiceErrorUpdateline', $next_invoice->ref);
2577
					return false;
2578
				}
2579
			}
2580
2581
			break; // Only the next invoice and not each next invoice
2582
		}
2583
2584
		return true;
2585
	}
2586
2587
	/**
2588
	 *	Set draft status
2589
	 *
2590
	 *	@param	User	$user			Object user that modify
2591
	 *	@param	int		$idwarehouse	Id warehouse to use for stock change.
2592
	 *	@return	int						<0 if KO, >0 if OK
2593
	 */
2594
	public function setDraft($user, $idwarehouse = -1)
2595
	{
2596
        // phpcs:enable
2597
		global $conf,$langs;
2598
2599
		$error=0;
2600
2601
		if ($this->statut == self::STATUS_DRAFT)
2602
		{
2603
			dol_syslog(__METHOD__." already draft status", LOG_WARNING);
2604
			return 0;
2605
		}
2606
2607
		dol_syslog(__METHOD__, LOG_DEBUG);
2608
2609
		$this->db->begin();
2610
2611
		$sql = "UPDATE ".MAIN_DB_PREFIX."facture";
2612
		$sql.= " SET fk_statut = ".self::STATUS_DRAFT;
2613
		$sql.= " WHERE rowid = ".$this->id;
2614
2615
		$result=$this->db->query($sql);
2616
		if ($result)
2617
		{
2618
			if (! $error)
2619
			{
2620
				$this->oldcopy= clone $this;
2621
			}
2622
2623
			// Si on decremente le produit principal et ses composants a la validation de facture, on réincrement
2624
			if ($this->type != self::TYPE_DEPOSIT && $result >= 0 && ! empty($conf->stock->enabled) && ! empty($conf->global->STOCK_CALCULATE_ON_BILL))
2625
			{
2626
				require_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
2627
				$langs->load("agenda");
2628
2629
				$num=count($this->lines);
2630
				for ($i = 0; $i < $num; $i++)
2631
				{
2632
					if ($this->lines[$i]->fk_product > 0)
2633
					{
2634
						$mouvP = new MouvementStock($this->db);
2635
						$mouvP->origin = &$this;
2636
						// We decrease stock for product
2637
						if ($this->type == self::TYPE_CREDIT_NOTE) $result=$mouvP->livraison($user, $this->lines[$i]->fk_product, $idwarehouse, $this->lines[$i]->qty, $this->lines[$i]->subprice, $langs->trans("InvoiceBackToDraftInDolibarr", $this->ref));
2638
						else $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
2639
					}
2640
				}
2641
			}
2642
2643
			if ($error == 0)
2644
			{
2645
				$old_statut=$this->statut;
2646
				$this->brouillon = 1;
2647
				$this->statut = self::STATUS_DRAFT;
2648
2649
				// Call trigger
2650
				$result=$this->call_trigger('BILL_UNVALIDATE', $user);
2651
				if ($result < 0)
2652
				{
2653
					$error++;
2654
					$this->statut=$old_statut;
2655
					$this->brouillon=0;
2656
				}
2657
				// End call triggers
2658
			} else {
2659
				$this->db->rollback();
2660
				return -1;
2661
			}
2662
2663
			if ($error == 0)
2664
			{
2665
				$this->db->commit();
2666
				return 1;
2667
			}
2668
			else
2669
			{
2670
				$this->db->rollback();
2671
				return -1;
2672
			}
2673
		}
2674
		else
2675
		{
2676
			$this->error=$this->db->error();
2677
			$this->db->rollback();
2678
			return -1;
2679
		}
2680
	}
2681
2682
2683
	/**
2684
	 * 		Add an invoice line into database (linked to product/service or not).
2685
	 * 		Les parametres sont deja cense etre juste et avec valeurs finales a l'appel
2686
	 *		de cette methode. Aussi, pour le taux tva, il doit deja avoir ete defini
2687
	 *		par l'appelant par la methode get_default_tva(societe_vendeuse,societe_acheteuse,produit)
2688
	 *		et le desc doit deja avoir la bonne valeur (a l'appelant de gerer le multilangue)
2689
	 *
2690
	 * 		@param    	string		$desc            	Description of line
2691
	 * 		@param    	double		$pu_ht              Unit price without tax (> 0 even for credit note)
2692
	 * 		@param    	double		$qty             	Quantity
2693
	 * 		@param    	double		$txtva           	Force Vat rate, -1 for auto (Can contain the vat_src_code too with syntax '9.9 (CODE)')
2694
	 * 		@param		double		$txlocaltax1		Local tax 1 rate (deprecated, use instead txtva with code inside)
2695
	 *  	@param		double		$txlocaltax2		Local tax 2 rate (deprecated, use instead txtva with code inside)
2696
	 *		@param    	int			$fk_product      	Id of predefined product/service
2697
	 * 		@param    	double		$remise_percent  	Percent of discount on line
2698
	 * 		@param    	int			$date_start      	Date start of service
2699
	 * 		@param    	int			$date_end        	Date end of service
2700
	 * 		@param    	int			$ventil          	Code of dispatching into accountancy
2701
	 * 		@param    	int			$info_bits			Bits of type of lines
2702
	 *		@param    	int			$fk_remise_except	Id discount used
2703
	 *		@param		string		$price_base_type	'HT' or 'TTC'
2704
	 * 		@param    	double		$pu_ttc             Unit price with tax (> 0 even for credit note)
2705
	 * 		@param		int			$type				Type of line (0=product, 1=service). Not used if fk_product is defined, the type of product is used.
2706
	 *      @param      int			$rang               Position of line
2707
	 *      @param		int			$special_code		Special code (also used by externals modules!)
2708
	 *      @param		string		$origin				'order', ...
2709
	 *      @param		int			$origin_id			Id of origin object
2710
	 *      @param		int			$fk_parent_line		Id of parent line
2711
	 * 		@param		int			$fk_fournprice		Supplier price id (to calculate margin) or ''
2712
	 * 		@param		int			$pa_ht				Buying price of line (to calculate margin) or ''
2713
	 * 		@param		string		$label				Label of the line (deprecated, do not use)
2714
	 *		@param		array		$array_options		extrafields array
2715
	 *      @param      int         $situation_percent  Situation advance percentage
2716
	 *      @param      int         $fk_prev_id         Previous situation line id reference
2717
	 * 		@param 		string		$fk_unit 			Code of the unit to use. Null to use the default one
2718
	 * 		@param		double		$pu_ht_devise		Unit price in currency
2719
	 *    	@return    	int             				<0 if KO, Id of line if OK
2720
	 */
2721
    public function 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 = self::TYPE_STANDARD, $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)
2722
    {
2723
		// Deprecation warning
2724
		if ($label) {
2725
			dol_syslog(__METHOD__ . ": using line label is deprecated", LOG_WARNING);
2726
			//var_dump(debug_backtrace(false));exit;
2727
		}
2728
2729
		global $mysoc, $conf, $langs;
2730
2731
		dol_syslog(get_class($this)."::addline id=$this->id,desc=$desc,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", LOG_DEBUG);
2732
2733
		if ($this->statut == self::STATUS_DRAFT)
2734
		{
2735
			include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
2736
2737
			// Clean parameters
2738
			if (empty($remise_percent)) $remise_percent=0;
2739
			if (empty($qty)) $qty=0;
2740
			if (empty($info_bits)) $info_bits=0;
2741
			if (empty($rang)) $rang=0;
2742
			if (empty($ventil)) $ventil=0;
2743
			if (empty($txtva)) $txtva=0;
2744
			if (empty($txlocaltax1)) $txlocaltax1=0;
2745
			if (empty($txlocaltax2)) $txlocaltax2=0;
2746
			if (empty($fk_parent_line) || $fk_parent_line < 0) $fk_parent_line=0;
2747
			if (empty($fk_prev_id)) $fk_prev_id = 'null';
2748
			if (! isset($situation_percent) || $situation_percent > 100 || (string) $situation_percent == '') $situation_percent = 100;
2749
2750
			$remise_percent=price2num($remise_percent);
2751
			$qty=price2num($qty);
2752
			$pu_ht=price2num($pu_ht);
2753
			$pu_ht_devise=price2num($pu_ht_devise);
2754
			$pu_ttc=price2num($pu_ttc);
2755
			$pa_ht=price2num($pa_ht);
2756
			if (!preg_match('/\((.*)\)/', $txtva)) {
2757
				$txtva = price2num($txtva);               // $txtva can have format '5.0(XXX)' or '5'
2758
			}
2759
			$txlocaltax1=price2num($txlocaltax1);
2760
			$txlocaltax2=price2num($txlocaltax2);
2761
2762
			if ($price_base_type=='HT')
2763
			{
2764
				$pu=$pu_ht;
2765
			}
2766
			else
2767
			{
2768
				$pu=$pu_ttc;
2769
			}
2770
2771
			// Check parameters
2772
			if ($type < 0) return -1;
2773
2774
			if ($date_start && $date_end && $date_start > $date_end) {
2775
				$langs->load("errors");
2776
				$this->error=$langs->trans('ErrorStartDateGreaterEnd');
2777
				return -1;
2778
			}
2779
2780
			$this->db->begin();
2781
2782
			$product_type=$type;
2783
			if (!empty($fk_product))
2784
			{
2785
				$product=new Product($this->db);
2786
				$result=$product->fetch($fk_product);
2787
				$product_type=$product->type;
2788
2789
				if (! empty($conf->global->STOCK_MUST_BE_ENOUGH_FOR_INVOICE) && $product_type == 0 && $product->stock_reel < $qty) {
2790
                    $langs->load("errors");
2791
				    $this->error=$langs->trans('ErrorStockIsNotEnoughToAddProductOnInvoice', $product->ref);
2792
					$this->db->rollback();
2793
					return -3;
2794
				}
2795
			}
2796
2797
			$localtaxes_type=getLocalTaxesFromRate($txtva, 0, $this->thirdparty, $mysoc);
2798
2799
			// Clean vat code
2800
			$vat_src_code='';
2801
			if (preg_match('/\((.*)\)/', $txtva, $reg))
2802
			{
2803
				$vat_src_code = $reg[1];
2804
				$txtva = preg_replace('/\s*\(.*\)/', '', $txtva);    // Remove code into vatrate.
2805
			}
2806
2807
			// Calcul du total TTC et de la TVA pour la ligne a partir de
2808
			// qty, pu, remise_percent et txtva
2809
			// TRES IMPORTANT: C'est au moment de l'insertion ligne qu'on doit stocker
2810
			// la part ht, tva et ttc, et ce au niveau de la ligne qui a son propre taux tva.
2811
2812
			$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);
2813
2814
			$total_ht  = $tabprice[0];
2815
			$total_tva = $tabprice[1];
2816
			$total_ttc = $tabprice[2];
2817
			$total_localtax1 = $tabprice[9];
2818
			$total_localtax2 = $tabprice[10];
2819
			$pu_ht = $tabprice[3];
2820
2821
			// MultiCurrency
2822
			$multicurrency_total_ht  = $tabprice[16];
2823
            $multicurrency_total_tva = $tabprice[17];
2824
            $multicurrency_total_ttc = $tabprice[18];
2825
			$pu_ht_devise = $tabprice[19];
2826
2827
			// Rank to use
2828
			$rangtouse = $rang;
2829
			if ($rangtouse == -1)
2830
			{
2831
				$rangmax = $this->line_max($fk_parent_line);
2832
				$rangtouse = $rangmax + 1;
2833
			}
2834
2835
			// Insert line
2836
			$this->line=new FactureLigne($this->db);
2837
2838
			$this->line->context = $this->context;
2839
2840
			$this->line->fk_facture=$this->id;
2841
			$this->line->label=$label;	// deprecated
1 ignored issue
show
Deprecated Code introduced by
The property FactureLigne::$label has been deprecated. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

2841
			/** @scrutinizer ignore-deprecated */ $this->line->label=$label;	// deprecated
Loading history...
2842
			$this->line->desc=$desc;
2843
2844
			$this->line->qty=            ($this->type==self::TYPE_CREDIT_NOTE?abs($qty):$qty);	    // For credit note, quantity is always positive and unit price negative
1 ignored issue
show
Documentation Bug introduced by
It seems like $this->type == self::TYP...NOTE ? abs($qty) : $qty can also be of type string. However, the property $qty is declared as type double. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
2845
			$this->line->subprice=       ($this->type==self::TYPE_CREDIT_NOTE?-abs($pu_ht):$pu_ht); // For credit note, unit price always negative, always positive otherwise
2846
2847
			$this->line->vat_src_code=$vat_src_code;
2848
			$this->line->tva_tx=$txtva;
1 ignored issue
show
Documentation Bug introduced by
It seems like $txtva can also be of type string. However, the property $tva_tx is declared as type double. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
2849
			$this->line->localtax1_tx=($total_localtax1?$localtaxes_type[1]:0);
2850
			$this->line->localtax2_tx=($total_localtax2?$localtaxes_type[3]:0);
2851
			$this->line->localtax1_type = $localtaxes_type[0];
2852
			$this->line->localtax2_type = $localtaxes_type[2];
2853
2854
			$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
2855
			$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
2856
			$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
2857
			$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
2858
			$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
2859
2860
			$this->line->fk_product=$fk_product;
2861
			$this->line->product_type=$product_type;
2862
			$this->line->remise_percent=$remise_percent;
1 ignored issue
show
Documentation Bug introduced by
The property $remise_percent was declared of type double, but $remise_percent is of type string. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
2863
			$this->line->date_start=$date_start;
2864
			$this->line->date_end=$date_end;
2865
			$this->line->ventil=$ventil;
0 ignored issues
show
Bug introduced by
The property ventil does not exist on FactureLigne. Did you mean fk_code_ventilation?
Loading history...
2866
			$this->line->rang=$rangtouse;
2867
			$this->line->info_bits=$info_bits;
2868
			$this->line->fk_remise_except=$fk_remise_except;
2869
2870
			$this->line->special_code=$special_code;
2871
			$this->line->fk_parent_line=$fk_parent_line;
2872
			$this->line->origin=$origin;
2873
			$this->line->origin_id=$origin_id;
2874
			$this->line->situation_percent = $situation_percent;
2875
			$this->line->fk_prev_id = $fk_prev_id;
1 ignored issue
show
Documentation Bug introduced by
It seems like $fk_prev_id can also be of type string. However, the property $fk_prev_id is declared as type integer. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
2876
			$this->line->fk_unit=$fk_unit;
2877
2878
			// infos marge
2879
			$this->line->fk_fournprice = $fk_fournprice;
2880
			$this->line->pa_ht = $pa_ht;
2881
2882
			// Multicurrency
2883
			$this->line->fk_multicurrency			= $this->fk_multicurrency;
2884
			$this->line->multicurrency_code			= $this->multicurrency_code;
2885
			$this->line->multicurrency_subprice		= $pu_ht_devise;
2886
			$this->line->multicurrency_total_ht 	= $multicurrency_total_ht;
2887
            $this->line->multicurrency_total_tva 	= $multicurrency_total_tva;
2888
            $this->line->multicurrency_total_ttc 	= $multicurrency_total_ttc;
2889
2890
			if (is_array($array_options) && count($array_options)>0) {
2891
				$this->line->array_options=$array_options;
2892
			}
2893
2894
			$result=$this->line->insert();
2895
			if ($result > 0)
2896
			{
2897
				// Reorder if child line
2898
				if (! empty($fk_parent_line)) $this->line_order(true, 'DESC');
2899
2900
				// Mise a jour informations denormalisees au niveau de la facture meme
2901
				$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.
2902
2903
				if ($result > 0)
2904
				{
2905
					$this->db->commit();
2906
					return $this->line->id;
2907
				}
2908
				else
2909
				{
2910
					$this->error=$this->db->lasterror();
2911
					$this->db->rollback();
2912
					return -1;
2913
				}
2914
			}
2915
			else
2916
			{
2917
				$this->error=$this->line->error;
2918
				$this->db->rollback();
2919
				return -2;
2920
			}
2921
		}
2922
		else
2923
		{
2924
			dol_syslog(get_class($this)."::addline status of order must be Draft to allow use of ->addline()", LOG_ERR);
2925
			return -3;
2926
		}
2927
	}
2928
2929
	/**
2930
	 *  Update a detail line
2931
	 *
2932
	 *  @param     	int			$rowid           	Id of line to update
2933
	 *  @param     	string		$desc            	Description of line
2934
	 *  @param     	double		$pu              	Prix unitaire (HT ou TTC selon price_base_type) (> 0 even for credit note lines)
2935
	 *  @param     	double		$qty             	Quantity
2936
	 *  @param     	double		$remise_percent  	Percentage discount of the line
2937
	 *  @param     	int		    $date_start      	Date de debut de validite du service
2938
	 *  @param     	int		    $date_end        	Date de fin de validite du service
2939
	 *  @param     	double		$txtva          	VAT Rate (Can be '8.5', '8.5 (ABC)')
2940
	 * 	@param		double		$txlocaltax1		Local tax 1 rate
2941
	 *  @param		double		$txlocaltax2		Local tax 2 rate
2942
	 * 	@param     	string		$price_base_type 	HT or TTC
2943
	 * 	@param     	int			$info_bits 		    Miscellaneous informations
2944
	 * 	@param		int			$type				Type of line (0=product, 1=service)
2945
	 * 	@param		int			$fk_parent_line		Id of parent line (0 in most cases, used by modules adding sublevels into lines).
2946
	 * 	@param		int			$skip_update_total	Keep fields total_xxx to 0 (used for special lines by some modules)
2947
	 * 	@param		int			$fk_fournprice		Id of origin supplier price
2948
	 * 	@param		int			$pa_ht				Price (without tax) of product when it was bought
2949
	 * 	@param		string		$label				Label of the line (deprecated, do not use)
2950
	 * 	@param		int			$special_code		Special code (also used by externals modules!)
2951
     *  @param		array		$array_options		extrafields array
2952
	 * 	@param      int         $situation_percent  Situation advance percentage
2953
	 * 	@param 		string		$fk_unit 			Code of the unit to use. Null to use the default one
2954
	 * 	@param		double		$pu_ht_devise		Unit price in currency
2955
	 * 	@param		int			$notrigger			disable line update trigger
2956
	 *  @return    	int             				< 0 if KO, > 0 if OK
2957
	 */
2958
    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)
2959
	{
2960
		global $conf,$user;
2961
		// Deprecation warning
2962
		if ($label) {
2963
			dol_syslog(__METHOD__ . ": using line label is deprecated", LOG_WARNING);
2964
		}
2965
2966
		include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
2967
2968
		global $mysoc,$langs;
2969
2970
		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);
2971
2972
		if ($this->brouillon)
2973
		{
2974
			if (!$this->is_last_in_cycle() && empty($this->error))
2975
			{
2976
				if (!$this->checkProgressLine($rowid, $situation_percent))
2977
				{
2978
					if (!$this->error) $this->error=$langs->trans('invoiceLineProgressError');
2979
					return -3;
2980
				}
2981
			}
2982
2983
			if ($date_start && $date_end && $date_start > $date_end) {
2984
				$langs->load("errors");
2985
				$this->error=$langs->trans('ErrorStartDateGreaterEnd');
2986
				return -1;
2987
			}
2988
2989
			$this->db->begin();
2990
2991
			// Clean parameters
2992
			if (empty($qty)) $qty=0;
2993
			if (empty($fk_parent_line) || $fk_parent_line < 0) $fk_parent_line=0;
2994
			if (empty($special_code) || $special_code == 3) $special_code=0;
2995
			if (! isset($situation_percent) || $situation_percent > 100 || (string) $situation_percent == '') $situation_percent = 100;
2996
2997
			$remise_percent	= price2num($remise_percent);
2998
			$qty			= price2num($qty);
2999
			$pu 			= price2num($pu);
3000
        	$pu_ht_devise	= price2num($pu_ht_devise);
3001
			$pa_ht			= price2num($pa_ht);
3002
			$txtva			= price2num($txtva);
3003
			$txlocaltax1	= price2num($txlocaltax1);
3004
			$txlocaltax2	= price2num($txlocaltax2);
3005
3006
			// Check parameters
3007
			if ($type < 0) return -1;
3008
3009
			// Calculate total with, without tax and tax from qty, pu, remise_percent and txtva
3010
			// TRES IMPORTANT: C'est au moment de l'insertion ligne qu'on doit stocker
3011
			// la part ht, tva et ttc, et ce au niveau de la ligne qui a son propre taux tva.
3012
3013
			$localtaxes_type=getLocalTaxesFromRate($txtva, 0, $this->thirdparty, $mysoc);
3014
3015
			// Clean vat code
3016
    		$vat_src_code='';
3017
    		if (preg_match('/\((.*)\)/', $txtva, $reg))
3018
    		{
3019
    		    $vat_src_code = $reg[1];
3020
    		    $txtva = preg_replace('/\s*\(.*\)/', '', $txtva);    // Remove code into vatrate.
3021
    		}
3022
3023
			$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);
3024
3025
			$total_ht  = $tabprice[0];
3026
			$total_tva = $tabprice[1];
3027
			$total_ttc = $tabprice[2];
3028
			$total_localtax1=$tabprice[9];
3029
			$total_localtax2=$tabprice[10];
3030
			$pu_ht  = $tabprice[3];
3031
			$pu_tva = $tabprice[4];
3032
			$pu_ttc = $tabprice[5];
3033
3034
			// MultiCurrency
3035
			$multicurrency_total_ht  = $tabprice[16];
3036
            $multicurrency_total_tva = $tabprice[17];
3037
            $multicurrency_total_ttc = $tabprice[18];
3038
			$pu_ht_devise = $tabprice[19];
3039
3040
			// Old properties: $price, $remise (deprecated)
3041
			$price = $pu;
3042
			$remise = 0;
3043
			if ($remise_percent > 0)
3044
			{
3045
				$remise = round(($pu * $remise_percent / 100), 2);
3046
				$price = ($pu - $remise);
3047
			}
3048
			$price    = price2num($price);
3049
3050
			//Fetch current line from the database and then clone the object and set it in $oldline property
3051
			$line = new FactureLigne($this->db);
3052
			$line->fetch($rowid);
3053
3054
			if (!empty($line->fk_product))
3055
			{
3056
				$product=new Product($this->db);
3057
				$result=$product->fetch($line->fk_product);
3058
				$product_type=$product->type;
3059
3060
				if (! empty($conf->global->STOCK_MUST_BE_ENOUGH_FOR_INVOICE) && $product_type == 0 && $product->stock_reel < $qty) {
3061
                    $langs->load("errors");
3062
				    $this->error=$langs->trans('ErrorStockIsNotEnoughToAddProductOnInvoice', $product->ref);
3063
					$this->db->rollback();
3064
					return -3;
3065
				}
3066
			}
3067
3068
			$staticline = clone $line;
3069
3070
			$line->oldline = $staticline;
3071
			$this->line = $line;
3072
            $this->line->context = $this->context;
3073
3074
			// Reorder if fk_parent_line change
3075
			if (! empty($fk_parent_line) && ! empty($staticline->fk_parent_line) && $fk_parent_line != $staticline->fk_parent_line)
3076
			{
3077
				$rangmax = $this->line_max($fk_parent_line);
3078
				$this->line->rang = $rangmax + 1;
3079
			}
3080
3081
			$this->line->rowid				= $rowid;
1 ignored issue
show
Deprecated Code introduced by
The property CommonObjectLine::$rowid has been deprecated: Try to use id property as possible (even if field into database is still rowid) ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

3081
			/** @scrutinizer ignore-deprecated */ $this->line->rowid				= $rowid;

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
3082
			$this->line->label				= $label;
1 ignored issue
show
Deprecated Code introduced by
The property FactureLigne::$label has been deprecated. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

3082
			/** @scrutinizer ignore-deprecated */ $this->line->label				= $label;
Loading history...
3083
			$this->line->desc				= $desc;
3084
			$this->line->qty				= ($this->type==self::TYPE_CREDIT_NOTE?abs($qty):$qty);	// For credit note, quantity is always positive and unit price negative
1 ignored issue
show
Documentation Bug introduced by
It seems like $this->type == self::TYP...NOTE ? abs($qty) : $qty can also be of type string. However, the property $qty is declared as type double. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
3085
3086
			$this->line->vat_src_code       = $vat_src_code;
3087
			$this->line->tva_tx				= $txtva;
1 ignored issue
show
Documentation Bug introduced by
The property $tva_tx was declared of type double, but $txtva is of type string. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
3088
			$this->line->localtax1_tx		= $txlocaltax1;
1 ignored issue
show
Documentation Bug introduced by
The property $localtax1_tx was declared of type double, but $txlocaltax1 is of type string. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
3089
			$this->line->localtax2_tx		= $txlocaltax2;
1 ignored issue
show
Documentation Bug introduced by
The property $localtax2_tx was declared of type double, but $txlocaltax2 is of type string. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
3090
			$this->line->localtax1_type		= $localtaxes_type[0];
3091
			$this->line->localtax2_type		= $localtaxes_type[2];
3092
3093
			$this->line->remise_percent		= $remise_percent;
1 ignored issue
show
Documentation Bug introduced by
The property $remise_percent was declared of type double, but $remise_percent is of type string. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
3094
			$this->line->subprice			= ($this->type==2?-abs($pu_ht):$pu_ht); // For credit note, unit price always negative, always positive otherwise
3095
			$this->line->date_start			= $date_start;
3096
			$this->line->date_end			= $date_end;
3097
			$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
3098
			$this->line->total_tva			= (($this->type==self::TYPE_CREDIT_NOTE||$qty<0)?-abs($total_tva):$total_tva);
3099
			$this->line->total_localtax1	= $total_localtax1;
3100
			$this->line->total_localtax2	= $total_localtax2;
3101
			$this->line->total_ttc			= (($this->type==self::TYPE_CREDIT_NOTE||$qty<0)?-abs($total_ttc):$total_ttc);
3102
			$this->line->info_bits			= $info_bits;
3103
			$this->line->special_code		= $special_code;
3104
			$this->line->product_type		= $type;
3105
			$this->line->fk_parent_line		= $fk_parent_line;
3106
			$this->line->skip_update_total	= $skip_update_total;
3107
			$this->line->situation_percent  = $situation_percent;
3108
			$this->line->fk_unit				= $fk_unit;
3109
3110
			$this->line->fk_fournprice = $fk_fournprice;
3111
			$this->line->pa_ht = $pa_ht;
3112
3113
			// Multicurrency
3114
			$this->line->multicurrency_subprice		= $pu_ht_devise;
3115
			$this->line->multicurrency_total_ht 	= $multicurrency_total_ht;
3116
            $this->line->multicurrency_total_tva 	= $multicurrency_total_tva;
3117
            $this->line->multicurrency_total_ttc 	= $multicurrency_total_ttc;
3118
3119
			if (is_array($array_options) && count($array_options)>0) {
3120
				$this->line->array_options=$array_options;
3121
			}
3122
3123
			$result=$this->line->update($user, $notrigger);
3124
			if ($result > 0)
3125
			{
3126
				// Reorder if child line
3127
				if (! empty($fk_parent_line)) $this->line_order(true, 'DESC');
3128
3129
				// Mise a jour info denormalisees au niveau facture
3130
				$this->update_price(1);
3131
				$this->db->commit();
3132
				return $result;
3133
			}
3134
			else
3135
			{
3136
			    $this->error=$this->line->error;
3137
				$this->db->rollback();
3138
				return -1;
3139
			}
3140
		}
3141
		else
3142
		{
3143
			$this->error="Invoice statut makes operation forbidden";
3144
			return -2;
3145
		}
3146
	}
3147
3148
	/**
3149
	 * Check if the percent edited is lower of next invoice line
3150
	 *
3151
	 * @param	int		$idline				id of line to check
3152
	 * @param	float	$situation_percent	progress percentage need to be test
3153
	 * @return false if KO, true if OK
3154
	 */
3155
    public function checkProgressLine($idline, $situation_percent)
3156
	{
3157
		$sql = 'SELECT fd.situation_percent FROM '.MAIN_DB_PREFIX.'facturedet fd
3158
				INNER JOIN '.MAIN_DB_PREFIX.'facture f ON (fd.fk_facture = f.rowid)
3159
				WHERE fd.fk_prev_id = '.$idline.'
3160
				AND f.fk_statut <> 0';
3161
3162
		$result = $this->db->query($sql);
3163
		if (! $result)
3164
		{
3165
			$this->error=$this->db->error();
3166
			return false;
3167
		}
3168
3169
		$obj = $this->db->fetch_object($result);
3170
3171
		if ($obj === null) return true;
3172
		else return $situation_percent < $obj->situation_percent;
3173
	}
3174
3175
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3176
	/**
3177
	 * Update invoice line with percentage
3178
	 *
3179
	 * @param  FactureLigne $line       Invoice line
3180
	 * @param  int          $percent    Percentage
3181
	 * @return void
3182
	 */
3183
    public function update_percent($line, $percent)
3184
	{
3185
        // phpcs:enable
3186
	    global $mysoc,$user;
3187
3188
	    // Progress should never be changed for discount lines
3189
	    if (($line->info_bits & 2) == 2)
3190
	    {
3191
	    	return;
3192
	    }
3193
3194
		include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
3195
3196
		// Cap percentages to 100
3197
		if ($percent > 100) $percent = 100;
3198
		$line->situation_percent = $percent;
3199
		$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);
3200
		$line->total_ht = $tabprice[0];
3201
		$line->total_tva = $tabprice[1];
3202
		$line->total_ttc = $tabprice[2];
3203
		$line->total_localtax1 = $tabprice[9];
3204
		$line->total_localtax2 = $tabprice[10];
3205
		$line->multicurrency_total_ht  = $tabprice[16];
3206
		$line->multicurrency_total_tva = $tabprice[17];
3207
		$line->multicurrency_total_ttc = $tabprice[18];
3208
		$line->update($user);
3209
		$this->update_price(1);
3210
	}
3211
3212
	/**
3213
	 *	Delete line in database
3214
	 *
3215
	 *	@param		int		$rowid		Id of line to delete
3216
	 *	@return		int					<0 if KO, >0 if OK
3217
	 */
3218
    public function deleteline($rowid)
3219
	{
3220
        global $user;
3221
3222
		dol_syslog(get_class($this)."::deleteline rowid=".$rowid, LOG_DEBUG);
3223
3224
		if (! $this->brouillon)
3225
		{
3226
			$this->error='ErrorDeleteLineNotAllowedByObjectStatus';
3227
			return -1;
3228
		}
3229
3230
		$this->db->begin();
3231
3232
		// Libere remise liee a ligne de facture
3233
		$sql = 'UPDATE '.MAIN_DB_PREFIX.'societe_remise_except';
3234
		$sql.= ' SET fk_facture_line = NULL';
3235
		$sql.= ' WHERE fk_facture_line = '.$rowid;
3236
3237
		dol_syslog(get_class($this)."::deleteline", LOG_DEBUG);
3238
		$result = $this->db->query($sql);
3239
		if (! $result)
3240
		{
3241
			$this->error=$this->db->error();
3242
			$this->db->rollback();
3243
			return -1;
3244
		}
3245
3246
		$line=new FactureLigne($this->db);
3247
3248
        $line->context = $this->context;
3249
3250
		// For triggers
3251
		$result = $line->fetch($rowid);
3252
		if (! ($result > 0)) dol_print_error($this->db, $line->error, $line->errors);
3253
3254
		if ($line->delete($user) > 0)
3255
		{
3256
			$result=$this->update_price(1);
3257
3258
			if ($result > 0)
3259
			{
3260
				$this->db->commit();
3261
				return 1;
3262
			}
3263
			else
3264
			{
3265
				$this->db->rollback();
3266
				$this->error=$this->db->lasterror();
3267
				return -1;
3268
			}
3269
		}
3270
		else
3271
		{
3272
			$this->db->rollback();
3273
			$this->error=$line->error;
3274
			return -1;
3275
		}
3276
	}
3277
3278
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3279
	/**
3280
	 *	Set percent discount
3281
	 *
3282
	 *	@param     	User	$user		User that set discount
3283
	 *	@param     	double	$remise		Discount
3284
	 *  @param     	int		$notrigger	1=Does not execute triggers, 0= execute triggers
3285
	 *	@return		int 		<0 if ko, >0 if ok
3286
	 */
3287
    public function set_remise($user, $remise, $notrigger = 0)
3288
	{
3289
        // phpcs:enable
3290
		// Clean parameters
3291
		if (empty($remise)) $remise=0;
3292
3293
		if ($user->rights->facture->creer)
3294
		{
3295
			$remise=price2num($remise);
3296
3297
			$error=0;
3298
3299
			$this->db->begin();
3300
3301
			$sql = 'UPDATE '.MAIN_DB_PREFIX.'facture';
3302
			$sql.= ' SET remise_percent = '.$remise;
3303
			$sql.= ' WHERE rowid = '.$this->id;
3304
			$sql.= ' AND fk_statut = '.self::STATUS_DRAFT;
3305
3306
			dol_syslog(__METHOD__, LOG_DEBUG);
3307
			$resql=$this->db->query($sql);
3308
			if (!$resql)
3309
			{
3310
				$this->errors[]=$this->db->error();
3311
				$error++;
3312
			}
3313
3314
			if (! $notrigger && empty($error))
3315
			{
3316
				// Call trigger
3317
				$result=$this->call_trigger('BILL_MODIFY', $user);
3318
				if ($result < 0) $error++;
3319
				// End call triggers
3320
			}
3321
3322
			if (! $error)
3323
			{
3324
				$this->remise_percent = $remise;
3325
				$this->update_price(1);
3326
3327
				$this->db->commit();
3328
				return 1;
3329
			}
3330
			else
3331
			{
3332
				foreach($this->errors as $errmsg)
3333
				{
3334
					dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
3335
					$this->error.=($this->error?', '.$errmsg:$errmsg);
3336
				}
3337
				$this->db->rollback();
3338
				return -1*$error;
3339
			}
3340
		}
3341
	}
3342
3343
3344
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3345
	/**
3346
	 *	Set absolute discount
3347
	 *
3348
	 *	@param     	User	$user 		User that set discount
3349
	 *	@param     	double	$remise		Discount
3350
	 *  @param     	int		$notrigger	1=Does not execute triggers, 0= execute triggers
3351
	 *	@return		int 				<0 if KO, >0 if OK
3352
	 */
3353
    public function set_remise_absolue($user, $remise, $notrigger = 0)
3354
	{
3355
        // phpcs:enable
3356
		if (empty($remise)) $remise=0;
3357
3358
		if ($user->rights->facture->creer)
3359
		{
3360
			$error=0;
3361
3362
			$this->db->begin();
3363
3364
			$remise=price2num($remise);
3365
3366
			$sql = 'UPDATE '.MAIN_DB_PREFIX.'facture';
3367
			$sql.= ' SET remise_absolue = '.$remise;
3368
			$sql.= ' WHERE rowid = '.$this->id;
3369
			$sql.= ' AND fk_statut = '.self::STATUS_DRAFT;
3370
3371
			dol_syslog(__METHOD__, LOG_DEBUG);
3372
			$resql=$this->db->query($sql);
3373
			if (!$resql)
3374
			{
3375
				$this->errors[]=$this->db->error();
3376
				$error++;
3377
			}
3378
3379
			if (! $error)
3380
			{
3381
				$this->oldcopy= clone $this;
3382
				$this->remise_absolue = $remise;
3383
				$this->update_price(1);
3384
			}
3385
3386
			if (! $notrigger && empty($error))
3387
			{
3388
				// Call trigger
3389
				$result=$this->call_trigger('BILL_MODIFY', $user);
3390
				if ($result < 0) $error++;
3391
				// End call triggers
3392
			}
3393
3394
			if (! $error)
3395
			{
3396
				$this->db->commit();
3397
				return 1;
3398
			}
3399
			else
3400
			{
3401
				foreach($this->errors as $errmsg)
3402
				{
3403
					dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
3404
					$this->error.=($this->error?', '.$errmsg:$errmsg);
3405
				}
3406
				$this->db->rollback();
3407
				return -1*$error;
3408
			}
3409
		}
3410
	}
3411
3412
	/**
3413
	 *      Return next reference of customer invoice not already used (or last reference)
3414
	 *      according to numbering module defined into constant FACTURE_ADDON
3415
	 *
3416
	 *      @param	   Societe		$soc		object company
3417
	 *      @param     string		$mode		'next' for next value or 'last' for last value
3418
	 *      @return    string					free ref or last ref
3419
	 */
3420
    public function getNextNumRef($soc, $mode = 'next')
3421
	{
3422
		global $conf, $langs;
3423
		$langs->load("bills");
3424
3425
		// Clean parameters (if not defined or using deprecated value)
3426
		if (empty($conf->global->FACTURE_ADDON)) $conf->global->FACTURE_ADDON='mod_facture_terre';
3427
		elseif ($conf->global->FACTURE_ADDON=='terre') $conf->global->FACTURE_ADDON='mod_facture_terre';
3428
		elseif ($conf->global->FACTURE_ADDON=='mercure') $conf->global->FACTURE_ADDON='mod_facture_mercure';
3429
3430
		if (! empty($conf->global->FACTURE_ADDON))
3431
		{
3432
			dol_syslog("Call getNextNumRef with FACTURE_ADDON = ".$conf->global->FACTURE_ADDON.", thirdparty=".$soc->nom.", type=".$soc->typent_code, LOG_DEBUG);
1 ignored issue
show
Deprecated Code introduced by
The property Societe::$nom has been deprecated: Use $name instead ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

3432
			dol_syslog("Call getNextNumRef with FACTURE_ADDON = ".$conf->global->FACTURE_ADDON.", thirdparty="./** @scrutinizer ignore-deprecated */ $soc->nom.", type=".$soc->typent_code, LOG_DEBUG);

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
3433
3434
			$mybool=false;
3435
3436
3437
			$file = $conf->global->FACTURE_ADDON.".php";
3438
			$classname = $conf->global->FACTURE_ADDON;
3439
3440
3441
			// Include file with class
3442
			$dirmodels = array_merge(array('/'), (array) $conf->modules_parts['models']);
3443
3444
			foreach ($dirmodels as $reldir) {
3445
3446
				$dir = dol_buildpath($reldir."core/modules/facture/");
3447
3448
				// Load file with numbering class (if found)
3449
				if (is_file($dir.$file) && is_readable($dir.$file))
3450
				{
3451
                    $mybool |= include_once $dir . $file;
3452
                }
3453
			}
3454
3455
			// For compatibility
3456
			if (! $mybool)
1 ignored issue
show
Bug Best Practice introduced by
The expression $mybool of type false|integer is loosely compared to false; this is ambiguous if the integer can be 0. You might want to explicitly use === false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
3457
			{
3458
				$file = $conf->global->FACTURE_ADDON."/".$conf->global->FACTURE_ADDON.".modules.php";
3459
				$classname = "mod_facture_".$conf->global->FACTURE_ADDON;
3460
				$classname = preg_replace('/\-.*$/', '', $classname);
3461
				// Include file with class
3462
				foreach ($conf->file->dol_document_root as $dirroot)
3463
				{
3464
					$dir = $dirroot."/core/modules/facture/";
3465
3466
					// Load file with numbering class (if found)
3467
					if (is_file($dir.$file) && is_readable($dir.$file)) {
3468
                        $mybool |= include_once $dir . $file;
3469
                    }
3470
				}
3471
			}
3472
3473
			if (! $mybool)
1 ignored issue
show
Bug Best Practice introduced by
The expression $mybool of type false|integer is loosely compared to false; this is ambiguous if the integer can be 0. You might want to explicitly use === false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
3474
			{
3475
				dol_print_error('', "Failed to include file ".$file);
3476
				return '';
3477
			}
3478
3479
			$obj = new $classname();
3480
			$numref = "";
3481
			$numref = $obj->getNextValue($soc, $this, $mode);
3482
3483
			/**
3484
			 * $numref can be empty in case we ask for the last value because if there is no invoice created with the
3485
			 * set up mask.
3486
			 */
3487
			if ($mode != 'last' && !$numref) {
3488
				$this->error=$obj->error;
3489
				//dol_print_error($this->db,"Facture::getNextNumRef ".$obj->error);
3490
				return "";
3491
			}
3492
3493
			return $numref;
3494
		}
3495
		else
3496
		{
3497
			$langs->load("errors");
3498
			print $langs->trans("Error")." ".$langs->trans("ErrorModuleSetupNotComplete");
3499
			return "";
3500
		}
3501
	}
3502
3503
	/**
3504
	 *	Load miscellaneous information for tab "Info"
3505
	 *
3506
	 *	@param  int		$id		Id of object to load
3507
	 *	@return	void
3508
	 */
3509
    public function info($id)
3510
	{
3511
		$sql = 'SELECT c.rowid, datec, date_valid as datev, tms as datem,';
3512
		$sql.= ' fk_user_author, fk_user_valid';
3513
		$sql.= ' FROM '.MAIN_DB_PREFIX.'facture as c';
3514
		$sql.= ' WHERE c.rowid = '.$id;
3515
3516
		$result=$this->db->query($sql);
3517
		if ($result)
3518
		{
3519
			if ($this->db->num_rows($result))
3520
			{
3521
				$obj = $this->db->fetch_object($result);
3522
				$this->id = $obj->rowid;
3523
				if ($obj->fk_user_author)
3524
				{
3525
					$cuser = new User($this->db);
3526
					$cuser->fetch($obj->fk_user_author);
3527
					$this->user_creation     = $cuser;
3528
				}
3529
				if ($obj->fk_user_valid)
3530
				{
3531
					$vuser = new User($this->db);
3532
					$vuser->fetch($obj->fk_user_valid);
3533
					$this->user_validation = $vuser;
3534
				}
3535
				$this->date_creation     = $this->db->jdate($obj->datec);
3536
				$this->date_modification = $this->db->jdate($obj->datem);
3537
				$this->date_validation   = $this->db->jdate($obj->datev);	// Should be in log table
3538
			}
3539
			$this->db->free($result);
3540
		}
3541
		else
3542
		{
3543
			dol_print_error($this->db);
3544
		}
3545
	}
3546
3547
3548
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3549
	/**
3550
	 *  Return list of invoices (eventually filtered on a user) into an array
3551
	 *
3552
	 *  @param		int		$shortlist		0=Return array[id]=ref, 1=Return array[](id=>id,ref=>ref,name=>name)
3553
	 *  @param      int		$draft      	0=not draft, 1=draft
3554
	 *  @param      User	$excluser      	Objet user to exclude
3555
	 *  @param    	int		$socid			Id third pary
3556
	 *  @param    	int		$limit			For pagination
3557
	 *  @param    	int		$offset			For pagination
3558
	 *  @param    	string	$sortfield		Sort criteria
3559
	 *  @param    	string	$sortorder		Sort order
3560
	 *  @return     int             		-1 if KO, array with result if OK
3561
	 */
3562
    public function liste_array($shortlist = 0, $draft = 0, $excluser = '', $socid = 0, $limit = 0, $offset = 0, $sortfield = 'f.datef,f.rowid', $sortorder = 'DESC')
3563
	{
3564
        // phpcs:enable
3565
		global $conf,$user;
3566
3567
		$ga = array();
3568
3569
		$sql = "SELECT s.rowid, s.nom as name, s.client,";
3570
		$sql.= " f.rowid as fid, f.ref as ref, f.datef as df";
3571
		if (! $user->rights->societe->client->voir && ! $socid) $sql .= ", sc.fk_soc, sc.fk_user";
3572
		$sql.= " FROM ".MAIN_DB_PREFIX."societe as s, ".MAIN_DB_PREFIX."facture as f";
3573
		if (! $user->rights->societe->client->voir && ! $socid) $sql .= ", ".MAIN_DB_PREFIX."societe_commerciaux as sc";
3574
		$sql.= " WHERE f.entity IN (".getEntity('invoice').")";
3575
		$sql.= " AND f.fk_soc = s.rowid";
3576
		if (! $user->rights->societe->client->voir && ! $socid) //restriction
3577
		{
3578
			$sql.= " AND s.rowid = sc.fk_soc AND sc.fk_user = " .$user->id;
3579
		}
3580
		if ($socid) $sql.= " AND s.rowid = ".$socid;
3581
		if ($draft) $sql.= " AND f.fk_statut = ".self::STATUS_DRAFT;
3582
		if (is_object($excluser)) $sql.= " AND f.fk_user_author <> ".$excluser->id;
3583
		$sql.= $this->db->order($sortfield, $sortorder);
3584
		$sql.= $this->db->plimit($limit, $offset);
3585
3586
		$result=$this->db->query($sql);
3587
		if ($result)
3588
		{
3589
			$numc = $this->db->num_rows($result);
3590
			if ($numc)
3591
			{
3592
				$i = 0;
3593
				while ($i < $numc)
3594
				{
3595
					$obj = $this->db->fetch_object($result);
3596
3597
					if ($shortlist == 1)
3598
					{
3599
						$ga[$obj->fid] = $obj->ref;
3600
					}
3601
					elseif ($shortlist == 2)
3602
					{
3603
						$ga[$obj->fid] = $obj->ref.' ('.$obj->name.')';
3604
					}
3605
					else
3606
					{
3607
						$ga[$i]['id']	= $obj->fid;
3608
						$ga[$i]['ref'] 	= $obj->ref;
3609
						$ga[$i]['name'] = $obj->name;
3610
					}
3611
					$i++;
3612
				}
3613
			}
3614
			return $ga;
3615
		}
3616
		else
3617
		{
3618
			dol_print_error($this->db);
3619
			return -1;
3620
		}
3621
	}
3622
3623
3624
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3625
	/**
3626
	 *	Return list of invoices qualified to be replaced by another invoice.
3627
	 *	Invoices matching the following rules are returned:
3628
	 *	(Status validated or abandonned for a reason 'other') + not payed + no payment at all + not already replaced
3629
	 *
3630
	 *	@param		int		$socid		Id thirdparty
3631
	 *	@return    	array				Array of invoices ('id'=>id, 'ref'=>ref, 'status'=>status, 'paymentornot'=>0/1)
3632
	 */
3633
    public function list_replacable_invoices($socid = 0)
3634
	{
3635
        // phpcs:enable
3636
		global $conf;
3637
3638
		$return = array();
3639
3640
		$sql = "SELECT f.rowid as rowid, f.ref, f.fk_statut,";
3641
		$sql.= " ff.rowid as rowidnext";
3642
		$sql.= " FROM ".MAIN_DB_PREFIX."facture as f";
3643
		$sql.= " LEFT JOIN ".MAIN_DB_PREFIX."paiement_facture as pf ON f.rowid = pf.fk_facture";
3644
		$sql.= " LEFT JOIN ".MAIN_DB_PREFIX."facture as ff ON f.rowid = ff.fk_facture_source";
3645
		$sql.= " WHERE (f.fk_statut = ".self::STATUS_VALIDATED." OR (f.fk_statut = ".self::STATUS_ABANDONED." AND f.close_code = '".self::CLOSECODE_ABANDONED."'))";
3646
		$sql.= " AND f.entity IN (".getEntity('invoice').")";
3647
		$sql.= " AND f.paye = 0";					// Pas classee payee completement
3648
		$sql.= " AND pf.fk_paiement IS NULL";		// Aucun paiement deja fait
3649
		$sql.= " AND ff.fk_statut IS NULL";			// Renvoi vrai si pas facture de remplacement
3650
		if ($socid > 0) $sql.=" AND f.fk_soc = ".$socid;
3651
		$sql.= " ORDER BY f.ref";
3652
3653
		dol_syslog(get_class($this)."::list_replacable_invoices", LOG_DEBUG);
3654
		$resql=$this->db->query($sql);
3655
		if ($resql)
3656
		{
3657
			while ($obj=$this->db->fetch_object($resql))
3658
			{
3659
				$return[$obj->rowid]=array(	'id' => $obj->rowid,
3660
				'ref' => $obj->ref,
3661
				'status' => $obj->fk_statut);
3662
			}
3663
			//print_r($return);
3664
			return $return;
3665
		}
3666
		else
3667
		{
3668
			$this->error=$this->db->error();
3669
			return -1;
3670
		}
3671
	}
3672
3673
3674
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3675
	/**
3676
	 *	Return list of invoices qualified to be corrected by a credit note.
3677
	 *	Invoices matching the following rules are returned:
3678
	 *	(validated + payment on process) or classified (payed completely or payed partiely) + not already replaced + not already a credit note
3679
	 *
3680
	 *	@param		int		$socid		Id thirdparty
3681
	 *	@return    	array				Array of invoices ($id => array('ref'=>,'paymentornot'=>,'status'=>,'paye'=>)
3682
	 */
3683
    public function list_qualified_avoir_invoices($socid = 0)
3684
	{
3685
        // phpcs:enable
3686
		global $conf;
3687
3688
		$return = array();
3689
3690
3691
		$sql = "SELECT f.rowid as rowid, f.ref, f.fk_statut, f.type, f.paye, pf.fk_paiement";
3692
		$sql.= " FROM ".MAIN_DB_PREFIX."facture as f";
3693
		$sql.= " LEFT JOIN ".MAIN_DB_PREFIX."paiement_facture as pf ON f.rowid = pf.fk_facture";
3694
		$sql.= " LEFT JOIN ".MAIN_DB_PREFIX."facture as ff ON (f.rowid = ff.fk_facture_source AND ff.type=".self::TYPE_REPLACEMENT.")";
3695
		$sql.= " WHERE f.entity IN (".getEntity('invoice').")";
3696
		$sql.= " AND f.fk_statut in (".self::STATUS_VALIDATED.",".self::STATUS_CLOSED.")";
3697
		//  $sql.= " WHERE f.fk_statut >= 1";
3698
		//	$sql.= " AND (f.paye = 1";				// Classee payee completement
3699
		//	$sql.= " OR f.close_code IS NOT NULL)";	// Classee payee partiellement
3700
		$sql.= " AND ff.type IS NULL";			// Renvoi vrai si pas facture de remplacement
3701
		$sql.= " AND f.type != ".self::TYPE_CREDIT_NOTE;				// Type non 2 si facture non avoir
3702
3703
		if($conf->global->INVOICE_USE_SITUATION_CREDIT_NOTE){
3704
		    // Select the last situation invoice
3705
		    $sqlSit = 'SELECT MAX(fs.rowid)';
3706
		    $sqlSit.= " FROM ".MAIN_DB_PREFIX."facture as fs";
3707
		    $sqlSit.= " WHERE fs.entity = ".$conf->entity;
3708
		    $sqlSit.= " AND fs.type = ".self::TYPE_SITUATION;
3709
		    $sqlSit.= " AND fs.fk_statut in (".self::STATUS_VALIDATED.",".self::STATUS_CLOSED.")";
3710
		    $sqlSit.= " GROUP BY fs.situation_cycle_ref";
3711
		    $sqlSit.= " ORDER BY fs.situation_counter";
3712
            $sql.= " AND ( f.type != ".self::TYPE_SITUATION . " OR f.rowid IN (".$sqlSit.") )";	// Type non 5 si facture non avoir
3713
		}
3714
		else
3715
		{
3716
		    $sql.= " AND f.type != ".self::TYPE_SITUATION ; // Type non 5 si facture non avoir
3717
		}
3718
3719
		if ($socid > 0) $sql.=" AND f.fk_soc = ".$socid;
3720
		$sql.= " ORDER BY f.ref";
3721
3722
		dol_syslog(get_class($this)."::list_qualified_avoir_invoices", LOG_DEBUG);
3723
		$resql=$this->db->query($sql);
3724
		if ($resql)
3725
		{
3726
			while ($obj=$this->db->fetch_object($resql))
3727
			{
3728
				$qualified=0;
3729
				if ($obj->fk_statut == self::STATUS_VALIDATED) $qualified=1;
3730
				if ($obj->fk_statut == self::STATUS_CLOSED) $qualified=1;
3731
				if ($qualified)
3732
				{
3733
					//$ref=$obj->ref;
3734
					$paymentornot=($obj->fk_paiement?1:0);
3735
					$return[$obj->rowid]=array('ref'=>$obj->ref,'status'=>$obj->fk_statut,'type'=>$obj->type,'paye'=>$obj->paye,'paymentornot'=>$paymentornot);
3736
				}
3737
			}
3738
3739
			return $return;
3740
		}
3741
		else
3742
		{
3743
			$this->error=$this->db->error();
3744
			return -1;
3745
		}
3746
	}
3747
3748
3749
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3750
	/**
3751
	 *	Create a withdrawal request for a standing order.
3752
	 *  Use the remain to pay excluding all existing open direct debit requests.
3753
	 *
3754
	 *	@param      User	$fuser      User asking the direct debit transfer
3755
	 *  @param		float	$amount		Amount we request direct debit for
3756
	 *	@return     int         		<0 if KO, >0 if OK
3757
	 */
3758
    public function demande_prelevement($fuser, $amount = 0)
3759
	{
3760
        // phpcs:enable
3761
3762
		$error=0;
3763
3764
		dol_syslog(get_class($this)."::demande_prelevement", LOG_DEBUG);
3765
3766
		if ($this->statut > self::STATUS_DRAFT && $this->paye == 0)
3767
		{
3768
	        require_once DOL_DOCUMENT_ROOT . '/societe/class/companybankaccount.class.php';
3769
	        $bac = new CompanyBankAccount($this->db);
3770
	        $bac->fetch(0, $this->socid);
3771
3772
        	$sql = 'SELECT count(*)';
3773
			$sql.= ' FROM '.MAIN_DB_PREFIX.'prelevement_facture_demande';
3774
			$sql.= ' WHERE fk_facture = '.$this->id;
3775
			$sql.= ' AND traite = 0';
3776
3777
			dol_syslog(get_class($this)."::demande_prelevement", LOG_DEBUG);
3778
			$resql=$this->db->query($sql);
3779
			if ($resql)
3780
			{
3781
				$row = $this->db->fetch_row($resql);
3782
				if ($row[0] == 0)
3783
				{
3784
					$now=dol_now();
3785
3786
                    $totalpaye  = $this->getSommePaiement();
3787
                    $totalcreditnotes = $this->getSumCreditNotesUsed();
3788
                    $totaldeposits = $this->getSumDepositsUsed();
3789
                    //print "totalpaye=".$totalpaye." totalcreditnotes=".$totalcreditnotes." totaldeposts=".$totaldeposits;
3790
3791
                    // We can also use bcadd to avoid pb with floating points
3792
                    // For example print 239.2 - 229.3 - 9.9; does not return 0.
3793
                    //$resteapayer=bcadd($this->total_ttc,$totalpaye,$conf->global->MAIN_MAX_DECIMALS_TOT);
3794
                    //$resteapayer=bcadd($resteapayer,$totalavoir,$conf->global->MAIN_MAX_DECIMALS_TOT);
3795
					if (empty($amount)) $amount = price2num($this->total_ttc - $totalpaye - $totalcreditnotes - $totaldeposits, 'MT');
3796
3797
					if (is_numeric($amount) && $amount != 0)
3798
					{
3799
						$sql = 'INSERT INTO '.MAIN_DB_PREFIX.'prelevement_facture_demande';
3800
						$sql .= ' (fk_facture, amount, date_demande, fk_user_demande, code_banque, code_guichet, number, cle_rib)';
3801
						$sql .= ' VALUES ('.$this->id;
3802
						$sql .= ",'".price2num($amount)."'";
3803
						$sql .= ",'".$this->db->idate($now)."'";
3804
						$sql .= ",".$fuser->id;
3805
						$sql .= ",'".$bac->code_banque."'";
3806
						$sql .= ",'".$bac->code_guichet."'";
3807
						$sql .= ",'".$bac->number."'";
3808
						$sql .= ",'".$bac->cle_rib."')";
3809
3810
						dol_syslog(get_class($this)."::demande_prelevement", LOG_DEBUG);
3811
						$resql=$this->db->query($sql);
3812
						if (! $resql)
3813
						{
3814
						    $this->error=$this->db->lasterror();
3815
						    dol_syslog(get_class($this).'::demandeprelevement Erreur');
3816
						    $error++;
3817
						}
3818
					}
3819
					else
3820
					{
3821
						$this->error='WithdrawRequestErrorNilAmount';
3822
	                    dol_syslog(get_class($this).'::demandeprelevement WithdrawRequestErrorNilAmount');
3823
	                    $error++;
3824
					}
3825
3826
        			if (! $error)
3827
        			{
3828
        				// Force payment mode of invoice to withdraw
3829
        				$payment_mode_id = dol_getIdFromCode($this->db, 'PRE', 'c_paiement', 'code', 'id', 1);
3830
        				if ($payment_mode_id > 0)
3831
        				{
3832
        					$result=$this->setPaymentMethods($payment_mode_id);
3833
        				}
3834
        			}
3835
3836
                    if ($error) return -1;
3837
                    return 1;
3838
                }
3839
                else
3840
                {
3841
                    $this->error="A request already exists";
3842
                    dol_syslog(get_class($this).'::demandeprelevement Impossible de creer une demande, demande deja en cours');
3843
                    return 0;
3844
                }
3845
            }
3846
            else
3847
            {
3848
                $this->error=$this->db->error();
3849
                dol_syslog(get_class($this).'::demandeprelevement Erreur -2');
3850
                return -2;
3851
            }
3852
        }
3853
        else
3854
        {
3855
            $this->error="Status of invoice does not allow this";
3856
            dol_syslog(get_class($this)."::demandeprelevement ".$this->error." $this->statut, $this->paye, $this->mode_reglement_id");
3857
            return -3;
3858
        }
3859
    }
3860
3861
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3862
	/**
3863
	 *  Supprime une demande de prelevement
3864
	 *
3865
	 *  @param  User	$fuser      User making delete
3866
	 *  @param  int		$did        id de la demande a supprimer
3867
	 *  @return	int					<0 if OK, >0 if KO
3868
	 */
3869
    public function demande_prelevement_delete($fuser, $did)
3870
	{
3871
        // phpcs:enable
3872
		$sql = 'DELETE FROM '.MAIN_DB_PREFIX.'prelevement_facture_demande';
3873
		$sql .= ' WHERE rowid = '.$did;
3874
		$sql .= ' AND traite = 0';
3875
		if ( $this->db->query($sql) )
3876
		{
3877
			return 0;
3878
		}
3879
		else
3880
		{
3881
			$this->error=$this->db->lasterror();
3882
			dol_syslog(get_class($this).'::demande_prelevement_delete Error '.$this->error);
3883
			return -1;
3884
		}
3885
	}
3886
3887
3888
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3889
	/**
3890
	 *	Load indicators for dashboard (this->nbtodo and this->nbtodolate)
3891
	 *
3892
	 *	@param  User		$user    	Object user
3893
	 *	@return WorkboardResponse|int 	<0 if KO, WorkboardResponse if OK
3894
	 */
3895
    public function load_board($user)
3896
	{
3897
        // phpcs:enable
3898
		global $conf, $langs;
3899
3900
		$clause = " WHERE";
3901
3902
		$sql = "SELECT f.rowid, f.date_lim_reglement as datefin,f.fk_statut, f.total";
3903
		$sql.= " FROM ".MAIN_DB_PREFIX."facture as f";
3904
		if (!$user->rights->societe->client->voir && !$user->societe_id)
1 ignored issue
show
Deprecated Code introduced by
The property User::$societe_id has been deprecated. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

3904
		if (!$user->rights->societe->client->voir && !/** @scrutinizer ignore-deprecated */ $user->societe_id)
Loading history...
3905
		{
3906
			$sql.= " LEFT JOIN ".MAIN_DB_PREFIX."societe_commerciaux as sc ON f.fk_soc = sc.fk_soc";
3907
			$sql.= " WHERE sc.fk_user = " .$user->id;
3908
			$clause = " AND";
3909
		}
3910
		$sql.= $clause." f.paye=0";
3911
		$sql.= " AND f.entity IN (".getEntity('invoice').")";
3912
		$sql.= " AND f.fk_statut = ".self::STATUS_VALIDATED;
3913
		if ($user->societe_id) $sql.= " AND f.fk_soc = ".$user->societe_id;
1 ignored issue
show
Deprecated Code introduced by
The property User::$societe_id has been deprecated. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

3913
		if ($user->societe_id) $sql.= " AND f.fk_soc = "./** @scrutinizer ignore-deprecated */ $user->societe_id;
Loading history...
3914
3915
		$resql=$this->db->query($sql);
3916
		if ($resql)
3917
		{
3918
			$langs->load("bills");
3919
			$now=dol_now();
3920
3921
			$response = new WorkboardResponse();
3922
			$response->warning_delay=$conf->facture->client->warning_delay/60/60/24;
3923
			$response->label=$langs->trans("CustomerBillsUnpaid");
3924
			$response->url=DOL_URL_ROOT.'/compta/facture/list.php?search_status=1&mainmenu=billing&leftmenu=customers_bills';
3925
			$response->img=img_object('', "bill");
3926
3927
			$generic_facture = new Facture($this->db);
3928
3929
			while ($obj=$this->db->fetch_object($resql))
3930
			{
3931
				$generic_facture->date_lim_reglement = $this->db->jdate($obj->datefin);
3932
				$generic_facture->statut = $obj->fk_statut;
3933
3934
				$response->nbtodo++;
3935
				$response->total += $obj->total;
3936
3937
				if ($generic_facture->hasDelay()) {
3938
					$response->nbtodolate++;
3939
				}
3940
			}
3941
3942
			return $response;
3943
		}
3944
		else
3945
		{
3946
			dol_print_error($this->db);
3947
			$this->error=$this->db->error();
3948
			return -1;
3949
		}
3950
	}
3951
3952
3953
	/* gestion des contacts d'une facture */
3954
3955
	/**
3956
	 *	Retourne id des contacts clients de facturation
3957
	 *
3958
	 *	@return     array       Liste des id contacts facturation
3959
	 */
3960
    public function getIdBillingContact()
3961
	{
3962
		return $this->getIdContact('external', 'BILLING');
3963
	}
3964
3965
	/**
3966
	 *	Retourne id des contacts clients de livraison
3967
	 *
3968
	 *	@return     array       Liste des id contacts livraison
3969
	 */
3970
    public function getIdShippingContact()
3971
	{
3972
		return $this->getIdContact('external', 'SHIPPING');
3973
	}
3974
3975
3976
	/**
3977
	 *  Initialise an instance with random values.
3978
	 *  Used to build previews or test instances.
3979
	 *	id must be 0 if object instance is a specimen.
3980
	 *
3981
	 *	@param	string		$option		''=Create a specimen invoice with lines, 'nolines'=No lines
3982
	 *  @return	void
3983
	 */
3984
    public function initAsSpecimen($option = '')
3985
	{
3986
		global $langs;
3987
3988
		$now=dol_now();
3989
		$arraynow=dol_getdate($now);
3990
		$nownotime=dol_mktime(0, 0, 0, $arraynow['mon'], $arraynow['mday'], $arraynow['year']);
3991
3992
        // Load array of products prodids
3993
		$num_prods = 0;
3994
		$prodids = array();
3995
		$sql = "SELECT rowid";
3996
		$sql.= " FROM ".MAIN_DB_PREFIX."product";
3997
		$sql.= " WHERE entity IN (".getEntity('product').")";
3998
		$resql = $this->db->query($sql);
3999
		if ($resql)
4000
		{
4001
			$num_prods = $this->db->num_rows($resql);
4002
			$i = 0;
4003
			while ($i < $num_prods)
4004
			{
4005
				$i++;
4006
				$row = $this->db->fetch_row($resql);
4007
				$prodids[$i] = $row[0];
4008
			}
4009
		}
4010
		//Avoid php warning Warning: mt_rand(): max(0) is smaller than min(1) when no product exists
4011
		if (empty($num_prods)) {
1 ignored issue
show
introduced by
The condition empty($num_prods) is always true.
Loading history...
4012
			$num_prods=1;
4013
		}
4014
4015
		// Initialize parameters
4016
		$this->id=0;
4017
		$this->entity = 1;
4018
		$this->ref = 'SPECIMEN';
4019
		$this->specimen=1;
4020
		$this->socid = 1;
4021
		$this->date = $nownotime;
4022
		$this->date_lim_reglement = $nownotime + 3600 * 24 *30;
4023
		$this->cond_reglement_id   = 1;
4024
		$this->cond_reglement_code = 'RECEP';
4025
		$this->date_lim_reglement=$this->calculate_date_lim_reglement();
4026
		$this->mode_reglement_id   = 0;		// Not forced to show payment mode CHQ + VIR
4027
		$this->mode_reglement_code = '';	// Not forced to show payment mode CHQ + VIR
4028
		$this->note_public='This is a comment (public)';
4029
		$this->note_private='This is a comment (private)';
4030
		$this->note='This is a comment (private)';
1 ignored issue
show
Deprecated Code introduced by
The property CommonObject::$note has been deprecated. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

4030
		/** @scrutinizer ignore-deprecated */ $this->note='This is a comment (private)';
Loading history...
4031
		$this->fk_incoterms=0;
4032
		$this->location_incoterms='';
4033
4034
		if (empty($option) || $option != 'nolines')
4035
		{
4036
			// Lines
4037
			$nbp = 5;
4038
			$xnbp = 0;
4039
			while ($xnbp < $nbp)
4040
			{
4041
				$line=new FactureLigne($this->db);
4042
				$line->desc=$langs->trans("Description")." ".$xnbp;
4043
				$line->qty=1;
4044
				$line->subprice=100;
4045
				$line->tva_tx=19.6;
4046
				$line->localtax1_tx=0;
4047
				$line->localtax2_tx=0;
4048
				$line->remise_percent=0;
4049
				if ($xnbp == 1)        // Qty is negative (product line)
4050
				{
4051
					$prodid = mt_rand(1, $num_prods);
4052
					$line->fk_product=$prodids[$prodid];
4053
					$line->qty=-1;
4054
					$line->total_ht=-100;
4055
					$line->total_ttc=-119.6;
4056
					$line->total_tva=-19.6;
4057
					$line->multicurrency_total_ht=-200;
4058
					$line->multicurrency_total_ttc=-239.2;
4059
					$line->multicurrency_total_tva=-39.2;
4060
				}
4061
				elseif ($xnbp == 2)    // UP is negative (free line)
4062
				{
4063
					$line->subprice=-100;
4064
					$line->total_ht=-100;
4065
					$line->total_ttc=-119.6;
4066
					$line->total_tva=-19.6;
4067
					$line->remise_percent=0;
4068
					$line->multicurrency_total_ht=-200;
4069
					$line->multicurrency_total_ttc=-239.2;
4070
					$line->multicurrency_total_tva=-39.2;
4071
				}
4072
				elseif ($xnbp == 3)    // Discount is 50% (product line)
4073
				{
4074
					$prodid = mt_rand(1, $num_prods);
4075
					$line->fk_product=$prodids[$prodid];
4076
					$line->total_ht=50;
4077
					$line->total_ttc=59.8;
4078
					$line->total_tva=9.8;
4079
					$line->multicurrency_total_ht=100;
4080
					$line->multicurrency_total_ttc=119.6;
4081
					$line->multicurrency_total_tva=19.6;
4082
					$line->remise_percent=50;
4083
				}
4084
				else    // (product line)
4085
				{
4086
					$prodid = mt_rand(1, $num_prods);
4087
					$line->fk_product=$prodids[$prodid];
4088
					$line->total_ht=100;
4089
					$line->total_ttc=119.6;
4090
					$line->total_tva=19.6;
4091
					$line->multicurrency_total_ht=200;
4092
					$line->multicurrency_total_ttc=239.2;
4093
					$line->multicurrency_total_tva=39.2;
4094
					$line->remise_percent=0;
4095
				}
4096
4097
				$this->lines[$xnbp]=$line;
4098
4099
4100
				$this->total_ht       += $line->total_ht;
4101
				$this->total_tva      += $line->total_tva;
4102
				$this->total_ttc      += $line->total_ttc;
4103
4104
				$this->multicurrency_total_ht       += $line->multicurrency_total_ht;
4105
				$this->multicurrency_total_tva      += $line->multicurrency_total_tva;
4106
				$this->multicurrency_total_ttc      += $line->multicurrency_total_ttc;
4107
4108
				$xnbp++;
4109
			}
4110
			$this->revenuestamp = 0;
4111
4112
			// Add a line "offered"
4113
			$line=new FactureLigne($this->db);
4114
			$line->desc=$langs->trans("Description")." (offered line)";
4115
			$line->qty=1;
4116
			$line->subprice=100;
4117
			$line->tva_tx=19.6;
4118
			$line->localtax1_tx=0;
4119
			$line->localtax2_tx=0;
4120
			$line->remise_percent=100;
4121
			$line->total_ht=0;
4122
			$line->total_ttc=0;    // 90 * 1.196
4123
			$line->total_tva=0;
4124
			$line->multicurrency_total_ht=0;
4125
			$line->multicurrency_total_ttc=0;
4126
			$line->multicurrency_total_tva=0;
4127
			$prodid = mt_rand(1, $num_prods);
4128
			$line->fk_product=$prodids[$prodid];
4129
4130
			$this->lines[$xnbp]=$line;
4131
			$xnbp++;
4132
		}
4133
	}
4134
4135
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4136
	/**
4137
	 *      Load indicators for dashboard (this->nbtodo and this->nbtodolate)
4138
	 *
4139
	 *      @return         int     <0 if KO, >0 if OK
4140
	 */
4141
    public function load_state_board()
4142
	{
4143
        // phpcs:enable
4144
		global $conf, $user;
4145
4146
		$this->nb=array();
4147
4148
		$clause = "WHERE";
4149
4150
		$sql = "SELECT count(f.rowid) as nb";
4151
		$sql.= " FROM ".MAIN_DB_PREFIX."facture as f";
4152
		$sql.= " LEFT JOIN ".MAIN_DB_PREFIX."societe as s ON f.fk_soc = s.rowid";
4153
		if (!$user->rights->societe->client->voir && !$user->societe_id)
4154
		{
4155
			$sql.= " LEFT JOIN ".MAIN_DB_PREFIX."societe_commerciaux as sc ON s.rowid = sc.fk_soc";
4156
			$sql.= " WHERE sc.fk_user = " .$user->id;
4157
			$clause = "AND";
4158
		}
4159
		$sql.= " ".$clause." f.entity IN (".getEntity('invoice').")";
4160
4161
		$resql=$this->db->query($sql);
4162
		if ($resql)
4163
		{
4164
			while ($obj=$this->db->fetch_object($resql))
4165
			{
4166
				$this->nb["invoices"]=$obj->nb;
4167
			}
4168
            $this->db->free($resql);
4169
			return 1;
4170
		}
4171
		else
4172
		{
4173
			dol_print_error($this->db);
4174
			$this->error=$this->db->error();
4175
			return -1;
4176
		}
4177
	}
4178
4179
	/**
4180
	 * 	Create an array of invoice lines
4181
	 *
4182
	 * 	@return int		>0 if OK, <0 if KO
4183
	 */
4184
    public function getLinesArray()
4185
	{
4186
	    return $this->fetch_lines();
4187
	}
4188
4189
	/**
4190
	 *  Create a document onto disk according to template module.
4191
	 *
4192
	 *	@param	string		$modele			Generator to use. Caller must set it to obj->modelpdf or GETPOST('modelpdf','alpha') for example.
4193
	 *	@param	Translate	$outputlangs	objet lang a utiliser pour traduction
4194
	 *  @param  int			$hidedetails    Hide details of lines
4195
	 *  @param  int			$hidedesc       Hide description
4196
	 *  @param  int			$hideref        Hide ref
4197
	 *  @param   null|array  $moreparams     Array to provide more information
4198
	 *	@return int        					<0 if KO, >0 if OK
4199
	 */
4200
	public function generateDocument($modele, $outputlangs, $hidedetails = 0, $hidedesc = 0, $hideref = 0, $moreparams = null)
4201
	{
4202
		global $conf,$langs;
4203
4204
		$langs->load("bills");
4205
4206
		if (! dol_strlen($modele))
4207
		{
4208
			$modele = 'crabe';
4209
			$thisTypeConfName = 'FACTURE_ADDON_PDF_'.$this->type;
4210
4211
			if ($this->modelpdf) {
4212
				$modele = $this->modelpdf;
4213
			} elseif (! empty($conf->global->$thisTypeConfName)) {
4214
				$modele = $conf->global->$thisTypeConfName;
4215
			} elseif (! empty($conf->global->FACTURE_ADDON_PDF)) {
4216
				$modele = $conf->global->FACTURE_ADDON_PDF;
4217
			}
4218
		}
4219
4220
		$modelpath = "core/modules/facture/doc/";
4221
4222
		return $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams);
4223
	}
4224
4225
	/**
4226
	 * Gets the smallest reference available for a new cycle
4227
	 *
4228
	 * @return int >= 1 if OK, -1 if error
4229
	 */
4230
    public function newCycle()
4231
	{
4232
		$sql = 'SELECT max(situation_cycle_ref) FROM ' . MAIN_DB_PREFIX . 'facture as f';
4233
		$sql.= " WHERE f.entity in (".getEntity('invoice', 0).")";
4234
		$resql = $this->db->query($sql);
4235
		if ($resql) {
4236
			if ($resql->num_rows > 0)
4237
			{
4238
				$res = $this->db->fetch_array($resql);
4239
				$ref = $res['max(situation_cycle_ref)'];
4240
				$ref++;
4241
			} else {
4242
				$ref = 1;
4243
			}
4244
			$this->db->free($resql);
4245
			return $ref;
4246
		} else {
4247
			$this->error = $this->db->lasterror();
4248
			dol_syslog("Error sql=" . $sql . ", error=" . $this->error, LOG_ERR);
4249
			return -1;
4250
		}
4251
	}
4252
4253
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4254
	/**
4255
	 * Checks if the invoice is the first of a cycle
4256
	 *
4257
	 * @return boolean
4258
	 */
4259
    public function is_first()
4260
	{
4261
        // phpcs:enable
4262
		return ($this->situation_counter == 1);
4263
	}
4264
4265
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4266
	/**
4267
	 * Returns an array containing the previous situations as Facture objects
4268
	 *
4269
	 * @return mixed -1 if error, array of previous situations
4270
	 */
4271
    public function get_prev_sits()
4272
	{
4273
        // phpcs:enable
4274
		global $conf;
4275
4276
		$sql = 'SELECT rowid FROM ' . MAIN_DB_PREFIX . 'facture';
4277
		$sql .= ' where situation_cycle_ref = ' . $this->situation_cycle_ref;
4278
		$sql .= ' and situation_counter < ' . $this->situation_counter;
4279
		$sql .= ' AND entity = '. ($this->entity > 0 ? $this->entity : $conf->entity);
4280
		$resql = $this->db->query($sql);
4281
		$res = array();
4282
		if ($resql && $resql->num_rows > 0) {
4283
			while ($row = $this->db->fetch_object($resql)) {
4284
				$id = $row->rowid;
4285
				$situation = new Facture($this->db);
4286
				$situation->fetch($id);
4287
				$res[] = $situation;
4288
			}
4289
		} else {
4290
			$this->error = $this->db->error();
4291
			dol_syslog("Error sql=" . $sql . ", error=" . $this->error, LOG_ERR);
4292
			return -1;
4293
		}
4294
4295
		return $res;
4296
	}
4297
4298
	/**
4299
	 * Sets the invoice as a final situation
4300
	 *
4301
	 *  @param  	User	$user    	Object user
4302
	 *  @param     	int		$notrigger	1=Does not execute triggers, 0= execute triggers
4303
	 *	@return		int 				<0 if KO, >0 if OK
4304
	 */
4305
    public function setFinal(User $user, $notrigger = 0)
4306
	{
4307
		$error=0;
4308
4309
		$this->db->begin();
4310
4311
		$sql = 'UPDATE ' . MAIN_DB_PREFIX . 'facture SET situation_final = ' . $this->situation_final . ' where rowid = ' . $this->id;
4312
4313
		dol_syslog(__METHOD__, LOG_DEBUG);
4314
		$resql=$this->db->query($sql);
4315
		if (!$resql)
4316
		{
4317
			$this->errors[]=$this->db->error();
4318
			$error++;
4319
		}
4320
4321
		if (! $notrigger && empty($error))
4322
		{
4323
			// Call trigger
4324
			$result=$this->call_trigger('BILL_MODIFY', $user);
4325
			if ($result < 0) $error++;
4326
			// End call triggers
4327
		}
4328
4329
		if (! $error)
4330
		{
4331
			$this->db->commit();
4332
			return 1;
4333
		}
4334
		else
4335
		{
4336
			foreach($this->errors as $errmsg)
4337
			{
4338
				dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
4339
				$this->error.=($this->error?', '.$errmsg:$errmsg);
4340
			}
4341
			$this->db->rollback();
4342
			return -1*$error;
4343
		}
4344
	}
4345
4346
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4347
	/**
4348
	 * Checks if the invoice is the last in its cycle
4349
	 *
4350
	 * @return bool Last of the cycle status
4351
	 */
4352
    public function is_last_in_cycle()
4353
	{
4354
        // phpcs:enable
4355
		global $conf;
4356
4357
		if (!empty($this->situation_cycle_ref)) {
4358
			// No point in testing anything if we're not inside a cycle
4359
			$sql = 'SELECT max(situation_counter) FROM ' . MAIN_DB_PREFIX . 'facture WHERE situation_cycle_ref = ' . $this->situation_cycle_ref . ' AND entity = ' . ($this->entity > 0 ? $this->entity : $conf->entity);
4360
			$resql = $this->db->query($sql);
4361
4362
			if ($resql && $resql->num_rows > 0) {
4363
				$res = $this->db->fetch_array($resql);
4364
				$last = $res['max(situation_counter)'];
4365
				return ($last == $this->situation_counter);
4366
			} else {
4367
				$this->error = $this->db->lasterror();
4368
				dol_syslog(get_class($this) . "::select Error " . $this->error, LOG_ERR);
4369
				return false;
4370
			}
4371
		} else {
4372
			return true;
4373
		}
4374
	}
4375
4376
	/**
4377
	 * Function used to replace a thirdparty id with another one.
4378
	 *
4379
	 * @param  DoliDB  $db             Database handler
4380
	 * @param  int     $origin_id      Old third-party id
4381
	 * @param  int     $dest_id        New third-party id
4382
	 * @return bool
4383
	 */
4384
	public static function replaceThirdparty(DoliDB $db, $origin_id, $dest_id)
4385
	{
4386
		$tables = array(
4387
			'facture'
4388
		);
4389
4390
		return CommonObject::commonReplaceThirdparty($db, $origin_id, $dest_id, $tables);
4391
	}
4392
4393
	/**
4394
	 * Is the customer invoice delayed?
4395
	 *
4396
	 * @return bool
4397
	 */
4398
	public function hasDelay()
4399
	{
4400
		global $conf;
4401
4402
		$now = dol_now();
4403
4404
		// Paid invoices have status STATUS_CLOSED
4405
		if ($this->statut != Facture::STATUS_VALIDATED) return false;
4406
4407
		return $this->date_lim_reglement < ($now - $conf->facture->client->warning_delay);
4408
	}
4409
4410
4411
	/**
4412
	 * @return number or -1 if not available
4413
	 */
4414
	public function getRetainedWarrantyAmount()
4415
	{
4416
	    if(empty($this->retained_warranty) ){
4417
	        return -1;
4418
	    }
4419
	    
4420
	    $retainedWarrantyAmount = 0;
4421
	    
4422
	    // Billed - retained warranty
4423
	    if($this->type == Facture::TYPE_SITUATION)
4424
	    {
4425
	        $displayWarranty = true;
4426
	        // Check if this situation invoice is 100% for real
4427
	        if(!empty($this->lines)){
4428
	            foreach($this->lines as $i => $line){
4429
	                if($line->product_type < 2 && $line->situation_percent < 100){
4430
	                    $displayWarranty = false;
4431
	                    break;
4432
	            	}
4433
	            }
4434
	        }
4435
	        
4436
	        if($displayWarranty && !empty($this->situation_final))
4437
	        {
4438
	            $this->fetchPreviousNextSituationInvoice();
4439
	            $TPreviousIncoice = $this->tab_previous_situation_invoice;
4440
	            
4441
	            $total2BillWT = 0;
4442
	            foreach ($TPreviousIncoice as &$fac){
4443
	                $total2BillWT += $fac->total_ttc;
4444
	            }
4445
	            $total2BillWT += $this->total_ttc;
4446
	            
4447
	            $retainedWarrantyAmount = $total2BillWT * $this->retained_warranty / 100;
4448
	        }
4449
	        else{
4450
	            return -1;
4451
	        }
4452
	    }
4453
	    else
4454
	    {
4455
	        // Because one day retained warranty could be used on standard invoices
4456
	        $retainedWarrantyAmount = $this->total_ttc * $this->retained_warranty / 100;
4457
	    }
4458
	    
4459
	    return $retainedWarrantyAmount;
4460
	}
4461
	
4462
	/**
4463
	 *  Change the retained warranty
4464
	 *
4465
	 *  @param		float		$value		value of retained warranty
4466
	 *  @return		int				>0 if OK, <0 if KO
4467
	 */
4468
	public function setRetainedWarranty($value)
4469
	{
4470
	    dol_syslog(get_class($this).'::setRetainedWarranty('.$value.')');
4471
	    if ($this->statut >= 0)
4472
	    {
4473
	        $fieldname = 'retained_warranty';
4474
	        $sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element;
4475
	        $sql .= ' SET '.$fieldname.' = '.floatval($value);
4476
	        $sql .= ' WHERE rowid='.$this->id;
4477
	        
4478
	        if ($this->db->query($sql))
4479
	        {
4480
	            $this->retained_warranty = floatval($value);
4481
	            return 1;
4482
	        }
4483
	        else
4484
	        {
4485
	            dol_syslog(get_class($this).'::setRetainedWarranty Erreur '.$sql.' - '.$this->db->error());
4486
	            $this->error=$this->db->error();
4487
	            return -1;
4488
	        }
4489
	    }
4490
	    else
4491
	    {
4492
	        dol_syslog(get_class($this).'::setRetainedWarranty, status of the object is incompatible');
4493
	        $this->error='Status of the object is incompatible '.$this->statut;
4494
	        return -2;
4495
	    }
4496
	}
4497
	
4498
	
4499
	/**
4500
	 *  Change the retained_warranty_date_limit
4501
	 *
4502
	 *  @param		int		$timestamp		date limit of retained warranty in timestamp format
4503
	 *  @param		string	$dateYmd		date limit of retained warranty in Y m d format
4504
	 *  @return		int				>0 if OK, <0 if KO
4505
	 */
4506
	public function setRetainedWarrantyDateLimit($timestamp, $dateYmd = false)
4507
	{
4508
	    if(!$timestamp && $dateYmd){
4509
	        $timestamp = $this->db->jdate($dateYmd);
4510
	    }
4511
	    
4512
	    
4513
	    dol_syslog(get_class($this).'::setRetainedWarrantyDateLimit('.$timestamp.')');
4514
	    if ($this->statut >= 0)
4515
	    {
4516
	        $fieldname = 'retained_warranty_date_limit';
4517
	        $sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element;
4518
	        $sql .= ' SET '.$fieldname.' = '.(strval($timestamp)!='' ? '\'' .$this->db->idate($timestamp).'\''  : 'null' );
4519
	        $sql .= ' WHERE rowid='.$this->id;
4520
	        
4521
	        if ($this->db->query($sql))
4522
	        {
4523
	            $this->retained_warranty_date_limit = $timestamp;
1 ignored issue
show
Documentation Bug introduced by
It seems like $timestamp can also be of type string. However, the property $retained_warranty_date_limit is declared as type integer. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
4524
	            return 1;
4525
	        }
4526
	        else
4527
	        {
4528
	            dol_syslog(get_class($this).'::setRetainedWarrantyDateLimit Erreur '.$sql.' - '.$this->db->error());
4529
	            $this->error=$this->db->error();
4530
	            return -1;
4531
	        }
4532
	    }
4533
	    else
4534
	    {
4535
	        dol_syslog(get_class($this).'::setRetainedWarrantyDateLimit, status of the object is incompatible');
4536
	        $this->error='Status of the object is incompatible '.$this->statut;
4537
	        return -2;
4538
	    }
4539
	}
4540
}
4541
4542
/**
4543
 *	Class to manage invoice lines.
4544
 *  Saved into database table llx_facturedet
4545
 */
4546
class FactureLigne extends CommonInvoiceLine
4547
{
4548
    /**
4549
	 * @var string ID to identify managed object
4550
	 */
4551
	public $element='facturedet';
4552
4553
    /**
4554
	 * @var string Name of table without prefix where object is stored
4555
	 */
4556
	public $table_element='facturedet';
4557
4558
	public $oldline;
4559
4560
	//! From llx_facturedet
4561
	//! Id facture
4562
	public $fk_facture;
4563
	//! Id parent line
4564
	public $fk_parent_line;
4565
	/**
4566
	 * @deprecated
4567
	 */
4568
	public $label;
4569
	//! Description ligne
4570
	public $desc;
4571
4572
	public $localtax1_type;	// Local tax 1 type
4573
	public $localtax2_type;	// Local tax 2 type
4574
	public $fk_remise_except;	// Link to line into llx_remise_except
4575
	public $rang = 0;
4576
4577
	public $fk_fournprice;
4578
	public $pa_ht;
4579
	public $marge_tx;
4580
	public $marque_tx;
4581
4582
	public $special_code;	// Liste d'options non cumulabels:
4583
	// 1: frais de port
4584
	// 2: ecotaxe
4585
	// 3: ??
4586
4587
	public $origin;
4588
	public $origin_id;
4589
4590
	public $fk_code_ventilation = 0;
4591
4592
	public $date_start;
4593
	public $date_end;
4594
4595
	// From llx_product
4596
	/**
4597
	 * @deprecated
4598
	 * @see $product_ref
4599
	 */
4600
	public $ref;				// Product ref (deprecated)
4601
	public $product_ref;       // Product ref
4602
	/**
4603
	 * @deprecated
4604
	 * @see $product_label
4605
	 */
4606
	public $libelle;      		// Product label (deprecated)
4607
	public $product_label;     // Product label
4608
	public $product_desc;  	// Description produit
4609
4610
	public $skip_update_total; // Skip update price total for special lines
4611
4612
	/**
4613
	 * @var int Situation advance percentage
4614
	 */
4615
	public $situation_percent;
4616
4617
	/**
4618
	 * @var int Previous situation line id reference
4619
	 */
4620
	public $fk_prev_id;
4621
4622
	// Multicurrency
4623
	public $fk_multicurrency;
4624
	public $multicurrency_code;
4625
	public $multicurrency_subprice;
4626
	public $multicurrency_total_ht;
4627
	public $multicurrency_total_tva;
4628
	public $multicurrency_total_ttc;
4629
4630
	/**
4631
	 *	Load invoice line from database
4632
	 *
4633
	 *	@param	int		$rowid      id of invoice line to get
4634
	 *	@return	int					<0 if KO, >0 if OK
4635
	 */
4636
    public function fetch($rowid)
4637
	{
4638
		$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,';
4639
		$sql.= ' fd.localtax1_tx, fd. localtax2_tx, fd.remise, fd.remise_percent, fd.fk_remise_except, fd.subprice,';
4640
		$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,';
4641
		$sql.= ' fd.info_bits, fd.special_code, fd.total_ht, fd.total_tva, fd.total_ttc, fd.total_localtax1, fd.total_localtax2, fd.rang,';
4642
		$sql.= ' fd.fk_code_ventilation,';
4643
		$sql.= ' fd.fk_unit, fd.fk_user_author, fd.fk_user_modif,';
4644
		$sql.= ' fd.situation_percent, fd.fk_prev_id,';
4645
		$sql.= ' fd.multicurrency_subprice,';
4646
		$sql.= ' fd.multicurrency_total_ht,';
4647
		$sql.= ' fd.multicurrency_total_tva,';
4648
		$sql.= ' fd.multicurrency_total_ttc,';
4649
		$sql.= ' p.ref as product_ref, p.label as product_libelle, p.description as product_desc';
4650
		$sql.= ' FROM '.MAIN_DB_PREFIX.'facturedet as fd';
4651
		$sql.= ' LEFT JOIN '.MAIN_DB_PREFIX.'product as p ON fd.fk_product = p.rowid';
4652
		$sql.= ' WHERE fd.rowid = '.$rowid;
4653
4654
		$result = $this->db->query($sql);
4655
		if ($result)
4656
		{
4657
			$objp = $this->db->fetch_object($result);
4658
4659
			$this->rowid				= $objp->rowid;
4660
			$this->id					= $objp->rowid;
4661
			$this->fk_facture			= $objp->fk_facture;
4662
			$this->fk_parent_line		= $objp->fk_parent_line;
4663
			$this->label				= $objp->custom_label;
4664
			$this->desc					= $objp->description;
4665
			$this->qty					= $objp->qty;
4666
			$this->subprice				= $objp->subprice;
4667
			$this->vat_src_code  		= $objp->vat_src_code;
4668
			$this->tva_tx				= $objp->tva_tx;
4669
			$this->localtax1_tx			= $objp->localtax1_tx;
4670
			$this->localtax2_tx			= $objp->localtax2_tx;
4671
			$this->remise_percent		= $objp->remise_percent;
4672
			$this->fk_remise_except		= $objp->fk_remise_except;
4673
			$this->fk_product			= $objp->fk_product;
4674
			$this->product_type			= $objp->product_type;
4675
			$this->date_start			= $this->db->jdate($objp->date_start);
4676
			$this->date_end				= $this->db->jdate($objp->date_end);
4677
			$this->info_bits			= $objp->info_bits;
4678
			$this->tva_npr              = ($objp->info_bits & 1 == 1) ? 1 : 0;
4679
			$this->special_code			= $objp->special_code;
4680
			$this->total_ht				= $objp->total_ht;
4681
			$this->total_tva			= $objp->total_tva;
4682
			$this->total_localtax1		= $objp->total_localtax1;
4683
			$this->total_localtax2		= $objp->total_localtax2;
4684
			$this->total_ttc			= $objp->total_ttc;
4685
			$this->fk_code_ventilation	= $objp->fk_code_ventilation;
4686
			$this->rang					= $objp->rang;
4687
			$this->fk_fournprice		= $objp->fk_fournprice;
4688
			$marginInfos				= getMarginInfos($objp->subprice, $objp->remise_percent, $objp->tva_tx, $objp->localtax1_tx, $objp->localtax2_tx, $this->fk_fournprice, $objp->pa_ht);
4689
			$this->pa_ht				= $marginInfos[0];
4690
			$this->marge_tx				= $marginInfos[1];
4691
			$this->marque_tx			= $marginInfos[2];
4692
4693
			$this->ref					= $objp->product_ref;      // deprecated
4694
			$this->product_ref			= $objp->product_ref;
4695
			$this->libelle				= $objp->product_libelle;  // deprecated
4696
			$this->product_label		= $objp->product_libelle;
4697
			$this->product_desc			= $objp->product_desc;
4698
			$this->fk_unit				= $objp->fk_unit;
4699
			$this->fk_user_modif		= $objp->fk_user_modif;
4700
			$this->fk_user_author		= $objp->fk_user_author;
4701
4702
			$this->situation_percent    = $objp->situation_percent;
4703
			$this->fk_prev_id           = $objp->fk_prev_id;
4704
4705
			$this->multicurrency_subprice = $objp->multicurrency_subprice;
4706
			$this->multicurrency_total_ht = $objp->multicurrency_total_ht;
4707
			$this->multicurrency_total_tva= $objp->multicurrency_total_tva;
4708
			$this->multicurrency_total_ttc= $objp->multicurrency_total_ttc;
4709
4710
			$this->db->free($result);
4711
4712
			return 1;
4713
		}
4714
		else
4715
		{
4716
		    $this->error = $this->db->lasterror();
4717
			return -1;
4718
		}
4719
	}
4720
4721
	/**
4722
	 *	Insert line into database
4723
	 *
4724
	 *	@param      int		$notrigger		                 1 no triggers
4725
	 *  @param      int     $noerrorifdiscountalreadylinked  1=Do not make error if lines is linked to a discount and discount already linked to another
4726
	 *	@return		int						                 <0 if KO, >0 if OK
4727
	 */
4728
    public function insert($notrigger = 0, $noerrorifdiscountalreadylinked = 0)
4729
	{
4730
		global $langs,$user,$conf;
4731
4732
		$error=0;
4733
4734
        $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'.
4735
4736
        dol_syslog(get_class($this)."::insert rang=".$this->rang, LOG_DEBUG);
4737
4738
		// Clean parameters
4739
		$this->desc=trim($this->desc);
4740
		if (empty($this->tva_tx)) $this->tva_tx=0;
4741
		if (empty($this->localtax1_tx)) $this->localtax1_tx=0;
4742
		if (empty($this->localtax2_tx)) $this->localtax2_tx=0;
4743
		if (empty($this->localtax1_type)) $this->localtax1_type=0;
4744
		if (empty($this->localtax2_type)) $this->localtax2_type=0;
4745
		if (empty($this->total_localtax1)) $this->total_localtax1=0;
4746
		if (empty($this->total_localtax2)) $this->total_localtax2=0;
4747
		if (empty($this->rang)) $this->rang=0;
4748
		if (empty($this->remise_percent)) $this->remise_percent=0;
4749
		if (empty($this->info_bits)) $this->info_bits=0;
4750
		if (empty($this->subprice)) $this->subprice=0;
4751
		if (empty($this->special_code)) $this->special_code=0;
4752
		if (empty($this->fk_parent_line)) $this->fk_parent_line=0;
4753
		if (empty($this->fk_prev_id)) $this->fk_prev_id = 0;
4754
		if (! isset($this->situation_percent) || $this->situation_percent > 100 || (string) $this->situation_percent == '') $this->situation_percent = 100;
4755
4756
		if (empty($this->pa_ht)) $this->pa_ht=0;
4757
		if (empty($this->multicurrency_subprice)) $this->multicurrency_subprice=0;
4758
		if (empty($this->multicurrency_total_ht)) $this->multicurrency_total_ht=0;
4759
		if (empty($this->multicurrency_total_tva)) $this->multicurrency_total_tva=0;
4760
		if (empty($this->multicurrency_total_ttc)) $this->multicurrency_total_ttc=0;
4761
4762
		// if buy price not defined, define buyprice as configured in margin admin
4763
		if ($this->pa_ht == 0 && $pa_ht_isemptystring)
4764
		{
4765
			if (($result = $this->defineBuyPrice($this->subprice, $this->remise_percent, $this->fk_product)) < 0)
4766
			{
4767
				return $result;
4768
			}
4769
			else
4770
			{
4771
				$this->pa_ht = $result;
4772
			}
4773
		}
4774
4775
		// Check parameters
4776
		if ($this->product_type < 0)
4777
		{
4778
			$this->error='ErrorProductTypeMustBe0orMore';
4779
			return -1;
4780
		}
4781
		if (! empty($this->fk_product))
4782
		{
4783
			// Check product exists
4784
			$result=Product::isExistingObject('product', $this->fk_product);
4785
			if ($result <= 0)
4786
			{
4787
				$this->error='ErrorProductIdDoesNotExists';
4788
				dol_syslog(get_class($this)."::insert Error ".$this->error, LOG_ERR);
4789
				return -1;
4790
			}
4791
		}
4792
4793
		$this->db->begin();
4794
4795
		// Insertion dans base de la ligne
4796
		$sql = 'INSERT INTO '.MAIN_DB_PREFIX.'facturedet';
4797
		$sql.= ' (fk_facture, fk_parent_line, label, description, qty,';
4798
		$sql.= ' vat_src_code, tva_tx, localtax1_tx, localtax2_tx, localtax1_type, localtax2_type,';
4799
		$sql.= ' fk_product, product_type, remise_percent, subprice, fk_remise_except,';
4800
		$sql.= ' date_start, date_end, fk_code_ventilation, ';
4801
		$sql.= ' rang, special_code, fk_product_fournisseur_price, buy_price_ht,';
4802
		$sql.= ' info_bits, total_ht, total_tva, total_ttc, total_localtax1, total_localtax2,';
4803
		$sql.= ' situation_percent, fk_prev_id,';
4804
		$sql.= ' fk_unit, fk_user_author, fk_user_modif,';
4805
		$sql.= ' fk_multicurrency, multicurrency_code, multicurrency_subprice, multicurrency_total_ht, multicurrency_total_tva, multicurrency_total_ttc';
4806
		$sql.= ')';
4807
		$sql.= " VALUES (".$this->fk_facture.",";
4808
		$sql.= " ".($this->fk_parent_line>0 ? $this->fk_parent_line:"null").",";
4809
		$sql.= " ".(! empty($this->label)?"'".$this->db->escape($this->label)."'":"null").",";
4810
		$sql.= " '".$this->db->escape($this->desc)."',";
4811
		$sql.= " ".price2num($this->qty).",";
4812
        $sql.= " ".(empty($this->vat_src_code)?"''":"'".$this->db->escape($this->vat_src_code)."'").",";
4813
		$sql.= " ".price2num($this->tva_tx).",";
4814
		$sql.= " ".price2num($this->localtax1_tx).",";
4815
		$sql.= " ".price2num($this->localtax2_tx).",";
4816
		$sql.= " '".$this->db->escape($this->localtax1_type)."',";
4817
		$sql.= " '".$this->db->escape($this->localtax2_type)."',";
4818
		$sql.= ' '.(! empty($this->fk_product)?$this->fk_product:"null").',';
4819
		$sql.= " ".((int) $this->product_type).",";
4820
		$sql.= " ".price2num($this->remise_percent).",";
4821
		$sql.= " ".price2num($this->subprice).",";
4822
		$sql.= ' '.(! empty($this->fk_remise_except)?$this->fk_remise_except:"null").',';
4823
		$sql.= " ".(! empty($this->date_start)?"'".$this->db->idate($this->date_start)."'":"null").",";
4824
		$sql.= " ".(! empty($this->date_end)?"'".$this->db->idate($this->date_end)."'":"null").",";
4825
		$sql.= ' '.$this->fk_code_ventilation.',';
4826
		$sql.= ' '.$this->rang.',';
4827
		$sql.= ' '.$this->special_code.',';
4828
		$sql.= ' '.(! empty($this->fk_fournprice)?$this->fk_fournprice:"null").',';
4829
		$sql.= ' '.price2num($this->pa_ht).',';
4830
		$sql.= " '".$this->db->escape($this->info_bits)."',";
4831
		$sql.= " ".price2num($this->total_ht).",";
4832
		$sql.= " ".price2num($this->total_tva).",";
4833
		$sql.= " ".price2num($this->total_ttc).",";
4834
		$sql.= " ".price2num($this->total_localtax1).",";
4835
		$sql.= " ".price2num($this->total_localtax2);
4836
		$sql.= ", " . $this->situation_percent;
4837
		$sql.= ", " . (!empty($this->fk_prev_id)?$this->fk_prev_id:"null");
4838
		$sql.= ", ".(!$this->fk_unit ? 'NULL' : $this->fk_unit);
4839
		$sql.= ", ".$user->id;
4840
		$sql.= ", ".$user->id;
4841
		$sql.= ", ".(int) $this->fk_multicurrency;
4842
		$sql.= ", '".$this->db->escape($this->multicurrency_code)."'";
4843
		$sql.= ", ".price2num($this->multicurrency_subprice);
4844
		$sql.= ", ".price2num($this->multicurrency_total_ht);
4845
		$sql.= ", ".price2num($this->multicurrency_total_tva);
4846
		$sql.= ", ".price2num($this->multicurrency_total_ttc);
4847
		$sql.= ')';
4848
4849
		dol_syslog(get_class($this)."::insert", LOG_DEBUG);
4850
		$resql=$this->db->query($sql);
4851
		if ($resql)
4852
		{
4853
			$this->id=$this->db->last_insert_id(MAIN_DB_PREFIX.'facturedet');
4854
			$this->rowid=$this->id;	// For backward compatibility
4855
4856
            if (empty($conf->global->MAIN_EXTRAFIELDS_DISABLED)) // For avoid conflicts if trigger used
4857
            {
4858
            	$result=$this->insertExtraFields();
4859
            	if ($result < 0)
4860
            	{
4861
            		$error++;
4862
            	}
4863
            }
4864
4865
			// Si fk_remise_except defini, on lie la remise a la facture
4866
			// ce qui la flague comme "consommee".
4867
			if ($this->fk_remise_except)
4868
			{
4869
				$discount=new DiscountAbsolute($this->db);
4870
				$result=$discount->fetch($this->fk_remise_except);
4871
				if ($result >= 0)
4872
				{
4873
					// Check if discount was found
4874
					if ($result > 0)
4875
					{
4876
					    // Check if discount not already affected to another invoice
4877
						if ($discount->fk_facture_line > 0)
4878
						{
4879
						    if (empty($noerrorifdiscountalreadylinked))
4880
						    {
4881
    							$this->error=$langs->trans("ErrorDiscountAlreadyUsed", $discount->id);
4882
    							dol_syslog(get_class($this)."::insert Error ".$this->error, LOG_ERR);
4883
    							$this->db->rollback();
4884
    							return -3;
4885
						    }
4886
						}
4887
						else
4888
						{
4889
							$result=$discount->link_to_invoice($this->rowid, 0);
4890
							if ($result < 0)
4891
							{
4892
								$this->error=$discount->error;
4893
								dol_syslog(get_class($this)."::insert Error ".$this->error, LOG_ERR);
4894
								$this->db->rollback();
4895
								return -3;
4896
							}
4897
						}
4898
					}
4899
					else
4900
					{
4901
						$this->error=$langs->trans("ErrorADiscountThatHasBeenRemovedIsIncluded");
4902
						dol_syslog(get_class($this)."::insert Error ".$this->error, LOG_ERR);
4903
						$this->db->rollback();
4904
						return -3;
4905
					}
4906
				}
4907
				else
4908
				{
4909
					$this->error=$discount->error;
4910
					dol_syslog(get_class($this)."::insert Error ".$this->error, LOG_ERR);
4911
					$this->db->rollback();
4912
					return -3;
4913
				}
4914
			}
4915
4916
			if (! $notrigger)
4917
			{
4918
                // Call trigger
4919
                $result=$this->call_trigger('LINEBILL_INSERT', $user);
4920
                if ($result < 0)
4921
                {
4922
					$this->db->rollback();
4923
					return -2;
4924
				}
4925
                // End call triggers
4926
			}
4927
4928
			$this->db->commit();
4929
			return $this->id;
4930
		}
4931
		else
4932
		{
4933
			$this->error=$this->db->lasterror();
4934
			$this->db->rollback();
4935
			return -2;
4936
		}
4937
	}
4938
4939
	/**
4940
	 *	Update line into database
4941
	 *
4942
	 *	@param		User	$user		User object
4943
	 *	@param		int		$notrigger	Disable triggers
4944
	 *	@return		int					<0 if KO, >0 if OK
4945
	 */
4946
    public function update($user = '', $notrigger = 0)
4947
	{
4948
		global $user,$conf;
4949
4950
		$error=0;
4951
4952
		$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'.
4953
4954
		// Clean parameters
4955
		$this->desc=trim($this->desc);
4956
		if (empty($this->tva_tx)) $this->tva_tx=0;
4957
		if (empty($this->localtax1_tx)) $this->localtax1_tx=0;
4958
		if (empty($this->localtax2_tx)) $this->localtax2_tx=0;
4959
		if (empty($this->localtax1_type)) $this->localtax1_type=0;
4960
		if (empty($this->localtax2_type)) $this->localtax2_type=0;
4961
		if (empty($this->total_localtax1)) $this->total_localtax1=0;
4962
		if (empty($this->total_localtax2)) $this->total_localtax2=0;
4963
		if (empty($this->remise_percent)) $this->remise_percent=0;
4964
		if (empty($this->info_bits)) $this->info_bits=0;
4965
		if (empty($this->special_code)) $this->special_code=0;
4966
		if (empty($this->product_type)) $this->product_type=0;
4967
		if (empty($this->fk_parent_line)) $this->fk_parent_line=0;
4968
		if (! isset($this->situation_percent) || $this->situation_percent > 100 || (string) $this->situation_percent == '') $this->situation_percent = 100;
4969
		if (empty($this->pa_ht)) $this->pa_ht=0;
4970
4971
		if (empty($this->multicurrency_subprice)) $this->multicurrency_subprice=0;
4972
		if (empty($this->multicurrency_total_ht)) $this->multicurrency_total_ht=0;
4973
		if (empty($this->multicurrency_total_tva)) $this->multicurrency_total_tva=0;
4974
		if (empty($this->multicurrency_total_ttc)) $this->multicurrency_total_ttc=0;
4975
4976
		// Check parameters
4977
		if ($this->product_type < 0) return -1;
4978
4979
		// if buy price not defined, define buyprice as configured in margin admin
4980
		if ($this->pa_ht == 0 && $pa_ht_isemptystring)
4981
		{
4982
			if (($result = $this->defineBuyPrice($this->subprice, $this->remise_percent, $this->fk_product)) < 0)
4983
			{
4984
				return $result;
4985
			}
4986
			else
4987
			{
4988
				$this->pa_ht = $result;
4989
			}
4990
		}
4991
4992
		$this->db->begin();
4993
4994
        // Mise a jour ligne en base
4995
        $sql = "UPDATE ".MAIN_DB_PREFIX."facturedet SET";
4996
        $sql.= " description='".$this->db->escape($this->desc)."'";
4997
        $sql.= ", label=".(! empty($this->label)?"'".$this->db->escape($this->label)."'":"null");
4998
        $sql.= ", subprice=".price2num($this->subprice)."";
4999
        $sql.= ", remise_percent=".price2num($this->remise_percent)."";
5000
        if ($this->fk_remise_except) $sql.= ", fk_remise_except=".$this->fk_remise_except;
5001
        else $sql.= ", fk_remise_except=null";
5002
		$sql.= ", vat_src_code = '".(empty($this->vat_src_code)?'':$this->db->escape($this->vat_src_code))."'";
5003
        $sql.= ", tva_tx=".price2num($this->tva_tx)."";
5004
        $sql.= ", localtax1_tx=".price2num($this->localtax1_tx)."";
5005
        $sql.= ", localtax2_tx=".price2num($this->localtax2_tx)."";
5006
		$sql.= ", localtax1_type='".$this->db->escape($this->localtax1_type)."'";
5007
		$sql.= ", localtax2_type='".$this->db->escape($this->localtax2_type)."'";
5008
        $sql.= ", qty=".price2num($this->qty);
5009
        $sql.= ", date_start=".(! empty($this->date_start)?"'".$this->db->idate($this->date_start)."'":"null");
5010
        $sql.= ", date_end=".(! empty($this->date_end)?"'".$this->db->idate($this->date_end)."'":"null");
5011
        $sql.= ", product_type=".$this->product_type;
5012
        $sql.= ", info_bits='".$this->db->escape($this->info_bits)."'";
5013
        $sql.= ", special_code='".$this->db->escape($this->special_code)."'";
5014
        if (empty($this->skip_update_total))
5015
        {
5016
        	$sql.= ", total_ht=".price2num($this->total_ht);
5017
        	$sql.= ", total_tva=".price2num($this->total_tva);
5018
        	$sql.= ", total_ttc=".price2num($this->total_ttc);
5019
        	$sql.= ", total_localtax1=".price2num($this->total_localtax1);
5020
        	$sql.= ", total_localtax2=".price2num($this->total_localtax2);
5021
        }
5022
		$sql.= ", fk_product_fournisseur_price=".(! empty($this->fk_fournprice)?"'".$this->db->escape($this->fk_fournprice)."'":"null");
5023
		$sql.= ", buy_price_ht='".price2num($this->pa_ht)."'";
5024
		$sql.= ", fk_parent_line=".($this->fk_parent_line>0?$this->fk_parent_line:"null");
5025
		if (! empty($this->rang)) $sql.= ", rang=".$this->rang;
5026
		$sql.= ", situation_percent=" . $this->situation_percent;
5027
		$sql.= ", fk_unit=".(!$this->fk_unit ? 'NULL' : $this->fk_unit);
5028
		$sql.= ", fk_user_modif =".$user->id;
5029
5030
		// Multicurrency
5031
		$sql.= ", multicurrency_subprice=".price2num($this->multicurrency_subprice)."";
5032
        $sql.= ", multicurrency_total_ht=".price2num($this->multicurrency_total_ht)."";
5033
        $sql.= ", multicurrency_total_tva=".price2num($this->multicurrency_total_tva)."";
5034
        $sql.= ", multicurrency_total_ttc=".price2num($this->multicurrency_total_ttc)."";
5035
5036
		$sql.= " WHERE rowid = ".$this->rowid;
5037
5038
		dol_syslog(get_class($this)."::update", LOG_DEBUG);
5039
		$resql=$this->db->query($sql);
5040
		if ($resql)
5041
		{
5042
        	if (empty($conf->global->MAIN_EXTRAFIELDS_DISABLED)) // For avoid conflicts if trigger used
5043
        	{
5044
        		$this->id=$this->rowid;
5045
        		$result=$this->insertExtraFields();
5046
        		if ($result < 0)
5047
        		{
5048
        			$error++;
5049
        		}
5050
        	}
5051
5052
			if (! $error && ! $notrigger)
5053
			{
5054
                // Call trigger
5055
                $result=$this->call_trigger('LINEBILL_UPDATE', $user);
5056
                if ($result < 0)
5057
 				{
5058
					$this->db->rollback();
5059
					return -2;
5060
				}
5061
                // End call triggers
5062
			}
5063
			$this->db->commit();
5064
			return 1;
5065
		}
5066
		else
5067
		{
5068
			$this->error=$this->db->error();
5069
			$this->db->rollback();
5070
			return -2;
5071
		}
5072
	}
5073
5074
	/**
5075
	 * 	Delete line in database
5076
	 *  TODO Add param User $user and notrigger (see skeleton)
5077
     *
5078
	 *	@return	    int		           <0 if KO, >0 if OK
5079
	 */
5080
    public function delete()
5081
	{
5082
		global $user;
5083
5084
		$this->db->begin();
5085
5086
		// Call trigger
5087
		$result=$this->call_trigger('LINEBILL_DELETE', $user);
5088
		if ($result < 0)
5089
		{
5090
			$this->db->rollback();
5091
			return -1;
5092
		}
5093
		// End call triggers
5094
5095
5096
		$sql = "DELETE FROM ".MAIN_DB_PREFIX."facturedet WHERE rowid = ".$this->rowid;
1 ignored issue
show
Deprecated Code introduced by
The property CommonObjectLine::$rowid has been deprecated: Try to use id property as possible (even if field into database is still rowid) ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

5096
		$sql = "DELETE FROM ".MAIN_DB_PREFIX."facturedet WHERE rowid = "./** @scrutinizer ignore-deprecated */ $this->rowid;

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
5097
		dol_syslog(get_class($this)."::delete", LOG_DEBUG);
5098
		if ($this->db->query($sql) )
5099
		{
5100
			$this->db->commit();
5101
			return 1;
5102
		}
5103
		else
5104
		{
5105
			$this->error=$this->db->error()." sql=".$sql;
5106
			$this->db->rollback();
5107
			return -1;
5108
		}
5109
	}
5110
5111
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5112
	/**
5113
     *	Update DB line fields total_xxx
5114
	 *	Used by migration
5115
	 *
5116
	 *	@return		int		<0 if KO, >0 if OK
5117
	 */
5118
    public function update_total()
5119
	{
5120
        // phpcs:enable
5121
		$this->db->begin();
5122
		dol_syslog(get_class($this)."::update_total", LOG_DEBUG);
5123
5124
		// Clean parameters
5125
		if (empty($this->total_localtax1)) $this->total_localtax1=0;
5126
		if (empty($this->total_localtax2)) $this->total_localtax2=0;
5127
5128
		// Mise a jour ligne en base
5129
		$sql = "UPDATE ".MAIN_DB_PREFIX."facturedet SET";
5130
		$sql.= " total_ht=".price2num($this->total_ht)."";
5131
		$sql.= ",total_tva=".price2num($this->total_tva)."";
5132
		$sql.= ",total_localtax1=".price2num($this->total_localtax1)."";
5133
		$sql.= ",total_localtax2=".price2num($this->total_localtax2)."";
5134
		$sql.= ",total_ttc=".price2num($this->total_ttc)."";
5135
		$sql.= " WHERE rowid = ".$this->rowid;
1 ignored issue
show
Deprecated Code introduced by
The property CommonObjectLine::$rowid has been deprecated: Try to use id property as possible (even if field into database is still rowid) ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

5135
		$sql.= " WHERE rowid = "./** @scrutinizer ignore-deprecated */ $this->rowid;

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
5136
5137
		dol_syslog(get_class($this)."::update_total", LOG_DEBUG);
5138
5139
		$resql=$this->db->query($sql);
5140
		if ($resql)
5141
		{
5142
			$this->db->commit();
5143
			return 1;
5144
		}
5145
		else
5146
		{
5147
			$this->error=$this->db->error();
5148
			$this->db->rollback();
5149
			return -2;
5150
		}
5151
	}
5152
5153
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5154
	/**
5155
	 * Returns situation_percent of the previous line.
5156
	 * Warning: If invoice is a replacement invoice, this->fk_prev_id is id of the replaced line.
5157
	 *
5158
	 * @param  int     $invoiceid      Invoice id
5159
	 * @param  bool    $include_credit_note		Include credit note or not
5160
	 * @return int                     >= 0
5161
	 */
5162
    public function get_prev_progress($invoiceid, $include_credit_note = true)
5163
	{
5164
        // phpcs:enable
5165
		if (is_null($this->fk_prev_id) || empty($this->fk_prev_id) || $this->fk_prev_id == "") {
5166
			return 0;
5167
		} else {
5168
		    // If invoice is not a situation invoice, this->fk_prev_id is used for something else
5169
            $tmpinvoice=new Facture($this->db);
5170
            $tmpinvoice->fetch($invoiceid);
5171
            if ($tmpinvoice->type != Facture::TYPE_SITUATION) return 0;
5172
5173
			$sql = 'SELECT situation_percent FROM ' . MAIN_DB_PREFIX . 'facturedet WHERE rowid=' . $this->fk_prev_id;
5174
			$resql = $this->db->query($sql);
5175
			if ($resql && $resql->num_rows > 0) {
5176
				$res = $this->db->fetch_array($resql);
5177
5178
				$returnPercent = floatval($res['situation_percent']);
5179
5180
				if($include_credit_note) {
5181
5182
				    $sql = 'SELECT fd.situation_percent FROM ' . MAIN_DB_PREFIX . 'facturedet fd';
5183
				    $sql.= ' JOIN ' . MAIN_DB_PREFIX . 'facture f ON (f.rowid = fd.fk_facture) ';
5184
				    $sql.= ' WHERE fd.fk_prev_id =' . $this->fk_prev_id;
5185
				    $sql.= ' AND f.situation_cycle_ref = '.$tmpinvoice->situation_cycle_ref; // Prevent cycle outed
5186
				    $sql.= ' AND f.type = '.Facture::TYPE_CREDIT_NOTE;
5187
5188
				    $res = $this->db->query($sql);
5189
				    if($res) {
5190
				        while($obj = $this->db->fetch_object($res)) {
5191
				            $returnPercent = $returnPercent + floatval($obj->situation_percent);
5192
				        }
5193
				    }
5194
				}
5195
5196
				return $returnPercent;
5197
			} else {
5198
				$this->error = $this->db->error();
5199
				dol_syslog(get_class($this) . "::select Error " . $this->error, LOG_ERR);
5200
				$this->db->rollback();
5201
				return -1;
5202
			}
5203
		}
5204
	}
5205
}
5206