Completed
Branch develop (d076ff)
by
unknown
28:37
created

Facture   F

Complexity

Total Complexity 632

Size/Duplication

Total Lines 4072
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 17

Importance

Changes 0
Metric Value
dl 0
loc 4072
rs 0.6314
c 0
b 0
f 0
wmc 632
lcom 1
cbo 17

50 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
F create() 0 499 101
C createFromCurrent() 0 71 8
F createFromClone() 0 108 18
D createFromOrder() 0 96 9
F getNomUrl() 0 74 32
F fetch() 0 142 16
B fetch_lines() 0 97 3
B fetchPreviousNextSituationInvoice() 0 25 6
F update() 0 108 59
B insert_discount() 0 75 6
C set_ref_client() 0 55 10
F delete() 0 157 30
C set_paid() 0 47 8
B set_unpaid() 0 37 4
B set_canceled() 0 51 6
F validate() 0 241 48
B updatePriceNextInvoice() 0 32 6
C set_draft() 0 79 13
F addline() 0 190 42
F updateline() 0 182 37
A checkProgressLine() 0 19 3
A update_percent() 0 19 2
B deleteline() 0 59 6
C set_remise() 0 54 10
C set_remise_absolue() 0 57 11
B getListOfPayments() 0 46 6
C getNextNumRef() 0 77 15
B info() 0 37 5
B getVentilExportCompta() 0 22 5
C is_erasable() 0 31 11
C liste_array() 0 59 15
B list_replacable_invoices() 0 38 4
C list_qualified_avoir_invoices() 0 45 8
C demande_prelevement() 0 101 12
A demande_prelevement_delete() 0 16 2
B load_board() 0 54 7
A getIdBillingContact() 0 4 1
A getIdShippingContact() 0 4 1
D initAsSpecimen() 0 128 10
B load_state_board() 0 36 5
A getLinesArray() 0 4 1
A generateDocument() 0 21 4
A newCycle() 0 22 3
A is_first() 0 4 1
B get_prev_sits() 0 25 5
C setFinal() 0 41 8
B is_last_in_cycle() 0 22 5
A replaceThirdparty() 0 8 1
A hasDelay() 0 11 2

How to fix   Complexity   

Complex Class

Complex classes like Facture often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

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

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

1
<?php
2
/* Copyright (C) 2002-2007 Rodolphe Quiedeville  <[email protected]>
3
 * Copyright (C) 2004-2013 Laurent Destailleur   <[email protected]>
4
 * Copyright (C) 2004      Sebastien Di Cintio   <[email protected]>
5
 * Copyright (C) 2004      Benoit Mortier        <[email protected]>
6
 * Copyright (C) 2005      Marc Barilley / Ocebo <[email protected]>
7
 * Copyright (C) 2005-2014 Regis Houssin         <[email protected]>
8
 * Copyright (C) 2006      Andre Cianfarani      <[email protected]>
9
 * Copyright (C) 2007      Franky Van Liedekerke <[email protected]>
10
 * Copyright (C) 2010-2016 Juanjo Menent         <[email protected]>
11
 * Copyright (C) 2012-2014 Christophe Battarel   <[email protected]>
12
 * Copyright (C) 2012-2015 Marcos García         <[email protected]>
13
 * Copyright (C) 2012      Cédric Salvador       <[email protected]>
14
 * Copyright (C) 2012-2014 Raphaël Doursenaud    <[email protected]>
15
 * Copyright (C) 2013      Cedric Gross          <[email protected]>
16
 * Copyright (C) 2013      Florian Henry		  	<[email protected]>
17
 * Copyright (C) 2016      Ferran Marcet        <[email protected]>
18
 *
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
	protected $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_ttc=0;
83
	public $revenuestamp;
84
85
	//! Fermeture apres paiement partiel: discount_vat, badcustomer, abandon
86
	//! Fermeture alors que aucun paiement: replaced (si remplace), abandon
87
	public $close_code;
88
	//! Commentaire si mis a paye sans paiement complet
89
	public $close_note;
90
	//! 1 if invoice paid COMPLETELY, 0 otherwise (do not use it anymore, use statut and close_code)
91
	public $paye;
92
	//! id of source invoice if replacement invoice or credit note
93
	public $fk_facture_source;
94
	public $linked_objects=array();
95
	public $date_lim_reglement;
96
	public $cond_reglement_code;		// Code in llx_c_paiement
97
	public $mode_reglement_code;		// Code in llx_c_paiement
98
	public $fk_bank;					// Field to store bank id to use when payment mode is withdraw
99
	/**
100
	 * @deprecated
101
	 */
102
	public $products=array();
103
	/**
104
	 * @var FactureLigne[]
105
	 */
106
	public $lines=array();
107
	public $line;
108
	public $extraparams=array();
109
	public $specimen;
110
111
	public $fac_rec;
112
113
	// Multicurrency
114
	public $fk_multicurrency;
115
	public $multicurrency_code;
116
	public $multicurrency_tx;
117
	public $multicurrency_total_ht;
118
	public $multicurrency_total_tva;
119
	public $multicurrency_total_ttc;
120
121
	/**
122
	 * @var int Situation cycle reference number
123
	 */
124
	public $situation_cycle_ref;
125
126
	/**
127
	 * @var int Situation counter inside the cycle
128
	 */
129
	public $situation_counter;
130
131
	/**
132
	 * @var bool Final situation flag
133
	 */
134
	public $situation_final;
135
136
	/**
137
	 * @var array Table of previous situations
138
	 */
139
	public $tab_previous_situation_invoice=array();
140
141
	/**
142
	 * @var array Table of next situations
143
	 */
144
	public $tab_next_situation_invoice=array();
145
146
	public $oldcopy;
147
148
    /**
149
     * Standard invoice
150
     */
151
    const TYPE_STANDARD = 0;
152
153
    /**
154
     * Replacement invoice
155
     */
156
    const TYPE_REPLACEMENT = 1;
157
158
    /**
159
     * Credit note invoice
160
     */
161
    const TYPE_CREDIT_NOTE = 2;
162
163
    /**
164
     * Deposit invoice
165
     */
166
    const TYPE_DEPOSIT = 3;
167
168
    /**
169
     * Proforma invoice (should not be used. a proforma is an order)
170
     */
171
    const TYPE_PROFORMA = 4;
172
173
	/**
174
	 * Situation invoice
175
	 */
176
	const TYPE_SITUATION = 5;
177
178
	/**
179
	 * Draft
180
	 */
181
	const STATUS_DRAFT = 0;
182
183
	/**
184
	 * Validated (need to be paid)
185
	 */
186
	const STATUS_VALIDATED = 1;
187
188
	/**
189
	 * Classified paid.
190
	 * If paid partially, $this->close_code can be:
191
	 * - CLOSECODE_DISCOUNTVAT
192
	 * - CLOSECODE_BADDEBT
193
	 * If paid completelly, this->close_code will be null
194
	 */
195
	const STATUS_CLOSED = 2;
196
197
	/**
198
	 * Classified abandoned and no payment done.
199
	 * $this->close_code can be:
200
	 * - CLOSECODE_BADDEBT
201
	 * - CLOSECODE_ABANDONED
202
	 * - CLOSECODE_REPLACED
203
	 */
204
	const STATUS_ABANDONED = 3;
205
206
	const CLOSECODE_DISCOUNTVAT = 'discount_vat';
207
	const CLOSECODE_BADDEBT = 'badcustomer';
208
	const CLOSECODE_ABANDONED = 'abandon';
209
	const CLOSECODE_REPLACED = 'replaced';
210
211
	/**
212
	 * 	Constructor
213
	 *
214
	 * 	@param	DoliDB		$db			Database handler
215
	 */
216
	function __construct($db)
217
	{
218
		$this->db = $db;
219
	}
220
221
	/**
222
	 *	Create invoice in database.
223
	 *  Note: this->ref can be set or empty. If empty, we will use "(PROV999)"
224
	 *  Note: this->fac_rec must be set to create invoice from a recurring invoice
225
	 *
226
	 *	@param	User	$user      		Object user that create
227
	 *	@param  int		$notrigger		1=Does not execute triggers, 0 otherwise
228
	 * 	@param	int		$forceduedate	1=Do not recalculate due date from payment condition but force it with value
229
	 *	@return	int						<0 if KO, >0 if OK
230
	 */
231
	function create($user,$notrigger=0,$forceduedate=0)
232
	{
233
		global $langs,$conf,$mysoc,$hookmanager;
234
		$error=0;
235
236
		// Clean parameters
237
		if (empty($this->type)) $this->type = self::TYPE_STANDARD;
238
		$this->ref_client=trim($this->ref_client);
239
		$this->note=(isset($this->note) ? trim($this->note) : trim($this->note_private)); // deprecated
240
		$this->note_private=(isset($this->note_private) ? trim($this->note_private) : trim($this->note_private));
241
		$this->note_public=trim($this->note_public);
242
		if (! $this->cond_reglement_id) $this->cond_reglement_id = 0;
243
		if (! $this->mode_reglement_id) $this->mode_reglement_id = 0;
244
		$this->brouillon = 1;
245
        if (empty($this->entity)) $this->entity = $conf->entity;
246
        
247
		// Multicurrency (test on $this->multicurrency_tx because we sould take the default rate only if not using origin rate)
248
		if (!empty($this->multicurrency_code) && empty($this->multicurrency_tx)) list($this->fk_multicurrency,$this->multicurrency_tx) = MultiCurrency::getIdAndTxFromCode($this->db, $this->multicurrency_code);
249
		else $this->fk_multicurrency = MultiCurrency::getIdFromCode($this->db, $this->multicurrency_code);
250
		if (empty($this->fk_multicurrency))
251
		{
252
			$this->multicurrency_code = $conf->currency;
253
			$this->fk_multicurrency = 0;
254
			$this->multicurrency_tx = 1;
255
		}
256
257
		dol_syslog(get_class($this)."::create user=".$user->id);
258
259
		// Check parameters
260
		if (empty($this->date) || empty($user->id))
261
		{
262
			$this->error="ErrorBadParameter";
263
			dol_syslog(get_class($this)."::create Try to create an invoice with an empty parameter (user, date, ...)", LOG_ERR);
264
			return -3;
265
		}
266
		$soc = new Societe($this->db);
267
		$result=$soc->fetch($this->socid);
268
		if ($result < 0)
269
		{
270
			$this->error="Failed to fetch company: ".$soc->error;
271
			dol_syslog(get_class($this)."::create ".$this->error, LOG_ERR);
272
			return -2;
273
		}
274
275
		$now=dol_now();
276
277
		$this->db->begin();
278
279
		// Create invoice from a template invoice
280
		if ($this->fac_rec > 0)
281
		{
282
			require_once DOL_DOCUMENT_ROOT.'/compta/facture/class/facture-rec.class.php';
283
			$_facrec = new FactureRec($this->db);
284
			$result=$_facrec->fetch($this->fac_rec);
285
			$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...
286
287
			$this->socid 		     = $_facrec->socid;  // Invoice created on same thirdparty than template
288
			$this->entity            = $_facrec->entity; // Invoice created in same entity than template
289
			
290
			// 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
291
			$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...
292
			$this->note_public       = GETPOST('note_public') ? GETPOST('note_public') : $_facrec->note_public;
293
			$this->note_private      = GETPOST('note_private') ? GETPOST('note_private') : $_facrec->note_private;
294
			$this->modelpdf          = GETPOST('model') ? GETPOST('model') : $_facrec->modelpdf;
295
			$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...
296
			$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...
297
			$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...
298
299
			// Set here to have this defined for substitution into notes, should be recalculated after adding lines to get same result
300
			$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...
301
			$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...
302
				
303
			// Fields always coming from template
304
			$this->remise_absolue    = $_facrec->remise_absolue;
305
			$this->remise_percent    = $_facrec->remise_percent;
306
			$this->fk_incoterms		 = $_facrec->fk_incoterms;
307
			$this->location_incoterms= $_facrec->location_incoterms;
308
309
			// Clean parameters
310
			if (! $this->type) $this->type = self::TYPE_STANDARD;
311
			$this->ref_client=trim($this->ref_client);
312
			$this->note_public=trim($this->note_public);
313
			$this->note_private=trim($this->note_private);
314
		    $this->note_private=dol_concatdesc($this->note_private, $langs->trans("GeneratedFromRecurringInvoice", $_facrec->ref));
315
316
			//if (! $this->remise) $this->remise = 0;
317
			if (! $this->mode_reglement_id) $this->mode_reglement_id = 0;
318
			$this->brouillon = 1;
319
320
			$this->linked_objects = $_facrec->linkedObjectsIds;
321
322
			$forceduedate = $this->calculate_date_lim_reglement();
323
324
			// For recurring invoices, update date and number of last generation of recurring template invoice, before inserting new invoice
325
			if ($_facrec->frequency > 0)
326
			{
327
			    dol_syslog("This is a recurring invoice so we set date_last_gen and next date_when");
328
			    if (empty($_facrec->date_when)) $_facrec->date_when = $now;
329
                $next_date = $_facrec->getNextDate();   // Calculate next date
330
                $result = $_facrec->setValueFrom('date_last_gen', $now, '', null, 'date', '', $user, '');
331
                //$_facrec->setValueFrom('nb_gen_done', $_facrec->nb_gen_done + 1);		// Not required, +1 already included into setNextDate when second param is 1.
332
                $result = $_facrec->setNextDate($next_date,1);
333
			}
334
335
			// Define lang of customer
336
			$outputlangs = $langs;
337
			$newlang='';
338
339
			if ($conf->global->MAIN_MULTILANGS && empty($newlang) && isset($this->thirdparty->default_lang)) $newlang=$this->thirdparty->default_lang;  // for proposal, order, invoice, ...
340
			if ($conf->global->MAIN_MULTILANGS && empty($newlang) && isset($this->default_lang)) $newlang=$this->default_lang;                  // for thirdparty
341
			if (! empty($newlang))
342
			{
343
			    $outputlangs = new Translate("",$conf);
344
			    $outputlangs->setDefaultLang($newlang);
345
			}
346
347
			// Array of possible substitutions (See also file mailing-send.php that should manage same substitutions)
348
			$substitutionarray=array(
349
			    '__TOTAL_HT__' => price($this->total_ht, 0, $outputlangs, 0, 0, -1, $conf->currency_code),
350
			    '__TOTAL_TTC__' => price($this->total_ttc, 0, $outputlangs, 0, 0, -1, $conf->currency_code),
351
			    '__INVOICE_PREVIOUS_MONTH__' => dol_print_date(dol_time_plus_duree($this->date, -1, 'm'), '%m'),
352
			    '__INVOICE_MONTH__' => dol_print_date($this->date, '%m'),
353
			    '__INVOICE_NEXT_MONTH__' => dol_print_date(dol_time_plus_duree($this->date, 1, 'm'), '%m'),
354
			    '__INVOICE_PREVIOUS_MONTH_TEXT__' => dol_print_date(dol_time_plus_duree($this->date, -1, 'm'), '%B'),
355
			    '__INVOICE_MONTH_TEXT__' => dol_print_date($this->date, '%B'),
356
			    '__INVOICE_NEXT_MONTH_TEXT__' => dol_print_date(dol_time_plus_duree($this->date, 1, 'm'), '%B'),
357
			    '__INVOICE_PREVIOUS_YEAR__' => dol_print_date(dol_time_plus_duree($this->date, -1, 'y'), '%Y'),
358
			    '__INVOICE_YEAR__' => dol_print_date($this->date, '%Y'),
359
			    '__INVOICE_NEXT_YEAR__' => dol_print_date(dol_time_plus_duree($this->date, 1, 'y'), '%Y'),
360
			);
361
			
362
			$substitutionisok=true;
363
			complete_substitutions_array($substitutionarray, $outputlangs);
364
			
365
			$this->note_public=make_substitutions($this->note_public,$substitutionarray);
366
			$this->note_private=make_substitutions($this->note_private,$substitutionarray);
367
		}
368
369
		// Define due date if not already defined
370
		$datelim=(empty($forceduedate)?$this->calculate_date_lim_reglement():$forceduedate);
371
372
		// Insert into database
373
		$socid  = $this->socid;
374
375
		$sql = "INSERT INTO ".MAIN_DB_PREFIX."facture (";
376
		$sql.= " facnumber";
377
		$sql.= ", entity";
378
		$sql.= ", ref_ext";
379
		$sql.= ", type";
380
		$sql.= ", fk_soc";
381
		$sql.= ", datec";
382
		$sql.= ", remise_absolue";
383
		$sql.= ", remise_percent";
384
		$sql.= ", datef";
385
		$sql.= ", date_pointoftax";
386
		$sql.= ", note_private";
387
		$sql.= ", note_public";
388
		$sql.= ", ref_client, ref_int";
389
        $sql.= ", fk_account";
390
		$sql.= ", fk_facture_source, fk_user_author, fk_projet";
391
		$sql.= ", fk_cond_reglement, fk_mode_reglement, date_lim_reglement, model_pdf";
392
		$sql.= ", situation_cycle_ref, situation_counter, situation_final";
393
		$sql.= ", fk_incoterms, location_incoterms";
394
        $sql.= ", fk_multicurrency";
395
        $sql.= ", multicurrency_code";
396
        $sql.= ", multicurrency_tx";
397
		$sql.= ")";
398
		$sql.= " VALUES (";
399
		$sql.= "'(PROV)'";
400
		$sql.= ", ".$this->entity;
401
		$sql.= ", ".($this->ref_ext?"'".$this->db->escape($this->ref_ext)."'":"null");
402
		$sql.= ", '".$this->db->escape($this->type)."'";
403
		$sql.= ", '".$socid."'";
404
		$sql.= ", '".$this->db->idate($now)."'";
405
		$sql.= ", ".($this->remise_absolue>0?$this->remise_absolue:'NULL');
406
		$sql.= ", ".($this->remise_percent>0?$this->remise_percent:'NULL');
407
		$sql.= ", '".$this->db->idate($this->date)."'";
408
		$sql.= ", ".(strval($this->date_pointoftax)!='' ? "'".$this->db->idate($this->date_pointoftax)."'" : 'null');
409
		$sql.= ", ".($this->note_private?"'".$this->db->escape($this->note_private)."'":"null");
410
		$sql.= ", ".($this->note_public?"'".$this->db->escape($this->note_public)."'":"null");
411
		$sql.= ", ".($this->ref_client?"'".$this->db->escape($this->ref_client)."'":"null");
412
		$sql.= ", ".($this->ref_int?"'".$this->db->escape($this->ref_int)."'":"null");
413
		$sql.= ", ".($this->fk_account>0?$this->fk_account:'NULL');
414
		$sql.= ", ".($this->fk_facture_source?"'".$this->db->escape($this->fk_facture_source)."'":"null");
415
		$sql.= ", ".($user->id > 0 ? "'".$user->id."'":"null");
416
		$sql.= ", ".($this->fk_project?$this->fk_project:"null");
417
		$sql.= ", ".$this->cond_reglement_id;
418
		$sql.= ", ".$this->mode_reglement_id;
419
		$sql.= ", '".$this->db->idate($datelim)."', '".$this->db->escape($this->modelpdf)."'";
420
		$sql.= ", ".($this->situation_cycle_ref?"'".$this->db->escape($this->situation_cycle_ref)."'":"null");
421
		$sql.= ", ".($this->situation_counter?"'".$this->db->escape($this->situation_counter)."'":"null");
422
		$sql.= ", ".($this->situation_final?$this->situation_final:0);
423
		$sql.= ", ".(int) $this->fk_incoterms;
424
        $sql.= ", '".$this->db->escape($this->location_incoterms)."'";
425
		$sql.= ", ".(int) $this->fk_multicurrency;
426
		$sql.= ", '".$this->db->escape($this->multicurrency_code)."'";
427
		$sql.= ", ".(double) $this->multicurrency_tx;
428
		$sql.=")";
429
430
		dol_syslog(get_class($this)."::create", LOG_DEBUG);
431
		$resql=$this->db->query($sql);
432
		if ($resql)
433
		{
434
			$this->id = $this->db->last_insert_id(MAIN_DB_PREFIX.'facture');
435
436
			// Update ref with new one
437
			$this->ref='(PROV'.$this->id.')';
438
			$sql = 'UPDATE '.MAIN_DB_PREFIX."facture SET facnumber='".$this->ref."' WHERE rowid=".$this->id;
439
440
			dol_syslog(get_class($this)."::create", LOG_DEBUG);
441
			$resql=$this->db->query($sql);
442
			if (! $resql) $error++;
443
444
			// Add object linked
445
			if (! $error && $this->id && is_array($this->linked_objects) && ! empty($this->linked_objects))
446
			{
447
				foreach($this->linked_objects as $origin => $tmp_origin_id)
448
				{
449
				    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, ...))
450
				    {
451
				        foreach($tmp_origin_id as $origin_id)
452
				        {
453
				            $ret = $this->add_object_linked($origin, $origin_id);
454
				            if (! $ret)
455
				            {
456
				                dol_print_error($this->db);
457
				                $error++;
458
				            }
459
				        }
460
				    }
461
				    else                                // Old behaviour, if linked_object has only one link per type, so is something like array('contract'=>id1))
462
				    {
463
				        $origin_id = $tmp_origin_id;
464
    					$ret = $this->add_object_linked($origin, $origin_id);
465
    					if (! $ret)
466
    					{
467
    						dol_print_error($this->db);
468
    						$error++;
469
    					}
470
				    }
471
				}
472
			}
