Passed
Branch develop (5cbde9)
by
unknown
26:38
created

Facture::setDraft()   C

Complexity

Conditions 14

Size

Total Lines 85
Code Lines 46

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 14
eloc 46
c 0
b 0
f 0
nop 2
dl 0
loc 85
rs 6.2666

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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
     * Standard invoice
211
     */
212
    const TYPE_STANDARD = 0;
213
214
    /**
215
     * Replacement invoice
216
     */
217
    const TYPE_REPLACEMENT = 1;
218
219
    /**
220
     * Credit note invoice
221
     */
222
    const TYPE_CREDIT_NOTE = 2;
223
224
    /**
225
     * Deposit invoice
226
     */
227
    const TYPE_DEPOSIT = 3;
228
229
    /**
230
     * Proforma invoice (should not be used. a proforma is an order)
231
     */
232
    const TYPE_PROFORMA = 4;
233
234
	/**
235
	 * Situation invoice
236
	 */
237
	const TYPE_SITUATION = 5;
238
239
	/**
240
	 * Draft status
241
	 */
242
	const STATUS_DRAFT = 0;
243
244
	/**
245
	 * Validated (need to be paid)
246
	 */
247
	const STATUS_VALIDATED = 1;
248
249
	/**
250
	 * Classified paid.
251
	 * If paid partially, $this->close_code can be:
252
	 * - CLOSECODE_DISCOUNTVAT
253
	 * - CLOSECODE_BADDEBT
254
	 * If paid completely, this->close_code will be null
255
	 */
256
	const STATUS_CLOSED = 2;
257
258
	/**
259
	 * Classified abandoned and no payment done.
260
	 * $this->close_code can be:
261
	 * - CLOSECODE_BADDEBT
262
	 * - CLOSECODE_ABANDONED
263
	 * - CLOSECODE_REPLACED
264
	 */
265
	const STATUS_ABANDONED = 3;
266
267
	const CLOSECODE_DISCOUNTVAT = 'discount_vat';	// Abandonned remain - escompte
268
	const CLOSECODE_BADDEBT = 'badcustomer';		// Abandonned - bad
269
	const CLOSECODE_ABANDONED = 'abandon';			// Abandonned - other
270
	const CLOSECODE_REPLACED = 'replaced';			// Closed after doing a replacement invoice
271
272
	/**
273
	 * 	Constructor
274
	 *
275
	 * 	@param	DoliDB		$db			Database handler
276
	 */
277
	public function __construct($db)
278
	{
279
		$this->db = $db;
280
	}
281
282
	/**
283
	 *	Create invoice in database.
284
	 *  Note: this->ref can be set or empty. If empty, we will use "(PROV999)"
285
	 *  Note: this->fac_rec must be set to create invoice from a recurring invoice
286
	 *
287
	 *	@param	User	$user      		Object user that create
288
	 *	@param  int		$notrigger		1=Does not execute triggers, 0 otherwise
289
	 * 	@param	int		$forceduedate	If set, do not recalculate due date from payment condition but force it with value
290
	 *	@return	int						<0 if KO, >0 if OK
291
	 */
292
    public function create(User $user, $notrigger = 0, $forceduedate = 0)
293
	{
294
		global $langs,$conf,$mysoc,$hookmanager;
295
		$error=0;
296
297
		// Clean parameters
298
		if (empty($this->type)) $this->type = self::TYPE_STANDARD;
299
		$this->ref_client=trim($this->ref_client);
300
		$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

300
		$this->note=(isset(/** @scrutinizer ignore-deprecated */ $this->note) ? trim($this->note) : trim($this->note_private)); // deprecated
Loading history...
301
		$this->note_private=(isset($this->note_private) ? trim($this->note_private) : trim($this->note_private));
302
		$this->note_public=trim($this->note_public);
303
		if (! $this->cond_reglement_id) $this->cond_reglement_id = 0;
304
		if (! $this->mode_reglement_id) $this->mode_reglement_id = 0;
305
		$this->brouillon = 1;
306
        if (empty($this->entity)) $this->entity = $conf->entity;
307
308
		// Multicurrency (test on $this->multicurrency_tx because we should take the default rate only if not using origin rate)
309
		if (!empty($this->multicurrency_code) && empty($this->multicurrency_tx)) list($this->fk_multicurrency,$this->multicurrency_tx) = MultiCurrency::getIdAndTxFromCode($this->db, $this->multicurrency_code);
310
		else $this->fk_multicurrency = MultiCurrency::getIdFromCode($this->db, $this->multicurrency_code);
311
		if (empty($this->fk_multicurrency))
312
		{
313
			$this->multicurrency_code = $conf->currency;
314
			$this->fk_multicurrency = 0;
315
			$this->multicurrency_tx = 1;
316
		}
317
318
		dol_syslog(get_class($this)."::create user=".$user->id." date=".$this->date);
319
320
		// Check parameters
321
		if (empty($this->date))
322
		{
323
			$this->error="Try to create an invoice with an empty parameter (date)";
324
			dol_syslog(get_class($this)."::create ".$this->error, LOG_ERR);
325
			return -3;
326
		}
327
		$soc = new Societe($this->db);
328
		$result=$soc->fetch($this->socid);
329
		if ($result < 0)
330
		{
331
			$this->error="Failed to fetch company: ".$soc->error;
332
			dol_syslog(get_class($this)."::create ".$this->error, LOG_ERR);
333
			return -2;
334
		}
335
336
		$now=dol_now();
337
338
		$this->db->begin();
339
340
		$originaldatewhen=null;
341
		$nextdatewhen=null;
342
		$previousdaynextdatewhen=null;
343
344
		// Create invoice from a template invoice
345
		if ($this->fac_rec > 0)
346
		{
347
		    $this->fk_fac_rec_source = $this->fac_rec;
348
349
			require_once DOL_DOCUMENT_ROOT.'/compta/facture/class/facture-rec.class.php';
350
			$_facrec = new FactureRec($this->db);
351
			$result=$_facrec->fetch($this->fac_rec);
352
			$result=$_facrec->fetchObjectLinked(null, '', null, '', 'OR', 1, 'sourcetype', 0);       // This load $_facrec->linkedObjectsIds
353
354
			// Define some dates
355
			$originaldatewhen = $_facrec->date_when;
356
			$nextdatewhen=dol_time_plus_duree($originaldatewhen, $_facrec->frequency, $_facrec->unit_frequency);
357
			$previousdaynextdatewhen=dol_time_plus_duree($nextdatewhen, -1, 'd');
358
359
			$this->socid 		     = $_facrec->socid;  // Invoice created on same thirdparty than template
360
			$this->entity            = $_facrec->entity; // Invoice created in same entity than template
361
362
			// 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
363
			$this->fk_project        = GETPOST('projectid', 'int') > 0 ? ((int) GETPOST('projectid', 'int')) : $_facrec->fk_project;
364
			$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...
365
			$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...
366
			$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...
367
			$this->cond_reglement_id = GETPOST('cond_reglement_id', 'int') > 0 ? ((int) GETPOST('cond_reglement_id', 'int')) : $_facrec->cond_reglement_id;
368
			$this->mode_reglement_id = GETPOST('mode_reglement_id', 'int') > 0 ? ((int) GETPOST('mode_reglement_id', 'int')) : $_facrec->mode_reglement_id;
369
			$this->fk_account        = GETPOST('fk_account') > 0 ? ((int) GETPOST('fk_account')) : $_facrec->fk_account;
370
371
			// Set here to have this defined for substitution into notes, should be recalculated after adding lines to get same result
372
			$this->total_ht          = $_facrec->total_ht;
373
			$this->total_ttc         = $_facrec->total_ttc;
374
375
			// Fields always coming from template
376
			$this->remise_absolue    = $_facrec->remise_absolue;
377
			$this->remise_percent    = $_facrec->remise_percent;
378
			$this->fk_incoterms		 = $_facrec->fk_incoterms;
379
			$this->location_incoterms= $_facrec->location_incoterms;
380
381
			// Clean parameters
382
			if (! $this->type) $this->type = self::TYPE_STANDARD;
383
			$this->ref_client=trim($this->ref_client);
384
			$this->note_public=trim($this->note_public);
385
			$this->note_private=trim($this->note_private);
386
		    $this->note_private=dol_concatdesc($this->note_private, $langs->trans("GeneratedFromRecurringInvoice", $_facrec->ref));
387
388
		    $this->array_options=$_facrec->array_options;
389
390
			//if (! $this->remise) $this->remise = 0;
391
			if (! $this->mode_reglement_id) $this->mode_reglement_id = 0;
392
			$this->brouillon = 1;
393
394
			$this->linked_objects = $_facrec->linkedObjectsIds;
395
			// We do not add link to template invoice or next invoice will be linked to all generated invoices
396
			//$this->linked_objects['facturerec'][0] = $this->fac_rec;
397
398
			$forceduedate = $this->calculate_date_lim_reglement();
399
400
			// For recurring invoices, update date and number of last generation of recurring template invoice, before inserting new invoice
401
			if ($_facrec->frequency > 0)
402
			{
403
			    dol_syslog("This is a recurring invoice so we set date_last_gen and next date_when");
404
			    if (empty($_facrec->date_when)) $_facrec->date_when = $now;
405
                $next_date = $_facrec->getNextDate();   // Calculate next date
406
                $result = $_facrec->setValueFrom('date_last_gen', $now, '', null, 'date', '', $user, '');
407
                //$_facrec->setValueFrom('nb_gen_done', $_facrec->nb_gen_done + 1);		// Not required, +1 already included into setNextDate when second param is 1.
408
                $result = $_facrec->setNextDate($next_date, 1);
409
			}
410
411
			// Define lang of customer
412
			$outputlangs = $langs;
413
			$newlang='';
414
415
			if ($conf->global->MAIN_MULTILANGS && empty($newlang) && isset($this->thirdparty->default_lang)) $newlang=$this->thirdparty->default_lang;  // for proposal, order, invoice, ...
416
			if ($conf->global->MAIN_MULTILANGS && empty($newlang) && isset($this->default_lang)) $newlang=$this->default_lang;                  // for thirdparty
417
			if (! empty($newlang))
418
			{
419
			    $outputlangs = new Translate("", $conf);
420
			    $outputlangs->setDefaultLang($newlang);
421
			}
422
423
			// Array of possible substitutions (See also file mailing-send.php that should manage same substitutions)
424
			$substitutionarray=getCommonSubstitutionArray($outputlangs, 0, null, $this);
425
			$substitutionarray['__INVOICE_PREVIOUS_MONTH__'] = dol_print_date(dol_time_plus_duree($this->date, -1, 'm'), '%m');
426
			$substitutionarray['__INVOICE_MONTH__'] = dol_print_date($this->date, '%m');
427
			$substitutionarray['__INVOICE_NEXT_MONTH__'] = dol_print_date(dol_time_plus_duree($this->date, 1, 'm'), '%m');
428
			$substitutionarray['__INVOICE_PREVIOUS_MONTH_TEXT__'] = dol_print_date(dol_time_plus_duree($this->date, -1, 'm'), '%B');
429
			$substitutionarray['__INVOICE_MONTH_TEXT__'] = dol_print_date($this->date, '%B');
430
			$substitutionarray['__INVOICE_NEXT_MONTH_TEXT__'] = dol_print_date(dol_time_plus_duree($this->date, 1, 'm'), '%B');
431
			$substitutionarray['__INVOICE_PREVIOUS_YEAR__'] = dol_print_date(dol_time_plus_duree($this->date, -1, 'y'), '%Y');
432
			$substitutionarray['__INVOICE_YEAR__'] = dol_print_date($this->date, '%Y');
433
			$substitutionarray['__INVOICE_NEXT_YEAR__'] = dol_print_date(dol_time_plus_duree($this->date, 1, 'y'), '%Y');
434
			// Only for tempalte invoice
435
			$substitutionarray['__INVOICE_DATE_NEXT_INVOICE_BEFORE_GEN__'] = dol_print_date($originaldatewhen, 'dayhour');
436
			$substitutionarray['__INVOICE_DATE_NEXT_INVOICE_AFTER_GEN__'] = dol_print_date($nextdatewhen, 'dayhour');
437
			$substitutionarray['__INVOICE_PREVIOUS_DATE_NEXT_INVOICE_AFTER_GEN__'] = dol_print_date($previousdaynextdatewhen, 'dayhour');
438
439
			//var_dump($substitutionarray);exit;
440
441
			complete_substitutions_array($substitutionarray, $outputlangs);
442
443
			$this->note_public=make_substitutions($this->note_public, $substitutionarray);
444
			$this->note_private=make_substitutions($this->note_private, $substitutionarray);
445
		}
446
447
		// Define due date if not already defined
448
		$datelim=(empty($forceduedate)?$this->calculate_date_lim_reglement():$forceduedate);
449
450
		// Insert into database
451
		$socid  = $this->socid;
452
453
		$sql = "INSERT INTO ".MAIN_DB_PREFIX."facture (";
454
		$sql.= " ref";
455
		$sql.= ", entity";
456
		$sql.= ", ref_ext";
457
		$sql.= ", type";
458
		$sql.= ", fk_soc";
459
		$sql.= ", datec";
460
		$sql.= ", remise_absolue";
461
		$sql.= ", remise_percent";
462
		$sql.= ", datef";
463
		$sql.= ", date_pointoftax";
464
		$sql.= ", note_private";
465
		$sql.= ", note_public";
466
		$sql.= ", ref_client, ref_int";
467
        $sql.= ", fk_account";
468
		$sql.= ", module_source, pos_source, fk_fac_rec_source, fk_facture_source, fk_user_author, fk_projet";
469
		$sql.= ", fk_cond_reglement, fk_mode_reglement, date_lim_reglement, model_pdf";
470
		$sql.= ", situation_cycle_ref, situation_counter, situation_final";
471
		$sql.= ", fk_incoterms, location_incoterms";
472
        $sql.= ", fk_multicurrency";
473
        $sql.= ", multicurrency_code";
474
        $sql.= ", multicurrency_tx";
475
		$sql.= ")";
476
		$sql.= " VALUES (";
477
		$sql.= "'(PROV)'";
478
		$sql.= ", ".$this->entity;
479
		$sql.= ", ".($this->ref_ext?"'".$this->db->escape($this->ref_ext)."'":"null");
480
		$sql.= ", '".$this->db->escape($this->type)."'";
481
		$sql.= ", '".$socid."'";
482
		$sql.= ", '".$this->db->idate($now)."'";
483
		$sql.= ", ".($this->remise_absolue>0?$this->remise_absolue:'NULL');
484
		$sql.= ", ".($this->remise_percent>0?$this->remise_percent:'NULL');
485
		$sql.= ", '".$this->db->idate($this->date)."'";
486
		$sql.= ", ".(strval($this->date_pointoftax)!='' ? "'".$this->db->idate($this->date_pointoftax)."'" : 'null');
487
		$sql.= ", ".($this->note_private?"'".$this->db->escape($this->note_private)."'":"null");
488
		$sql.= ", ".($this->note_public?"'".$this->db->escape($this->note_public)."'":"null");
489
		$sql.= ", ".($this->ref_client?"'".$this->db->escape($this->ref_client)."'":"null");
490
		$sql.= ", ".($this->ref_int?"'".$this->db->escape($this->ref_int)."'":"null");
491
		$sql.= ", ".($this->fk_account>0?$this->fk_account:'NULL');
492
		$sql.= ", ".($this->module_source ? "'".$this->db->escape($this->module_source)."'" : "null");
493
		$sql.= ", ".($this->pos_source != '' ? "'".$this->db->escape($this->pos_source)."'" : "null");
494
		$sql.= ", ".($this->fk_fac_rec_source?"'".$this->db->escape($this->fk_fac_rec_source)."'":"null");
495
		$sql.= ", ".($this->fk_facture_source?"'".$this->db->escape($this->fk_facture_source)."'":"null");
496
		$sql.= ", ".($user->id > 0 ? "'".$user->id."'":"null");
497
		$sql.= ", ".($this->fk_project?$this->fk_project:"null");
498
		$sql.= ", ".$this->cond_reglement_id;
499
		$sql.= ", ".$this->mode_reglement_id;
500
		$sql.= ", '".$this->db->idate($datelim)."', '".$this->db->escape($this->modelpdf)."'";
501
		$sql.= ", ".($this->situation_cycle_ref?"'".$this->db->escape($this->situation_cycle_ref)."'":"null");
502
		$sql.= ", ".($this->situation_counter?"'".$this->db->escape($this->situation_counter)."'":"null");
503
		$sql.= ", ".($this->situation_final?$this->situation_final:0);
504
		$sql.= ", ".(int) $this->fk_incoterms;
505
        $sql.= ", '".$this->db->escape($this->location_incoterms)."'";
506
		$sql.= ", ".(int) $this->fk_multicurrency;
507
		$sql.= ", '".$this->db->escape($this->multicurrency_code)."'";
508
		$sql.= ", ".(double) $this->multicurrency_tx;
509
		$sql.=")";
510
511
		$resql=$this->db->query($sql);
512
		if ($resql)
513
		{
514
			$this->id = $this->db->last_insert_id(MAIN_DB_PREFIX.'facture');
515
516
			// Update ref with new one
517
			$this->ref='(PROV'.$this->id.')';
518
			$sql = 'UPDATE '.MAIN_DB_PREFIX."facture SET ref='".$this->db->escape($this->ref)."' WHERE rowid=".$this->id;
519
520
			$resql=$this->db->query($sql);
521
			if (! $resql) $error++;
522
523
			if (! empty($this->linkedObjectsIds) && empty($this->linked_objects))	// To use new linkedObjectsIds instead of old linked_objects
524
			{
525
				$this->linked_objects = $this->linkedObjectsIds;	// TODO Replace linked_objects with linkedObjectsIds
526
			}
527
528
			// Add object linked
529
			if (! $error && $this->id && is_array($this->linked_objects) && ! empty($this->linked_objects))
530
			{
531
				foreach($this->linked_objects as $origin => $tmp_origin_id)
532
				{
533
				    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, ...))
534
				    {
535
				        foreach($tmp_origin_id as $origin_id)
536
				        {
537
				            $ret = $this->add_object_linked($origin, $origin_id);
538
				            if (! $ret)
539
				            {
540
				                $this->error=$this->db->lasterror();
541
				                $error++;
542
				            }
543
				        }
544
				    }
545
				    else                                // Old behaviour, if linked_object has only one link per type, so is something like array('contract'=>id1))
546
				    {
547
				        $origin_id = $tmp_origin_id;
548
    					$ret = $this->add_object_linked($origin, $origin_id);
549
    					if (! $ret)
550
    					{
551
    						$this->error=$this->db->lasterror();
552
    						$error++;
553
    					}
554
				    }
555
				}
556
			}
