Completed
Branch develop (5ab7fa)
by
unknown
30:39
created

Facture::hasDelay()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 11
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 5
nc 2
nop 0
dl 0
loc 11
rs 9.4285
c 0
b 0
f 0
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
 *
19
 * This program is free software; you can redistribute it and/or modify
20
 * it under the terms of the GNU General Public License as published by
21
 * the Free Software Foundation; either version 3 of the License, or
22
 * (at your option) any later version.
23
 *
24
 * This program is distributed in the hope that it will be useful,
25
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
26
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
27
 * GNU General Public License for more details.
28
 *
29
 * You should have received a copy of the GNU General Public License
30
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
31
 */
32
33
/**
34
 *	\file       htdocs/compta/facture/class/facture.class.php
35
 *	\ingroup    facture
36
 *	\brief      File of class to manage invoices
37
 */
38
39
include_once DOL_DOCUMENT_ROOT.'/core/class/commoninvoice.class.php';
40
require_once DOL_DOCUMENT_ROOT.'/core/class/commonobjectline.class.php';
41
require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
42
require_once DOL_DOCUMENT_ROOT.'/societe/class/client.class.php';
43
require_once DOL_DOCUMENT_ROOT.'/margin/lib/margins.lib.php';
44
require_once DOL_DOCUMENT_ROOT.'/multicurrency/class/multicurrency.class.php';
45
46
/**
47
 *	Class to manage invoices
48
 */
