Passed
Push — master ( 0f9140...c4489d )
by Alxarafe
22:27
created

Facture   F

Complexity

Total Complexity 664

Size/Duplication

Total Lines 4269
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 2212
dl 0
loc 4269
rs 0.8
c 0
b 0
f 0
wmc 664

48 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 3 1
C set_draft() 0 78 13
A getIdShippingContact() 0 3 1
F updateline() 0 181 37
B insert_discount() 0 92 7
F create() 0 554 125
F validate() 0 248 50
C liste_array() 0 58 15
B list_qualified_avoir_invoices() 0 62 9
A is_first() 0 4 1
C demande_prelevement() 0 100 12
B set_canceled() 0 50 6
A info() 0 35 5
B deleteline() 0 57 6
D delete() 0 161 30
C getNextNumRef() 0 78 15
D getNomUrl() 0 96 43
A list_replacable_invoices() 0 37 4
B fetch_lines() 0 102 3
A demande_prelevement_delete() 0 15 2
C initAsSpecimen() 0 148 10
B set_remise_absolue() 0 55 11
A checkProgressLine() 0 18 3
B fetchPreviousNextSituationInvoice() 0 27 8
D update() 0 101 64
B createFromOrder() 0 100 9
A hasDelay() 0 10 2
A get_prev_sits() 0 25 5
B setFinal() 0 38 8
A newCycle() 0 20 3
B set_paid() 0 46 8
F fetch() 0 143 18
C createFromClone() 0 102 17
A getDirectExternalLink() 0 16 1
F addline() 0 198 45
A getIdBillingContact() 0 3 1
A update_percent() 0 22 2
A replaceThirdparty() 0 7 1
A generateDocument() 0 23 5
A is_last_in_cycle() 0 21 5
A load_state_board() 0 35 5
A getLinesArray() 0 3 1
B load_board() 0 54 7
B set_remise() 0 52 10
B createFromCurrent() 0 82 10
B set_ref_client() 0 54 10
A set_unpaid() 0 36 4
A updatePriceNextInvoice() 0 31 6

How to fix   Complexity   

Complex Class

Complex classes like Facture often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Facture, and based on these observations, apply Extract Interface, too.

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

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

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

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

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
617
					{
618
						$newinvoiceline->date_end = $previousdaynextdatewhen;
619
					}
620
621
					if ($result >= 0)
622
					{
623
						// Reset fk_parent_line for no child products and special product
624
						if (($newinvoiceline->product_type != 9 && empty($newinvoiceline->fk_parent_line)) || $newinvoiceline->product_type == 9) {
625
							$fk_parent_line = 0;
626
						}
627
628
						$newinvoiceline->fk_parent_line=$fk_parent_line;
629
630
						if($this->type === Facture::TYPE_REPLACEMENT && $newinvoiceline->fk_remise_except){
631
                            $discount = new DiscountAbsolute($this->db);
632
                            $discount->fetch($newinvoiceline->fk_remise_except);
633
634
						    $discountId = $soc->set_remise_except($discount->amount_ht, $user, $discount->description, $discount->tva_tx);
635
						    $newinvoiceline->fk_remise_except = $discountId;
636
                        }
637
638
						$result=$newinvoiceline->insert();
639
640
						// Defined the new fk_parent_line
641
						if ($result > 0 && $newinvoiceline->product_type == 9) {
642
							$fk_parent_line = $result;
643
						}
644
					}
645
					if ($result < 0)
646
					{
647
						$this->error=$newinvoiceline->error;
648
						$error++;
649
						break;
650
					}
651
				}
652
			}
653
			elseif (! $error && empty($this->fac_rec)) 		// If this->lines is an array of invoice line arrays
654
			{
655
				$fk_parent_line = 0;
656
657
				dol_syslog("There is ".count($this->lines)." lines that are array lines");
658
659
				foreach ($this->lines as $i => $val)
660
				{
661
                	$line = $this->lines[$i];
662
663
                	// Test and convert into object this->lines[$i]. When coming from REST API, we may still have an array
664
				    //if (! is_object($line)) $line=json_decode(json_encode($line), false);  // convert recursively array into object.
665
                	if (! is_object($line)) $line = (object) $line;
666
667
				    if ($result >= 0)
668
					{
669
						// Reset fk_parent_line for no child products and special product
670
						if (($line->product_type != 9 && empty($line->fk_parent_line)) || $line->product_type == 9) {
671
							$fk_parent_line = 0;
672
						}
673
674
						// Complete vat rate with code
675
						$vatrate = $line->tva_tx;
676
						if ($line->vat_src_code && ! preg_match('/\(.*\)/', $vatrate)) $vatrate.=' ('.$line->vat_src_code.')';
677
678
						$result = $this->addline(
679
							$line->desc,
680
							$line->subprice,
681
							$line->qty,
682
							$vatrate,
683
							$line->localtax1_tx,
684
							$line->localtax2_tx,
685
							$line->fk_product,
686
							$line->remise_percent,
687
							$line->date_start,
688
							$line->date_end,
689
							$line->fk_code_ventilation,
690
							$line->info_bits,
691
							$line->fk_remise_except,
692
							'HT',
693
							0,
694
							$line->product_type,
695
							$line->rang,
696
							$line->special_code,
697
                            $this->element,
698
                            $line->id,
699
							$fk_parent_line,
700
							$line->fk_fournprice,
701
							$line->pa_ht,
702
							$line->label,
703
							$line->array_options,
704
							$line->situation_percent,
705
							$line->fk_prev_id,
706
							$line->fk_unit,
707
							$line->pu_ht_devise
708
						);
709
						if ($result < 0)
710
						{
711
							$this->error=$this->db->lasterror();
712
							dol_print_error($this->db);
713
							$this->db->rollback();
714
							return -1;
715
						}
716
717
						// Defined the new fk_parent_line
718
						if ($result > 0 && $line->product_type == 9) {
719
							$fk_parent_line = $result;
720
						}
721
					}
722
				}
723
			}
724
725
			/*
726
			 * Insert lines of predefined invoices
727
			 */
728
			if (! $error && $this->fac_rec > 0)
729
			{
730
				foreach ($_facrec->lines as $i => $val)
731
				{
732
					if ($_facrec->lines[$i]->fk_product)
733
					{
734
						$prod = new Product($this->db);
735
						$res=$prod->fetch($_facrec->lines[$i]->fk_product);
736
					}
737
738
					// For line from template invoice, we use data from template invoice
739
					/*
740
					$tva_tx = get_default_tva($mysoc,$soc,$prod->id);
741
					$tva_npr = get_default_npr($mysoc,$soc,$prod->id);
742
					if (empty($tva_tx)) $tva_npr=0;
743
					$localtax1_tx=get_localtax($tva_tx,1,$soc,$mysoc,$tva_npr);
744
					$localtax2_tx=get_localtax($tva_tx,2,$soc,$mysoc,$tva_npr);
745
					*/
746
					$tva_tx = $_facrec->lines[$i]->tva_tx.($_facrec->lines[$i]->vat_src_code ? '('.$_facrec->lines[$i]->vat_src_code.')' : '');
747
					$tva_npr = $_facrec->lines[$i]->info_bits;
748
					if (empty($tva_tx)) $tva_npr=0;
749
					$localtax1_tx = $_facrec->lines[$i]->localtax1_tx;
750
					$localtax2_tx = $_facrec->lines[$i]->localtax2_tx;
751
752
					$result_insert = $this->addline(
753
						$_facrec->lines[$i]->desc,
754
						$_facrec->lines[$i]->subprice,
755
						$_facrec->lines[$i]->qty,
756
						$tva_tx,
757
						$localtax1_tx,
758
						$localtax2_tx,
759
						$_facrec->lines[$i]->fk_product,
760
						$_facrec->lines[$i]->remise_percent,
761
						($_facrec->lines[$i]->date_start_fill == 1 && $originaldatewhen)?$originaldatewhen:'',
762
						($_facrec->lines[$i]->date_end_fill == 1 && $previousdaynextdatewhen)?$previousdaynextdatewhen:'',
0 ignored issues
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...
763
						0,
764
						$tva_npr,
765
						'',
766
						'HT',
767
						0,
768
						$_facrec->lines[$i]->product_type,
769
						$_facrec->lines[$i]->rang,
770
						$_facrec->lines[$i]->special_code,
771
						'',
772
						0,
773
						0,
774
						null,
775
						0,
776
						$_facrec->lines[$i]->label,
777
						empty($_facrec->lines[$i]->array_options)?null:$_facrec->lines[$i]->array_options,
778
						$_facrec->lines[$i]->situation_percent,
779
						'',
780
						$_facrec->lines[$i]->fk_unit,
781
						$_facrec->lines[$i]->pu_ht_devise
782
					);
783
784
					if ( $result_insert < 0)
785
					{
786
						$error++;
787
						$this->error=$this->db->error();
788
						break;
789
					}
790
				}
791
			}
792
793
			if (! $error)
794
			{
795
796
				$result=$this->update_price(1);
797
				if ($result > 0)
798
				{
799
					$action='create';
800
801
					// Actions on extra fields
802
					if (! $error)
803
					{
804
					    $result=$this->insertExtraFields();
805
					    if ($result < 0) $error++;
806
					}
807
808
			        if (! $error && ! $notrigger)
809
			        {
810
			           // Call trigger
811
			           $result=$this->call_trigger('BILL_CREATE',$user);
812
			           if ($result < 0) $error++;
813
			           // End call triggers
814
			        }
815
816
					if (! $error)
817
					{
818
						$this->db->commit();
819
						return $this->id;
820
					}
821
					else
822
					{
823
						$this->db->rollback();
824
						return -4;
825
					}
826
				}
827
				else
828
				{
829
					$this->error=$langs->trans('FailedToUpdatePrice');
830
					$this->db->rollback();
831
					return -3;
832
				}
833
			}
834
			else
835
			{
836
				dol_syslog(get_class($this)."::create error ".$this->error, LOG_ERR);
837
				$this->db->rollback();
838
				return -2;
839
			}
840
		}
841
		else
842
		{
843
			$this->error=$this->db->error();
844
			$this->db->rollback();
845
			return -1;
846
		}
847
	}
848
849
850
	/**
851
	 *	Create a new invoice in database from current invoice
852
	 *
853
	 *	@param      User	$user    		Object user that ask creation
854
	 *	@param		int		$invertdetail	Reverse sign of amounts for lines
855
	 *	@return		int						<0 if KO, >0 if OK
856
	 */
857
	function createFromCurrent(User $user, $invertdetail=0)
858
	{
859
		global $conf;
860
861
		// Charge facture source
862
		$facture=new Facture($this->db);
863
864
		// Retreive all extrafield
865
		// fetch optionals attributes and labels
866
		$this->fetch_optionals();
867
868
        if(!empty($this->array_options)){
869
                    $facture->array_options = $this->array_options;
870
        }
871
872
        foreach($this->lines as &$line){
873
                    $line->fetch_optionals();//fetch extrafields
874
        }
875
876
		$facture->fk_facture_source = $this->fk_facture_source;
877
		$facture->type 			    = $this->type;
878
		$facture->socid 		    = $this->socid;
879
		$facture->date              = $this->date;
880
		$facture->date_pointoftax   = $this->date_pointoftax;
881
		$facture->note_public       = $this->note_public;
882
		$facture->note_private      = $this->note_private;
883
		$facture->ref_client        = $this->ref_client;
884
		$facture->modelpdf          = $this->modelpdf;
885
		$facture->fk_project        = $this->fk_project;
886
		$facture->cond_reglement_id = $this->cond_reglement_id;
887
		$facture->mode_reglement_id = $this->mode_reglement_id;
888
		$facture->remise_absolue    = $this->remise_absolue;
889
		$facture->remise_percent    = $this->remise_percent;
890
891
		$facture->origin                        = $this->origin;
892
		$facture->origin_id                     = $this->origin_id;
893
894
		$facture->lines		    	= $this->lines;	// Tableau des lignes de factures
895
		$facture->products		    = $this->lines;	// Tant que products encore utilise
896
		$facture->situation_counter = $this->situation_counter;
897
		$facture->situation_cycle_ref=$this->situation_cycle_ref;
898
		$facture->situation_final  = $this->situation_final;
899
900
		// Loop on each line of new invoice
901
		foreach($facture->lines as $i => $tmpline)
902
		{
903
			$facture->lines[$i]->fk_prev_id = $this->lines[$i]->rowid;
0 ignored issues
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

903
			$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...
904
			if ($invertdetail)
905
			{
906
				$facture->lines[$i]->subprice  = -$facture->lines[$i]->subprice;
907
				$facture->lines[$i]->total_ht  = -$facture->lines[$i]->total_ht;
908
				$facture->lines[$i]->total_tva = -$facture->lines[$i]->total_tva;
909
				$facture->lines[$i]->total_localtax1 = -$facture->lines[$i]->total_localtax1;
910
				$facture->lines[$i]->total_localtax2 = -$facture->lines[$i]->total_localtax2;
911
				$facture->lines[$i]->total_ttc = -$facture->lines[$i]->total_ttc;
912
			}
913
		}
914
915
		dol_syslog(get_class($this)."::createFromCurrent invertdetail=".$invertdetail." socid=".$this->socid." nboflines=".count($facture->lines));
916
917
		$facid = $facture->create($user);
918
		if ($facid <= 0)
919
		{
920
			$this->error=$facture->error;
921
			$this->errors=$facture->errors;
922
		}
923
		elseif ($this->type == self::TYPE_SITUATION && !empty($conf->global->INVOICE_USE_SITUATION))
924
		{
925
			$this->fetchObjectLinked('', '', $facture->id, 'facture');
926
927
			foreach ($this->linkedObjectsIds as $typeObject => $Tfk_object)
928
			{
929
				foreach ($Tfk_object as $fk_object)
930
				{
931
					$facture->add_object_linked($typeObject, $fk_object);
932
				}
933
			}
934
935
			$facture->add_object_linked('facture', $this->fk_facture_source);
936
		}
937
938
		return $facid;
939
	}