557
558
			// Propagate contacts
559
			if (! $error && $this->id && ! empty($conf->global->MAIN_PROPAGATE_CONTACTS_FROM_ORIGIN) && ! empty($this->origin) && ! empty($this->origin_id))   // Get contact from origin object
560
			{
561
				$originforcontact = $this->origin;
562
				$originidforcontact = $this->origin_id;
563
				if ($originforcontact == 'shipping')     // shipment and order share the same contacts. If creating from shipment we take data of order
564
				{
565
				    require_once DOL_DOCUMENT_ROOT . '/expedition/class/expedition.class.php';
566
				    $exp = new Expedition($this->db);
567
				    $exp->fetch($this->origin_id);
568
				    $exp->fetchObjectLinked(null, '', null, '', 'OR', 1, 'sourcetype', 0);
569
				    if (count($exp->linkedObjectsIds['commande']) > 0)
570
				    {
571
				        foreach ($exp->linkedObjectsIds['commande'] as $key => $value)
572
				        {
573
				            $originforcontact = 'commande';
574
				            if (is_object($value)) $originidforcontact = $value->id;
575
				            else $originidforcontact = $value;
576
				            break; // We take first one
577
				        }
578
				    }
579
				}
580
581
				$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";
582
				$sqlcontact.= " WHERE element_id = ".$originidforcontact." AND ec.fk_c_type_contact = ctc.rowid AND ctc.element = '".$originforcontact."'";
583
584
				$resqlcontact = $this->db->query($sqlcontact);
585
				if ($resqlcontact)
586
				{
587
				    while($objcontact = $this->db->fetch_object($resqlcontact))
588
				    {
589
				        //print $objcontact->code.'-'.$objcontact->source.'-'.$objcontact->fk_socpeople."\n";
590
				        $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
591
				    }
592
				}
593
				else dol_print_error($resqlcontact);
594
			}
595
596
			/*
597
			 *  Insert lines of invoices, if not from template invoice, into database
598
			 */
599
			if (! $error && empty($this->fac_rec) && count($this->lines) && is_object($this->lines[0]))	// If this->lines is array of InvoiceLines (preferred mode)
600
			{
601
				$fk_parent_line = 0;
602
603
				dol_syslog("There is ".count($this->lines)." lines that are invoice lines objects");
604
				foreach ($this->lines as $i => $val)
605
				{
606
					$newinvoiceline=$this->lines[$i];
607
					$newinvoiceline->fk_facture=$this->id;
608
609
					$newinvoiceline->origin = $this->lines[$i]->element;
610
					$newinvoiceline->origin_id = $this->lines[$i]->id;
611
612
					// Auto set date of service ?
613
					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...
614
					{
615
						$newinvoiceline->date_start = $originaldatewhen;
616
					}
617
					if ($this->lines[$i]->date_end_fill == 1 && $previousdaynextdatewhen)	// $previousdaynextdatewhen is defined when generating from recurring invoice only
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...
Bug introduced by
The property date_end_fill does not exist on FactureLigne. Did you mean date_end?
Loading history...
618
					{
619
						$newinvoiceline->date_end = $previousdaynextdatewhen;
620
					}
621
622
					if ($result >= 0)
623
					{
624
						// Reset fk_parent_line for no child products and special product
625
						if (($newinvoiceline->product_type != 9 && empty($newinvoiceline->fk_parent_line)) || $newinvoiceline->product_type == 9) {
626
							$fk_parent_line = 0;
627
						}
628
629
						$newinvoiceline->fk_parent_line=$fk_parent_line;
630
631
						if($this->type === Facture::TYPE_REPLACEMENT && $newinvoiceline->fk_remise_except){
632
                            $discount = new DiscountAbsolute($this->db);
633
                            $discount->fetch($newinvoiceline->fk_remise_except);
634
635
						    $discountId = $soc->set_remise_except($discount->amount_ht, $user, $discount->description, $discount->tva_tx);
636
						    $newinvoiceline->fk_remise_except = $discountId;
637
                        }
638
639
						$result=$newinvoiceline->insert();
640
641
						// Defined the new fk_parent_line
642
						if ($result > 0 && $newinvoiceline->product_type == 9) {
643
							$fk_parent_line = $result;
644
						}
645
					}
646
					if ($result < 0)
647
					{
648
						$this->error=$newinvoiceline->error;
649
						$error++;
650
						break;
651
					}
652
				}
653
			}
654
			elseif (! $error && empty($this->fac_rec)) 		// If this->lines is an array of invoice line arrays
655
			{
656
				$fk_parent_line = 0;
657
658
				dol_syslog("There is ".count($this->lines)." lines that are array lines");
659
660
				foreach ($this->lines as $i => $val)
661
				{
662
                	$line = $this->lines[$i];
663
664
                	// Test and convert into object this->lines[$i]. When coming from REST API, we may still have an array
665
				    //if (! is_object($line)) $line=json_decode(json_encode($line), false);  // convert recursively array into object.
666
                	if (! is_object($line)) $line = (object) $line;
667
668
				    if ($result >= 0)
669
					{
670
						// Reset fk_parent_line for no child products and special product
671
						if (($line->product_type != 9 && empty($line->fk_parent_line)) || $line->product_type == 9) {
672
							$fk_parent_line = 0;
673
						}
674
675
						// Complete vat rate with code
676
						$vatrate = $line->tva_tx;
677
						if ($line->vat_src_code && ! preg_match('/\(.*\)/', $vatrate)) $vatrate.=' ('.$line->vat_src_code.')';
678
679
                        $result = $this->addline(
680
							$line->desc,
681
							$line->subprice,
682
							$line->qty,
683
							$vatrate,
684
							$line->localtax1_tx,
685
							$line->localtax2_tx,
686
							$line->fk_product,
687
							$line->remise_percent,
688
							$line->date_start,
689
							$line->date_end,
690
							$line->fk_code_ventilation,
691
							$line->info_bits,
692
							$line->fk_remise_except,
693
							'HT',
694
							0,
695
							$line->product_type,
696
							$line->rang,
697
							$line->special_code,
698
                            $this->element,
699
                            $line->id,
700
							$fk_parent_line,
701
							$line->fk_fournprice,
702
							$line->pa_ht,
703
							$line->label,
704
							$line->array_options,
705
							$line->situation_percent,
706
							$line->fk_prev_id,
707
							$line->fk_unit,
708
							$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...
709
						);
710
						if ($result < 0)
711
						{
712
							$this->error=$this->db->lasterror();
713
							dol_print_error($this->db);
714
							$this->db->rollback();
715
							return -1;
716
						}
717
718
						// Defined the new fk_parent_line
719
						if ($result > 0 && $line->product_type == 9) {
720
							$fk_parent_line = $result;
721
						}
722
					}
723
				}
724
			}
725
726
			/*
727
			 * Insert lines of template invoices
728
			 */
729
			if (! $error && $this->fac_rec > 0)