49
class Facture extends CommonInvoice
50
{
51
	public $element='facture';
52
	public $table_element='facture';
53
	public $table_element_line = 'facturedet';
54
	public $fk_element = 'fk_facture';
55
	public $ismultientitymanaged = 1;	// 0=No test on entity, 1=Test with field entity, 2=Test with link by societe
56
	public $picto='bill';
57
58
	/**
59
	 * {@inheritdoc}
60
	 */
61
	protected $table_ref_field = 'facnumber';
62
63
	public $socid;
64
65
	public $author;
66
	public $fk_user_author;
67
	public $fk_user_valid;
68
	public $date;              // Date invoice
69
	public $date_creation;		// Creation date
70
	public $date_validation;	// Validation date
71
	public $datem;
72
	public $ref_client;
73
	public $ref_int;
74
	//Check constants for types
75
	public $type = self::TYPE_STANDARD;
76
77
	//var $amount;
78
	public $remise_absolue;
79
	public $remise_percent;
80
	public $total_ht=0;
81
	public $total_tva=0;
82
	public $total_localtax1=0;
83
	public $total_localtax2=0;
84
	public $total_ttc=0;
85
	public $revenuestamp;
86
87
	//! Fermeture apres paiement partiel: discount_vat, badcustomer, abandon
88
	//! Fermeture alors que aucun paiement: replaced (si remplace), abandon
89
	public $close_code;
90
	//! Commentaire si mis a paye sans paiement complet
91
	public $close_note;
92
	//! 1 if invoice paid COMPLETELY, 0 otherwise (do not use it anymore, use statut and close_code)
93
	public $paye;
94
	//! id of source invoice if replacement invoice or credit note
95
	public $fk_facture_source;
96
	public $linked_objects=array();
97
	public $date_lim_reglement;
98
	public $cond_reglement_code;		// Code in llx_c_paiement
99
	public $mode_reglement_code;		// Code in llx_c_paiement
100
	public $fk_bank;					// Field to store bank id to use when payment mode is withdraw
101
	/**
102
	 * @deprecated
103
	 */
104
	public $products=array();
105
	/**
106
	 * @var FactureLigne[]
107
	 */
108
	public $lines=array();
109
	public $line;
110
	public $extraparams=array();
111
	public $specimen;
112
113
	public $fac_rec;
114
115
	// Multicurrency
116
	public $fk_multicurrency;
117
	public $multicurrency_code;
118
	public $multicurrency_tx;
119
	public $multicurrency_total_ht;
120
	public $multicurrency_total_tva;
121
	public $multicurrency_total_ttc;
122
123
	/**
124
	 * @var int Situation cycle reference number
125
	 */
126
	public $situation_cycle_ref;
127
128
	/**
129
	 * @var int Situation counter inside the cycle
130
	 */
131
	public $situation_counter;
132
133
	/**
134
	 * @var bool Final situation flag
135
	 */
136
	public $situation_final;
137
138
	/**
139
	 * @var array Table of previous situations
140
	 */
141
	public $tab_previous_situation_invoice=array();
142
143
	/**
144
	 * @var array Table of next situations
145
	 */
146
	public $tab_next_situation_invoice=array();
147
148
	public $oldcopy;
149
150
    /**
151
     * Standard invoice
152
     */
153
    const TYPE_STANDARD = 0;
154
155
    /**
156
     * Replacement invoice
157
     */
158
    const TYPE_REPLACEMENT = 1;
159
160
    /**
161
     * Credit note invoice
162
     */
163
    const TYPE_CREDIT_NOTE = 2;
164
165
    /**
166
     * Deposit invoice
167
     */
168
    const TYPE_DEPOSIT = 3;
169
170
    /**
171
     * Proforma invoice (should not be used. a proforma is an order)
172
     */
173
    const TYPE_PROFORMA = 4;
174
175
	/**
176
	 * Situation invoice
177
	 */
178
	const TYPE_SITUATION = 5;
179
180
	/**
181
	 * Draft
182
	 */
183
	const STATUS_DRAFT = 0;
184
185
	/**
186
	 * Validated (need to be paid)
187
	 */
188
	const STATUS_VALIDATED = 1;
189
190
	/**
191
	 * Classified paid.
192
	 * If paid partially, $this->close_code can be:
193
	 * - CLOSECODE_DISCOUNTVAT
194
	 * - CLOSECODE_BADDEBT
195
	 * If paid completelly, this->close_code will be null
196
	 */
197
	const STATUS_CLOSED = 2;
198
199
	/**
200
	 * Classified abandoned and no payment done.
201
	 * $this->close_code can be:
202
	 * - CLOSECODE_BADDEBT
203
	 * - CLOSECODE_ABANDONED
204
	 * - CLOSECODE_REPLACED
205
	 */
206
	const STATUS_ABANDONED = 3;
207
208
	const CLOSECODE_DISCOUNTVAT = 'discount_vat';
209
	const CLOSECODE_BADDEBT = 'badcustomer';
210
	const CLOSECODE_ABANDONED = 'abandon';
211
	const CLOSECODE_REPLACED = 'replaced';
212
213
	/**
214
	 * 	Constructor
215
	 *
216
	 * 	@param	DoliDB		$db			Database handler
217
	 */
218
	function __construct($db)
219
	{
220
		$this->db = $db;
221
	}
222
223
	/**
224
	 *	Create invoice in database.
225
	 *  Note: this->ref can be set or empty. If empty, we will use "(PROV999)"
226
	 *  Note: this->fac_rec must be set to create invoice from a recurring invoice
227
	 *
228
	 *	@param	User	$user      		Object user that create
229
	 *	@param  int		$notrigger		1=Does not execute triggers, 0 otherwise
230
	 * 	@param	int		$forceduedate	1=Do not recalculate due date from payment condition but force it with value
231
	 *	@return	int						<0 if KO, >0 if OK
232
	 */
233
	function create($user,$notrigger=0,$forceduedate=0)
234
	{
235
		global $langs,$conf,$mysoc,$hookmanager;
236
		$error=0;
237
238
		// Clean parameters
239
		if (empty($this->type)) $this->type = self::TYPE_STANDARD;
240
		$this->ref_client=trim($this->ref_client);
241
		$this->note=(isset($this->note) ? trim($this->note) : trim($this->note_private)); // deprecated
242
		$this->note_private=(isset($this->note_private) ? trim($this->note_private) : trim($this->note_private));
243
		$this->note_public=trim($this->note_public);
244
		if (! $this->cond_reglement_id) $this->cond_reglement_id = 0;
245
		if (! $this->mode_reglement_id) $this->mode_reglement_id = 0;
246
		$this->brouillon = 1;
247
        if (empty($this->entity)) $this->entity = $conf->entity;
248
249
		// Multicurrency (test on $this->multicurrency_tx because we should take the default rate only if not using origin rate)
250
		if (!empty($this->multicurrency_code) && empty($this->multicurrency_tx)) list($this->fk_multicurrency,$this->multicurrency_tx) = MultiCurrency::getIdAndTxFromCode($this->db, $this->multicurrency_code);
251
		else $this->fk_multicurrency = MultiCurrency::getIdFromCode($this->db, $this->multicurrency_code);
252
		if (empty($this->fk_multicurrency))
253
		{
254
			$this->multicurrency_code = $conf->currency;
255
			$this->fk_multicurrency = 0;
256
			$this->multicurrency_tx = 1;
257
		}
258
259
		dol_syslog(get_class($this)."::create user=".$user->id);
260
261
		// Check parameters
262
		if (empty($this->date) || empty($user->id))
263
		{
264
			$this->error="ErrorBadParameter";
265
			dol_syslog(get_class($this)."::create Try to create an invoice with an empty parameter (user, date, ...)", LOG_ERR);
266
			return -3;
267
		}
268
		$soc = new Societe($this->db);
269
		$result=$soc->fetch($this->socid);
270
		if ($result < 0)
271
		{
272
			$this->error="Failed to fetch company: ".$soc->error;
273
			dol_syslog(get_class($this)."::create ".$this->error, LOG_ERR);
274
			return -2;
275
		}
276
277
		$now=dol_now();
278
279
		$this->db->begin();
280
281
		// Create invoice from a template invoice
282
		if ($this->fac_rec > 0)
283
		{
284
		    $this->fk_fac_rec_source = $this->fac_rec;
285
286
			require_once DOL_DOCUMENT_ROOT.'/compta/facture/class/facture-rec.class.php';
287
			$_facrec = new FactureRec($this->db);
288
			$result=$_facrec->fetch($this->fac_rec);
289
			$result=$_facrec->fetchObjectLinked();       // This load $_facrec->linkedObjectsIds
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $result is correct as $_facrec->fetchObjectLinked() (which targets CommonObject::fetchObjectLinked()) seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
290
291
			$originaldatewhen = $_facrec->date_when;
292
293
			$this->socid 		     = $_facrec->socid;  // Invoice created on same thirdparty than template
294
			$this->entity            = $_facrec->entity; // Invoice created in same entity than template
295
296
			// 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
297
			$this->fk_project        = GETPOST('projectid','int') > 0 ? GETPOST('projectid','int') : $_facrec->fk_project;
0 ignored issues
show
Documentation Bug introduced by
It seems like GETPOST('projectid', 'in... : $_facrec->fk_project can also be of type string or array<integer,string>. However, the property $fk_project 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...
298
			$this->note_public       = GETPOST('note_public','none') ? GETPOST('note_public','none') : $_facrec->note_public;
299
			$this->note_private      = GETPOST('note_private','none') ? GETPOST('note_private','none') : $_facrec->note_private;
300
			$this->modelpdf          = GETPOST('model') ? GETPOST('model') : $_facrec->modelpdf;
301
			$this->cond_reglement_id = GETPOST('cond_reglement_id') > 0 ? GETPOST('cond_reglement_id') : $_facrec->cond_reglement_id;
0 ignored issues
show
Documentation Bug introduced by
It seems like GETPOST('cond_reglement_...crec->cond_reglement_id can also be of type string or array<integer,string>. However, the property $cond_reglement_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...
302
			$this->mode_reglement_id = GETPOST('mode_reglement_id') > 0 ? GETPOST('mode_reglement_id') : $_facrec->mode_reglement_id;
0 ignored issues
show
Documentation Bug introduced by
It seems like GETPOST('mode_reglement_...crec->mode_reglement_id can also be of type string or array<integer,string>. However, the property $mode_reglement_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...
303
			$this->fk_account        = GETPOST('fk_account') > 0 ? GETPOST('fk_account') : $_facrec->fk_account;
0 ignored issues
show
Documentation Bug introduced by
It seems like GETPOST('fk_account') > ... : $_facrec->fk_account can also be of type string or array<integer,string>. However, the property $fk_account 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...
304
305
			// Set here to have this defined for substitution into notes, should be recalculated after adding lines to get same result
306
			$this->total_ht          = $_facrec->total_ht;
0 ignored issues
show
Documentation Bug introduced by
The property $total_ht was declared of type integer, but $_facrec->total_ht is of type double. 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...
307
			$this->total_ttc         = $_facrec->total_ttc;
0 ignored issues
show
Documentation Bug introduced by
The property $total_ttc was declared of type integer, but $_facrec->total_ttc is of type double. 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...
308
309
			// Fields always coming from template
310
			$this->remise_absolue    = $_facrec->remise_absolue;
311
			$this->remise_percent    = $_facrec->remise_percent;
312
			$this->fk_incoterms		 = $_facrec->fk_incoterms;
313
			$this->location_incoterms= $_facrec->location_incoterms;
314
315
			// Clean parameters
316
			if (! $this->type) $this->type = self::TYPE_STANDARD;
317
			$this->ref_client=trim($this->ref_client);
318
			$this->note_public=trim($this->note_public);
319
			$this->note_private=trim($this->note_private);
320
		    $this->note_private=dol_concatdesc($this->note_private, $langs->trans("GeneratedFromRecurringInvoice", $_facrec->ref));
321
322
		    $this->array_options=$_facrec->array_options;
323
324
			//if (! $this->remise) $this->remise = 0;
325
			if (! $this->mode_reglement_id) $this->mode_reglement_id = 0;
326
			$this->brouillon = 1;
327
328
			$this->linked_objects = $_facrec->linkedObjectsIds;
329
330
			$forceduedate = $this->calculate_date_lim_reglement();
331
332
			// For recurring invoices, update date and number of last generation of recurring template invoice, before inserting new invoice
333
			if ($_facrec->frequency > 0)
334
			{
335
			    dol_syslog("This is a recurring invoice so we set date_last_gen and next date_when");
336
			    if (empty($_facrec->date_when)) $_facrec->date_when = $now;
337
                $next_date = $_facrec->getNextDate();   // Calculate next date
338
                $result = $_facrec->setValueFrom('date_last_gen', $now, '', null, 'date', '', $user, '');
339
                //$_facrec->setValueFrom('nb_gen_done', $_facrec->nb_gen_done + 1);		// Not required, +1 already included into setNextDate when second param is 1.
340
                $result = $_facrec->setNextDate($next_date,1);
341
			}
342
343
			// Define lang of customer
344
			$outputlangs = $langs;
345
			$newlang='';
346
347
			if ($conf->global->MAIN_MULTILANGS && empty($newlang) && isset($this->thirdparty->default_lang)) $newlang=$this->thirdparty->default_lang;  // for proposal, order, invoice, ...
348
			if ($conf->global->MAIN_MULTILANGS && empty($newlang) && isset($this->default_lang)) $newlang=$this->default_lang;                  // for thirdparty
349
			if (! empty($newlang))
350
			{
351
			    $outputlangs = new Translate("",$conf);
352
			    $outputlangs->setDefaultLang($newlang);
353
			}
354
355
			// Array of possible substitutions (See also file mailing-send.php that should manage same substitutions)
356
			$substitutionarray=getCommonSubstitutionArray($outputlangs, 0, null, $this);
357
			$substitutionarray['__INVOICE_PREVIOUS_MONTH__'] = dol_print_date(dol_time_plus_duree($this->date, -1, 'm'), '%m');
358
			$substitutionarray['__INVOICE_MONTH__'] = dol_print_date($this->date, '%m');
359
			$substitutionarray['__INVOICE_NEXT_MONTH__'] = dol_print_date(dol_time_plus_duree($this->date, 1, 'm'), '%m');
360
			$substitutionarray['__INVOICE_PREVIOUS_MONTH_TEXT__'] = dol_print_date(dol_time_plus_duree($this->date, -1, 'm'), '%B');
361
			$substitutionarray['__INVOICE_MONTH_TEXT__'] = dol_print_date($this->date, '%B');
362
			$substitutionarray['__INVOICE_NEXT_MONTH_TEXT__'] = dol_print_date(dol_time_plus_duree($this->date, 1, 'm'), '%B');
363
			$substitutionarray['__INVOICE_PREVIOUS_YEAR__'] = dol_print_date(dol_time_plus_duree($this->date, -1, 'y'), '%Y');
364
			$substitutionarray['__INVOICE_YEAR__'] = dol_print_date($this->date, '%Y');
365
			$substitutionarray['__INVOICE_NEXT_YEAR__'] = dol_print_date(dol_time_plus_duree($this->date, 1, 'y'), '%Y');
366
			// Only for tempalte invoice
367
			$substitutionarray['__INVOICE_DATE_NEXT_INVOICE_BEFORE_GEN__'] = dol_print_date($originaldatewhen, 'dayhour');
368
			$substitutionarray['__INVOICE_DATE_NEXT_INVOICE_AFTER_GEN__'] = dol_print_date(dol_time_plus_duree($originaldatewhen, $_facrec->frequency, $_facrec->unit_frequency), 'dayhour');
369
370
			//var_dump($substitutionarray);exit;
371
372
			$substitutionisok=true;
373
			complete_substitutions_array($substitutionarray, $outputlangs);
374
375
			$this->note_public=make_substitutions($this->note_public,$substitutionarray);
376
			$this->note_private=make_substitutions($this->note_private,$substitutionarray);
377
		}
378
379
		// Define due date if not already defined
380
		$datelim=(empty($forceduedate)?$this->calculate_date_lim_reglement():$forceduedate);
381
382
		// Insert into database
383
		$socid  = $this->socid;
384
385
		$sql = "INSERT INTO ".MAIN_DB_PREFIX."facture (";
386
		$sql.= " facnumber";
387
		$sql.= ", entity";
388
		$sql.= ", ref_ext";
389
		$sql.= ", type";
390
		$sql.= ", fk_soc";
391
		$sql.= ", datec";
392
		$sql.= ", remise_absolue";
393
		$sql.= ", remise_percent";
394
		$sql.= ", datef";
395
		$sql.= ", date_pointoftax";
396
		$sql.= ", note_private";
397
		$sql.= ", note_public";
398
		$sql.= ", ref_client, ref_int";
399
        $sql.= ", fk_account";
400
		$sql.= ", fk_fac_rec_source, fk_facture_source, fk_user_author, fk_projet";
401
		$sql.= ", fk_cond_reglement, fk_mode_reglement, date_lim_reglement, model_pdf";
402
		$sql.= ", situation_cycle_ref, situation_counter, situation_final";
403
		$sql.= ", fk_incoterms, location_incoterms";
404
        $sql.= ", fk_multicurrency";
405
        $sql.= ", multicurrency_code";
406
        $sql.= ", multicurrency_tx";
407
		$sql.= ")";
408
		$sql.= " VALUES (";
409
		$sql.= "'(PROV)'";
410
		$sql.= ", ".$this->entity;
411
		$sql.= ", ".($this->ref_ext?"'".$this->db->escape($this->ref_ext)."'":"null");
412
		$sql.= ", '".$this->db->escape($this->type)."'";
413
		$sql.= ", '".$socid."'";
414
		$sql.= ", '".$this->db->idate($now)."'";
415
		$sql.= ", ".($this->remise_absolue>0?$this->remise_absolue:'NULL');
416
		$sql.= ", ".($this->remise_percent>0?$this->remise_percent:'NULL');
417
		$sql.= ", '".$this->db->idate($this->date)."'";
418
		$sql.= ", ".(strval($this->date_pointoftax)!='' ? "'".$this->db->idate($this->date_pointoftax)."'" : 'null');
419
		$sql.= ", ".($this->note_private?"'".$this->db->escape($this->note_private)."'":"null");
420
		$sql.= ", ".($this->note_public?"'".$this->db->escape($this->note_public)."'":"null");
421
		$sql.= ", ".($this->ref_client?"'".$this->db->escape($this->ref_client)."'":"null");
422
		$sql.= ", ".($this->ref_int?"'".$this->db->escape($this->ref_int)."'":"null");
423
		$sql.= ", ".($this->fk_account>0?$this->fk_account:'NULL');
424
		$sql.= ", ".($this->fk_fac_rec_source?"'".$this->db->escape($this->fk_fac_rec_source)."'":"null");
425
		$sql.= ", ".($this->fk_facture_source?"'".$this->db->escape($this->fk_facture_source)."'":"null");
426
		$sql.= ", ".($user->id > 0 ? "'".$user->id."'":"null");
427
		$sql.= ", ".($this->fk_project?$this->fk_project:"null");
428
		$sql.= ", ".$this->cond_reglement_id;
429
		$sql.= ", ".$this->mode_reglement_id;
430
		$sql.= ", '".$this->db->idate($datelim)."', '".$this->db->escape($this->modelpdf)."'";
431
		$sql.= ", ".($this->situation_cycle_ref?"'".$this->db->escape($this->situation_cycle_ref)."'":"null");
432
		$sql.= ", ".($this->situation_counter?"'".$this->db->escape($this->situation_counter)."'":"null");
433
		$sql.= ", ".($this->situation_final?$this->situation_final:0);
434
		$sql.= ", ".(int) $this->fk_incoterms;
435
        $sql.= ", '".$this->db->escape($this->location_incoterms)."'";
436
		$sql.= ", ".(int) $this->fk_multicurrency;
437
		$sql.= ", '".$this->db->escape($this->multicurrency_code)."'";
438
		$sql.= ", ".(double) $this->multicurrency_tx;
439
		$sql.=")";
440
441
		$resql=$this->db->query($sql);
442
		if ($resql)
443
		{
444
			$this->id = $this->db->last_insert_id(MAIN_DB_PREFIX.'facture');
445
446
			// Update ref with new one
447
			$this->ref='(PROV'.$this->id.')';
448
			$sql = 'UPDATE '.MAIN_DB_PREFIX."facture SET facnumber='".$this->db->escape($this->ref)."' WHERE rowid=".$this->id;
449
450
			$resql=$this->db->query($sql);
451
			if (! $resql) $error++;
452
453
			if (! empty($this->linkedObjectsIds) && empty($this->linked_objects))	// To use new linkedObjectsIds instead of old linked_objects
454
			{
455
				$this->linked_objects = $this->linkedObjectsIds;	// TODO Replace linked_objects with linkedObjectsIds
456
			}
457
458
			// Add object linked
459
			if (! $error && $this->id && is_array($this->linked_objects) && ! empty($this->linked_objects))
460
			{
461
				foreach($this->linked_objects as $origin => $tmp_origin_id)
462
				{
463
				    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, ...))
464
				    {
465
				        foreach($tmp_origin_id as $origin_id)
466
				        {
467
				            $ret = $this->add_object_linked($origin, $origin_id);
468
				            if (! $ret)
469
				            {
470
				                $this->error=$this->db->lasterror();
471
				                $error++;
472
				            }
473
				        }
474
				    }
475
				    else                                // Old behaviour, if linked_object has only one link per type, so is something like array('contract'=>id1))
476
				    {
477
				        $origin_id = $tmp_origin_id;
478
    					$ret = $this->add_object_linked($origin, $origin_id);
479
    					if (! $ret)
480
    					{
481
    						$this->error=$this->db->lasterror();
482
    						$error++;
483
    					}
484
				    }
485
				}
486
			}
487
488
			if (! $error && $this->id && ! empty($conf->global->MAIN_PROPAGATE_CONTACTS_FROM_ORIGIN) && ! empty($this->origin) && ! empty($this->origin_id))   // Get contact from origin object
489
			{
490
				$originforcontact = $this->origin;
491
				$originidforcontact = $this->origin_id;
492
				if ($originforcontact == 'shipping')     // shipment and order share the same contacts. If creating from shipment we take data of order
493
				{
494
				    require_once DOL_DOCUMENT_ROOT . '/expedition/class/expedition.class.php';
495
				    $exp = new Expedition($this->db);
496
				    $exp->fetch($this->origin_id);
497
				    $exp->fetchObjectLinked();
498
				    if (count($exp->linkedObjectsIds['commande']) > 0)
499
				    {
500
				        foreach ($exp->linkedObjectsIds['commande'] as $key => $value)
0 ignored issues
show
Bug introduced by
The expression $exp->linkedObjectsIds['commande'] of type integer is not traversable.
Loading history...
501
				        {
502
				            $originforcontact = 'commande';
503
				            if (is_object($value)) $originidforcontact = $value->id;
504
				            else $originidforcontact = $value;
505
				            break; // We take first one
506
				        }
507
				    }
508
				}
509
510
				$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";
511
				$sqlcontact.= " WHERE element_id = ".$originidforcontact." AND ec.fk_c_type_contact = ctc.rowid AND ctc.element = '".$originforcontact."'";
512
513
				$resqlcontact = $this->db->query($sqlcontact);
514
				if ($resqlcontact)
515
				{
516
				    while($objcontact = $this->db->fetch_object($resqlcontact))
517
				    {
518
				        //print $objcontact->code.'-'.$objcontact->source.'-'.$objcontact->fk_socpeople."\n";
519
				        $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
520
				    }
521
				}
522
				else dol_print_error($resqlcontact);
523
			}
524
525
			/*
526
			 *  Insert lines of invoices into database
527
			 */
528
			if (count($this->lines) && is_object($this->lines[0]))	// If this->lines is array of InvoiceLines (preferred mode)
529
			{
530
				$fk_parent_line = 0;
531
532
				dol_syslog("There is ".count($this->lines)." lines that are invoice lines objects");
533
				foreach ($this->lines as $i => $val)
534
				{
535
					$newinvoiceline=$this->lines[$i];
536
					$newinvoiceline->fk_facture=$this->id;
537
538
					// TODO This seems not used. Here we put origin 'facture' but after,  we put an id of object !
539
					$newinvoiceline->origin = $this->element;
540
                    $newinvoiceline->origin_id = $this->lines[$i]->id;
541
542
					if ($result >= 0)
543
					{
544
						// Reset fk_parent_line for no child products and special product
545
						if (($newinvoiceline->product_type != 9 && empty($newinvoiceline->fk_parent_line)) || $newinvoiceline->product_type == 9) {
546
							$fk_parent_line = 0;
547
						}
548
549
						$newinvoiceline->fk_parent_line=$fk_parent_line;
550
						$result=$newinvoiceline->insert();
551
552
						// Defined the new fk_parent_line
553
						if ($result > 0 && $newinvoiceline->product_type == 9) {
554
							$fk_parent_line = $result;
555
						}
556
					}
557
					if ($result < 0)
558
					{
559
						$this->error=$newinvoiceline->error;
560
						$error++;
561
						break;
562
					}
563
				}
564
			}
565
			else	// If this->lines is an array of invoice line arrays
566
			{
567
				$fk_parent_line = 0;
568
569
				dol_syslog("There is ".count($this->lines)." lines that are array lines");
570
571
				foreach ($this->lines as $i => $val)
572
				{
573
                	$line = $this->lines[$i];
574
575
                	// Test and convert into object this->lines[$i]. When coming from REST API, we may still have an array
576
				    //if (! is_object($line)) $line=json_decode(json_encode($line), FALSE);  // convert recursively array into object.
577
                	if (! is_object($line)) $line = (object) $line;
578
579
				    if ($result >= 0)
580
					{
581
						// Reset fk_parent_line for no child products and special product
582
						if (($line->product_type != 9 && empty($line->fk_parent_line)) || $line->product_type == 9) {
583
							$fk_parent_line = 0;
584
						}
585
586
						// Complete vat rate with code
587
						$vatrate = $line->tva_tx;
588
						if ($line->vat_src_code && ! preg_match('/\(.*\)/', $vatrate)) $vatrate.=' ('.$line->vat_src_code.')';
589
590
						$result = $this->addline(
591
							$line->desc,
592
							$line->subprice,
593
							$line->qty,
594
							$vatrate,
595
							$line->localtax1_tx,
596
							$line->localtax2_tx,
597
							$line->fk_product,
598
							$line->remise_percent,
599
							$line->date_start,
600
							$line->date_end,
601
							$line->fk_code_ventilation,
602
							$line->info_bits,
603
							$line->fk_remise_except,
604
							'HT',
605
							0,
606
							$line->product_type,
607
							$line->rang,
608
							$line->special_code,
609
                            $this->element,
610
                            $line->id,
611
							$fk_parent_line,
612
							$line->fk_fournprice,
613
							$line->pa_ht,
614
							$line->label,
615
							$line->array_options,
616
							$line->situation_percent,
617
							$line->fk_prev_id,
618
							$line->fk_unit,
619
							$line->pu_ht_devise
620
						);
621
						if ($result < 0)
622
						{
623
							$this->error=$this->db->lasterror();
624
							dol_print_error($this->db);
625
							$this->db->rollback();
626
							return -1;
627
						}
628
629
						// Defined the new fk_parent_line
630
						if ($result > 0 && $line->product_type == 9) {
631
							$fk_parent_line = $result;
632
						}
633
					}
634
				}
635
			}
636
637
			/*
638
			 * Insert lines of predefined invoices
639
			 */
640
			if (! $error && $this->fac_rec > 0)
641
			{
642
				foreach ($_facrec->lines as $i => $val)
643
				{
644
					if ($_facrec->lines[$i]->fk_product)
645
					{
646
						$prod = new Product($this->db);
647
						$res=$prod->fetch($_facrec->lines[$i]->fk_product);
0 ignored issues
show
Bug introduced by
The variable $_facrec does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
648
					}
649
					$tva_tx = get_default_tva($mysoc,$soc,$prod->id);
0 ignored issues
show
Bug introduced by
The variable $prod does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
650
					$tva_npr = get_default_npr($mysoc,$soc,$prod->id);
651
					if (empty($tva_tx)) $tva_npr=0;
652
					$localtax1_tx=get_localtax($tva_tx,1,$soc,$mysoc,$tva_npr);
653
					$localtax2_tx=get_localtax($tva_tx,2,$soc,$mysoc,$tva_npr);
654
655
					$result_insert = $this->addline(
656
						$_facrec->lines[$i]->desc,
657
						$_facrec->lines[$i]->subprice,
658
						$_facrec->lines[$i]->qty,
659
						$tva_tx,
660
						$localtax1_tx,
661
						$localtax2_tx,
662
						$_facrec->lines[$i]->fk_product,
663
						$_facrec->lines[$i]->remise_percent,
664
						'','',0,$tva_npr,'','HT',0,
665
						$_facrec->lines[$i]->product_type,
666
						$_facrec->lines[$i]->rang,
667
						$_facrec->lines[$i]->special_code,
668
						'',
669
						0,
670
						0,
671
						null,
672
						0,
673
						$_facrec->lines[$i]->label,
674
						empty($_facrec->lines[$i]->array_options)?null:$_facrec->lines[$i]->array_options,
675
						$_facrec->lines[$i]->situation_percent,
676
						'',
677
						$_facrec->lines[$i]->fk_unit,
678
						$_facrec->lines[$i]->pu_ht_devise
679
					);
680
681
					if ( $result_insert < 0)
682
					{
683
						$error++;
684
						$this->error=$this->db->error();
685
						break;
686
					}
687
				}
688
			}
689
690
			if (! $error)
691
			{
692
693
				$result=$this->update_price(1);
694
				if ($result > 0)
695
				{
696
					$action='create';
697
698
					// Actions on extra fields (by external module or standard code)
699
					// TODO le hook fait double emploi avec le trigger !!
700
					/*
701
					$hookmanager->initHooks(array('invoicedao'));
702
					$parameters=array('invoiceid'=>$this->id);
703
					$reshook=$hookmanager->executeHooks('insertExtraFields',$parameters,$this,$action); // Note that $action and $object may have been modified by some hooks
704
					if (empty($reshook))
705
					{
706
						if (empty($conf->global->MAIN_EXTRAFIELDS_DISABLED)) // For avoid conflicts if trigger used
707
						{*/
708
					if (! $error)
709
					{
710
					    $result=$this->insertExtraFields();
711
					    if ($result < 0) $error++;
712
					}
713
						/*}
714
					}
715
					else if ($reshook < 0) $error++;*/
716
717
                    // Call trigger
718
                    $result=$this->call_trigger('BILL_CREATE',$user);
719
                    if ($result < 0) $error++;
720
                    // End call triggers
721
722
					if (! $error)
723
					{
724
						$this->db->commit();
725
						return $this->id;
726
					}
727
					else
728
					{
729
						$this->db->rollback();
730
						return -4;
731
					}
732
				}
733
				else
734
				{
735
					$this->error=$langs->trans('FailedToUpdatePrice');
736
					$this->db->rollback();
737
					return -3;
738
				}
739
			}
740
			else
741
			{
742
				dol_syslog(get_class($this)."::create error ".$this->error, LOG_ERR);
743
				$this->db->rollback();
744
				return -2;
745
			}
746
		}
747
		else
748
		{
749
			$this->error=$this->db->error();
750
			$this->db->rollback();
751
			return -1;
752
		}
753
	}
754
755
756
	/**
757
	 *	Create a new invoice in database from current invoice
758
	 *
759
	 *	@param      User	$user    		Object user that ask creation
760
	 *	@param		int		$invertdetail	Reverse sign of amounts for lines
761
	 *	@return		int						<0 if KO, >0 if OK
762
	 */
763
	function createFromCurrent(User $user, $invertdetail=0)
764
	{
765
		global $conf;
766
767
		// Charge facture source
768
		$facture=new Facture($this->db);
769
770
                $this->fetch_optionals();
771
                if(!empty($this->array_options)){
772
                    $facture->array_options = $this->array_options;
773
                }
774
775
                foreach($this->lines as &$line){
776
                    $line->fetch_optionals();//fetch extrafields
777
                }
778
779
		$facture->fk_facture_source = $this->fk_facture_source;
780
		$facture->type 			    = $this->type;
781
		$facture->socid 		    = $this->socid;
782
		$facture->date              = $this->date;
783
		$facture->date_pointoftax   = $this->date_pointoftax;
784
		$facture->note_public       = $this->note_public;
785
		$facture->note_private      = $this->note_private;
786
		$facture->ref_client        = $this->ref_client;
787
		$facture->modelpdf          = $this->modelpdf;
788
		$facture->fk_project        = $this->fk_project;
789
		$facture->cond_reglement_id = $this->cond_reglement_id;
790
		$facture->mode_reglement_id = $this->mode_reglement_id;
791
		$facture->remise_absolue    = $this->remise_absolue;
792
		$facture->remise_percent    = $this->remise_percent;
793
794
		$facture->origin                        = $this->origin;
795
		$facture->origin_id                     = $this->origin_id;
796
797
		$facture->lines		    	= $this->lines;	// Tableau des lignes de factures
798
		$facture->products		    = $this->lines;	// Tant que products encore utilise
799
		$facture->situation_counter = $this->situation_counter;
800
		$facture->situation_cycle_ref=$this->situation_cycle_ref;
801
		$facture->situation_final  = $this->situation_final;
802
803
		// Loop on each line of new invoice
804
		foreach($facture->lines as $i => $tmpline)
805
		{
806
			$facture->lines[$i]->fk_prev_id = $this->lines[$i]->rowid;
807
			if ($invertdetail)
808
			{
809
				$facture->lines[$i]->subprice  = -$facture->lines[$i]->subprice;
810
				$facture->lines[$i]->total_ht  = -$facture->lines[$i]->total_ht;
811
				$facture->lines[$i]->total_tva = -$facture->lines[$i]->total_tva;
812
				$facture->lines[$i]->total_localtax1 = -$facture->lines[$i]->total_localtax1;
813
				$facture->lines[$i]->total_localtax2 = -$facture->lines[$i]->total_localtax2;
814
				$facture->lines[$i]->total_ttc = -$facture->lines[$i]->total_ttc;
815
			}
816
		}
817
818
		dol_syslog(get_class($this)."::createFromCurrent invertdetail=".$invertdetail." socid=".$this->socid." nboflines=".count($facture->lines));
819
820
		$facid = $facture->create($user);
821
		if ($facid <= 0)
822
		{
823
			$this->error=$facture->error;
824
			$this->errors=$facture->errors;
825
		}
826
		elseif ($this->type == self::TYPE_SITUATION && !empty($conf->global->INVOICE_USE_SITUATION))
827
		{
828
			$this->fetchObjectLinked('', '', $object->id, 'facture');
0 ignored issues
show
Bug introduced by
The variable $object does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
829
830
			foreach ($this->linkedObjectsIds as $typeObject => $Tfk_object)
831
			{
832
				foreach ($Tfk_object as $fk_object)
0 ignored issues
show
Bug introduced by
The expression $Tfk_object of type integer is not traversable.
Loading history...
833
				{
834
					$facture->add_object_linked($typeObject, $fk_object);
835
				}
836
			}
837
838
			$facture->add_object_linked('facture', $this->fk_facture_source);
839
		}
840
841
		return $facid;
842
	}
843
844
845
	/**
846
	 *		Load an object from its id and create a new one in database
847
	 *
848
	 *		@param		int				$socid			Id of thirdparty
849
	 * 	 	@return		int								New id of clone
850
	 */
851
	function createFromClone($socid=0)
852
	{
853
		global $user,$hookmanager;
854
855
		$error=0;
856
857
		$this->context['createfromclone'] = 'createfromclone';
858
859
		$this->db->begin();
860
861
		// get extrafields so they will be clone
862
		foreach($this->lines as $line)
863
			$line->fetch_optionals($line->rowid);
864
865
		// Load source object
866
		$objFrom = clone $this;
867
868
869
870
		// Change socid if needed
871
		if (! empty($socid) && $socid != $this->socid)
872
		{
873
			$objsoc = new Societe($this->db);
874
875
			if ($objsoc->fetch($socid)>0)
876
			{
877
				$this->socid 				= $objsoc->id;
878
				$this->cond_reglement_id	= (! empty($objsoc->cond_reglement_id) ? $objsoc->cond_reglement_id : 0);
879
				$this->mode_reglement_id	= (! empty($objsoc->mode_reglement_id) ? $objsoc->mode_reglement_id : 0);
880
				$this->fk_project			= '';
881
				$this->fk_delivery_address	= '';
882
			}
883
884
			// TODO Change product price if multi-prices
885
		}
886
887
		$this->id=0;
888
		$this->statut= self::STATUS_DRAFT;
889
890
		// Clear fields
891
		$this->date               = dol_now();	// Date of invoice is set to current date when cloning. // TODO Best is to ask date into confirm box
892
		$this->user_author        = $user->id;
893
		$this->user_valid         = '';
894
		$this->fk_facture_source  = 0;
895
		$this->date_creation      = '';
896
		$this->date_validation    = '';
897
		$this->ref_client         = '';
898
		$this->close_code         = '';
899
		$this->close_note         = '';
900
		$this->products = $this->lines;	// Tant que products encore utilise
901
902
		// Loop on each line of new invoice
903
		foreach($this->lines as $i => $line)
904
		{
905
			if (($this->lines[$i]->info_bits & 0x02) == 0x02)	// We do not clone line of discounts
906
			{
907
				unset($this->lines[$i]);
908
				unset($this->products[$i]);	// Tant que products encore utilise
909
			}
910
		}
911
912
		// Create clone
913
		$result=$this->create($user);
914
		if ($result < 0) $error++;
915
		else {
916
			// copy internal contacts
917
			if ($this->copy_linked_contact($objFrom, 'internal') < 0)
918
				$error++;
919
920
			// copy external contacts if same company
921
			elseif ($objFrom->socid == $this->socid)
922
			{
923
				if ($this->copy_linked_contact($objFrom, 'external') < 0)
924
					$error++;
925
			}
926
		}
927
928
		if (! $error)
929
		{
930
			// Hook of thirdparty module
931
			if (is_object($hookmanager))
932
			{
933
				$parameters=array('objFrom'=>$objFrom);
934
				$action='';
935
				$reshook=$hookmanager->executeHooks('createFrom',$parameters,$this,$action);    // Note that $action and $object may have been modified by some hooks
936
				if ($reshook < 0) $error++;
937
			}
938
939
            // Call trigger
940
            $result=$this->call_trigger('BILL_CLONE',$user);
941
            if ($result < 0) $error++;
942
            // End call triggers
943
		}
944
945
		unset($this->context['createfromclone']);
946
947
		// End
948
		if (! $error)
949
		{
950
			$this->db->commit();
951
			return $this->id;
952
		}
953
		else
954
		{
955
			$this->db->rollback();
956
			return -1;
957
		}
958
	}
959
960
	/**
961
	 *  Load an object from an order and create a new invoice into database
962
	 *
963
	 *  @param      Object			$object         	Object source
964
	 *  @param		User			$user				Object user
965
	 *  @return     int             					<0 if KO, 0 if nothing done, 1 if OK
966
	 */
967
	function createFromOrder($object, User $user)
968
	{
969
		global $hookmanager;
970
971
		$error=0;
972
973
		// Closed order
974
		$this->date = dol_now();
975
		$this->source = 0;
976
977
		$num=count($object->lines);
978
		for ($i = 0; $i < $num; $i++)
979
		{
980
			$line = new FactureLigne($this->db);
981
982
			$line->libelle			= $object->lines[$i]->libelle;
983
			$line->label			= $object->lines[$i]->label;
984
			$line->desc				= $object->lines[$i]->desc;
985
			$line->subprice			= $object->lines[$i]->subprice;
986
			$line->total_ht			= $object->lines[$i]->total_ht;
987
			$line->total_tva		= $object->lines[$i]->total_tva;
988
			$line->total_localtax1	= $object->lines[$i]->total_localtax1;
989
			$line->total_localtax2	= $object->lines[$i]->total_localtax2;
990
			$line->total_ttc		= $object->lines[$i]->total_ttc;
991
			$line->vat_src_code  	= $object->lines[$i]->vat_src_code;
992
			$line->tva_tx			= $object->lines[$i]->tva_tx;
993
			$line->localtax1_tx		= $object->lines[$i]->localtax1_tx;
994
			$line->localtax2_tx		= $object->lines[$i]->localtax2_tx;
995
			$line->qty				= $object->lines[$i]->qty;
996
			$line->fk_remise_except	= $object->lines[$i]->fk_remise_except;
997
			$line->remise_percent	= $object->lines[$i]->remise_percent;
998
			$line->fk_product		= $object->lines[$i]->fk_product;
999
			$line->info_bits		= $object->lines[$i]->info_bits;
1000
			$line->product_type		= $object->lines[$i]->product_type;
1001
			$line->rang				= $object->lines[$i]->rang;
1002
			$line->special_code		= $object->lines[$i]->special_code;
1003
			$line->fk_parent_line	= $object->lines[$i]->fk_parent_line;
1004
			$line->fk_unit			= $object->lines[$i]->fk_unit;
1005
			$line->date_start 		= $object->lines[$i]->date_start;
1006
			$line->date_end 		= $object->lines[$i]->date_end;
1007
1008
			$line->fk_fournprice	= $object->lines[$i]->fk_fournprice;
1009
			$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);
1010
			$line->pa_ht			= $marginInfos[0];
1011
1012
            // get extrafields from original line
1013
			$object->lines[$i]->fetch_optionals($object->lines[$i]->rowid);
1014
			foreach($object->lines[$i]->array_options as $options_key => $value)
1015
				$line->array_options[$options_key] = $value;
1016
1017
			$this->lines[$i] = $line;
1018
		}
1019
1020
		$this->socid                = $object->socid;
1021
		$this->fk_project           = $object->fk_project;
1022
		$this->cond_reglement_id    = $object->cond_reglement_id;
1023
		$this->mode_reglement_id    = $object->mode_reglement_id;
1024
		$this->availability_id      = $object->availability_id;
1025
		$this->demand_reason_id     = $object->demand_reason_id;
1026
		$this->date_livraison       = $object->date_livraison;
1027
		$this->fk_delivery_address  = $object->fk_delivery_address;
1028
		$this->contact_id           = $object->contactid;
1029
		$this->ref_client           = $object->ref_client;
1030
		$this->note_private         = $object->note_private;
1031
		$this->note_public          = $object->note_public;
1032
1033
		$this->origin				= $object->element;
1034
		$this->origin_id			= $object->id;
1035
1036
        // get extrafields from original line
1037
		$object->fetch_optionals($object->id);
1038
		foreach($object->array_options as $options_key => $value)
1039
			$this->array_options[$options_key] = $value;
1040
1041
		// Possibility to add external linked objects with hooks
1042
		$this->linked_objects[$this->origin] = $this->origin_id;
1043
		if (! empty($object->other_linked_objects) && is_array($object->other_linked_objects))
1044
		{
1045
			$this->linked_objects = array_merge($this->linked_objects, $object->other_linked_objects);
1046
		}
1047
1048
		$ret = $this->create($user);
1049
1050
		if ($ret > 0)
1051
		{
1052
			// Actions hooked (by external module)
1053
			$hookmanager->initHooks(array('invoicedao'));
1054
1055
			$parameters=array('objFrom'=>$object);
1056
			$action='';
1057
			$reshook=$hookmanager->executeHooks('createFrom',$parameters,$this,$action);    // Note that $action and $object may have been modified by some hooks
1058
			if ($reshook < 0) $error++;
1059
1060
			if (! $error)
1061
			{
1062
				return 1;
1063
			}
1064
			else return -1;
1065
		}
1066
		else return -1;
1067
	}
1068
1069
	/**
1070
	 * Return link to download file from a direct external access
1071
	 *
1072
	 * @param	int				$withpicto			Add download picto into link
1073
	 * @return	string			HTML link to file
1074
	 */
1075
	function getDirectExternalLink($withpicto=0)
1076
	{
1077
		global $dolibarr_main_url_root;
1078
1079
		// Define $urlwithroot
1080
		$urlwithouturlroot=preg_replace('/'.preg_quote(DOL_URL_ROOT,'/').'$/i','',trim($dolibarr_main_url_root));
1081
		$urlwithroot=$urlwithouturlroot.DOL_URL_ROOT;		// This is to use external domain name found into config file
1082
		//$urlwithroot=DOL_MAIN_URL_ROOT;					// This is to use same domain name than current
1083
1084
		// TODO Read into ecmfile table to get entry and hash exists (PS: If not found, add it)
1085
		include_once DOL_DOCUMENT_ROOT.'/ecm/class/ecmfiles.class.php';
1086
		$ecmfile=new EcmFiles($this->db);
1087
		//$result = $ecmfile->get();
1088
1089
		$hashp='todo';
1090
		return '<a href="'.$urlwithroot.'/document.php?modulepart=invoice&hashp='.$hashp.'" target="_download" rel="noindex, nofollow">'.$this->ref.'</a>';
1091
	}
1092
1093
	/**
1094
	 *  Return clicable link of object (with eventually picto)
1095
	 *
1096
	 *  @param	int		$withpicto       			Add picto into link
1097
	 *  @param  string	$option          			Where point the link
1098
	 *  @param  int		$max             			Maxlength of ref
1099
	 *  @param  int		$short           			1=Return just URL
1100
	 *  @param  string  $moretitle       			Add more text to title tooltip
1101
     *  @param	int  	$notooltip		 			1=Disable tooltip
1102
     *  @param  int     $addlinktonotes  			1=Add link to notes
1103
     *  @param  int     $save_lastsearch_value		-1=Auto, 0=No save of lastsearch_values when clicking, 1=Save lastsearch_values whenclicking
1104
	 *  @return string 			         			String with URL
1105
	 */
1106
	function getNomUrl($withpicto=0, $option='', $max=0, $short=0, $moretitle='', $notooltip=0, $addlinktonotes=0, $save_lastsearch_value=-1)
1107
	{
1108
		global $langs, $conf, $user, $form;
1109
1110
        if (! empty($conf->dol_no_mouse_hover)) $notooltip=1;   // Force disable tooltips
1111
1112
		$result='';
1113
1114
		if ($option == 'withdraw') $url = DOL_URL_ROOT.'/compta/facture/prelevement.php?facid='.$this->id;
1115
		else $url = DOL_URL_ROOT.'/compta/facture/card.php?facid='.$this->id;
1116
1117
		if ($short) return $url;
1118
1119
		if ($option !== 'nolink')
1120
		{
1121
			// Add param to save lastsearch_values or not
1122
			$add_save_lastsearch_values=($save_lastsearch_value == 1 ? 1 : 0);
1123
			if ($save_lastsearch_value == -1 && preg_match('/list\.php/',$_SERVER["PHP_SELF"])) $add_save_lastsearch_values=1;
1124
			if ($add_save_lastsearch_values) $url.='&save_lastsearch_values=1';
1125
		}
1126
1127
		$picto='bill';
1128
		if ($this->type == self::TYPE_REPLACEMENT) $picto.='r';	// Replacement invoice
1129
		if ($this->type == self::TYPE_CREDIT_NOTE) $picto.='a';	// Credit note
1130
		if ($this->type == self::TYPE_DEPOSIT) $picto.='d';	// Deposit invoice
1131
        $label='';
1132
1133
        if ($user->rights->facture->lire) {
1134
            $label = '<u>' . $langs->trans("ShowInvoice") . '</u>';
1135
            if (! empty($this->ref))
1136
                $label .= '<br><b>'.$langs->trans('Ref') . ':</b> ' . $this->ref;
1137
            if (! empty($this->ref_client))
1138
                $label .= '<br><b>' . $langs->trans('RefCustomer') . ':</b> ' . $this->ref_client;
1139
            if (! empty($this->total_ht))
1140
                $label.= '<br><b>' . $langs->trans('AmountHT') . ':</b> ' . price($this->total_ht, 0, $langs, 0, -1, -1, $conf->currency);
1141
            if (! empty($this->total_tva))
1142
                $label.= '<br><b>' . $langs->trans('VAT') . ':</b> ' . price($this->total_tva, 0, $langs, 0, -1, -1, $conf->currency);
1143
            if (! empty($this->total_tva))
1144
                $label.= '<br><b>' . $langs->trans('LT1') . ':</b> ' . price($this->total_localtax1, 0, $langs, 0, -1, -1, $conf->currency);
1145
            if (! empty($this->total_tva))
1146
                $label.= '<br><b>' . $langs->trans('LT2') . ':</b> ' . price($this->total_localtax2, 0, $langs, 0, -1, -1, $conf->currency);
1147
            if (! empty($this->total_ttc))
1148
                $label.= '<br><b>' . $langs->trans('AmountTTC') . ':</b> ' . price($this->total_ttc, 0, $langs, 0, -1, -1, $conf->currency);
1149
    		if ($this->type == self::TYPE_REPLACEMENT) $label=$langs->transnoentitiesnoconv("ShowInvoiceReplace").': '.$this->ref;
1150
    		if ($this->type == self::TYPE_CREDIT_NOTE) $label=$langs->transnoentitiesnoconv("ShowInvoiceAvoir").': '.$this->ref;
1151
    		if ($this->type == self::TYPE_DEPOSIT) $label=$langs->transnoentitiesnoconv("ShowInvoiceDeposit").': '.$this->ref;
1152
    		if ($this->type == self::TYPE_SITUATION) $label=$langs->transnoentitiesnoconv("ShowInvoiceSituation").': '.$this->ref;
1153
    		if ($moretitle) $label.=' - '.$moretitle;
1154
        }
1155
1156
		$linkclose='';
1157
		if (empty($notooltip) && $user->rights->facture->lire)
1158
		{
1159
		    if (! empty($conf->global->MAIN_OPTIMIZEFORTEXTBROWSER))
1160
		    {
1161
		        $label=$langs->trans("ShowInvoice");
1162
		        $linkclose.=' alt="'.dol_escape_htmltag($label, 1).'"';
1163
		    }
1164
		    $linkclose.= ' title="'.dol_escape_htmltag($label, 1).'"';
1165
		    $linkclose.=' class="classfortooltip"';
1166
		}
1167
1168
        $linkstart='<a href="'.$url.'"';
1169
        $linkstart.=$linkclose.'>';
1170
		$linkend='</a>';
1171
1172
		$result .= $linkstart;
1173
		if ($withpicto) $result.=img_object(($notooltip?'':$label), $picto, ($notooltip?(($withpicto != 2) ? 'class="paddingright"' : ''):'class="'.(($withpicto != 2) ? 'paddingright ' : '').'classfortooltip"'), 0, 0, $notooltip?0:1);
1174
		if ($withpicto != 2) $result.= ($max?dol_trunc($this->ref,$max):$this->ref);
1175
		$result .= $linkend;
1176
1177
		if ($addlinktonotes)
1178
		{
1179
		    $txttoshow=($user->societe_id>0?$this->note_public:$this->note_private);
1180
		    if ($txttoshow)
1181
		    {
1182
                $notetoshow=$langs->trans("ViewPrivateNote").':<br>'.dol_string_nohtmltag($txttoshow,1);
1183
    		    $result.=' <span class="note inline-block">';
1184
    		    $result.='<a href="'.DOL_URL_ROOT.'/compta/facture/note.php?id='.$this->id.'" class="classfortooltip" title="'.dol_escape_htmltag($notetoshow).'">'.img_picto('','object_generic').'</a>';
1185
    		    //$result.=img_picto($langs->trans("ViewNote"),'object_generic');
1186
    		    //$result.='</a>';
1187
    		    $result.='</span>';
1188
		    }
1189
		}
1190
1191
		return $result;
1192
	}
1193
1194
	/**
1195
	 *	Get object and lines from database
1196
	 *
1197
	 *	@param      int		$rowid       	Id of object to load
1198
	 * 	@param		string	$ref			Reference of invoice
1199
	 * 	@param		string	$ref_ext		External reference of invoice
1200
	 * 	@param		int		$ref_int		Internal reference of other object
1201
	 *  @param		bool	$fetch_situation	Fetch the previous and next situation in $tab_previous_situation_invoice and $tab_next_situation_invoice
1202
	 *	@return     int         			>0 if OK, <0 if KO, 0 if not found
1203
	 */
1204
	function fetch($rowid, $ref='', $ref_ext='', $ref_int='', $fetch_situation=false)
1205
	{
1206
		global $conf;
1207
1208
		if (empty($rowid) && empty($ref) && empty($ref_ext) && empty($ref_int)) return -1;
1209
1210
		$sql = 'SELECT f.rowid,f.facnumber,f.ref_client,f.ref_ext,f.ref_int,f.type,f.fk_soc,f.amount';
1211
		$sql.= ', f.tva, f.localtax1, f.localtax2, f.total, f.total_ttc, f.revenuestamp';
1212
		$sql.= ', f.remise_percent, f.remise_absolue, f.remise';
1213
		$sql.= ', f.datef as df, f.date_pointoftax';
1214
		$sql.= ', f.date_lim_reglement as dlr';
1215
		$sql.= ', f.datec as datec';
1216
		$sql.= ', f.date_valid as datev';
1217
		$sql.= ', f.tms as datem';
1218
		$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';
1219
		$sql.= ', f.fk_facture_source';
1220
		$sql.= ', f.fk_mode_reglement, f.fk_cond_reglement, f.fk_projet, f.extraparams';
1221
		$sql.= ', f.situation_cycle_ref, f.situation_counter, f.situation_final';
1222
		$sql.= ', f.fk_account';
1223
		$sql.= ", f.fk_multicurrency, f.multicurrency_code, f.multicurrency_tx, f.multicurrency_total_ht, f.multicurrency_total_tva, f.multicurrency_total_ttc";
1224
		$sql.= ', p.code as mode_reglement_code, p.libelle as mode_reglement_libelle';
1225
		$sql.= ', c.code as cond_reglement_code, c.libelle as cond_reglement_libelle, c.libelle_facture as cond_reglement_libelle_doc';
1226
        $sql.= ', f.fk_incoterms, f.location_incoterms';
1227
        $sql.= ", i.libelle as libelle_incoterms";
1228
		$sql.= ' FROM '.MAIN_DB_PREFIX.'facture as f';
1229
		$sql.= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_payment_term as c ON f.fk_cond_reglement = c.rowid AND c.entity IN (' . getEntity('c_payment_term').')';
1230
		$sql.= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_paiement as p ON f.fk_mode_reglement = p.id AND p.entity IN ('.getEntity('c_paiement').')';
1231
		$sql.= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_incoterms as i ON f.fk_incoterms = i.rowid';
1232
		$sql.= ' WHERE f.entity IN ('.getEntity('facture').')';
1233
		if ($rowid)   $sql.= " AND f.rowid=".$rowid;
1234
		if ($ref)     $sql.= " AND f.facnumber='".$this->db->escape($ref)."'";
1235
		if ($ref_ext) $sql.= " AND f.ref_ext='".$this->db->escape($ref_ext)."'";
1236
		if ($ref_int) $sql.= " AND f.ref_int='".$this->db->escape($ref_int)."'";
1237
1238
		dol_syslog(get_class($this)."::fetch", LOG_DEBUG);
1239
		$result = $this->db->query($sql);
1240
		if ($result)
1241
		{
1242
			if ($this->db->num_rows($result))
1243
			{
1244
				$obj = $this->db->fetch_object($result);
1245
1246
				$this->id					= $obj->rowid;
1247
				$this->ref					= $obj->facnumber;
1248
				$this->ref_client			= $obj->ref_client;
1249
				$this->ref_ext				= $obj->ref_ext;
1250
				$this->ref_int				= $obj->ref_int;
1251
				$this->type					= $obj->type;
1252
				$this->date					= $this->db->jdate($obj->df);
1253
				$this->date_pointoftax		= $this->db->jdate($obj->date_pointoftax);
1254
				$this->date_creation		= $this->db->jdate($obj->datec);
1255
				$this->date_validation		= $this->db->jdate($obj->datev);
1256
				$this->datem				= $this->db->jdate($obj->datem);
1257
				$this->remise_percent		= $obj->remise_percent;
1258
				$this->remise_absolue		= $obj->remise_absolue;
1259
				$this->total_ht				= $obj->total;
1260
				$this->total_tva			= $obj->tva;
1261
				$this->total_localtax1		= $obj->localtax1;
1262
				$this->total_localtax2		= $obj->localtax2;
1263
				$this->total_ttc			= $obj->total_ttc;
1264
				$this->revenuestamp         = $obj->revenuestamp;
1265
				$this->paye					= $obj->paye;
1266
				$this->close_code			= $obj->close_code;
1267
				$this->close_note			= $obj->close_note;
1268
				$this->socid				= $obj->fk_soc;
1269
				$this->statut				= $obj->fk_statut;
1270
				$this->date_lim_reglement	= $this->db->jdate($obj->dlr);
1271
				$this->mode_reglement_id	= $obj->fk_mode_reglement;
1272
				$this->mode_reglement_code	= $obj->mode_reglement_code;
1273
				$this->mode_reglement		= $obj->mode_reglement_libelle;
1274
				$this->cond_reglement_id	= $obj->fk_cond_reglement;
1275
				$this->cond_reglement_code	= $obj->cond_reglement_code;
1276
				$this->cond_reglement		= $obj->cond_reglement_libelle;
1277
				$this->cond_reglement_doc	= $obj->cond_reglement_libelle_doc;
1278
				$this->fk_account           = ($obj->fk_account>0)?$obj->fk_account:null;
1279
				$this->fk_project			= $obj->fk_projet;
1280
				$this->fk_facture_source	= $obj->fk_facture_source;
1281
				$this->note					= $obj->note_private;	// deprecated
1282
				$this->note_private			= $obj->note_private;
1283
				$this->note_public			= $obj->note_public;
1284
				$this->user_author			= $obj->fk_user_author;
1285
				$this->user_valid			= $obj->fk_user_valid;
1286
				$this->modelpdf				= $obj->model_pdf;
1287
				$this->last_main_doc		= $obj->last_main_doc;
1288
				$this->situation_cycle_ref  = $obj->situation_cycle_ref;
1289
				$this->situation_counter    = $obj->situation_counter;
1290
				$this->situation_final      = $obj->situation_final;
1291
				$this->extraparams			= (array) json_decode($obj->extraparams, true);
1292
1293
				//Incoterms
1294
				$this->fk_incoterms = $obj->fk_incoterms;
1295
				$this->location_incoterms = $obj->location_incoterms;
1296
				$this->libelle_incoterms = $obj->libelle_incoterms;
1297
1298
				// Multicurrency
1299
				$this->fk_multicurrency 		= $obj->fk_multicurrency;
1300
				$this->multicurrency_code 		= $obj->multicurrency_code;
1301
				$this->multicurrency_tx 		= $obj->multicurrency_tx;
1302
				$this->multicurrency_total_ht 	= $obj->multicurrency_total_ht;
1303
				$this->multicurrency_total_tva 	= $obj->multicurrency_total_tva;
1304
				$this->multicurrency_total_ttc 	= $obj->multicurrency_total_ttc;
1305
1306
				if ($this->type == self::TYPE_SITUATION && $fetch_situation)
1307
				{
1308
					$this->fetchPreviousNextSituationInvoice();
1309
				}
1310
1311
				if ($this->statut == self::STATUS_DRAFT)	$this->brouillon = 1;
1312
1313
				// Retrieve all extrafield for invoice
1314
				// fetch optionals attributes and labels
1315
				require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php';
1316
				$extrafields=new ExtraFields($this->db);
1317
				$extralabels=$extrafields->fetch_name_optionals_label($this->table_element,true);
1318
				$this->fetch_optionals($this->id,$extralabels);
1319
1320
				/*
1321
				 * Lines
1322
				*/
1323
1324
				$this->lines  = array();
1325
1326
				$result=$this->fetch_lines();
1327
				if ($result < 0)
1328
				{
1329
					$this->error=$this->db->error();
1330
					return -3;
1331
				}
1332
				return 1;
1333
			}
1334
			else
1335
			{
1336
				$this->error='Bill with id '.$rowid.' or ref '.$ref.' not found';
1337
				dol_syslog(get_class($this)."::fetch Error ".$this->error, LOG_ERR);
1338
				return 0;
1339
			}
1340
		}
1341
		else
1342
		{
1343
			$this->error=$this->db->error();
1344
			return -1;
1345
		}
1346
	}
1347
1348
1349
	/**
1350
	 *	Load all detailed lines into this->lines
1351
	 *
1352
	 *	@return     int         1 if OK, < 0 if KO
1353
	 */
1354
	function fetch_lines()
1355
	{
1356
		$this->lines=array();
1357
1358
		$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,';
1359
		$sql.= ' l.situation_percent, l.fk_prev_id,';
1360
		$sql.= ' l.localtax1_tx, l.localtax2_tx, l.localtax1_type, l.localtax2_type, l.remise_percent, l.fk_remise_except, l.subprice,';
1361
		$sql.= ' l.rang, l.special_code,';
1362
		$sql.= ' l.date_start as date_start, l.date_end as date_end,';
1363
		$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,';
1364
		$sql.= ' l.fk_unit,';
1365
		$sql.= ' l.fk_multicurrency, l.multicurrency_code, l.multicurrency_subprice, l.multicurrency_total_ht, l.multicurrency_total_tva, l.multicurrency_total_ttc,';
1366
		$sql.= ' p.ref as product_ref, p.fk_product_type as fk_product_type, p.label as product_label, p.description as product_desc';
1367
		$sql.= ' FROM '.MAIN_DB_PREFIX.'facturedet as l';
1368
		$sql.= ' LEFT JOIN '.MAIN_DB_PREFIX.'product as p ON l.fk_product = p.rowid';
1369
		$sql.= ' WHERE l.fk_facture = '.$this->id;
1370
		$sql.= ' ORDER BY l.rang, l.rowid';
1371
1372
		dol_syslog(get_class($this).'::fetch_lines', LOG_DEBUG);
1373
		$result = $this->db->query($sql);
1374
		if ($result)
1375
		{
1376
			$num = $this->db->num_rows($result);
1377
			$i = 0;
1378
			while ($i < $num)
1379
			{
1380
				$objp = $this->db->fetch_object($result);
1381
				$line = new FactureLigne($this->db);
1382
1383
				$line->id               = $objp->rowid;
1384
				$line->rowid	        = $objp->rowid;             // deprecated
1385
				$line->fk_facture       = $objp->fk_facture;
1386
				$line->label            = $objp->custom_label;		// deprecated
1387
				$line->desc             = $objp->description;		// Description line
1388
				$line->description      = $objp->description;		// Description line
1389
				$line->product_type     = $objp->product_type;		// Type of line
1390
				$line->ref              = $objp->product_ref;		// Ref product
1391
				$line->product_ref      = $objp->product_ref;		// Ref product
1392
				$line->libelle          = $objp->product_label;		// TODO deprecated
1393
				$line->product_label	= $objp->product_label;		// Label product
1394
				$line->product_desc     = $objp->product_desc;		// Description product
1395
				$line->fk_product_type  = $objp->fk_product_type;	// Type of product
1396
				$line->qty              = $objp->qty;
1397
				$line->subprice         = $objp->subprice;
1398
1399
                $line->vat_src_code     = $objp->vat_src_code;
1400
				$line->tva_tx           = $objp->tva_tx;
1401
				$line->localtax1_tx     = $objp->localtax1_tx;
1402
				$line->localtax2_tx     = $objp->localtax2_tx;
1403
				$line->localtax1_type   = $objp->localtax1_type;
1404
				$line->localtax2_type   = $objp->localtax2_type;
1405
				$line->remise_percent   = $objp->remise_percent;
1406
				$line->fk_remise_except = $objp->fk_remise_except;
1407
				$line->fk_product       = $objp->fk_product;
1408
				$line->date_start       = $this->db->jdate($objp->date_start);
1409
				$line->date_end         = $this->db->jdate($objp->date_end);
1410
				$line->date_start       = $this->db->jdate($objp->date_start);
1411
				$line->date_end         = $this->db->jdate($objp->date_end);
1412
				$line->info_bits        = $objp->info_bits;
1413
				$line->total_ht         = $objp->total_ht;
1414
				$line->total_tva        = $objp->total_tva;
1415
				$line->total_localtax1  = $objp->total_localtax1;
1416
				$line->total_localtax2  = $objp->total_localtax2;
1417
				$line->total_ttc        = $objp->total_ttc;
1418
				$line->code_ventilation = $objp->fk_code_ventilation;
1419
				$line->fk_fournprice 	= $objp->fk_fournprice;
1420
				$marginInfos			= getMarginInfos($objp->subprice, $objp->remise_percent, $objp->tva_tx, $objp->localtax1_tx, $objp->localtax2_tx, $line->fk_fournprice, $objp->pa_ht);
1421
				$line->pa_ht 			= $marginInfos[0];
1422
				$line->marge_tx			= $marginInfos[1];
1423
				$line->marque_tx		= $marginInfos[2];
1424
				$line->rang				= $objp->rang;
1425
				$line->special_code		= $objp->special_code;
1426
				$line->fk_parent_line	= $objp->fk_parent_line;
1427
				$line->situation_percent= $objp->situation_percent;
1428
				$line->fk_prev_id       = $objp->fk_prev_id;
1429
				$line->fk_unit	        = $objp->fk_unit;
1430
1431
				// Multicurrency
1432
				$line->fk_multicurrency 		= $objp->fk_multicurrency;
1433
				$line->multicurrency_code 		= $objp->multicurrency_code;
1434
				$line->multicurrency_subprice 	= $objp->multicurrency_subprice;
1435
				$line->multicurrency_total_ht 	= $objp->multicurrency_total_ht;
1436
				$line->multicurrency_total_tva 	= $objp->multicurrency_total_tva;
1437
				$line->multicurrency_total_ttc 	= $objp->multicurrency_total_ttc;
1438
1439
				// TODO Fetch optional like done in fetch line of facture_rec ?
1440
1441
				$this->lines[$i] = $line;
1442
1443
				$i++;
1444
			}
1445
			$this->db->free($result);
1446
			return 1;
1447
		}
1448
		else
1449
		{
1450
			$this->error=$this->db->error();
1451
			return -3;
1452
		}
1453
	}
1454
1455
	/**
1456
	 * Fetch previous and next situations invoices
1457
	 *
1458
	 * @return	void
1459
	 */
1460
	function fetchPreviousNextSituationInvoice()
1461
	{
1462
		global $conf;
1463
1464
		$this->tab_previous_situation_invoice = array();
1465
		$this->tab_next_situation_invoice = array();
1466
1467
		$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';
1468
1469
		dol_syslog(get_class($this).'::fetchPreviousNextSituationInvoice ', LOG_DEBUG);
1470
		$result = $this->db->query($sql);
1471
		if ($result && $this->db->num_rows($result) > 0)
1472
		{
1473
			while ($objp = $this->db->fetch_object($result))
1474
			{
1475
				$invoice = new Facture($this->db);
1476
				if ($invoice->fetch($objp->rowid) > 0)
1477
				{
1478
					if ($objp->situation_counter < $this->situation_counter) $this->tab_previous_situation_invoice[] = $invoice;
1479
					else $this->tab_next_situation_invoice[] = $invoice;
1480
				}
1481
			}
1482
		}
1483
1484
	}
1485
1486
	/**
1487
	 *      Update database
1488
	 *
1489
	 *      @param      User	$user        	User that modify
1490
	 *      @param      int		$notrigger	    0=launch triggers after, 1=disable triggers
1491
	 *      @return     int      			   	<0 if KO, >0 if OK
1492
	 */
1493
	function update($user=null, $notrigger=0)
1494
	{
1495
		$error=0;
1496
1497
		// Clean parameters
1498
		if (empty($this->type)) $this->type= self::TYPE_STANDARD;
1499
		if (isset($this->facnumber)) $this->facnumber=trim($this->ref);
1500
		if (isset($this->ref_client)) $this->ref_client=trim($this->ref_client);
1501
		if (isset($this->increment)) $this->increment=trim($this->increment);
1502
		if (isset($this->close_code)) $this->close_code=trim($this->close_code);
1503
		if (isset($this->close_note)) $this->close_note=trim($this->close_note);
1504
		if (isset($this->note) || isset($this->note_private)) $this->note=(isset($this->note) ? trim($this->note) : trim($this->note_private));		// deprecated
1505
		if (isset($this->note) || isset($this->note_private)) $this->note_private=(isset($this->note_private) ? trim($this->note_private) : trim($this->note));
1506
		if (isset($this->note_public)) $this->note_public=trim($this->note_public);
1507
		if (isset($this->modelpdf)) $this->modelpdf=trim($this->modelpdf);
1508
		if (isset($this->import_key)) $this->import_key=trim($this->import_key);
1509
		if (empty($this->situation_cycle_ref)) {
1510
			$this->situation_cycle_ref = 'null';
0 ignored issues
show
Documentation Bug introduced by
The property $situation_cycle_ref was declared of type integer, but 'null' 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...
1511
		}
1512
1513
		if (empty($this->situation_counter)) {
1514
			$this->situation_counter = 'null';
0 ignored issues
show
Documentation Bug introduced by
The property $situation_counter was declared of type integer, but 'null' 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...
1515
		}
1516
1517
		if (empty($this->situation_final)) {
1518
			$this->situation_final = '0';
0 ignored issues
show
Documentation Bug introduced by
The property $situation_final was declared of type boolean, but '0' 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...
1519
		}
1520
1521
		// Check parameters
1522
		// Put here code to add control on parameters values
1523
1524
		// Update request
1525
		$sql = "UPDATE ".MAIN_DB_PREFIX."facture SET";
1526
1527
		$sql.= " facnumber=".(isset($this->ref)?"'".$this->db->escape($this->ref)."'":"null").",";
1528
		$sql.= " type=".(isset($this->type)?$this->type:"null").",";
1529
		$sql.= " ref_client=".(isset($this->ref_client)?"'".$this->db->escape($this->ref_client)."'":"null").",";
1530
		$sql.= " increment=".(isset($this->increment)?"'".$this->db->escape($this->increment)."'":"null").",";
1531
		$sql.= " fk_soc=".(isset($this->socid)?$this->socid:"null").",";
1532
		$sql.= " datec=".(strval($this->date_creation)!='' ? "'".$this->db->idate($this->date_creation)."'" : 'null').",";
1533
		$sql.= " datef=".(strval($this->date)!='' ? "'".$this->db->idate($this->date)."'" : 'null').",";
1534
		$sql.= " date_pointoftax=".(strval($this->date_pointoftax)!='' ? "'".$this->db->idate($this->date_pointoftax)."'" : 'null').",";
1535
		$sql.= " date_valid=".(strval($this->date_validation)!='' ? "'".$this->db->idate($this->date_validation)."'" : 'null').",";
1536
		$sql.= " paye=".(isset($this->paye)?$this->paye:"null").",";
1537
		$sql.= " remise_percent=".(isset($this->remise_percent)?$this->remise_percent:"null").",";
1538
		$sql.= " remise_absolue=".(isset($this->remise_absolue)?$this->remise_absolue:"null").",";
1539
		$sql.= " close_code=".(isset($this->close_code)?"'".$this->db->escape($this->close_code)."'":"null").",";
1540
		$sql.= " close_note=".(isset($this->close_note)?"'".$this->db->escape($this->close_note)."'":"null").",";
1541
		$sql.= " tva=".(isset($this->total_tva)?$this->total_tva:"null").",";
1542
		$sql.= " localtax1=".(isset($this->total_localtax1)?$this->total_localtax1:"null").",";
1543
		$sql.= " localtax2=".(isset($this->total_localtax2)?$this->total_localtax2:"null").",";
1544
		$sql.= " total=".(isset($this->total_ht)?$this->total_ht:"null").",";
1545
		$sql.= " total_ttc=".(isset($this->total_ttc)?$this->total_ttc:"null").",";
1546
		$sql.= " revenuestamp=".((isset($this->revenuestamp) && $this->revenuestamp != '')?$this->revenuestamp:"null").",";
1547
		$sql.= " fk_statut=".(isset($this->statut)?$this->statut:"null").",";
1548
		$sql.= " fk_user_author=".(isset($this->user_author)?$this->user_author:"null").",";
1549
		$sql.= " fk_user_valid=".(isset($this->fk_user_valid)?$this->fk_user_valid:"null").",";
1550
		$sql.= " fk_facture_source=".(isset($this->fk_facture_source)?$this->fk_facture_source:"null").",";
1551
		$sql.= " fk_projet=".(isset($this->fk_project)?$this->fk_project:"null").",";
1552
		$sql.= " fk_cond_reglement=".(isset($this->cond_reglement_id)?$this->cond_reglement_id:"null").",";
1553
		$sql.= " fk_mode_reglement=".(isset($this->mode_reglement_id)?$this->mode_reglement_id:"null").",";
1554
		$sql.= " date_lim_reglement=".(strval($this->date_lim_reglement)!='' ? "'".$this->db->idate($this->date_lim_reglement)."'" : 'null').",";
1555
		$sql.= " note_private=".(isset($this->note_private)?"'".$this->db->escape($this->note_private)."'":"null").",";
1556
		$sql.= " note_public=".(isset($this->note_public)?"'".$this->db->escape($this->note_public)."'":"null").",";
1557
		$sql.= " model_pdf=".(isset($this->modelpdf)?"'".$this->db->escape($this->modelpdf)."'":"null").",";
1558
		$sql.= " import_key=".(isset($this->import_key)?"'".$this->db->escape($this->import_key)."'":"null");
1559
		$sql.= ", situation_cycle_ref=".$this->situation_cycle_ref;
1560
		$sql.= ", situation_counter=".$this->situation_counter;
1561
		$sql.= ", situation_final=".$this->situation_final;
1562
1563
		$sql.= " WHERE rowid=".$this->id;
1564
1565
		$this->db->begin();
1566
1567
		dol_syslog(get_class($this)."::update", LOG_DEBUG);
1568
		$resql = $this->db->query($sql);
1569
		if (! $resql) {
1570
			$error++; $this->errors[]="Error ".$this->db->lasterror();
1571
		}
1572
1573
		if (! $error)
1574
		{
1575
			if (! $notrigger)
1576
			{
1577
	            // Call trigger
1578
	            $result=$this->call_trigger('BILL_MODIFY',$user);
0 ignored issues
show
Bug introduced by
It seems like $user defined by parameter $user on line 1493 can be null; however, CommonObject::call_trigger() does not accept null, maybe add an additional type check?

It seems like you allow that null is being passed for a parameter, however the function which is called does not seem to accept null.

We recommend to add an additional type check (or disallow null for the parameter):

function notNullable(stdClass $x) { }

// Unsafe
function withoutCheck(stdClass $x = null) {
    notNullable($x);
}

// Safe - Alternative 1: Adding Additional Type-Check
function withCheck(stdClass $x = null) {
    if ($x instanceof stdClass) {
        notNullable($x);
    }
}

// Safe - Alternative 2: Changing Parameter
function withNonNullableParam(stdClass $x) {
    notNullable($x);
}
Loading history...
1579
	            if ($result < 0) $error++;
1580
	            // End call triggers
1581
			}
1582
		}
1583
1584
		// Commit or rollback
1585
		if ($error)
1586
		{
1587
			foreach($this->errors as $errmsg)
1588
			{
1589
				dol_syslog(get_class($this)."::update ".$errmsg, LOG_ERR);
1590
				$this->error.=($this->error?', '.$errmsg:$errmsg);
1591
			}
1592
			$this->db->rollback();
1593
			return -1*$error;
1594
		}
1595
		else
1596
		{
1597
			$this->db->commit();
1598
			return 1;
1599
		}
1600
	}
1601
1602
1603
	/**
1604
	 *    Add a discount line into an invoice (as an invoice line) using an existing absolute discount (Consume the discount)
1605
	 *
1606
	 *    @param     int	$idremise	Id of absolute discount
1607
	 *    @return    int          		>0 if OK, <0 if KO
1608
	 */
1609
	function insert_discount($idremise)
1610
	{
1611
		global $langs;
1612
1613
		include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
1614
		include_once DOL_DOCUMENT_ROOT.'/core/class/discount.class.php';
1615
1616
		$this->db->begin();
1617
1618
		$remise=new DiscountAbsolute($this->db);
1619
		$result=$remise->fetch($idremise);
1620
1621
		if ($result > 0)
1622
		{
1623
			if ($remise->fk_facture)	// Protection against multiple submission
1624
			{
1625
				$this->error=$langs->trans("ErrorDiscountAlreadyUsed");
1626
				$this->db->rollback();
1627
				return -5;
1628
			}
1629
1630
			$facligne=new FactureLigne($this->db);
1631
			$facligne->fk_facture=$this->id;
1632
			$facligne->fk_remise_except=$remise->id;
1633
			$facligne->desc=$remise->description;   	// Description ligne
1634
			$facligne->vat_src_code=$remise->vat_src_code;
1635
			$facligne->tva_tx=$remise->tva_tx;
0 ignored issues
show
Documentation Bug introduced by
It seems like $remise->tva_tx can also be of type integer or 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...
1636
			$facligne->subprice = -$remise->amount_ht;
1637
			$facligne->fk_product=0;					// Id produit predefini
1638
			$facligne->qty=1;
1639
			$facligne->remise_percent=0;
1640
			$facligne->rang=-1;
1641
			$facligne->info_bits=2;
1642
1643
			// Get buy/cost price of invoice that is source of discount
1644
			if ($remise->fk_facture_source > 0)
1645
			{
1646
    			$srcinvoice=new Facture($this->db);
1647
    			$srcinvoice->fetch($remise->fk_facture_source);
1648
    			$totalcostpriceofinvoice=0;
1649
    			include_once DOL_DOCUMENT_ROOT.'/core/class/html.formmargin.class.php';  // TODO Move this into commonobject
1650
    			$formmargin=new FormMargin($this->db);
1651
    			$arraytmp=$formmargin->getMarginInfosArray($srcinvoice, false);
1652
        		$facligne->pa_ht = $arraytmp['pa_total'];
1653
			}
1654
1655
			$facligne->total_ht  = -$remise->amount_ht;
1656
			$facligne->total_tva = -$remise->amount_tva;
1657
			$facligne->total_ttc = -$remise->amount_ttc;
1658
1659
			$facligne->multicurrency_subprice = -$remise->multicurrency_subprice;
1660
			$facligne->multicurrency_total_ht = -$remise->multicurrency_total_ht;
1661
			$facligne->multicurrency_total_tva = -$remise->multicurrency_total_tva;
1662
			$facligne->multicurrency_total_ttc = -$remise->multicurrency_total_ttc;
1663
1664
			$lineid=$facligne->insert();
1665
			if ($lineid > 0)
1666
			{
1667
				$result=$this->update_price(1);
1668
				if ($result > 0)
1669
				{
1670
					// Create link between discount and invoice line
1671
					$result=$remise->link_to_invoice($lineid,0);
1672
					if ($result < 0)
1673
					{
1674
						$this->error=$remise->error;
1675
						$this->db->rollback();
1676
						return -4;
1677
					}
1678
1679
					$this->db->commit();
1680
					return 1;
1681
				}
1682
				else
1683
				{
1684
					$this->error=$facligne->error;
1685
					$this->db->rollback();
1686
					return -1;
1687
				}
1688
			}
1689
			else
1690
			{
1691
				$this->error=$facligne->error;
1692
				$this->db->rollback();
1693
				return -2;
1694
			}
1695
		}
1696
		else
1697
		{
1698
			$this->db->rollback();
1699
			return -3;
1700
		}
1701
	}
1702
1703
	/**
1704
	 *	Set customer ref
1705
	 *
1706
	 *	@param     	string	$ref_client		Customer ref
1707
	 *  @param     	int		$notrigger		1=Does not execute triggers, 0= execute triggers
1708
	 *	@return		int						<0 if KO, >0 if OK
1709
	 */
1710
	function set_ref_client($ref_client, $notrigger=0)
1711
	{
1712
	    global $user;
1713
1714
		$error=0;
1715
1716
		$this->db->begin();
1717
1718
		$sql = 'UPDATE '.MAIN_DB_PREFIX.'facture';
1719
		if (empty($ref_client))
1720
			$sql .= ' SET ref_client = NULL';
1721
		else
1722
			$sql .= ' SET ref_client = \''.$this->db->escape($ref_client).'\'';
1723
		$sql .= ' WHERE rowid = '.$this->id;
1724
1725
		dol_syslog(__METHOD__.' this->id='.$this->id.', ref_client='.$ref_client, LOG_DEBUG);
1726
		$resql=$this->db->query($sql);
1727
		if (!$resql)
1728
		{
1729
			$this->errors[]=$this->db->error();
1730
			$error++;
1731
		}
1732
1733
		if (! $error)
1734
		{
1735
			$this->ref_client = $ref_client;
1736
		}
1737
1738
		if (! $notrigger && empty($error))
1739
		{
1740
			// Call trigger
1741
			$result=$this->call_trigger('BILL_MODIFY',$user);
1742
			if ($result < 0) $error++;
1743
			// End call triggers
1744
		}
1745
1746
		if (! $error)
1747
		{
1748
1749
			$this->ref_client = $ref_client;
1750
1751
			$this->db->commit();
1752
			return 1;
1753
		}
1754
		else
1755
		{
1756
			foreach($this->errors as $errmsg)
1757
			{
1758
				dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
1759
				$this->error.=($this->error?', '.$errmsg:$errmsg);
1760
			}
1761
			$this->db->rollback();
1762
			return -1*$error;
1763
		}
1764
	}
1765
1766
	/**
1767
	 *	Delete invoice
1768
	 *
1769
	 *	@param     	User	$user      	    User making the deletion.
1770
	 *	@param		int		$notrigger		1=Does not execute triggers, 0= execute triggers
1771
	 *	@param		int		$idwarehouse	Id warehouse to use for stock change.
1772
	 *	@return		int						<0 if KO, 0=Refused, >0 if OK
1773
	 */
1774
	function delete($user, $notrigger=0, $idwarehouse=-1)
1775
	{
1776
		global $langs,$conf;
1777
		require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
1778
1779
		$rowid=$this->id;
1780
1781
		dol_syslog(get_class($this)."::delete rowid=".$rowid.", ref=".$this->ref.", thirdparty=".$this->thirdparty->name, LOG_DEBUG);
1782
1783
		// Test to avoid invoice deletion (allowed if draft)
1784
		$test = $this->is_erasable();
1785
1786
		if ($test <= 0) return $test;
1787
1788
		$error=0;
1789
1790
		$this->db->begin();
1791
1792
		if (! $error && ! $notrigger)
1793
		{
1794
            // Call trigger
1795
            $result=$this->call_trigger('BILL_DELETE',$user);
1796
            if ($result < 0) $error++;
1797
            // End call triggers
1798
		}
1799
1800
		// Removed extrafields
1801
		if (! $error) {
1802
			$result=$this->deleteExtraFields();
1803
			if ($result < 0)
1804
			{
1805
				$error++;
1806
				dol_syslog(get_class($this)."::delete error deleteExtraFields ".$this->error, LOG_ERR);
1807
			}
1808
		}
1809
1810
		if (! $error)
1811
		{
1812
			// Delete linked object
1813
			$res = $this->deleteObjectLinked();
1814
			if ($res < 0) $error++;
1815
		}
1816
1817
		if (! $error)
1818
		{
1819
			// If invoice was converted into a discount not yet consumed, we remove discount
1820
			$sql = 'DELETE FROM '.MAIN_DB_PREFIX.'societe_remise_except';
1821
			$sql.= ' WHERE fk_facture_source = '.$rowid;
1822
			$sql.= ' AND fk_facture_line IS NULL';
1823
			$resql=$this->db->query($sql);
1824
1825
			// If invoice has consumned discounts
1826
			$this->fetch_lines();
1827
			$list_rowid_det=array();
1828
			foreach($this->lines as $key => $invoiceline)
1829
			{
1830
				$list_rowid_det[]=$invoiceline->rowid;
1831
			}
1832
1833
			// Consumned discounts are freed
1834
			if (count($list_rowid_det))
1835
			{
1836
				$sql = 'UPDATE '.MAIN_DB_PREFIX.'societe_remise_except';
1837
				$sql.= ' SET fk_facture = NULL, fk_facture_line = NULL';
1838
				$sql.= ' WHERE fk_facture_line IN ('.join(',',$list_rowid_det).')';
1839
1840
				dol_syslog(get_class($this)."::delete", LOG_DEBUG);
1841
				if (! $this->db->query($sql))
1842
				{
1843
					$this->error=$this->db->error()." sql=".$sql;
1844
					$this->db->rollback();
1845
					return -5;
1846
				}
1847
			}
1848
1849
			// If we decrement stock on invoice validation, we increment
1850
			if ($this->type != self::TYPE_DEPOSIT && $result >= 0 && ! empty($conf->stock->enabled) && ! empty($conf->global->STOCK_CALCULATE_ON_BILL) && $idwarehouse!=-1)
0 ignored issues
show
Bug introduced by
The variable $result does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
1851
			{
1852
				require_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
1853
				$langs->load("agenda");
1854
1855
				$num=count($this->lines);
1856
				for ($i = 0; $i < $num; $i++)
1857
				{
1858
					if ($this->lines[$i]->fk_product > 0)
1859
					{
1860
						$mouvP = new MouvementStock($this->db);
1861
						$mouvP->origin = &$this;
1862
						// We decrease stock for product
1863
						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));
1864
						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
1865
					}
1866
				}
1867
			}
1868
1869
1870
			// Delete invoice line
1871
			$sql = 'DELETE FROM '.MAIN_DB_PREFIX.'facturedet WHERE fk_facture = '.$rowid;
1872
1873
			dol_syslog(get_class($this)."::delete", LOG_DEBUG);
1874
1875
			if ($this->db->query($sql) && $this->delete_linked_contact())
1876
			{
1877
				$sql = 'DELETE FROM '.MAIN_DB_PREFIX.'facture WHERE rowid = '.$rowid;
1878
1879
				dol_syslog(get_class($this)."::delete", LOG_DEBUG);
1880
1881
				$resql=$this->db->query($sql);
1882
				if ($resql)
1883
				{
1884
					// On efface le repertoire de pdf provisoire
1885
					$ref = dol_sanitizeFileName($this->ref);
1886
					if ($conf->facture->dir_output && !empty($this->ref))
1887
					{
1888
						$dir = $conf->facture->dir_output . "/" . $ref;
1889
						$file = $conf->facture->dir_output . "/" . $ref . "/" . $ref . ".pdf";
1890
						if (file_exists($file))	// We must delete all files before deleting directory
1891
						{
1892
							$ret=dol_delete_preview($this);
1893
1894
							if (! dol_delete_file($file,0,0,0,$this)) // For triggers
1895
							{
1896
								$this->error=$langs->trans("ErrorCanNotDeleteFile",$file);
1897
								$this->db->rollback();
1898
								return 0;
1899
							}
1900
						}
1901
						if (file_exists($dir))
1902
						{
1903
							if (! dol_delete_dir_recursive($dir)) // For remove dir and meta
1904
							{
1905
								$this->error=$langs->trans("ErrorCanNotDeleteDir",$dir);
1906
								$this->db->rollback();
1907
								return 0;
1908
							}
1909
						}
1910
					}
1911
1912
					$this->db->commit();
1913
					return 1;
1914
				}
1915
				else
1916
				{
1917
					$this->error=$this->db->lasterror()." sql=".$sql;
1918
					$this->db->rollback();
1919
					return -6;
1920
				}
1921
			}
1922
			else
1923
			{
1924
				$this->error=$this->db->lasterror()." sql=".$sql;
1925
				$this->db->rollback();
1926
				return -4;
1927
			}
1928
		}