473
			
474
			if (! $error && $this->id && ! empty($conf->global->MAIN_PROPAGATE_CONTACTS_FROM_ORIGIN) && ! empty($this->origin) && ! empty($this->origin_id))   // Get contact from origin object
475
			{
476
				$originforcontact = $this->origin;
477
				$originidforcontact = $this->origin_id;
478
				if ($originforcontact == 'shipping')     // shipment and order share the same contacts. If creating from shipment we take data of order
479
				{
480
				    require_once DOL_DOCUMENT_ROOT . '/expedition/class/expedition.class.php';
481
				    $exp = new Expedition($this->db);
482
				    $exp->fetch($this->origin_id);
483
				    $exp->fetchObjectLinked();
484
				    if (count($exp->linkedObjectsIds['commande']) > 0) 
485
				    {
486
				        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...
487
				        {
488
				            $originforcontact = 'commande';
489
				            $originidforcontact = $value->id;
490
				            break; // We take first one
491
				        }
492
				    }
493
				}
494
				
495
				$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";
496
				$sqlcontact.= " WHERE element_id = ".$originidforcontact." AND ec.fk_c_type_contact = ctc.rowid AND ctc.element = '".$originforcontact."'";
497
	
498
				$resqlcontact = $this->db->query($sqlcontact);
499
				if ($resqlcontact)
500
				{
501
				    while($objcontact = $this->db->fetch_object($resqlcontact))
502
				    {
503
				        //print $objcontact->code.'-'.$objcontact->source.'-'.$objcontact->fk_socpeople."\n";
504
				        $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
505
				    }
506
				}
507
				else dol_print_error($resqlcontact);
508
			}
509
510
			/*
511
			 *  Insert lines of invoices into database
512
			 */
513
			if (count($this->lines) && is_object($this->lines[0]))	// If this->lines is array of InvoiceLines (preferred mode)
514
			{
515
				$fk_parent_line = 0;
516
517
				dol_syslog("There is ".count($this->lines)." lines that are invoice lines objects");
518
				foreach ($this->lines as $i => $val)
519
				{
520
					$newinvoiceline=$this->lines[$i];
521
					$newinvoiceline->fk_facture=$this->id;
522
                    $newinvoiceline->origin = $this->element;           // TODO This seems not used. Here we but origin 'facture' but after
523
                    $newinvoiceline->origin_id = $this->lines[$i]->id;  // we put an id of object !
524
					if ($result >= 0 && ($newinvoiceline->info_bits & 0x01) == 0)	// We keep only lines with first bit = 0
525
					{
526
						// Reset fk_parent_line for no child products and special product
527
						if (($newinvoiceline->product_type != 9 && empty($newinvoiceline->fk_parent_line)) || $newinvoiceline->product_type == 9) {
528
							$fk_parent_line = 0;
529
						}
530
531
						$newinvoiceline->fk_parent_line=$fk_parent_line;
532
						$result=$newinvoiceline->insert();
533
534
						// Defined the new fk_parent_line
535
						if ($result > 0 && $newinvoiceline->product_type == 9) {
536
							$fk_parent_line = $result;
537
						}
538
					}
539
					if ($result < 0)
540
					{
541
						$this->error=$newinvoiceline->error;
542
						$error++;
543
						break;
544
					}
545
				}
546
			}
547
			else	// If this->lines is an array of invoice line arrays
548
			{
549
				$fk_parent_line = 0;
550
551
				dol_syslog("There is ".count($this->lines)." lines that are array lines");
552
553
				foreach ($this->lines as $i => $val)
554
				{
555
                	$line = $this->lines[$i];
556
                	
557
                	// Test and convert into object this->lines[$i]. When coming from REST API, we may still have an array
558
				    //if (! is_object($line)) $line=json_decode(json_encode($line), FALSE);  // convert recursively array into object.
559
                	if (! is_object($line)) $line = (object) $line;
560
				    
561
				    if (($line->info_bits & 0x01) == 0)	// We keep only lines with first bit = 0
562
					{
563
						// Reset fk_parent_line for no child products and special product
564
						if (($line->product_type != 9 && empty($line->fk_parent_line)) || $line->product_type == 9) {
565
							$fk_parent_line = 0;
566
						}
567
568
						$result = $this->addline(
569
							$line->desc,
570
							$line->subprice,
571
							$line->qty,
572
							$line->tva_tx,
573
							$line->localtax1_tx,
574
							$line->localtax2_tx,
575
							$line->fk_product,
576
							$line->remise_percent,
577
							$line->date_start,
578
							$line->date_end,
579
							$line->fk_code_ventilation,
580
							$line->info_bits,
581
							$line->fk_remise_except,
582
							'HT',
583
							0,
584
							$line->product_type,
585
							$line->rang,
586
							$line->special_code,
587
                            $this->element,
588
                            $line->id,
589
							$fk_parent_line,
590
							$line->fk_fournprice,
591
							$line->pa_ht,
592
							$line->label,
593
							$line->array_options,
594
							$line->situation_percent,
595
							$line->fk_prev_id,
596
							$line->fk_unit
597
						);
598
						if ($result < 0)
599
						{
600
							$this->error=$this->db->lasterror();
601
							dol_print_error($this->db);
602
							$this->db->rollback();
603
							return -1;
604
						}
605
606
						// Defined the new fk_parent_line
607
						if ($result > 0 && $line->product_type == 9) {
608
							$fk_parent_line = $result;
609
						}
610
					}
611
				}
612
			}
613
614
			/*
615
			 * Insert lines of predefined invoices
616
			 */
617
			if (! $error && $this->fac_rec > 0)
618
			{
619
				foreach ($_facrec->lines as $i => $val)
620
				{
621
					if ($_facrec->lines[$i]->fk_product)
622
					{
623
						$prod = new Product($this->db);
624
						$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...
625
					}
626
					$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...
627
					$tva_npr = get_default_npr($mysoc,$soc,$prod->id);
628
					if (empty($tva_tx)) $tva_npr=0;
629
					$localtax1_tx=get_localtax($tva_tx,1,$soc,$mysoc,$tva_npr);
630
					$localtax2_tx=get_localtax($tva_tx,2,$soc,$mysoc,$tva_npr);
631
632
					$result_insert = $this->addline(
633
						$_facrec->lines[$i]->desc,
634
						$_facrec->lines[$i]->subprice,
635
						$_facrec->lines[$i]->qty,
636
						$tva_tx,
637
						$localtax1_tx,
638
						$localtax2_tx,
639
						$_facrec->lines[$i]->fk_product,
640
						$_facrec->lines[$i]->remise_percent,
641
						'','',0,$tva_npr,'','HT',0,
642
						$_facrec->lines[$i]->product_type,
643
						$_facrec->lines[$i]->rang,
644
						$_facrec->lines[$i]->special_code,
645
						'',
646
						0,
647
						0,
648
						null,
649
						0,
650
						$_facrec->lines[$i]->label,
651
						null,
652
						$_facrec->lines[$i]->situation_percent,
653
						'',
654
						$_facrec->lines[$i]->fk_unit
655
					);
656
657
					if ( $result_insert < 0)
658
					{
659
						$error++;
660
						$this->error=$this->db->error();
661
						break;
662
					}
663
				}
664
			}
665
666
			if (! $error)
667
			{
668
669
				$result=$this->update_price(1);
670
				if ($result > 0)
671
				{
672
					$action='create';
673
674
					// Actions on extra fields (by external module or standard code)
675
					// TODO le hook fait double emploi avec le trigger !!
676
					/*
677
					$hookmanager->initHooks(array('invoicedao'));
678
					$parameters=array('invoiceid'=>$this->id);
679
					$reshook=$hookmanager->executeHooks('insertExtraFields',$parameters,$this,$action); // Note that $action and $object may have been modified by some hooks
680
					if (empty($reshook))
681
					{
682
						if (empty($conf->global->MAIN_EXTRAFIELDS_DISABLED)) // For avoid conflicts if trigger used
683
						{*/
684
					if (! $error)
685
					{
686
					    $result=$this->insertExtraFields();
687
					    if ($result < 0) $error++;
688
					}
689
						/*}
690
					}
691
					else if ($reshook < 0) $error++;*/
692
693
                    // Call trigger
694
                    $result=$this->call_trigger('BILL_CREATE',$user);
695
                    if ($result < 0) $error++;
696
                    // End call triggers
697
698
					if (! $error)
699
					{
700
						$this->db->commit();
701
						return $this->id;
702
					}
703
					else
704
					{
705
						$this->db->rollback();
706
						return -4;
707
					}
708
				}
709
				else
710
				{
711
					$this->error=$langs->trans('FailedToUpdatePrice');
712
					$this->db->rollback();
713
					return -3;
714
				}
715
			}
716
			else
717
			{
718
				dol_syslog(get_class($this)."::create error ".$this->error, LOG_ERR);
719
				$this->db->rollback();
720
				return -2;
721
			}
722
		}
723
		else
724
		{
725
			$this->error=$this->db->error();
726
			$this->db->rollback();
727
			return -1;
728
		}
729
	}
730
731
732
	/**
733
	 *	Create a new invoice in database from current invoice
734
	 *
735
	 *	@param      User	$user    		Object user that ask creation
736
	 *	@param		int		$invertdetail	Reverse sign of amounts for lines
737
	 *	@return		int						<0 if KO, >0 if OK
738
	 */
739
	function createFromCurrent($user,$invertdetail=0)
740
	{
741
		global $conf;
742
743
		// Charge facture source
744
		$facture=new Facture($this->db);
745
746
		$facture->fk_facture_source = $this->fk_facture_source;
747
		$facture->type 			    = $this->type;
748
		$facture->socid 		    = $this->socid;
749
		$facture->date              = $this->date;
750
		$facture->date_pointoftax   = $this->date_pointoftax;
751
		$facture->note_public       = $this->note_public;
752
		$facture->note_private      = $this->note_private;
753
		$facture->ref_client        = $this->ref_client;
754
		$facture->modelpdf          = $this->modelpdf;
755
		$facture->fk_project        = $this->fk_project;
756
		$facture->cond_reglement_id = $this->cond_reglement_id;
757
		$facture->mode_reglement_id = $this->mode_reglement_id;
758
		$facture->remise_absolue    = $this->remise_absolue;
759
		$facture->remise_percent    = $this->remise_percent;
760
761
		$facture->origin                        = $this->origin;
762
		$facture->origin_id                     = $this->origin_id;
763
764
		$facture->lines		    	= $this->lines;	// Tableau des lignes de factures
765
		$facture->products		    = $this->lines;	// Tant que products encore utilise
766
		$facture->situation_counter = $this->situation_counter;
767
		$facture->situation_cycle_ref=$this->situation_cycle_ref;
768
		$facture->situation_final  = $this->situation_final;
769
770
		// Loop on each line of new invoice
771
		foreach($facture->lines as $i => $line)
772
		{
773
			$facture->lines[$i]->fk_prev_id = $this->lines[$i]->rowid;
774
			if ($invertdetail)
775
			{
776
				$facture->lines[$i]->subprice  = -$facture->lines[$i]->subprice;
777
				$facture->lines[$i]->total_ht  = -$facture->lines[$i]->total_ht;
778
				$facture->lines[$i]->total_tva = -$facture->lines[$i]->total_tva;
779
				$facture->lines[$i]->total_localtax1 = -$facture->lines[$i]->total_localtax1;
780
				$facture->lines[$i]->total_localtax2 = -$facture->lines[$i]->total_localtax2;
781
				$facture->lines[$i]->total_ttc = -$facture->lines[$i]->total_ttc;
782
			}
783
		}
784
785
		dol_syslog(get_class($this)."::createFromCurrent invertdetail=".$invertdetail." socid=".$this->socid." nboflines=".count($facture->lines));
786
787
		$facid = $facture->create($user);
788
		if ($facid <= 0)
789
		{
790
			$this->error=$facture->error;
791
			$this->errors=$facture->errors;
792
		}
793
		elseif ($this->type == self::TYPE_SITUATION && !empty($conf->global->INVOICE_USE_SITUATION))
794
		{
795
			$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...
796
797
			foreach ($this->linkedObjectsIds as $typeObject => $Tfk_object)
798
			{
799
				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...
800
				{
801
					$facture->add_object_linked($typeObject, $fk_object);
802
				}
803
			}
804
805
			$facture->add_object_linked('facture', $this->fk_facture_source);
806
		}
807
808
		return $facid;
809
	}
810
811
812
	/**
813
	 *		Load an object from its id and create a new one in database
814
	 *
815
	 *		@param		int				$socid			Id of thirdparty
816
	 * 	 	@return		int								New id of clone
817
	 */
818
	function createFromClone($socid=0)
819
	{
820
		global $user,$hookmanager;
821
822
		$error=0;
823
824
		$this->context['createfromclone'] = 'createfromclone';
825
826
		$this->db->begin();
827
828
		// get extrafields so they will be clone
829
		foreach($this->lines as $line)
830
			$line->fetch_optionals($line->rowid);
831
832
		// Load source object
833
		$objFrom = clone $this;
834
835
836
837
		// Change socid if needed
838
		if (! empty($socid) && $socid != $this->socid)
839
		{
840
			$objsoc = new Societe($this->db);
841
842
			if ($objsoc->fetch($socid)>0)
843
			{
844
				$this->socid 				= $objsoc->id;
845
				$this->cond_reglement_id	= (! empty($objsoc->cond_reglement_id) ? $objsoc->cond_reglement_id : 0);
846
				$this->mode_reglement_id	= (! empty($objsoc->mode_reglement_id) ? $objsoc->mode_reglement_id : 0);
847
				$this->fk_project			= '';
1 ignored issue
show
Documentation Bug introduced by
The property $fk_project was declared of type integer, but '' is of type string. Maybe add a type cast?

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

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

$answer = 42;

$correct = false;

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

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

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

$answer = 42;

$correct = false;

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

This check marks calls to isset(...) or empty(...) that are found before the variable itself is defined. These will always have the same result.

This is likely the result of code being shifted around. Consider removing these calls.

Loading history...
1681
1682
		dol_syslog(get_class($this)."::delete rowid=".$rowid, LOG_DEBUG);
1683
1684
		// TODO Test if there is at least one payment. If yes, refuse to delete.
1685
1686
		$error=0;
1687
		$this->db->begin();
1688
1689
		if (! $error && ! $notrigger)
1690
		{
1691
            // Call trigger
1692
            $result=$this->call_trigger('BILL_DELETE',$user);
1693
            if ($result < 0) $error++;
1694
            // End call triggers
1695
		}
1696
1697
		// Removed extrafields
1698
		if (! $error) {
1699
			$result=$this->deleteExtraFields();
1700
			if ($result < 0)
1701
			{
1702
				$error++;
1703
				dol_syslog(get_class($this)."::delete error deleteExtraFields ".$this->error, LOG_ERR);
1704
			}
1705
		}
1706
1707
		if (! $error)
1708
		{
1709
			// Delete linked object
1710
			$res = $this->deleteObjectLinked();
1711
			if ($res < 0) $error++;
1712
		}
1713
1714
		if (! $error)
1715
		{
1716
			// If invoice was converted into a discount not yet consumed, we remove discount
1717
			$sql = 'DELETE FROM '.MAIN_DB_PREFIX.'societe_remise_except';
1718
			$sql.= ' WHERE fk_facture_source = '.$rowid;
1719
			$sql.= ' AND fk_facture_line IS NULL';
1720
			$resql=$this->db->query($sql);
1721
1722
			// If invoice has consumned discounts
1723
			$this->fetch_lines();
1724
			$list_rowid_det=array();
1725
			foreach($this->lines as $key => $invoiceline)
1726
			{
1727
				$list_rowid_det[]=$invoiceline->rowid;
1728
			}
1729
1730
			// Consumned discounts are freed
1731
			if (count($list_rowid_det))
1732
			{
1733
				$sql = 'UPDATE '.MAIN_DB_PREFIX.'societe_remise_except';
1734
				$sql.= ' SET fk_facture = NULL, fk_facture_line = NULL';
1735
				$sql.= ' WHERE fk_facture_line IN ('.join(',',$list_rowid_det).')';
1736
1737
				dol_syslog(get_class($this)."::delete", LOG_DEBUG);
1738
				if (! $this->db->query($sql))
1739
				{
1740
					$this->error=$this->db->error()." sql=".$sql;
1741
					$this->db->rollback();
1742
					return -5;
1743
				}
1744
			}
1745
1746
			// If we decrement stock on invoice validation, we increment
1747
			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...
1748
			{
1749
				require_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
1750
				$langs->load("agenda");
1751
1752
				$num=count($this->lines);
1753
				for ($i = 0; $i < $num; $i++)
1754
				{
1755
					if ($this->lines[$i]->fk_product > 0)
1756
					{
1757
						$mouvP = new MouvementStock($this->db);
1758
						$mouvP->origin = &$this;
1759
						// We decrease stock for product
1760
						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));
1761
						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
1762
					}
1763
				}
1764
			}
1765
1766
1767
			// Delete invoice line
1768
			$sql = 'DELETE FROM '.MAIN_DB_PREFIX.'facturedet WHERE fk_facture = '.$rowid;
1769
1770
			dol_syslog(get_class($this)."::delete", LOG_DEBUG);
1771
1772
			if ($this->db->query($sql) && $this->delete_linked_contact())
1773
			{
1774
				$sql = 'DELETE FROM '.MAIN_DB_PREFIX.'facture WHERE rowid = '.$rowid;
1775
1776
				dol_syslog(get_class($this)."::delete", LOG_DEBUG);
1777
1778
				$resql=$this->db->query($sql);
1779
				if ($resql)
1780
				{
1781
					// On efface le repertoire de pdf provisoire
1782
					$ref = dol_sanitizeFileName($this->ref);
1783
					if ($conf->facture->dir_output && !empty($this->ref))
1784
					{
1785
						$dir = $conf->facture->dir_output . "/" . $ref;
1786
						$file = $conf->facture->dir_output . "/" . $ref . "/" . $ref . ".pdf";
1787
						if (file_exists($file))	// We must delete all files before deleting directory
1788
						{
1789
							$ret=dol_delete_preview($this);
1790
1791
							if (! dol_delete_file($file,0,0,0,$this)) // For triggers
1792
							{
1793
								$this->error=$langs->trans("ErrorCanNotDeleteFile",$file);
1794
								$this->db->rollback();
1795
								return 0;
1796
							}
1797
						}
1798
						if (file_exists($dir))
1799
						{
1800
							if (! dol_delete_dir_recursive($dir)) // For remove dir and meta
1801
							{
1802
								$this->error=$langs->trans("ErrorCanNotDeleteDir",$dir);
1803
								$this->db->rollback();
1804
								return 0;
1805
							}
1806
						}
1807
					}
1808
1809
					$this->db->commit();
1810
					return 1;
1811
				}
1812
				else
1813
				{
1814
					$this->error=$this->db->lasterror()." sql=".$sql;
1815
					$this->db->rollback();
1816
					return -6;
1817
				}
1818
			}
1819
			else
1820
			{
1821
				$this->error=$this->db->lasterror()." sql=".$sql;
1822
				$this->db->rollback();
1823
				return -4;
1824
			}
1825
		}
1826
		else
1827
		{
1828
			$this->db->rollback();
1829
			return -2;
1830
		}
1831
	}