730
			{
731
				foreach ($_facrec->lines as $i => $val)
732
				{
733
					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...
734
					{
735
						$prod = new Product($this->db);
736
						$res=$prod->fetch($_facrec->lines[$i]->fk_product);
737
					}
738
739
					// For line from template invoice, we use data from template invoice
740
					/*
741
					$tva_tx = get_default_tva($mysoc,$soc,$prod->id);
742
					$tva_npr = get_default_npr($mysoc,$soc,$prod->id);
743
					if (empty($tva_tx)) $tva_npr=0;
744
					$localtax1_tx=get_localtax($tva_tx,1,$soc,$mysoc,$tva_npr);
745
					$localtax2_tx=get_localtax($tva_tx,2,$soc,$mysoc,$tva_npr);
746
					*/
747
					$tva_tx = $_facrec->lines[$i]->tva_tx.($_facrec->lines[$i]->vat_src_code ? '('.$_facrec->lines[$i]->vat_src_code.')' : '');
748
					$tva_npr = $_facrec->lines[$i]->info_bits;
749
					if (empty($tva_tx)) $tva_npr=0;
750
					$localtax1_tx = $_facrec->lines[$i]->localtax1_tx;
751
					$localtax2_tx = $_facrec->lines[$i]->localtax2_tx;
752
753
					$fk_product_fournisseur_price = empty($_facrec->lines[$i]->fk_product_fournisseur_price)?null:$_facrec->lines[$i]->fk_product_fournisseur_price;
754
					$buyprice = empty($_facrec->lines[$i]->buyprice)?0:$_facrec->lines[$i]->buyprice;
755
					// If buyprice not defined from template invoice, we try to guess the best value
756
					if (! $buyprice && $_facrec->lines[$i]->fk_product > 0)
757
                    {
758
                        require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.product.class.php';
759
                        $producttmp = new ProductFournisseur($this->db);
760
                        $producttmp->fetch($_facrec->lines[$i]->fk_product);
761
762
                        // If margin module defined on costprice, we try the costprice
763
                        // If not defined or if module margin defined and pmp and stock module enabled, we try pmp price
764
                        // else we get the best supplier price
765
                        if ($conf->global->MARGIN_TYPE == 'costprice' && ! empty($producttmp->cost_price)) $buyprice = $producttmp->cost_price;
766
                        elseif (! empty($conf->stock->enabled) && ($conf->global->MARGIN_TYPE == 'costprice' || $conf->global->MARGIN_TYPE == 'pmp') && ! empty($producttmp->pmp)) $buyprice = $producttmp->pmp;
767
                        else {
768
                            if ($producttmp->find_min_price_product_fournisseur($_facrec->lines[$i]->fk_product) > 0)
769
                            {
770
                                if ($producttmp->product_fourn_price_id > 0)
771
                                {
772
                                    $buyprice = price2num($producttmp->fourn_unitprice * (1 - $producttmp->fourn_remise_percent/100) + $producttmp->fourn_remise, 'MU');
773
                                }
774
                            }
775
                        }
776
                    }
777
778
					$result_insert = $this->addline(
779
						$_facrec->lines[$i]->desc,
780
						$_facrec->lines[$i]->subprice,
781
						$_facrec->lines[$i]->qty,
782
						$tva_tx,
783
						$localtax1_tx,
784
						$localtax2_tx,
785
						$_facrec->lines[$i]->fk_product,
786
						$_facrec->lines[$i]->remise_percent,
787
						($_facrec->lines[$i]->date_start_fill == 1 && $originaldatewhen)?$originaldatewhen:'',
788
						($_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...
789
						0,
790
						$tva_npr,
791
						'',
792
						'HT',
793
						0,
794
						$_facrec->lines[$i]->product_type,
795
						$_facrec->lines[$i]->rang,
796
						$_facrec->lines[$i]->special_code,
797
						'',
798
						0,
799
						0,
800
					    $fk_product_fournisseur_price,
801
						$buyprice,
802
						$_facrec->lines[$i]->label,
803
						empty($_facrec->lines[$i]->array_options)?null:$_facrec->lines[$i]->array_options,
804
						$_facrec->lines[$i]->situation_percent,
805
						'',
806
						$_facrec->lines[$i]->fk_unit,
807
						$_facrec->lines[$i]->pu_ht_devise
808
					);
809
810
					if ( $result_insert < 0)
811
					{
812
						$error++;
813
						$this->error=$this->db->error();
814
						break;
815
					}
816
				}
817
			}
818
819
			if (! $error)
820
			{
821
822
				$result=$this->update_price(1);
823
				if ($result > 0)
824
				{
825
					$action='create';
826
827
					// Actions on extra fields
828
					if (! $error)
829
					{
830
					    $result=$this->insertExtraFields();
831
					    if ($result < 0) $error++;
832
					}
833
834
			        if (! $error && ! $notrigger)
835
			        {
836
			           // Call trigger
837
			           $result=$this->call_trigger('BILL_CREATE', $user);
838
			           if ($result < 0) $error++;
839
			           // End call triggers
840
			        }
841
842
					if (! $error)
843
					{
844
						$this->db->commit();
845
						return $this->id;
846
					}
847
					else
848
					{
849
						$this->db->rollback();
850
						return -4;
851
					}
852
				}
853
				else
854
				{
855
					$this->error=$langs->trans('FailedToUpdatePrice');
856
					$this->db->rollback();
857
					return -3;
858
				}
859
			}
860
			else
861
			{
862
				dol_syslog(get_class($this)."::create error ".$this->error, LOG_ERR);
863
				$this->db->rollback();
864
				return -2;
865
			}
866
		}
867
		else
868
		{
869
			$this->error=$this->db->error();
870
			$this->db->rollback();
871
			return -1;
872
		}
873
	}
874
875
876
	/**
877
	 *	Create a new invoice in database from current invoice
878
	 *
879
	 *	@param      User	$user    		Object user that ask creation
880
	 *	@param		int		$invertdetail	Reverse sign of amounts for lines
881
	 *	@return		int						<0 if KO, >0 if OK
882
	 */
883
    public function createFromCurrent(User $user, $invertdetail = 0)
884
	{
885
		global $conf;
886
887
		// Charge facture source
888
		$facture=new Facture($this->db);
889
890
		// Retreive all extrafield
891
		// fetch optionals attributes and labels
892
		$this->fetch_optionals();
893
894
        if(!empty($this->array_options)){
895
                    $facture->array_options = $this->array_options;
896
        }
897
898
        foreach($this->lines as &$line){
899
                    $line->fetch_optionals();//fetch extrafields
900
        }
901
902
		$facture->fk_facture_source = $this->fk_facture_source;
903
		$facture->type 			    = $this->type;
904
		$facture->socid 		    = $this->socid;
905
		$facture->date              = $this->date;
906
		$facture->date_pointoftax   = $this->date_pointoftax;
907
		$facture->note_public       = $this->note_public;
908
		$facture->note_private      = $this->note_private;
909
		$facture->ref_client        = $this->ref_client;
910
		$facture->modelpdf          = $this->modelpdf;
911
		$facture->fk_project        = $this->fk_project;
912
		$facture->cond_reglement_id = $this->cond_reglement_id;
913
		$facture->mode_reglement_id = $this->mode_reglement_id;
914
		$facture->remise_absolue    = $this->remise_absolue;
915
		$facture->remise_percent    = $this->remise_percent;
916
917
		$facture->origin                        = $this->origin;
918
		$facture->origin_id                     = $this->origin_id;
919
920
		$facture->lines		    	= $this->lines;	// Array of lines of invoice
921
		$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

921
		/** @scrutinizer ignore-deprecated */ $facture->products		    = $this->lines;	// Tant que products encore utilise
Loading history...
922
		$facture->situation_counter = $this->situation_counter;
923
		$facture->situation_cycle_ref=$this->situation_cycle_ref;
924
		$facture->situation_final  = $this->situation_final;
925
926
		// Loop on each line of new invoice
927
		foreach($facture->lines as $i => $tmpline)
928
		{
929
			$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

929
			$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...
930
			if ($invertdetail)
931
			{
932
				$facture->lines[$i]->subprice  = -$facture->lines[$i]->subprice;
933
				$facture->lines[$i]->total_ht  = -$facture->lines[$i]->total_ht;
934
				$facture->lines[$i]->total_tva = -$facture->lines[$i]->total_tva;
935
				$facture->lines[$i]->total_localtax1 = -$facture->lines[$i]->total_localtax1;
936
				$facture->lines[$i]->total_localtax2 = -$facture->lines[$i]->total_localtax2;
937
				$facture->lines[$i]->total_ttc = -$facture->lines[$i]->total_ttc;
938
			}
939
		}
940
941
		dol_syslog(get_class($this)."::createFromCurrent invertdetail=".$invertdetail." socid=".$this->socid." nboflines=".count($facture->lines));
942
943
		$facid = $facture->create($user);
944
		if ($facid <= 0)
945
		{
946
			$this->error=$facture->error;
947
			$this->errors=$facture->errors;
948
		}
949
		elseif ($this->type == self::TYPE_SITUATION && !empty($conf->global->INVOICE_USE_SITUATION))
950
		{
951
			$this->fetchObjectLinked('', '', $facture->id, 'facture');
952
953
			foreach ($this->linkedObjectsIds as $typeObject => $Tfk_object)
954
			{
955
				foreach ($Tfk_object as $fk_object)
956
				{
957
					$facture->add_object_linked($typeObject, $fk_object);
958
				}
959
			}
960
961
			$facture->add_object_linked('facture', $this->fk_facture_source);
962
		}
963
964
		return $facid;
965
	}
966
967
968
	/**
969
	 *	Load an object from its id and create a new one in database
970
	 *
971
     *	@param      User	$user        	User that clone
972
	 *  @param  	int 	$fromid         Id of object to clone
973
	 * 	@return		int					    New id of clone
974
	 */
975
	public function createFromClone(User $user, $fromid = 0)