940
941
942
	/**
943
	 *		Load an object from its id and create a new one in database
944
	 *
945
	 *		@param		int				$socid			Id of thirdparty
946
	 * 	 	@return		int								New id of clone
947
	 */
948
	function createFromClone($socid=0)
949
	{
950
		global $user,$hookmanager;
951
952
		$error=0;
953
954
		$this->context['createfromclone'] = 'createfromclone';
955
956
		$this->db->begin();
957
958
		// get extrafields so they will be clone
959
		foreach($this->lines as $line)
960
			$line->fetch_optionals($line->rowid);
0 ignored issues
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

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

2430
	                        	/** @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...
2431
	                        }
2432
						}
2433
					}
2434
				}
2435
			}
2436
2437
			if (! $error && !$this->is_last_in_cycle())
2438
			{
2439
				if (! $this->updatePriceNextInvoice($langs))
2440
				{
2441
					$error++;
2442
				}
2443
			}
2444
2445
			// Set new ref and define current status
2446
			if (! $error)
2447
			{
2448
				$this->ref = $num;
2449
				$this->ref=$num;
2450
				$this->statut= self::STATUS_VALIDATED;
2451
				$this->brouillon=0;
2452
				$this->date_validation=$now;
2453
				$i = 0;
2454
2455
                if (!empty($conf->global->INVOICE_USE_SITUATION))
2456
                {
2457
                	$final = true;
2458
    				$nboflines = count($this->lines);
2459
    				while (($i < $nboflines) && $final) {
2460
    					$final = ($this->lines[$i]->situation_percent == 100);
2461
    					$i++;
2462
    				}
2463
2464
    				if (empty($final)) $this->situation_final = 0;
2465
    				else $this->situation_final = 1;
2466
2467
				$this->setFinal($user);
2468
                }
2469
			}
2470
		}
2471
		else
2472
		{
2473
			$error++;
2474
		}
2475
2476
		if (! $error)
2477
		{
2478
			$this->db->commit();
2479
			return 1;
2480
		}
2481
		else
2482
		{
2483
			$this->db->rollback();
2484
			return -1;
2485
		}
2486
	}
2487
2488
	/**
2489
	 * Update price of next invoice
2490
	 *
2491
	 * @param	Translate	$langs	Translate object
2492
	 * @return bool		false if KO, true if OK
2493
	 */
2494
	function updatePriceNextInvoice(&$langs)
2495
	{
2496
		foreach ($this->tab_next_situation_invoice as $next_invoice)
2497
		{
2498
			$is_last = $next_invoice->is_last_in_cycle();
2499
2500
			if ($next_invoice->brouillon && $is_last != 1)
2501
			{
2502
				$this->error = $langs->trans('updatePriceNextInvoiceErrorUpdateline', $next_invoice->ref);
2503
				return false;
2504
			}
2505
2506
			$next_invoice->brouillon = 1;
2507
			foreach ($next_invoice->lines as $line)
2508
			{
2509
				$result = $next_invoice->updateline($line->id, $line->desc, $line->subprice, $line->qty, $line->remise_percent,
2510
														$line->date_start, $line->date_end, $line->tva_tx, $line->localtax1_tx, $line->localtax2_tx, 'HT', $line->info_bits, $line->product_type,
2511
														$line->fk_parent_line, 0, $line->fk_fournprice, $line->pa_ht, $line->label, $line->special_code, $line->array_options, $line->situation_percent,
2512
														$line->fk_unit);
2513
2514
				if ($result < 0)
2515
				{
2516
					$this->error = $langs->trans('updatePriceNextInvoiceErrorUpdateline', $next_invoice->ref);
2517
					return false;
2518
				}
2519
			}
2520
2521
			break; // Only the next invoice and not each next invoice
2522
		}
2523
2524
		return true;
2525
	}
2526
2527
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
2528
	/**
2529
	 *	Set draft status
2530
	 *
2531
	 *	@param	User	$user			Object user that modify
2532
	 *	@param	int		$idwarehouse	Id warehouse to use for stock change.
2533
	 *	@return	int						<0 if KO, >0 if OK
2534
	 */
2535
	function set_draft($user,$idwarehouse=-1)
2536
	{
2537
        // phpcs:enable
2538
		global $conf,$langs;
2539
2540
		$error=0;
2541
2542
		if ($this->statut == self::STATUS_DRAFT)
2543
		{
2544
			dol_syslog(get_class($this)."::set_draft already draft status", LOG_WARNING);
2545
			return 0;
2546
		}
2547
2548
		$this->db->begin();
2549
2550
		$sql = "UPDATE ".MAIN_DB_PREFIX."facture";
2551
		$sql.= " SET fk_statut = ".self::STATUS_DRAFT;
2552
		$sql.= " WHERE rowid = ".$this->id;
2553
2554
		dol_syslog(get_class($this)."::set_draft", LOG_DEBUG);
2555
		$result=$this->db->query($sql);
2556
		if ($result)
2557
		{
2558
			// Si on decremente le produit principal et ses composants a la validation de facture, on réincrement
2559
			if ($this->type != self::TYPE_DEPOSIT && $result >= 0 && ! empty($conf->stock->enabled) && ! empty($conf->global->STOCK_CALCULATE_ON_BILL))
2560
			{
2561
				require_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
2562
				$langs->load("agenda");
2563
2564
				$num=count($this->lines);
2565
				for ($i = 0; $i < $num; $i++)
2566
				{
2567
					if ($this->lines[$i]->fk_product > 0)
2568
					{
2569
						$mouvP = new MouvementStock($this->db);
2570
						$mouvP->origin = &$this;
2571
						// We decrease stock for product
2572
						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));
2573
						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
2574
					}
2575
				}
2576
			}
2577
2578
			if ($error == 0)
2579
			{
2580
				$old_statut=$this->statut;
2581
				$this->brouillon = 1;
2582
				$this->statut = self::STATUS_DRAFT;
2583
	            // Call trigger
2584
	            $result=$this->call_trigger('BILL_UNVALIDATE',$user);
2585
	            if ($result < 0)
2586
				{
2587
					$error++;
2588
					$this->statut=$old_statut;
2589
					$this->brouillon=0;
2590
				}
2591
	            // End call triggers
2592
			} else {
2593
				$this->db->rollback();
2594
				return -1;
2595
			}
2596
2597
			if ($error == 0)
2598
			{
2599
				$this->db->commit();
2600
				return 1;
2601
			}
2602
			else
2603
			{
2604
				$this->db->rollback();
2605
				return -1;
2606
			}
2607
		}
2608
		else
2609
		{
2610
			$this->error=$this->db->error();
2611
			$this->db->rollback();
2612
			return -1;
2613
		}
2614
	}
2615
2616
2617
	/**
2618
	 * 		Add an invoice line into database (linked to product/service or not).
2619
	 * 		Les parametres sont deja cense etre juste et avec valeurs finales a l'appel
2620
	 *		de cette methode. Aussi, pour le taux tva, il doit deja avoir ete defini
2621
	 *		par l'appelant par la methode get_default_tva(societe_vendeuse,societe_acheteuse,produit)
2622
	 *		et le desc doit deja avoir la bonne valeur (a l'appelant de gerer le multilangue)
2623
	 *
2624
	 * 		@param    	string		$desc            	Description of line
2625
	 * 		@param    	double		$pu_ht              Unit price without tax (> 0 even for credit note)
2626
	 * 		@param    	double		$qty             	Quantity
2627
	 * 		@param    	double		$txtva           	Force Vat rate, -1 for auto (Can contain the vat_src_code too with syntax '9.9 (CODE)')
2628
	 * 		@param		double		$txlocaltax1		Local tax 1 rate (deprecated, use instead txtva with code inside)
2629
	 *  	@param		double		$txlocaltax2		Local tax 2 rate (deprecated, use instead txtva with code inside)
2630
	 *		@param    	int			$fk_product      	Id of predefined product/service
2631
	 * 		@param    	double		$remise_percent  	Percent of discount on line
2632
	 * 		@param    	int			$date_start      	Date start of service
2633
	 * 		@param    	int			$date_end        	Date end of service
2634
	 * 		@param    	int			$ventil          	Code of dispatching into accountancy
2635
	 * 		@param    	int			$info_bits			Bits de type de lignes
2636
	 *		@param    	int			$fk_remise_except	Id discount used
2637
	 *		@param		string		$price_base_type	'HT' or 'TTC'
2638
	 * 		@param    	double		$pu_ttc             Unit price with tax (> 0 even for credit note)
2639
	 * 		@param		int			$type				Type of line (0=product, 1=service). Not used if fk_product is defined, the type of product is used.
2640
	 *      @param      int			$rang               Position of line
2641
	 *      @param		int			$special_code		Special code (also used by externals modules!)
2642
	 *      @param		string		$origin				'order', ...
2643
	 *      @param		int			$origin_id			Id of origin object
2644
	 *      @param		int			$fk_parent_line		Id of parent line
2645
	 * 		@param		int			$fk_fournprice		Supplier price id (to calculate margin) or ''
2646
	 * 		@param		int			$pa_ht				Buying price of line (to calculate margin) or ''
2647
	 * 		@param		string		$label				Label of the line (deprecated, do not use)
2648
	 *		@param		array		$array_options		extrafields array
2649
	 *      @param      int         $situation_percent  Situation advance percentage
2650
	 *      @param      int         $fk_prev_id         Previous situation line id reference
2651
	 * 		@param 		string		$fk_unit 			Code of the unit to use. Null to use the default one
2652
	 * 		@param		double		$pu_ht_devise		Unit price in currency
2653
	 *    	@return    	int             				<0 if KO, Id of line if OK
2654
	 */
2655
	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)
2656
	{
2657
		// Deprecation warning
2658
		if ($label) {
2659
			dol_syslog(__METHOD__ . ": using line label is deprecated", LOG_WARNING);
2660
			//var_dump(debug_backtrace(false));exit;
2661
		}
2662
2663
		global $mysoc, $conf, $langs;
2664
2665
		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);
2666
		if (! empty($this->brouillon))
2667
		{
2668
			include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
2669
2670
			// Clean parameters
2671
			if (empty($remise_percent)) $remise_percent=0;
2672
			if (empty($qty)) $qty=0;
2673
			if (empty($info_bits)) $info_bits=0;
2674
			if (empty($rang)) $rang=0;
2675
			if (empty($ventil)) $ventil=0;
2676
			if (empty($txtva)) $txtva=0;
2677
			if (empty($txlocaltax1)) $txlocaltax1=0;
2678
			if (empty($txlocaltax2)) $txlocaltax2=0;
2679
			if (empty($fk_parent_line) || $fk_parent_line < 0) $fk_parent_line=0;
2680
			if (empty($fk_prev_id)) $fk_prev_id = 'null';
2681
			if (! isset($situation_percent) || $situation_percent > 100 || (string) $situation_percent == '') $situation_percent = 100;
2682
2683
			$remise_percent=price2num($remise_percent);
2684
			$qty=price2num($qty);
2685
			$pu_ht=price2num($pu_ht);
2686
			$pu_ht_devise=price2num($pu_ht_devise);
2687
			$pu_ttc=price2num($pu_ttc);
2688
			$pa_ht=price2num($pa_ht);
2689
			if (!preg_match('/\((.*)\)/', $txtva)) {
2690
				$txtva = price2num($txtva);               // $txtva can have format '5.0(XXX)' or '5'
2691
			}
2692
			$txlocaltax1=price2num($txlocaltax1);
2693
			$txlocaltax2=price2num($txlocaltax2);
2694
2695
			if ($price_base_type=='HT')
2696
			{
2697
				$pu=$pu_ht;
2698
			}
2699
			else
2700
			{
2701
				$pu=$pu_ttc;
2702
			}
2703
2704
			// Check parameters
2705
			if ($type < 0) return -1;
2706
2707
			$this->db->begin();
2708
2709
			$product_type=$type;
2710
			if (!empty($fk_product))
2711
			{
2712
				$product=new Product($this->db);
2713
				$result=$product->fetch($fk_product);
2714
				$product_type=$product->type;
2715
2716
				if (! empty($conf->global->STOCK_MUST_BE_ENOUGH_FOR_INVOICE) && $product_type == 0 && $product->stock_reel < $qty) {
2717
                    $langs->load("errors");
2718
				    $this->error=$langs->trans('ErrorStockIsNotEnoughToAddProductOnInvoice', $product->ref);
2719
					$this->db->rollback();
2720
					return -3;
2721
				}
2722
			}
2723
2724
			$localtaxes_type=getLocalTaxesFromRate($txtva, 0, $this->thirdparty, $mysoc);
2725
2726
			// Clean vat code
2727
			$vat_src_code='';
2728
			if (preg_match('/\((.*)\)/', $txtva, $reg))
2729
			{
2730
				$vat_src_code = $reg[1];
2731
				$txtva = preg_replace('/\s*\(.*\)/', '', $txtva);    // Remove code into vatrate.
2732
			}
2733
2734
			// Calcul du total TTC et de la TVA pour la ligne a partir de
2735
			// qty, pu, remise_percent et txtva
2736
			// TRES IMPORTANT: C'est au moment de l'insertion ligne qu'on doit stocker
2737
			// la part ht, tva et ttc, et ce au niveau de la ligne qui a son propre taux tva.
2738
2739
			$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);
2740
2741
			$total_ht  = $tabprice[0];
2742
			$total_tva = $tabprice[1];
2743
			$total_ttc = $tabprice[2];
2744
			$total_localtax1 = $tabprice[9];
2745
			$total_localtax2 = $tabprice[10];
2746
			$pu_ht = $tabprice[3];
2747
2748
			// MultiCurrency
2749
			$multicurrency_total_ht  = $tabprice[16];
2750
            $multicurrency_total_tva = $tabprice[17];