1832
1833
	/**
1834
	 *  Tag la facture comme paye completement (si close_code non renseigne) => this->fk_statut=2, this->paye=1
1835
	 *  ou partiellement (si close_code renseigne) + appel trigger BILL_PAYED => this->fk_statut=2, this->paye stay 0
1836
	 *
1837
	 *  @param	User	$user      	Objet utilisateur qui modifie
1838
	 *	@param  string	$close_code	Code renseigne si on classe a payee completement alors que paiement incomplet (cas escompte par exemple)
1839
	 *	@param  string	$close_note	Commentaire renseigne si on classe a payee alors que paiement incomplet (cas escompte par exemple)
1840
	 *  @return int         		<0 if KO, >0 if OK
1841
	 */
1842
	function set_paid($user, $close_code='', $close_note='')
1843
	{
1844
		$error=0;
1845
1846
		if ($this->paye != 1)
1847
		{
1848
			$this->db->begin();
1849
1850
			dol_syslog(get_class($this)."::set_paid rowid=".$this->id, LOG_DEBUG);
1851
			$sql = 'UPDATE '.MAIN_DB_PREFIX.'facture SET';
1852
			$sql.= ' fk_statut='.self::STATUS_CLOSED;
1853
			if (! $close_code) $sql.= ', paye=1';
1854
			if ($close_code) $sql.= ", close_code='".$this->db->escape($close_code)."'";
1855
			if ($close_note) $sql.= ", close_note='".$this->db->escape($close_note)."'";
1856
			$sql.= ' WHERE rowid = '.$this->id;
1857
1858
			dol_syslog(get_class($this)."::set_paid", LOG_DEBUG);
1859
			$resql = $this->db->query($sql);
1860
			if ($resql)
1861
			{
1862
	            // Call trigger
1863
	            $result=$this->call_trigger('BILL_PAYED',$user);
1864
	            if ($result < 0) $error++;
1865
	            // End call triggers
1866
			}
1867
			else
1868
			{
1869
				$error++;
1870
				$this->error=$this->db->lasterror();
1871
			}
1872
1873
			if (! $error)
1874
			{
1875
				$this->db->commit();
1876
				return 1;
1877
			}
1878
			else
1879
			{
1880
				$this->db->rollback();
1881
				return -1;
1882
			}
1883
		}
1884
		else
1885
		{
1886
			return 0;
1887
		}
1888
	}
1889
1890
1891
	/**
1892
	 *  Tag la facture comme non payee completement + appel trigger BILL_UNPAYED
1893
	 *	Fonction utilisee quand un paiement prelevement est refuse,
1894
	 * 	ou quand une facture annulee et reouverte.
1895
	 *
1896
	 *  @param	User	$user       Object user that change status
1897
	 *  @return int         		<0 if KO, >0 if OK
1898
	 */
1899
	function set_unpaid($user)
1900
	{
1901
		$error=0;
1902
1903
		$this->db->begin();
1904
1905
		$sql = 'UPDATE '.MAIN_DB_PREFIX.'facture';
1906
		$sql.= ' SET paye=0, fk_statut='.self::STATUS_VALIDATED.', close_code=null, close_note=null';
1907
		$sql.= ' WHERE rowid = '.$this->id;
1908
1909
		dol_syslog(get_class($this)."::set_unpaid", LOG_DEBUG);
1910
		$resql = $this->db->query($sql);
1911
		if ($resql)
1912
		{
1913
            // Call trigger
1914
            $result=$this->call_trigger('BILL_UNPAYED',$user);
1915
            if ($result < 0) $error++;
1916
            // End call triggers
1917
		}
1918
		else
1919
		{
1920
			$error++;
1921
			$this->error=$this->db->error();
1922
			dol_print_error($this->db);
1923
		}
1924
1925
		if (! $error)
1926
		{
1927
			$this->db->commit();
1928
			return 1;
1929
		}
1930
		else
1931
		{
1932
			$this->db->rollback();
1933
			return -1;
1934
		}
1935
	}
1936
1937
1938
	/**
1939
	 *	Tag invoice as canceled, with no payment on it (example for replacement invoice or payment never received) + call trigger BILL_CANCEL
1940
	 *	Warning, if option to decrease stock on invoice was set, this function does not change stock (it might be a cancel because
1941
	 *  of no payment even if merchandises were sent).
1942
	 *
1943
	 *	@param	User	$user        	Object user making change
1944
	 *	@param	string	$close_code		Code de fermeture
1945
	 *	@param	string	$close_note		Comment
1946
	 *	@return int         			<0 if KO, >0 if OK
1947
	 */
1948
	function set_canceled($user,$close_code='',$close_note='')
1949
	{
1950
1951
		dol_syslog(get_class($this)."::set_canceled rowid=".$this->id, LOG_DEBUG);
1952
1953
		$this->db->begin();
1954
1955
		$sql = 'UPDATE '.MAIN_DB_PREFIX.'facture SET';
1956
		$sql.= ' fk_statut='.self::STATUS_ABANDONED;
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
		$resql = $this->db->query($sql);
1962
		if ($resql)
1963
		{
1964
			// On desaffecte de la facture les remises liees
1965
			// car elles n'ont pas ete utilisees vu que la facture est abandonnee.
1966
			$sql = 'UPDATE '.MAIN_DB_PREFIX.'societe_remise_except';
1967
			$sql.= ' SET fk_facture = NULL';
1968
			$sql.= ' WHERE fk_facture = '.$this->id;
1969
1970
			$resql=$this->db->query($sql);
1971
			if ($resql)
1972
			{
1973
	            // Call trigger
1974
	            $result=$this->call_trigger('BILL_CANCEL',$user);
1975
	            if ($result < 0)
1976
	            {
1977
					$this->db->rollback();
1978
					return -1;
1979
				}
1980
	            // End call triggers
1981
1982
				$this->db->commit();
1983
				return 1;
1984
			}
1985
			else
1986
			{
1987
				$this->error=$this->db->error()." sql=".$sql;
1988
				$this->db->rollback();
1989
				return -1;
1990
			}
1991
		}
1992
		else
1993
		{
1994
			$this->error=$this->db->error()." sql=".$sql;
1995
			$this->db->rollback();
1996
			return -2;
1997
		}
1998
	}
1999
2000
	/**
2001
	 * Tag invoice as validated + call trigger BILL_VALIDATE
2002
	 * Object must have lines loaded with fetch_lines
2003
	 *
2004
	 * @param	User	$user           Object user that validate
2005
	 * @param   string	$force_number	Reference to force on invoice
2006
	 * @param	int		$idwarehouse	Id of warehouse to use for stock decrease if option to decreasenon stock is on (0=no decrease)
2007
	 * @param	int		$notrigger		1=Does not execute triggers, 0= execute triggers
2008
     * @return	int						<0 if KO, >0 if OK
2009
	 */
2010
	function validate($user, $force_number='', $idwarehouse=0, $notrigger=0)
2011
	{
2012
		global $conf,$langs;
2013
		require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
2014
2015
		$now=dol_now();
2016
2017
		$error=0;
2018
		dol_syslog(get_class($this).'::validate user='.$user->id.', force_number='.$force_number.', idwarehouse='.$idwarehouse);
2019
2020
		// Check parameters
2021
		if (! $this->brouillon)
2022
		{
2023
			dol_syslog(get_class($this)."::validate no draft status", LOG_WARNING);
2024
			return 0;
2025
		}
2026
2027
		if ((empty($conf->global->MAIN_USE_ADVANCED_PERMS) && empty($user->rights->facture->creer))
2028
       	|| (! empty($conf->global->MAIN_USE_ADVANCED_PERMS) && empty($user->rights->facture->invoice_advance->validate)))
2029
		{
2030
			$this->error='Permission denied';
2031
			dol_syslog(get_class($this)."::validate ".$this->error.' MAIN_USE_ADVANCED_PERMS='.$conf->global->MAIN_USE_ADVANCED_PERMS, LOG_ERR);
2032
			return -1;
2033
		}
2034
2035
		$this->db->begin();
2036
2037
		$this->fetch_thirdparty();
2038
		$this->fetch_lines();
2039
2040
		// Check parameters
2041
		if ($this->type == self::TYPE_REPLACEMENT)		// si facture de remplacement
2042
		{
2043
			// Controle que facture source connue
2044
			if ($this->fk_facture_source <= 0)
2045
			{
2046
				$this->error=$langs->trans("ErrorFieldRequired",$langs->trans("InvoiceReplacement"));
2047
				$this->db->rollback();
2048
				return -10;
2049
			}
2050
2051
			// Charge la facture source a remplacer
2052
			$facreplaced=new Facture($this->db);
2053
			$result=$facreplaced->fetch($this->fk_facture_source);
2054
			if ($result <= 0)
2055
			{
2056
				$this->error=$langs->trans("ErrorBadInvoice");
2057
				$this->db->rollback();
2058
				return -11;
2059
			}
2060
2061
			// Controle que facture source non deja remplacee par une autre
2062
			$idreplacement=$facreplaced->getIdReplacingInvoice('validated');
2063
			if ($idreplacement && $idreplacement != $this->id)
2064
			{
2065
				$facreplacement=new Facture($this->db);
2066
				$facreplacement->fetch($idreplacement);
2067
				$this->error=$langs->trans("ErrorInvoiceAlreadyReplaced",$facreplaced->ref,$facreplacement->ref);
2068
				$this->db->rollback();
2069
				return -12;
2070
			}
2071
2072
			$result=$facreplaced->set_canceled($user,'replaced','');
2073
			if ($result < 0)
2074
			{
2075
				$this->error=$facreplaced->error;
2076
				$this->db->rollback();
2077
				return -13;
2078
			}
2079
		}
2080
2081
		// Define new ref
2082
		if ($force_number)
2083
		{
2084
			$num = $force_number;
2085
		}
2086
		else if (preg_match('/^[\(]?PROV/i', $this->ref) || empty($this->ref)) // empty should not happened, but when it occurs, the test save life
2087
		{
2088
			if (! empty($conf->global->FAC_FORCE_DATE_VALIDATION))	// If option enabled, we force invoice date
2089
			{
2090
				$this->date=dol_now();
2091
				$this->date_lim_reglement=$this->calculate_date_lim_reglement();
2092
			}
2093
			$num = $this->getNextNumRef($this->thirdparty);
2094
		}
2095
		else
2096
		{
2097
			$num = $this->ref;
2098
		}
2099
		$this->newref = $num;
2100
2101
		if ($num)
2102
		{
2103
			$this->update_price(1);
2104
2105
			// Validate
2106
			$sql = 'UPDATE '.MAIN_DB_PREFIX.'facture';
2107
			$sql.= " SET facnumber='".$num."', fk_statut = ".self::STATUS_VALIDATED.", fk_user_valid = ".$user->id.", date_valid = '".$this->db->idate($now)."'";
2108
			if (! empty($conf->global->FAC_FORCE_DATE_VALIDATION))	// If option enabled, we force invoice date
2109
			{
2110
				$sql.= ", datef='".$this->db->idate($this->date)."'";
2111
				$sql.= ", date_lim_reglement='".$this->db->idate($this->date_lim_reglement)."'";
2112
			}
2113
			$sql.= ' WHERE rowid = '.$this->id;
2114
2115
			dol_syslog(get_class($this)."::validate", LOG_DEBUG);
2116
			$resql=$this->db->query($sql);
2117
			if (! $resql)
2118
			{
2119
				dol_print_error($this->db);
2120
				$error++;
2121
			}
2122
2123
			// On verifie si la facture etait une provisoire
2124
			if (! $error && (preg_match('/^[\(]?PROV/i', $this->ref)))
2125
			{
2126
				// La verif qu'une remise n'est pas utilisee 2 fois est faite au moment de l'insertion de ligne
2127
			}
2128
2129
			if (! $error)
2130
			{
2131
				// Define third party as a customer
2132
				$result=$this->thirdparty->set_as_client();
2133
2134
				// Si active on decremente le produit principal et ses composants a la validation de facture
2135
				if ($this->type != self::TYPE_DEPOSIT && $result >= 0 && ! empty($conf->stock->enabled) && ! empty($conf->global->STOCK_CALCULATE_ON_BILL) && $idwarehouse > 0)
2136
				{
2137
					require_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
2138
					$langs->load("agenda");
2139
2140
					// Loop on each line
2141
					$cpt=count($this->lines);
2142
					for ($i = 0; $i < $cpt; $i++)
2143
					{
2144
						if ($this->lines[$i]->fk_product > 0)
2145
						{
2146
							$mouvP = new MouvementStock($this->db);
2147
							$mouvP->origin = &$this;
2148
							// We decrease stock for product
2149
							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));
2150
							else $result=$mouvP->livraison($user, $this->lines[$i]->fk_product, $idwarehouse, $this->lines[$i]->qty, $this->lines[$i]->subprice, $langs->trans("InvoiceValidatedInDolibarr",$num));
2151
							if ($result < 0) {
2152
								$error++;
2153
							}
2154
						}
2155
					}
2156
				}
2157
			}
2158
2159
			// Trigger calls
2160
			if (! $error && ! $notrigger)
2161
			{
2162
	            // Call trigger
2163
	            $result=$this->call_trigger('BILL_VALIDATE',$user);
2164
	            if ($result < 0) $error++;
2165
	            // End call triggers
2166
			}
2167
2168
			if (! $error)
2169
			{
2170
				$this->oldref = $this->ref;
2171
2172
				// Rename directory if dir was a temporary ref
2173
				if (preg_match('/^[\(]?PROV/i', $this->ref))
2174
				{
2175
					// Rename of object directory ($this->ref = old ref, $num = new ref)
2176
					// to  not lose the linked files
2177
					$oldref = dol_sanitizeFileName($this->ref);
2178
					$newref = dol_sanitizeFileName($num);
2179
					$dirsource = $conf->facture->dir_output.'/'.$oldref;
2180
					$dirdest = $conf->facture->dir_output.'/'.$newref;
2181
					if (file_exists($dirsource))
2182
					{
2183
						dol_syslog(get_class($this)."::validate rename dir ".$dirsource." into ".$dirdest);
2184
2185
						if (@rename($dirsource, $dirdest))
2186
						{
2187
							dol_syslog("Rename ok");
2188
	                        // Rename docs starting with $oldref with $newref
2189
	                        $listoffiles=dol_dir_list($conf->facture->dir_output.'/'.$newref, 'files', 1, '^'.preg_quote($oldref,'/'));
2190
	                        foreach($listoffiles as $fileentry)
2191
	                        {
2192
	                        	$dirsource=$fileentry['name'];
2193
	                        	$dirdest=preg_replace('/^'.preg_quote($oldref,'/').'/',$newref, $dirsource);
2194
	                        	$dirsource=$fileentry['path'].'/'.$dirsource;
2195
	                        	$dirdest=$fileentry['path'].'/'.$dirdest;
2196
	                        	@rename($dirsource, $dirdest);
2197
	                        }
2198
						}
2199
					}
2200
				}
2201
			}
2202
2203
			if (! $error && !$this->is_last_in_cycle())
2204
			{
2205
				if (! $this->updatePriceNextInvoice($langs))
2206
				{
2207
					$error++;
2208
				}
2209
			}
2210
2211
			// Set new ref and define current statut
2212
			if (! $error)
2213
			{
2214
				$this->ref = $num;
2215
				$this->facnumber=$num;
2216
				$this->statut= self::STATUS_VALIDATED;
2217
				$this->brouillon=0;
2218
				$this->date_validation=$now;
2219
				$i = 0;
2220
2221
                if (!empty($conf->global->INVOICE_USE_SITUATION))
2222
                {
2223
    				$final = True;
2224
    				$nboflines = count($this->lines);
2225
    				while (($i < $nboflines) && $final) {
2226
    					$final = ($this->lines[$i]->situation_percent == 100);
2227
    					$i++;
2228
    				}
2229
    				if ($final) {
2230
    					$this->setFinal($user);
2231
    				}
2232
                }
2233
			}
2234
		}
2235
		else
2236
		{
2237
			$error++;
2238
		}
2239
2240
		if (! $error)
2241
		{
2242
			$this->db->commit();
2243
			return 1;
2244
		}
2245
		else
2246
		{
2247
			$this->db->rollback();
2248
			return -1;
2249
		}
2250
	}
2251
2252
	/**
2253
	 * Update price of next invoice
2254
	 *
2255
	 * @param	Translate	$langs	Translate object
2256
	 * @return bool		false if KO, true if OK
2257
	 */
2258
	function updatePriceNextInvoice(&$langs)
2259
	{
2260
		foreach ($this->tab_next_situation_invoice as $next_invoice)
2261
		{
2262
			$is_last = $next_invoice->is_last_in_cycle();
2263
2264
			if ($next_invoice->brouillon && $is_last != 1)
2265
			{
2266
				$this->error = $langs->trans('updatePriceNextInvoiceErrorUpdateline', $next_invoice->ref);
2267
				return false;
2268
			}
2269
2270
			$next_invoice->brouillon = 1;
2271
			foreach ($next_invoice->lines as $line)
2272
			{
2273
				$result = $next_invoice->updateline($line->id, $line->desc, $line->subprice, $line->qty, $line->remise_percent,
2274
														$line->date_start, $line->date_end, $line->tva_tx, $line->localtax1_tx, $line->localtax2_tx, 'HT', $line->info_bits, $line->product_type,
2275
														$line->fk_parent_line, 0, $line->fk_fournprice, $line->pa_ht, $line->label, $line->special_code, $line->array_options, $line->situation_percent,
2276
														$line->fk_unit);
2277
2278
				if ($result < 0)
2279
				{
2280
					$this->error = $langs->trans('updatePriceNextInvoiceErrorUpdateline', $next_invoice->ref);
2281
					return false;
2282
				}
2283
			}
2284
2285
			break; // Only the next invoice and not each next invoice
2286
		}
2287
2288
		return true;
2289
	}
2290
2291
	/**
2292
	 *	Set draft status
2293
	 *
2294
	 *	@param	User	$user			Object user that modify
2295
	 *	@param	int		$idwarehouse	Id warehouse to use for stock change.
2296
	 *	@return	int						<0 if KO, >0 if OK
2297
	 */
2298
	function set_draft($user,$idwarehouse=-1)
2299
	{
2300
		global $conf,$langs;
2301
2302
		$error=0;
2303
2304
		if ($this->statut == self::STATUS_DRAFT)
2305
		{
2306
			dol_syslog(get_class($this)."::set_draft already draft status", LOG_WARNING);
2307
			return 0;
2308
		}
2309
2310
		$this->db->begin();
2311
2312
		$sql = "UPDATE ".MAIN_DB_PREFIX."facture";
2313
		$sql.= " SET fk_statut = ".self::STATUS_DRAFT;
2314
		$sql.= " WHERE rowid = ".$this->id;
2315
2316
		dol_syslog(get_class($this)."::set_draft", LOG_DEBUG);
2317
		$result=$this->db->query($sql);
2318
		if ($result)
2319
		{
2320
			// Si on decremente le produit principal et ses composants a la validation de facture, on réincrement
2321
			if ($this->type != self::TYPE_DEPOSIT && $result >= 0 && ! empty($conf->stock->enabled) && ! empty($conf->global->STOCK_CALCULATE_ON_BILL))
2322
			{
2323
				require_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
2324
				$langs->load("agenda");
2325
2326
				$num=count($this->lines);
2327
				for ($i = 0; $i < $num; $i++)
2328
				{
2329
					if ($this->lines[$i]->fk_product > 0)
2330
					{
2331
						$mouvP = new MouvementStock($this->db);
2332
						$mouvP->origin = &$this;
2333
						// We decrease stock for product
2334
						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));
2335
						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
2336
					}
2337
				}
2338
			}
2339
2340
			if ($error == 0)
2341
			{
2342
				$old_statut=$this->statut;
2343
				$this->brouillon = 1;
2344
				$this->statut = self::STATUS_DRAFT;
2345
	            // Call trigger
2346
	            $result=$this->call_trigger('BILL_UNVALIDATE',$user);
2347
	            if ($result < 0)
2348
				{
2349
					$error++;
2350
					$this->statut=$old_statut;
2351
					$this->brouillon=0;
2352
				}
2353
	            // End call triggers
2354
			} else {
2355
				$this->db->rollback();
2356
				return -1;
2357
			}