976
	{
977
		global $hookmanager;
978
979
		$error=0;
980
981
		$object=new Facture($this->db);
982
983
		$this->db->begin();
984
985
		$object->fetch($fromid);
986
987
		// Change socid if needed
988
		if (! empty($this->socid) && $this->socid != $object->socid)
989
		{
990
			$objsoc = new Societe($this->db);
991
992
			if ($objsoc->fetch($this->socid)>0)
993
			{
994
			    $object->socid 				= $objsoc->id;
995
			    $object->cond_reglement_id	= (! empty($objsoc->cond_reglement_id) ? $objsoc->cond_reglement_id : 0);
996
			    $object->mode_reglement_id	= (! empty($objsoc->mode_reglement_id) ? $objsoc->mode_reglement_id : 0);
997
			    $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...
998
			    $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

998
			    /** @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...
999
			}
1000
1001
			// TODO Change product price if multi-prices
1002
		}
1003
1004
		$object->id=0;
1005
		$object->statut= self::STATUS_DRAFT;
1006
1007
		// Clear fields
1008
		$object->date               = (empty($this->date) ? dol_now() : $this->date);
1009
		$object->user_author        = $user->id;
1010
		$object->user_valid         = '';
1011
		$object->fk_facture_source  = 0;
1012
		$object->date_creation      = '';
1013
		$object->date_modification = '';
1014
		$object->date_validation    = '';
1015
		$object->ref_client         = '';
1016
		$object->close_code         = '';
1017
		$object->close_note         = '';
1018
		$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

1018
		/** @scrutinizer ignore-deprecated */ $object->products = $object->lines;	        // For backward compatibility
Loading history...
1019
1020
		// Loop on each line of new invoice
1021
		foreach($object->lines as $i => $line)
1022
		{
1023
		    if (($object->lines[$i]->info_bits & 0x02) == 0x02)	// We do not clone line of discounts
1024
			{
1025
			    unset($object->lines[$i]);
1026
			    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

1026
			    unset(/** @scrutinizer ignore-deprecated */ $object->products[$i]);	// Tant que products encore utilise
Loading history...
1027
			}
1028
		}
1029
1030
		// Create clone
1031
		$object->context['createfromclone'] = 'createfromclone';
1032
		$result=$object->create($user);
1033
		if ($result < 0) $error++;
1034
		else {
1035
			// copy internal contacts
1036
		    if ($object->copy_linked_contact($this, 'internal') < 0)
1037
				$error++;
1038
1039
			// copy external contacts if same company
1040
			elseif ($this->socid == $object->socid)
1041
			{
1042
			    if ($object->copy_linked_contact($this, 'external') < 0)
1043
					$error++;
1044
			}
1045
		}
1046
1047
		if (! $error)
1048
		{
1049
			// Hook of thirdparty module
1050
			if (is_object($hookmanager))
1051
			{
1052
				$parameters=array('objFrom'=>$this);
1053
				$action='';
1054
				$reshook=$hookmanager->executeHooks('createFrom', $parameters, $object, $action);    // Note that $action and $object may have been modified by some hooks
1055
				if ($reshook < 0) $error++;
1056
			}
1057
		}
1058
1059
		unset($object->context['createfromclone']);
1060
1061
		// End
1062
		if (! $error)
1063
		{
1064
			$this->db->commit();
1065
			return $object->id;
1066
		}
1067
		else
1068
		{
1069
			$this->db->rollback();
1070
			return -1;
1071
		}
1072
	}
1073
1074
	/**
1075
	 *  Load an object from an order and create a new invoice into database
1076
	 *
1077
	 *  @param      Object			$object         	Object source
1078
	 *  @param		User			$user				Object user
1079
	 *  @return     int             					<0 if KO, 0 if nothing done, 1 if OK
1080
	 */
1081
    public function createFromOrder($object, User $user)
1082
	{
1083
		global $conf, $hookmanager;
1084
1085
		$error=0;
1086
1087
		// Closed order
1088
		$this->date = dol_now();
1089
		$this->source = 0;
1090
1091
		$num=count($object->lines);
1092
		for ($i = 0; $i < $num; $i++)
1093
		{
1094
			$line = new FactureLigne($this->db);
1095
1096
			$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

1096
			/** @scrutinizer ignore-deprecated */ $line->libelle			= $object->lines[$i]->libelle;
Loading history...
1097
			$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

1097
			/** @scrutinizer ignore-deprecated */ $line->label			= $object->lines[$i]->label;
Loading history...
1098
			$line->desc				= $object->lines[$i]->desc;
1099
			$line->subprice			= $object->lines[$i]->subprice;
1100
			$line->total_ht			= $object->lines[$i]->total_ht;
1101
			$line->total_tva		= $object->lines[$i]->total_tva;
1102
			$line->total_localtax1	= $object->lines[$i]->total_localtax1;
1103
			$line->total_localtax2	= $object->lines[$i]->total_localtax2;
1104
			$line->total_ttc		= $object->lines[$i]->total_ttc;
1105
			$line->vat_src_code  	= $object->lines[$i]->vat_src_code;
1106
			$line->tva_tx			= $object->lines[$i]->tva_tx;
1107
			$line->localtax1_tx		= $object->lines[$i]->localtax1_tx;
1108
			$line->localtax2_tx		= $object->lines[$i]->localtax2_tx;
1109
			$line->qty				= $object->lines[$i]->qty;
1110
			$line->fk_remise_except	= $object->lines[$i]->fk_remise_except;
1111
			$line->remise_percent	= $object->lines[$i]->remise_percent;
1112
			$line->fk_product		= $object->lines[$i]->fk_product;
1113
			$line->info_bits		= $object->lines[$i]->info_bits;
1114
			$line->product_type		= $object->lines[$i]->product_type;
1115
			$line->rang				= $object->lines[$i]->rang;
1116
			$line->special_code		= $object->lines[$i]->special_code;
1117
			$line->fk_parent_line	= $object->lines[$i]->fk_parent_line;
1118
			$line->fk_unit			= $object->lines[$i]->fk_unit;
1119
			$line->date_start 		= $object->lines[$i]->date_start;
1120
			$line->date_end 		= $object->lines[$i]->date_end;
1121
1122
			$line->fk_fournprice	= $object->lines[$i]->fk_fournprice;
1123
			$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);
1124
			$line->pa_ht			= $marginInfos[0];
1125
1126
            // get extrafields from original line
1127
			$object->lines[$i]->fetch_optionals();
1128
			foreach($object->lines[$i]->array_options as $options_key => $value)
1129
				$line->array_options[$options_key] = $value;
1130
1131
			$this->lines[$i] = $line;
1132
		}
1133
1134
		$this->socid                = $object->socid;
1135
		$this->fk_project           = $object->fk_project;
1136
		$this->cond_reglement_id    = $object->cond_reglement_id;
1137
		$this->mode_reglement_id    = $object->mode_reglement_id;
1138
		$this->availability_id      = $object->availability_id;
1139
		$this->demand_reason_id     = $object->demand_reason_id;
1140
		$this->date_livraison       = $object->date_livraison;
1141
		$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

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

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

2809
			/** @scrutinizer ignore-deprecated */ $this->line->label=$label;	// deprecated
Loading history...
2810
			$this->line->desc=$desc;
2811
2812
			$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...
2813
			$this->line->subprice=       ($this->type==self::TYPE_CREDIT_NOTE?-abs($pu_ht):$pu_ht); // For credit note, unit price always negative, always positive otherwise
2814
2815
			$this->line->vat_src_code=$vat_src_code;
2816
			$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...
2817
			$this->line->localtax1_tx=($total_localtax1?$localtaxes_type[1]:0);
2818
			$this->line->localtax2_tx=($total_localtax2?$localtaxes_type[3]:0);
2819
			$this->line->localtax1_type = $localtaxes_type[0];
2820
			$this->line->localtax2_type = $localtaxes_type[2];
2821
2822
			$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
2823
			$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
2824
			$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
2825
			$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
2826
			$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
2827
2828
			$this->line->fk_product=$fk_product;
2829
			$this->line->product_type=$product_type;
2830
			$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...
2831
			$this->line->date_start=$date_start;
2832
			$this->line->date_end=$date_end;
2833
			$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...
2834
			$this->line->rang=$rangtouse;
2835
			$this->line->info_bits=$info_bits;
2836
			$this->line->fk_remise_except=$fk_remise_except;
2837
2838
			$this->line->special_code=$special_code;
2839
			$this->line->fk_parent_line=$fk_parent_line;
2840
			$this->line->origin=$origin;
2841
			$this->line->origin_id=$origin_id;
2842
			$this->line->situation_percent = $situation_percent;
2843
			$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...
2844
			$this->line->fk_unit=$fk_unit;
2845
2846
			// infos marge
2847
			$this->line->fk_fournprice = $fk_fournprice;
2848
			$this->line->pa_ht = $pa_ht;
2849
2850
			// Multicurrency
2851
			$this->line->fk_multicurrency			= $this->fk_multicurrency;
2852
			$this->line->multicurrency_code			= $this->multicurrency_code;
2853
			$this->line->multicurrency_subprice		= $pu_ht_devise;
2854
			$this->line->multicurrency_total_ht 	= $multicurrency_total_ht;
2855
            $this->line->multicurrency_total_tva 	= $multicurrency_total_tva;
2856
            $this->line->multicurrency_total_ttc 	= $multicurrency_total_ttc;
2857
2858
			if (is_array($array_options) && count($array_options)>0) {
2859
				$this->line->array_options=$array_options;
2860
			}
2861
2862
			$result=$this->line->insert();
2863
			if ($result > 0)
2864
			{
2865
				// Reorder if child line
2866
				if (! empty($fk_parent_line)) $this->line_order(true, 'DESC');
2867
2868
				// Mise a jour informations denormalisees au niveau de la facture meme
2869
				$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.
2870
2871
				if ($result > 0)
2872
				{
2873
					$this->db->commit();
2874
					return $this->line->id;
2875
				}
2876
				else
2877
				{
2878
					$this->error=$this->db->lasterror();
2879
					$this->db->rollback();
2880
					return -1;
2881
				}
2882
			}
2883
			else
2884
			{
2885
				$this->error=$this->line->error;
2886
				$this->db->rollback();
2887
				return -2;
2888
			}
2889
		}
2890
		else
2891
		{
2892
			dol_syslog(get_class($this)."::addline status of order must be Draft to allow use of ->addline()", LOG_ERR);
2893
			return -3;
2894
		}
2895
	}
2896
2897
	/**
2898
	 *  Update a detail line
2899
	 *
2900
	 *  @param     	int			$rowid           	Id of line to update
2901
	 *  @param     	string		$desc            	Description of line
2902
	 *  @param     	double		$pu              	Prix unitaire (HT ou TTC selon price_base_type) (> 0 even for credit note lines)
2903
	 *  @param     	double		$qty             	Quantity
2904
	 *  @param     	double		$remise_percent  	Percentage discount of the line
2905
	 *  @param     	int		    $date_start      	Date de debut de validite du service
2906
	 *  @param     	int		    $date_end        	Date de fin de validite du service
2907
	 *  @param     	double		$txtva          	VAT Rate (Can be '8.5', '8.5 (ABC)')
2908
	 * 	@param		double		$txlocaltax1		Local tax 1 rate
2909
	 *  @param		double		$txlocaltax2		Local tax 2 rate
2910
	 * 	@param     	string		$price_base_type 	HT or TTC
2911
	 * 	@param     	int			$info_bits 		    Miscellaneous informations
2912
	 * 	@param		int			$type				Type of line (0=product, 1=service)
2913
	 * 	@param		int			$fk_parent_line		Id of parent line (0 in most cases, used by modules adding sublevels into lines).
2914
	 * 	@param		int			$skip_update_total	Keep fields total_xxx to 0 (used for special lines by some modules)
2915
	 * 	@param		int			$fk_fournprice		Id of origin supplier price
2916
	 * 	@param		int			$pa_ht				Price (without tax) of product when it was bought
2917
	 * 	@param		string		$label				Label of the line (deprecated, do not use)
2918
	 * 	@param		int			$special_code		Special code (also used by externals modules!)
2919
     *  @param		array		$array_options		extrafields array
2920
	 * 	@param      int         $situation_percent  Situation advance percentage
2921
	 * 	@param 		string		$fk_unit 			Code of the unit to use. Null to use the default one
2922
	 * 	@param		double		$pu_ht_devise		Unit price in currency
2923
	 * 	@param		int			$notrigger			disable line update trigger
2924
	 *  @return    	int             				< 0 if KO, > 0 if OK
2925
	 */
2926
    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)
2927
	{
2928
		global $conf,$user;
2929
		// Deprecation warning
2930
		if ($label) {
2931
			dol_syslog(__METHOD__ . ": using line label is deprecated", LOG_WARNING);
2932
		}
2933
2934
		include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
2935
2936
		global $mysoc,$langs;
2937
2938
		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);
2939
2940
		if ($this->brouillon)
2941
		{
2942
			if (!$this->is_last_in_cycle() && empty($this->error))
2943
			{
2944
				if (!$this->checkProgressLine($rowid, $situation_percent))
2945
				{
2946
					if (!$this->error) $this->error=$langs->trans('invoiceLineProgressError');
2947
					return -3;
2948
				}
2949
			}
2950
2951
			if ($date_start && $date_end && $date_start > $date_end) {
2952
				$langs->load("errors");
2953
				$this->error=$langs->trans('ErrorStartDateGreaterEnd');
2954
				return -1;
2955
			}
2956
2957
			$this->db->begin();
2958
2959
			// Clean parameters
2960
			if (empty($qty)) $qty=0;
2961
			if (empty($fk_parent_line) || $fk_parent_line < 0) $fk_parent_line=0;
2962
			if (empty($special_code) || $special_code == 3) $special_code=0;
2963
			if (! isset($situation_percent) || $situation_percent > 100 || (string) $situation_percent == '') $situation_percent = 100;
2964
2965
			$remise_percent	= price2num($remise_percent);
2966
			$qty			= price2num($qty);
2967
			$pu 			= price2num($pu);
2968
        	$pu_ht_devise	= price2num($pu_ht_devise);
2969
			$pa_ht			= price2num($pa_ht);
2970
			$txtva			= price2num($txtva);
2971
			$txlocaltax1	= price2num($txlocaltax1);
2972
			$txlocaltax2	= price2num($txlocaltax2);
2973
2974
			// Check parameters
2975
			if ($type < 0) return -1;
2976
2977
			// Calculate total with, without tax and tax from qty, pu, remise_percent and txtva
2978
			// TRES IMPORTANT: C'est au moment de l'insertion ligne qu'on doit stocker
2979
			// la part ht, tva et ttc, et ce au niveau de la ligne qui a son propre taux tva.
2980
2981
			$localtaxes_type=getLocalTaxesFromRate($txtva, 0, $this->thirdparty, $mysoc);
2982
2983
			// Clean vat code
2984
    		$vat_src_code='';
2985
    		if (preg_match('/\((.*)\)/', $txtva, $reg))
2986
    		{
2987
    		    $vat_src_code = $reg[1];
2988
    		    $txtva = preg_replace('/\s*\(.*\)/', '', $txtva);    // Remove code into vatrate.
2989
    		}
2990
2991
			$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);
2992
2993
			$total_ht  = $tabprice[0];
2994
			$total_tva = $tabprice[1];
2995
			$total_ttc = $tabprice[2];
2996
			$total_localtax1=$tabprice[9];
2997
			$total_localtax2=$tabprice[10];
2998
			$pu_ht  = $tabprice[3];
2999
			$pu_tva = $tabprice[4];
3000
			$pu_ttc = $tabprice[5];
3001
3002
			// MultiCurrency
3003
			$multicurrency_total_ht  = $tabprice[16];
3004
            $multicurrency_total_tva = $tabprice[17];
3005
            $multicurrency_total_ttc = $tabprice[18];
3006
			$pu_ht_devise = $tabprice[19];
3007
3008
			// Old properties: $price, $remise (deprecated)
3009
			$price = $pu;
3010
			$remise = 0;
3011
			if ($remise_percent > 0)
3012
			{
3013
				$remise = round(($pu * $remise_percent / 100), 2);
3014
				$price = ($pu - $remise);
3015
			}
3016
			$price    = price2num($price);
3017
3018
			//Fetch current line from the database and then clone the object and set it in $oldline property
3019
			$line = new FactureLigne($this->db);