2751
            $multicurrency_total_ttc = $tabprice[18];
2752
			$pu_ht_devise = $tabprice[19];
2753
2754
			// Rank to use
2755
			$rangtouse = $rang;
2756
			if ($rangtouse == -1)
2757
			{
2758
				$rangmax = $this->line_max($fk_parent_line);
2759
				$rangtouse = $rangmax + 1;
2760
			}
2761
2762
			// Insert line
2763
			$this->line=new FactureLigne($this->db);
2764
2765
			$this->line->context = $this->context;
2766
2767
			$this->line->fk_facture=$this->id;
2768
			$this->line->label=$label;	// deprecated
2769
			$this->line->desc=$desc;
2770
2771
			$this->line->qty=            ($this->type==self::TYPE_CREDIT_NOTE?abs($qty):$qty);	    // For credit note, quantity is always positive and unit price negative
0 ignored issues
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...
2772
			$this->line->subprice=       ($this->type==self::TYPE_CREDIT_NOTE?-abs($pu_ht):$pu_ht); // For credit note, unit price always negative, always positive otherwise
2773
2774
			$this->line->vat_src_code=$vat_src_code;
2775
			$this->line->tva_tx=$txtva;
0 ignored issues
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...
2776
			$this->line->localtax1_tx=($total_localtax1?$localtaxes_type[1]:0);
2777
			$this->line->localtax2_tx=($total_localtax2?$localtaxes_type[3]:0);
2778
			$this->line->localtax1_type = $localtaxes_type[0];
2779
			$this->line->localtax2_type = $localtaxes_type[2];
2780
2781
			$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
2782
			$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
2783
			$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
2784
			$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
2785
			$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
2786
2787
			$this->line->fk_product=$fk_product;
2788
			$this->line->product_type=$product_type;
2789
			$this->line->remise_percent=$remise_percent;
0 ignored issues
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...
2790
			$this->line->date_start=$date_start;
2791
			$this->line->date_end=$date_end;
2792
			$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...
2793
			$this->line->rang=$rangtouse;
2794
			$this->line->info_bits=$info_bits;
2795
			$this->line->fk_remise_except=$fk_remise_except;
2796
2797
			$this->line->special_code=$special_code;
2798
			$this->line->fk_parent_line=$fk_parent_line;
2799
			$this->line->origin=$origin;
2800
			$this->line->origin_id=$origin_id;
2801
			$this->line->situation_percent = $situation_percent;
2802
			$this->line->fk_prev_id = $fk_prev_id;
0 ignored issues
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...
2803
			$this->line->fk_unit=$fk_unit;
2804
2805
			// infos marge
2806
			$this->line->fk_fournprice = $fk_fournprice;
2807
			$this->line->pa_ht = $pa_ht;
2808
2809
			// Multicurrency
2810
			$this->line->fk_multicurrency			= $this->fk_multicurrency;
2811
			$this->line->multicurrency_code			= $this->multicurrency_code;
2812
			$this->line->multicurrency_subprice		= $pu_ht_devise;
2813
			$this->line->multicurrency_total_ht 	= $multicurrency_total_ht;
2814
            $this->line->multicurrency_total_tva 	= $multicurrency_total_tva;
2815
            $this->line->multicurrency_total_ttc 	= $multicurrency_total_ttc;
2816
2817
			if (is_array($array_options) && count($array_options)>0) {
2818
				$this->line->array_options=$array_options;
2819
			}
2820
2821
			$result=$this->line->insert();
2822
			if ($result > 0)
2823
			{
2824
				// Reorder if child line
2825
				if (! empty($fk_parent_line)) $this->line_order(true,'DESC');
2826
2827
				// Mise a jour informations denormalisees au niveau de la facture meme
2828
				$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.
2829
2830
				if ($result > 0)
2831
				{
2832
					$this->db->commit();
2833
					return $this->line->id;
2834
				}
2835
				else
2836
				{
2837
					$this->error=$this->db->lasterror();
2838
					$this->db->rollback();
2839
					return -1;
2840
				}
2841
			}
2842
			else
2843
			{
2844
				$this->error=$this->line->error;
2845
				$this->db->rollback();
2846
				return -2;
2847
			}
2848
		}
2849
		else
2850
		{
2851
			dol_syslog(get_class($this)."::addline status of order must be Draft to allow use of ->addline()", LOG_ERR);
2852
			return -3;
2853
		}
2854
	}
2855
2856
	/**
2857
	 *  Update a detail line
2858
	 *
2859
	 *  @param     	int			$rowid           	Id of line to update
2860
	 *  @param     	string		$desc            	Description of line
2861
	 *  @param     	double		$pu              	Prix unitaire (HT ou TTC selon price_base_type) (> 0 even for credit note lines)
2862
	 *  @param     	double		$qty             	Quantity
2863
	 *  @param     	double		$remise_percent  	Pourcentage de remise de la ligne
2864
	 *  @param     	int		$date_start      	Date de debut de validite du service
2865
	 *  @param     	int		$date_end        	Date de fin de validite du service
2866
	 *  @param     	double		$txtva          	VAT Rate (Can be '8.5', '8.5 (ABC)')
2867
	 * 	@param		double		$txlocaltax1		Local tax 1 rate
2868
	 *  @param		double		$txlocaltax2		Local tax 2 rate
2869
	 * 	@param     	string		$price_base_type 	HT or TTC
2870
	 * 	@param     	int			$info_bits 		    Miscellaneous informations
2871
	 * 	@param		int			$type				Type of line (0=product, 1=service)
2872
	 * 	@param		int			$fk_parent_line		Id of parent line (0 in most cases, used by modules adding sublevels into lines).
2873
	 * 	@param		int			$skip_update_total	Keep fields total_xxx to 0 (used for special lines by some modules)
2874
	 * 	@param		int			$fk_fournprice		Id of origin supplier price
2875
	 * 	@param		int			$pa_ht				Price (without tax) of product when it was bought
2876
	 * 	@param		string		$label				Label of the line (deprecated, do not use)
2877
	 * 	@param		int			$special_code		Special code (also used by externals modules!)
2878
     *  @param		array		$array_options		extrafields array
2879
	 * 	@param      int         $situation_percent  Situation advance percentage
2880
	 * 	@param 		string		$fk_unit 			Code of the unit to use. Null to use the default one
2881
	 * 	@param		double		$pu_ht_devise		Unit price in currency
2882
	 * 	@param		int			$notrigger			disable line update trigger
2883
	 *  @return    	int             				< 0 if KO, > 0 if OK
2884
	 */
2885
	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)
2886
	{
2887
		global $conf,$user;
2888
		// Deprecation warning
2889
		if ($label) {
2890
			dol_syslog(__METHOD__ . ": using line label is deprecated", LOG_WARNING);
2891
		}
2892
2893
		include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
2894
2895
		global $mysoc,$langs;
2896
2897
		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);
2898
2899
		if ($this->brouillon)
2900
		{
2901
			if (!$this->is_last_in_cycle() && empty($this->error))
2902
			{
2903
				if (!$this->checkProgressLine($rowid, $situation_percent))
2904
				{
2905
					if (!$this->error) $this->error=$langs->trans('invoiceLineProgressError');
2906
					return -3;
2907
				}
2908
			}
2909
2910
			$this->db->begin();
2911
2912
			// Clean parameters
2913
			if (empty($qty)) $qty=0;
2914
			if (empty($fk_parent_line) || $fk_parent_line < 0) $fk_parent_line=0;
2915
			if (empty($special_code) || $special_code == 3) $special_code=0;
2916
			if (! isset($situation_percent) || $situation_percent > 100 || (string) $situation_percent == '') $situation_percent = 100;
2917
2918
			$remise_percent	= price2num($remise_percent);
2919
			$qty			= price2num($qty);
2920
			$pu 			= price2num($pu);
2921
        	$pu_ht_devise	= price2num($pu_ht_devise);
2922
			$pa_ht			= price2num($pa_ht);
2923
			$txtva			= price2num($txtva);
2924
			$txlocaltax1	= price2num($txlocaltax1);
2925
			$txlocaltax2	= price2num($txlocaltax2);
2926
2927
			// Check parameters
2928
			if ($type < 0) return -1;
2929
2930
			// Calculate total with, without tax and tax from qty, pu, remise_percent and txtva
2931
			// TRES IMPORTANT: C'est au moment de l'insertion ligne qu'on doit stocker
2932
			// la part ht, tva et ttc, et ce au niveau de la ligne qui a son propre taux tva.
2933
2934
			$localtaxes_type=getLocalTaxesFromRate($txtva,0,$this->thirdparty, $mysoc);
2935
2936
			// Clean vat code
2937
    		$vat_src_code='';
2938
    		if (preg_match('/\((.*)\)/', $txtva, $reg))
2939
    		{
2940
    		    $vat_src_code = $reg[1];
2941
    		    $txtva = preg_replace('/\s*\(.*\)/', '', $txtva);    // Remove code into vatrate.
2942
    		}
2943
2944
			$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);
2945
2946
			$total_ht  = $tabprice[0];
2947
			$total_tva = $tabprice[1];
2948
			$total_ttc = $tabprice[2];
2949
			$total_localtax1=$tabprice[9];
2950
			$total_localtax2=$tabprice[10];
2951
			$pu_ht  = $tabprice[3];
2952
			$pu_tva = $tabprice[4];
2953
			$pu_ttc = $tabprice[5];
2954
2955
			// MultiCurrency
2956
			$multicurrency_total_ht  = $tabprice[16];
2957
            $multicurrency_total_tva = $tabprice[17];
2958
            $multicurrency_total_ttc = $tabprice[18];
2959
			$pu_ht_devise = $tabprice[19];
2960
2961
			// Old properties: $price, $remise (deprecated)
2962
			$price = $pu;
2963
			$remise = 0;
2964
			if ($remise_percent > 0)
2965
			{
2966
				$remise = round(($pu * $remise_percent / 100),2);
2967
				$price = ($pu - $remise);
2968
			}
2969
			$price    = price2num($price);
2970
2971
			//Fetch current line from the database and then clone the object and set it in $oldline property
2972
			$line = new FactureLigne($this->db);
2973
			$line->fetch($rowid);
2974
2975
			if (!empty($line->fk_product))
2976
			{
2977
				$product=new Product($this->db);
2978
				$result=$product->fetch($line->fk_product);
2979
				$product_type=$product->type;
2980
2981
				if (! empty($conf->global->STOCK_MUST_BE_ENOUGH_FOR_INVOICE) && $product_type == 0 && $product->stock_reel < $qty) {
2982
                    $langs->load("errors");
2983
				    $this->error=$langs->trans('ErrorStockIsNotEnoughToAddProductOnInvoice', $product->ref);
2984
					$this->db->rollback();
2985
					return -3;
2986
				}
2987
			}
2988
2989
			$staticline = clone $line;
2990
2991
			$line->oldline = $staticline;
2992
			$this->line = $line;
2993
            $this->line->context = $this->context;
2994
2995
			// Reorder if fk_parent_line change
2996
			if (! empty($fk_parent_line) && ! empty($staticline->fk_parent_line) && $fk_parent_line != $staticline->fk_parent_line)
2997
			{
2998
				$rangmax = $this->line_max($fk_parent_line);
2999
				$this->line->rang = $rangmax + 1;
3000
			}
3001
3002
			$this->line->rowid				= $rowid;
0 ignored issues
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

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

3348
			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...
3349
3350
			$mybool=false;
3351
3352
			$file = $conf->global->FACTURE_ADDON.".php";
3353
			$classname = $conf->global->FACTURE_ADDON;
3354
3355
			// Include file with class
3356
			$dirmodels = array_merge(array('/'), (array) $conf->modules_parts['models']);
3357
3358
			foreach ($dirmodels as $reldir) {
3359
3360
				$dir = dol_buildpath($reldir."core/modules/facture/");
3361
3362
				// Load file with numbering class (if found)
3363
				if (is_file($dir.$file) && is_readable($dir.$file))
3364
				{
3365
                    $mybool |= include_once $dir . $file;
3366
                }
3367
			}
3368
3369
			// For compatibility
3370
			if (! $mybool)
0 ignored issues
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...
3371
			{
3372
				$file = $conf->global->FACTURE_ADDON."/".$conf->global->FACTURE_ADDON.".modules.php";
3373
				$classname = "mod_facture_".$conf->global->FACTURE_ADDON;
3374
				$classname = preg_replace('/\-.*$/','',$classname);
3375
				// Include file with class
3376
				foreach ($conf->file->dol_document_root as $dirroot)
3377
				{
3378
					$dir = $dirroot."/core/modules/facture/";
3379
3380
					// Load file with numbering class (if found)
3381
					if (is_file($dir.$file) && is_readable($dir.$file)) {
3382
                        $mybool |= include_once $dir . $file;
3383
                    }
3384
				}
3385
			}
3386
3387
			if (! $mybool)
0 ignored issues
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...
3388
			{
3389
				dol_print_error('',"Failed to include file ".$file);
3390
				return '';
3391
			}
3392
3393
			$obj = new $classname();
3394
			$numref = "";
3395
			$numref = $obj->getNextValue($soc,$this,$mode);
3396
3397
			/**
3398
			 * $numref can be empty in case we ask for the last value because if there is no invoice created with the
3399
			 * set up mask.
3400
			 */
3401
			if ($mode != 'last' && !$numref) {
3402
				$this->error=$obj->error;
3403
				//dol_print_error($this->db,"Facture::getNextNumRef ".$obj->error);
3404
				return "";
3405
			}
3406
3407
			return $numref;
3408
		}
3409
		else
3410
		{
3411
			$langs->load("errors");
3412
			print $langs->trans("Error")." ".$langs->trans("ErrorModuleSetupNotComplete");
3413
			return "";
3414
		}
3415
	}
3416
3417
	/**
3418
	 *	Load miscellaneous information for tab "Info"
3419
	 *
3420
	 *	@param  int		$id		Id of object to load
3421
	 *	@return	void
3422
	 */