2358
2359
			if ($error == 0)
2360
			{
2361
				$this->db->commit();
2362
				return 1;
2363
			}
2364
			else
2365
			{
2366
				$this->db->rollback();
2367
				return -1;
2368
			}
2369
		}
2370
		else
2371
		{
2372
			$this->error=$this->db->error();
2373
			$this->db->rollback();
2374
			return -1;
2375
		}
2376
	}
2377
2378
2379
	/**
2380
	 * 		Add an invoice line into database (linked to product/service or not).
2381
	 * 		Les parametres sont deja cense etre juste et avec valeurs finales a l'appel
2382
	 *		de cette methode. Aussi, pour le taux tva, il doit deja avoir ete defini
2383
	 *		par l'appelant par la methode get_default_tva(societe_vendeuse,societe_acheteuse,produit)
2384
	 *		et le desc doit deja avoir la bonne valeur (a l'appelant de gerer le multilangue)
2385
	 *
2386
	 * 		@param    	string		$desc            	Description of line
2387
	 * 		@param    	double		$pu_ht              Unit price without tax (> 0 even for credit note)
2388
	 * 		@param    	double		$qty             	Quantity
2389
	 * 		@param    	double		$txtva           	Force Vat rate, -1 for auto
2390
	 * 		@param		double		$txlocaltax1		Local tax 1 rate (deprecated)
2391
	 *  	@param		double		$txlocaltax2		Local tax 2 rate (deprecated)
2392
	 *		@param    	int			$fk_product      	Id of predefined product/service
2393
	 * 		@param    	double		$remise_percent  	Percent of discount on line
2394
	 * 		@param    	int	$date_start      	Date start of service
2395
	 * 		@param    	int	$date_end        	Date end of service
2396
	 * 		@param    	int			$ventil          	Code of dispatching into accountancy
2397
	 * 		@param    	int			$info_bits			Bits de type de lignes
2398
	 *		@param    	int			$fk_remise_except	Id discount used
2399
	 *		@param		string		$price_base_type	'HT' or 'TTC'
2400
	 * 		@param    	double		$pu_ttc             Unit price with tax (> 0 even for credit note)
2401
	 * 		@param		int			$type				Type of line (0=product, 1=service). Not used if fk_product is defined, the type of product is used.
2402
	 *      @param      int			$rang               Position of line
2403
	 *      @param		int			$special_code		Special code (also used by externals modules!)
2404
	 *      @param		string		$origin				'order', ...
2405
	 *      @param		int			$origin_id			Id of origin object
2406
	 *      @param		int			$fk_parent_line		Id of parent line
2407
	 * 		@param		int			$fk_fournprice		Supplier price id (to calculate margin) or ''
2408
	 * 		@param		int			$pa_ht				Buying price of line (to calculate margin) or ''
2409
	 * 		@param		string		$label				Label of the line (deprecated, do not use)
2410
	 *		@param		array		$array_options		extrafields array
2411
	 *      @param      int         $situation_percent  Situation advance percentage
2412
	 *      @param      int         $fk_prev_id         Previous situation line id reference
2413
	 * 		@param 		string		$fk_unit 			Code of the unit to use. Null to use the default one
2414
	 * 		@param		double		$pu_ht_devise		Unit price in currency
2415
	 *    	@return    	int             				<0 if KO, Id of line if OK
2416
	 */
2417
	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)
2418
	{
2419
		// Deprecation warning
2420
		if ($label) {
2421
			dol_syslog(__METHOD__ . ": using line label is deprecated", LOG_WARNING);
2422
		}
2423
2424
		global $mysoc, $conf, $langs;
2425
2426
		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);
2427
		include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
2428
2429
		// Clean parameters
2430
		if (empty($remise_percent)) $remise_percent=0;
2431
		if (empty($qty)) $qty=0;
2432
		if (empty($info_bits)) $info_bits=0;
2433
		if (empty($rang)) $rang=0;
2434
		if (empty($ventil)) $ventil=0;
2435
		if (empty($txtva)) $txtva=0;
2436
		if (empty($txlocaltax1)) $txlocaltax1=0;
2437
		if (empty($txlocaltax2)) $txlocaltax2=0;
2438
		if (empty($fk_parent_line) || $fk_parent_line < 0) $fk_parent_line=0;
2439
		if (empty($fk_prev_id)) $fk_prev_id = 'null';
2440
		if (! isset($situation_percent) || $situation_percent > 100 || (string) $situation_percent == '') $situation_percent = 100;
2441
2442
		$localtaxes_type=getLocalTaxesFromRate($txtva, 0, $this->thirdparty, $mysoc);
2443
			
2444
		// Clean vat code
2445
		$vat_src_code='';
2446
		if (preg_match('/\((.*)\)/', $txtva, $reg))
2447
		{
2448
		    $vat_src_code = $reg[1];
2449
		    $txtva = preg_replace('/\s*\(.*\)/', '', $txtva);    // Remove code into vatrate.
2450
		}
2451
		
2452
		$remise_percent=price2num($remise_percent);
2453
		$qty=price2num($qty);
2454
		$pu_ht=price2num($pu_ht);
2455
		$pu_ttc=price2num($pu_ttc);
2456
		$pa_ht=price2num($pa_ht);
2457
		$txtva=price2num($txtva);
2458
		$txlocaltax1=price2num($txlocaltax1);
2459
		$txlocaltax2=price2num($txlocaltax2);
2460
2461
		if ($price_base_type=='HT')
2462
		{
2463
			$pu=$pu_ht;
2464
		}
2465
		else
2466
		{
2467
			$pu=$pu_ttc;
2468
		}
2469
2470
		// Check parameters
2471
		if ($type < 0) return -1;
2472
2473
		if (! empty($this->brouillon))
2474
		{
2475
			$this->db->begin();
2476
2477
			$product_type=$type;
2478
			if (!empty($fk_product))
2479
			{
2480
				$product=new Product($this->db);
2481
				$result=$product->fetch($fk_product);
2482
				$product_type=$product->type;
2483
2484
				if (! empty($conf->global->STOCK_MUST_BE_ENOUGH_FOR_INVOICE) && $product_type == 0 && $product->stock_reel < $qty) {
2485
                    $langs->load("errors");
2486
				    $this->error=$langs->trans('ErrorStockIsNotEnoughToAddProductOnInvoice', $product->ref);
2487
					$this->db->rollback();
2488
					return -3;
2489
				}
2490
			}
2491
2492
			// Calcul du total TTC et de la TVA pour la ligne a partir de
2493
			// qty, pu, remise_percent et txtva
2494
			// TRES IMPORTANT: C'est au moment de l'insertion ligne qu'on doit stocker
2495
			// la part ht, tva et ttc, et ce au niveau de la ligne qui a son propre taux tva.
2496
2497
			$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);
2498
2499
			$total_ht  = $tabprice[0];
2500
			$total_tva = $tabprice[1];
2501
			$total_ttc = $tabprice[2];
2502
			$total_localtax1 = $tabprice[9];
2503
			$total_localtax2 = $tabprice[10];
2504
			$pu_ht = $tabprice[3];
2505
2506
			// MultiCurrency
2507
			$multicurrency_total_ht  = $tabprice[16];
2508
            $multicurrency_total_tva = $tabprice[17];
2509
            $multicurrency_total_ttc = $tabprice[18];
2510
			$pu_ht_devise = $tabprice[19];
2511
2512
			// Rank to use
2513
			$rangtouse = $rang;
2514
			if ($rangtouse == -1)
2515
			{
2516
				$rangmax = $this->line_max($fk_parent_line);
2517
				$rangtouse = $rangmax + 1;
2518
			}
2519
2520
			// Insert line
2521
			$this->line=new FactureLigne($this->db);
2522
2523
			$this->line->context = $this->context;
2524
2525
			$this->line->fk_facture=$this->id;
2526
			$this->line->label=$label;	// deprecated
2527
			$this->line->desc=$desc;
2528
2529
			$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...
2530
			$this->line->subprice=       ($this->type==self::TYPE_CREDIT_NOTE?-abs($pu_ht):$pu_ht); // For credit note, unit price always negative, always positive otherwise
2531
2532
			$this->line->vat_src_code=$vat_src_code;
2533
			$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...
2534
			$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...
2535
			$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...
2536
			$this->line->localtax1_type = $localtaxes_type[0];
2537
			$this->line->localtax2_type = $localtaxes_type[2];
2538
2539
			$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
2540
			$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
2541
			$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
2542
			$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
2543
			$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
2544
2545
			$this->line->fk_product=$fk_product;
2546
			$this->line->product_type=$product_type;
2547
			$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...
2548
			$this->line->date_start=$date_start;
2549
			$this->line->date_end=$date_end;
2550
			$this->line->ventil=$ventil;
2551
			$this->line->rang=$rangtouse;
2552
			$this->line->info_bits=$info_bits;
2553
			$this->line->fk_remise_except=$fk_remise_except;
2554
2555
			$this->line->special_code=$special_code;
2556
			$this->line->fk_parent_line=$fk_parent_line;
2557
			$this->line->origin=$origin;
2558
			$this->line->origin_id=$origin_id;
2559
			$this->line->situation_percent = $situation_percent;
2560
			$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...
2561
			$this->line->fk_unit=$fk_unit;
2562
2563
			// infos marge
2564
			$this->line->fk_fournprice = $fk_fournprice;
2565
			$this->line->pa_ht = $pa_ht;
2566
2567
			// Multicurrency
2568
			$this->line->fk_multicurrency			= $this->fk_multicurrency;
2569
			$this->line->multicurrency_code			= $this->multicurrency_code;
2570
			$this->line->multicurrency_subprice		= $pu_ht_devise;
2571
			$this->line->multicurrency_total_ht 	= $multicurrency_total_ht;
2572
            $this->line->multicurrency_total_tva 	= $multicurrency_total_tva;
2573
            $this->line->multicurrency_total_ttc 	= $multicurrency_total_ttc;
2574
2575
			if (is_array($array_options) && count($array_options)>0) {
2576
				$this->line->array_options=$array_options;
2577
			}
2578
2579
			$result=$this->line->insert();
2580
			if ($result > 0)
2581
			{
2582
				// Reorder if child line
2583
				if (! empty($fk_parent_line)) $this->line_order(true,'DESC');
2584
2585
				// Mise a jour informations denormalisees au niveau de la facture meme
2586
				$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.
2587
				if ($result > 0)
2588
				{
2589
					$this->db->commit();
2590
					return $this->line->rowid;
2591
				}
2592
				else
2593
				{
2594
					$this->error=$this->db->error();
2595
					$this->db->rollback();
2596
					return -1;
2597
				}
2598
			}
2599
			else
2600
			{
2601
				$this->error=$this->line->error;
2602
				$this->db->rollback();
2603
				return -2;
2604
			}
2605
		}
2606
	}
2607
2608
	/**
2609
	 *  Update a detail line
2610
	 *
2611
	 *  @param     	int			$rowid           	Id of line to update
2612
	 *  @param     	string		$desc            	Description of line
2613
	 *  @param     	double		$pu              	Prix unitaire (HT ou TTC selon price_base_type) (> 0 even for credit note lines)
2614
	 *  @param     	double		$qty             	Quantity
2615
	 *  @param     	double		$remise_percent  	Pourcentage de remise de la ligne
2616
	 *  @param     	int		$date_start      	Date de debut de validite du service
2617
	 *  @param     	int		$date_end        	Date de fin de validite du service
2618
	 *  @param     	double		$txtva          	VAT Rate
2619
	 * 	@param		double		$txlocaltax1		Local tax 1 rate
2620
	 *  @param		double		$txlocaltax2		Local tax 2 rate
2621
	 * 	@param     	string		$price_base_type 	HT or TTC
2622
	 * 	@param     	int			$info_bits 		    Miscellaneous informations
2623
	 * 	@param		int			$type				Type of line (0=product, 1=service)
2624
	 * 	@param		int			$fk_parent_line		Id of parent line (0 in most cases, used by modules adding sublevels into lines).
2625
	 * 	@param		int			$skip_update_total	Keep fields total_xxx to 0 (used for special lines by some modules)
2626
	 * 	@param		int			$fk_fournprice		Id of origin supplier price
2627
	 * 	@param		int			$pa_ht				Price (without tax) of product when it was bought
2628
	 * 	@param		string		$label				Label of the line (deprecated, do not use)
2629
	 * 	@param		int			$special_code		Special code (also used by externals modules!)
2630
     *  @param		array		$array_options		extrafields array
2631
	 * 	@param      int         $situation_percent  Situation advance percentage
2632
	 * 	@param 		string		$fk_unit 			Code of the unit to use. Null to use the default one
2633
	 * 	@param		double		$pu_ht_devise		Unit price in currency
2634
	 *  @return    	int             				< 0 if KO, > 0 if OK
2635
	 */
2636
	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=0, $fk_unit = null, $pu_ht_devise = 0)
2637
	{
2638
		global $conf,$user;
2639
		// Deprecation warning
2640
		if ($label) {
2641
			dol_syslog(__METHOD__ . ": using line label is deprecated", LOG_WARNING);
2642
		}
2643
2644
		include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
2645
2646
		global $mysoc,$langs;
2647
2648
		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", LOG_DEBUG);
2649
2650
		if ($this->brouillon)
2651
		{
2652
			if (!$this->is_last_in_cycle() && empty($this->error))
2653
			{
2654
				if (!$this->checkProgressLine($rowid, $situation_percent))
2655
				{
2656
					if (!$this->error) $this->error=$langs->trans('invoiceLineProgressError');
2657
					return -3;
2658
				}
2659
			}
2660
2661
			$this->db->begin();
2662
2663
			// Clean parameters
2664
			if (empty($qty)) $qty=0;
2665
			if (empty($fk_parent_line) || $fk_parent_line < 0) $fk_parent_line=0;
2666
			if (empty($special_code) || $special_code == 3) $special_code=0;
2667
			if (! isset($situation_percent) || $situation_percent > 100 || (string) $situation_percent == '') $situation_percent = 100;
2668
2669
			$remise_percent	= price2num($remise_percent);
2670
			$qty			= price2num($qty);
2671
			$pu 			= price2num($pu);
2672
			$pa_ht			= price2num($pa_ht);
2673
			$txtva			= price2num($txtva);
2674
			$txlocaltax1	= price2num($txlocaltax1);
2675
			$txlocaltax2	= price2num($txlocaltax2);
2676
2677
			// Check parameters
2678
			if ($type < 0) return -1;
2679
2680
			// Calculate total with, without tax and tax from qty, pu, remise_percent and txtva
2681
			// TRES IMPORTANT: C'est au moment de l'insertion ligne qu'on doit stocker
2682
			// la part ht, tva et ttc, et ce au niveau de la ligne qui a son propre taux tva.
2683
2684
			$localtaxes_type=getLocalTaxesFromRate($txtva,0,$this->thirdparty, $mysoc);
2685
2686
			// Clean vat code
2687
    		$vat_src_code='';
2688
    		if (preg_match('/\((.*)\)/', $txtva, $reg))
2689
    		{
2690
    		    $vat_src_code = $reg[1];
2691
    		    $txtva = preg_replace('/\s*\(.*\)/', '', $txtva);    // Remove code into vatrate.
2692
    		}
2693
2694
			$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);
2695
2696
			$total_ht  = $tabprice[0];
2697
			$total_tva = $tabprice[1];
2698
			$total_ttc = $tabprice[2];
2699
			$total_localtax1=$tabprice[9];
2700
			$total_localtax2=$tabprice[10];
2701
			$pu_ht  = $tabprice[3];
2702
			$pu_tva = $tabprice[4];
2703
			$pu_ttc = $tabprice[5];
2704
2705
			// MultiCurrency
2706
			$multicurrency_total_ht  = $tabprice[16];
2707
            $multicurrency_total_tva = $tabprice[17];
2708
            $multicurrency_total_ttc = $tabprice[18];
2709
			$pu_ht_devise = $tabprice[19];
2710
2711
			// Old properties: $price, $remise (deprecated)
2712
			$price = $pu;
2713
			$remise = 0;
2714
			if ($remise_percent > 0)
2715
			{
2716
				$remise = round(($pu * $remise_percent / 100),2);
2717
				$price = ($pu - $remise);
2718
			}
2719
			$price    = price2num($price);
2720
2721
			//Fetch current line from the database and then clone the object and set it in $oldline property
2722
			$line = new FactureLigne($this->db);
2723
			$line->fetch($rowid);
2724
2725
			if (!empty($line->fk_product))
2726
			{
2727
				$product=new Product($this->db);
2728
				$result=$product->fetch($line->fk_product);
2729
				$product_type=$product->type;
2730
2731
				if (! empty($conf->global->STOCK_MUST_BE_ENOUGH_FOR_INVOICE) && $product_type == 0 && $product->stock_reel < $qty) {
2732
                    $langs->load("errors");
2733
				    $this->error=$langs->trans('ErrorStockIsNotEnoughToAddProductOnInvoice', $product->ref);
2734
					$this->db->rollback();
2735
					return -3;
2736
				}
2737
			}
2738
2739
			$staticline = clone $line;
2740
2741
			$line->oldline = $staticline;
2742
			$this->line = $line;
2743
            $this->line->context = $this->context;
2744
2745
			// Reorder if fk_parent_line change
2746
			if (! empty($fk_parent_line) && ! empty($staticline->fk_parent_line) && $fk_parent_line != $staticline->fk_parent_line)
2747
			{
2748
				$rangmax = $this->line_max($fk_parent_line);
2749
				$this->line->rang = $rangmax + 1;
2750
			}
2751
2752
			$this->line->rowid				= $rowid;
2753
			$this->line->label				= $label;
2754
			$this->line->desc				= $desc;
2755
			$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...
2756
            
2757
			$this->line->vat_src_code       = $vat_src_code;
2758
			$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...
2759
			$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...
2760
			$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...
2761
			$this->line->localtax1_type		= $localtaxes_type[0];
2762
			$this->line->localtax2_type		= $localtaxes_type[2];
2763
			
2764
			$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...
2765
			$this->line->subprice			= ($this->type==2?-abs($pu_ht):$pu_ht); // For credit note, unit price always negative, always positive otherwise
2766
			$this->line->date_start			= $date_start;
2767
			$this->line->date_end			= $date_end;
2768
			$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
2769
			$this->line->total_tva			= (($this->type==self::TYPE_CREDIT_NOTE||$qty<0)?-abs($total_tva):$total_tva);
2770
			$this->line->total_localtax1	= $total_localtax1;
2771
			$this->line->total_localtax2	= $total_localtax2;
2772
			$this->line->total_ttc			= (($this->type==self::TYPE_CREDIT_NOTE||$qty<0)?-abs($total_ttc):$total_ttc);
2773
			$this->line->info_bits			= $info_bits;
2774
			$this->line->special_code		= $special_code;
2775
			$this->line->product_type		= $type;
2776
			$this->line->fk_parent_line		= $fk_parent_line;
2777
			$this->line->skip_update_total	= $skip_update_total;
2778
			$this->line->situation_percent  = $situation_percent;
2779
			$this->line->fk_unit				= $fk_unit;
2780
2781
			$this->line->fk_fournprice = $fk_fournprice;
2782
			$this->line->pa_ht = $pa_ht;
2783
2784
			// Multicurrency
2785
			$this->line->multicurrency_subprice		= $pu_ht_devise;
2786
			$this->line->multicurrency_total_ht 	= $multicurrency_total_ht;
2787
            $this->line->multicurrency_total_tva 	= $multicurrency_total_tva;
2788
            $this->line->multicurrency_total_ttc 	= $multicurrency_total_ttc;
2789
2790
			if (is_array($array_options) && count($array_options)>0) {
2791
				$this->line->array_options=$array_options;
2792
			}
2793
2794
			$result=$this->line->update($user);
2795
			if ($result > 0)
2796
			{
2797
				// Reorder if child line
2798
				if (! empty($fk_parent_line)) $this->line_order(true,'DESC');
2799
2800
				// Mise a jour info denormalisees au niveau facture
2801
				$this->update_price(1);
2802
				$this->db->commit();
2803
				return $result;
2804
			}
2805
			else
2806
			{
2807
			    $this->error=$this->line->error;
2808
				$this->db->rollback();
2809
				return -1;
2810
			}
2811
		}