1929
		else
1930
		{
1931
			$this->db->rollback();
1932
			return -2;
1933
		}
1934
	}
1935
1936
	/**
1937
	 *  Tag la facture comme paye completement (si close_code non renseigne) => this->fk_statut=2, this->paye=1
1938
	 *  ou partiellement (si close_code renseigne) + appel trigger BILL_PAYED => this->fk_statut=2, this->paye stay 0
1939
	 *
1940
	 *  @param	User	$user      	Objet utilisateur qui modifie
1941
	 *	@param  string	$close_code	Code renseigne si on classe a payee completement alors que paiement incomplet (cas escompte par exemple)
1942
	 *	@param  string	$close_note	Commentaire renseigne si on classe a payee alors que paiement incomplet (cas escompte par exemple)
1943
	 *  @return int         		<0 if KO, >0 if OK
1944
	 */
1945
	function set_paid($user, $close_code='', $close_note='')
1946
	{
1947
		$error=0;
1948
1949
		if ($this->paye != 1)
1950
		{
1951
			$this->db->begin();
1952
1953
			dol_syslog(get_class($this)."::set_paid rowid=".$this->id, LOG_DEBUG);
1954
			$sql = 'UPDATE '.MAIN_DB_PREFIX.'facture SET';
1955
			$sql.= ' fk_statut='.self::STATUS_CLOSED;
1956
			if (! $close_code) $sql.= ', paye=1';
1957
			if ($close_code) $sql.= ", close_code='".$this->db->escape($close_code)."'";
1958
			if ($close_note) $sql.= ", close_note='".$this->db->escape($close_note)."'";
1959
			$sql.= ' WHERE rowid = '.$this->id;
1960
1961
			dol_syslog(get_class($this)."::set_paid", LOG_DEBUG);
1962
			$resql = $this->db->query($sql);
1963
			if ($resql)
1964
			{
1965
	            // Call trigger
1966
	            $result=$this->call_trigger('BILL_PAYED',$user);
1967
	            if ($result < 0) $error++;
1968
	            // End call triggers
1969
			}
1970
			else
1971
			{
1972
				$error++;
1973
				$this->error=$this->db->lasterror();
1974
			}
1975
1976
			if (! $error)
1977
			{
1978
				$this->db->commit();
1979
				return 1;
1980
			}
1981
			else
1982
			{
1983
				$this->db->rollback();
1984
				return -1;
1985
			}
1986
		}
1987
		else
1988
		{
1989
			return 0;
1990
		}
1991
	}