3423
	function info($id)
3424
	{
3425
		$sql = 'SELECT c.rowid, datec, date_valid as datev, tms as datem,';
3426
		$sql.= ' fk_user_author, fk_user_valid';
3427
		$sql.= ' FROM '.MAIN_DB_PREFIX.'facture as c';
3428
		$sql.= ' WHERE c.rowid = '.$id;
3429
3430
		$result=$this->db->query($sql);
3431
		if ($result)
3432
		{
3433
			if ($this->db->num_rows($result))
3434
			{
3435
				$obj = $this->db->fetch_object($result);
3436
				$this->id = $obj->rowid;
3437
				if ($obj->fk_user_author)
3438
				{
3439
					$cuser = new User($this->db);
3440
					$cuser->fetch($obj->fk_user_author);
3441
					$this->user_creation     = $cuser;
3442
				}
3443
				if ($obj->fk_user_valid)
3444
				{
3445
					$vuser = new User($this->db);
3446
					$vuser->fetch($obj->fk_user_valid);
3447
					$this->user_validation = $vuser;
3448
				}
3449
				$this->date_creation     = $this->db->jdate($obj->datec);
3450
				$this->date_modification = $this->db->jdate($obj->datem);
3451
				$this->date_validation   = $this->db->jdate($obj->datev);	// Should be in log table
3452
			}
3453
			$this->db->free($result);
3454
		}
3455
		else
3456
		{
3457
			dol_print_error($this->db);
3458
		}
3459
	}
3460
3461
3462
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
3463
	/**
3464
	 *  Return list of invoices (eventually filtered on a user) into an array
3465
	 *
3466
	 *  @param		int		$shortlist		0=Return array[id]=ref, 1=Return array[](id=>id,ref=>ref,name=>name)
3467
	 *  @param      int		$draft      	0=not draft, 1=draft
3468
	 *  @param      User	$excluser      	Objet user to exclude
3469
	 *  @param    	int		$socid			Id third pary
3470
	 *  @param    	int		$limit			For pagination
3471
	 *  @param    	int		$offset			For pagination
3472
	 *  @param    	string	$sortfield		Sort criteria
3473
	 *  @param    	string	$sortorder		Sort order
3474
	 *  @return     int             		-1 if KO, array with result if OK
3475
	 */
3476
	function liste_array($shortlist=0, $draft=0, $excluser='', $socid=0, $limit=0, $offset=0, $sortfield='f.datef,f.rowid', $sortorder='DESC')
3477
	{
3478
        // phpcs:enable
3479
		global $conf,$user;
3480
3481
		$ga = array();
3482
3483
		$sql = "SELECT s.rowid, s.nom as name, s.client,";
3484
		$sql.= " f.rowid as fid, f.ref as ref, f.datef as df";
3485
		if (! $user->rights->societe->client->voir && ! $socid) $sql .= ", sc.fk_soc, sc.fk_user";
3486
		$sql.= " FROM ".MAIN_DB_PREFIX."societe as s, ".MAIN_DB_PREFIX."facture as f";
3487
		if (! $user->rights->societe->client->voir && ! $socid) $sql .= ", ".MAIN_DB_PREFIX."societe_commerciaux as sc";
3488
		$sql.= " WHERE f.entity = ".$conf->entity;
3489
		$sql.= " AND f.fk_soc = s.rowid";
3490
		if (! $user->rights->societe->client->voir && ! $socid) //restriction
3491
		{
3492
			$sql.= " AND s.rowid = sc.fk_soc AND sc.fk_user = " .$user->id;
3493
		}
3494
		if ($socid) $sql.= " AND s.rowid = ".$socid;
3495
		if ($draft) $sql.= " AND f.fk_statut = ".self::STATUS_DRAFT;
3496
		if (is_object($excluser)) $sql.= " AND f.fk_user_author <> ".$excluser->id;
3497
		$sql.= $this->db->order($sortfield,$sortorder);
3498
		$sql.= $this->db->plimit($limit,$offset);
3499
3500
		$result=$this->db->query($sql);
3501
		if ($result)
3502
		{
3503
			$numc = $this->db->num_rows($result);
3504
			if ($numc)
3505
			{
3506
				$i = 0;
3507
				while ($i < $numc)
3508
				{
3509
					$obj = $this->db->fetch_object($result);
3510
3511
					if ($shortlist == 1)
3512
					{
3513
						$ga[$obj->fid] = $obj->ref;
3514
					}
3515
					else if ($shortlist == 2)
3516
					{
3517
						$ga[$obj->fid] = $obj->ref.' ('.$obj->name.')';
3518
					}
3519
					else
3520
					{
3521
						$ga[$i]['id']	= $obj->fid;
3522
						$ga[$i]['ref'] 	= $obj->ref;
3523
						$ga[$i]['name'] = $obj->name;
3524
					}
3525
					$i++;
3526
				}
3527
			}
3528
			return $ga;
3529
		}
3530
		else
3531
		{
3532
			dol_print_error($this->db);
3533
			return -1;
3534
		}
3535
	}
3536
3537
3538
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
3539
	/**
3540
	 *	Return list of invoices qualified to be replaced by another invoice.
3541
	 *	Invoices matching the following rules are returned:
3542
	 *	(Status validated or abandonned for a reason 'other') + not payed + no payment at all + not already replaced
3543
	 *
3544
	 *	@param		int		$socid		Id thirdparty
3545
	 *	@return    	array				Array of invoices ('id'=>id, 'ref'=>ref, 'status'=>status, 'paymentornot'=>0/1)
3546
	 */
3547
	function list_replacable_invoices($socid=0)
3548
	{
3549
        // phpcs:enable
3550
		global $conf;
3551
3552
		$return = array();
3553
3554
		$sql = "SELECT f.rowid as rowid, f.ref, f.fk_statut,";
3555
		$sql.= " ff.rowid as rowidnext";
3556
		$sql.= " FROM ".MAIN_DB_PREFIX."facture as f";
3557
		$sql.= " LEFT JOIN ".MAIN_DB_PREFIX."paiement_facture as pf ON f.rowid = pf.fk_facture";
3558
		$sql.= " LEFT JOIN ".MAIN_DB_PREFIX."facture as ff ON f.rowid = ff.fk_facture_source";
3559
		$sql.= " WHERE (f.fk_statut = ".self::STATUS_VALIDATED." OR (f.fk_statut = ".self::STATUS_ABANDONED." AND f.close_code = '".self::CLOSECODE_ABANDONED."'))";
3560
		$sql.= " AND f.entity = ".$conf->entity;
3561
		$sql.= " AND f.paye = 0";					// Pas classee payee completement
3562
		$sql.= " AND pf.fk_paiement IS NULL";		// Aucun paiement deja fait
3563
		$sql.= " AND ff.fk_statut IS NULL";			// Renvoi vrai si pas facture de remplacement
3564
		if ($socid > 0) $sql.=" AND f.fk_soc = ".$socid;
3565
		$sql.= " ORDER BY f.ref";
3566
3567
		dol_syslog(get_class($this)."::list_replacable_invoices", LOG_DEBUG);
3568
		$resql=$this->db->query($sql);
3569
		if ($resql)
3570
		{
3571
			while ($obj=$this->db->fetch_object($resql))
3572
			{
3573
				$return[$obj->rowid]=array(	'id' => $obj->rowid,
3574
				'ref' => $obj->ref,
3575
				'status' => $obj->fk_statut);
3576
			}
3577
			//print_r($return);
3578
			return $return;
3579
		}
3580
		else
3581
		{
3582
			$this->error=$this->db->error();
3583
			return -1;
3584
		}
3585
	}
3586
3587
3588
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
3589
	/**
3590
	 *	Return list of invoices qualified to be corrected by a credit note.
3591
	 *	Invoices matching the following rules are returned:
3592
	 *	(validated + payment on process) or classified (payed completely or payed partiely) + not already replaced + not already a credit note
3593
	 *
3594
	 *	@param		int		$socid		Id thirdparty
3595
	 *	@return    	array				Array of invoices ($id => array('ref'=>,'paymentornot'=>,'status'=>,'paye'=>)
3596
	 */
3597
	function list_qualified_avoir_invoices($socid=0)
3598
	{
3599
        // phpcs:enable
3600
		global $conf;
3601
3602
		$return = array();
3603
3604
3605
		$sql = "SELECT f.rowid as rowid, f.ref, f.fk_statut, f.type, f.paye, pf.fk_paiement";
3606
		$sql.= " FROM ".MAIN_DB_PREFIX."facture as f";
3607
		$sql.= " LEFT JOIN ".MAIN_DB_PREFIX."paiement_facture as pf ON f.rowid = pf.fk_facture";
3608
		$sql.= " LEFT JOIN ".MAIN_DB_PREFIX."facture as ff ON (f.rowid = ff.fk_facture_source AND ff.type=".self::TYPE_REPLACEMENT.")";
3609
		$sql.= " WHERE f.entity = ".$conf->entity;
3610
		$sql.= " AND f.fk_statut in (".self::STATUS_VALIDATED.",".self::STATUS_CLOSED.")";
3611
		//  $sql.= " WHERE f.fk_statut >= 1";
3612
		//	$sql.= " AND (f.paye = 1";				// Classee payee completement
3613
		//	$sql.= " OR f.close_code IS NOT NULL)";	// Classee payee partiellement
3614
		$sql.= " AND ff.type IS NULL";			// Renvoi vrai si pas facture de remplacement
3615
		$sql.= " AND f.type != ".self::TYPE_CREDIT_NOTE;				// Type non 2 si facture non avoir
3616
3617
		if($conf->global->INVOICE_USE_SITUATION_CREDIT_NOTE){
3618
		    // Select the last situation invoice
3619
		    $sqlSit = 'SELECT MAX(fs.rowid)';
3620
		    $sqlSit.= " FROM ".MAIN_DB_PREFIX."facture as fs";
3621
		    $sqlSit.= " WHERE fs.entity = ".$conf->entity;
3622
		    $sqlSit.= " AND fs.type = ".self::TYPE_SITUATION;
3623
		    $sqlSit.= " AND fs.fk_statut in (".self::STATUS_VALIDATED.",".self::STATUS_CLOSED.")";
3624
		    $sqlSit.= " GROUP BY fs.situation_cycle_ref";
3625
		    $sqlSit.= " ORDER BY fs.situation_counter";
3626
            $sql.= " AND ( f.type != ".self::TYPE_SITUATION . " OR f.rowid IN (".$sqlSit.") )";	// Type non 5 si facture non avoir
3627
		}
3628
		else
3629
		{
3630
		    $sql.= " AND f.type != ".self::TYPE_SITUATION ; // Type non 5 si facture non avoir
3631
		}
3632
3633
		if ($socid > 0) $sql.=" AND f.fk_soc = ".$socid;
3634
		$sql.= " ORDER BY f.ref";
3635
3636
		dol_syslog(get_class($this)."::list_qualified_avoir_invoices", LOG_DEBUG);
3637
		$resql=$this->db->query($sql);
3638
		if ($resql)
3639
		{
3640
			while ($obj=$this->db->fetch_object($resql))
3641
			{
3642
				$qualified=0;
3643
				if ($obj->fk_statut == self::STATUS_VALIDATED) $qualified=1;
3644
				if ($obj->fk_statut == self::STATUS_CLOSED) $qualified=1;
3645
				if ($qualified)
3646
				{
3647
					//$ref=$obj->ref;
3648
					$paymentornot=($obj->fk_paiement?1:0);
3649
					$return[$obj->rowid]=array('ref'=>$obj->ref,'status'=>$obj->fk_statut,'type'=>$obj->type,'paye'=>$obj->paye,'paymentornot'=>$paymentornot);
3650
				}
3651
			}
3652
3653
			return $return;
3654
		}
3655
		else
3656
		{
3657
			$this->error=$this->db->error();
3658
			return -1;
3659
		}
3660
	}
3661
3662
3663
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
3664
	/**
3665
	 *	Create a withdrawal request for a standing order.
3666
	 *  Use the remain to pay excluding all existing open direct debit requests.
3667
	 *
3668
	 *	@param      User	$fuser      User asking the direct debit transfer
3669
	 *  @param		float	$amount		Amount we request direct debit for
3670
	 *	@return     int         		<0 if KO, >0 if OK
3671
	 */
3672
	function demande_prelevement($fuser, $amount=0)