2812
		else
2813
		{
2814
			$this->error="Invoice statut makes operation forbidden";
2815
			return -2;
2816
		}
2817
	}
2818
2819
	/**
2820
	 * Check if the percent edited is lower of next invoice line
2821
	 *
2822
	 * @param	int		$idline				id of line to check
2823
	 * @param	float	$situation_percent	progress percentage need to be test
2824
	 * @return false if KO, true if OK
2825
	 */
2826
	function checkProgressLine($idline, $situation_percent)
2827
	{
2828
		$sql = 'SELECT fd.situation_percent FROM '.MAIN_DB_PREFIX.'facturedet fd
2829
				INNER JOIN '.MAIN_DB_PREFIX.'facture f ON (fd.fk_facture = f.rowid)
2830
				WHERE fd.fk_prev_id = '.$idline.'
2831
				AND f.fk_statut <> 0';
2832
2833
		$result = $this->db->query($sql);
2834
		if (! $result)
2835
		{
2836
			$this->error=$this->db->error();
2837
			return false;
2838
		}
2839
2840
		$obj = $this->db->fetch_object($result);
2841
2842
		if ($obj === null) return true;
2843
		else return $situation_percent < $obj->situation_percent;
2844
	}
2845
2846
	/**
2847
	 * Update invoice line with percentage
2848
	 *
2849
	 * @param  FactureLigne $line       Invoice line
2850
	 * @param  int          $percent    Percentage
2851
	 * @return void
2852
	 */
2853
	function update_percent($line, $percent)
2854
	{
2855
	    global $mysoc,$user;
2856
2857
		include_once(DOL_DOCUMENT_ROOT . '/core/lib/price.lib.php');
2858
2859
		// Cap percentages to 100
2860
		if ($percent > 100) $percent = 100;
2861
		$line->situation_percent = $percent;
2862
		$tabprice = calcul_price_total($line->qty, $line->subprice, $line->remise_percent, $line->tva_tx, $line->localtax1_tx, $line->localtax2_tx, $line->product_type, 'HT', 0, 0, $mysoc, '', $percent);
2863
		$line->total_ht = $tabprice[0];
2864
		$line->total_tva = $tabprice[1];
2865
		$line->total_ttc = $tabprice[2];
2866
		$line->total_localtax1 = $tabprice[9];
2867
		$line->total_localtax2 = $tabprice[10];
2868
		$line->update($user);
2869
		$this->update_price(1);
2870
		$this->db->commit();
2871
	}
2872
2873
	/**
2874
	 *	Delete line in database
2875
	 *
2876
	 *	@param		int		$rowid		Id of line to delete
2877
	 *	@return		int					<0 if KO, >0 if OK
2878
	 */
2879
	function deleteline($rowid)
2880
	{
2881
        global $user;
2882
        
2883
		dol_syslog(get_class($this)."::deleteline rowid=".$rowid, LOG_DEBUG);
2884
2885
		if (! $this->brouillon)
2886
		{
2887
			$this->error='ErrorBadStatus';
2888
			return -1;
2889
		}
2890
2891
		$this->db->begin();
2892
2893
		// Libere remise liee a ligne de facture
2894
		$sql = 'UPDATE '.MAIN_DB_PREFIX.'societe_remise_except';
2895
		$sql.= ' SET fk_facture_line = NULL';
2896
		$sql.= ' WHERE fk_facture_line = '.$rowid;
2897
2898
		dol_syslog(get_class($this)."::deleteline", LOG_DEBUG);
2899
		$result = $this->db->query($sql);
2900
		if (! $result)
2901
		{
2902
			$this->error=$this->db->error();
2903
			$this->db->rollback();
2904
			return -1;
2905
		}
2906
2907
		$line=new FactureLigne($this->db);
2908
		
2909
        $line->context = $this->context;
2910
2911
		// For triggers
2912
		$result = $line->fetch($rowid);
2913
		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...
2914
		
2915
		if ($line->delete($user) > 0)
2916
		{
2917
			$result=$this->update_price(1);
2918
2919
			if ($result > 0)
2920
			{
2921
				$this->db->commit();
2922
				return 1;
2923
			}
2924
			else
2925
			{
2926
				$this->db->rollback();
2927
				$this->error=$this->db->lasterror();
2928
				return -1;
2929
			}
2930
		}
2931
		else
2932
		{
2933
			$this->db->rollback();
2934
			$this->error=$line->error;
2935
			return -1;
2936
		}
2937
	}
2938
2939
	/**
2940
	 *	Set percent discount
2941
	 *
2942
	 *	@param     	User	$user		User that set discount
2943
	 *	@param     	double	$remise		Discount
2944
	 *  @param     	int		$notrigger	1=Does not execute triggers, 0= execute triggers
2945
	 *	@return		int 		<0 if ko, >0 if ok
2946
	 */
2947
	function set_remise($user, $remise, $notrigger=0)
2948
	{
2949
		// Clean parameters
2950
		if (empty($remise)) $remise=0;
2951
2952
		if ($user->rights->facture->creer)
2953
		{
2954
			$remise=price2num($remise);
2955
2956
			$error=0;
2957
2958
			$this->db->begin();
2959
2960
			$sql = 'UPDATE '.MAIN_DB_PREFIX.'facture';
2961
			$sql.= ' SET remise_percent = '.$remise;
2962
			$sql.= ' WHERE rowid = '.$this->id;
2963
			$sql.= ' AND fk_statut = '.self::STATUS_DRAFT;
2964
2965
			dol_syslog(__METHOD__, LOG_DEBUG);
2966
			$resql=$this->db->query($sql);
2967
			if (!$resql)
2968
			{
2969
				$this->errors[]=$this->db->error();
2970
				$error++;
2971
			}
2972
2973
			if (! $notrigger && empty($error))
2974
			{
2975
				// Call trigger
2976
				$result=$this->call_trigger('BILL_MODIFY',$user);
2977
				if ($result < 0) $error++;
2978
				// End call triggers
2979
			}
2980
2981
			if (! $error)
2982
			{
2983
				$this->remise_percent = $remise;
2984
				$this->update_price(1);
2985
2986
				$this->db->commit();
2987
				return 1;
2988
			}
2989
			else
2990
			{
2991
				foreach($this->errors as $errmsg)
2992
				{
2993
					dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2994
					$this->error.=($this->error?', '.$errmsg:$errmsg);
2995
				}
2996
				$this->db->rollback();
2997
				return -1*$error;
2998
			}
2999
		}
3000
	}
3001
3002
3003
	/**
3004
	 *	Set absolute discount
3005
	 *
3006
	 *	@param     	User	$user 		User that set discount
3007
	 *	@param     	double	$remise		Discount
3008
	 *  @param     	int		$notrigger	1=Does not execute triggers, 0= execute triggers
3009
	 *	@return		int 				<0 if KO, >0 if OK
3010
	 */
3011
	function set_remise_absolue($user, $remise, $notrigger=0)
3012
	{
3013
		if (empty($remise)) $remise=0;
3014
3015
		if ($user->rights->facture->creer)
3016
		{
3017
			$error=0;
3018
3019
			$this->db->begin();
3020
3021
			$remise=price2num($remise);
3022
3023
			$sql = 'UPDATE '.MAIN_DB_PREFIX.'facture';
3024
			$sql.= ' SET remise_absolue = '.$remise;
3025
			$sql.= ' WHERE rowid = '.$this->id;
3026
			$sql.= ' AND fk_statut = '.self::STATUS_DRAFT;
3027
3028
			dol_syslog(__METHOD__, LOG_DEBUG);
3029
			$resql=$this->db->query($sql);
3030
			if (!$resql)
3031
			{
3032
				$this->errors[]=$this->db->error();
3033
				$error++;
3034
			}
3035
3036
			if (! $error)
3037
			{
3038
				$this->oldcopy= clone $this;
3039
				$this->remise_absolue = $remise;
3040
				$this->update_price(1);
3041
			}
3042
3043
			if (! $notrigger && empty($error))
3044
			{
3045
				// Call trigger
3046
				$result=$this->call_trigger('BILL_MODIFY',$user);
3047
				if ($result < 0) $error++;
3048
				// End call triggers
3049
			}
3050
3051
			if (! $error)
3052
			{
3053
				$this->db->commit();
3054
				return 1;
3055
			}
3056
			else
3057
			{
3058
				foreach($this->errors as $errmsg)
3059
				{
3060
					dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
3061
					$this->error.=($this->error?', '.$errmsg:$errmsg);
3062
				}
3063
				$this->db->rollback();
3064
				return -1*$error;
3065
			}
3066
		}
3067
	}
3068
3069
	/**
3070
	 *  Return list of payments
3071
	 *
3072
	 *	@param		string	$filtertype		1 to filter on type of payment == 'PRE'
3073
	 *  @return     array					Array with list of payments
3074
	 */
3075
	function getListOfPayments($filtertype='')
3076
	{
3077
		$retarray=array();
3078
3079
		$table='paiement_facture';
3080
		$table2='paiement';
3081
		$field='fk_facture';
3082
		$field2='fk_paiement';
3083
		if ($this->element == 'facture_fourn' || $this->element == 'invoice_supplier')
3084
		{
3085
			$table='paiementfourn_facturefourn';
3086
			$table2='paiementfourn';
3087
			$field='fk_facturefourn';
3088
			$field2='fk_paiementfourn';
3089
		}
3090
3091
		$sql = 'SELECT pf.amount, pf.multicurrency_amount, p.fk_paiement, p.datep, p.num_paiement as num, t.code';
3092
		$sql.= ' FROM '.MAIN_DB_PREFIX.$table.' as pf, '.MAIN_DB_PREFIX.$table2.' as p, '.MAIN_DB_PREFIX.'c_paiement as t';
3093
		$sql.= ' WHERE pf.'.$field.' = '.$this->id;
3094
		//$sql.= ' WHERE pf.'.$field.' = 1';
3095
		$sql.= ' AND pf.'.$field2.' = p.rowid';
3096
		$sql.= ' AND p.fk_paiement = t.id';
3097
		if ($filtertype) $sql.=" AND t.code='PRE'";
3098
3099
		dol_syslog(get_class($this)."::getListOfPayments", LOG_DEBUG);
3100
		$resql=$this->db->query($sql);
3101
		if ($resql)
3102
		{
3103
			$num = $this->db->num_rows($resql);
3104
			$i=0;
3105
			while ($i < $num)
3106
			{
3107
				$obj = $this->db->fetch_object($resql);
3108
				$retarray[]=array('amount'=>$obj->amount,'type'=>$obj->code, 'date'=>$obj->datep, 'num'=>$obj->num);
3109
				$i++;
3110
			}
3111
			$this->db->free($resql);
3112
			return $retarray;
3113
		}
3114
		else
3115
		{
3116
			$this->error=$this->db->lasterror();
3117
			dol_print_error($this->db);
3118
			return array();
3119
		}
3120
	}
3121
3122
3123
	/**
3124
	 *      Return next reference of customer invoice not already used (or last reference)
3125
	 *      according to numbering module defined into constant FACTURE_ADDON
3126
	 *
3127
	 *      @param	   Societe		$soc		object company
3128
	 *      @param     string		$mode		'next' for next value or 'last' for last value
3129
	 *      @return    string					free ref or last ref
3130
	 */
3131
	function getNextNumRef($soc,$mode='next')
3132
	{
3133
		global $conf, $langs;
3134
		$langs->load("bills");
3135
3136
		// Clean parameters (if not defined or using deprecated value)
3137
		if (empty($conf->global->FACTURE_ADDON)) $conf->global->FACTURE_ADDON='mod_facture_terre';
3138
		else if ($conf->global->FACTURE_ADDON=='terre') $conf->global->FACTURE_ADDON='mod_facture_terre';
3139
		else if ($conf->global->FACTURE_ADDON=='mercure') $conf->global->FACTURE_ADDON='mod_facture_mercure';
3140
3141
		if (! empty($conf->global->FACTURE_ADDON))
3142
		{
3143
			$mybool=false;
3144
3145
			$file = $conf->global->FACTURE_ADDON.".php";
3146
			$classname = $conf->global->FACTURE_ADDON;
3147
3148
			// Include file with class
3149
			$dirmodels = array_merge(array('/'), (array) $conf->modules_parts['models']);
3150
3151
			foreach ($dirmodels as $reldir) {
3152
3153
				$dir = dol_buildpath($reldir."core/modules/facture/");
3154
3155
				// Load file with numbering class (if found)
3156
				if (is_file($dir.$file) && is_readable($dir.$file))
3157
				{
3158
                    $mybool |= include_once $dir . $file;
3159
                }
3160
			}
3161
3162
			// For compatibility
3163
			if (! $mybool)
3164
			{
3165
				$file = $conf->global->FACTURE_ADDON."/".$conf->global->FACTURE_ADDON.".modules.php";
3166
				$classname = "mod_facture_".$conf->global->FACTURE_ADDON;
3167
				$classname = preg_replace('/\-.*$/','',$classname);
3168
				// Include file with class
3169
				foreach ($conf->file->dol_document_root as $dirroot)
3170
				{
3171
					$dir = $dirroot."/core/modules/facture/";
3172
3173
					// Load file with numbering class (if found)
3174
					if (is_file($dir.$file) && is_readable($dir.$file)) {
3175
                        $mybool |= include_once $dir . $file;
3176
                    }
3177
				}
3178
			}
3179
3180
			if (! $mybool)
3181
			{
3182
				dol_print_error('',"Failed to include file ".$file);
3183
				return '';
3184
			}
3185
3186
			$obj = new $classname();
3187
			$numref = "";
3188
			$numref = $obj->getNextValue($soc,$this,$mode);
3189
3190
			/**
3191
			 * $numref can be empty in case we ask for the last value because if there is no invoice created with the
3192
			 * set up mask.
3193
			 */
3194
			if ($mode != 'last' && !$numref) {
3195
				dol_print_error($this->db,"Facture::getNextNumRef ".$obj->error);
3196
				return "";
3197
			}
3198
3199
			return $numref;
3200
		}
3201
		else
3202
		{
3203
			$langs->load("errors");
3204
			print $langs->trans("Error")." ".$langs->trans("ErrorModuleSetupNotComplete");
3205
			return "";
3206
		}
3207
	}
3208
3209
	/**
3210
	 *	Load miscellaneous information for tab "Info"
3211
	 *
3212
	 *	@param  int		$id		Id of object to load
3213
	 *	@return	void
3214
	 */
3215
	function info($id)
3216
	{
3217
		$sql = 'SELECT c.rowid, datec, date_valid as datev, tms as datem,';
3218
		$sql.= ' fk_user_author, fk_user_valid';
3219
		$sql.= ' FROM '.MAIN_DB_PREFIX.'facture as c';
3220
		$sql.= ' WHERE c.rowid = '.$id;
3221
3222
		$result=$this->db->query($sql);
3223
		if ($result)
3224
		{
3225
			if ($this->db->num_rows($result))
3226
			{
3227
				$obj = $this->db->fetch_object($result);
3228
				$this->id = $obj->rowid;
3229
				if ($obj->fk_user_author)
3230
				{
3231
					$cuser = new User($this->db);
3232
					$cuser->fetch($obj->fk_user_author);
3233
					$this->user_creation     = $cuser;
3234
				}
3235
				if ($obj->fk_user_valid)
3236
				{
3237
					$vuser = new User($this->db);
3238
					$vuser->fetch($obj->fk_user_valid);
3239
					$this->user_validation = $vuser;
3240
				}
3241
				$this->date_creation     = $this->db->jdate($obj->datec);
3242
				$this->date_modification = $this->db->jdate($obj->datem);
3243
				$this->date_validation   = $this->db->jdate($obj->datev);	// Should be in log table
3244
			}
3245
			$this->db->free($result);
3246
		}
3247
		else
3248
		{
3249
			dol_print_error($this->db);
3250
		}
3251
	}
3252
3253
	/**
3254
	 *	Renvoi si les lignes de facture sont ventilees et/ou exportees en compta
3255
	 *
3256
	 *   @return     int         <0 if KO, 0=no, 1=yes
3257
	 */
3258
	function getVentilExportCompta()
3259
	{
3260
		// On verifie si les lignes de factures ont ete exportees en compta et/ou ventilees
3261
		$ventilExportCompta = 0 ;
3262
		$num=count($this->lines);
3263
		for ($i = 0; $i < $num; $i++)
3264
		{
3265
			if (! empty($this->lines[$i]->export_compta) && ! empty($this->lines[$i]->code_ventilation))
3266
			{
3267
				$ventilExportCompta++;
3268
			}
3269
		}
3270
3271
		if ($ventilExportCompta <> 0)
3272
		{
3273
			return 1;
3274
		}
3275
		else
3276
		{
3277
			return 0;
3278
		}
3279
	}
3280
3281
3282
	/**
3283
	 *  Return if an invoice can be deleted
3284
	 *	Rule is:
3285
	 *	If hidden option INVOICE_CAN_ALWAYS_BE_REMOVED is on, we can
3286
	 *  If invoice has a definitive ref, is last, without payment and not dipatched into accountancy -> yes end of rule
3287
	 *  If invoice is draft and ha a temporary ref -> yes
3288
	 *
3289
	 *  @return    int         <0 if KO, 0=no, 1=yes
3290
	 */
3291
	function is_erasable()
3292
	{
3293
		global $conf;
3294
3295
		if (! empty($conf->global->INVOICE_CAN_ALWAYS_BE_REMOVED)) return 1;
3296
		if (! empty($conf->global->INVOICE_CAN_NEVER_BE_REMOVED))  return 0;
3297
3298
		// on verifie si la facture est en numerotation provisoire
3299
		$facref = substr($this->ref, 1, 4);
3300
3301
		// If not a draft invoice and not temporary invoice
3302
		if ($facref != 'PROV')
3303
		{
3304
			$maxfacnumber = $this->getNextNumRef($this->thirdparty,'last');
3305
			$ventilExportCompta = $this->getVentilExportCompta();
3306
			// If there is no invoice into the reset range and not already dispatched, we can delete
3307
			if ($maxfacnumber == '' && $ventilExportCompta == 0) return 1;
3308
			// If invoice to delete is last one and not already dispatched, we can delete
3309
			if ($maxfacnumber == $this->ref && $ventilExportCompta == 0) return 1;
3310
			if ($this->situation_cycle_ref) {
3311
				$last = $this->is_last_in_cycle();
3312
				return $last;
3313
			}
3314
		}
3315
		else if ($this->statut == self::STATUS_DRAFT && $facref == 'PROV') // Si facture brouillon et provisoire
3316
		{
3317
			return 1;
3318
		}
3319
3320
		return 0;
3321
	}
3322
3323
3324
	/**
3325
	 *  Return list of invoices (eventually filtered on a user) into an array
3326
	 *
3327
	 *  @param		int		$shortlist		0=Return array[id]=ref, 1=Return array[](id=>id,ref=>ref,name=>name)
3328
	 *  @param      int		$draft      	0=not draft, 1=draft
3329
	 *  @param      User	$excluser      	Objet user to exclude
3330
	 *  @param    	int		$socid			Id third pary
3331
	 *  @param    	int		$limit			For pagination
3332
	 *  @param    	int		$offset			For pagination
3333
	 *  @param    	string	$sortfield		Sort criteria
3334
	 *  @param    	string	$sortorder		Sort order
3335
	 *  @return     int             		-1 if KO, array with result if OK
3336
	 */
3337
	function liste_array($shortlist=0, $draft=0, $excluser='', $socid=0, $limit=0, $offset=0, $sortfield='f.datef,f.rowid', $sortorder='DESC')