3020
			$line->fetch($rowid);
3021
3022
			if (!empty($line->fk_product))
3023
			{
3024
				$product=new Product($this->db);
3025
				$result=$product->fetch($line->fk_product);
3026
				$product_type=$product->type;
3027
3028
				if (! empty($conf->global->STOCK_MUST_BE_ENOUGH_FOR_INVOICE) && $product_type == 0 && $product->stock_reel < $qty) {
3029
                    $langs->load("errors");
3030
				    $this->error=$langs->trans('ErrorStockIsNotEnoughToAddProductOnInvoice', $product->ref);
3031
					$this->db->rollback();
3032
					return -3;
3033
				}
3034
			}
3035
3036
			$staticline = clone $line;
3037
3038
			$line->oldline = $staticline;
3039
			$this->line = $line;
3040
            $this->line->context = $this->context;
3041
3042
			// Reorder if fk_parent_line change
3043
			if (! empty($fk_parent_line) && ! empty($staticline->fk_parent_line) && $fk_parent_line != $staticline->fk_parent_line)
3044
			{
3045
				$rangmax = $this->line_max($fk_parent_line);
3046
				$this->line->rang = $rangmax + 1;
3047
			}
3048
3049
			$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

3049
			/** @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...
3050
			$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

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

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

3872
		if (!$user->rights->societe->client->voir && !/** @scrutinizer ignore-deprecated */ $user->societe_id)
Loading history...
3873
		{
3874
			$sql.= " LEFT JOIN ".MAIN_DB_PREFIX."societe_commerciaux as sc ON f.fk_soc = sc.fk_soc";
3875
			$sql.= " WHERE sc.fk_user = " .$user->id;
3876
			$clause = " AND";
3877
		}
3878
		$sql.= $clause." f.paye=0";
3879
		$sql.= " AND f.entity IN (".getEntity('invoice').")";
3880
		$sql.= " AND f.fk_statut = ".self::STATUS_VALIDATED;
3881
		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

3881
		if ($user->societe_id) $sql.= " AND f.fk_soc = "./** @scrutinizer ignore-deprecated */ $user->societe_id;
Loading history...
3882
3883
		$resql=$this->db->query($sql);
3884
		if ($resql)
3885
		{
3886
			$langs->load("bills");
3887
			$now=dol_now();
3888
3889
			$response = new WorkboardResponse();
3890
			$response->warning_delay=$conf->facture->client->warning_delay/60/60/24;
3891
			$response->label=$langs->trans("CustomerBillsUnpaid");
3892
			$response->url=DOL_URL_ROOT.'/compta/facture/list.php?search_status=1&mainmenu=billing&leftmenu=customers_bills';
3893
			$response->img=img_object('', "bill");
3894
3895
			$generic_facture = new Facture($this->db);
3896
3897
			while ($obj=$this->db->fetch_object($resql))
3898
			{
3899
				$generic_facture->date_lim_reglement = $this->db->jdate($obj->datefin);
3900
				$generic_facture->statut = $obj->fk_statut;
3901
3902
				$response->nbtodo++;
3903
				$response->total += $obj->total;
3904
3905
				if ($generic_facture->hasDelay()) {
3906
					$response->nbtodolate++;
3907
				}
3908
			}
3909
3910
			return $response;
3911
		}
3912
		else
3913
		{
3914
			dol_print_error($this->db);
3915
			$this->error=$this->db->error();
3916
			return -1;
3917
		}
3918
	}
3919
3920
3921
	/* gestion des contacts d'une facture */
3922
3923
	/**
3924
	 *	Retourne id des contacts clients de facturation
3925
	 *
3926
	 *	@return     array       Liste des id contacts facturation
3927
	 */
3928
    public function getIdBillingContact()
3929
	{
3930
		return $this->getIdContact('external', 'BILLING');
3931
	}
3932
3933
	/**
3934
	 *	Retourne id des contacts clients de livraison
3935
	 *
3936
	 *	@return     array       Liste des id contacts livraison
3937
	 */
3938
    public function getIdShippingContact()
3939
	{
3940
		return $this->getIdContact('external', 'SHIPPING');
3941
	}
3942
3943
3944
	/**
3945
	 *  Initialise an instance with random values.
3946
	 *  Used to build previews or test instances.
3947
	 *	id must be 0 if object instance is a specimen.
3948
	 *
3949
	 *	@param	string		$option		''=Create a specimen invoice with lines, 'nolines'=No lines
3950
	 *  @return	void
3951
	 */
3952
    public function initAsSpecimen($option = '')
3953
	{
3954
		global $langs;
3955
3956
		$now=dol_now();
3957
		$arraynow=dol_getdate($now);
3958
		$nownotime=dol_mktime(0, 0, 0, $arraynow['mon'], $arraynow['mday'], $arraynow['year']);
3959
3960
        // Load array of products prodids
3961
		$num_prods = 0;
3962
		$prodids = array();
3963
		$sql = "SELECT rowid";
3964
		$sql.= " FROM ".MAIN_DB_PREFIX."product";
3965
		$sql.= " WHERE entity IN (".getEntity('product').")";
3966
		$resql = $this->db->query($sql);
3967
		if ($resql)
3968
		{
3969
			$num_prods = $this->db->num_rows($resql);
3970
			$i = 0;
3971
			while ($i < $num_prods)
3972
			{
3973
				$i++;
3974
				$row = $this->db->fetch_row($resql);
3975
				$prodids[$i] = $row[0];
3976
			}
3977
		}
3978
		//Avoid php warning Warning: mt_rand(): max(0) is smaller than min(1) when no product exists
3979
		if (empty($num_prods)) {
1 ignored issue
show
introduced by
The condition empty($num_prods) is always true.
Loading history...
3980
			$num_prods=1;
3981
		}
3982
3983
		// Initialize parameters
3984
		$this->id=0;
3985
		$this->entity = 1;
3986
		$this->ref = 'SPECIMEN';
3987
		$this->specimen=1;
3988
		$this->socid = 1;
3989
		$this->date = $nownotime;
3990
		$this->date_lim_reglement = $nownotime + 3600 * 24 *30;
3991
		$this->cond_reglement_id   = 1;
3992
		$this->cond_reglement_code = 'RECEP';
3993
		$this->date_lim_reglement=$this->calculate_date_lim_reglement();
3994
		$this->mode_reglement_id   = 0;		// Not forced to show payment mode CHQ + VIR
3995
		$this->mode_reglement_code = '';	// Not forced to show payment mode CHQ + VIR
3996
		$this->note_public='This is a comment (public)';
3997
		$this->note_private='This is a comment (private)';
3998
		$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

3998
		/** @scrutinizer ignore-deprecated */ $this->note='This is a comment (private)';
Loading history...
3999
		$this->fk_incoterms=0;
4000
		$this->location_incoterms='';
4001
4002
		if (empty($option) || $option != 'nolines')
4003
		{
4004
			// Lines
4005
			$nbp = 5;
4006
			$xnbp = 0;
4007
			while ($xnbp < $nbp)
4008
			{
4009
				$line=new FactureLigne($this->db);
4010
				$line->desc=$langs->trans("Description")." ".$xnbp;
4011
				$line->qty=1;
4012
				$line->subprice=100;
4013
				$line->tva_tx=19.6;
4014
				$line->localtax1_tx=0;
4015
				$line->localtax2_tx=0;
4016
				$line->remise_percent=0;
4017
				if ($xnbp == 1)        // Qty is negative (product line)
4018
				{
4019
					$prodid = mt_rand(1, $num_prods);
4020
					$line->fk_product=$prodids[$prodid];
4021
					$line->qty=-1;
4022
					$line->total_ht=-100;
4023
					$line->total_ttc=-119.6;
4024
					$line->total_tva=-19.6;
4025
					$line->multicurrency_total_ht=-200;
4026
					$line->multicurrency_total_ttc=-239.2;
4027
					$line->multicurrency_total_tva=-39.2;
4028
				}
4029
				elseif ($xnbp == 2)    // UP is negative (free line)
4030
				{
4031
					$line->subprice=-100;
4032
					$line->total_ht=-100;
4033
					$line->total_ttc=-119.6;
4034
					$line->total_tva=-19.6;
4035
					$line->remise_percent=0;
4036
					$line->multicurrency_total_ht=-200;
4037
					$line->multicurrency_total_ttc=-239.2;
4038
					$line->multicurrency_total_tva=-39.2;
4039
				}
4040
				elseif ($xnbp == 3)    // Discount is 50% (product line)
4041
				{
4042
					$prodid = mt_rand(1, $num_prods);
4043
					$line->fk_product=$prodids[$prodid];
4044
					$line->total_ht=50;
4045
					$line->total_ttc=59.8;
4046
					$line->total_tva=9.8;
4047
					$line->multicurrency_total_ht=100;
4048
					$line->multicurrency_total_ttc=119.6;
4049
					$line->multicurrency_total_tva=19.6;
4050
					$line->remise_percent=50;
4051
				}
4052
				else    // (product line)
4053
				{
4054
					$prodid = mt_rand(1, $num_prods);
4055
					$line->fk_product=$prodids[$prodid];
4056
					$line->total_ht=100;
4057
					$line->total_ttc=119.6;
4058
					$line->total_tva=19.6;
4059
					$line->multicurrency_total_ht=200;
4060
					$line->multicurrency_total_ttc=239.2;
4061
					$line->multicurrency_total_tva=39.2;
4062
					$line->remise_percent=0;
4063
				}
4064
4065
				$this->lines[$xnbp]=$line;
4066
4067
4068
				$this->total_ht       += $line->total_ht;
4069
				$this->total_tva      += $line->total_tva;
4070
				$this->total_ttc      += $line->total_ttc;
4071
4072
				$this->multicurrency_total_ht       += $line->multicurrency_total_ht;
4073
				$this->multicurrency_total_tva      += $line->multicurrency_total_tva;
4074
				$this->multicurrency_total_ttc      += $line->multicurrency_total_ttc;
4075
4076
				$xnbp++;
4077
			}
4078
			$this->revenuestamp = 0;
4079
4080
			// Add a line "offered"
4081
			$line=new FactureLigne($this->db);
4082
			$line->desc=$langs->trans("Description")." (offered line)";
4083
			$line->qty=1;
4084
			$line->subprice=100;
4085
			$line->tva_tx=19.6;
4086
			$line->localtax1_tx=0;
4087
			$line->localtax2_tx=0;
4088
			$line->remise_percent=100;
4089
			$line->total_ht=0;
4090
			$line->total_ttc=0;    // 90 * 1.196
4091
			$line->total_tva=0;
4092
			$line->multicurrency_total_ht=0;
4093
			$line->multicurrency_total_ttc=0;
4094
			$line->multicurrency_total_tva=0;
4095
			$prodid = mt_rand(1, $num_prods);
4096
			$line->fk_product=$prodids[$prodid];
4097
4098
			$this->lines[$xnbp]=$line;
4099
			$xnbp++;
4100
		}
4101
	}
4102
4103
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4104
	/**
4105
	 *      Load indicators for dashboard (this->nbtodo and this->nbtodolate)
4106
	 *
4107
	 *      @return         int     <0 if KO, >0 if OK
4108
	 */
4109
    public function load_state_board()
4110
	{
4111
        // phpcs:enable
4112
		global $conf, $user;
4113
4114
		$this->nb=array();
4115
4116
		$clause = "WHERE";
4117
4118
		$sql = "SELECT count(f.rowid) as nb";
4119
		$sql.= " FROM ".MAIN_DB_PREFIX."facture as f";
4120
		$sql.= " LEFT JOIN ".MAIN_DB_PREFIX."societe as s ON f.fk_soc = s.rowid";
4121
		if (!$user->rights->societe->client->voir && !$user->societe_id)
4122
		{
4123
			$sql.= " LEFT JOIN ".MAIN_DB_PREFIX."societe_commerciaux as sc ON s.rowid = sc.fk_soc";
4124
			$sql.= " WHERE sc.fk_user = " .$user->id;
4125
			$clause = "AND";
4126
		}
4127
		$sql.= " ".$clause." f.entity IN (".getEntity('invoice').")";
4128
4129
		$resql=$this->db->query($sql);
4130
		if ($resql)
4131
		{
4132
			while ($obj=$this->db->fetch_object($resql))
4133
			{
4134
				$this->nb["invoices"]=$obj->nb;
4135
			}
4136
            $this->db->free($resql);
4137
			return 1;
4138
		}
4139
		else
4140
		{
4141
			dol_print_error($this->db);
4142
			$this->error=$this->db->error();
4143
			return -1;
4144
		}
4145
	}
4146
4147
	/**
4148
	 * 	Create an array of invoice lines
4149
	 *
4150
	 * 	@return int		>0 if OK, <0 if KO
4151
	 */
4152
    public function getLinesArray()
4153
	{
4154
	    return $this->fetch_lines();
4155
	}