3673
	{
3674
        // phpcs:enable
3675
3676
		$error=0;
3677
3678
		dol_syslog(get_class($this)."::demande_prelevement", LOG_DEBUG);
3679
3680
		if ($this->statut > self::STATUS_DRAFT && $this->paye == 0)
3681
		{
3682
	        require_once DOL_DOCUMENT_ROOT . '/societe/class/companybankaccount.class.php';
3683
	        $bac = new CompanyBankAccount($this->db);
3684
	        $bac->fetch(0,$this->socid);
3685
3686
        	$sql = 'SELECT count(*)';
3687
			$sql.= ' FROM '.MAIN_DB_PREFIX.'prelevement_facture_demande';
3688
			$sql.= ' WHERE fk_facture = '.$this->id;
3689
			$sql.= ' AND traite = 0';
3690
3691
			dol_syslog(get_class($this)."::demande_prelevement", LOG_DEBUG);
3692
			$resql=$this->db->query($sql);
3693
			if ($resql)
3694
			{
3695
				$row = $this->db->fetch_row($resql);
3696
				if ($row[0] == 0)
3697
				{
3698
					$now=dol_now();
3699
3700
                    $totalpaye  = $this->getSommePaiement();
3701
                    $totalcreditnotes = $this->getSumCreditNotesUsed();
3702
                    $totaldeposits = $this->getSumDepositsUsed();
3703
                    //print "totalpaye=".$totalpaye." totalcreditnotes=".$totalcreditnotes." totaldeposts=".$totaldeposits;
3704
3705
                    // We can also use bcadd to avoid pb with floating points
3706
                    // For example print 239.2 - 229.3 - 9.9; does not return 0.
3707
                    //$resteapayer=bcadd($this->total_ttc,$totalpaye,$conf->global->MAIN_MAX_DECIMALS_TOT);
3708
                    //$resteapayer=bcadd($resteapayer,$totalavoir,$conf->global->MAIN_MAX_DECIMALS_TOT);
3709
					if (empty($amount)) $amount = price2num($this->total_ttc - $totalpaye - $totalcreditnotes - $totaldeposits,'MT');
3710
3711
					if (is_numeric($amount) && $amount != 0)
3712
					{
3713
						$sql = 'INSERT INTO '.MAIN_DB_PREFIX.'prelevement_facture_demande';
3714
						$sql .= ' (fk_facture, amount, date_demande, fk_user_demande, code_banque, code_guichet, number, cle_rib)';
3715
						$sql .= ' VALUES ('.$this->id;
3716
						$sql .= ",'".price2num($amount)."'";
3717
						$sql .= ",'".$this->db->idate($now)."'";
3718
						$sql .= ",".$fuser->id;
3719
						$sql .= ",'".$bac->code_banque."'";
3720
						$sql .= ",'".$bac->code_guichet."'";
3721
						$sql .= ",'".$bac->number."'";
3722
						$sql .= ",'".$bac->cle_rib."')";
3723
3724
						dol_syslog(get_class($this)."::demande_prelevement", LOG_DEBUG);
3725
						$resql=$this->db->query($sql);
3726
						if (! $resql)
3727
						{
3728
						    $this->error=$this->db->lasterror();
3729
						    dol_syslog(get_class($this).'::demandeprelevement Erreur');
3730
						    $error++;
3731
						}
3732
					}
3733
					else
3734
					{
3735
						$this->error='WithdrawRequestErrorNilAmount';
3736
	                    dol_syslog(get_class($this).'::demandeprelevement WithdrawRequestErrorNilAmount');
3737
	                    $error++;
3738
					}
3739
3740
        			if (! $error)
3741
        			{
3742
        				// Force payment mode of invoice to withdraw
3743
        				$payment_mode_id = dol_getIdFromCode($this->db, 'PRE', 'c_paiement', 'code', 'id', 1);
3744
        				if ($payment_mode_id > 0)
3745
        				{
3746
        					$result=$this->setPaymentMethods($payment_mode_id);
3747
        				}
3748
        			}
3749
3750
                    if ($error) return -1;
3751
                    return 1;
3752
                }
3753
                else
3754
                {
3755
                    $this->error="A request already exists";
3756
                    dol_syslog(get_class($this).'::demandeprelevement Impossible de creer une demande, demande deja en cours');
3757
                    return 0;
3758
                }
3759
            }
3760
            else
3761
            {
3762
                $this->error=$this->db->error();
3763
                dol_syslog(get_class($this).'::demandeprelevement Erreur -2');
3764
                return -2;
3765
            }
3766
        }
3767
        else
3768
        {
3769
            $this->error="Status of invoice does not allow this";
3770
            dol_syslog(get_class($this)."::demandeprelevement ".$this->error." $this->statut, $this->paye, $this->mode_reglement_id");
3771
            return -3;
3772
        }
3773
    }
3774
3775
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
3776
	/**
3777
	 *  Supprime une demande de prelevement
3778
	 *
3779
	 *  @param  User	$fuser      User making delete
3780
	 *  @param  int		$did        id de la demande a supprimer
3781
	 *  @return	int					<0 if OK, >0 if KO
3782
	 */
3783
	function demande_prelevement_delete($fuser, $did)
3784
	{
3785
        // phpcs:enable
3786
		$sql = 'DELETE FROM '.MAIN_DB_PREFIX.'prelevement_facture_demande';
3787
		$sql .= ' WHERE rowid = '.$did;
3788
		$sql .= ' AND traite = 0';
3789
		if ( $this->db->query($sql) )
3790
		{
3791
			return 0;
3792
		}
3793
		else
3794
		{
3795
			$this->error=$this->db->lasterror();
3796
			dol_syslog(get_class($this).'::demande_prelevement_delete Error '.$this->error);
3797
			return -1;
3798
		}
3799
	}
3800
3801
3802
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
3803
	/**
3804
	 *	Load indicators for dashboard (this->nbtodo and this->nbtodolate)
3805
	 *
3806
	 *	@param  User		$user    	Object user
3807
	 *	@return WorkboardResponse|int 	<0 if KO, WorkboardResponse if OK
3808
	 */
3809
	function load_board($user)
3810
	{
3811
        // phpcs:enable
3812
		global $conf, $langs;
3813
3814
		$clause = " WHERE";
3815
3816
		$sql = "SELECT f.rowid, f.date_lim_reglement as datefin,f.fk_statut, f.total";
3817
		$sql.= " FROM ".MAIN_DB_PREFIX."facture as f";
3818
		if (!$user->rights->societe->client->voir && !$user->societe_id)
3819
		{
3820
			$sql.= " LEFT JOIN ".MAIN_DB_PREFIX."societe_commerciaux as sc ON f.fk_soc = sc.fk_soc";
3821
			$sql.= " WHERE sc.fk_user = " .$user->id;
3822
			$clause = " AND";
3823
		}
3824
		$sql.= $clause." f.paye=0";
3825
		$sql.= " AND f.entity = ".$conf->entity;
3826
		$sql.= " AND f.fk_statut = ".self::STATUS_VALIDATED;
3827
		if ($user->societe_id) $sql.= " AND f.fk_soc = ".$user->societe_id;
3828
3829
		$resql=$this->db->query($sql);
3830
		if ($resql)
3831
		{
3832
			$langs->load("bills");
3833
			$now=dol_now();
3834
3835
			$response = new WorkboardResponse();
3836
			$response->warning_delay=$conf->facture->client->warning_delay/60/60/24;
3837
			$response->label=$langs->trans("CustomerBillsUnpaid");
3838
			$response->url=DOL_URL_ROOT.'/compta/facture/list.php?search_status=1&mainmenu=billing&leftmenu=customers_bills';
3839
			$response->img=img_object('',"bill");
3840
3841
			$generic_facture = new Facture($this->db);
3842
3843
			while ($obj=$this->db->fetch_object($resql))
3844
			{
3845
				$generic_facture->date_lim_reglement = $this->db->jdate($obj->datefin);
3846
				$generic_facture->statut = $obj->fk_statut;
3847
3848
				$response->nbtodo++;
3849
				$response->total += $obj->total;
3850
3851
				if ($generic_facture->hasDelay()) {
3852
					$response->nbtodolate++;
3853
				}
3854
			}
3855
3856
			return $response;
3857
		}
3858
		else
3859
		{
3860
			dol_print_error($this->db);
3861
			$this->error=$this->db->error();
3862
			return -1;
3863
		}
3864
	}
3865
3866
3867
	/* gestion des contacts d'une facture */
3868
3869
	/**
3870
	 *	Retourne id des contacts clients de facturation
3871
	 *
3872
	 *	@return     array       Liste des id contacts facturation
3873
	 */
3874
	function getIdBillingContact()
3875
	{
3876
		return $this->getIdContact('external','BILLING');
3877
	}
3878
3879
	/**
3880
	 *	Retourne id des contacts clients de livraison
3881
	 *
3882
	 *	@return     array       Liste des id contacts livraison
3883
	 */
3884
	function getIdShippingContact()
3885
	{
3886
		return $this->getIdContact('external','SHIPPING');
3887
	}
3888
3889
3890
	/**
3891
	 *  Initialise an instance with random values.
3892
	 *  Used to build previews or test instances.
3893
	 *	id must be 0 if object instance is a specimen.
3894
	 *
3895
	 *	@param	string		$option		''=Create a specimen invoice with lines, 'nolines'=No lines
3896
	 *  @return	void
3897
	 */
3898
	function initAsSpecimen($option='')
3899
	{
3900
		global $langs;
3901
3902
		$now=dol_now();
3903
		$arraynow=dol_getdate($now);
3904
		$nownotime=dol_mktime(0, 0, 0, $arraynow['mon'], $arraynow['mday'], $arraynow['year']);
3905
3906
        // Load array of products prodids
3907
		$num_prods = 0;
3908
		$prodids = array();
3909
		$sql = "SELECT rowid";
3910
		$sql.= " FROM ".MAIN_DB_PREFIX."product";
3911
		$sql.= " WHERE entity IN (".getEntity('product').")";
3912
		$resql = $this->db->query($sql);
3913
		if ($resql)
3914
		{
3915
			$num_prods = $this->db->num_rows($resql);
3916
			$i = 0;
3917
			while ($i < $num_prods)
3918
			{
3919
				$i++;
3920
				$row = $this->db->fetch_row($resql);
3921
				$prodids[$i] = $row[0];
3922
			}
3923
		}
3924
		//Avoid php warning Warning: mt_rand(): max(0) is smaller than min(1) when no product exists
3925
		if (empty($num_prods)) {
3926
			$num_prods=1;
3927
		}
3928
3929
		// Initialize parameters
3930
		$this->id=0;
3931
		$this->entity = 1;
3932
		$this->ref = 'SPECIMEN';
3933
		$this->specimen=1;
3934
		$this->socid = 1;
3935
		$this->date = $nownotime;
3936
		$this->date_lim_reglement = $nownotime + 3600 * 24 *30;
3937
		$this->cond_reglement_id   = 1;
3938
		$this->cond_reglement_code = 'RECEP';
3939
		$this->date_lim_reglement=$this->calculate_date_lim_reglement();
3940
		$this->mode_reglement_id   = 0;		// Not forced to show payment mode CHQ + VIR
3941
		$this->mode_reglement_code = '';	// Not forced to show payment mode CHQ + VIR
3942
		$this->note_public='This is a comment (public)';
3943
		$this->note_private='This is a comment (private)';
3944
		$this->note='This is a comment (private)';
3945
		$this->fk_incoterms=0;
3946
		$this->location_incoterms='';
3947
3948
		if (empty($option) || $option != 'nolines')
3949
		{
3950
			// Lines
3951
			$nbp = 5;
3952
			$xnbp = 0;
3953
			while ($xnbp < $nbp)
3954
			{
3955
				$line=new FactureLigne($this->db);
3956
				$line->desc=$langs->trans("Description")." ".$xnbp;
3957
				$line->qty=1;
3958
				$line->subprice=100;
3959
				$line->tva_tx=19.6;
3960
				$line->localtax1_tx=0;
3961
				$line->localtax2_tx=0;
3962
				$line->remise_percent=0;
3963
				if ($xnbp == 1)        // Qty is negative (product line)
3964
				{
3965
					$prodid = mt_rand(1, $num_prods);
3966
					$line->fk_product=$prodids[$prodid];
3967
					$line->qty=-1;
3968
					$line->total_ht=-100;
3969
					$line->total_ttc=-119.6;
3970
					$line->total_tva=-19.6;
3971
					$line->multicurrency_total_ht=-200;
3972
					$line->multicurrency_total_ttc=-239.2;
3973
					$line->multicurrency_total_tva=-39.2;
3974
				}
3975
				else if ($xnbp == 2)    // UP is negative (free line)
3976
				{
3977
					$line->subprice=-100;
3978
					$line->total_ht=-100;
3979
					$line->total_ttc=-119.6;
3980
					$line->total_tva=-19.6;
3981
					$line->remise_percent=0;
3982
					$line->multicurrency_total_ht=-200;
3983
					$line->multicurrency_total_ttc=-239.2;
3984
					$line->multicurrency_total_tva=-39.2;
3985
				}
3986
				else if ($xnbp == 3)    // Discount is 50% (product line)
3987
				{
3988
					$prodid = mt_rand(1, $num_prods);
3989
					$line->fk_product=$prodids[$prodid];
3990
					$line->total_ht=50;
3991
					$line->total_ttc=59.8;
3992
					$line->total_tva=9.8;
3993
					$line->multicurrency_total_ht=100;
3994
					$line->multicurrency_total_ttc=119.6;
3995
					$line->multicurrency_total_tva=19.6;
3996
					$line->remise_percent=50;
3997
				}
3998
				else    // (product line)
3999
				{
4000
					$prodid = mt_rand(1, $num_prods);
4001
					$line->fk_product=$prodids[$prodid];
4002
					$line->total_ht=100;
4003
					$line->total_ttc=119.6;
4004
					$line->total_tva=19.6;
4005
					$line->multicurrency_total_ht=200;
4006
					$line->multicurrency_total_ttc=239.2;
4007
					$line->multicurrency_total_tva=39.2;
4008
					$line->remise_percent=0;
4009
				}
4010
4011
				$this->lines[$xnbp]=$line;
4012
4013
4014
				$this->total_ht       += $line->total_ht;
4015
				$this->total_tva      += $line->total_tva;
4016
				$this->total_ttc      += $line->total_ttc;
4017
4018
				$this->multicurrency_total_ht       += $line->multicurrency_total_ht;
4019
				$this->multicurrency_total_tva      += $line->multicurrency_total_tva;
4020
				$this->multicurrency_total_ttc      += $line->multicurrency_total_ttc;
4021
4022
				$xnbp++;
4023
			}
4024
			$this->revenuestamp = 0;
4025
4026
			// Add a line "offered"
4027
			$line=new FactureLigne($this->db);
4028
			$line->desc=$langs->trans("Description")." (offered line)";
4029
			$line->qty=1;
4030
			$line->subprice=100;
4031
			$line->tva_tx=19.6;
4032
			$line->localtax1_tx=0;
4033
			$line->localtax2_tx=0;
4034
			$line->remise_percent=100;
4035
			$line->total_ht=0;
4036
			$line->total_ttc=0;    // 90 * 1.196
4037
			$line->total_tva=0;
4038
			$line->multicurrency_total_ht=0;
4039
			$line->multicurrency_total_ttc=0;
4040
			$line->multicurrency_total_tva=0;
4041
			$prodid = mt_rand(1, $num_prods);
4042
			$line->fk_product=$prodids[$prodid];
4043
4044
			$this->lines[$xnbp]=$line;
4045
			$xnbp++;
4046
		}
4047
	}