3338
	{
3339
		global $conf,$user;
3340
3341
		$ga = array();
3342
3343
		$sql = "SELECT s.rowid, s.nom as name, s.client,";
3344
		$sql.= " f.rowid as fid, f.facnumber as ref, f.datef as df";
3345
		if (! $user->rights->societe->client->voir && ! $socid) $sql .= ", sc.fk_soc, sc.fk_user";
3346
		$sql.= " FROM ".MAIN_DB_PREFIX."societe as s, ".MAIN_DB_PREFIX."facture as f";
3347
		if (! $user->rights->societe->client->voir && ! $socid) $sql .= ", ".MAIN_DB_PREFIX."societe_commerciaux as sc";
3348
		$sql.= " WHERE f.entity = ".$conf->entity;
3349
		$sql.= " AND f.fk_soc = s.rowid";
3350
		if (! $user->rights->societe->client->voir && ! $socid) //restriction
3351
		{
3352
			$sql.= " AND s.rowid = sc.fk_soc AND sc.fk_user = " .$user->id;
3353
		}
3354
		if ($socid) $sql.= " AND s.rowid = ".$socid;
3355
		if ($draft) $sql.= " AND f.fk_statut = ".self::STATUS_DRAFT;
3356
		if (is_object($excluser)) $sql.= " AND f.fk_user_author <> ".$excluser->id;
3357
		$sql.= $this->db->order($sortfield,$sortorder);
3358
		$sql.= $this->db->plimit($limit,$offset);
3359
3360
		$result=$this->db->query($sql);
3361
		if ($result)
3362
		{
3363
			$numc = $this->db->num_rows($result);
3364
			if ($numc)
3365
			{
3366
				$i = 0;
3367
				while ($i < $numc)
3368
				{
3369
					$obj = $this->db->fetch_object($result);
3370
3371
					if ($shortlist == 1)
3372
					{
3373
						$ga[$obj->fid] = $obj->ref;
3374
					}
3375
					else if ($shortlist == 2)
3376
					{
3377
						$ga[$obj->fid] = $obj->ref.' ('.$obj->name.')';
3378
					}
3379
					else
3380
					{
3381
						$ga[$i]['id']	= $obj->fid;
3382
						$ga[$i]['ref'] 	= $obj->ref;
3383
						$ga[$i]['name'] = $obj->name;
3384
					}
3385
					$i++;
3386
				}
3387
			}
3388
			return $ga;
3389
		}
3390
		else
3391
		{
3392
			dol_print_error($this->db);
3393
			return -1;
3394
		}
3395
	}
3396
3397
3398
	/**
3399
	 *	Renvoi liste des factures remplacables
3400
	 *	Statut validee ou abandonnee pour raison autre + non payee + aucun paiement + pas deja remplacee
3401
	 *
3402
	 *	@param		int		$socid		Id societe
3403
	 *	@return    	array				Tableau des factures ('id'=>id, 'ref'=>ref, 'status'=>status, 'paymentornot'=>0/1)
3404
	 */
3405
	function list_replacable_invoices($socid=0)
3406
	{
3407
		global $conf;
3408
3409
		$return = array();
3410
3411
		$sql = "SELECT f.rowid as rowid, f.facnumber, f.fk_statut,";
3412
		$sql.= " ff.rowid as rowidnext";
3413
		$sql.= " FROM ".MAIN_DB_PREFIX."facture as f";
3414
		$sql.= " LEFT JOIN ".MAIN_DB_PREFIX."paiement_facture as pf ON f.rowid = pf.fk_facture";
3415
		$sql.= " LEFT JOIN ".MAIN_DB_PREFIX."facture as ff ON f.rowid = ff.fk_facture_source";
3416
		$sql.= " WHERE (f.fk_statut = ".self::STATUS_VALIDATED." OR (f.fk_statut = ".self::STATUS_ABANDONED." AND f.close_code = '".self::CLOSECODE_ABANDONED."'))";
3417
		$sql.= " AND f.entity = ".$conf->entity;
3418
		$sql.= " AND f.paye = 0";					// Pas classee payee completement
3419
		$sql.= " AND pf.fk_paiement IS NULL";		// Aucun paiement deja fait
3420
		$sql.= " AND ff.fk_statut IS NULL";			// Renvoi vrai si pas facture de remplacement
3421
		if ($socid > 0) $sql.=" AND f.fk_soc = ".$socid;
3422
		$sql.= " ORDER BY f.facnumber";
3423
3424
		dol_syslog(get_class($this)."::list_replacable_invoices", LOG_DEBUG);
3425
		$resql=$this->db->query($sql);
3426
		if ($resql)
3427
		{
3428
			while ($obj=$this->db->fetch_object($resql))
3429
			{
3430
				$return[$obj->rowid]=array(	'id' => $obj->rowid,
3431
				'ref' => $obj->facnumber,
3432
				'status' => $obj->fk_statut);
3433
			}
3434
			//print_r($return);
3435
			return $return;
3436
		}
3437
		else
3438
		{
3439
			$this->error=$this->db->error();
3440
			return -1;
3441
		}
3442
	}
3443
3444
3445
	/**
3446
	 *	Renvoi liste des factures qualifiables pour correction par avoir
3447
	 *	Les factures qui respectent les regles suivantes sont retournees:
3448
	 *	(validee + paiement en cours) ou classee (payee completement ou payee partiellement) + pas deja remplacee + pas deja avoir
3449
	 *
3450
	 *	@param		int		$socid		Id societe
3451
	 *	@return    	array				Tableau des factures ($id => array('ref'=>,'paymentornot'=>,'status'=>,'paye'=>)
3452
	 */
3453
	function list_qualified_avoir_invoices($socid=0)
3454
	{
3455
		global $conf;
3456
3457
		$return = array();
3458
3459
		$sql = "SELECT f.rowid as rowid, f.facnumber, f.fk_statut, f.type, f.paye, pf.fk_paiement";
3460
		$sql.= " FROM ".MAIN_DB_PREFIX."facture as f";
3461
		$sql.= " LEFT JOIN ".MAIN_DB_PREFIX."paiement_facture as pf ON f.rowid = pf.fk_facture";
3462
		$sql.= " LEFT JOIN ".MAIN_DB_PREFIX."facture as ff ON (f.rowid = ff.fk_facture_source AND ff.type=".self::TYPE_REPLACEMENT.")";
3463
		$sql.= " WHERE f.entity = ".$conf->entity;
3464
		$sql.= " AND f.fk_statut in (".self::STATUS_VALIDATED.",".self::STATUS_CLOSED.")";
3465
		//  $sql.= " WHERE f.fk_statut >= 1";
3466
		//	$sql.= " AND (f.paye = 1";				// Classee payee completement
3467
		//	$sql.= " OR f.close_code IS NOT NULL)";	// Classee payee partiellement
3468
		$sql.= " AND ff.type IS NULL";			// Renvoi vrai si pas facture de remplacement
3469
		$sql.= " AND f.type != ".self::TYPE_CREDIT_NOTE;				// Type non 2 si facture non avoir
3470
		if ($socid > 0) $sql.=" AND f.fk_soc = ".$socid;
3471
		$sql.= " ORDER BY f.facnumber";
3472
3473
		dol_syslog(get_class($this)."::list_qualified_avoir_invoices", LOG_DEBUG);
3474
		$resql=$this->db->query($sql);
3475
		if ($resql)
3476
		{
3477
			while ($obj=$this->db->fetch_object($resql))
3478
			{
3479
				$qualified=0;
3480
				if ($obj->fk_statut == self::STATUS_VALIDATED) $qualified=1;
3481
				if ($obj->fk_statut == self::STATUS_CLOSED) $qualified=1;
3482
				if ($qualified)
3483
				{
3484
					//$ref=$obj->facnumber;
3485
					$paymentornot=($obj->fk_paiement?1:0);
3486
					$return[$obj->rowid]=array('ref'=>$obj->facnumber,'status'=>$obj->fk_statut,'type'=>$obj->type,'paye'=>$obj->paye,'paymentornot'=>$paymentornot);
3487
				}
3488
			}
3489
3490
			return $return;
3491
		}
3492
		else
3493
		{
3494
			$this->error=$this->db->error();
3495
			return -1;
3496
		}
3497
	}
3498
3499
3500
	/**
3501
	 *	Create a withdrawal request for a standing order
3502
	 *
3503
	 *	@param      User	$fuser       User asking standing order
3504
	 *  @param		float	$amount		Amount we request withdraw for
3505
	 *	@return     int         		<0 if KO, >0 if OK
3506
	 */
3507
	function demande_prelevement($fuser, $amount=0)
3508
	{
3509
3510
		$error=0;
3511
3512
		dol_syslog(get_class($this)."::demande_prelevement", LOG_DEBUG);
3513
3514
		if ($this->statut > self::STATUS_DRAFT && $this->paye == 0)
3515
		{
3516
	        require_once DOL_DOCUMENT_ROOT . '/societe/class/companybankaccount.class.php';
3517
	        $bac = new CompanyBankAccount($this->db);
3518
	        $bac->fetch(0,$this->socid);
3519
3520
        	$sql = 'SELECT count(*)';
3521
			$sql.= ' FROM '.MAIN_DB_PREFIX.'prelevement_facture_demande';
3522
			$sql.= ' WHERE fk_facture = '.$this->id;
3523
			$sql.= ' AND traite = 0';
3524
3525
			dol_syslog(get_class($this)."::demande_prelevement", LOG_DEBUG);
3526
			$resql=$this->db->query($sql);
3527
			if ($resql)
3528
			{
3529
				$row = $this->db->fetch_row($resql);
3530
				if ($row[0] == 0)
3531
				{
3532
					$now=dol_now();
3533
3534
                    $totalpaye  = $this->getSommePaiement();
3535
                    $totalcreditnotes = $this->getSumCreditNotesUsed();
3536
                    $totaldeposits = $this->getSumDepositsUsed();
3537
                    //print "totalpaye=".$totalpaye." totalcreditnotes=".$totalcreditnotes." totaldeposts=".$totaldeposits;
3538
3539
                    // We can also use bcadd to avoid pb with floating points
3540
                    // For example print 239.2 - 229.3 - 9.9; does not return 0.
3541
                    //$resteapayer=bcadd($this->total_ttc,$totalpaye,$conf->global->MAIN_MAX_DECIMALS_TOT);
3542
                    //$resteapayer=bcadd($resteapayer,$totalavoir,$conf->global->MAIN_MAX_DECIMALS_TOT);
3543
					if (empty($amount)) $amount = price2num($this->total_ttc - $totalpaye - $totalcreditnotes - $totaldeposits,'MT');
3544
3545
					if (is_numeric($amount) && $amount != 0)
3546
					{
3547
						$sql = 'INSERT INTO '.MAIN_DB_PREFIX.'prelevement_facture_demande';
3548
						$sql .= ' (fk_facture, amount, date_demande, fk_user_demande, code_banque, code_guichet, number, cle_rib)';
3549
						$sql .= ' VALUES ('.$this->id;
3550
						$sql .= ",'".price2num($amount)."'";
3551
						$sql .= ",'".$this->db->idate($now)."'";
3552
						$sql .= ",".$fuser->id;
3553
						$sql .= ",'".$bac->code_banque."'";
3554
						$sql .= ",'".$bac->code_guichet."'";
3555
						$sql .= ",'".$bac->number."'";
3556
						$sql .= ",'".$bac->cle_rib."')";
3557
3558
						dol_syslog(get_class($this)."::demande_prelevement", LOG_DEBUG);
3559
						$resql=$this->db->query($sql);
3560
						if (! $resql)
3561
						{
3562
						    $this->error=$this->db->lasterror();
3563
						    dol_syslog(get_class($this).'::demandeprelevement Erreur');
3564
						    $error++;
3565
						}
3566
					}
3567
					else
3568
					{
3569
						$this->error='WithdrawRequestErrorNilAmount';
3570
	                    dol_syslog(get_class($this).'::demandeprelevement WithdrawRequestErrorNilAmount');
3571
	                    $error++;
3572
					}
3573
3574
        			if (! $error)
3575
        			{
3576
        				// Force payment mode of invoice to withdraw
3577
        				$payment_mode_id = dol_getIdFromCode($this->db, 'PRE', 'c_paiement');
3578
        				if ($payment_mode_id > 0)
3579
        				{
3580
        					$result=$this->setPaymentMethods($payment_mode_id);
3581
        				}
3582
        			}
3583
3584
                    if ($error) return -1;
3585
                    return 1;
3586
                }
3587
                else
3588
                {
3589
                    $this->error="A request already exists";
3590
                    dol_syslog(get_class($this).'::demandeprelevement Impossible de creer une demande, demande deja en cours');
3591
                    return 0;
3592
                }
3593
            }
3594
            else
3595
            {
3596
                $this->error=$this->db->error();
3597
                dol_syslog(get_class($this).'::demandeprelevement Erreur -2');
3598
                return -2;
3599
            }
3600
        }
3601
        else
3602
        {
3603
            $this->error="Status of invoice does not allow this";
3604
            dol_syslog(get_class($this)."::demandeprelevement ".$this->error." $this->statut, $this->paye, $this->mode_reglement_id");
3605
            return -3;
3606
        }
3607
    }
3608
3609
	/**
3610
	 *  Supprime une demande de prelevement
3611
	 *
3612
	 *  @param  User	$fuser      User making delete
3613
	 *  @param  int		$did        id de la demande a supprimer
3614
	 *  @return	int					<0 if OK, >0 if KO
3615
	 */
3616
	function demande_prelevement_delete($fuser, $did)
3617
	{
3618
		$sql = 'DELETE FROM '.MAIN_DB_PREFIX.'prelevement_facture_demande';
3619
		$sql .= ' WHERE rowid = '.$did;
3620
		$sql .= ' AND traite = 0';
3621
		if ( $this->db->query($sql) )
3622
		{
3623
			return 0;
3624
		}
3625
		else
3626
		{
3627
			$this->error=$this->db->lasterror();
3628
			dol_syslog(get_class($this).'::demande_prelevement_delete Error '.$this->error);
3629
			return -1;
3630
		}
3631
	}
3632
3633
3634
	/**
3635
	 *	Load indicators for dashboard (this->nbtodo and this->nbtodolate)
3636
	 *
3637
	 *	@param  User		$user    	Object user
3638
	 *	@return WorkboardResponse|int 	<0 if KO, WorkboardResponse if OK
3639
	 */
3640
	function load_board($user)
3641
	{
3642
		global $conf, $langs;
3643
3644
		$clause = " WHERE";
3645
3646
		$sql = "SELECT f.rowid, f.date_lim_reglement as datefin,f.fk_statut";
3647
		$sql.= " FROM ".MAIN_DB_PREFIX."facture as f";
3648
		if (!$user->rights->societe->client->voir && !$user->societe_id)
3649
		{
3650
			$sql.= " LEFT JOIN ".MAIN_DB_PREFIX."societe_commerciaux as sc ON f.fk_soc = sc.fk_soc";
3651
			$sql.= " WHERE sc.fk_user = " .$user->id;
3652
			$clause = " AND";
3653
		}
3654
		$sql.= $clause." f.paye=0";
3655
		$sql.= " AND f.entity = ".$conf->entity;
3656
		$sql.= " AND f.fk_statut = ".self::STATUS_VALIDATED;
3657
		if ($user->societe_id) $sql.= " AND f.fk_soc = ".$user->societe_id;
3658
3659
		$resql=$this->db->query($sql);
3660
		if ($resql)
3661
		{
3662
			$langs->load("bills");
3663
			$now=dol_now();
3664
3665
			$response = new WorkboardResponse();
3666
			$response->warning_delay=$conf->facture->client->warning_delay/60/60/24;
3667
			$response->label=$langs->trans("CustomerBillsUnpaid");
3668
			$response->url=DOL_URL_ROOT.'/compta/facture/list.php?search_status=1&mainmenu=accountancy&leftmenu=customers_bills';
3669
			$response->img=img_object($langs->trans("Bills"),"bill");
3670
3671
			$generic_facture = new Facture($this->db);
3672
3673
			while ($obj=$this->db->fetch_object($resql))
3674
			{
3675
				$generic_facture->date_lim_reglement = $this->db->jdate($obj->datefin);
3676
				$generic_facture->statut = $obj->fk_statut;
3677
3678
				$response->nbtodo++;
3679
3680
				if ($generic_facture->hasDelay()) {
3681
					$response->nbtodolate++;
3682
				}
3683
			}
3684
3685
			return $response;
3686
		}
3687
		else
3688
		{
3689
			dol_print_error($this->db);
3690
			$this->error=$this->db->error();
3691
			return -1;
3692
		}
3693
	}
3694
3695
3696
	/* gestion des contacts d'une facture */
3697
3698
	/**
3699
	 *	Retourne id des contacts clients de facturation
3700
	 *
3701
	 *	@return     array       Liste des id contacts facturation
3702
	 */
3703
	function getIdBillingContact()
3704
	{
3705
		return $this->getIdContact('external','BILLING');
3706
	}
3707
3708
	/**
3709
	 *	Retourne id des contacts clients de livraison
3710
	 *
3711
	 *	@return     array       Liste des id contacts livraison
3712
	 */
3713
	function getIdShippingContact()
3714
	{
3715
		return $this->getIdContact('external','SHIPPING');
3716
	}
3717
3718
3719
	/**
3720
	 *  Initialise an instance with random values.
3721
	 *  Used to build previews or test instances.
3722
	 *	id must be 0 if object instance is a specimen.
3723
	 *
3724
	 *	@param	string		$option		''=Create a specimen invoice with lines, 'nolines'=No lines
3725
	 *  @return	void
3726
	 */
3727
	function initAsSpecimen($option='')
3728
	{
3729
		global $langs;
3730
3731
		$now=dol_now();
3732
		$arraynow=dol_getdate($now);
3733
		$nownotime=dol_mktime(0, 0, 0, $arraynow['mon'], $arraynow['mday'], $arraynow['year']);
3734
3735
        // Load array of products prodids
3736
		$num_prods = 0;
3737
		$prodids = array();
3738
		$sql = "SELECT rowid";
3739
		$sql.= " FROM ".MAIN_DB_PREFIX."product";
3740
		$sql.= " WHERE entity IN (".getEntity('product', 1).")";
3741
		$resql = $this->db->query($sql);
3742
		if ($resql)
3743
		{
3744
			$num_prods = $this->db->num_rows($resql);
3745
			$i = 0;
3746
			while ($i < $num_prods)
3747
			{
3748
				$i++;
3749
				$row = $this->db->fetch_row($resql);
3750
				$prodids[$i] = $row[0];
3751
			}
3752
		}
3753
		//Avoid php warning Warning: mt_rand(): max(0) is smaller than min(1) when no product exists
3754
		if (empty($num_prods)) {
3755
			$num_prods=1;
3756
		}
3757
3758
		// Initialize parameters
3759
		$this->id=0;
3760
		$this->ref = 'SPECIMEN';
3761
		$this->specimen=1;
3762
		$this->socid = 1;
3763
		$this->date = $nownotime;
3764
		$this->date_lim_reglement = $nownotime + 3600 * 24 *30;
3765
		$this->cond_reglement_id   = 1;
3766
		$this->cond_reglement_code = 'RECEP';
3767
		$this->date_lim_reglement=$this->calculate_date_lim_reglement();
3768
		$this->mode_reglement_id   = 0;		// Not forced to show payment mode CHQ + VIR
3769
		$this->mode_reglement_code = '';	// Not forced to show payment mode CHQ + VIR
3770
		$this->note_public='This is a comment (public)';
3771
		$this->note_private='This is a comment (private)';
3772
		$this->note='This is a comment (private)';
3773
		$this->fk_incoterms=0;
3774
		$this->location_incoterms='';
3775
3776
		if (empty($option) || $option != 'nolines')
3777
		{
3778
			// Lines
3779
			$nbp = 5;
3780
			$xnbp = 0;
3781
			while ($xnbp < $nbp)
3782
			{
3783
				$line=new FactureLigne($this->db);
3784
				$line->desc=$langs->trans("Description")." ".$xnbp;
3785
				$line->qty=1;
3786
				$line->subprice=100;
3787
				$line->tva_tx=19.6;
3788
				$line->localtax1_tx=0;
3789
				$line->localtax2_tx=0;
3790
				$line->remise_percent=0;
3791
				if ($xnbp == 1)        // Qty is negative (product line)
3792
				{
3793
					$prodid = mt_rand(1, $num_prods);
3794
					$line->fk_product=$prodids[$prodid];
3795
					$line->qty=-1;
3796
					$line->total_ht=-100;
3797
					$line->total_ttc=-119.6;
3798
					$line->total_tva=-19.6;
3799
				}
3800
				else if ($xnbp == 2)    // UP is negative (free line)
3801
				{
3802
					$line->subprice=-100;
3803
					$line->total_ht=-100;
3804
					$line->total_ttc=-119.6;
3805
					$line->total_tva=-19.6;
3806
					$line->remise_percent=0;
3807
				}
3808
				else if ($xnbp == 3)    // Discount is 50% (product line)
3809
				{
3810
					$prodid = mt_rand(1, $num_prods);
3811
					$line->fk_product=$prodids[$prodid];
3812
					$line->total_ht=50;
3813
					$line->total_ttc=59.8;
3814
					$line->total_tva=9.8;
3815
					$line->remise_percent=50;
3816
				}
3817
				else    // (product line)
3818
				{
3819
					$prodid = mt_rand(1, $num_prods);
3820
					$line->fk_product=$prodids[$prodid];
3821
					$line->total_ht=100;
3822
					$line->total_ttc=119.6;
3823
					$line->total_tva=19.6;
3824
					$line->remise_percent=00;
3825
				}
3826
3827
				$this->lines[$xnbp]=$line;
3828
				$xnbp++;
3829
3830
				$this->total_ht       += $line->total_ht;
3831
				$this->total_tva      += $line->total_tva;
3832
				$this->total_ttc      += $line->total_ttc;
3833
			}
3834
			$this->revenuestamp = 0;
3835
3836
			// Add a line "offered"
3837
			$line=new FactureLigne($this->db);
3838
			$line->desc=$langs->trans("Description")." (offered line)";
3839
			$line->qty=1;
3840
			$line->subprice=100;
3841
			$line->tva_tx=19.6;
3842
			$line->localtax1_tx=0;
3843
			$line->localtax2_tx=0;
3844
			$line->remise_percent=100;
3845
			$line->total_ht=0;
3846
			$line->total_ttc=0;    // 90 * 1.196
3847
			$line->total_tva=0;
3848
			$prodid = mt_rand(1, $num_prods);
3849
			$line->fk_product=$prodids[$prodid];
3850
3851
			$this->lines[$xnbp]=$line;
3852
			$xnbp++;
3853
		}
3854
	}