1992
1993
1994
	/**
1995
	 *  Tag la facture comme non payee completement + appel trigger BILL_UNPAYED
1996
	 *	Fonction utilisee quand un paiement prelevement est refuse,
1997
	 * 	ou quand une facture annulee et reouverte.
1998
	 *
1999
	 *  @param	User	$user       Object user that change status
2000
	 *  @return int         		<0 if KO, >0 if OK
2001
	 */
2002
	function set_unpaid($user)
2003
	{
2004
		$error=0;
2005
2006
		$this->db->begin();
2007
2008
		$sql = 'UPDATE '.MAIN_DB_PREFIX.'facture';
2009
		$sql.= ' SET paye=0, fk_statut='.self::STATUS_VALIDATED.', close_code=null, close_note=null';
2010
		$sql.= ' WHERE rowid = '.$this->id;
2011
2012
		dol_syslog(get_class($this)."::set_unpaid", LOG_DEBUG);
2013
		$resql = $this->db->query($sql);
2014
		if ($resql)
2015
		{
2016
            // Call trigger
2017
            $result=$this->call_trigger('BILL_UNPAYED',$user);
2018
            if ($result < 0) $error++;
2019
            // End call triggers
2020
		}
2021
		else
2022
		{
2023
			$error++;
2024
			$this->error=$this->db->error();
2025
			dol_print_error($this->db);
2026
		}
2027
2028
		if (! $error)
2029
		{
2030
			$this->db->commit();
2031
			return 1;
2032
		}
2033
		else
2034
		{
2035
			$this->db->rollback();
2036
			return -1;
2037
		}
2038
	}