4048
4049
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
4050
	/**
4051
	 *      Load indicators for dashboard (this->nbtodo and this->nbtodolate)
4052
	 *
4053
	 *      @return         int     <0 if KO, >0 if OK
4054
	 */
4055
	function load_state_board()
4056
	{
4057
        // phpcs:enable
4058
		global $conf, $user;
4059
4060
		$this->nb=array();
4061
4062
		$clause = "WHERE";
4063
4064
		$sql = "SELECT count(f.rowid) as nb";
4065
		$sql.= " FROM ".MAIN_DB_PREFIX."facture as f";
4066
		$sql.= " LEFT JOIN ".MAIN_DB_PREFIX."societe as s ON f.fk_soc = s.rowid";
4067
		if (!$user->rights->societe->client->voir && !$user->societe_id)
4068
		{
4069
			$sql.= " LEFT JOIN ".MAIN_DB_PREFIX."societe_commerciaux as sc ON s.rowid = sc.fk_soc";
4070
			$sql.= " WHERE sc.fk_user = " .$user->id;
4071
			$clause = "AND";
4072
		}
4073
		$sql.= " ".$clause." f.entity = ".$conf->entity;
4074
4075
		$resql=$this->db->query($sql);
4076
		if ($resql)
4077
		{
4078
			while ($obj=$this->db->fetch_object($resql))
4079
			{
4080
				$this->nb["invoices"]=$obj->nb;
4081
			}
4082
            $this->db->free($resql);
4083
			return 1;
4084
		}
4085
		else
4086
		{
4087
			dol_print_error($this->db);
4088
			$this->error=$this->db->error();
4089
			return -1;
4090
		}
4091
	}
4092
4093
	/**
4094
	 * 	Create an array of invoice lines
4095
	 *
4096
	 * 	@return int		>0 if OK, <0 if KO
4097
	 */
4098
	function getLinesArray()
4099
	{
4100
	    return $this->fetch_lines();
4101
	}
4102
4103
	/**
4104
	 *  Create a document onto disk according to template module.
4105
	 *
4106
	 *	@param	string		$modele			Generator to use. Caller must set it to obj->modelpdf or GETPOST('modelpdf') for example.
4107
	 *	@param	Translate	$outputlangs	objet lang a utiliser pour traduction
4108
	 *  @param  int			$hidedetails    Hide details of lines
4109
	 *  @param  int			$hidedesc       Hide description
4110
	 *  @param  int			$hideref        Hide ref
4111
	 *  @param   null|array  $moreparams     Array to provide more information
4112
	 *	@return int        					<0 if KO, >0 if OK
4113
	 */
4114
	public function generateDocument($modele, $outputlangs, $hidedetails=0, $hidedesc=0, $hideref=0, $moreparams=null)
4115
	{
4116
		global $conf,$langs;
4117
4118
		$langs->load("bills");
4119
4120
		if (! dol_strlen($modele))
4121
		{
4122
			$modele = 'crabe';
4123
			$thisTypeConfName = 'FACTURE_ADDON_PDF_'.$this->type;
4124
4125
			if ($this->modelpdf) {
4126
				$modele = $this->modelpdf;
4127
			} elseif (! empty($conf->global->$thisTypeConfName)) {
4128
				$modele = $conf->global->$thisTypeConfName;
4129
			} elseif (! empty($conf->global->FACTURE_ADDON_PDF)) {
4130
				$modele = $conf->global->FACTURE_ADDON_PDF;
4131
			}
4132
		}
4133
4134
		$modelpath = "core/modules/facture/doc/";
4135
4136
		return $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams);
4137
	}
4138
4139
	/**
4140
	 * Gets the smallest reference available for a new cycle
4141
	 *
4142
	 * @return int >= 1 if OK, -1 if error
4143
	 */
4144
	function newCycle()
4145
	{
4146
		$sql = 'SELECT max(situation_cycle_ref) FROM ' . MAIN_DB_PREFIX . 'facture as f';
4147
		$sql.= " WHERE f.entity in (".getEntity('invoice', 0).")";
4148
		$resql = $this->db->query($sql);
4149
		if ($resql) {
4150
			if ($resql->num_rows > 0)
4151
			{
4152
				$res = $this->db->fetch_array($resql);
4153
				$ref = $res['max(situation_cycle_ref)'];
4154
				$ref++;
4155
			} else {
4156
				$ref = 1;
4157
			}
4158
			$this->db->free($resql);
4159
			return $ref;
4160
		} else {
4161
			$this->error = $this->db->lasterror();
4162
			dol_syslog("Error sql=" . $sql . ", error=" . $this->error, LOG_ERR);
4163
			return -1;
4164
		}
4165
	}
4166
4167
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
4168
	/**
4169
	 * Checks if the invoice is the first of a cycle
4170
	 *
4171
	 * @return boolean
4172
	 */
4173
	function is_first()
4174
	{
4175
        // phpcs:enable
4176
		return ($this->situation_counter == 1);
4177
	}
4178
4179
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
4180
	/**
4181
	 * Returns an array containing the previous situations as Facture objects
4182
	 *
4183
	 * @return mixed -1 if error, array of previous situations
4184
	 */
4185
	function get_prev_sits()
4186
	{
4187
        // phpcs:enable
4188
		global $conf;
4189
4190
		$sql = 'SELECT rowid FROM ' . MAIN_DB_PREFIX . 'facture';
4191
		$sql .= ' where situation_cycle_ref = ' . $this->situation_cycle_ref;
4192
		$sql .= ' and situation_counter < ' . $this->situation_counter;
4193
		$sql .= ' AND entity = '. ($this->entity > 0 ? $this->entity : $conf->entity);
4194
		$resql = $this->db->query($sql);
4195
		$res = array();
4196
		if ($resql && $resql->num_rows > 0) {
4197
			while ($row = $this->db->fetch_object($resql)) {
4198
				$id = $row->rowid;
4199
				$situation = new Facture($this->db);
4200
				$situation->fetch($id);
4201
				$res[] = $situation;
4202
			}
4203
		} else {
4204
			$this->error = $this->db->error();
4205
			dol_syslog("Error sql=" . $sql . ", error=" . $this->error, LOG_ERR);
4206
			return -1;
4207
		}
4208
4209
		return $res;
4210
	}
4211
4212
	/**
4213
	 * Sets the invoice as a final situation
4214
	 *
4215
	 *  @param  	User	$user    	Object user
4216
	 *  @param     	int		$notrigger	1=Does not execute triggers, 0= execute triggers
4217
	 *	@return		int 				<0 if KO, >0 if OK
4218
	 */
4219
	function setFinal(User $user, $notrigger=0)
4220
	{
4221
		$error=0;
4222
4223
		$this->db->begin();
4224
4225
		$sql = 'UPDATE ' . MAIN_DB_PREFIX . 'facture SET situation_final = ' . $this->situation_final . ' where rowid = ' . $this->id;
4226
4227
		dol_syslog(__METHOD__, LOG_DEBUG);
4228
		$resql=$this->db->query($sql);
4229
		if (!$resql)
4230
		{
4231
			$this->errors[]=$this->db->error();
4232
			$error++;
4233
		}
4234
4235
		if (! $notrigger && empty($error))
4236
		{
4237
			// Call trigger
4238
			$result=$this->call_trigger('BILL_MODIFY',$user);
4239
			if ($result < 0) $error++;
4240
			// End call triggers
4241
		}
4242
4243
		if (! $error)
4244
		{
4245
			$this->db->commit();
4246
			return 1;
4247
		}
4248
		else
4249
		{
4250
			foreach($this->errors as $errmsg)
4251
			{
4252
				dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
4253
				$this->error.=($this->error?', '.$errmsg:$errmsg);
4254
			}
4255
			$this->db->rollback();
4256
			return -1*$error;
4257
		}
4258
	}
4259
4260
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
4261
	/**
4262
	 * Checks if the invoice is the last in its cycle
4263
	 *
4264
	 * @return bool Last of the cycle status
4265
	 *
4266
	 */
4267
	function is_last_in_cycle()
4268
	{
4269
        // phpcs:enable
4270
		global $conf;
4271
4272
		if (!empty($this->situation_cycle_ref)) {
4273
			// No point in testing anything if we're not inside a cycle
4274
			$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);
4275
			$resql = $this->db->query($sql);
4276
4277
			if ($resql && $resql->num_rows > 0) {
4278
				$res = $this->db->fetch_array($resql);
4279
				$last = $res['max(situation_counter)'];
4280
				return ($last == $this->situation_counter);
4281
			} else {
4282
				$this->error = $this->db->lasterror();
4283
				dol_syslog(get_class($this) . "::select Error " . $this->error, LOG_ERR);
4284
				return false;
4285
			}
4286
		} else {
4287
			return true;
4288
		}
4289
	}
4290
4291
	/**
4292
	 * Function used to replace a thirdparty id with another one.
4293
	 *
4294
	 * @param DoliDB $db Database handler
4295
	 * @param int $origin_id Old thirdparty id
4296
	 * @param int $dest_id New thirdparty id
4297
	 * @return bool
4298
	 */
4299
	public static function replaceThirdparty(DoliDB $db, $origin_id, $dest_id)
4300
	{
4301
		$tables = array(
4302
			'facture'
4303
		);
4304
4305
		return CommonObject::commonReplaceThirdparty($db, $origin_id, $dest_id, $tables);
4306
	}
4307
4308
	/**
4309
	 * Is the customer invoice delayed?
4310
	 *
4311
	 * @return bool
4312
	 */
4313
	public function hasDelay()
4314
	{
4315
		global $conf;
4316
4317
		$now = dol_now();
4318
4319
		// Paid invoices have status STATUS_CLOSED
4320
		if ($this->statut != Facture::STATUS_VALIDATED) return false;
4321
4322
		return $this->date_lim_reglement < ($now - $conf->facture->client->warning_delay);
4323
	}
4324
}
4325
4326
/**
4327
 *	Class to manage invoice lines.
4328
 *  Saved into database table llx_facturedet
4329
 */