3855
3856
	/**
3857
	 *      Load indicators for dashboard (this->nbtodo and this->nbtodolate)
3858
	 *
3859
	 *      @return         int     <0 if KO, >0 if OK
3860
	 */
3861
	function load_state_board()
3862
	{
3863
		global $conf, $user;
3864
3865
		$this->nb=array();
3866
3867
		$clause = "WHERE";
3868
3869
		$sql = "SELECT count(f.rowid) as nb";
3870
		$sql.= " FROM ".MAIN_DB_PREFIX."facture as f";
3871
		$sql.= " LEFT JOIN ".MAIN_DB_PREFIX."societe as s ON f.fk_soc = s.rowid";
3872
		if (!$user->rights->societe->client->voir && !$user->societe_id)
3873
		{
3874
			$sql.= " LEFT JOIN ".MAIN_DB_PREFIX."societe_commerciaux as sc ON s.rowid = sc.fk_soc";
3875
			$sql.= " WHERE sc.fk_user = " .$user->id;
3876
			$clause = "AND";
3877
		}
3878
		$sql.= " ".$clause." f.entity = ".$conf->entity;
3879
3880
		$resql=$this->db->query($sql);
3881
		if ($resql)
3882
		{
3883
			while ($obj=$this->db->fetch_object($resql))
3884
			{
3885
				$this->nb["invoices"]=$obj->nb;
3886
			}
3887
            $this->db->free($resql);
3888
			return 1;
3889
		}
3890
		else
3891
		{
3892
			dol_print_error($this->db);
3893
			$this->error=$this->db->error();
3894
			return -1;
3895
		}
3896
	}
3897
3898
	/**
3899
	 * 	Create an array of invoice lines
3900
	 *
3901
	 * 	@return int		>0 if OK, <0 if KO
3902
	 */
3903
	function getLinesArray()
3904
	{
3905
	    return $this->fetch_lines();
3906
	}
3907
3908
	/**
3909
	 *  Create a document onto disk according to template module.
3910
	 *
3911
	 *	@param	string		$modele			Generator to use. Caller must set it to obj->modelpdf or GETPOST('modelpdf') for example.
3912
	 *	@param	Translate	$outputlangs	objet lang a utiliser pour traduction
3913
	 *  @param  int			$hidedetails    Hide details of lines
3914
	 *  @param  int			$hidedesc       Hide description
3915
	 *  @param  int			$hideref        Hide ref
3916
	 *	@return int        					<0 if KO, >0 if OK
3917
	 */
3918
	public function generateDocument($modele, $outputlangs, $hidedetails=0, $hidedesc=0, $hideref=0)
3919
	{
3920
		global $conf,$langs;
3921
3922
		$langs->load("bills");
3923
3924
		if (! dol_strlen($modele)) {
3925
3926
			$modele = 'crabe';
3927
3928
			if ($this->modelpdf) {
3929
				$modele = $this->modelpdf;
3930
			} elseif (! empty($conf->global->FACTURE_ADDON_PDF)) {
3931
				$modele = $conf->global->FACTURE_ADDON_PDF;
3932
			}
3933
		}
3934
3935
		$modelpath = "core/modules/facture/doc/";
3936
3937
		return $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref);
3938
	}
3939
3940
	/**
3941
	 * Gets the smallest reference available for a new cycle
3942
	 *
3943
	 * @return int >= 1 if OK, -1 if error
3944
	 */
3945
	function newCycle()
3946
	{
3947
		$sql = 'SELECT max(situation_cycle_ref) FROM ' . MAIN_DB_PREFIX . 'facture as f';
3948
		$sql.= " WHERE f.entity in (".getEntity('facture').")";
3949
		$resql = $this->db->query($sql);
3950
		if ($resql) {
3951
			if ($resql->num_rows > 0)
3952
			{
3953
				$res = $this->db->fetch_array($resql);
3954
				$ref = $res['max(situation_cycle_ref)'];
3955
				$ref++;
3956
			} else {
3957
				$ref = 1;
3958
			}
3959
			$this->db->free($resql);
3960
			return $ref;
3961
		} else {
3962
			$this->error = $this->db->lasterror();
3963
			dol_syslog("Error sql=" . $sql . ", error=" . $this->error, LOG_ERR);
3964
			return -1;
3965
		}
3966
	}
3967
3968
	/**
3969
	 * Checks if the invoice is the first of a cycle
3970
	 *
3971
	 * @return boolean
3972
	 */
3973
	function is_first()
3974
	{
3975
		return ($this->situation_counter == 1);
3976
	}
3977
3978
	/**
3979
	 * Returns an array containing the previous situations as Facture objects
3980
	 *
3981
	 * @return mixed -1 if error, array of previous situations
3982
	 */
3983
	function get_prev_sits()
3984
	{
3985
		global $conf;
3986
3987
		$sql = 'SELECT rowid FROM ' . MAIN_DB_PREFIX . 'facture';
3988
		$sql .= ' where situation_cycle_ref = ' . $this->situation_cycle_ref;
3989
		$sql .= ' and situation_counter < ' . $this->situation_counter;
3990
		$sql .= ' AND entity = '. ($this->entity > 0 ? $this->entity : $conf->entity);
3991
		$resql = $this->db->query($sql);
3992
		$res = array();
3993
		if ($resql && $resql->num_rows > 0) {
3994
			while ($row = $this->db->fetch_object($resql)) {
3995
				$id = $row->rowid;
3996
				$situation = new Facture($this->db);
3997
				$situation->fetch($id);
3998
				$res[] = $situation;
3999
			}
4000
		} else {
4001
			$this->error = $this->db->error();
4002
			dol_syslog("Error sql=" . $sql . ", error=" . $this->error, LOG_ERR);
4003
			return -1;
4004
		}
4005
4006
		return $res;
4007
	}
4008
4009
	/**
4010
	 * Sets the invoice as a final situation
4011
	 *
4012
	 *  @param  	User	$user    	Object user
4013
	 *  @param     	int		$notrigger	1=Does not execute triggers, 0= execute triggers
4014
	 *	@return		int 				<0 if KO, >0 if OK
4015
	 */
4016
	function setFinal(User $user, $notrigger=0)
4017
	{
4018
		$error=0;
4019
4020
		$this->db->begin();
4021
4022
		$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...
4023
		$sql = 'UPDATE ' . MAIN_DB_PREFIX . 'facture SET situation_final = ' . $this->situation_final . ' where rowid = ' . $this->id;
4024
4025
		dol_syslog(__METHOD__, LOG_DEBUG);
4026
		$resql=$this->db->query($sql);
4027
		if (!$resql)
4028
		{
4029
			$this->errors[]=$this->db->error();
4030
			$error++;
4031
		}
4032
4033
		if (! $notrigger && empty($error))
4034
		{
4035
			// Call trigger
4036
			$result=$this->call_trigger('BILL_MODIFY',$user);
4037
			if ($result < 0) $error++;
4038
			// End call triggers
4039
		}
4040
4041
		if (! $error)
4042
		{
4043
			$this->db->commit();
4044
			return 1;
4045
		}
4046
		else
4047
		{
4048
			foreach($this->errors as $errmsg)
4049
			{
4050
				dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
4051
				$this->error.=($this->error?', '.$errmsg:$errmsg);
4052
			}
4053
			$this->db->rollback();
4054
			return -1*$error;
4055
		}
4056
	}
4057
4058
	/**
4059
	 * Checks if the invoice is the last in its cycle
4060
	 *
4061
	 * @return bool Last of the cycle status
4062
	 *
4063
	 */
4064
	function is_last_in_cycle()
4065
	{
4066
		global $conf;
4067
4068
		if (!empty($this->situation_cycle_ref)) {
4069
			// No point in testing anything if we're not inside a cycle
4070
			$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);
4071
			$resql = $this->db->query($sql);
4072
4073
			if ($resql && $resql->num_rows > 0) {
4074
				$res = $this->db->fetch_array($resql);
4075
				$last = $res['max(situation_counter)'];
4076
				return ($last == $this->situation_counter);
4077
			} else {
4078
				$this->error = $this->db->lasterror();
4079
				dol_syslog(get_class($this) . "::select Error " . $this->error, LOG_ERR);
4080
				return false;
4081
			}
4082
		} else {
4083
			return true;
4084
		}
4085
	}
4086
4087
	/**
4088
	 * Function used to replace a thirdparty id with another one.
4089
	 *
4090
	 * @param DoliDB $db Database handler
4091
	 * @param int $origin_id Old thirdparty id
4092
	 * @param int $dest_id New thirdparty id
4093
	 * @return bool
4094
	 */
4095
	public static function replaceThirdparty(DoliDB $db, $origin_id, $dest_id)
4096
	{
4097
		$tables = array(
4098
			'facture'
4099
		);
4100
4101
		return CommonObject::commonReplaceThirdparty($db, $origin_id, $dest_id, $tables);
4102
	}
4103
4104
	/**
4105
	 * Is the customer invoice delayed?
4106
	 *
4107
	 * @return bool
4108
	 */
4109
	public function hasDelay()
4110
	{
4111
		global $conf;
4112
4113
		$now = dol_now();
4114
4115
		// Paid invoices have status STATUS_CLOSED
4116
		if ($this->statut != Facture::STATUS_VALIDATED) return false;
4117
4118
		return $this->date_lim_reglement < ($now - $conf->facture->client->warning_delay);
4119
	}
4120
}
4121
4122
/**
4123
 *	Class to manage invoice lines.
4124
 *  Saved into database table llx_facturedet
4125
 */