2039
2040
2041
	/**
2042
	 *	Tag invoice as canceled, with no payment on it (example for replacement invoice or payment never received) + call trigger BILL_CANCEL
2043
	 *	Warning, if option to decrease stock on invoice was set, this function does not change stock (it might be a cancel because
2044
	 *  of no payment even if merchandises were sent).
2045
	 *
2046
	 *	@param	User	$user        	Object user making change
2047
	 *	@param	string	$close_code		Code de fermeture
2048
	 *	@param	string	$close_note		Comment
2049
	 *	@return int         			<0 if KO, >0 if OK
2050
	 */
2051
	function set_canceled($user,$close_code='',$close_note='')
2052
	{
2053
2054
		dol_syslog(get_class($this)."::set_canceled rowid=".$this->id, LOG_DEBUG);
2055
2056
		$this->db->begin();
2057
2058
		$sql = 'UPDATE '.MAIN_DB_PREFIX.'facture SET';
2059
		$sql.= ' fk_statut='.self::STATUS_ABANDONED;
2060
		if ($close_code) $sql.= ", close_code='".$this->db->escape($close_code)."'";
2061
		if ($close_note) $sql.= ", close_note='".$this->db->escape($close_note)."'";
2062
		$sql.= ' WHERE rowid = '.$this->id;
2063
2064
		$resql = $this->db->query($sql);
2065
		if ($resql)
2066
		{
2067
			// On desaffecte de la facture les remises liees
2068
			// car elles n'ont pas ete utilisees vu que la facture est abandonnee.
2069
			$sql = 'UPDATE '.MAIN_DB_PREFIX.'societe_remise_except';
2070
			$sql.= ' SET fk_facture = NULL';
2071
			$sql.= ' WHERE fk_facture = '.$this->id;
2072
2073
			$resql=$this->db->query($sql);
2074
			if ($resql)
2075
			{
2076
	            // Call trigger
2077
	            $result=$this->call_trigger('BILL_CANCEL',$user);
2078
	            if ($result < 0)
2079
	            {
2080
					$this->db->rollback();
2081
					return -1;
2082
				}
2083
	            // End call triggers
2084
2085
				$this->db->commit();
2086
				return 1;
2087
			}
2088
			else
2089
			{
2090
				$this->error=$this->db->error()." sql=".$sql;
2091
				$this->db->rollback();
2092
				return -1;
2093
			}
2094
		}
2095
		else
2096
		{
2097
			$this->error=$this->db->error()." sql=".$sql;
2098
			$this->db->rollback();
2099
			return -2;
2100
		}
2101
	}