4330
class FactureLigne extends CommonInvoiceLine
4331
{
4332
    /**
4333
	 * @var string ID to identify managed object
4334
	 */
4335
	public $element='facturedet';
4336
4337
    /**
4338
	 * @var string Name of table without prefix where object is stored
4339
	 */
4340
	public $table_element='facturedet';
4341
4342
	public $oldline;
4343
4344
	//! From llx_facturedet
4345
	//! Id facture
4346
	public $fk_facture;
4347
	//! Id parent line
4348
	public $fk_parent_line;
4349
	/**
4350
	 * @deprecated
4351
	 */
4352
	public $label;
4353
	//! Description ligne
4354
	public $desc;
4355
4356
	public $localtax1_type;	// Local tax 1 type
4357
	public $localtax2_type;	// Local tax 2 type
4358
	public $fk_remise_except;	// Link to line into llx_remise_except
4359
	public $rang = 0;
4360
4361
	public $fk_fournprice;
4362
	public $pa_ht;
4363
	public $marge_tx;
4364
	public $marque_tx;
4365
4366
	public $special_code;	// Liste d'options non cumulabels:
4367
	// 1: frais de port
4368
	// 2: ecotaxe
4369
	// 3: ??
4370
4371
	public $origin;
4372
	public $origin_id;
4373
4374
	public $fk_code_ventilation = 0;
4375
4376
	public $date_start;
4377
	public $date_end;
4378
4379
	// Ne plus utiliser
4380
	//var $price;         	// P.U. HT apres remise % de ligne (exemple 80)
4381
	//var $remise;			// Montant calcule de la remise % sur PU HT (exemple 20)
4382
4383
	// From llx_product
4384
	/**
4385
	 * @deprecated
4386
	 * @see product_ref
4387
	 */
4388
	public $ref;				// Product ref (deprecated)
4389
	public $product_ref;       // Product ref
4390
	/**
4391
	 * @deprecated
4392
	 * @see product_label
4393
	 */
4394
	public $libelle;      		// Product label (deprecated)
4395
	public $product_label;     // Product label
4396
	public $product_desc;  	// Description produit
4397
4398
	public $skip_update_total; // Skip update price total for special lines
4399
4400
	/**
4401
	 * @var int Situation advance percentage
4402
	 */
4403
	public $situation_percent;
4404
4405
	/**
4406
	 * @var int Previous situation line id reference
4407
	 */
4408
	public $fk_prev_id;
4409
4410
	// Multicurrency
4411
	public $fk_multicurrency;
4412
	public $multicurrency_code;
4413
	public $multicurrency_subprice;
4414
	public $multicurrency_total_ht;
4415
	public $multicurrency_total_tva;
4416
	public $multicurrency_total_ttc;
4417
4418
	/**
4419
	 *	Load invoice line from database
4420
	 *
4421
	 *	@param	int		$rowid      id of invoice line to get
4422
	 *	@return	int					<0 if KO, >0 if OK
4423
	 */
4424
	function fetch($rowid)
4425
	{
4426
		$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,';
4427
		$sql.= ' fd.localtax1_tx, fd. localtax2_tx, fd.remise, fd.remise_percent, fd.fk_remise_except, fd.subprice,';
4428
		$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,';
4429
		$sql.= ' fd.info_bits, fd.special_code, fd.total_ht, fd.total_tva, fd.total_ttc, fd.total_localtax1, fd.total_localtax2, fd.rang,';
4430
		$sql.= ' fd.fk_code_ventilation,';
4431
		$sql.= ' fd.fk_unit, fd.fk_user_author, fd.fk_user_modif,';
4432
		$sql.= ' fd.situation_percent, fd.fk_prev_id,';
4433
		$sql.= ' fd.multicurrency_subprice,';
4434
		$sql.= ' fd.multicurrency_total_ht,';
4435
		$sql.= ' fd.multicurrency_total_tva,';
4436
		$sql.= ' fd.multicurrency_total_ttc,';
4437
		$sql.= ' p.ref as product_ref, p.label as product_libelle, p.description as product_desc';
4438
		$sql.= ' FROM '.MAIN_DB_PREFIX.'facturedet as fd';
4439
		$sql.= ' LEFT JOIN '.MAIN_DB_PREFIX.'product as p ON fd.fk_product = p.rowid';
4440
		$sql.= ' WHERE fd.rowid = '.$rowid;
4441
4442
		$result = $this->db->query($sql);
4443
		if ($result)
4444
		{
4445
			$objp = $this->db->fetch_object($result);
4446
4447
			$this->rowid				= $objp->rowid;
4448
			$this->id					= $objp->rowid;
4449
			$this->fk_facture			= $objp->fk_facture;
4450
			$this->fk_parent_line		= $objp->fk_parent_line;
4451
			$this->label				= $objp->custom_label;
4452
			$this->desc					= $objp->description;
4453
			$this->qty					= $objp->qty;
4454
			$this->subprice				= $objp->subprice;
4455
			$this->vat_src_code  		= $objp->vat_src_code;
4456
			$this->tva_tx				= $objp->tva_tx;
4457
			$this->localtax1_tx			= $objp->localtax1_tx;
4458
			$this->localtax2_tx			= $objp->localtax2_tx;
4459
			$this->remise_percent		= $objp->remise_percent;
4460
			$this->fk_remise_except		= $objp->fk_remise_except;
4461
			$this->fk_product			= $objp->fk_product;
4462
			$this->product_type			= $objp->product_type;
4463
			$this->date_start			= $this->db->jdate($objp->date_start);
4464
			$this->date_end				= $this->db->jdate($objp->date_end);
4465
			$this->info_bits			= $objp->info_bits;
4466
			$this->tva_npr              = ($objp->info_bits & 1 == 1) ? 1 : 0;
4467
			$this->special_code			= $objp->special_code;
4468
			$this->total_ht				= $objp->total_ht;
4469
			$this->total_tva			= $objp->total_tva;
4470
			$this->total_localtax1		= $objp->total_localtax1;
4471
			$this->total_localtax2		= $objp->total_localtax2;
4472
			$this->total_ttc			= $objp->total_ttc;
4473
			$this->fk_code_ventilation	= $objp->fk_code_ventilation;
4474
			$this->rang					= $objp->rang;
4475
			$this->fk_fournprice		= $objp->fk_fournprice;
4476
			$marginInfos				= getMarginInfos($objp->subprice, $objp->remise_percent, $objp->tva_tx, $objp->localtax1_tx, $objp->localtax2_tx, $this->fk_fournprice, $objp->pa_ht);
4477
			$this->pa_ht				= $marginInfos[0];
4478
			$this->marge_tx				= $marginInfos[1];
4479
			$this->marque_tx			= $marginInfos[2];
4480
4481
			$this->ref					= $objp->product_ref;      // deprecated
4482
			$this->product_ref			= $objp->product_ref;
4483
			$this->libelle				= $objp->product_libelle;  // deprecated
4484
			$this->product_label		= $objp->product_libelle;
4485
			$this->product_desc			= $objp->product_desc;
4486
			$this->fk_unit				= $objp->fk_unit;
4487
			$this->fk_user_modif		= $objp->fk_user_modif;
4488
			$this->fk_user_author		= $objp->fk_user_author;
4489
4490
			$this->situation_percent    = $objp->situation_percent;
4491
			$this->fk_prev_id           = $objp->fk_prev_id;
4492
4493
			$this->multicurrency_subprice = $objp->multicurrency_subprice;
4494
			$this->multicurrency_total_ht = $objp->multicurrency_total_ht;
4495
			$this->multicurrency_total_tva= $objp->multicurrency_total_tva;
4496
			$this->multicurrency_total_ttc= $objp->multicurrency_total_ttc;
4497
4498
			$this->db->free($result);
4499
4500
			return 1;
4501
		}
4502
		else
4503
		{
4504
		    $this->error = $this->db->lasterror();
4505
			return -1;
4506
		}
4507
	}
4508
4509
	/**
4510
	 *	Insert line into database
4511
	 *
4512
	 *	@param      int		$notrigger		                 1 no triggers
4513
	 *  @param      int     $noerrorifdiscountalreadylinked  1=Do not make error if lines is linked to a discount and discount already linked to another
4514
	 *	@return		int						                 <0 if KO, >0 if OK
4515
	 */
4516
	function insert($notrigger=0, $noerrorifdiscountalreadylinked=0)
4517
	{
4518
		global $langs,$user,$conf;
4519
4520
		$error=0;
4521
4522
        $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'.
4523
4524
        dol_syslog(get_class($this)."::insert rang=".$this->rang, LOG_DEBUG);
4525
4526
		// Clean parameters
4527
		$this->desc=trim($this->desc);
4528
		if (empty($this->tva_tx)) $this->tva_tx=0;
4529
		if (empty($this->localtax1_tx)) $this->localtax1_tx=0;
4530
		if (empty($this->localtax2_tx)) $this->localtax2_tx=0;
4531
		if (empty($this->localtax1_type)) $this->localtax1_type=0;
4532
		if (empty($this->localtax2_type)) $this->localtax2_type=0;
4533
		if (empty($this->total_localtax1)) $this->total_localtax1=0;
4534
		if (empty($this->total_localtax2)) $this->total_localtax2=0;
4535
		if (empty($this->rang)) $this->rang=0;
4536
		if (empty($this->remise_percent)) $this->remise_percent=0;
4537
		if (empty($this->info_bits)) $this->info_bits=0;
4538
		if (empty($this->subprice)) $this->subprice=0;
4539
		if (empty($this->special_code)) $this->special_code=0;
4540
		if (empty($this->fk_parent_line)) $this->fk_parent_line=0;
4541
		if (empty($this->fk_prev_id)) $this->fk_prev_id = 0;
4542
		if (! isset($this->situation_percent) || $this->situation_percent > 100 || (string) $this->situation_percent == '') $this->situation_percent = 100;
4543
4544
		if (empty($this->pa_ht)) $this->pa_ht=0;
4545
		if (empty($this->multicurrency_subprice)) $this->multicurrency_subprice=0;
4546
		if (empty($this->multicurrency_total_ht)) $this->multicurrency_total_ht=0;
4547
		if (empty($this->multicurrency_total_tva)) $this->multicurrency_total_tva=0;
4548
		if (empty($this->multicurrency_total_ttc)) $this->multicurrency_total_ttc=0;
4549
4550
		// if buy price not defined, define buyprice as configured in margin admin
4551
		if ($this->pa_ht == 0 && $pa_ht_isemptystring)
4552
		{
4553
			if (($result = $this->defineBuyPrice($this->subprice, $this->remise_percent, $this->fk_product)) < 0)
4554
			{
4555
				return $result;
4556
			}
4557
			else
4558
			{
4559
				$this->pa_ht = $result;
4560
			}
4561
		}
4562
4563
		// Check parameters
4564
		if ($this->product_type < 0)
4565
		{
4566
			$this->error='ErrorProductTypeMustBe0orMore';
4567
			return -1;
4568
		}
4569
		if (! empty($this->fk_product))
4570
		{
4571
			// Check product exists
4572
			$result=Product::isExistingObject('product', $this->fk_product);
4573
			if ($result <= 0)
4574
			{
4575
				$this->error='ErrorProductIdDoesNotExists';
4576
				return -1;
4577
			}
4578
		}
4579
4580
		$this->db->begin();
4581
4582
		// Insertion dans base de la ligne
4583
		$sql = 'INSERT INTO '.MAIN_DB_PREFIX.'facturedet';
4584
		$sql.= ' (fk_facture, fk_parent_line, label, description, qty,';
4585
		$sql.= ' vat_src_code, tva_tx, localtax1_tx, localtax2_tx, localtax1_type, localtax2_type,';
4586
		$sql.= ' fk_product, product_type, remise_percent, subprice, fk_remise_except,';
4587
		$sql.= ' date_start, date_end, fk_code_ventilation, ';
4588
		$sql.= ' rang, special_code, fk_product_fournisseur_price, buy_price_ht,';
4589
		$sql.= ' info_bits, total_ht, total_tva, total_ttc, total_localtax1, total_localtax2,';
4590
		$sql.= ' situation_percent, fk_prev_id,';
4591
		$sql.= ' fk_unit, fk_user_author, fk_user_modif,';
4592
		$sql.= ' fk_multicurrency, multicurrency_code, multicurrency_subprice, multicurrency_total_ht, multicurrency_total_tva, multicurrency_total_ttc';
4593
		$sql.= ')';
4594
		$sql.= " VALUES (".$this->fk_facture.",";
4595
		$sql.= " ".($this->fk_parent_line>0 ? $this->fk_parent_line:"null").",";
4596
		$sql.= " ".(! empty($this->label)?"'".$this->db->escape($this->label)."'":"null").",";
4597
		$sql.= " '".$this->db->escape($this->desc)."',";
4598
		$sql.= " ".price2num($this->qty).",";
4599
        $sql.= " ".(empty($this->vat_src_code)?"''":"'".$this->db->escape($this->vat_src_code)."'").",";
4600
		$sql.= " ".price2num($this->tva_tx).",";
4601
		$sql.= " ".price2num($this->localtax1_tx).",";
4602
		$sql.= " ".price2num($this->localtax2_tx).",";
4603
		$sql.= " '".$this->db->escape($this->localtax1_type)."',";
4604
		$sql.= " '".$this->db->escape($this->localtax2_type)."',";
4605
		$sql.= ' '.(! empty($this->fk_product)?$this->fk_product:"null").',';
4606
		$sql.= " ".$this->product_type.",";
4607
		$sql.= " ".price2num($this->remise_percent).",";
4608
		$sql.= " ".price2num($this->subprice).",";
4609
		$sql.= ' '.(! empty($this->fk_remise_except)?$this->fk_remise_except:"null").',';
4610
		$sql.= " ".(! empty($this->date_start)?"'".$this->db->idate($this->date_start)."'":"null").",";
4611
		$sql.= " ".(! empty($this->date_end)?"'".$this->db->idate($this->date_end)."'":"null").",";
4612
		$sql.= ' '.$this->fk_code_ventilation.',';
4613
		$sql.= ' '.$this->rang.',';
4614
		$sql.= ' '.$this->special_code.',';
4615
		$sql.= ' '.(! empty($this->fk_fournprice)?$this->fk_fournprice:"null").',';
4616
		$sql.= ' '.price2num($this->pa_ht).',';
4617
		$sql.= " '".$this->db->escape($this->info_bits)."',";
4618
		$sql.= " ".price2num($this->total_ht).",";
4619
		$sql.= " ".price2num($this->total_tva).",";
4620
		$sql.= " ".price2num($this->total_ttc).",";
4621
		$sql.= " ".price2num($this->total_localtax1).",";
4622
		$sql.= " ".price2num($this->total_localtax2);
4623
		$sql.= ", " . $this->situation_percent;
4624
		$sql.= ", " . (!empty($this->fk_prev_id)?$this->fk_prev_id:"null");
4625
		$sql.= ", ".(!$this->fk_unit ? 'NULL' : $this->fk_unit);
4626
		$sql.= ", ".$user->id;
4627
		$sql.= ", ".$user->id;
4628
		$sql.= ", ".(int) $this->fk_multicurrency;
4629
		$sql.= ", '".$this->db->escape($this->multicurrency_code)."'";
4630
		$sql.= ", ".price2num($this->multicurrency_subprice);
4631
		$sql.= ", ".price2num($this->multicurrency_total_ht);
4632
		$sql.= ", ".price2num($this->multicurrency_total_tva);
4633
		$sql.= ", ".price2num($this->multicurrency_total_ttc);
4634
		$sql.= ')';
4635
4636
		dol_syslog(get_class($this)."::insert", LOG_DEBUG);
4637
		$resql=$this->db->query($sql);
4638
		if ($resql)
4639
		{
4640
			$this->id=$this->db->last_insert_id(MAIN_DB_PREFIX.'facturedet');
4641
			$this->rowid=$this->id;	// For backward compatibility
4642
4643
            if (empty($conf->global->MAIN_EXTRAFIELDS_DISABLED)) // For avoid conflicts if trigger used
4644
            {
4645
            	$result=$this->insertExtraFields();
4646
            	if ($result < 0)
4647
            	{
4648
            		$error++;
4649
            	}
4650
            }
4651
4652
			// Si fk_remise_except defini, on lie la remise a la facture
4653
			// ce qui la flague comme "consommee".
4654
			if ($this->fk_remise_except)
4655
			{
4656
				$discount=new DiscountAbsolute($this->db);
4657
				$result=$discount->fetch($this->fk_remise_except);
4658
				if ($result >= 0)
4659
				{
4660
					// Check if discount was found
4661
					if ($result > 0)
4662
					{
4663
					    // Check if discount not already affected to another invoice
4664
						if ($discount->fk_facture_line > 0)
4665
						{
4666
						    if (empty($noerrorifdiscountalreadylinked))
4667
						    {
4668
    							$this->error=$langs->trans("ErrorDiscountAlreadyUsed",$discount->id);
4669
    							dol_syslog(get_class($this)."::insert Error ".$this->error, LOG_ERR);
4670
    							$this->db->rollback();
4671
    							return -3;
4672
						    }
4673
						}
4674
						else
4675
						{
4676
							$result=$discount->link_to_invoice($this->rowid,0);
4677
							if ($result < 0)
4678
							{
4679
								$this->error=$discount->error;
4680
								dol_syslog(get_class($this)."::insert Error ".$this->error, LOG_ERR);
4681
								$this->db->rollback();
4682
								return -3;
4683
							}
4684
						}
4685
					}
4686
					else
4687
					{
4688
						$this->error=$langs->trans("ErrorADiscountThatHasBeenRemovedIsIncluded");
4689
						dol_syslog(get_class($this)."::insert Error ".$this->error, LOG_ERR);
4690
						$this->db->rollback();
4691
						return -3;
4692
					}
4693
				}
4694
				else
4695
				{
4696
					$this->error=$discount->error;
4697
					dol_syslog(get_class($this)."::insert Error ".$this->error, LOG_ERR);
4698
					$this->db->rollback();
4699
					return -3;
4700
				}
4701
			}
4702
4703
			if (! $notrigger)
4704
			{
4705
                // Call trigger
4706
                $result=$this->call_trigger('LINEBILL_INSERT',$user);
4707
                if ($result < 0)
4708
                {
4709
					$this->db->rollback();
4710
					return -2;
4711
				}
4712
                // End call triggers
4713
			}
4714
4715
			$this->db->commit();
4716
			return $this->id;
4717
		}
4718
		else
4719
		{
4720
			$this->error=$this->db->lasterror();
4721
			$this->db->rollback();
4722
			return -2;
4723
		}
4724
	}
4725
4726
	/**
4727
	 *	Update line into database
4728
	 *
4729
	 *	@param		User	$user		User object
4730
	 *	@param		int		$notrigger	Disable triggers
4731
	 *	@return		int					<0 if KO, >0 if OK
4732
	 */
4733
	function update($user='',$notrigger=0)
4734
	{
4735
		global $user,$conf;
4736
4737
		$error=0;
4738
4739
		$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'.
4740
4741
		// Clean parameters
4742
		$this->desc=trim($this->desc);
4743
		if (empty($this->tva_tx)) $this->tva_tx=0;
4744
		if (empty($this->localtax1_tx)) $this->localtax1_tx=0;
4745
		if (empty($this->localtax2_tx)) $this->localtax2_tx=0;
4746
		if (empty($this->localtax1_type)) $this->localtax1_type=0;
4747
		if (empty($this->localtax2_type)) $this->localtax2_type=0;
4748
		if (empty($this->total_localtax1)) $this->total_localtax1=0;
4749
		if (empty($this->total_localtax2)) $this->total_localtax2=0;
4750
		if (empty($this->remise_percent)) $this->remise_percent=0;
4751
		if (empty($this->info_bits)) $this->info_bits=0;
4752
		if (empty($this->special_code)) $this->special_code=0;
4753
		if (empty($this->product_type)) $this->product_type=0;
4754
		if (empty($this->fk_parent_line)) $this->fk_parent_line=0;
4755
		if (! isset($this->situation_percent) || $this->situation_percent > 100 || (string) $this->situation_percent == '') $this->situation_percent = 100;
4756
		if (empty($this->pa_ht)) $this->pa_ht=0;
4757
4758
		if (empty($this->multicurrency_subprice)) $this->multicurrency_subprice=0;
4759
		if (empty($this->multicurrency_total_ht)) $this->multicurrency_total_ht=0;
4760
		if (empty($this->multicurrency_total_tva)) $this->multicurrency_total_tva=0;
4761
		if (empty($this->multicurrency_total_ttc)) $this->multicurrency_total_ttc=0;
4762
4763
		// Check parameters
4764
		if ($this->product_type < 0) return -1;
4765
4766
		// if buy price not defined, define buyprice as configured in margin admin
4767
		if ($this->pa_ht == 0 && $pa_ht_isemptystring)
4768
		{
4769
			if (($result = $this->defineBuyPrice($this->subprice, $this->remise_percent, $this->fk_product)) < 0)
4770
			{
4771
				return $result;
4772
			}
4773
			else
4774
			{
4775
				$this->pa_ht = $result;
4776
			}
4777
		}
4778
4779
		$this->db->begin();
4780
4781
        // Mise a jour ligne en base
4782
        $sql = "UPDATE ".MAIN_DB_PREFIX."facturedet SET";
4783
        $sql.= " description='".$this->db->escape($this->desc)."'";
4784
        $sql.= ", label=".(! empty($this->label)?"'".$this->db->escape($this->label)."'":"null");
4785
        $sql.= ", subprice=".price2num($this->subprice)."";
4786
        $sql.= ", remise_percent=".price2num($this->remise_percent)."";
4787
        if ($this->fk_remise_except) $sql.= ", fk_remise_except=".$this->fk_remise_except;
4788
        else $sql.= ", fk_remise_except=null";
4789
		$sql.= ", vat_src_code = '".(empty($this->vat_src_code)?'':$this->db->escape($this->vat_src_code))."'";
4790
        $sql.= ", tva_tx=".price2num($this->tva_tx)."";
4791
        $sql.= ", localtax1_tx=".price2num($this->localtax1_tx)."";
4792
        $sql.= ", localtax2_tx=".price2num($this->localtax2_tx)."";
4793
		$sql.= ", localtax1_type='".$this->db->escape($this->localtax1_type)."'";
4794
		$sql.= ", localtax2_type='".$this->db->escape($this->localtax2_type)."'";
4795
        $sql.= ", qty=".price2num($this->qty);
4796
        $sql.= ", date_start=".(! empty($this->date_start)?"'".$this->db->idate($this->date_start)."'":"null");
4797
        $sql.= ", date_end=".(! empty($this->date_end)?"'".$this->db->idate($this->date_end)."'":"null");
4798
        $sql.= ", product_type=".$this->product_type;
4799
        $sql.= ", info_bits='".$this->db->escape($this->info_bits)."'";
4800
        $sql.= ", special_code='".$this->db->escape($this->special_code)."'";
4801
        if (empty($this->skip_update_total))
4802
        {
4803
        	$sql.= ", total_ht=".price2num($this->total_ht);
4804
        	$sql.= ", total_tva=".price2num($this->total_tva);
4805
        	$sql.= ", total_ttc=".price2num($this->total_ttc);
4806
        	$sql.= ", total_localtax1=".price2num($this->total_localtax1);
4807
        	$sql.= ", total_localtax2=".price2num($this->total_localtax2);
4808
        }
4809
		$sql.= ", fk_product_fournisseur_price=".(! empty($this->fk_fournprice)?"'".$this->db->escape($this->fk_fournprice)."'":"null");
4810
		$sql.= ", buy_price_ht='".price2num($this->pa_ht)."'";
4811
		$sql.= ", fk_parent_line=".($this->fk_parent_line>0?$this->fk_parent_line:"null");
4812
		if (! empty($this->rang)) $sql.= ", rang=".$this->rang;
4813
		$sql.= ", situation_percent=" . $this->situation_percent;
4814
		$sql.= ", fk_unit=".(!$this->fk_unit ? 'NULL' : $this->fk_unit);
4815
		$sql.= ", fk_user_modif =".$user->id;
4816
4817
		// Multicurrency
4818
		$sql.= ", multicurrency_subprice=".price2num($this->multicurrency_subprice)."";
4819
        $sql.= ", multicurrency_total_ht=".price2num($this->multicurrency_total_ht)."";
4820
        $sql.= ", multicurrency_total_tva=".price2num($this->multicurrency_total_tva)."";
4821
        $sql.= ", multicurrency_total_ttc=".price2num($this->multicurrency_total_ttc)."";
4822
4823
		$sql.= " WHERE rowid = ".$this->rowid;
4824
4825
		dol_syslog(get_class($this)."::update", LOG_DEBUG);
4826
		$resql=$this->db->query($sql);
4827
		if ($resql)
4828
		{
4829
        	if (empty($conf->global->MAIN_EXTRAFIELDS_DISABLED)) // For avoid conflicts if trigger used
4830
        	{
4831
        		$this->id=$this->rowid;
4832
        		$result=$this->insertExtraFields();
4833
        		if ($result < 0)
4834
        		{
4835
        			$error++;
4836
        		}
4837
        	}
4838
4839
			if (! $error && ! $notrigger)
4840
			{
4841
                // Call trigger
4842
                $result=$this->call_trigger('LINEBILL_UPDATE',$user);
4843
                if ($result < 0)
4844
 				{
4845
					$this->db->rollback();
4846
					return -2;
4847
				}
4848
                // End call triggers
4849
			}
4850
			$this->db->commit();
4851
			return 1;
4852
		}
4853
		else
4854
		{
4855
			$this->error=$this->db->error();
4856
			$this->db->rollback();
4857
			return -2;
4858
		}
4859
	}
4860
4861
	/**
4862
	 * 	Delete line in database
4863
	 *  TODO Add param User $user and notrigger (see skeleton)
4864
     *
4865
	 *	@return	    int		           <0 if KO, >0 if OK
4866
	 */
4867
	function delete()
4868
	{
4869
		global $user;
4870
4871
		$this->db->begin();
4872
4873
		// Call trigger
4874
		$result=$this->call_trigger('LINEBILL_DELETE',$user);
4875
		if ($result < 0)
4876
		{
4877
			$this->db->rollback();
4878
			return -1;
4879
		}
4880
		// End call triggers
4881
4882
4883
		$sql = "DELETE FROM ".MAIN_DB_PREFIX."facturedet WHERE rowid = ".$this->rowid;
0 ignored issues
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

4883
		$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...
4884
		dol_syslog(get_class($this)."::delete", LOG_DEBUG);
4885
		if ($this->db->query($sql) )
4886
		{
4887
			$this->db->commit();
4888
			return 1;
4889
		}
4890
		else
4891
		{
4892
			$this->error=$this->db->error()." sql=".$sql;
4893
			$this->db->rollback();
4894
			return -1;
4895
		}
4896
	}
4897
4898
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
4899
	/**
4900
     *	Update DB line fields total_xxx
4901
	 *	Used by migration
4902
	 *
4903
	 *	@return		int		<0 if KO, >0 if OK
4904
	 */
4905
	function update_total()
4906
	{
4907
        // phpcs:enable
4908
		$this->db->begin();
4909
		dol_syslog(get_class($this)."::update_total", LOG_DEBUG);
4910
4911
		// Clean parameters
4912
		if (empty($this->total_localtax1)) $this->total_localtax1=0;
4913
		if (empty($this->total_localtax2)) $this->total_localtax2=0;
4914
4915
		// Mise a jour ligne en base
4916
		$sql = "UPDATE ".MAIN_DB_PREFIX."facturedet SET";
4917
		$sql.= " total_ht=".price2num($this->total_ht)."";
4918
		$sql.= ",total_tva=".price2num($this->total_tva)."";
4919
		$sql.= ",total_localtax1=".price2num($this->total_localtax1)."";
4920
		$sql.= ",total_localtax2=".price2num($this->total_localtax2)."";
4921
		$sql.= ",total_ttc=".price2num($this->total_ttc)."";
4922
		$sql.= " WHERE rowid = ".$this->rowid;
0 ignored issues
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

4922
		$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...
4923
4924
		dol_syslog(get_class($this)."::update_total", LOG_DEBUG);
4925
4926
		$resql=$this->db->query($sql);
4927
		if ($resql)
4928
		{
4929
			$this->db->commit();
4930
			return 1;
4931
		}
4932
		else
4933
		{
4934
			$this->error=$this->db->error();
4935
			$this->db->rollback();
4936
			return -2;
4937
		}
4938
	}
4939
4940
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
4941
	/**
4942
	 * Returns situation_percent of the previous line.
4943
	 * Warning: If invoice is a replacement invoice, this->fk_prev_id is id of the replaced line.
4944
	 *
4945
	 * @param  int     $invoiceid      Invoice id
4946
	 * @param  bool    $include_credit_note		Include credit note or not
4947
	 * @return int                     >= 0
4948
	 */
4949
	function get_prev_progress($invoiceid, $include_credit_note=true)
4950
	{
4951
        // phpcs:enable
4952
		if (is_null($this->fk_prev_id) || empty($this->fk_prev_id) || $this->fk_prev_id == "") {
4953
			return 0;
4954
		} else {
4955
		    // If invoice is not a situation invoice, this->fk_prev_id is used for something else
4956
            $tmpinvoice=new Facture($this->db);
4957
            $tmpinvoice->fetch($invoiceid);
4958
            if ($tmpinvoice->type != Facture::TYPE_SITUATION) return 0;
4959
4960
			$sql = 'SELECT situation_percent FROM ' . MAIN_DB_PREFIX . 'facturedet WHERE rowid=' . $this->fk_prev_id;
4961
			$resql = $this->db->query($sql);
4962
			if ($resql && $resql->num_rows > 0) {
4963
				$res = $this->db->fetch_array($resql);
4964
4965
				$returnPercent = floatval($res['situation_percent']);
4966
4967
				if($include_credit_note) {
4968
4969
				    $sql = 'SELECT fd.situation_percent FROM ' . MAIN_DB_PREFIX . 'facturedet fd';
4970
				    $sql.= ' JOIN ' . MAIN_DB_PREFIX . 'facture f ON (f.rowid = fd.fk_facture) ';
4971
				    $sql.= ' WHERE fd.fk_prev_id =' . $this->fk_prev_id;
4972
				    $sql.= ' AND f.situation_cycle_ref = '.$tmpinvoice->situation_cycle_ref; // Prevent cycle outed
4973
				    $sql.= ' AND f.type = '.Facture::TYPE_CREDIT_NOTE;
4974
4975
				    $res = $this->db->query($sql);
4976
				    if($res) {
4977
				        while($obj = $this->db->fetch_object($res)) {
4978
				            $returnPercent = $returnPercent + floatval($obj->situation_percent);
4979
				        }
4980
				    }
4981
				}
4982
4983
				return $returnPercent;
4984
			} else {
4985
				$this->error = $this->db->error();
4986
				dol_syslog(get_class($this) . "::select Error " . $this->error, LOG_ERR);
4987
				$this->db->rollback();
4988
				return -1;
4989
			}
4990
		}
4991
	}
4992
}
4993