4126
class FactureLigne extends CommonInvoiceLine
4127
{
4128
    public $element='facturedet';
4129
    public $table_element='facturedet';
4130
4131
	var $oldline;
4132
4133
	//! From llx_facturedet
4134
	//! Id facture
4135
	var $fk_facture;
4136
	//! Id parent line
4137
	var $fk_parent_line;
4138
	/**
4139
	 * @deprecated
4140
	 */
4141
	var $label;
4142
	//! Description ligne
4143
	var $desc;
4144
4145
	var $localtax1_type;	// Local tax 1 type
4146
	var $localtax2_type;	// Local tax 2 type
4147
	var $fk_remise_except;	// Link to line into llx_remise_except
4148
	var $rang = 0;
4149
4150
	var $fk_fournprice;
4151
	var $pa_ht;
4152
	var $marge_tx;
4153
	var $marque_tx;
4154
4155
	var $special_code;	// Liste d'options non cumulabels:
4156
	// 1: frais de port
4157
	// 2: ecotaxe
4158
	// 3: ??
4159
4160
	var $origin;
4161
	var $origin_id;
4162
4163
	var $fk_code_ventilation = 0;
4164
4165
	var $date_start;
4166
	var $date_end;
4167
4168
	// Ne plus utiliser
4169
	//var $price;         	// P.U. HT apres remise % de ligne (exemple 80)
4170
	//var $remise;			// Montant calcule de la remise % sur PU HT (exemple 20)
4171
4172
	// From llx_product
4173
	/**
4174
	 * @deprecated
4175
	 * @see product_ref
4176
	 */
4177
	var $ref;				// Product ref (deprecated)
4178
	var $product_ref;       // Product ref
4179
	/**
4180
	 * @deprecated
4181
	 * @see product_label
4182
	 */
4183
	var $libelle;      		// Product label (deprecated)
4184
	var $product_label;     // Product label
4185
	var $product_desc;  	// Description produit
4186
4187
	var $skip_update_total; // Skip update price total for special lines
4188
4189
	/**
4190
	 * @var int Situation advance percentage
4191
	 */
4192
	public $situation_percent;
4193
4194
	/**
4195
	 * @var int Previous situation line id reference
4196
	 */
4197
	public $fk_prev_id;
4198
4199
	// Multicurrency
4200
	var $fk_multicurrency;
4201
	var $multicurrency_code;
4202
	var $multicurrency_subprice;
4203
	var $multicurrency_total_ht;
4204
	var $multicurrency_total_tva;
4205
	var $multicurrency_total_ttc;
4206
4207
	/**
4208
	 *	Load invoice line from database
4209
	 *
4210
	 *	@param	int		$rowid      id of invoice line to get
4211
	 *	@return	int					<0 if KO, >0 if OK
4212
	 */
4213
	function fetch($rowid)
4214
	{
4215
		$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.tva_tx,';
4216
		$sql.= ' fd.localtax1_tx, fd. localtax2_tx, fd.remise, fd.remise_percent, fd.fk_remise_except, fd.subprice,';
4217
		$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,';
4218
		$sql.= ' fd.info_bits, fd.special_code, fd.total_ht, fd.total_tva, fd.total_ttc, fd.total_localtax1, fd.total_localtax2, fd.rang,';
4219
		$sql.= ' fd.fk_code_ventilation,';
4220
		$sql.= ' fd.fk_unit, fd.fk_user_author, fd.fk_user_modif,';
4221
		$sql.= ' fd.situation_percent, fd.fk_prev_id,';
4222
		$sql.= ' fd.multicurrency_subprice,';
4223
		$sql.= ' fd.multicurrency_total_ht,';
4224
		$sql.= ' fd.multicurrency_total_tva,';
4225
		$sql.= ' fd.multicurrency_total_ttc,';
4226
		$sql.= ' p.ref as product_ref, p.label as product_libelle, p.description as product_desc';
4227
		$sql.= ' FROM '.MAIN_DB_PREFIX.'facturedet as fd';
4228
		$sql.= ' LEFT JOIN '.MAIN_DB_PREFIX.'product as p ON fd.fk_product = p.rowid';
4229
		$sql.= ' WHERE fd.rowid = '.$rowid;
4230
4231
		$result = $this->db->query($sql);
4232
		if ($result)
4233
		{
4234
			$objp = $this->db->fetch_object($result);
4235
4236
			$this->rowid				= $objp->rowid;
4237
			$this->fk_facture			= $objp->fk_facture;
4238
			$this->fk_parent_line		= $objp->fk_parent_line;
4239
			$this->label				= $objp->custom_label;
4240
			$this->desc					= $objp->description;
4241
			$this->qty					= $objp->qty;
4242
			$this->subprice				= $objp->subprice;
4243
			$this->tva_tx				= $objp->tva_tx;
4244
			$this->localtax1_tx			= $objp->localtax1_tx;
4245
			$this->localtax2_tx			= $objp->localtax2_tx;
4246
			$this->remise_percent		= $objp->remise_percent;
4247
			$this->fk_remise_except		= $objp->fk_remise_except;
4248
			$this->fk_product			= $objp->fk_product;
4249
			$this->product_type			= $objp->product_type;
4250
			$this->date_start			= $this->db->jdate($objp->date_start);
4251
			$this->date_end				= $this->db->jdate($objp->date_end);
4252
			$this->info_bits			= $objp->info_bits;
4253
			$this->special_code			= $objp->special_code;
4254
			$this->total_ht				= $objp->total_ht;
4255
			$this->total_tva			= $objp->total_tva;
4256
			$this->total_localtax1		= $objp->total_localtax1;
4257
			$this->total_localtax2		= $objp->total_localtax2;
4258
			$this->total_ttc			= $objp->total_ttc;
4259
			$this->fk_code_ventilation	= $objp->fk_code_ventilation;
4260
			$this->rang					= $objp->rang;
4261
			$this->fk_fournprice		= $objp->fk_fournprice;
4262
			$marginInfos				= getMarginInfos($objp->subprice, $objp->remise_percent, $objp->tva_tx, $objp->localtax1_tx, $objp->localtax2_tx, $this->fk_fournprice, $objp->pa_ht);
4263
			$this->pa_ht				= $marginInfos[0];
4264
			$this->marge_tx				= $marginInfos[1];
4265
			$this->marque_tx			= $marginInfos[2];
4266
4267
			$this->ref					= $objp->product_ref;      // deprecated
4268
			$this->product_ref			= $objp->product_ref;
4269
			$this->libelle				= $objp->product_libelle;  // deprecated
4270
			$this->product_label		= $objp->product_libelle;
4271
			$this->product_desc			= $objp->product_desc;
4272
			$this->fk_unit				= $objp->fk_unit;
4273
			$this->fk_user_modif		= $objp->fk_user_modif;
4274
			$this->fk_user_author		= $objp->fk_user_author;
4275
			
4276
			$this->situation_percent    = $objp->situation_percent;
4277
			$this->fk_prev_id           = $objp->fk_prev_id;
4278
4279
			$this->multicurrency_subprice = $objp->multicurrency_subprice;
4280
			$this->multicurrency_total_ht = $objp->multicurrency_total_ht;
4281
			$this->multicurrency_total_tva= $objp->multicurrency_total_tva;
4282
			$this->multicurrency_total_ttc= $objp->multicurrency_total_ttc;
4283
4284
			$this->db->free($result);
4285
4286
			return 1;
4287
		}
4288
		else
4289
		{
4290
		    $this->error = $this->db->lasterror();
4291
			return -1;
4292
		}
4293
	}
4294
4295
	/**
4296
	 *	Insert line into database
4297
	 *
4298
	 *	@param      int		$notrigger		1 no triggers
4299
	 *	@return		int						<0 if KO, >0 if OK
4300
	 */
4301
	function insert($notrigger=0)
4302
	{
4303
		global $langs,$user,$conf;
4304
4305
		$error=0;
4306
4307
        $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'.
4308
4309
        dol_syslog(get_class($this)."::insert rang=".$this->rang, LOG_DEBUG);
4310
4311
		// Clean parameters
4312
		$this->desc=trim($this->desc);
4313
		if (empty($this->tva_tx)) $this->tva_tx=0;
4314
		if (empty($this->localtax1_tx)) $this->localtax1_tx=0;
4315
		if (empty($this->localtax2_tx)) $this->localtax2_tx=0;
4316
		if (empty($this->localtax1_type)) $this->localtax1_type=0;
4317
		if (empty($this->localtax2_type)) $this->localtax2_type=0;
4318
		if (empty($this->total_localtax1)) $this->total_localtax1=0;
4319
		if (empty($this->total_localtax2)) $this->total_localtax2=0;
4320
		if (empty($this->rang)) $this->rang=0;
4321
		if (empty($this->remise_percent)) $this->remise_percent=0;
4322
		if (empty($this->info_bits)) $this->info_bits=0;
4323
		if (empty($this->subprice)) $this->subprice=0;
4324
		if (empty($this->special_code)) $this->special_code=0;
4325
		if (empty($this->fk_parent_line)) $this->fk_parent_line=0;
4326
		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...
4327
		if (! isset($this->situation_percent) || $this->situation_percent > 100 || (string) $this->situation_percent == '') $this->situation_percent = 100;
4328
4329
		if (empty($this->pa_ht)) $this->pa_ht=0;
4330
		if (empty($this->multicurrency_subprice)) $this->multicurrency_subprice=0;
4331
		if (empty($this->multicurrency_total_ht)) $this->multicurrency_total_ht=0;
4332
		if (empty($this->multicurrency_total_tva)) $this->multicurrency_total_tva=0;
4333
		if (empty($this->multicurrency_total_ttc)) $this->multicurrency_total_ttc=0;
4334
4335
		// if buy price not defined, define buyprice as configured in margin admin
4336
		if ($this->pa_ht == 0 && $pa_ht_isemptystring)
4337
		{
4338
			if (($result = $this->defineBuyPrice($this->subprice, $this->remise_percent, $this->fk_product)) < 0)
4339
			{
4340
				return $result;
4341
			}
4342
			else
4343
			{
4344
				$this->pa_ht = $result;
4345
			}
4346
		}
4347
4348
		// Check parameters
4349
		if ($this->product_type < 0)
4350
		{
4351
			$this->error='ErrorProductTypeMustBe0orMore';
4352
			return -1;
4353
		}
4354
		if (! empty($this->fk_product))
4355
		{
4356
			// Check product exists
4357
			$result=Product::isExistingObject('product', $this->fk_product);
4358
			if ($result <= 0)
4359
			{
4360
				$this->error='ErrorProductIdDoesNotExists';
4361
				return -1;
4362
			}
4363
		}
4364
4365
		$this->db->begin();
4366
4367
		// Insertion dans base de la ligne
4368
		$sql = 'INSERT INTO '.MAIN_DB_PREFIX.'facturedet';
4369
		$sql.= ' (fk_facture, fk_parent_line, label, description, qty,';
4370
		$sql.= ' vat_src_code, tva_tx, localtax1_tx, localtax2_tx, localtax1_type, localtax2_type,';
4371
		$sql.= ' fk_product, product_type, remise_percent, subprice, fk_remise_except,';
4372
		$sql.= ' date_start, date_end, fk_code_ventilation, ';
4373
		$sql.= ' rang, special_code, fk_product_fournisseur_price, buy_price_ht,';
4374
		$sql.= ' info_bits, total_ht, total_tva, total_ttc, total_localtax1, total_localtax2,';
4375
		$sql.= ' situation_percent, fk_prev_id,';
4376
		$sql.= ' fk_unit, fk_user_author, fk_user_modif,';
4377
		$sql.= ' fk_multicurrency, multicurrency_code, multicurrency_subprice, multicurrency_total_ht, multicurrency_total_tva, multicurrency_total_ttc';
4378
		$sql.= ')';
4379
		$sql.= " VALUES (".$this->fk_facture.",";
4380
		$sql.= " ".($this->fk_parent_line>0?"'".$this->fk_parent_line."'":"null").",";
4381
		$sql.= " ".(! empty($this->label)?"'".$this->db->escape($this->label)."'":"null").",";
4382
		$sql.= " '".$this->db->escape($this->desc)."',";
4383
		$sql.= " ".price2num($this->qty).",";
4384
        $sql.= " ".(empty($this->vat_src_code)?"''":"'".$this->vat_src_code."'").",";
4385
		$sql.= " ".price2num($this->tva_tx).",";
4386
		$sql.= " ".price2num($this->localtax1_tx).",";
4387
		$sql.= " ".price2num($this->localtax2_tx).",";
4388
		$sql.= " '".$this->localtax1_type."',";
4389
		$sql.= " '".$this->localtax2_type."',";
4390
		$sql.= ' '.(! empty($this->fk_product)?$this->fk_product:"null").',';
4391
		$sql.= " ".$this->product_type.",";
4392
		$sql.= " ".price2num($this->remise_percent).",";
4393
		$sql.= " ".price2num($this->subprice).",";
4394
		$sql.= ' '.(! empty($this->fk_remise_except)?$this->fk_remise_except:"null").',';
4395
		$sql.= " ".(! empty($this->date_start)?"'".$this->db->idate($this->date_start)."'":"null").",";
4396
		$sql.= " ".(! empty($this->date_end)?"'".$this->db->idate($this->date_end)."'":"null").",";
4397
		$sql.= ' '.$this->fk_code_ventilation.',';
4398
		$sql.= ' '.$this->rang.',';
4399
		$sql.= ' '.$this->special_code.',';
4400
		$sql.= ' '.(! empty($this->fk_fournprice)?$this->fk_fournprice:"null").',';
4401
		$sql.= ' '.price2num($this->pa_ht).',';
4402
		$sql.= " '".$this->info_bits."',";
4403
		$sql.= " ".price2num($this->total_ht).",";
4404
		$sql.= " ".price2num($this->total_tva).",";
4405
		$sql.= " ".price2num($this->total_ttc).",";
4406
		$sql.= " ".price2num($this->total_localtax1).",";
4407
		$sql.= " ".price2num($this->total_localtax2);
4408
		$sql.= ", " . $this->situation_percent;
4409
		$sql.= ", " . $this->fk_prev_id;
4410
		$sql.= ", ".(!$this->fk_unit ? 'NULL' : $this->fk_unit);
4411
		$sql.= ", ".$user->id;
4412
		$sql.= ", ".$user->id;
4413
		$sql.= ", ".(int) $this->fk_multicurrency;
4414
		$sql.= ", '".$this->db->escape($this->multicurrency_code)."'";
4415
		$sql.= ", ".price2num($this->multicurrency_subprice);
4416
		$sql.= ", ".price2num($this->multicurrency_total_ht);
4417
		$sql.= ", ".price2num($this->multicurrency_total_tva);
4418
		$sql.= ", ".price2num($this->multicurrency_total_ttc);
4419
		$sql.= ')';
4420
4421
		dol_syslog(get_class($this)."::insert", LOG_DEBUG);
4422
		$resql=$this->db->query($sql);
4423
		if ($resql)
4424
		{
4425
			$this->rowid=$this->db->last_insert_id(MAIN_DB_PREFIX.'facturedet');
4426
4427
            if (empty($conf->global->MAIN_EXTRAFIELDS_DISABLED)) // For avoid conflicts if trigger used
4428
            {
4429
            	$this->id=$this->rowid;
4430
            	$result=$this->insertExtraFields();
4431
            	if ($result < 0)
4432
            	{
4433
            		$error++;
4434
            	}
4435
            }
4436
4437
			// Si fk_remise_except defini, on lie la remise a la facture
4438
			// ce qui la flague comme "consommee".
4439
			if ($this->fk_remise_except)
4440
			{
4441
				$discount=new DiscountAbsolute($this->db);
4442
				$result=$discount->fetch($this->fk_remise_except);
4443
				if ($result >= 0)
4444
				{
4445
					// Check if discount was found
4446
					if ($result > 0)
4447
					{
4448
						// Check if discount not already affected to another invoice
4449
						if ($discount->fk_facture)
4450
						{
4451
							$this->error=$langs->trans("ErrorDiscountAlreadyUsed",$discount->id);
4452
							dol_syslog(get_class($this)."::insert Error ".$this->error, LOG_ERR);
4453
							$this->db->rollback();
4454
							return -3;
4455
						}
4456
						else
4457
						{
4458
							$result=$discount->link_to_invoice($this->rowid,0);
4459
							if ($result < 0)
4460
							{
4461
								$this->error=$discount->error;
4462
								dol_syslog(get_class($this)."::insert Error ".$this->error, LOG_ERR);
4463
								$this->db->rollback();
4464
								return -3;
4465
							}
4466
						}
4467
					}
4468
					else
4469
					{
4470
						$this->error=$langs->trans("ErrorADiscountThatHasBeenRemovedIsIncluded");
4471
						dol_syslog(get_class($this)."::insert Error ".$this->error, LOG_ERR);
4472
						$this->db->rollback();
4473
						return -3;
4474
					}
4475
				}
4476
				else
4477
				{
4478
					$this->error=$discount->error;
4479
					dol_syslog(get_class($this)."::insert Error ".$this->error, LOG_ERR);
4480
					$this->db->rollback();
4481
					return -3;
4482
				}
4483
			}
4484
4485
			if (! $notrigger)
4486
			{
4487
                // Call trigger
4488
                $result=$this->call_trigger('LINEBILL_INSERT',$user);
4489
                if ($result < 0)
4490
                {
4491
					$this->db->rollback();
4492
					return -2;
4493
				}
4494
                // End call triggers
4495
			}
4496
4497
			$this->db->commit();
4498
			return $this->rowid;
4499
4500
		}
4501
		else
4502
		{
4503
			$this->error=$this->db->error();
4504
			$this->db->rollback();
4505
			return -2;
4506
		}
4507
	}
4508
4509
	/**
4510
	 *	Update line into database
4511
	 *
4512
	 *	@param		User	$user		User object
4513
	 *	@param		int		$notrigger	Disable triggers
4514
	 *	@return		int					<0 if KO, >0 if OK
4515
	 */
4516
	function update($user='',$notrigger=0)
4517
	{
4518
		global $user,$conf;
4519
4520
		$error=0;
4521
4522
		$pa_ht_isemptystring = (empty($this->pa_ht) && $this->pa_ht == ''); // If true, we can use a default value. If this->pa_ht = '0', we must use '0'.
4523
4524
		// Clean parameters
4525
		$this->desc=trim($this->desc);
4526
		if (empty($this->tva_tx)) $this->tva_tx=0;
4527
		if (empty($this->localtax1_tx)) $this->localtax1_tx=0;
4528
		if (empty($this->localtax2_tx)) $this->localtax2_tx=0;
4529
		if (empty($this->localtax1_type)) $this->localtax1_type=0;
4530
		if (empty($this->localtax2_type)) $this->localtax2_type=0;
4531
		if (empty($this->total_localtax1)) $this->total_localtax1=0;
4532
		if (empty($this->total_localtax2)) $this->total_localtax2=0;
4533
		if (empty($this->remise_percent)) $this->remise_percent=0;
4534
		if (empty($this->info_bits)) $this->info_bits=0;
4535
		if (empty($this->special_code)) $this->special_code=0;
4536
		if (empty($this->product_type)) $this->product_type=0;
4537
		if (empty($this->fk_parent_line)) $this->fk_parent_line=0;
4538
		if (! isset($this->situation_percent) || $this->situation_percent > 100 || (string) $this->situation_percent == '') $this->situation_percent = 100;
4539
		if (empty($this->pa_ht)) $this->pa_ht=0;
4540
4541
		if (empty($this->multicurrency_subprice)) $this->multicurrency_subprice=0;
4542
		if (empty($this->multicurrency_total_ht)) $this->multicurrency_total_ht=0;
4543
		if (empty($this->multicurrency_total_tva)) $this->multicurrency_total_tva=0;
4544
		if (empty($this->multicurrency_total_ttc)) $this->multicurrency_total_ttc=0;
4545
4546
		// Check parameters
4547
		if ($this->product_type < 0) return -1;
4548
4549
		// if buy price not defined, define buyprice as configured in margin admin
4550
		if ($this->pa_ht == 0 && $pa_ht_isemptystring)
4551
		{
4552
			if (($result = $this->defineBuyPrice($this->subprice, $this->remise_percent, $this->fk_product)) < 0)
4553
			{
4554
				return $result;
4555
			}
4556
			else
4557
			{
4558
				$this->pa_ht = $result;
4559
			}
4560
		}
4561
4562
		$this->db->begin();
4563
4564
        // Mise a jour ligne en base
4565
        $sql = "UPDATE ".MAIN_DB_PREFIX."facturedet SET";
4566
        $sql.= " description='".$this->db->escape($this->desc)."'";
4567
        $sql.= ", label=".(! empty($this->label)?"'".$this->db->escape($this->label)."'":"null");
4568
        $sql.= ", subprice=".price2num($this->subprice)."";
4569
        $sql.= ", remise_percent=".price2num($this->remise_percent)."";
4570
        if ($this->fk_remise_except) $sql.= ", fk_remise_except=".$this->fk_remise_except;
4571
        else $sql.= ", fk_remise_except=null";
4572
		$sql.= ", vat_src_code = '".(empty($this->vat_src_code)?'':$this->vat_src_code)."'";
4573
        $sql.= ", tva_tx=".price2num($this->tva_tx)."";
4574
        $sql.= ", localtax1_tx=".price2num($this->localtax1_tx)."";
4575
        $sql.= ", localtax2_tx=".price2num($this->localtax2_tx)."";
4576
		$sql.= ", localtax1_type='".$this->localtax1_type."'";
4577
		$sql.= ", localtax2_type='".$this->localtax2_type."'";
4578
        $sql.= ", qty=".price2num($this->qty)."";
4579
        $sql.= ", date_start=".(! empty($this->date_start)?"'".$this->db->idate($this->date_start)."'":"null");
4580
        $sql.= ", date_end=".(! empty($this->date_end)?"'".$this->db->idate($this->date_end)."'":"null");
4581
        $sql.= ", product_type=".$this->product_type;
4582
        $sql.= ", info_bits='".$this->info_bits."'";
4583
        $sql.= ", special_code='".$this->special_code."'";
4584
        if (empty($this->skip_update_total))
4585
        {
4586
        	$sql.= ", total_ht=".price2num($this->total_ht)."";
4587
        	$sql.= ", total_tva=".price2num($this->total_tva)."";
4588
        	$sql.= ", total_ttc=".price2num($this->total_ttc)."";
4589
        	$sql.= ", total_localtax1=".price2num($this->total_localtax1)."";
4590
        	$sql.= ", total_localtax2=".price2num($this->total_localtax2)."";
4591
        }
4592
		$sql.= ", fk_product_fournisseur_price=".(! empty($this->fk_fournprice)?"'".$this->db->escape($this->fk_fournprice)."'":"null");
4593
		$sql.= ", buy_price_ht='".price2num($this->pa_ht)."'";
4594
		$sql.= ", fk_parent_line=".($this->fk_parent_line>0?$this->fk_parent_line:"null");
4595
		if (! empty($this->rang)) $sql.= ", rang=".$this->rang;
4596
		$sql.= ", situation_percent=" . $this->situation_percent;
4597
		$sql.= ", fk_unit=".(!$this->fk_unit ? 'NULL' : $this->fk_unit);
4598
		$sql.= ", fk_user_modif =".$user->id;
4599
4600
		// Multicurrency
4601
		$sql.= ", multicurrency_subprice=".price2num($this->multicurrency_subprice)."";
4602
        $sql.= ", multicurrency_total_ht=".price2num($this->multicurrency_total_ht)."";
4603
        $sql.= ", multicurrency_total_tva=".price2num($this->multicurrency_total_tva)."";
4604
        $sql.= ", multicurrency_total_ttc=".price2num($this->multicurrency_total_ttc)."";
4605
4606
		$sql.= " WHERE rowid = ".$this->rowid;
4607
4608
		dol_syslog(get_class($this)."::update", LOG_DEBUG);
4609
		$resql=$this->db->query($sql);
4610
		if ($resql)
4611
		{
4612
        	if (empty($conf->global->MAIN_EXTRAFIELDS_DISABLED)) // For avoid conflicts if trigger used
4613
        	{
4614
        		$this->id=$this->rowid;
4615
        		$result=$this->insertExtraFields();
4616
        		if ($result < 0)
4617
        		{
4618
        			$error++;
4619
        		}
4620
        	}
4621
4622
			if (! $notrigger)
4623
			{
4624
                // Call trigger
4625
                $result=$this->call_trigger('LINEBILL_UPDATE',$user);
4626
                if ($result < 0)
4627
 				{
4628
					$this->db->rollback();
4629
					return -2;
4630
				}
4631
                // End call triggers
4632
			}
4633
			$this->db->commit();
4634
			return 1;
4635
		}
4636
		else
4637
		{
4638
			$this->error=$this->db->error();
4639
			$this->db->rollback();
4640
			return -2;
4641
		}
4642
	}
4643
4644
	/**
4645
	 * 	Delete line in database
4646
	 *  TODO Add param User $user and notrigger (see skeleton)
4647
     *
4648
	 *	@return	    int		           <0 if KO, >0 if OK
4649
	 */
4650
	function delete()
4651
	{
4652
		global $user;
4653
4654
		$this->db->begin();
4655
4656
		// Call trigger
4657
		$result=$this->call_trigger('LINEBILL_DELETE',$user);
4658
		if ($result < 0)
4659
		{
4660
			$this->db->rollback();
4661
			return -1;
4662
		}
4663
		// End call triggers
4664
4665
4666
		$sql = "DELETE FROM ".MAIN_DB_PREFIX."facturedet WHERE rowid = ".$this->rowid;
4667
		dol_syslog(get_class($this)."::delete", LOG_DEBUG);
4668
		if ($this->db->query($sql) )
4669
		{
4670
			$this->db->commit();
4671
			return 1;
4672
		}
4673
		else
4674
		{
4675
			$this->error=$this->db->error()." sql=".$sql;
4676
			$this->db->rollback();
4677
			return -1;
4678
		}
4679
	}
4680
4681
	/**
4682
	 *  Mise a jour en base des champs total_xxx de ligne de facture
4683
	 *
4684
	 *	@return		int		<0 if KO, >0 if OK
4685
	 */
4686
	function update_total()
4687
	{
4688
		$this->db->begin();
4689
		dol_syslog(get_class($this)."::update_total", LOG_DEBUG);
4690
4691
		// Clean parameters
4692
		if (empty($this->total_localtax1)) $this->total_localtax1=0;
4693
		if (empty($this->total_localtax2)) $this->total_localtax2=0;
4694
4695
		// Mise a jour ligne en base
4696
		$sql = "UPDATE ".MAIN_DB_PREFIX."facturedet SET";
4697
		$sql.= " total_ht=".price2num($this->total_ht)."";
4698
		$sql.= ",total_tva=".price2num($this->total_tva)."";
4699
		$sql.= ",total_localtax1=".price2num($this->total_localtax1)."";
4700
		$sql.= ",total_localtax2=".price2num($this->total_localtax2)."";
4701
		$sql.= ",total_ttc=".price2num($this->total_ttc)."";
4702
		$sql.= " WHERE rowid = ".$this->rowid;
4703
4704
		dol_syslog(get_class($this)."::update_total", LOG_DEBUG);
4705
4706
		$resql=$this->db->query($sql);
4707
		if ($resql)
4708
		{
4709
			$this->db->commit();
4710
			return 1;
4711
		}
4712
		else
4713
		{
4714
			$this->error=$this->db->error();
4715
			$this->db->rollback();
4716
			return -2;
4717
		}
4718
	}
4719
4720
	/**
4721
	 * Returns situation_percent of the previous line.
4722
	 * Warning: If invoice is a replacement invoice, this->fk_prev_id is id of the replaced line.
4723
	 *
4724
	 * @param  int     $invoiceid      Invoice id
4725
	 * @return int                     >= 0
4726
	 */
4727
	function get_prev_progress($invoiceid)
4728
	{
4729
		if (is_null($this->fk_prev_id) || empty($this->fk_prev_id) || $this->fk_prev_id == "") {
4730
			return 0;
4731
		} else {
4732
		    // If invoice is a not a situation invoice, this->fk_prev_id is used for something else
4733
            $tmpinvoice=new Facture($this->db);
4734
            $tmpinvoice->fetch($invoiceid);
4735
            if ($tmpinvoice->type != Facture::TYPE_SITUATION) return 0;
4736
4737
			$sql = 'SELECT situation_percent FROM ' . MAIN_DB_PREFIX . 'facturedet WHERE rowid=' . $this->fk_prev_id;
4738
			$resql = $this->db->query($sql);
4739
			if ($resql && $resql->num_rows > 0) {
4740
				$res = $this->db->fetch_array($resql);
4741
				return $res['situation_percent'];
4742
			} else {
4743
				$this->error = $this->db->error();
4744
				dol_syslog(get_class($this) . "::select Error " . $this->error, LOG_ERR);
4745
				$this->db->rollback();
4746
				return -1;
4747
			}
4748
		}
4749
	}
4750
}
4751