4156
4157
	/**
4158
	 *  Create a document onto disk according to template module.
4159
	 *
4160
	 *	@param	string		$modele			Generator to use. Caller must set it to obj->modelpdf or GETPOST('modelpdf','alpha') for example.
4161
	 *	@param	Translate	$outputlangs	objet lang a utiliser pour traduction
4162
	 *  @param  int			$hidedetails    Hide details of lines
4163
	 *  @param  int			$hidedesc       Hide description
4164
	 *  @param  int			$hideref        Hide ref
4165
	 *  @param   null|array  $moreparams     Array to provide more information
4166
	 *	@return int        					<0 if KO, >0 if OK
4167
	 */
4168
	public function generateDocument($modele, $outputlangs, $hidedetails = 0, $hidedesc = 0, $hideref = 0, $moreparams = null)
4169
	{
4170
		global $conf,$langs;
4171
4172
		$langs->load("bills");
4173
4174
		if (! dol_strlen($modele))
4175
		{
4176
			$modele = 'crabe';
4177
			$thisTypeConfName = 'FACTURE_ADDON_PDF_'.$this->type;
4178
4179
			if ($this->modelpdf) {
4180
				$modele = $this->modelpdf;
4181
			} elseif (! empty($conf->global->$thisTypeConfName)) {
4182
				$modele = $conf->global->$thisTypeConfName;
4183
			} elseif (! empty($conf->global->FACTURE_ADDON_PDF)) {
4184
				$modele = $conf->global->FACTURE_ADDON_PDF;
4185
			}
4186
		}
4187
4188
		$modelpath = "core/modules/facture/doc/";
4189
4190
		return $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams);
4191
	}
4192
4193
	/**
4194
	 * Gets the smallest reference available for a new cycle
4195
	 *
4196
	 * @return int >= 1 if OK, -1 if error
4197
	 */
4198
    public function newCycle()
4199
	{
4200
		$sql = 'SELECT max(situation_cycle_ref) FROM ' . MAIN_DB_PREFIX . 'facture as f';
4201
		$sql.= " WHERE f.entity in (".getEntity('invoice', 0).")";
4202
		$resql = $this->db->query($sql);
4203
		if ($resql) {
4204
			if ($resql->num_rows > 0)
4205
			{
4206
				$res = $this->db->fetch_array($resql);
4207
				$ref = $res['max(situation_cycle_ref)'];
4208
				$ref++;
4209
			} else {
4210
				$ref = 1;
4211
			}
4212
			$this->db->free($resql);
4213
			return $ref;
4214
		} else {
4215
			$this->error = $this->db->lasterror();
4216
			dol_syslog("Error sql=" . $sql . ", error=" . $this->error, LOG_ERR);
4217
			return -1;
4218
		}
4219
	}
4220
4221
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4222
	/**
4223
	 * Checks if the invoice is the first of a cycle
4224
	 *
4225
	 * @return boolean
4226
	 */
4227
    public function is_first()
4228
	{
4229
        // phpcs:enable
4230
		return ($this->situation_counter == 1);
4231
	}
4232
4233
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4234
	/**
4235
	 * Returns an array containing the previous situations as Facture objects
4236
	 *
4237
	 * @return mixed -1 if error, array of previous situations
4238
	 */
4239
    public function get_prev_sits()
4240
	{
4241
        // phpcs:enable
4242
		global $conf;
4243
4244
		$sql = 'SELECT rowid FROM ' . MAIN_DB_PREFIX . 'facture';
4245
		$sql .= ' where situation_cycle_ref = ' . $this->situation_cycle_ref;
4246
		$sql .= ' and situation_counter < ' . $this->situation_counter;
4247
		$sql .= ' AND entity = '. ($this->entity > 0 ? $this->entity : $conf->entity);
4248
		$resql = $this->db->query($sql);
4249
		$res = array();
4250
		if ($resql && $resql->num_rows > 0) {
4251
			while ($row = $this->db->fetch_object($resql)) {
4252
				$id = $row->rowid;
4253
				$situation = new Facture($this->db);
4254
				$situation->fetch($id);
4255
				$res[] = $situation;
4256
			}
4257
		} else {
4258
			$this->error = $this->db->error();
4259
			dol_syslog("Error sql=" . $sql . ", error=" . $this->error, LOG_ERR);
4260
			return -1;
4261
		}
4262
4263
		return $res;
4264
	}
4265
4266
	/**
4267
	 * Sets the invoice as a final situation
4268
	 *
4269
	 *  @param  	User	$user    	Object user
4270
	 *  @param     	int		$notrigger	1=Does not execute triggers, 0= execute triggers
4271
	 *	@return		int 				<0 if KO, >0 if OK
4272
	 */
4273
    public function setFinal(User $user, $notrigger = 0)
4274
	{
4275
		$error=0;
4276
4277
		$this->db->begin();
4278
4279
		$sql = 'UPDATE ' . MAIN_DB_PREFIX . 'facture SET situation_final = ' . $this->situation_final . ' where rowid = ' . $this->id;
4280
4281
		dol_syslog(__METHOD__, LOG_DEBUG);
4282
		$resql=$this->db->query($sql);
4283
		if (!$resql)
4284
		{
4285
			$this->errors[]=$this->db->error();
4286
			$error++;
4287
		}
4288
4289
		if (! $notrigger && empty($error))
4290
		{
4291
			// Call trigger
4292
			$result=$this->call_trigger('BILL_MODIFY', $user);
4293
			if ($result < 0) $error++;
4294
			// End call triggers
4295
		}
4296
4297
		if (! $error)
4298
		{
4299
			$this->db->commit();
4300
			return 1;
4301
		}
4302
		else
4303
		{
4304
			foreach($this->errors as $errmsg)
4305
			{
4306
				dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
4307
				$this->error.=($this->error?', '.$errmsg:$errmsg);
4308
			}
4309
			$this->db->rollback();
4310
			return -1*$error;
4311
		}
4312
	}
4313
4314
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4315
	/**
4316
	 * Checks if the invoice is the last in its cycle
4317
	 *
4318
	 * @return bool Last of the cycle status
4319
	 */
4320
    public function is_last_in_cycle()
4321
	{
4322
        // phpcs:enable
4323
		global $conf;
4324
4325
		if (!empty($this->situation_cycle_ref)) {
4326
			// No point in testing anything if we're not inside a cycle
4327
			$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);
4328
			$resql = $this->db->query($sql);
4329
4330
			if ($resql && $resql->num_rows > 0) {
4331
				$res = $this->db->fetch_array($resql);
4332
				$last = $res['max(situation_counter)'];
4333
				return ($last == $this->situation_counter);
4334
			} else {
4335
				$this->error = $this->db->lasterror();
4336
				dol_syslog(get_class($this) . "::select Error " . $this->error, LOG_ERR);
4337
				return false;
4338
			}
4339
		} else {
4340
			return true;
4341
		}
4342
	}
4343
4344
	/**
4345
	 * Function used to replace a thirdparty id with another one.
4346
	 *
4347
	 * @param  DoliDB  $db             Database handler
4348
	 * @param  int     $origin_id      Old third-party id
4349
	 * @param  int     $dest_id        New third-party id
4350
	 * @return bool
4351
	 */
4352
	public static function replaceThirdparty(DoliDB $db, $origin_id, $dest_id)
4353
	{
4354
		$tables = array(
4355
			'facture'
4356
		);
4357
4358
		return CommonObject::commonReplaceThirdparty($db, $origin_id, $dest_id, $tables);
4359
	}
4360
4361
	/**
4362
	 * Is the customer invoice delayed?
4363
	 *
4364
	 * @return bool
4365
	 */
4366
	public function hasDelay()
4367
	{
4368
		global $conf;
4369
4370
		$now = dol_now();
4371
4372
		// Paid invoices have status STATUS_CLOSED
4373
		if ($this->statut != Facture::STATUS_VALIDATED) return false;
4374
4375
		return $this->date_lim_reglement < ($now - $conf->facture->client->warning_delay);
4376
	}
4377
}
4378
4379
/**
4380
 *	Class to manage invoice lines.
4381
 *  Saved into database table llx_facturedet
4382
 */