2102
2103
	/**
2104
	 * Tag invoice as validated + call trigger BILL_VALIDATE
2105
	 * Object must have lines loaded with fetch_lines
2106
	 *
2107
	 * @param	User	$user           Object user that validate
2108
	 * @param   string	$force_number	Reference to force on invoice
2109
	 * @param	int		$idwarehouse	Id of warehouse to use for stock decrease if option to decreasenon stock is on (0=no decrease)
2110
	 * @param	int		$notrigger		1=Does not execute triggers, 0= execute triggers
2111
     * @return	int						<0 if KO, 0=Nothing done because invoice is not a draft, >0 if OK
2112
	 */
2113
	function validate($user, $force_number='', $idwarehouse=0, $notrigger=0)
2114
	{
2115
		global $conf,$langs;
2116
		require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
2117
2118
		$now=dol_now();
2119
2120
		$error=0;
2121
		dol_syslog(get_class($this).'::validate user='.$user->id.', force_number='.$force_number.', idwarehouse='.$idwarehouse);
2122
2123
		// Force to have object complete for checks
2124
		$this->fetch_thirdparty();
2125
		$this->fetch_lines();
2126
2127
		// Check parameters
2128
		if (! $this->brouillon)
2129
		{
2130
			dol_syslog(get_class($this)."::validate no draft status", LOG_WARNING);
2131
			return 0;
2132
		}
2133
		if (count($this->lines) <= 0)
2134
		{
2135
        	$langs->load("errors");
2136
			$this->error=$langs->trans("ErrorObjectMustHaveLinesToBeValidated", $this->ref);
2137
			return -1;
2138
		}
2139
		if ((empty($conf->global->MAIN_USE_ADVANCED_PERMS) && empty($user->rights->facture->creer))
2140
       	|| (! empty($conf->global->MAIN_USE_ADVANCED_PERMS) && empty($user->rights->facture->invoice_advance->validate)))
2141
		{
2142
			$this->error='Permission denied';
2143
			dol_syslog(get_class($this)."::validate ".$this->error.' MAIN_USE_ADVANCED_PERMS='.$conf->global->MAIN_USE_ADVANCED_PERMS, LOG_ERR);
2144
			return -1;
2145
		}
2146
2147
		$this->db->begin();
2148
2149
		// Check parameters
2150
		if ($this->type == self::TYPE_REPLACEMENT)		// si facture de remplacement
2151
		{
2152
			// Controle que facture source connue
2153
			if ($this->fk_facture_source <= 0)
2154
			{
2155
				$this->error=$langs->trans("ErrorFieldRequired",$langs->trans("InvoiceReplacement"));
2156
				$this->db->rollback();
2157
				return -10;
2158
			}
2159
2160
			// Charge la facture source a remplacer
2161
			$facreplaced=new Facture($this->db);
2162
			$result=$facreplaced->fetch($this->fk_facture_source);
2163
			if ($result <= 0)
2164
			{
2165
				$this->error=$langs->trans("ErrorBadInvoice");
2166
				$this->db->rollback();
2167
				return -11;
2168
			}
2169
2170
			// Controle que facture source non deja remplacee par une autre
2171
			$idreplacement=$facreplaced->getIdReplacingInvoice('validated');
2172
			if ($idreplacement && $idreplacement != $this->id)
2173
			{
2174
				$facreplacement=new Facture($this->db);
2175
				$facreplacement->fetch($idreplacement);
2176
				$this->error=$langs->trans("ErrorInvoiceAlreadyReplaced",$facreplaced->ref,$facreplacement->ref);
2177
				$this->db->rollback();
2178
				return -12;
2179
			}
2180
2181
			$result=$facreplaced->set_canceled($user,'replaced','');
2182
			if ($result < 0)
2183
			{
2184
				$this->error=$facreplaced->error;
2185
				$this->db->rollback();
2186
				return -13;
2187
			}
2188
		}
2189
2190
		// Define new ref
2191
		if ($force_number)
2192
		{
2193
			$num = $force_number;
2194
		}
2195
		else if (preg_match('/^[\(]?PROV/i', $this->ref) || empty($this->ref)) // empty should not happened, but when it occurs, the test save life
2196
		{
2197
			if (! empty($conf->global->FAC_FORCE_DATE_VALIDATION))	// If option enabled, we force invoice date
2198
			{
2199
				$this->date=dol_now();
2200
				$this->date_lim_reglement=$this->calculate_date_lim_reglement();
2201
			}
2202
			$num = $this->getNextNumRef($this->thirdparty);
2203
		}
2204
		else
2205
		{
2206
			$num = $this->ref;
2207
		}
2208
		$this->newref = $num;
2209
2210
		if ($num)
2211
		{
2212
			$this->update_price(1);
2213
2214
			// Validate
2215
			$sql = 'UPDATE '.MAIN_DB_PREFIX.'facture';
2216
			$sql.= " SET facnumber='".$num."', fk_statut = ".self::STATUS_VALIDATED.", fk_user_valid = ".$user->id.", date_valid = '".$this->db->idate($now)."'";
2217
			if (! empty($conf->global->FAC_FORCE_DATE_VALIDATION))	// If option enabled, we force invoice date
2218
			{
2219
				$sql.= ", datef='".$this->db->idate($this->date)."'";
2220
				$sql.= ", date_lim_reglement='".$this->db->idate($this->date_lim_reglement)."'";
2221
			}
2222
			$sql.= ' WHERE rowid = '.$this->id;
2223
2224
			dol_syslog(get_class($this)."::validate", LOG_DEBUG);
2225
			$resql=$this->db->query($sql);
2226
			if (! $resql)
2227
			{
2228
				dol_print_error($this->db);
2229
				$error++;
2230
			}
2231
2232
			// On verifie si la facture etait une provisoire
2233
			if (! $error && (preg_match('/^[\(]?PROV/i', $this->ref)))
2234
			{
2235
				// La verif qu'une remise n'est pas utilisee 2 fois est faite au moment de l'insertion de ligne
2236
			}
2237
2238
			if (! $error)
2239
			{
2240
				// Define third party as a customer
2241
				$result=$this->thirdparty->set_as_client();
2242
2243
				// Si active on decremente le produit principal et ses composants a la validation de facture
2244
				if ($this->type != self::TYPE_DEPOSIT && $result >= 0 && ! empty($conf->stock->enabled) && ! empty($conf->global->STOCK_CALCULATE_ON_BILL) && $idwarehouse > 0)
2245
				{
2246
					require_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
2247
					$langs->load("agenda");
2248
2249
					// Loop on each line
2250
					$cpt=count($this->lines);
2251
					for ($i = 0; $i < $cpt; $i++)
2252
					{
2253
						if ($this->lines[$i]->fk_product > 0)
2254
						{
2255
							$mouvP = new MouvementStock($this->db);
2256
							$mouvP->origin = &$this;
2257
							// We decrease stock for product
2258
							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));
2259
							else $result=$mouvP->livraison($user, $this->lines[$i]->fk_product, $idwarehouse, $this->lines[$i]->qty, $this->lines[$i]->subprice, $langs->trans("InvoiceValidatedInDolibarr",$num));
2260
							if ($result < 0) {
2261
								$error++;
2262
							}
2263
						}
2264
					}
2265
				}
2266
			}
2267
2268
			// Trigger calls
2269
			if (! $error && ! $notrigger)
2270
			{
2271
	            // Call trigger
2272
	            $result=$this->call_trigger('BILL_VALIDATE',$user);
2273
	            if ($result < 0) $error++;
2274
	            // End call triggers
2275
			}
2276
2277
			if (! $error)
2278
			{
2279
				$this->oldref = $this->ref;
2280
2281
				// Rename directory if dir was a temporary ref
2282
				if (preg_match('/^[\(]?PROV/i', $this->ref))
2283
				{
2284
					// Rename of object directory ($this->ref = old ref, $num = new ref)
2285
					// to  not lose the linked files
2286
					$oldref = dol_sanitizeFileName($this->ref);
2287
					$newref = dol_sanitizeFileName($num);
2288
					$dirsource = $conf->facture->dir_output.'/'.$oldref;
2289
					$dirdest = $conf->facture->dir_output.'/'.$newref;
2290
					if (file_exists($dirsource))
2291
					{
2292
						dol_syslog(get_class($this)."::validate rename dir ".$dirsource." into ".$dirdest);
2293
2294
						if (@rename($dirsource, $dirdest))
2295
						{
2296
							dol_syslog("Rename ok");
2297
	                        // Rename docs starting with $oldref with $newref
2298
	                        $listoffiles=dol_dir_list($conf->facture->dir_output.'/'.$newref, 'files', 1, '^'.preg_quote($oldref,'/'));
2299
	                        foreach($listoffiles as $fileentry)
2300
	                        {
2301
	                        	$dirsource=$fileentry['name'];
2302
	                        	$dirdest=preg_replace('/^'.preg_quote($oldref,'/').'/',$newref, $dirsource);
2303
	                        	$dirsource=$fileentry['path'].'/'.$dirsource;
2304
	                        	$dirdest=$fileentry['path'].'/'.$dirdest;
2305
	                        	@rename($dirsource, $dirdest);
2306
	                        }
2307
						}
2308
					}
2309
				}
2310
			}
2311
2312
			if (! $error && !$this->is_last_in_cycle())
2313
			{
2314
				if (! $this->updatePriceNextInvoice($langs))
2315
				{
2316
					$error++;
2317
				}
2318
			}
2319
2320
			// Set new ref and define current statut
2321
			if (! $error)
2322
			{
2323
				$this->ref = $num;
2324
				$this->facnumber=$num;
2325
				$this->statut= self::STATUS_VALIDATED;
2326
				$this->brouillon=0;
2327
				$this->date_validation=$now;
2328
				$i = 0;
2329
2330
                if (!empty($conf->global->INVOICE_USE_SITUATION))
2331
                {
2332
    				$final = True;
2333
    				$nboflines = count($this->lines);
2334
    				while (($i < $nboflines) && $final) {
2335
    					$final = ($this->lines[$i]->situation_percent == 100);
2336
    					$i++;
2337
    				}
2338
    				if ($final) {
2339
    					$this->setFinal($user);
2340
    				}
2341
                }
2342
			}
2343
		}
2344
		else
2345
		{
2346
			$error++;
2347
		}
2348
2349
		if (! $error)
2350
		{
2351
			$this->db->commit();
2352
			return 1;
2353
		}
2354
		else
2355
		{
2356
			$this->db->rollback();
2357
			return -1;
2358
		}
2359
	}
2360
2361
	/**
2362
	 * Update price of next invoice
2363
	 *
2364
	 * @param	Translate	$langs	Translate object
2365
	 * @return bool		false if KO, true if OK
2366
	 */
2367
	function updatePriceNextInvoice(&$langs)
2368
	{
2369
		foreach ($this->tab_next_situation_invoice as $next_invoice)
2370
		{
2371
			$is_last = $next_invoice->is_last_in_cycle();
2372
2373
			if ($next_invoice->brouillon && $is_last != 1)
2374
			{
2375
				$this->error = $langs->trans('updatePriceNextInvoiceErrorUpdateline', $next_invoice->ref);
2376
				return false;
2377
			}
2378
2379
			$next_invoice->brouillon = 1;
2380
			foreach ($next_invoice->lines as $line)
2381
			{
2382
				$result = $next_invoice->updateline($line->id, $line->desc, $line->subprice, $line->qty, $line->remise_percent,
2383
														$line->date_start, $line->date_end, $line->tva_tx, $line->localtax1_tx, $line->localtax2_tx, 'HT', $line->info_bits, $line->product_type,
2384
														$line->fk_parent_line, 0, $line->fk_fournprice, $line->pa_ht, $line->label, $line->special_code, $line->array_options, $line->situation_percent,
2385
														$line->fk_unit);
2386
2387
				if ($result < 0)
2388
				{
2389
					$this->error = $langs->trans('updatePriceNextInvoiceErrorUpdateline', $next_invoice->ref);
2390
					return false;
2391
				}
2392
			}
2393
2394
			break; // Only the next invoice and not each next invoice
2395
		}
2396
2397
		return true;
2398
	}
2399
2400
	/**
2401
	 *	Set draft status
2402
	 *
2403
	 *	@param	User	$user			Object user that modify
2404
	 *	@param	int		$idwarehouse	Id warehouse to use for stock change.
2405
	 *	@return	int						<0 if KO, >0 if OK
2406
	 */
2407
	function set_draft($user,$idwarehouse=-1)
2408
	{
2409
		global $conf,$langs;
2410
2411
		$error=0;
2412
2413
		if ($this->statut == self::STATUS_DRAFT)
2414
		{
2415
			dol_syslog(get_class($this)."::set_draft already draft status", LOG_WARNING);
2416
			return 0;
2417
		}
2418
2419
		$this->db->begin();
2420
2421
		$sql = "UPDATE ".MAIN_DB_PREFIX."facture";
2422
		$sql.= " SET fk_statut = ".self::STATUS_DRAFT;
2423
		$sql.= " WHERE rowid = ".$this->id;
2424
2425
		dol_syslog(get_class($this)."::set_draft", LOG_DEBUG);
2426
		$result=$this->db->query($sql);
2427
		if ($result)
2428
		{
2429
			// Si on decremente le produit principal et ses composants a la validation de facture, on réincrement
2430
			if ($this->type != self::TYPE_DEPOSIT && $result >= 0 && ! empty($conf->stock->enabled) && ! empty($conf->global->STOCK_CALCULATE_ON_BILL))
2431
			{
2432
				require_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
2433
				$langs->load("agenda");
2434
2435
				$num=count($this->lines);
2436
				for ($i = 0; $i < $num; $i++)
2437
				{
2438
					if ($this->lines[$i]->fk_product > 0)
2439
					{
2440
						$mouvP = new MouvementStock($this->db);
2441
						$mouvP->origin = &$this;
2442
						// We decrease stock for product
2443
						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));
2444
						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
2445
					}
2446
				}
2447
			}
2448
2449
			if ($error == 0)
2450
			{
2451
				$old_statut=$this->statut;
2452
				$this->brouillon = 1;
2453
				$this->statut = self::STATUS_DRAFT;
2454
	            // Call trigger
2455
	            $result=$this->call_trigger('BILL_UNVALIDATE',$user);
2456
	            if ($result < 0)
2457
				{
2458
					$error++;
2459
					$this->statut=$old_statut;
2460
					$this->brouillon=0;
2461
				}
2462
	            // End call triggers
2463
			} else {
2464
				$this->db->rollback();
2465
				return -1;
2466
			}
2467
2468
			if ($error == 0)
2469
			{
2470
				$this->db->commit();
2471
				return 1;
2472
			}
2473
			else
2474
			{
2475
				$this->db->rollback();
2476
				return -1;
2477
			}
2478
		}
2479
		else
2480
		{
2481
			$this->error=$this->db->error();
2482
			$this->db->rollback();
2483
			return -1;
2484
		}
2485
	}
2486
2487
2488
	/**
2489
	 * 		Add an invoice line into database (linked to product/service or not).
2490
	 * 		Les parametres sont deja cense etre juste et avec valeurs finales a l'appel
2491
	 *		de cette methode. Aussi, pour le taux tva, il doit deja avoir ete defini
2492
	 *		par l'appelant par la methode get_default_tva(societe_vendeuse,societe_acheteuse,produit)
2493
	 *		et le desc doit deja avoir la bonne valeur (a l'appelant de gerer le multilangue)
2494
	 *
2495
	 * 		@param    	string		$desc            	Description of line
2496
	 * 		@param    	double		$pu_ht              Unit price without tax (> 0 even for credit note)
2497
	 * 		@param    	double		$qty             	Quantity
2498
	 * 		@param    	double		$txtva           	Force Vat rate, -1 for auto (Can contain the vat_src_code too with syntax '9.9 (CODE)')
2499
	 * 		@param		double		$txlocaltax1		Local tax 1 rate (deprecated, use instead txtva with code inside)
2500
	 *  	@param		double		$txlocaltax2		Local tax 2 rate (deprecated, use instead txtva with code inside)
2501
	 *		@param    	int			$fk_product      	Id of predefined product/service
2502
	 * 		@param    	double		$remise_percent  	Percent of discount on line
2503
	 * 		@param    	int	$date_start      	Date start of service
2504
	 * 		@param    	int	$date_end        	Date end of service
2505
	 * 		@param    	int			$ventil          	Code of dispatching into accountancy
2506
	 * 		@param    	int			$info_bits			Bits de type de lignes
2507
	 *		@param    	int			$fk_remise_except	Id discount used
2508
	 *		@param		string		$price_base_type	'HT' or 'TTC'
2509
	 * 		@param    	double		$pu_ttc             Unit price with tax (> 0 even for credit note)
2510
	 * 		@param		int			$type				Type of line (0=product, 1=service). Not used if fk_product is defined, the type of product is used.
2511
	 *      @param      int			$rang               Position of line
2512
	 *      @param		int			$special_code		Special code (also used by externals modules!)
2513
	 *      @param		string		$origin				'order', ...
2514
	 *      @param		int			$origin_id			Id of origin object
2515
	 *      @param		int			$fk_parent_line		Id of parent line
2516
	 * 		@param		int			$fk_fournprice		Supplier price id (to calculate margin) or ''
2517
	 * 		@param		int			$pa_ht				Buying price of line (to calculate margin) or ''
2518
	 * 		@param		string		$label				Label of the line (deprecated, do not use)
2519
	 *		@param		array		$array_options		extrafields array
2520
	 *      @param      int         $situation_percent  Situation advance percentage
2521
	 *      @param      int         $fk_prev_id         Previous situation line id reference
2522
	 * 		@param 		string		$fk_unit 			Code of the unit to use. Null to use the default one
2523
	 * 		@param		double		$pu_ht_devise		Unit price in currency
2524
	 *    	@return    	int             				<0 if KO, Id of line if OK
2525
	 */
2526
	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='', $fk_unit = null, $pu_ht_devise = 0)
2527
	{
2528
		// Deprecation warning
2529
		if ($label) {
2530
			dol_syslog(__METHOD__ . ": using line label is deprecated", LOG_WARNING);
2531
		}
2532
2533
		global $mysoc, $conf, $langs;
2534
2535
		dol_syslog(get_class($this)."::addline facid=$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);
2536
		include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
2537
2538
		// Clean parameters
2539
		if (empty($remise_percent)) $remise_percent=0;
2540
		if (empty($qty)) $qty=0;
2541
		if (empty($info_bits)) $info_bits=0;
2542
		if (empty($rang)) $rang=0;
2543
		if (empty($ventil)) $ventil=0;
2544
		if (empty($txtva)) $txtva=0;
2545
		if (empty($txlocaltax1)) $txlocaltax1=0;
2546
		if (empty($txlocaltax2)) $txlocaltax2=0;
2547
		if (empty($fk_parent_line) || $fk_parent_line < 0) $fk_parent_line=0;
2548
		if (empty($fk_prev_id)) $fk_prev_id = 'null';
2549
		if (! isset($situation_percent) || $situation_percent > 100 || (string) $situation_percent == '') $situation_percent = 100;
2550
2551
		$localtaxes_type=getLocalTaxesFromRate($txtva, 0, $this->thirdparty, $mysoc);
2552
2553
		// Clean vat code
2554
		$vat_src_code='';
2555
		if (preg_match('/\((.*)\)/', $txtva, $reg))
2556
		{
2557
		    $vat_src_code = $reg[1];
2558
		    $txtva = preg_replace('/\s*\(.*\)/', '', $txtva);    // Remove code into vatrate.
2559
		}
2560
2561
		$remise_percent=price2num($remise_percent);
2562
		$qty=price2num($qty);
2563
		$pu_ht=price2num($pu_ht);
2564
		$pu_ttc=price2num($pu_ttc);
2565
		$pa_ht=price2num($pa_ht);
2566
		$txtva=price2num($txtva);
2567
		$txlocaltax1=price2num($txlocaltax1);
2568
		$txlocaltax2=price2num($txlocaltax2);
2569
2570
		if ($price_base_type=='HT')
2571
		{
2572
			$pu=$pu_ht;
2573
		}
2574
		else
2575
		{
2576
			$pu=$pu_ttc;
2577
		}
2578
2579
		// Check parameters
2580
		if ($type < 0) return -1;
2581
2582
		if (! empty($this->brouillon))
2583
		{
2584
			$this->db->begin();
2585
2586
			$product_type=$type;
2587
			if (!empty($fk_product))
2588
			{
2589
				$product=new Product($this->db);
2590
				$result=$product->fetch($fk_product);
2591
				$product_type=$product->type;
2592
2593
				if (! empty($conf->global->STOCK_MUST_BE_ENOUGH_FOR_INVOICE) && $product_type == 0 && $product->stock_reel < $qty) {
2594
                    $langs->load("errors");
2595
				    $this->error=$langs->trans('ErrorStockIsNotEnoughToAddProductOnInvoice', $product->ref);
2596
					$this->db->rollback();
2597
					return -3;
2598
				}
2599
			}
2600
2601
			// Calcul du total TTC et de la TVA pour la ligne a partir de
2602
			// qty, pu, remise_percent et txtva
2603
			// TRES IMPORTANT: C'est au moment de l'insertion ligne qu'on doit stocker
2604
			// la part ht, tva et ttc, et ce au niveau de la ligne qui a son propre taux tva.
2605
2606
			$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);
2607
2608
			$total_ht  = $tabprice[0];
2609
			$total_tva = $tabprice[1];
2610
			$total_ttc = $tabprice[2];
2611
			$total_localtax1 = $tabprice[9];
2612
			$total_localtax2 = $tabprice[10];
2613
			$pu_ht = $tabprice[3];
2614
2615
			// MultiCurrency
2616
			$multicurrency_total_ht  = $tabprice[16];
2617
            $multicurrency_total_tva = $tabprice[17];
2618
            $multicurrency_total_ttc = $tabprice[18];
2619
			$pu_ht_devise = $tabprice[19];
2620
2621
			// Rank to use
2622
			$rangtouse = $rang;
2623
			if ($rangtouse == -1)
2624
			{
2625
				$rangmax = $this->line_max($fk_parent_line);
2626
				$rangtouse = $rangmax + 1;
2627
			}
2628
2629
			// Insert line
2630
			$this->line=new FactureLigne($this->db);
2631
2632
			$this->line->context = $this->context;
2633
2634
			$this->line->fk_facture=$this->id;
2635
			$this->line->label=$label;	// deprecated
2636
			$this->line->desc=$desc;
2637
2638
			$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 double or string. However, the property $qty 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...
2639
			$this->line->subprice=       ($this->type==self::TYPE_CREDIT_NOTE?-abs($pu_ht):$pu_ht); // For credit note, unit price always negative, always positive otherwise
2640
2641
			$this->line->vat_src_code=$vat_src_code;
2642
			$this->line->tva_tx=$txtva;
1 ignored issue
show
Documentation Bug introduced by
It seems like $txtva can also be of type string. However, the property $tva_tx is declared as type double. Maybe add an additional type check?

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

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

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

class Id
{
    public $id;

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

}

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

$account_id = false;

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

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
2643
			$this->line->localtax1_tx=$txlocaltax1;
1 ignored issue
show
Documentation Bug introduced by
It seems like $txlocaltax1 can also be of type string. However, the property $localtax1_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...
2644
			$this->line->localtax2_tx=$txlocaltax2;
1 ignored issue
show
Documentation Bug introduced by
It seems like $txlocaltax2 can also be of type string. However, the property $localtax2_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...
2645
			$this->line->localtax1_type = $localtaxes_type[0];
2646
			$this->line->localtax2_type = $localtaxes_type[2];
2647
2648
			$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
2649
			$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
2650
			$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
2651
			$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
2652
			$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
2653
2654
			$this->line->fk_product=$fk_product;
2655
			$this->line->product_type=$product_type;
2656
			$this->line->remise_percent=$remise_percent;
1 ignored issue
show
Documentation Bug introduced by
It seems like $remise_percent can also be of type string. However, the property $remise_percent 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...
2657
			$this->line->date_start=$date_start;
2658
			$this->line->date_end=$date_end;
2659
			$this->line->ventil=$ventil;
2660
			$this->line->rang=$rangtouse;
2661
			$this->line->info_bits=$info_bits;
2662
			$this->line->fk_remise_except=$fk_remise_except;
2663
2664
			$this->line->special_code=$special_code;
2665
			$this->line->fk_parent_line=$fk_parent_line;
2666
			$this->line->origin=$origin;
2667
			$this->line->origin_id=$origin_id;
2668
			$this->line->situation_percent = $situation_percent;
2669
			$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...
2670
			$this->line->fk_unit=$fk_unit;
2671
2672
			// infos marge
2673
			$this->line->fk_fournprice = $fk_fournprice;
2674
			$this->line->pa_ht = $pa_ht;
2675
2676
			// Multicurrency
2677
			$this->line->fk_multicurrency			= $this->fk_multicurrency;
2678
			$this->line->multicurrency_code			= $this->multicurrency_code;
2679
			$this->line->multicurrency_subprice		= $pu_ht_devise;
2680
			$this->line->multicurrency_total_ht 	= $multicurrency_total_ht;
2681
            $this->line->multicurrency_total_tva 	= $multicurrency_total_tva;
2682
            $this->line->multicurrency_total_ttc 	= $multicurrency_total_ttc;
2683
2684
			if (is_array($array_options) && count($array_options)>0) {
2685
				$this->line->array_options=$array_options;
2686
			}
2687
2688
			$result=$this->line->insert();
2689
			if ($result > 0)
2690
			{
2691
				// Reorder if child line
2692
				if (! empty($fk_parent_line)) $this->line_order(true,'DESC');
2693
2694
				// Mise a jour informations denormalisees au niveau de la facture meme
2695
				$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.
2696
				if ($result > 0)
2697
				{
2698
					$this->db->commit();
2699
					return $this->line->rowid;
2700
				}
2701
				else
2702
				{
2703
					$this->error=$this->db->error();
2704
					$this->db->rollback();
2705
					return -1;
2706
				}
2707
			}
2708
			else
2709
			{
2710
				$this->error=$this->line->error;
2711
				$this->db->rollback();
2712
				return -2;
2713
			}
2714
		}
2715
		else
2716
		{
2717
			dol_syslog(get_class($this)."::addline status of order must be Draft to allow use of ->addline()", LOG_ERR);
2718
			return -3;
2719
		}
2720
	}