4383
class FactureLigne extends CommonInvoiceLine
4384
{
4385
    /**
4386
	 * @var string ID to identify managed object
4387
	 */
4388
	public $element='facturedet';
4389
4390
    /**
4391
	 * @var string Name of table without prefix where object is stored
4392
	 */
4393
	public $table_element='facturedet';
4394
4395
	public $oldline;
4396
4397
	//! From llx_facturedet
4398
	//! Id facture
4399
	public $fk_facture;
4400
	//! Id parent line
4401
	public $fk_parent_line;
4402
	/**
4403
	 * @deprecated
4404
	 */
4405
	public $label;
4406
	//! Description ligne
4407
	public $desc;
4408
4409
	public $localtax1_type;	// Local tax 1 type
4410
	public $localtax2_type;	// Local tax 2 type
4411
	public $fk_remise_except;	// Link to line into llx_remise_except
4412
	public $rang = 0;
4413
4414
	public $fk_fournprice;
4415
	public $pa_ht;
4416
	public $marge_tx;
4417
	public $marque_tx;
4418
4419
	public $special_code;	// Liste d'options non cumulabels:
4420
	// 1: frais de port
4421
	// 2: ecotaxe
4422
	// 3: ??
4423
4424
	public $origin;
4425
	public $origin_id;
4426
4427
	public $fk_code_ventilation = 0;
4428
4429
	public $date_start;
4430
	public $date_end;
4431
4432
	// From llx_product
4433
	/**
4434
	 * @deprecated
4435
	 * @see $product_ref
4436
	 */
4437
	public $ref;				// Product ref (deprecated)
4438
	public $product_ref;       // Product ref
4439
	/**
4440
	 * @deprecated
4441
	 * @see $product_label
4442
	 */
4443
	public $libelle;      		// Product label (deprecated)
4444
	public $product_label;     // Product label
4445
	public $product_desc;  	// Description produit
4446
4447
	public $skip_update_total; // Skip update price total for special lines
4448
4449
	/**
4450
	 * @var int Situation advance percentage
4451
	 */
4452
	public $situation_percent;
4453
4454
	/**
4455
	 * @var int Previous situation line id reference
4456
	 */
4457
	public $fk_prev_id;
4458
4459
	// Multicurrency
4460
	public $fk_multicurrency;
4461
	public $multicurrency_code;
4462
	public $multicurrency_subprice;
4463
	public $multicurrency_total_ht;
4464
	public $multicurrency_total_tva;
4465
	public $multicurrency_total_ttc;
4466
4467
	/**
4468
	 *	Load invoice line from database
4469
	 *
4470
	 *	@param	int		$rowid      id of invoice line to get
4471
	 *	@return	int					<0 if KO, >0 if OK
4472
	 */
4473
    public function fetch($rowid)
4474
	{
4475
		$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,';
4476
		$sql.= ' fd.localtax1_tx, fd. localtax2_tx, fd.remise, fd.remise_percent, fd.fk_remise_except, fd.subprice,';
4477
		$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,';
4478
		$sql.= ' fd.info_bits, fd.special_code, fd.total_ht, fd.total_tva, fd.total_ttc, fd.total_localtax1, fd.total_localtax2, fd.rang,';
4479
		$sql.= ' fd.fk_code_ventilation,';
4480
		$sql.= ' fd.fk_unit, fd.fk_user_author, fd.fk_user_modif,';
4481
		$sql.= ' fd.situation_percent, fd.fk_prev_id,';
4482
		$sql.= ' fd.multicurrency_subprice,';
4483
		$sql.= ' fd.multicurrency_total_ht,';
4484
		$sql.= ' fd.multicurrency_total_tva,';
4485
		$sql.= ' fd.multicurrency_total_ttc,';
4486
		$sql.= ' p.ref as product_ref, p.label as product_libelle, p.description as product_desc';
4487
		$sql.= ' FROM '.MAIN_DB_PREFIX.'facturedet as fd';
4488
		$sql.= ' LEFT JOIN '.MAIN_DB_PREFIX.'product as p ON fd.fk_product = p.rowid';
4489
		$sql.= ' WHERE fd.rowid = '.$rowid;
4490
4491
		$result = $this->db->query($sql);
4492
		if ($result)
4493
		{
4494
			$objp = $this->db->fetch_object($result);
4495
4496
			$this->rowid				= $objp->rowid;
4497
			$this->id					= $objp->rowid;
4498
			$this->fk_facture			= $objp->fk_facture;
4499
			$this->fk_parent_line		= $objp->fk_parent_line;
4500
			$this->label				= $objp->custom_label;
4501
			$this->desc					= $objp->description;
4502
			$this->qty					= $objp->qty;
4503
			$this->subprice				= $objp->subprice;
4504
			$this->vat_src_code  		= $objp->vat_src_code;
4505
			$this->tva_tx				= $objp->tva_tx;
4506
			$this->localtax1_tx			= $objp->localtax1_tx;
4507
			$this->localtax2_tx			= $objp->localtax2_tx;
4508
			$this->remise_percent		= $objp->remise_percent;
4509
			$this->fk_remise_except		= $objp->fk_remise_except;
4510
			$this->fk_product			= $objp->fk_product;
4511
			$this->product_type			= $objp->product_type;
4512
			$this->date_start			= $this->db->jdate($objp->date_start);
4513
			$this->date_end				= $this->db->jdate($objp->date_end);
4514
			$this->info_bits			= $objp->info_bits;
4515
			$this->tva_npr              = ($objp->info_bits & 1 == 1) ? 1 : 0;
4516
			$this->special_code			= $objp->special_code;
4517
			$this->total_ht				= $objp->total_ht;
4518
			$this->total_tva			= $objp->total_tva;
4519
			$this->total_localtax1		= $objp->total_localtax1;
4520
			$this->total_localtax2		= $objp->total_localtax2;
4521
			$this->total_ttc			= $objp->total_ttc;
4522
			$this->fk_code_ventilation	= $objp->fk_code_ventilation;
4523
			$this->rang					= $objp->rang;
4524
			$this->fk_fournprice		= $objp->fk_fournprice;
4525
			$marginInfos				= getMarginInfos($objp->subprice, $objp->remise_percent, $objp->tva_tx, $objp->localtax1_tx, $objp->localtax2_tx, $this->fk_fournprice, $objp->pa_ht);
4526
			$this->pa_ht				= $marginInfos[0];
4527
			$this->marge_tx				= $marginInfos[1];
4528
			$this->marque_tx			= $marginInfos[2];
4529
4530
			$this->ref					= $objp->product_ref;      // deprecated
4531
			$this->product_ref			= $objp->product_ref;
4532
			$this->libelle				= $objp->product_libelle;  // deprecated
4533
			$this->product_label		= $objp->product_libelle;
4534
			$this->product_desc			= $objp->product_desc;
4535
			$this->fk_unit				= $objp->fk_unit;
4536
			$this->fk_user_modif		= $objp->fk_user_modif;
4537
			$this->fk_user_author		= $objp->fk_user_author;
4538
4539
			$this->situation_percent    = $objp->situation_percent;
4540
			$this->fk_prev_id           = $objp->fk_prev_id;
4541
4542
			$this->multicurrency_subprice = $objp->multicurrency_subprice;
4543
			$this->multicurrency_total_ht = $objp->multicurrency_total_ht;
4544
			$this->multicurrency_total_tva= $objp->multicurrency_total_tva;
4545
			$this->multicurrency_total_ttc= $objp->multicurrency_total_ttc;
4546
4547
			$this->db->free($result);
4548
4549
			return 1;
4550
		}
4551
		else
4552
		{
4553
		    $this->error = $this->db->lasterror();
4554
			return -1;
4555
		}
4556
	}
4557
4558
	/**
4559
	 *	Insert line into database
4560
	 *
4561
	 *	@param      int		$notrigger		                 1 no triggers
4562
	 *  @param      int     $noerrorifdiscountalreadylinked  1=Do not make error if lines is linked to a discount and discount already linked to another
4563
	 *	@return		int						                 <0 if KO, >0 if OK
4564
	 */
4565
    public function insert($notrigger = 0, $noerrorifdiscountalreadylinked = 0)
4566
	{
4567
		global $langs,$user,$conf;
4568
4569
		$error=0;
4570
4571
        $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'.
4572
4573
        dol_syslog(get_class($this)."::insert rang=".$this->rang, LOG_DEBUG);
4574
4575
		// Clean parameters
4576
		$this->desc=trim($this->desc);
4577
		if (empty($this->tva_tx)) $this->tva_tx=0;
4578
		if (empty($this->localtax1_tx)) $this->localtax1_tx=0;
4579
		if (empty($this->localtax2_tx)) $this->localtax2_tx=0;
4580
		if (empty($this->localtax1_type)) $this->localtax1_type=0;
4581
		if (empty($this->localtax2_type)) $this->localtax2_type=0;
4582
		if (empty($this->total_localtax1)) $this->total_localtax1=0;
4583
		if (empty($this->total_localtax2)) $this->total_localtax2=0;
4584
		if (empty($this->rang)) $this->rang=0;
4585
		if (empty($this->remise_percent)) $this->remise_percent=0;
4586
		if (empty($this->info_bits)) $this->info_bits=0;
4587
		if (empty($this->subprice)) $this->subprice=0;
4588
		if (empty($this->special_code)) $this->special_code=0;
4589
		if (empty($this->fk_parent_line)) $this->fk_parent_line=0;
4590
		if (empty($this->fk_prev_id)) $this->fk_prev_id = 0;
4591
		if (! isset($this->situation_percent) || $this->situation_percent > 100 || (string) $this->situation_percent == '') $this->situation_percent = 100;
4592
4593
		if (empty($this->pa_ht)) $this->pa_ht=0;
4594
		if (empty($this->multicurrency_subprice)) $this->multicurrency_subprice=0;
4595
		if (empty($this->multicurrency_total_ht)) $this->multicurrency_total_ht=0;
4596
		if (empty($this->multicurrency_total_tva)) $this->multicurrency_total_tva=0;
4597
		if (empty($this->multicurrency_total_ttc)) $this->multicurrency_total_ttc=0;
4598
4599
		// if buy price not defined, define buyprice as configured in margin admin
4600
		if ($this->pa_ht == 0 && $pa_ht_isemptystring)
4601
		{
4602
			if (($result = $this->defineBuyPrice($this->subprice, $this->remise_percent, $this->fk_product)) < 0)
4603
			{
4604
				return $result;
4605
			}
4606
			else
4607
			{
4608
				$this->pa_ht = $result;
4609
			}
4610
		}
4611
4612
		// Check parameters
4613
		if ($this->product_type < 0)
4614
		{
4615
			$this->error='ErrorProductTypeMustBe0orMore';
4616
			return -1;
4617
		}
4618
		if (! empty($this->fk_product))
4619
		{
4620
			// Check product exists
4621
			$result=Product::isExistingObject('product', $this->fk_product);
4622
			if ($result <= 0)
4623
			{
4624
				$this->error='ErrorProductIdDoesNotExists';
4625
				dol_syslog(get_class($this)."::insert Error ".$this->error, LOG_ERR);
4626
				return -1;
4627
			}
4628
		}
4629
4630
		$this->db->begin();
4631
4632
		// Insertion dans base de la ligne
4633
		$sql = 'INSERT INTO '.MAIN_DB_PREFIX.'facturedet';
4634
		$sql.= ' (fk_facture, fk_parent_line, label, description, qty,';
4635
		$sql.= ' vat_src_code, tva_tx, localtax1_tx, localtax2_tx, localtax1_type, localtax2_type,';
4636
		$sql.= ' fk_product, product_type, remise_percent, subprice, fk_remise_except,';
4637
		$sql.= ' date_start, date_end, fk_code_ventilation, ';
4638
		$sql.= ' rang, special_code, fk_product_fournisseur_price, buy_price_ht,';
4639
		$sql.= ' info_bits, total_ht, total_tva, total_ttc, total_localtax1, total_localtax2,';
4640
		$sql.= ' situation_percent, fk_prev_id,';
4641
		$sql.= ' fk_unit, fk_user_author, fk_user_modif,';
4642
		$sql.= ' fk_multicurrency, multicurrency_code, multicurrency_subprice, multicurrency_total_ht, multicurrency_total_tva, multicurrency_total_ttc';
4643
		$sql.= ')';
4644
		$sql.= " VALUES (".$this->fk_facture.",";
4645
		$sql.= " ".($this->fk_parent_line>0 ? $this->fk_parent_line:"null").",";
4646
		$sql.= " ".(! empty($this->label)?"'".$this->db->escape($this->label)."'":"null").",";
4647
		$sql.= " '".$this->db->escape($this->desc)."',";
4648
		$sql.= " ".price2num($this->qty).",";
4649
        $sql.= " ".(empty($this->vat_src_code)?"''":"'".$this->db->escape($this->vat_src_code)."'").",";
4650
		$sql.= " ".price2num($this->tva_tx).",";
4651
		$sql.= " ".price2num($this->localtax1_tx).",";
4652
		$sql.= " ".price2num($this->localtax2_tx).",";
4653
		$sql.= " '".$this->db->escape($this->localtax1_type)."',";
4654
		$sql.= " '".$this->db->escape($this->localtax2_type)."',";
4655
		$sql.= ' '.(! empty($this->fk_product)?$this->fk_product:"null").',';
4656
		$sql.= " ".((int) $this->product_type).",";
4657
		$sql.= " ".price2num($this->remise_percent).",";
4658
		$sql.= " ".price2num($this->subprice).",";
4659
		$sql.= ' '.(! empty($this->fk_remise_except)?$this->fk_remise_except:"null").',';
4660
		$sql.= " ".(! empty($this->date_start)?"'".$this->db->idate($this->date_start)."'":"null").",";
4661
		$sql.= " ".(! empty($this->date_end)?"'".$this->db->idate($this->date_end)."'":"null").",";
4662
		$sql.= ' '.$this->fk_code_ventilation.',';
4663
		$sql.= ' '.$this->rang.',';
4664
		$sql.= ' '.$this->special_code.',';
4665
		$sql.= ' '.(! empty($this->fk_fournprice)?$this->fk_fournprice:"null").',';
4666
		$sql.= ' '.price2num($this->pa_ht).',';
4667
		$sql.= " '".$this->db->escape($this->info_bits)."',";
4668
		$sql.= " ".price2num($this->total_ht).",";
4669
		$sql.= " ".price2num($this->total_tva).",";
4670
		$sql.= " ".price2num($this->total_ttc).",";
4671
		$sql.= " ".price2num($this->total_localtax1).",";
4672
		$sql.= " ".price2num($this->total_localtax2);
4673
		$sql.= ", " . $this->situation_percent;
4674
		$sql.= ", " . (!empty($this->fk_prev_id)?$this->fk_prev_id:"null");
4675
		$sql.= ", ".(!$this->fk_unit ? 'NULL' : $this->fk_unit);
4676
		$sql.= ", ".$user->id;
4677
		$sql.= ", ".$user->id;
4678
		$sql.= ", ".(int) $this->fk_multicurrency;
4679
		$sql.= ", '".$this->db->escape($this->multicurrency_code)."'";
4680
		$sql.= ", ".price2num($this->multicurrency_subprice);
4681
		$sql.= ", ".price2num($this->multicurrency_total_ht);
4682
		$sql.= ", ".price2num($this->multicurrency_total_tva);
4683
		$sql.= ", ".price2num($this->multicurrency_total_ttc);
4684
		$sql.= ')';
4685
4686
		dol_syslog(get_class($this)."::insert", LOG_DEBUG);
4687
		$resql=$this->db->query($sql);
4688
		if ($resql)
4689
		{
4690
			$this->id=$this->db->last_insert_id(MAIN_DB_PREFIX.'facturedet');
4691
			$this->rowid=$this->id;	// For backward compatibility
4692
4693
            if (empty($conf->global->MAIN_EXTRAFIELDS_DISABLED)) // For avoid conflicts if trigger used
4694
            {
4695
            	$result=$this->insertExtraFields();
4696
            	if ($result < 0)
4697
            	{
4698
            		$error++;
4699
            	}
4700
            }
4701
4702
			// Si fk_remise_except defini, on lie la remise a la facture
4703
			// ce qui la flague comme "consommee".
4704
			if ($this->fk_remise_except)
4705
			{
4706
				$discount=new DiscountAbsolute($this->db);
4707
				$result=$discount->fetch($this->fk_remise_except);
4708
				if ($result >= 0)
4709
				{
4710
					// Check if discount was found
4711
					if ($result > 0)
4712
					{
4713
					    // Check if discount not already affected to another invoice
4714
						if ($discount->fk_facture_line > 0)
4715
						{
4716
						    if (empty($noerrorifdiscountalreadylinked))
4717
						    {
4718
    							$this->error=$langs->trans("ErrorDiscountAlreadyUsed", $discount->id);
4719
    							dol_syslog(get_class($this)."::insert Error ".$this->error, LOG_ERR);
4720
    							$this->db->rollback();
4721
    							return -3;
4722
						    }
4723
						}
4724
						else
4725
						{
4726
							$result=$discount->link_to_invoice($this->rowid, 0);
4727
							if ($result < 0)
4728
							{
4729
								$this->error=$discount->error;
4730
								dol_syslog(get_class($this)."::insert Error ".$this->error, LOG_ERR);
4731
								$this->db->rollback();
4732
								return -3;
4733
							}
4734
						}
4735
					}
4736
					else
4737
					{
4738
						$this->error=$langs->trans("ErrorADiscountThatHasBeenRemovedIsIncluded");
4739
						dol_syslog(get_class($this)."::insert Error ".$this->error, LOG_ERR);
4740
						$this->db->rollback();
4741
						return -3;
4742
					}
4743
				}
4744
				else
4745
				{
4746
					$this->error=$discount->error;
4747
					dol_syslog(get_class($this)."::insert Error ".$this->error, LOG_ERR);
4748
					$this->db->rollback();
4749
					return -3;
4750
				}
4751
			}
4752
4753
			if (! $notrigger)
4754
			{
4755
                // Call trigger
4756
                $result=$this->call_trigger('LINEBILL_INSERT', $user);
4757
                if ($result < 0)
4758
                {
4759
					$this->db->rollback();
4760
					return -2;
4761
				}
4762
                // End call triggers
4763
			}
4764
4765
			$this->db->commit();
4766
			return $this->id;
4767
		}
4768
		else
4769
		{
4770
			$this->error=$this->db->lasterror();
4771
			$this->db->rollback();
4772
			return -2;
4773
		}
4774
	}
4775
4776
	/**
4777
	 *	Update line into database
4778
	 *
4779
	 *	@param		User	$user		User object
4780
	 *	@param		int		$notrigger	Disable triggers
4781
	 *	@return		int					<0 if KO, >0 if OK
4782
	 */
4783
    public function update($user = '', $notrigger = 0)
4784
	{
4785
		global $user,$conf;
4786
4787
		$error=0;
4788
4789
		$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'.
4790
4791
		// Clean parameters
4792
		$this->desc=trim($this->desc);
4793
		if (empty($this->tva_tx)) $this->tva_tx=0;
4794
		if (empty($this->localtax1_tx)) $this->localtax1_tx=0;
4795
		if (empty($this->localtax2_tx)) $this->localtax2_tx=0;
4796
		if (empty($this->localtax1_type)) $this->localtax1_type=0;
4797
		if (empty($this->localtax2_type)) $this->localtax2_type=0;
4798
		if (empty($this->total_localtax1)) $this->total_localtax1=0;
4799
		if (empty($this->total_localtax2)) $this->total_localtax2=0;
4800
		if (empty($this->remise_percent)) $this->remise_percent=0;
4801
		if (empty($this->info_bits)) $this->info_bits=0;
4802
		if (empty($this->special_code)) $this->special_code=0;
4803
		if (empty($this->product_type)) $this->product_type=0;
4804
		if (empty($this->fk_parent_line)) $this->fk_parent_line=0;
4805
		if (! isset($this->situation_percent) || $this->situation_percent > 100 || (string) $this->situation_percent == '') $this->situation_percent = 100;
4806
		if (empty($this->pa_ht)) $this->pa_ht=0;
4807
4808
		if (empty($this->multicurrency_subprice)) $this->multicurrency_subprice=0;
4809
		if (empty($this->multicurrency_total_ht)) $this->multicurrency_total_ht=0;
4810
		if (empty($this->multicurrency_total_tva)) $this->multicurrency_total_tva=0;
4811
		if (empty($this->multicurrency_total_ttc)) $this->multicurrency_total_ttc=0;
4812
4813
		// Check parameters
4814
		if ($this->product_type < 0) return -1;
4815
4816
		// if buy price not defined, define buyprice as configured in margin admin
4817
		if ($this->pa_ht == 0 && $pa_ht_isemptystring)
4818
		{
4819
			if (($result = $this->defineBuyPrice($this->subprice, $this->remise_percent, $this->fk_product)) < 0)
4820
			{
4821
				return $result;
4822
			}
4823
			else
4824
			{
4825
				$this->pa_ht = $result;
4826
			}
4827
		}
4828
4829
		$this->db->begin();
4830
4831
        // Mise a jour ligne en base
4832
        $sql = "UPDATE ".MAIN_DB_PREFIX."facturedet SET";
4833
        $sql.= " description='".$this->db->escape($this->desc)."'";
4834
        $sql.= ", label=".(! empty($this->label)?"'".$this->db->escape($this->label)."'":"null");
4835
        $sql.= ", subprice=".price2num($this->subprice)."";
4836
        $sql.= ", remise_percent=".price2num($this->remise_percent)."";
4837
        if ($this->fk_remise_except) $sql.= ", fk_remise_except=".$this->fk_remise_except;
4838
        else $sql.= ", fk_remise_except=null";
4839
		$sql.= ", vat_src_code = '".(empty($this->vat_src_code)?'':$this->db->escape($this->vat_src_code))."'";
4840
        $sql.= ", tva_tx=".price2num($this->tva_tx)."";
4841
        $sql.= ", localtax1_tx=".price2num($this->localtax1_tx)."";
4842
        $sql.= ", localtax2_tx=".price2num($this->localtax2_tx)."";
4843
		$sql.= ", localtax1_type='".$this->db->escape($this->localtax1_type)."'";
4844
		$sql.= ", localtax2_type='".$this->db->escape($this->localtax2_type)."'";
4845
        $sql.= ", qty=".price2num($this->qty);
4846
        $sql.= ", date_start=".(! empty($this->date_start)?"'".$this->db->idate($this->date_start)."'":"null");
4847
        $sql.= ", date_end=".(! empty($this->date_end)?"'".$this->db->idate($this->date_end)."'":"null");
4848
        $sql.= ", product_type=".$this->product_type;
4849
        $sql.= ", info_bits='".$this->db->escape($this->info_bits)."'";
4850
        $sql.= ", special_code='".$this->db->escape($this->special_code)."'";
4851
        if (empty($this->skip_update_total))
4852
        {
4853
        	$sql.= ", total_ht=".price2num($this->total_ht);
4854
        	$sql.= ", total_tva=".price2num($this->total_tva);
4855
        	$sql.= ", total_ttc=".price2num($this->total_ttc);
4856
        	$sql.= ", total_localtax1=".price2num($this->total_localtax1);
4857
        	$sql.= ", total_localtax2=".price2num($this->total_localtax2);
4858
        }
4859
		$sql.= ", fk_product_fournisseur_price=".(! empty($this->fk_fournprice)?"'".$this->db->escape($this->fk_fournprice)."'":"null");
4860
		$sql.= ", buy_price_ht='".price2num($this->pa_ht)."'";
4861
		$sql.= ", fk_parent_line=".($this->fk_parent_line>0?$this->fk_parent_line:"null");
4862
		if (! empty($this->rang)) $sql.= ", rang=".$this->rang;
4863
		$sql.= ", situation_percent=" . $this->situation_percent;
4864
		$sql.= ", fk_unit=".(!$this->fk_unit ? 'NULL' : $this->fk_unit);
4865
		$sql.= ", fk_user_modif =".$user->id;
4866
4867
		// Multicurrency
4868
		$sql.= ", multicurrency_subprice=".price2num($this->multicurrency_subprice)."";
4869
        $sql.= ", multicurrency_total_ht=".price2num($this->multicurrency_total_ht)."";
4870
        $sql.= ", multicurrency_total_tva=".price2num($this->multicurrency_total_tva)."";
4871
        $sql.= ", multicurrency_total_ttc=".price2num($this->multicurrency_total_ttc)."";
4872
4873
		$sql.= " WHERE rowid = ".$this->rowid;
4874
4875
		dol_syslog(get_class($this)."::update", LOG_DEBUG);
4876
		$resql=$this->db->query($sql);
4877
		if ($resql)
4878
		{
4879
        	if (empty($conf->global->MAIN_EXTRAFIELDS_DISABLED)) // For avoid conflicts if trigger used
4880
        	{
4881
        		$this->id=$this->rowid;
4882
        		$result=$this->insertExtraFields();
4883
        		if ($result < 0)
4884
        		{
4885
        			$error++;
4886
        		}
4887
        	}
4888
4889
			if (! $error && ! $notrigger)
4890
			{
4891
                // Call trigger
4892
                $result=$this->call_trigger('LINEBILL_UPDATE', $user);
4893
                if ($result < 0)
4894
 				{
4895
					$this->db->rollback();
4896
					return -2;
4897
				}
4898
                // End call triggers
4899
			}
4900
			$this->db->commit();
4901
			return 1;
4902
		}
4903
		else
4904
		{
4905
			$this->error=$this->db->error();
4906
			$this->db->rollback();
4907
			return -2;
4908
		}
4909
	}
4910
4911
	/**
4912
	 * 	Delete line in database
4913
	 *  TODO Add param User $user and notrigger (see skeleton)
4914
     *
4915
	 *	@return	    int		           <0 if KO, >0 if OK
4916
	 */
4917
    public function delete()
4918
	{
4919
		global $user;
4920
4921
		$this->db->begin();
4922
4923
		// Call trigger
4924
		$result=$this->call_trigger('LINEBILL_DELETE', $user);
4925
		if ($result < 0)
4926
		{
4927
			$this->db->rollback();
4928
			return -1;
4929
		}
4930
		// End call triggers
4931
4932
4933
		$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

4933
		$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...
4934
		dol_syslog(get_class($this)."::delete", LOG_DEBUG);
4935
		if ($this->db->query($sql) )
4936
		{
4937
			$this->db->commit();
4938
			return 1;
4939
		}
4940
		else
4941
		{
4942
			$this->error=$this->db->error()." sql=".$sql;
4943
			$this->db->rollback();
4944
			return -1;
4945
		}
4946
	}
4947
4948
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4949
	/**
4950
     *	Update DB line fields total_xxx
4951
	 *	Used by migration
4952
	 *
4953
	 *	@return		int		<0 if KO, >0 if OK
4954
	 */
4955
    public function update_total()
4956
	{
4957
        // phpcs:enable
4958
		$this->db->begin();
4959
		dol_syslog(get_class($this)."::update_total", LOG_DEBUG);
4960
4961
		// Clean parameters
4962
		if (empty($this->total_localtax1)) $this->total_localtax1=0;
4963
		if (empty($this->total_localtax2)) $this->total_localtax2=0;
4964
4965
		// Mise a jour ligne en base
4966
		$sql = "UPDATE ".MAIN_DB_PREFIX."facturedet SET";
4967
		$sql.= " total_ht=".price2num($this->total_ht)."";
4968
		$sql.= ",total_tva=".price2num($this->total_tva)."";
4969
		$sql.= ",total_localtax1=".price2num($this->total_localtax1)."";
4970
		$sql.= ",total_localtax2=".price2num($this->total_localtax2)."";
4971
		$sql.= ",total_ttc=".price2num($this->total_ttc)."";
4972
		$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

4972
		$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...
4973
4974
		dol_syslog(get_class($this)."::update_total", LOG_DEBUG);
4975
4976
		$resql=$this->db->query($sql);
4977
		if ($resql)
4978
		{
4979
			$this->db->commit();
4980
			return 1;
4981
		}
4982
		else
4983
		{
4984
			$this->error=$this->db->error();
4985
			$this->db->rollback();
4986
			return -2;
4987
		}
4988
	}
4989
4990
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4991
	/**
4992
	 * Returns situation_percent of the previous line.
4993
	 * Warning: If invoice is a replacement invoice, this->fk_prev_id is id of the replaced line.
4994
	 *
4995
	 * @param  int     $invoiceid      Invoice id
4996
	 * @param  bool    $include_credit_note		Include credit note or not
4997
	 * @return int                     >= 0
4998
	 */
4999
    public function get_prev_progress($invoiceid, $include_credit_note = true)
5000
	{
5001
        // phpcs:enable
5002
		if (is_null($this->fk_prev_id) || empty($this->fk_prev_id) || $this->fk_prev_id == "") {
5003
			return 0;
5004
		} else {
5005
		    // If invoice is not a situation invoice, this->fk_prev_id is used for something else
5006
            $tmpinvoice=new Facture($this->db);
5007
            $tmpinvoice->fetch($invoiceid);
5008
            if ($tmpinvoice->type != Facture::TYPE_SITUATION) return 0;
5009
5010
			$sql = 'SELECT situation_percent FROM ' . MAIN_DB_PREFIX . 'facturedet WHERE rowid=' . $this->fk_prev_id;
5011
			$resql = $this->db->query($sql);
5012
			if ($resql && $resql->num_rows > 0) {
5013
				$res = $this->db->fetch_array($resql);
5014
5015
				$returnPercent = floatval($res['situation_percent']);
5016
5017
				if($include_credit_note) {
5018
5019
				    $sql = 'SELECT fd.situation_percent FROM ' . MAIN_DB_PREFIX . 'facturedet fd';
5020
				    $sql.= ' JOIN ' . MAIN_DB_PREFIX . 'facture f ON (f.rowid = fd.fk_facture) ';
5021
				    $sql.= ' WHERE fd.fk_prev_id =' . $this->fk_prev_id;
5022
				    $sql.= ' AND f.situation_cycle_ref = '.$tmpinvoice->situation_cycle_ref; // Prevent cycle outed
5023
				    $sql.= ' AND f.type = '.Facture::TYPE_CREDIT_NOTE;
5024
5025
				    $res = $this->db->query($sql);
5026
				    if($res) {
5027
				        while($obj = $this->db->fetch_object($res)) {
5028
				            $returnPercent = $returnPercent + floatval($obj->situation_percent);
5029
				        }
5030
				    }
5031
				}
5032
5033
				return $returnPercent;
5034
			} else {
5035
				$this->error = $this->db->error();
5036
				dol_syslog(get_class($this) . "::select Error " . $this->error, LOG_ERR);
5037
				$this->db->rollback();
5038
				return -1;
5039
			}
5040
		}
5041
	}
5042
}
5043