2721
2722
	/**
2723
	 *  Update a detail line
2724
	 *
2725
	 *  @param     	int			$rowid           	Id of line to update
2726
	 *  @param     	string		$desc            	Description of line
2727
	 *  @param     	double		$pu              	Prix unitaire (HT ou TTC selon price_base_type) (> 0 even for credit note lines)
2728
	 *  @param     	double		$qty             	Quantity
2729
	 *  @param     	double		$remise_percent  	Pourcentage de remise de la ligne
2730
	 *  @param     	int		$date_start      	Date de debut de validite du service
2731
	 *  @param     	int		$date_end        	Date de fin de validite du service
2732
	 *  @param     	double		$txtva          	VAT Rate (Can be '8.5', '8.5 (ABC)')
2733
	 * 	@param		double		$txlocaltax1		Local tax 1 rate
2734
	 *  @param		double		$txlocaltax2		Local tax 2 rate
2735
	 * 	@param     	string		$price_base_type 	HT or TTC
2736
	 * 	@param     	int			$info_bits 		    Miscellaneous informations
2737
	 * 	@param		int			$type				Type of line (0=product, 1=service)
2738
	 * 	@param		int			$fk_parent_line		Id of parent line (0 in most cases, used by modules adding sublevels into lines).
2739
	 * 	@param		int			$skip_update_total	Keep fields total_xxx to 0 (used for special lines by some modules)
2740
	 * 	@param		int			$fk_fournprice		Id of origin supplier price
2741
	 * 	@param		int			$pa_ht				Price (without tax) of product when it was bought
2742
	 * 	@param		string		$label				Label of the line (deprecated, do not use)
2743
	 * 	@param		int			$special_code		Special code (also used by externals modules!)
2744
     *  @param		array		$array_options		extrafields array
2745
	 * 	@param      int         $situation_percent  Situation advance percentage
2746
	 * 	@param 		string		$fk_unit 			Code of the unit to use. Null to use the default one
2747
	 * 	@param		double		$pu_ht_devise		Unit price in currency
2748
	 * 	@param		int			$notrigger			disable line update trigger
2749
	 *  @return    	int             				< 0 if KO, > 0 if OK
2750
	 */
2751
	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)
2752
	{
2753
		global $conf,$user;
2754
		// Deprecation warning
2755
		if ($label) {
2756
			dol_syslog(__METHOD__ . ": using line label is deprecated", LOG_WARNING);
2757
		}
2758
2759
		include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
2760
2761
		global $mysoc,$langs;
2762
2763
		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);
2764
2765
		if ($this->brouillon)
2766
		{
2767
			if (!$this->is_last_in_cycle() && empty($this->error))
2768
			{
2769
				if (!$this->checkProgressLine($rowid, $situation_percent))
2770
				{
2771
					if (!$this->error) $this->error=$langs->trans('invoiceLineProgressError');
2772
					return -3;
2773
				}
2774
			}
2775
2776
			$this->db->begin();
2777
2778
			// Clean parameters
2779
			if (empty($qty)) $qty=0;
2780
			if (empty($fk_parent_line) || $fk_parent_line < 0) $fk_parent_line=0;
2781
			if (empty($special_code) || $special_code == 3) $special_code=0;
2782
			if (! isset($situation_percent) || $situation_percent > 100 || (string) $situation_percent == '') $situation_percent = 100;
2783
2784
			$remise_percent	= price2num($remise_percent);
2785
			$qty			= price2num($qty);
2786
			$pu 			= price2num($pu);
2787
			$pa_ht			= price2num($pa_ht);
2788
			$txtva			= price2num($txtva);
2789
			$txlocaltax1	= price2num($txlocaltax1);
2790
			$txlocaltax2	= price2num($txlocaltax2);
2791
2792
			// Check parameters
2793
			if ($type < 0) return -1;
2794
2795
			// Calculate total with, without tax and tax from qty, pu, remise_percent and txtva
2796
			// TRES IMPORTANT: C'est au moment de l'insertion ligne qu'on doit stocker
2797
			// la part ht, tva et ttc, et ce au niveau de la ligne qui a son propre taux tva.
2798
2799
			$localtaxes_type=getLocalTaxesFromRate($txtva,0,$this->thirdparty, $mysoc);
2800
2801
			// Clean vat code
2802
    		$vat_src_code='';
2803
    		if (preg_match('/\((.*)\)/', $txtva, $reg))
2804
    		{
2805
    		    $vat_src_code = $reg[1];
2806
    		    $txtva = preg_replace('/\s*\(.*\)/', '', $txtva);    // Remove code into vatrate.
2807
    		}
2808
2809
			$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);
2810
2811
			$total_ht  = $tabprice[0];
2812
			$total_tva = $tabprice[1];
2813
			$total_ttc = $tabprice[2];
2814
			$total_localtax1=$tabprice[9];
2815
			$total_localtax2=$tabprice[10];
2816
			$pu_ht  = $tabprice[3];
2817
			$pu_tva = $tabprice[4];
2818
			$pu_ttc = $tabprice[5];
2819
2820
			// MultiCurrency
2821
			$multicurrency_total_ht  = $tabprice[16];
2822
            $multicurrency_total_tva = $tabprice[17];
2823
            $multicurrency_total_ttc = $tabprice[18];
2824
			$pu_ht_devise = $tabprice[19];
2825
2826
			// Old properties: $price, $remise (deprecated)
2827
			$price = $pu;
2828
			$remise = 0;
2829
			if ($remise_percent > 0)
2830
			{
2831
				$remise = round(($pu * $remise_percent / 100),2);
2832
				$price = ($pu - $remise);
2833
			}
2834
			$price    = price2num($price);
2835
2836
			//Fetch current line from the database and then clone the object and set it in $oldline property
2837
			$line = new FactureLigne($this->db);
2838
			$line->fetch($rowid);
2839
2840
			if (!empty($line->fk_product))
2841
			{
2842
				$product=new Product($this->db);
2843
				$result=$product->fetch($line->fk_product);
2844
				$product_type=$product->type;
2845
2846
				if (! empty($conf->global->STOCK_MUST_BE_ENOUGH_FOR_INVOICE) && $product_type == 0 && $product->stock_reel < $qty) {
2847
                    $langs->load("errors");
2848
				    $this->error=$langs->trans('ErrorStockIsNotEnoughToAddProductOnInvoice', $product->ref);
2849
					$this->db->rollback();
2850
					return -3;
2851
				}
2852
			}
2853
2854
			$staticline = clone $line;
2855
2856
			$line->oldline = $staticline;
2857
			$this->line = $line;
2858
            $this->line->context = $this->context;
2859
2860
			// Reorder if fk_parent_line change
2861
			if (! empty($fk_parent_line) && ! empty($staticline->fk_parent_line) && $fk_parent_line != $staticline->fk_parent_line)
2862
			{
2863
				$rangmax = $this->line_max($fk_parent_line);
2864
				$this->line->rang = $rangmax + 1;
2865
			}
2866
2867
			$this->line->rowid				= $rowid;
2868
			$this->line->label				= $label;
2869
			$this->line->desc				= $desc;
2870
			$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 double or string. However, the property $qty 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...
2871
2872
			$this->line->vat_src_code       = $vat_src_code;
2873
			$this->line->tva_tx				= $txtva;
1 ignored issue
show
Documentation Bug introduced by
It seems like $txtva can also be of type string. However, the property $tva_tx is declared as type double. Maybe add an additional type check?

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

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

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

class Id
{
    public $id;

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

}

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

$account_id = false;

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

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
2874
			$this->line->localtax1_tx		= $txlocaltax1;
1 ignored issue
show
Documentation Bug introduced by
It seems like $txlocaltax1 can also be of type string. However, the property $localtax1_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...
2875
			$this->line->localtax2_tx		= $txlocaltax2;
1 ignored issue
show
Documentation Bug introduced by
It seems like $txlocaltax2 can also be of type string. However, the property $localtax2_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...
2876
			$this->line->localtax1_type		= $localtaxes_type[0];
2877
			$this->line->localtax2_type		= $localtaxes_type[2];
2878
2879
			$this->line->remise_percent		= $remise_percent;
1 ignored issue
show
Documentation Bug introduced by
It seems like $remise_percent can also be of type string. However, the property $remise_percent 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...
2880
			$this->line->subprice			= ($this->type==2?-abs($pu_ht):$pu_ht); // For credit note, unit price always negative, always positive otherwise
2881
			$this->line->date_start			= $date_start;
2882
			$this->line->date_end			= $date_end;
2883
			$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
2884
			$this->line->total_tva			= (($this->type==self::TYPE_CREDIT_NOTE||$qty<0)?-abs($total_tva):$total_tva);
2885
			$this->line->total_localtax1	= $total_localtax1;
2886
			$this->line->total_localtax2	= $total_localtax2;
2887
			$this->line->total_ttc			= (($this->type==self::TYPE_CREDIT_NOTE||$qty<0)?-abs($total_ttc):$total_ttc);
2888
			$this->line->info_bits			= $info_bits;
2889
			$this->line->special_code		= $special_code;
2890
			$this->line->product_type		= $type;
2891
			$this->line->fk_parent_line		= $fk_parent_line;
2892
			$this->line->skip_update_total	= $skip_update_total;
2893
			$this->line->situation_percent  = $situation_percent;
2894
			$this->line->fk_unit				= $fk_unit;
2895
2896
			$this->line->fk_fournprice = $fk_fournprice;
2897
			$this->line->pa_ht = $pa_ht;
2898
2899
			// Multicurrency
2900
			$this->line->multicurrency_subprice		= $pu_ht_devise;
2901
			$this->line->multicurrency_total_ht 	= $multicurrency_total_ht;
2902
            $this->line->multicurrency_total_tva 	= $multicurrency_total_tva;
2903
            $this->line->multicurrency_total_ttc 	= $multicurrency_total_ttc;
2904
2905
			if (is_array($array_options) && count($array_options)>0) {
2906
				$this->line->array_options=$array_options;
2907
			}
2908
2909
			$result=$this->line->update($user, $notrigger);
2910
			if ($result > 0)
2911
			{
2912
				// Reorder if child line
2913
				if (! empty($fk_parent_line)) $this->line_order(true,'DESC');
2914
2915
				// Mise a jour info denormalisees au niveau facture
2916
				$this->update_price(1);
2917
				$this->db->commit();
2918
				return $result;
2919
			}
2920
			else
2921
			{
2922
			    $this->error=$this->line->error;
2923
				$this->db->rollback();
2924
				return -1;
2925
			}
2926
		}
2927
		else
2928
		{
2929
			$this->error="Invoice statut makes operation forbidden";
2930
			return -2;
2931
		}
2932
	}
2933
2934
	/**
2935
	 * Check if the percent edited is lower of next invoice line
2936
	 *
2937
	 * @param	int		$idline				id of line to check
2938
	 * @param	float	$situation_percent	progress percentage need to be test
2939
	 * @return false if KO, true if OK
2940
	 */
2941
	function checkProgressLine($idline, $situation_percent)
2942
	{
2943
		$sql = 'SELECT fd.situation_percent FROM '.MAIN_DB_PREFIX.'facturedet fd
2944
				INNER JOIN '.MAIN_DB_PREFIX.'facture f ON (fd.fk_facture = f.rowid)
2945
				WHERE fd.fk_prev_id = '.$idline.'
2946
				AND f.fk_statut <> 0';
2947
2948
		$result = $this->db->query($sql);
2949
		if (! $result)
2950
		{
2951
			$this->error=$this->db->error();
2952
			return false;
2953
		}
2954
2955
		$obj = $this->db->fetch_object($result);
2956
2957
		if ($obj === null) return true;
2958
		else return $situation_percent < $obj->situation_percent;
2959
	}
2960
2961
	/**
2962
	 * Update invoice line with percentage
2963
	 *
2964
	 * @param  FactureLigne $line       Invoice line
2965
	 * @param  int          $percent    Percentage
2966
	 * @return void
2967
	 */
2968
	function update_percent($line, $percent)
2969
	{
2970
	    global $mysoc,$user;
2971
2972
		include_once(DOL_DOCUMENT_ROOT . '/core/lib/price.lib.php');
2973
2974
		// Cap percentages to 100
2975
		if ($percent > 100) $percent = 100;
2976
		$line->situation_percent = $percent;
2977
		$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);
2978
		$line->total_ht = $tabprice[0];
2979
		$line->total_tva = $tabprice[1];
2980
		$line->total_ttc = $tabprice[2];
2981
		$line->total_localtax1 = $tabprice[9];
2982
		$line->total_localtax2 = $tabprice[10];
2983
		$line->update($user);
2984
		$this->update_price(1);
2985
		$this->db->commit();
2986
	}
2987
2988
	/**
2989
	 *	Delete line in database
2990
	 *
2991
	 *	@param		int		$rowid		Id of line to delete
2992
	 *	@return		int					<0 if KO, >0 if OK
2993
	 */
2994
	function deleteline($rowid)
2995
	{
2996
        global $user;
2997
2998
		dol_syslog(get_class($this)."::deleteline rowid=".$rowid, LOG_DEBUG);
2999
3000
		if (! $this->brouillon)
3001
		{
3002
			$this->error='ErrorBadStatus';
3003
			return -1;
3004
		}
3005
3006
		$this->db->begin();
3007
3008
		// Libere remise liee a ligne de facture
3009
		$sql = 'UPDATE '.MAIN_DB_PREFIX.'societe_remise_except';
3010
		$sql.= ' SET fk_facture_line = NULL';
3011
		$sql.= ' WHERE fk_facture_line = '.$rowid;
3012
3013
		dol_syslog(get_class($this)."::deleteline", LOG_DEBUG);
3014
		$result = $this->db->query($sql);
3015
		if (! $result)
3016
		{
3017
			$this->error=$this->db->error();
3018
			$this->db->rollback();
3019
			return -1;
3020
		}
3021
3022
		$line=new FactureLigne($this->db);
3023
3024
        $line->context = $this->context;
3025
3026
		// For triggers
3027
		$result = $line->fetch($rowid);
3028
		if (! ($result > 0)) dol_print_error($db, $line->error, $line->errors);
0 ignored issues
show
Bug introduced by
The variable $db does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

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