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

htdocs/compta/facture/class/facture.class.php (1 issue)

1
<?php
2
/* Copyright (C) 2002-2007 Rodolphe Quiedeville  <[email protected]>
3
 * Copyright (C) 2004-2013 Laurent Destailleur   <[email protected]>
4
 * Copyright (C) 2004      Sebastien Di Cintio   <[email protected]>
5
 * Copyright (C) 2004      Benoit Mortier        <[email protected]>
6
 * Copyright (C) 2005      Marc Barilley / Ocebo <[email protected]>
7
 * Copyright (C) 2005-2014 Regis Houssin         <[email protected]>
8
 * Copyright (C) 2006      Andre Cianfarani      <[email protected]>
9
 * Copyright (C) 2007      Franky Van Liedekerke <[email protected]>
10
 * Copyright (C) 2010-2016 Juanjo Menent         <[email protected]>
11
 * Copyright (C) 2012-2014 Christophe Battarel   <[email protected]>
12
 * Copyright (C) 2012-2015 Marcos García         <[email protected]>
13
 * Copyright (C) 2012      Cédric Salvador       <[email protected]>
14
 * Copyright (C) 2012-2014 Raphaël Doursenaud    <[email protected]>
15
 * Copyright (C) 2013      Cedric Gross          <[email protected]>
16
 * Copyright (C) 2013      Florian Henry         <[email protected]>
17
 * Copyright (C) 2016      Ferran Marcet         <[email protected]>
18
 * Copyright (C) 2018      Alexandre Spangaro    <[email protected]>
19
 * Copyright (C) 2018      Nicolas ZABOURI        <[email protected]>
20
 *
21
 * This program is free software; you can redistribute it and/or modify
22
 * it under the terms of the GNU General Public License as published by
23
 * the Free Software Foundation; either version 3 of the License, or
24
 * (at your option) any later version.
25
 *
26
 * This program is distributed in the hope that it will be useful,
27
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
28
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
29
 * GNU General Public License for more details.
30
 *
31
 * You should have received a copy of the GNU General Public License
32
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
33
 */
34
35
/**
36
 *	\file       htdocs/compta/facture/class/facture.class.php
37
 *	\ingroup    facture
38
 *	\brief      File of class to manage invoices
39
 */
40
41
include_once DOL_DOCUMENT_ROOT.'/core/class/commoninvoice.class.php';
42
require_once DOL_DOCUMENT_ROOT.'/core/class/commonobjectline.class.php';
43
require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
44
require_once DOL_DOCUMENT_ROOT.'/societe/class/client.class.php';
45
require_once DOL_DOCUMENT_ROOT.'/margin/lib/margins.lib.php';
46
require_once DOL_DOCUMENT_ROOT.'/multicurrency/class/multicurrency.class.php';
47
48
if (! empty($conf->accounting->enabled)) require_once DOL_DOCUMENT_ROOT.'/core/class/html.formaccounting.class.php';
49
if (! empty($conf->accounting->enabled)) require_once DOL_DOCUMENT_ROOT.'/accountancy/class/accountingaccount.class.php';
50
51
/**
52
 *	Class to manage invoices
53
 */
54
class Facture extends CommonInvoice
55
{
56
	/**
57
	 * @var string ID to identify managed object
58
	 */
59
	public $element='facture';
60
61
	/**
62
	 * @var string Name of table without prefix where object is stored
63
	 */
64
	public $table_element='facture';
65
66
	/**
67
	 * @var int    Name of subtable line
68
	 */
69
	public $table_element_line = 'facturedet';
70
71
	/**
72
	 * @var int Field with ID of parent key if this field has a parent
73
	 */
74
	public $fk_element = 'fk_facture';
75
76
	/**
77
	 * @var string String with name of icon for myobject. Must be the part after the 'object_' into object_myobject.png
78
	 */
79
	public $picto='bill';
80
81
	/**
82
	 * 0=No test on entity, 1=Test with field entity, 2=Test with link by societe
83
	 * @var int
84
	 */
85
	public $ismultientitymanaged = 1;
86
87
	/**
88
	 * 0=Default, 1=View may be restricted to sales representative only if no permission to see all or to company of external user if external user
89
	 * @var integer
90
	 */
91
	public $restrictiononfksoc = 1;
92
93
	/**
94
	 * {@inheritdoc}
95
	 */
96
	protected $table_ref_field = 'ref';
97
98
	public $socid;
99
100
	public $author;
101
102
	/**
103
     * @var int ID
104
     */
105
	public $fk_user_author;
106
107
	/**
108
     * @var int ID
109
     */
110
	public $fk_user_valid;
111
112
	public $date;              // Date invoice
113
	public $datem;
114
	public $ref_client;
115
	public $ref_int;
116
	//Check constants for types
117
	public $type = self::TYPE_STANDARD;
118
119
	//var $amount;
120
	public $remise_absolue;
121
	public $remise_percent;
122
	public $total_ht=0;
123
	public $total_tva=0;
124
	public $total_localtax1=0;
125
	public $total_localtax2=0;
126
	public $total_ttc=0;
127
	public $revenuestamp;
128
129
	//! Fermeture apres paiement partiel: discount_vat, badcustomer, abandon
130
	//! Fermeture alors que aucun paiement: replaced (si remplace), abandon
131
	public $close_code;
132
	//! Commentaire si mis a paye sans paiement complet
133
	public $close_note;
134
	//! 1 if invoice paid COMPLETELY, 0 otherwise (do not use it anymore, use statut and close_code)
135
	public $paye;
136
	//! key of module source when invoice generated from a dedicated module ('cashdesk', 'takepos', ...)
137
	public $module_source;
138
	//! key of pos source ('0', '1', ...)
139
	public $pos_source;
140
	//! id of template invoice when generated from a template invoice
141
	public $fk_fac_rec_source;
142
	//! id of source invoice if replacement invoice or credit note
143
	public $fk_facture_source;
144
	public $linked_objects=array();
145
	public $date_lim_reglement;
146
	public $cond_reglement_code;		// Code in llx_c_paiement
147
	public $mode_reglement_code;		// Code in llx_c_paiement
148
149
	/**
150
     * @var int ID Field to store bank id to use when payment mode is withdraw
151
     */
152
	public $fk_bank;
153
154
	/**
155
	 * @deprecated
156
	 */
157
	public $products=array();
158
159
	/**
160
	 * @var FactureLigne[]
161
	 */
162
	public $lines=array();
163
164
	public $line;
165
	public $extraparams=array();
166
	public $specimen;
167
168
	public $fac_rec;
169
170
	// Multicurrency
171
	/**
172
     * @var int ID
173
     */
174
	public $fk_multicurrency;
175
176
	public $multicurrency_code;
177
	public $multicurrency_tx;
178
	public $multicurrency_total_ht;
179
	public $multicurrency_total_tva;
180
	public $multicurrency_total_ttc;
181
182
	/**
183
	 * @var int Situation cycle reference number
184
	 */
185
	public $situation_cycle_ref;
186
187
	/**
188
	 * @var int Situation counter inside the cycle
189
	 */
190
	public $situation_counter;
191
192
	/**
193
	 * @var int Final situation flag
194
	 */
195
	public $situation_final;
196
197
	/**
198
	 * @var array Table of previous situations
199
	 */
200
	public $tab_previous_situation_invoice=array();
201
202
	/**
203
	 * @var array Table of next situations
204
	 */
205
	public $tab_next_situation_invoice=array();
206
207
	public $oldcopy;
208
209
    /**
210
     * Standard invoice
211
     */
212
    const TYPE_STANDARD = 0;
213
214
    /**
215
     * Replacement invoice
216
     */
217
    const TYPE_REPLACEMENT = 1;
218
219
    /**
220
     * Credit note invoice
221
     */
222
    const TYPE_CREDIT_NOTE = 2;
223
224
    /**
225
     * Deposit invoice
226
     */
227
    const TYPE_DEPOSIT = 3;
228
229
    /**
230
     * Proforma invoice (should not be used. a proforma is an order)
231
     */
232
    const TYPE_PROFORMA = 4;
233
234
	/**
235
	 * Situation invoice
236
	 */
237
	const TYPE_SITUATION = 5;
238
239
	/**
240
	 * Draft status
241
	 */
242
	const STATUS_DRAFT = 0;
243
244
	/**
245
	 * Validated (need to be paid)
246
	 */
247
	const STATUS_VALIDATED = 1;
248
249
	/**
250
	 * Classified paid.
251
	 * If paid partially, $this->close_code can be:
252
	 * - CLOSECODE_DISCOUNTVAT
253
	 * - CLOSECODE_BADDEBT
254
	 * If paid completely, this->close_code will be null
255
	 */
256
	const STATUS_CLOSED = 2;
257
258
	/**
259
	 * Classified abandoned and no payment done.
260
	 * $this->close_code can be:
261
	 * - CLOSECODE_BADDEBT
262
	 * - CLOSECODE_ABANDONED
263
	 * - CLOSECODE_REPLACED
264
	 */
265
	const STATUS_ABANDONED = 3;
266
267
	const CLOSECODE_DISCOUNTVAT = 'discount_vat';	// Abandonned remain - escompte
268
	const CLOSECODE_BADDEBT = 'badcustomer';		// Abandonned - bad
269
	const CLOSECODE_ABANDONED = 'abandon';			// Abandonned - other
270
	const CLOSECODE_REPLACED = 'replaced';			// Closed after doing a replacement invoice
271
272
	/**
273
	 * 	Constructor
274
	 *
275
	 * 	@param	DoliDB		$db			Database handler
276
	 */
277
	function __construct($db)
278
	{
279
		$this->db = $db;
280
	}
281
282
	/**
283
	 *	Create invoice in database.
284
	 *  Note: this->ref can be set or empty. If empty, we will use "(PROV999)"
285
	 *  Note: this->fac_rec must be set to create invoice from a recurring invoice
286
	 *
287
	 *	@param	User	$user      		Object user that create
288
	 *	@param  int		$notrigger		1=Does not execute triggers, 0 otherwise
289
	 * 	@param	int		$forceduedate	1=Do not recalculate due date from payment condition but force it with value
290
	 *	@return	int						<0 if KO, >0 if OK
291
	 */
292
	function create(User $user, $notrigger=0, $forceduedate=0)
293
	{
294
		global $langs,$conf,$mysoc,$hookmanager;
295
		$error=0;
296
297
		// Clean parameters
298
		if (empty($this->type)) $this->type = self::TYPE_STANDARD;
299
		$this->ref_client=trim($this->ref_client);
300
		$this->note=(isset($this->note) ? trim($this->note) : trim($this->note_private)); // deprecated
301
		$this->note_private=(isset($this->note_private) ? trim($this->note_private) : trim($this->note_private));
302
		$this->note_public=trim($this->note_public);
303
		if (! $this->cond_reglement_id) $this->cond_reglement_id = 0;
304
		if (! $this->mode_reglement_id) $this->mode_reglement_id = 0;
305
		$this->brouillon = 1;
306
        if (empty($this->entity)) $this->entity = $conf->entity;
307
308
		// Multicurrency (test on $this->multicurrency_tx because we should take the default rate only if not using origin rate)
309
		if (!empty($this->multicurrency_code) && empty($this->multicurrency_tx)) list($this->fk_multicurrency,$this->multicurrency_tx) = MultiCurrency::getIdAndTxFromCode($this->db, $this->multicurrency_code);
310
		else $this->fk_multicurrency = MultiCurrency::getIdFromCode($this->db, $this->multicurrency_code);
311
		if (empty($this->fk_multicurrency))
312
		{
313
			$this->multicurrency_code = $conf->currency;
314
			$this->fk_multicurrency = 0;
315
			$this->multicurrency_tx = 1;
316
		}
317
318
		dol_syslog(get_class($this)."::create user=".$user->id." date=".$this->date);
319
320
		// Check parameters
321
		if (empty($this->date))
322
		{
323
			$this->error="Try to create an invoice with an empty parameter (date)";
324
			dol_syslog(get_class($this)."::create ".$this->error, LOG_ERR);
325
			return -3;
326
		}
327
		$soc = new Societe($this->db);
328
		$result=$soc->fetch($this->socid);
329
		if ($result < 0)
330
		{
331
			$this->error="Failed to fetch company: ".$soc->error;
332
			dol_syslog(get_class($this)."::create ".$this->error, LOG_ERR);
333
			return -2;
334
		}
335
336
		$now=dol_now();
337
338
		$this->db->begin();
339
340
		$originaldatewhen=null;
341
		$nextdatewhen=null;
342
		$previousdaynextdatewhen=null;
343
344
		// Create invoice from a template invoice
345
		if ($this->fac_rec > 0)
346
		{
347
		    $this->fk_fac_rec_source = $this->fac_rec;
348
349
			require_once DOL_DOCUMENT_ROOT.'/compta/facture/class/facture-rec.class.php';
350
			$_facrec = new FactureRec($this->db);
351
			$result=$_facrec->fetch($this->fac_rec);
352
			$result=$_facrec->fetchObjectLinked();       // This load $_facrec->linkedObjectsIds
353
354
			// Define some dates
355
			$originaldatewhen = $_facrec->date_when;
356
			$nextdatewhen=dol_time_plus_duree($originaldatewhen, $_facrec->frequency, $_facrec->unit_frequency);
357
			$previousdaynextdatewhen=dol_time_plus_duree($nextdatewhen, -1, 'd');
358
359
			$this->socid 		     = $_facrec->socid;  // Invoice created on same thirdparty than template
360
			$this->entity            = $_facrec->entity; // Invoice created in same entity than template
361
362
			// Fields coming from GUI (priority on template). TODO Value of template should be used as default value on GUI so we can use here always value from GUI
363
			$this->fk_project        = GETPOST('projectid','int') > 0 ? ((int) GETPOST('projectid','int')) : $_facrec->fk_project;
364
			$this->note_public       = GETPOST('note_public','none') ? GETPOST('note_public','none') : $_facrec->note_public;
365
			$this->note_private      = GETPOST('note_private','none') ? GETPOST('note_private','none') : $_facrec->note_private;
366
			$this->modelpdf          = GETPOST('model','alpha') ? GETPOST('model','apha') : $_facrec->modelpdf;
367
			$this->cond_reglement_id = GETPOST('cond_reglement_id','int') > 0 ? ((int) GETPOST('cond_reglement_id','int')) : $_facrec->cond_reglement_id;
368
			$this->mode_reglement_id = GETPOST('mode_reglement_id','int') > 0 ? ((int) GETPOST('mode_reglement_id','int')) : $_facrec->mode_reglement_id;
369
			$this->fk_account        = GETPOST('fk_account') > 0 ? ((int) GETPOST('fk_account')) : $_facrec->fk_account;
370
371
			// Set here to have this defined for substitution into notes, should be recalculated after adding lines to get same result
372
			$this->total_ht          = $_facrec->total_ht;
373
			$this->total_ttc         = $_facrec->total_ttc;
374
375
			// Fields always coming from template
376
			$this->remise_absolue    = $_facrec->remise_absolue;
377
			$this->remise_percent    = $_facrec->remise_percent;
378
			$this->fk_incoterms		 = $_facrec->fk_incoterms;
379
			$this->location_incoterms= $_facrec->location_incoterms;
380
381
			// Clean parameters
382
			if (! $this->type) $this->type = self::TYPE_STANDARD;
383
			$this->ref_client=trim($this->ref_client);
384
			$this->note_public=trim($this->note_public);
385
			$this->note_private=trim($this->note_private);
386
		    $this->note_private=dol_concatdesc($this->note_private, $langs->trans("GeneratedFromRecurringInvoice", $_facrec->ref));
387
388
		    $this->array_options=$_facrec->array_options;
389
390
			//if (! $this->remise) $this->remise = 0;
391
			if (! $this->mode_reglement_id) $this->mode_reglement_id = 0;
392
			$this->brouillon = 1;
393
394
			$this->linked_objects = $_facrec->linkedObjectsIds;
395
396
			$forceduedate = $this->calculate_date_lim_reglement();
397
398
			// For recurring invoices, update date and number of last generation of recurring template invoice, before inserting new invoice
399
			if ($_facrec->frequency > 0)
400
			{
401
			    dol_syslog("This is a recurring invoice so we set date_last_gen and next date_when");
402
			    if (empty($_facrec->date_when)) $_facrec->date_when = $now;
403
                $next_date = $_facrec->getNextDate();   // Calculate next date
404
                $result = $_facrec->setValueFrom('date_last_gen', $now, '', null, 'date', '', $user, '');
405
                //$_facrec->setValueFrom('nb_gen_done', $_facrec->nb_gen_done + 1);		// Not required, +1 already included into setNextDate when second param is 1.
406
                $result = $_facrec->setNextDate($next_date,1);
407
			}
408
409
			// Define lang of customer
410
			$outputlangs = $langs;
411
			$newlang='';
412
413
			if ($conf->global->MAIN_MULTILANGS && empty($newlang) && isset($this->thirdparty->default_lang)) $newlang=$this->thirdparty->default_lang;  // for proposal, order, invoice, ...
414
			if ($conf->global->MAIN_MULTILANGS && empty($newlang) && isset($this->default_lang)) $newlang=$this->default_lang;                  // for thirdparty
415
			if (! empty($newlang))
416
			{
417
			    $outputlangs = new Translate("",$conf);
418
			    $outputlangs->setDefaultLang($newlang);
419
			}
420
421
			// Array of possible substitutions (See also file mailing-send.php that should manage same substitutions)
422
			$substitutionarray=getCommonSubstitutionArray($outputlangs, 0, null, $this);
423
			$substitutionarray['__INVOICE_PREVIOUS_MONTH__'] = dol_print_date(dol_time_plus_duree($this->date, -1, 'm'), '%m');
424
			$substitutionarray['__INVOICE_MONTH__'] = dol_print_date($this->date, '%m');
425
			$substitutionarray['__INVOICE_NEXT_MONTH__'] = dol_print_date(dol_time_plus_duree($this->date, 1, 'm'), '%m');
426
			$substitutionarray['__INVOICE_PREVIOUS_MONTH_TEXT__'] = dol_print_date(dol_time_plus_duree($this->date, -1, 'm'), '%B');
427
			$substitutionarray['__INVOICE_MONTH_TEXT__'] = dol_print_date($this->date, '%B');
428
			$substitutionarray['__INVOICE_NEXT_MONTH_TEXT__'] = dol_print_date(dol_time_plus_duree($this->date, 1, 'm'), '%B');
429
			$substitutionarray['__INVOICE_PREVIOUS_YEAR__'] = dol_print_date(dol_time_plus_duree($this->date, -1, 'y'), '%Y');
430
			$substitutionarray['__INVOICE_YEAR__'] = dol_print_date($this->date, '%Y');
431
			$substitutionarray['__INVOICE_NEXT_YEAR__'] = dol_print_date(dol_time_plus_duree($this->date, 1, 'y'), '%Y');
432
			// Only for tempalte invoice
433
			$substitutionarray['__INVOICE_DATE_NEXT_INVOICE_BEFORE_GEN__'] = dol_print_date($originaldatewhen, 'dayhour');
434
			$substitutionarray['__INVOICE_DATE_NEXT_INVOICE_AFTER_GEN__'] = dol_print_date($nextdatewhen, 'dayhour');
435
			$substitutionarray['__INVOICE_PREVIOUS_DATE_NEXT_INVOICE_AFTER_GEN__'] = dol_print_date($previousdaynextdatewhen, 'dayhour');
436
437
			//var_dump($substitutionarray);exit;
438
439
			$substitutionisok=true;
440
			complete_substitutions_array($substitutionarray, $outputlangs);
441
442
			$this->note_public=make_substitutions($this->note_public,$substitutionarray);
443
			$this->note_private=make_substitutions($this->note_private,$substitutionarray);
444
		}
445
446
		// Define due date if not already defined
447
		$datelim=(empty($forceduedate)?$this->calculate_date_lim_reglement():$forceduedate);
448
449
		// Insert into database
450
		$socid  = $this->socid;
451
452
		$sql = "INSERT INTO ".MAIN_DB_PREFIX."facture (";
453
		$sql.= " ref";
454
		$sql.= ", entity";
455
		$sql.= ", ref_ext";
456
		$sql.= ", type";
457
		$sql.= ", fk_soc";
458
		$sql.= ", datec";
459
		$sql.= ", remise_absolue";
460
		$sql.= ", remise_percent";
461
		$sql.= ", datef";
462
		$sql.= ", date_pointoftax";
463
		$sql.= ", note_private";
464
		$sql.= ", note_public";
465
		$sql.= ", ref_client, ref_int";
466
        $sql.= ", fk_account";
467
		$sql.= ", module_source, pos_source, fk_fac_rec_source, fk_facture_source, fk_user_author, fk_projet";
468
		$sql.= ", fk_cond_reglement, fk_mode_reglement, date_lim_reglement, model_pdf";
469
		$sql.= ", situation_cycle_ref, situation_counter, situation_final";
470
		$sql.= ", fk_incoterms, location_incoterms";
471
        $sql.= ", fk_multicurrency";
472
        $sql.= ", multicurrency_code";
473
        $sql.= ", multicurrency_tx";
474
		$sql.= ")";
475
		$sql.= " VALUES (";
476
		$sql.= "'(PROV)'";
477
		$sql.= ", ".$this->entity;
478
		$sql.= ", ".($this->ref_ext?"'".$this->db->escape($this->ref_ext)."'":"null");
479
		$sql.= ", '".$this->db->escape($this->type)."'";
480
		$sql.= ", '".$socid."'";
481
		$sql.= ", '".$this->db->idate($now)."'";
482
		$sql.= ", ".($this->remise_absolue>0?$this->remise_absolue:'NULL');
483
		$sql.= ", ".($this->remise_percent>0?$this->remise_percent:'NULL');
484
		$sql.= ", '".$this->db->idate($this->date)."'";
485
		$sql.= ", ".(strval($this->date_pointoftax)!='' ? "'".$this->db->idate($this->date_pointoftax)."'" : 'null');
486
		$sql.= ", ".($this->note_private?"'".$this->db->escape($this->note_private)."'":"null");
487
		$sql.= ", ".($this->note_public?"'".$this->db->escape($this->note_public)."'":"null");
488
		$sql.= ", ".($this->ref_client?"'".$this->db->escape($this->ref_client)."'":"null");
489
		$sql.= ", ".($this->ref_int?"'".$this->db->escape($this->ref_int)."'":"null");
490
		$sql.= ", ".($this->fk_account>0?$this->fk_account:'NULL');
491
		$sql.= ", ".($this->module_source ? "'".$this->db->escape($this->module_source)."'" : "null");
492
		$sql.= ", ".($this->pos_source != '' ? "'".$this->db->escape($this->pos_source)."'" : "null");
493
		$sql.= ", ".($this->fk_fac_rec_source?"'".$this->db->escape($this->fk_fac_rec_source)."'":"null");
494
		$sql.= ", ".($this->fk_facture_source?"'".$this->db->escape($this->fk_facture_source)."'":"null");
495
		$sql.= ", ".($user->id > 0 ? "'".$user->id."'":"null");
496
		$sql.= ", ".($this->fk_project?$this->fk_project:"null");
497
		$sql.= ", ".$this->cond_reglement_id;
498
		$sql.= ", ".$this->mode_reglement_id;
499
		$sql.= ", '".$this->db->idate($datelim)."', '".$this->db->escape($this->modelpdf)."'";
500
		$sql.= ", ".($this->situation_cycle_ref?"'".$this->db->escape($this->situation_cycle_ref)."'":"null");
501
		$sql.= ", ".($this->situation_counter?"'".$this->db->escape($this->situation_counter)."'":"null");
502
		$sql.= ", ".($this->situation_final?$this->situation_final:0);
503
		$sql.= ", ".(int) $this->fk_incoterms;
504
        $sql.= ", '".$this->db->escape($this->location_incoterms)."'";
505
		$sql.= ", ".(int) $this->fk_multicurrency;
506
		$sql.= ", '".$this->db->escape($this->multicurrency_code)."'";
507
		$sql.= ", ".(double) $this->multicurrency_tx;
508
		$sql.=")";
509
510
		$resql=$this->db->query($sql);
511
		if ($resql)
512
		{
513
			$this->id = $this->db->last_insert_id(MAIN_DB_PREFIX.'facture');
514
515
			// Update ref with new one
516
			$this->ref='(PROV'.$this->id.')';
517
			$sql = 'UPDATE '.MAIN_DB_PREFIX."facture SET ref='".$this->db->escape($this->ref)."' WHERE rowid=".$this->id;
518
519
			$resql=$this->db->query($sql);
520
			if (! $resql) $error++;
521
522
			if (! empty($this->linkedObjectsIds) && empty($this->linked_objects))	// To use new linkedObjectsIds instead of old linked_objects
523
			{
524
				$this->linked_objects = $this->linkedObjectsIds;	// TODO Replace linked_objects with linkedObjectsIds
525
			}
526
527
			// Add object linked
528
			if (! $error && $this->id && is_array($this->linked_objects) && ! empty($this->linked_objects))
529
			{
530
				foreach($this->linked_objects as $origin => $tmp_origin_id)
531
				{
532
				    if (is_array($tmp_origin_id))       // New behaviour, if linked_object can have several links per type, so is something like array('contract'=>array(id1, id2, ...))
533
				    {
534
				        foreach($tmp_origin_id as $origin_id)
535
				        {
536
				            $ret = $this->add_object_linked($origin, $origin_id);
537
				            if (! $ret)
538
				            {
539
				                $this->error=$this->db->lasterror();
540
				                $error++;
541
				            }
542
				        }
543
				    }
544
				    else                                // Old behaviour, if linked_object has only one link per type, so is something like array('contract'=>id1))
545
				    {
546
				        $origin_id = $tmp_origin_id;
547
    					$ret = $this->add_object_linked($origin, $origin_id);
548
    					if (! $ret)
549
    					{
550
    						$this->error=$this->db->lasterror();
551
    						$error++;
552
    					}
553
				    }
554
				}
555
			}
556
557
			// Propagate contacts
558
			if (! $error && $this->id && ! empty($conf->global->MAIN_PROPAGATE_CONTACTS_FROM_ORIGIN) && ! empty($this->origin) && ! empty($this->origin_id))   // Get contact from origin object
559
			{
560
				$originforcontact = $this->origin;
561
				$originidforcontact = $this->origin_id;
562
				if ($originforcontact == 'shipping')     // shipment and order share the same contacts. If creating from shipment we take data of order
563
				{
564
				    require_once DOL_DOCUMENT_ROOT . '/expedition/class/expedition.class.php';
565
				    $exp = new Expedition($this->db);
566
				    $exp->fetch($this->origin_id);
567
				    $exp->fetchObjectLinked();
568
				    if (count($exp->linkedObjectsIds['commande']) > 0)
569
				    {
570
				        foreach ($exp->linkedObjectsIds['commande'] as $key => $value)
571
				        {
572
				            $originforcontact = 'commande';
573
				            if (is_object($value)) $originidforcontact = $value->id;
574
				            else $originidforcontact = $value;
575
				            break; // We take first one
576
				        }
577
				    }
578
				}
579
580
				$sqlcontact = "SELECT ctc.code, ctc.source, ec.fk_socpeople FROM ".MAIN_DB_PREFIX."element_contact as ec, ".MAIN_DB_PREFIX."c_type_contact as ctc";
581
				$sqlcontact.= " WHERE element_id = ".$originidforcontact." AND ec.fk_c_type_contact = ctc.rowid AND ctc.element = '".$originforcontact."'";
582
583
				$resqlcontact = $this->db->query($sqlcontact);
584
				if ($resqlcontact)
585
				{
586
				    while($objcontact = $this->db->fetch_object($resqlcontact))
587
				    {
588
				        //print $objcontact->code.'-'.$objcontact->source.'-'.$objcontact->fk_socpeople."\n";
589
				        $this->add_contact($objcontact->fk_socpeople, $objcontact->code, $objcontact->source);    // May failed because of duplicate key or because code of contact type does not exists for new object
590
				    }
591
				}
592
				else dol_print_error($resqlcontact);
593
			}
594
595
			/*
596
			 *  Insert lines of invoices, if not from template invoice, into database
597
			 */
598
			if (! $error && empty($this->fac_rec) && count($this->lines) && is_object($this->lines[0]))	// If this->lines is array of InvoiceLines (preferred mode)
599
			{
600
				$fk_parent_line = 0;
601
602
				dol_syslog("There is ".count($this->lines)." lines that are invoice lines objects");
603
				foreach ($this->lines as $i => $val)
604
				{
605
					$newinvoiceline=$this->lines[$i];
606
					$newinvoiceline->fk_facture=$this->id;
607
608
					$newinvoiceline->origin = $this->lines[$i]->element;
609
					$newinvoiceline->origin_id = $this->lines[$i]->id;
610
611
					// Auto set date of service ?
612
					if ($this->lines[$i]->date_start_fill == 1 && $originaldatewhen)			// $originaldatewhen is defined when generating from recurring invoice only
613
					{
614
						$newinvoiceline->date_start = $originaldatewhen;
615
					}
616
					if ($this->lines[$i]->date_end_fill == 1 && $previousdaynextdatewhen)	// $previousdaynextdatewhen is defined when generating from recurring invoice only
617
					{
618
						$newinvoiceline->date_end = $previousdaynextdatewhen;
619
					}
620
621
					if ($result >= 0)
622
					{
623
						// Reset fk_parent_line for no child products and special product
624
						if (($newinvoiceline->product_type != 9 && empty($newinvoiceline->fk_parent_line)) || $newinvoiceline->product_type == 9) {
625
							$fk_parent_line = 0;
626
						}
627
628
						$newinvoiceline->fk_parent_line=$fk_parent_line;
629
630
						if($this->type === Facture::TYPE_REPLACEMENT && $newinvoiceline->fk_remise_except){
631
                            $discount = new DiscountAbsolute($this->db);
632
                            $discount->fetch($newinvoiceline->fk_remise_except);
633
634
						    $discountId = $soc->set_remise_except($discount->amount_ht, $user, $discount->description, $discount->tva_tx);
635
						    $newinvoiceline->fk_remise_except = $discountId;
636
                        }
637
638
						$result=$newinvoiceline->insert();
639
640
						// Defined the new fk_parent_line
641
						if ($result > 0 && $newinvoiceline->product_type == 9) {
642
							$fk_parent_line = $result;
643
						}
644
					}
645
					if ($result < 0)
646
					{
647
						$this->error=$newinvoiceline->error;
648
						$error++;
649
						break;
650
					}
651
				}
652
			}
653
			elseif (! $error && empty($this->fac_rec)) 		// If this->lines is an array of invoice line arrays
654
			{
655
				$fk_parent_line = 0;
656
657
				dol_syslog("There is ".count($this->lines)." lines that are array lines");
658
659
				foreach ($this->lines as $i => $val)
660
				{
661
                	$line = $this->lines[$i];
662
663
                	// Test and convert into object this->lines[$i]. When coming from REST API, we may still have an array
664
				    //if (! is_object($line)) $line=json_decode(json_encode($line), false);  // convert recursively array into object.
665
                	if (! is_object($line)) $line = (object) $line;
666
667
				    if ($result >= 0)
668
					{
669
						// Reset fk_parent_line for no child products and special product
670
						if (($line->product_type != 9 && empty($line->fk_parent_line)) || $line->product_type == 9) {
671
							$fk_parent_line = 0;
672
						}
673
674
						// Complete vat rate with code
675
						$vatrate = $line->tva_tx;
676
						if ($line->vat_src_code && ! preg_match('/\(.*\)/', $vatrate)) $vatrate.=' ('.$line->vat_src_code.')';
677
678
						$result = $this->addline(
679
							$line->desc,
680
							$line->subprice,
681
							$line->qty,
682
							$vatrate,
683
							$line->localtax1_tx,
684
							$line->localtax2_tx,
685
							$line->fk_product,
686
							$line->remise_percent,
687
							$line->date_start,
688
							$line->date_end,
689
							$line->fk_code_ventilation,
690
							$line->info_bits,
691
							$line->fk_remise_except,
692
							'HT',
693
							0,
694
							$line->product_type,
695
							$line->rang,
696
							$line->special_code,
697
                            $this->element,
698
                            $line->id,
699
							$fk_parent_line,
700
							$line->fk_fournprice,
701
							$line->pa_ht,
702
							$line->label,
703
							$line->array_options,
704
							$line->situation_percent,
705
							$line->fk_prev_id,
706
							$line->fk_unit,
707
							$line->pu_ht_devise
708
						);
709
						if ($result < 0)
710
						{
711
							$this->error=$this->db->lasterror();
712
							dol_print_error($this->db);
713
							$this->db->rollback();
714
							return -1;
715
						}
716
717
						// Defined the new fk_parent_line
718
						if ($result > 0 && $line->product_type == 9) {
719
							$fk_parent_line = $result;
720
						}
721
					}
722
				}
723
			}
724
725
			/*
726
			 * Insert lines of predefined invoices
727
			 */
728
			if (! $error && $this->fac_rec > 0)
729
			{
730
				foreach ($_facrec->lines as $i => $val)
731
				{
732
					if ($_facrec->lines[$i]->fk_product)
733
					{
734
						$prod = new Product($this->db);
735
						$res=$prod->fetch($_facrec->lines[$i]->fk_product);
736
					}
737
738
					// For line from template invoice, we use data from template invoice
739
					/*
740
					$tva_tx = get_default_tva($mysoc,$soc,$prod->id);
741
					$tva_npr = get_default_npr($mysoc,$soc,$prod->id);
742
					if (empty($tva_tx)) $tva_npr=0;
743
					$localtax1_tx=get_localtax($tva_tx,1,$soc,$mysoc,$tva_npr);
744
					$localtax2_tx=get_localtax($tva_tx,2,$soc,$mysoc,$tva_npr);
745
					*/
746
					$tva_tx = $_facrec->lines[$i]->tva_tx.($_facrec->lines[$i]->vat_src_code ? '('.$_facrec->lines[$i]->vat_src_code.')' : '');
747
					$tva_npr = $_facrec->lines[$i]->info_bits;
748
					if (empty($tva_tx)) $tva_npr=0;
749
					$localtax1_tx = $_facrec->lines[$i]->localtax1_tx;
750
					$localtax2_tx = $_facrec->lines[$i]->localtax2_tx;
751
752
					$result_insert = $this->addline(
753
						$_facrec->lines[$i]->desc,
754
						$_facrec->lines[$i]->subprice,
755
						$_facrec->lines[$i]->qty,
756
						$tva_tx,
757
						$localtax1_tx,
758
						$localtax2_tx,
759
						$_facrec->lines[$i]->fk_product,
760
						$_facrec->lines[$i]->remise_percent,
761
						($_facrec->lines[$i]->date_start_fill == 1 && $originaldatewhen)?$originaldatewhen:'',
762
						($_facrec->lines[$i]->date_end_fill == 1 && $previousdaynextdatewhen)?$previousdaynextdatewhen:'',
763
						0,
764
						$tva_npr,
765
						'',
766
						'HT',
767
						0,
768
						$_facrec->lines[$i]->product_type,
769
						$_facrec->lines[$i]->rang,
770
						$_facrec->lines[$i]->special_code,
771
						'',
772
						0,
773
						0,
774
						null,
775
						0,
776
						$_facrec->lines[$i]->label,
777
						empty($_facrec->lines[$i]->array_options)?null:$_facrec->lines[$i]->array_options,
778
						$_facrec->lines[$i]->situation_percent,
779
						'',
780
						$_facrec->lines[$i]->fk_unit,
781
						$_facrec->lines[$i]->pu_ht_devise
782
					);
783
784
					if ( $result_insert < 0)
785
					{
786
						$error++;
787
						$this->error=$this->db->error();
788
						break;
789
					}
790
				}
791
			}
792
793
			if (! $error)
794
			{
795
796
				$result=$this->update_price(1);
797
				if ($result > 0)
798
				{
799
					$action='create';
800
801
					// Actions on extra fields
802
					if (! $error)
803
					{
804
					    $result=$this->insertExtraFields();
805
					    if ($result < 0) $error++;
806
					}
807
808
			        if (! $error && ! $notrigger)
809
			        {
810
			           // Call trigger
811
			           $result=$this->call_trigger('BILL_CREATE',$user);
812
			           if ($result < 0) $error++;
813
			           // End call triggers
814
			        }
815
816
					if (! $error)
817
					{
818
						$this->db->commit();
819
						return $this->id;
820
					}
821
					else
822
					{
823
						$this->db->rollback();
824
						return -4;
825
					}
826
				}
827
				else
828
				{
829
					$this->error=$langs->trans('FailedToUpdatePrice');
830
					$this->db->rollback();
831
					return -3;
832
				}
833
			}
834
			else
835
			{
836
				dol_syslog(get_class($this)."::create error ".$this->error, LOG_ERR);
837
				$this->db->rollback();
838
				return -2;
839
			}
840
		}
841
		else
842
		{
843
			$this->error=$this->db->error();
844
			$this->db->rollback();
845
			return -1;
846
		}
847
	}
848
849
850
	/**
851
	 *	Create a new invoice in database from current invoice
852
	 *
853
	 *	@param      User	$user    		Object user that ask creation
854
	 *	@param		int		$invertdetail	Reverse sign of amounts for lines
855
	 *	@return		int						<0 if KO, >0 if OK
856
	 */
857
	function createFromCurrent(User $user, $invertdetail=0)
858
	{
859
		global $conf;
860
861
		// Charge facture source
862
		$facture=new Facture($this->db);
863
864
		// Retreive all extrafield
865
		// fetch optionals attributes and labels
866
		$this->fetch_optionals();
867
868
        if(!empty($this->array_options)){
869
                    $facture->array_options = $this->array_options;
870
        }
871
872
        foreach($this->lines as &$line){
873
                    $line->fetch_optionals();//fetch extrafields
874
        }
875
876
		$facture->fk_facture_source = $this->fk_facture_source;
877
		$facture->type 			    = $this->type;
878
		$facture->socid 		    = $this->socid;
879
		$facture->date              = $this->date;
880
		$facture->date_pointoftax   = $this->date_pointoftax;
881
		$facture->note_public       = $this->note_public;
882
		$facture->note_private      = $this->note_private;
883
		$facture->ref_client        = $this->ref_client;
884
		$facture->modelpdf          = $this->modelpdf;
885
		$facture->fk_project        = $this->fk_project;
886
		$facture->cond_reglement_id = $this->cond_reglement_id;
887
		$facture->mode_reglement_id = $this->mode_reglement_id;
888
		$facture->remise_absolue    = $this->remise_absolue;
889
		$facture->remise_percent    = $this->remise_percent;
890
891
		$facture->origin                        = $this->origin;
892
		$facture->origin_id                     = $this->origin_id;
893
894
		$facture->lines		    	= $this->lines;	// Tableau des lignes de factures
895
		$facture->products		    = $this->lines;	// Tant que products encore utilise
896
		$facture->situation_counter = $this->situation_counter;
897
		$facture->situation_cycle_ref=$this->situation_cycle_ref;
898
		$facture->situation_final  = $this->situation_final;
899
900
		// Loop on each line of new invoice
901
		foreach($facture->lines as $i => $tmpline)
902
		{
903
			$facture->lines[$i]->fk_prev_id = $this->lines[$i]->rowid;
904
			if ($invertdetail)
905
			{
906
				$facture->lines[$i]->subprice  = -$facture->lines[$i]->subprice;
907
				$facture->lines[$i]->total_ht  = -$facture->lines[$i]->total_ht;
908
				$facture->lines[$i]->total_tva = -$facture->lines[$i]->total_tva;
909
				$facture->lines[$i]->total_localtax1 = -$facture->lines[$i]->total_localtax1;
910
				$facture->lines[$i]->total_localtax2 = -$facture->lines[$i]->total_localtax2;
911
				$facture->lines[$i]->total_ttc = -$facture->lines[$i]->total_ttc;
912
			}
913
		}
914
915
		dol_syslog(get_class($this)."::createFromCurrent invertdetail=".$invertdetail." socid=".$this->socid." nboflines=".count($facture->lines));
916
917
		$facid = $facture->create($user);
918
		if ($facid <= 0)
919
		{
920
			$this->error=$facture->error;
921
			$this->errors=$facture->errors;
922
		}
923
		elseif ($this->type == self::TYPE_SITUATION && !empty($conf->global->INVOICE_USE_SITUATION))
924
		{
925
			$this->fetchObjectLinked('', '', $facture->id, 'facture');
926
927
			foreach ($this->linkedObjectsIds as $typeObject => $Tfk_object)
928
			{
929
				foreach ($Tfk_object as $fk_object)
930
				{
931
					$facture->add_object_linked($typeObject, $fk_object);
932
				}
933
			}
934
935
			$facture->add_object_linked('facture', $this->fk_facture_source);
936
		}
937
938
		return $facid;
939
	}
940
941
942
	/**
943
	 *		Load an object from its id and create a new one in database
944
	 *
945
	 *		@param		int				$socid			Id of thirdparty
946
	 * 	 	@return		int								New id of clone
947
	 */
948
	function createFromClone($socid=0)
949
	{
950
		global $user,$hookmanager;
951
952
		$error=0;
953
954
		$this->context['createfromclone'] = 'createfromclone';
955
956
		$this->db->begin();
957
958
		// get extrafields so they will be clone
959
		foreach($this->lines as $line)
960
			$line->fetch_optionals($line->rowid);
961
962
		// Load source object
963
		$objFrom = clone $this;
964
965
966
967
		// Change socid if needed
968
		if (! empty($socid) && $socid != $this->socid)
969
		{
970
			$objsoc = new Societe($this->db);
971
972
			if ($objsoc->fetch($socid)>0)
973
			{
974
				$this->socid 				= $objsoc->id;
975
				$this->cond_reglement_id	= (! empty($objsoc->cond_reglement_id) ? $objsoc->cond_reglement_id : 0);
976
				$this->mode_reglement_id	= (! empty($objsoc->mode_reglement_id) ? $objsoc->mode_reglement_id : 0);
977
				$this->fk_project			= '';
978
				$this->fk_delivery_address	= '';
979
			}
980
981
			// TODO Change product price if multi-prices
982
		}
983
984
		$this->id=0;
985
		$this->statut= self::STATUS_DRAFT;
986
987
		// Clear fields
988
		$this->date               = dol_now();	// Date of invoice is set to current date when cloning. // TODO Best is to ask date into confirm box
989
		$this->user_author        = $user->id;
990
		$this->user_valid         = '';
991
		$this->fk_facture_source  = 0;
992
		$this->date_creation      = '';
993
		$this->date_modification = '';
994
		$this->date_validation    = '';
995
		$this->ref_client         = '';
996
		$this->close_code         = '';
997
		$this->close_note         = '';
998
		$this->products = $this->lines;	// Tant que products encore utilise
999
1000
		// Loop on each line of new invoice
1001
		foreach($this->lines as $i => $line)
1002
		{
1003
			if (($this->lines[$i]->info_bits & 0x02) == 0x02)	// We do not clone line of discounts
1004
			{
1005
				unset($this->lines[$i]);
1006
				unset($this->products[$i]);	// Tant que products encore utilise
1007
			}
1008
		}
1009
1010
		// Create clone
1011
		$result=$this->create($user);
1012
		if ($result < 0) $error++;
1013
		else {
1014
			// copy internal contacts
1015
			if ($this->copy_linked_contact($objFrom, 'internal') < 0)
1016
				$error++;
1017
1018
			// copy external contacts if same company
1019
			elseif ($objFrom->socid == $this->socid)
1020
			{
1021
				if ($this->copy_linked_contact($objFrom, 'external') < 0)
1022
					$error++;
1023
			}
1024
		}
1025
1026
		if (! $error)
1027
		{
1028
			// Hook of thirdparty module
1029
			if (is_object($hookmanager))
1030
			{
1031
				$parameters=array('objFrom'=>$objFrom);
1032
				$action='';
1033
				$reshook=$hookmanager->executeHooks('createFrom',$parameters,$this,$action);    // Note that $action and $object may have been modified by some hooks
1034
				if ($reshook < 0) $error++;
1035
			}
1036
		}
1037
1038
		unset($this->context['createfromclone']);
1039
1040
		// End
1041
		if (! $error)
1042
		{
1043
			$this->db->commit();
1044
			return $this->id;
1045
		}
1046
		else
1047
		{
1048
			$this->db->rollback();
1049
			return -1;
1050
		}
1051
	}
1052
1053
	/**
1054
	 *  Load an object from an order and create a new invoice into database
1055
	 *
1056
	 *  @param      Object			$object         	Object source
1057
	 *  @param		User			$user				Object user
1058
	 *  @return     int             					<0 if KO, 0 if nothing done, 1 if OK
1059
	 */
1060
	function createFromOrder($object, User $user)
1061
	{
1062
		global $hookmanager;
1063
1064
		$error=0;
1065
1066
		// Closed order
1067
		$this->date = dol_now();
1068
		$this->source = 0;
1069
1070
		$num=count($object->lines);
1071
		for ($i = 0; $i < $num; $i++)
1072
		{
1073
			$line = new FactureLigne($this->db);
1074
1075
			$line->libelle			= $object->lines[$i]->libelle;
1076
			$line->label			= $object->lines[$i]->label;
1077
			$line->desc				= $object->lines[$i]->desc;
1078
			$line->subprice			= $object->lines[$i]->subprice;
1079
			$line->total_ht			= $object->lines[$i]->total_ht;
1080
			$line->total_tva		= $object->lines[$i]->total_tva;
1081
			$line->total_localtax1	= $object->lines[$i]->total_localtax1;
1082
			$line->total_localtax2	= $object->lines[$i]->total_localtax2;
1083
			$line->total_ttc		= $object->lines[$i]->total_ttc;
1084
			$line->vat_src_code  	= $object->lines[$i]->vat_src_code;
1085
			$line->tva_tx			= $object->lines[$i]->tva_tx;
1086
			$line->localtax1_tx		= $object->lines[$i]->localtax1_tx;
1087
			$line->localtax2_tx		= $object->lines[$i]->localtax2_tx;
1088
			$line->qty				= $object->lines[$i]->qty;
1089
			$line->fk_remise_except	= $object->lines[$i]->fk_remise_except;
1090
			$line->remise_percent	= $object->lines[$i]->remise_percent;
1091
			$line->fk_product		= $object->lines[$i]->fk_product;
1092
			$line->info_bits		= $object->lines[$i]->info_bits;
1093
			$line->product_type		= $object->lines[$i]->product_type;
1094
			$line->rang				= $object->lines[$i]->rang;
1095
			$line->special_code		= $object->lines[$i]->special_code;
1096
			$line->fk_parent_line	= $object->lines[$i]->fk_parent_line;
1097
			$line->fk_unit			= $object->lines[$i]->fk_unit;
1098
			$line->date_start 		= $object->lines[$i]->date_start;
1099
			$line->date_end 		= $object->lines[$i]->date_end;
1100
1101
			$line->fk_fournprice	= $object->lines[$i]->fk_fournprice;
1102
			$marginInfos			= getMarginInfos($object->lines[$i]->subprice, $object->lines[$i]->remise_percent, $object->lines[$i]->tva_tx, $object->lines[$i]->localtax1_tx, $object->lines[$i]->localtax2_tx, $object->lines[$i]->fk_fournprice, $object->lines[$i]->pa_ht);
1103
			$line->pa_ht			= $marginInfos[0];
1104
1105
            // get extrafields from original line
1106
			$object->lines[$i]->fetch_optionals();
1107
			foreach($object->lines[$i]->array_options as $options_key => $value)
1108
				$line->array_options[$options_key] = $value;
1109
1110
			$this->lines[$i] = $line;
1111
		}
1112
1113
		$this->socid                = $object->socid;
1114
		$this->fk_project           = $object->fk_project;
1115
		$this->cond_reglement_id    = $object->cond_reglement_id;
1116
		$this->mode_reglement_id    = $object->mode_reglement_id;
1117
		$this->availability_id      = $object->availability_id;
1118
		$this->demand_reason_id     = $object->demand_reason_id;
1119
		$this->date_livraison       = $object->date_livraison;
1120
		$this->fk_delivery_address  = $object->fk_delivery_address;
1121
		$this->contact_id           = $object->contactid;
1122
		$this->ref_client           = $object->ref_client;
1123
		$this->note_private         = $object->note_private;
1124
		$this->note_public          = $object->note_public;
1125
1126
		$this->origin				= $object->element;
1127
		$this->origin_id			= $object->id;
1128
1129
        // get extrafields from original line
1130
		$object->fetch_optionals($object->id);
1131
		foreach($object->array_options as $options_key => $value)
1132
			$this->array_options[$options_key] = $value;
1133
1134
		// Possibility to add external linked objects with hooks
1135
		$this->linked_objects[$this->origin] = $this->origin_id;
1136
		if (! empty($object->other_linked_objects) && is_array($object->other_linked_objects))
1137
		{
1138
			$this->linked_objects = array_merge($this->linked_objects, $object->other_linked_objects);
1139
		}
1140
1141
		$ret = $this->create($user);
1142
1143
		if ($ret > 0)
1144
		{
1145
			// Actions hooked (by external module)
1146
			$hookmanager->initHooks(array('invoicedao'));
1147
1148
			$parameters=array('objFrom'=>$object);
1149
			$action='';
1150
			$reshook=$hookmanager->executeHooks('createFrom',$parameters,$this,$action);    // Note that $action and $object may have been modified by some hooks
1151
			if ($reshook < 0) $error++;
1152
1153
			if (! $error)
1154
			{
1155
				return 1;
1156
			}
1157
			else return -1;
1158
		}
1159
		else return -1;
1160
	}
1161
1162
	/**
1163
	 * Return link to download file from a direct external access
1164
	 *
1165
	 * @param	int				$withpicto			Add download picto into link
1166
	 * @return	string			HTML link to file
1167
	 */
1168
	function getDirectExternalLink($withpicto=0)
1169
	{
1170
		global $dolibarr_main_url_root;
1171
1172
		// Define $urlwithroot
1173
		$urlwithouturlroot=preg_replace('/'.preg_quote(DOL_URL_ROOT,'/').'$/i','',trim($dolibarr_main_url_root));
1174
		$urlwithroot=$urlwithouturlroot.DOL_URL_ROOT;		// This is to use external domain name found into config file
1175
		//$urlwithroot=DOL_MAIN_URL_ROOT;					// This is to use same domain name than current
1176
1177
		// TODO Read into ecmfile table to get entry and hash exists (PS: If not found, add it)
1178
		include_once DOL_DOCUMENT_ROOT.'/ecm/class/ecmfiles.class.php';
1179
		$ecmfile=new EcmFiles($this->db);
1180
		//$result = $ecmfile->get();
1181
1182
		$hashp='todo';
1183
		return '<a href="'.$urlwithroot.'/document.php?modulepart=invoice&hashp='.$hashp.'" target="_download" rel="noindex, nofollow">'.$this->ref.'</a>';
1184
	}
1185
1186
	/**
1187
	 *  Return clicable link of object (with eventually picto)
1188
	 *
1189
	 *  @param	int		$withpicto       			Add picto into link
1190
	 *  @param  string	$option          			Where point the link
1191
	 *  @param  int		$max             			Maxlength of ref
1192
	 *  @param  int		$short           			1=Return just URL
1193
	 *  @param  string  $moretitle       			Add more text to title tooltip
1194
     *  @param	int  	$notooltip		 			1=Disable tooltip
1195
     *  @param  int     $addlinktonotes  			1=Add link to notes
1196
     *  @param  int     $save_lastsearch_value		-1=Auto, 0=No save of lastsearch_values when clicking, 1=Save lastsearch_values whenclicking
1197
	 *  @return string 			         			String with URL
1198
	 */
1199
	function getNomUrl($withpicto=0, $option='', $max=0, $short=0, $moretitle='', $notooltip=0, $addlinktonotes=0, $save_lastsearch_value=-1)
1200
	{
1201
		global $langs, $conf, $user, $form;
1202
1203
		if (! empty($conf->dol_no_mouse_hover)) $notooltip=1;   // Force disable tooltips
1204
1205
		$result='';
1206
1207
		if ($option == 'withdraw') $url = DOL_URL_ROOT.'/compta/facture/prelevement.php?facid='.$this->id;
1208
		else $url = DOL_URL_ROOT.'/compta/facture/card.php?facid='.$this->id;
1209
1210
        if (!$user->rights->facture->lire)
1211
            $option = 'nolink';
1212
1213
		if ($option !== 'nolink')
1214
		{
1215
			// Add param to save lastsearch_values or not
1216
			$add_save_lastsearch_values=($save_lastsearch_value == 1 ? 1 : 0);
1217
			if ($save_lastsearch_value == -1 && preg_match('/list\.php/',$_SERVER["PHP_SELF"])) $add_save_lastsearch_values=1;
1218
			if ($add_save_lastsearch_values) $url.='&save_lastsearch_values=1';
1219
		}
1220
1221
		if ($short) return $url;
1222
1223
		$picto='bill';
1224
		if ($this->type == self::TYPE_REPLACEMENT) $picto.='r';	// Replacement invoice
1225
		if ($this->type == self::TYPE_CREDIT_NOTE) $picto.='a';	// Credit note
1226
		if ($this->type == self::TYPE_DEPOSIT) $picto.='d';	// Deposit invoice
1227
        $label='';
1228
1229
        if ($user->rights->facture->lire) {
1230
            $label = '<u>' . $langs->trans("ShowInvoice") . '</u>';
1231
            if ($this->type == self::TYPE_REPLACEMENT) $label='<u>' . $langs->transnoentitiesnoconv("ShowInvoiceReplace") . '</u>';
1232
            if ($this->type == self::TYPE_CREDIT_NOTE) $label='<u>' . $langs->transnoentitiesnoconv("ShowInvoiceAvoir") . '</u>';
1233
            if ($this->type == self::TYPE_DEPOSIT)     $label='<u>' . $langs->transnoentitiesnoconv("ShowInvoiceDeposit") . '</u>';
1234
            if ($this->type == self::TYPE_SITUATION)   $label='<u>' . $langs->transnoentitiesnoconv("ShowInvoiceSituation") . '</u>';
1235
            if (! empty($this->ref))
1236
                $label .= '<br><b>'.$langs->trans('Ref') . ':</b> ' . $this->ref;
1237
            if (! empty($this->ref_client))
1238
                $label .= '<br><b>' . $langs->trans('RefCustomer') . ':</b> ' . $this->ref_client;
1239
            if (! empty($this->total_ht))
1240
                $label.= '<br><b>' . $langs->trans('AmountHT') . ':</b> ' . price($this->total_ht, 0, $langs, 0, -1, -1, $conf->currency);
1241
            if (! empty($this->total_tva))
1242
                $label.= '<br><b>' . $langs->trans('VAT') . ':</b> ' . price($this->total_tva, 0, $langs, 0, -1, -1, $conf->currency);
1243
            if (! empty($this->total_localtax1) && $this->total_localtax1 != 0)		// We keep test != 0 because $this->total_localtax1 can be '0.00000000'
1244
                $label.= '<br><b>' . $langs->trans('LT1') . ':</b> ' . price($this->total_localtax1, 0, $langs, 0, -1, -1, $conf->currency);
1245
            if (! empty($this->total_localtax2) && $this->total_localtax2 != 0)
1246
                $label.= '<br><b>' . $langs->trans('LT2') . ':</b> ' . price($this->total_localtax2, 0, $langs, 0, -1, -1, $conf->currency);
1247
            if (! empty($this->total_ttc))
1248
                $label.= '<br><b>' . $langs->trans('AmountTTC') . ':</b> ' . price($this->total_ttc, 0, $langs, 0, -1, -1, $conf->currency);
1249
    		if ($moretitle) $label.=' - '.$moretitle;
1250
        }
1251
1252
		$linkclose='';
1253
		if (empty($notooltip) && $user->rights->facture->lire)
1254
		{
1255
		    if (! empty($conf->global->MAIN_OPTIMIZEFORTEXTBROWSER))
1256
		    {
1257
		        $label=$langs->trans("ShowInvoice");
1258
		        $linkclose.=' alt="'.dol_escape_htmltag($label, 1).'"';
1259
		    }
1260
		    $linkclose.= ' title="'.dol_escape_htmltag($label, 1).'"';
1261
		    $linkclose.=' class="classfortooltip"';
1262
		}
1263
1264
        $linkstart='<a href="'.$url.'"';
1265
        $linkstart.=$linkclose.'>';
1266
		$linkend='</a>';
1267
1268
        if ($option == 'nolink') {
1269
            $linkstart = '';
1270
            $linkend = '';
1271
        }
1272
1273
		$result .= $linkstart;
1274
		if ($withpicto) $result.=img_object(($notooltip?'':$label), $picto, ($notooltip?(($withpicto != 2) ? 'class="paddingright"' : ''):'class="'.(($withpicto != 2) ? 'paddingright ' : '').'classfortooltip"'), 0, 0, $notooltip?0:1);
1275
		if ($withpicto != 2) $result.= ($max?dol_trunc($this->ref,$max):$this->ref);
1276
		$result .= $linkend;
1277
1278
		if ($addlinktonotes)
1279
		{
1280
		    $txttoshow=($user->socid > 0 ? $this->note_public : $this->note_private);
1281
		    if ($txttoshow)
1282
		    {
1283
                $notetoshow=$langs->trans("ViewPrivateNote").':<br>'.dol_string_nohtmltag($txttoshow,1);
1284
    		    $result.=' <span class="note inline-block">';
1285
    		    $result.='<a href="'.DOL_URL_ROOT.'/compta/facture/note.php?id='.$this->id.'" class="classfortooltip" title="'.dol_escape_htmltag($notetoshow).'">';
1286
    		    $result.=img_picto('','note');
1287
    		    $result.='</a>';
1288
    		    //$result.=img_picto($langs->trans("ViewNote"),'object_generic');
1289
    		    //$result.='</a>';
1290
    		    $result.='</span>';
1291
		    }
1292
		}
1293
1294
		return $result;
1295
	}
1296
1297
	/**
1298
	 *	Get object and lines from database
1299
	 *
1300
	 *	@param      int		$rowid       	Id of object to load
1301
	 * 	@param		string	$ref			Reference of invoice
1302
	 * 	@param		string	$ref_ext		External reference of invoice
1303
	 * 	@param		int		$ref_int		Internal reference of other object
1304
	 *  @param		bool	$fetch_situation	Fetch the previous and next situation in $tab_previous_situation_invoice and $tab_next_situation_invoice
1305
	 *	@return     int         			>0 if OK, <0 if KO, 0 if not found
1306
	 */
1307
	function fetch($rowid, $ref='', $ref_ext='', $ref_int='', $fetch_situation=false)
1308
	{
1309
		global $conf;
1310
1311
		if (empty($rowid) && empty($ref) && empty($ref_ext) && empty($ref_int)) return -1;
1312
1313
		$sql = 'SELECT f.rowid,f.entity,f.ref,f.ref_client,f.ref_ext,f.ref_int,f.type,f.fk_soc,f.amount';
1314
		$sql.= ', f.tva, f.localtax1, f.localtax2, f.total, f.total_ttc, f.revenuestamp';
1315
		$sql.= ', f.remise_percent, f.remise_absolue, f.remise';
1316
		$sql.= ', f.datef as df, f.date_pointoftax';
1317
		$sql.= ', f.date_lim_reglement as dlr';
1318
		$sql.= ', f.datec as datec';
1319
		$sql.= ', f.date_valid as datev';
1320
		$sql.= ', f.tms as datem';
1321
		$sql.= ', f.note_private, f.note_public, f.fk_statut, f.paye, f.close_code, f.close_note, f.fk_user_author, f.fk_user_valid, f.model_pdf, f.last_main_doc';
1322
		$sql.= ', f.fk_facture_source';
1323
		$sql.= ', f.fk_mode_reglement, f.fk_cond_reglement, f.fk_projet, f.extraparams';
1324
		$sql.= ', f.situation_cycle_ref, f.situation_counter, f.situation_final';
1325
		$sql.= ', f.fk_account';
1326
		$sql.= ", f.fk_multicurrency, f.multicurrency_code, f.multicurrency_tx, f.multicurrency_total_ht, f.multicurrency_total_tva, f.multicurrency_total_ttc";
1327
		$sql.= ', p.code as mode_reglement_code, p.libelle as mode_reglement_libelle';
1328
		$sql.= ', c.code as cond_reglement_code, c.libelle as cond_reglement_libelle, c.libelle_facture as cond_reglement_libelle_doc';
1329
        $sql.= ', f.fk_incoterms, f.location_incoterms';
1330
        $sql.= ", i.libelle as libelle_incoterms";
1331
		$sql.= ' FROM '.MAIN_DB_PREFIX.'facture as f';
1332
		$sql.= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_payment_term as c ON f.fk_cond_reglement = c.rowid';
1333
		$sql.= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_paiement as p ON f.fk_mode_reglement = p.id';
1334
		$sql.= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_incoterms as i ON f.fk_incoterms = i.rowid';
1335
1336
		if ($rowid)   $sql.= " WHERE f.rowid=".$rowid;
1337
		else $sql.= ' WHERE f.entity IN ('.getEntity('invoice').')'; // Dont't use entity if you use rowid
1338
1339
		if ($ref)     $sql.= " AND f.ref='".$this->db->escape($ref)."'";
1340
		if ($ref_ext) $sql.= " AND f.ref_ext='".$this->db->escape($ref_ext)."'";
1341
		if ($ref_int) $sql.= " AND f.ref_int='".$this->db->escape($ref_int)."'";
1342
1343
		dol_syslog(get_class($this)."::fetch", LOG_DEBUG);
1344
		$result = $this->db->query($sql);
1345
		if ($result)
1346
		{
1347
			if ($this->db->num_rows($result))
1348
			{
1349
				$obj = $this->db->fetch_object($result);
1350
1351
				$this->id					= $obj->rowid;
1352
				$this->entity				= $obj->entity;
1353
1354
				$this->ref					= $obj->ref;
1355
				$this->ref_client			= $obj->ref_client;
1356
				$this->ref_ext				= $obj->ref_ext;
1357
				$this->ref_int				= $obj->ref_int;
1358
				$this->type					= $obj->type;
1359
				$this->date					= $this->db->jdate($obj->df);
1360
				$this->date_pointoftax		= $this->db->jdate($obj->date_pointoftax);
1361
				$this->date_creation		= $this->db->jdate($obj->datec);
1362
				$this->date_validation		= $this->db->jdate($obj->datev);
1363
				$this->date_modification		= $this->db->jdate($obj->datem);
1364
				$this->datem				= $this->db->jdate($obj->datem);
1365
				$this->remise_percent		= $obj->remise_percent;
1366
				$this->remise_absolue		= $obj->remise_absolue;
1367
				$this->total_ht				= $obj->total;
1368
				$this->total_tva			= $obj->tva;
1369
				$this->total_localtax1		= $obj->localtax1;
1370
				$this->total_localtax2		= $obj->localtax2;
1371
				$this->total_ttc			= $obj->total_ttc;
1372
				$this->revenuestamp         = $obj->revenuestamp;
1373
				$this->paye					= $obj->paye;
1374
				$this->close_code			= $obj->close_code;
1375
				$this->close_note			= $obj->close_note;
1376
				$this->socid				= $obj->fk_soc;
1377
				$this->statut				= $obj->fk_statut;
1378
				$this->date_lim_reglement	= $this->db->jdate($obj->dlr);
1379
				$this->mode_reglement_id	= $obj->fk_mode_reglement;
1380
				$this->mode_reglement_code	= $obj->mode_reglement_code;
1381
				$this->mode_reglement		= $obj->mode_reglement_libelle;
1382
				$this->cond_reglement_id	= $obj->fk_cond_reglement;
1383
				$this->cond_reglement_code	= $obj->cond_reglement_code;
1384
				$this->cond_reglement		= $obj->cond_reglement_libelle;
1385
				$this->cond_reglement_doc	= $obj->cond_reglement_libelle_doc;
1386
				$this->fk_account           = ($obj->fk_account>0)?$obj->fk_account:null;
1387
				$this->fk_project			= $obj->fk_projet;
1388
				$this->fk_facture_source	= $obj->fk_facture_source;
1389
				$this->note					= $obj->note_private;	// deprecated
1390
				$this->note_private			= $obj->note_private;
1391
				$this->note_public			= $obj->note_public;
1392
				$this->user_author			= $obj->fk_user_author;
1393
				$this->user_valid			= $obj->fk_user_valid;
1394
				$this->modelpdf				= $obj->model_pdf;
1395
				$this->last_main_doc		= $obj->last_main_doc;
1396
				$this->situation_cycle_ref  = $obj->situation_cycle_ref;
1397
				$this->situation_counter    = $obj->situation_counter;
1398
				$this->situation_final      = $obj->situation_final;
1399
				$this->extraparams			= (array) json_decode($obj->extraparams, true);
1400
1401
				//Incoterms
1402
				$this->fk_incoterms = $obj->fk_incoterms;
1403
				$this->location_incoterms = $obj->location_incoterms;
1404
				$this->libelle_incoterms = $obj->libelle_incoterms;
1405
1406
				// Multicurrency
1407
				$this->fk_multicurrency 		= $obj->fk_multicurrency;
1408
				$this->multicurrency_code 		= $obj->multicurrency_code;
1409
				$this->multicurrency_tx 		= $obj->multicurrency_tx;
1410
				$this->multicurrency_total_ht 	= $obj->multicurrency_total_ht;
1411
				$this->multicurrency_total_tva 	= $obj->multicurrency_total_tva;
1412
				$this->multicurrency_total_ttc 	= $obj->multicurrency_total_ttc;
1413
1414
				if (($this->type == self::TYPE_SITUATION || ($this->type == self::TYPE_CREDIT_NOTE && $this->situation_cycle_ref > 0))  && $fetch_situation)
0 ignored issues
show
Consider adding parentheses for clarity. Current Interpretation: ($this->type == self::TY... 0) && $fetch_situation, Probably Intended Meaning: $this->type == self::TYP... 0 && $fetch_situation)
Loading history...
1415
				{
1416
					$this->fetchPreviousNextSituationInvoice();
1417
				}
1418
1419
				if ($this->statut == self::STATUS_DRAFT)	$this->brouillon = 1;
1420
1421
				// Retreive all extrafield
1422
				// fetch optionals attributes and labels
1423
				$this->fetch_optionals();
1424
1425
				/*
1426
				 * Lines
1427
				 */
1428
1429
				$this->lines  = array();
1430
1431
				$result=$this->fetch_lines();
1432
				if ($result < 0)
1433
				{
1434
					$this->error=$this->db->error();
1435
					return -3;
1436
				}
1437
				return 1;
1438
			}
1439
			else
1440
			{
1441
				$this->error='Invoice with id='.$rowid.' or ref='.$ref.' or ref_ext='.$ref_ext.' not found';
1442
				dol_syslog(get_class($this)."::fetch Error ".$this->error, LOG_ERR);
1443
				return 0;
1444
			}
1445
		}
1446
		else
1447
		{
1448
			$this->error=$this->db->error();
1449
			return -1;
1450
		}
1451
	}
1452
1453
1454
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
1455
	/**
1456
	 *	Load all detailed lines into this->lines
1457
	 *
1458
	 *	@return     int         1 if OK, < 0 if KO
1459
	 */
1460
	function fetch_lines()
1461
	{
1462
        // phpcs:enable
1463
		$this->lines=array();
1464
1465
		$sql = 'SELECT l.rowid, l.fk_facture, l.fk_product, l.fk_parent_line, l.label as custom_label, l.description, l.product_type, l.price, l.qty, l.vat_src_code, l.tva_tx,';
1466
		$sql.= ' l.situation_percent, l.fk_prev_id,';
1467
		$sql.= ' l.localtax1_tx, l.localtax2_tx, l.localtax1_type, l.localtax2_type, l.remise_percent, l.fk_remise_except, l.subprice,';
1468
		$sql.= ' l.rang, l.special_code,';
1469
		$sql.= ' l.date_start as date_start, l.date_end as date_end,';
1470
		$sql.= ' l.info_bits, l.total_ht, l.total_tva, l.total_localtax1, l.total_localtax2, l.total_ttc, l.fk_code_ventilation, l.fk_product_fournisseur_price as fk_fournprice, l.buy_price_ht as pa_ht,';
1471
		$sql.= ' l.fk_unit,';
1472
		$sql.= ' l.fk_multicurrency, l.multicurrency_code, l.multicurrency_subprice, l.multicurrency_total_ht, l.multicurrency_total_tva, l.multicurrency_total_ttc,';
1473
		$sql.= ' p.ref as product_ref, p.fk_product_type as fk_product_type, p.label as product_label, p.description as product_desc';
1474
		$sql.= ' FROM '.MAIN_DB_PREFIX.'facturedet as l';
1475
		$sql.= ' LEFT JOIN '.MAIN_DB_PREFIX.'product as p ON l.fk_product = p.rowid';
1476
		$sql.= ' WHERE l.fk_facture = '.$this->id;
1477
		$sql.= ' ORDER BY l.rang, l.rowid';
1478
1479
		dol_syslog(get_class($this).'::fetch_lines', LOG_DEBUG);
1480
		$result = $this->db->query($sql);
1481
		if ($result)
1482
		{
1483
			$num = $this->db->num_rows($result);
1484
			$i = 0;
1485
			while ($i < $num)
1486
			{
1487
				$objp = $this->db->fetch_object($result);
1488
				$line = new FactureLigne($this->db);
1489
1490
				$line->id               = $objp->rowid;
1491
				$line->rowid	        = $objp->rowid;             // deprecated
1492
				$line->fk_facture       = $objp->fk_facture;
1493
				$line->label            = $objp->custom_label;		// deprecated
1494
				$line->desc             = $objp->description;		// Description line
1495
				$line->description      = $objp->description;		// Description line
1496
				$line->product_type     = $objp->product_type;		// Type of line
1497
				$line->ref              = $objp->product_ref;		// Ref product
1498
				$line->product_ref      = $objp->product_ref;		// Ref product
1499
				$line->libelle          = $objp->product_label;		// TODO deprecated
1500
				$line->product_label	= $objp->product_label;		// Label product
1501
				$line->product_desc     = $objp->product_desc;		// Description product
1502
				$line->fk_product_type  = $objp->fk_product_type;	// Type of product
1503
				$line->qty              = $objp->qty;
1504
				$line->subprice         = $objp->subprice;
1505
1506
                $line->vat_src_code     = $objp->vat_src_code;
1507
				$line->tva_tx           = $objp->tva_tx;
1508
				$line->localtax1_tx     = $objp->localtax1_tx;
1509
				$line->localtax2_tx     = $objp->localtax2_tx;
1510
				$line->localtax1_type   = $objp->localtax1_type;
1511
				$line->localtax2_type   = $objp->localtax2_type;
1512
				$line->remise_percent   = $objp->remise_percent;
1513
				$line->fk_remise_except = $objp->fk_remise_except;
1514
				$line->fk_product       = $objp->fk_product;
1515
				$line->date_start       = $this->db->jdate($objp->date_start);
1516
				$line->date_end         = $this->db->jdate($objp->date_end);
1517
				$line->date_start       = $this->db->jdate($objp->date_start);
1518
				$line->date_end         = $this->db->jdate($objp->date_end);
1519
				$line->info_bits        = $objp->info_bits;
1520
				$line->total_ht         = $objp->total_ht;
1521
				$line->total_tva        = $objp->total_tva;
1522
				$line->total_localtax1  = $objp->total_localtax1;
1523
				$line->total_localtax2  = $objp->total_localtax2;
1524
				$line->total_ttc        = $objp->total_ttc;
1525
				$line->code_ventilation = $objp->fk_code_ventilation;
1526
				$line->fk_fournprice 	= $objp->fk_fournprice;
1527
				$marginInfos			= getMarginInfos($objp->subprice, $objp->remise_percent, $objp->tva_tx, $objp->localtax1_tx, $objp->localtax2_tx, $line->fk_fournprice, $objp->pa_ht);
1528
				$line->pa_ht 			= $marginInfos[0];
1529
				$line->marge_tx			= $marginInfos[1];
1530
				$line->marque_tx		= $marginInfos[2];
1531
				$line->rang				= $objp->rang;
1532
				$line->special_code		= $objp->special_code;
1533
				$line->fk_parent_line	= $objp->fk_parent_line;
1534
				$line->situation_percent= $objp->situation_percent;
1535
				$line->fk_prev_id       = $objp->fk_prev_id;
1536
				$line->fk_unit	        = $objp->fk_unit;
1537
1538
				// Accountancy
1539
				$line->fk_accounting_account	= $objp->fk_code_ventilation;
1540
1541
				// Multicurrency
1542
				$line->fk_multicurrency 		= $objp->fk_multicurrency;
1543
				$line->multicurrency_code 		= $objp->multicurrency_code;
1544
				$line->multicurrency_subprice 	= $objp->multicurrency_subprice;
1545
				$line->multicurrency_total_ht 	= $objp->multicurrency_total_ht;
1546
				$line->multicurrency_total_tva 	= $objp->multicurrency_total_tva;
1547
				$line->multicurrency_total_ttc 	= $objp->multicurrency_total_ttc;
1548
1549
                                $line->fetch_optionals();
1550
1551
				$this->lines[$i] = $line;
1552
1553
				$i++;
1554
			}
1555
			$this->db->free($result);
1556
			return 1;
1557
		}
1558
		else
1559
		{
1560
			$this->error=$this->db->error();
1561
			return -3;
1562
		}
1563
	}
1564
1565
	/**
1566
	 * Fetch previous and next situations invoices
1567
	 *
1568
	 * @return	void
1569
	 */
1570
	function fetchPreviousNextSituationInvoice()
1571
	{
1572
		global $conf;
1573
1574
		$this->tab_previous_situation_invoice = array();
1575
		$this->tab_next_situation_invoice = array();
1576
1577
		$sql = 'SELECT rowid, situation_counter FROM '.MAIN_DB_PREFIX.'facture WHERE rowid <> '.$this->id.' AND entity = '.$conf->entity.' AND situation_cycle_ref = '.(int) $this->situation_cycle_ref.' ORDER BY situation_counter ASC';
1578
1579
		dol_syslog(get_class($this).'::fetchPreviousNextSituationInvoice ', LOG_DEBUG);
1580
		$result = $this->db->query($sql);
1581
		if ($result && $this->db->num_rows($result) > 0)
1582
		{
1583
			while ($objp = $this->db->fetch_object($result))
1584
			{
1585
				$invoice = new Facture($this->db);
1586
				if ($invoice->fetch($objp->rowid) > 0)
1587
				{
1588
				    if ($objp->situation_counter < $this->situation_counter
1589
				        || ($objp->situation_counter == $this->situation_counter && $objp->rowid < $this->id) // This case appear when there are credit notes
1590
				       )
1591
					{
1592
					    $this->tab_previous_situation_invoice[] = $invoice;
1593
					}
1594
					else
1595
					{
1596
					    $this->tab_next_situation_invoice[] = $invoice;
1597
					}
1598
				}
1599
			}
1600
		}
1601
	}
1602
1603
	/**
1604
	 *      Update database
1605
	 *
1606
	 *      @param      User	$user        	User that modify
1607
	 *      @param      int		$notrigger	    0=launch triggers after, 1=disable triggers
1608
	 *      @return     int      			   	<0 if KO, >0 if OK
1609
	 */
1610
	function update(User $user, $notrigger=0)
1611
	{
1612
		global $conf;
1613
1614
		$error=0;
1615
1616
		// Clean parameters
1617
		if (empty($this->type)) $this->type= self::TYPE_STANDARD;
1618
		if (isset($this->ref)) $this->ref=trim($this->ref);
1619
		if (isset($this->ref_client)) $this->ref_client=trim($this->ref_client);
1620
		if (isset($this->increment)) $this->increment=trim($this->increment);
1621
		if (isset($this->close_code)) $this->close_code=trim($this->close_code);
1622
		if (isset($this->close_note)) $this->close_note=trim($this->close_note);
1623
		if (isset($this->note) || isset($this->note_private)) $this->note=(isset($this->note) ? trim($this->note) : trim($this->note_private));		// deprecated
1624
		if (isset($this->note) || isset($this->note_private)) $this->note_private=(isset($this->note_private) ? trim($this->note_private) : trim($this->note));
1625
		if (isset($this->note_public)) $this->note_public=trim($this->note_public);
1626
		if (isset($this->modelpdf)) $this->modelpdf=trim($this->modelpdf);
1627
		if (isset($this->import_key)) $this->import_key=trim($this->import_key);
1628
1629
		// Check parameters
1630
		// Put here code to add control on parameters values
1631
1632
		// Update request
1633
		$sql = "UPDATE ".MAIN_DB_PREFIX."facture SET";
1634
		$sql.= " ref=".(isset($this->ref)?"'".$this->db->escape($this->ref)."'":"null").",";
1635
		$sql.= " type=".(isset($this->type)?$this->db->escape($this->type):"null").",";
1636
		$sql.= " ref_client=".(isset($this->ref_client)?"'".$this->db->escape($this->ref_client)."'":"null").",";
1637
		$sql.= " increment=".(isset($this->increment)?"'".$this->db->escape($this->increment)."'":"null").",";
1638
		$sql.= " fk_soc=".(isset($this->socid)?$this->db->escape($this->socid):"null").",";
1639
		$sql.= " datec=".(strval($this->date_creation)!='' ? "'".$this->db->idate($this->date_creation)."'" : 'null').",";
1640
		$sql.= " datef=".(strval($this->date)!='' ? "'".$this->db->idate($this->date)."'" : 'null').",";
1641
		$sql.= " date_pointoftax=".(strval($this->date_pointoftax)!='' ? "'".$this->db->idate($this->date_pointoftax)."'" : 'null').",";
1642
		$sql.= " date_valid=".(strval($this->date_validation)!='' ? "'".$this->db->idate($this->date_validation)."'" : 'null').",";
1643
		$sql.= " paye=".(isset($this->paye)?$this->db->escape($this->paye):"null").",";
1644
		$sql.= " remise_percent=".(isset($this->remise_percent)?$this->db->escape($this->remise_percent):"null").",";
1645
		$sql.= " remise_absolue=".(isset($this->remise_absolue)?$this->db->escape($this->remise_absolue):"null").",";
1646
		$sql.= " close_code=".(isset($this->close_code)?"'".$this->db->escape($this->close_code)."'":"null").",";
1647
		$sql.= " close_note=".(isset($this->close_note)?"'".$this->db->escape($this->close_note)."'":"null").",";
1648
		$sql.= " tva=".(isset($this->total_tva)?$this->total_tva:"null").",";
1649
		$sql.= " localtax1=".(isset($this->total_localtax1)?$this->total_localtax1:"null").",";
1650
		$sql.= " localtax2=".(isset($this->total_localtax2)?$this->total_localtax2:"null").",";
1651
		$sql.= " total=".(isset($this->total_ht)?$this->total_ht:"null").",";
1652
		$sql.= " total_ttc=".(isset($this->total_ttc)?$this->total_ttc:"null").",";
1653
		$sql.= " revenuestamp=".((isset($this->revenuestamp) && $this->revenuestamp != '')?$this->db->escape($this->revenuestamp):"null").",";
1654
		$sql.= " fk_statut=".(isset($this->statut)?$this->db->escape($this->statut):"null").",";
1655
		$sql.= " fk_user_author=".(isset($this->user_author)?$this->db->escape($this->user_author):"null").",";
1656
		$sql.= " fk_user_valid=".(isset($this->fk_user_valid)?$this->db->escape($this->fk_user_valid):"null").",";
1657
		$sql.= " fk_facture_source=".(isset($this->fk_facture_source)?$this->db->escape($this->fk_facture_source):"null").",";
1658
		$sql.= " fk_projet=".(isset($this->fk_project)?$this->db->escape($this->fk_project):"null").",";
1659
		$sql.= " fk_cond_reglement=".(isset($this->cond_reglement_id)?$this->db->escape($this->cond_reglement_id):"null").",";
1660
		$sql.= " fk_mode_reglement=".(isset($this->mode_reglement_id)?$this->db->escape($this->mode_reglement_id):"null").",";
1661
		$sql.= " date_lim_reglement=".(strval($this->date_lim_reglement)!='' ? "'".$this->db->idate($this->date_lim_reglement)."'" : 'null').",";
1662
		$sql.= " note_private=".(isset($this->note_private)?"'".$this->db->escape($this->note_private)."'":"null").",";
1663
		$sql.= " note_public=".(isset($this->note_public)?"'".$this->db->escape($this->note_public)."'":"null").",";
1664
		$sql.= " model_pdf=".(isset($this->modelpdf)?"'".$this->db->escape($this->modelpdf)."'":"null").",";
1665
		$sql.= " import_key=".(isset($this->import_key)?"'".$this->db->escape($this->import_key)."'":"null").",";
1666
		$sql.= " situation_cycle_ref=".(empty($this->situation_cycle_ref)?"null":$this->db->escape($this->situation_cycle_ref)).",";
1667
		$sql.= " situation_counter=".(empty($this->situation_counter)?"null":$this->db->escape($this->situation_counter)).",";
1668
		$sql.= " situation_final=".(empty($this->situation_counter)?"0":$this->db->escape($this->situation_counter));
1669
		$sql.= " WHERE rowid=".$this->id;
1670
1671
		$this->db->begin();
1672
1673
		dol_syslog(get_class($this)."::update", LOG_DEBUG);
1674
		$resql = $this->db->query($sql);
1675
		if (! $resql) {
1676
			$error++; $this->errors[]="Error ".$this->db->lasterror();
1677
		}
1678
1679
		if (! $error && empty($conf->global->MAIN_EXTRAFIELDS_DISABLED) && is_array($this->array_options) && count($this->array_options)>0)
1680
		{
1681
			$result=$this->insertExtraFields();
1682
			if ($result < 0)
1683
			{
1684
				$error++;
1685
			}
1686
		}
1687
1688
		if (! $error && ! $notrigger)
1689
		{
1690
			// Call trigger
1691
			$result=$this->call_trigger('BILL_MODIFY',$user);
1692
			if ($result < 0) $error++;
1693
			// End call triggers
1694
		}
1695
1696
		// Commit or rollback
1697
		if ($error)
1698
		{
1699
			foreach($this->errors as $errmsg)
1700
			{
1701
				dol_syslog(get_class($this)."::update ".$errmsg, LOG_ERR);
1702
				$this->error.=($this->error?', '.$errmsg:$errmsg);
1703
			}
1704
			$this->db->rollback();
1705
			return -1*$error;
1706
		}
1707
		else
1708
		{
1709
			$this->db->commit();
1710
			return 1;
1711
		}
1712
	}
1713
1714
1715
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
1716
	/**
1717
	 *    Add a discount line into an invoice (as an invoice line) using an existing absolute discount (Consume the discount)
1718
	 *
1719
	 *    @param     int	$idremise	Id of absolute discount
1720
	 *    @return    int          		>0 if OK, <0 if KO
1721
	 */
1722
	function insert_discount($idremise)
1723
	{
1724
        // phpcs:enable
1725
		global $langs;
1726
1727
		include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
1728
		include_once DOL_DOCUMENT_ROOT.'/core/class/discount.class.php';
1729
1730
		$this->db->begin();
1731
1732
		$remise=new DiscountAbsolute($this->db);
1733
		$result=$remise->fetch($idremise);
1734
1735
		if ($result > 0)
1736
		{
1737
			if ($remise->fk_facture)	// Protection against multiple submission
1738
			{
1739
				$this->error=$langs->trans("ErrorDiscountAlreadyUsed");
1740
				$this->db->rollback();
1741
				return -5;
1742
			}
1743
1744
			$facligne=new FactureLigne($this->db);
1745
			$facligne->fk_facture=$this->id;
1746
			$facligne->fk_remise_except=$remise->id;
1747
			$facligne->desc=$remise->description;   	// Description ligne
1748
			$facligne->vat_src_code=$remise->vat_src_code;
1749
			$facligne->tva_tx=$remise->tva_tx;
1750
			$facligne->subprice = -$remise->amount_ht;
1751
			$facligne->fk_product=0;					// Id produit predefini
1752
			$facligne->qty=1;
1753
			$facligne->remise_percent=0;
1754
			$facligne->rang=-1;
1755
			$facligne->info_bits=2;
1756
1757
			// Get buy/cost price of invoice that is source of discount
1758
			if ($remise->fk_facture_source > 0)
1759
			{
1760
    			$srcinvoice=new Facture($this->db);
1761
    			$srcinvoice->fetch($remise->fk_facture_source);
1762
    			$totalcostpriceofinvoice=0;
1763
    			include_once DOL_DOCUMENT_ROOT.'/core/class/html.formmargin.class.php';  // TODO Move this into commonobject
1764
    			$formmargin=new FormMargin($this->db);
1765
    			$arraytmp=$formmargin->getMarginInfosArray($srcinvoice, false);
1766
        		$facligne->pa_ht = $arraytmp['pa_total'];
1767
			}
1768
1769
			$facligne->total_ht  = -$remise->amount_ht;
1770
			$facligne->total_tva = -$remise->amount_tva;
1771
			$facligne->total_ttc = -$remise->amount_ttc;
1772
1773
			$facligne->multicurrency_subprice = -$remise->multicurrency_subprice;
1774
			$facligne->multicurrency_total_ht = -$remise->multicurrency_amount_ht;
1775
			$facligne->multicurrency_total_tva = -$remise->multicurrency_amount_tva;
1776
			$facligne->multicurrency_total_ttc = -$remise->multicurrency_amount_ttc;
1777
1778
			$lineid=$facligne->insert();
1779
			if ($lineid > 0)
1780
			{
1781
				$result=$this->update_price(1);
1782
				if ($result > 0)
1783
				{
1784
					// Create link between discount and invoice line
1785
					$result=$remise->link_to_invoice($lineid,0);
1786
					if ($result < 0)
1787
					{
1788
						$this->error=$remise->error;
1789
						$this->db->rollback();
1790
						return -4;
1791
					}
1792
1793
					$this->db->commit();
1794
					return 1;
1795
				}
1796
				else
1797
				{
1798
					$this->error=$facligne->error;
1799
					$this->db->rollback();
1800
					return -1;
1801
				}
1802
			}
1803
			else
1804
			{
1805
				$this->error=$facligne->error;
1806
				$this->db->rollback();
1807
				return -2;
1808
			}
1809
		}
1810
		else
1811
		{
1812
			$this->db->rollback();
1813
			return -3;
1814
		}
1815
	}
1816
1817
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
1818
	/**
1819
	 *	Set customer ref
1820
	 *
1821
	 *	@param     	string	$ref_client		Customer ref
1822
	 *  @param     	int		$notrigger		1=Does not execute triggers, 0= execute triggers
1823
	 *	@return		int						<0 if KO, >0 if OK
1824
	 */
1825
	function set_ref_client($ref_client, $notrigger=0)
1826
	{
1827
        // phpcs:enable
1828
	    global $user;
1829
1830
		$error=0;
1831
1832
		$this->db->begin();
1833
1834
		$sql = 'UPDATE '.MAIN_DB_PREFIX.'facture';
1835
		if (empty($ref_client))
1836
			$sql .= ' SET ref_client = NULL';
1837
		else
1838
			$sql .= ' SET ref_client = \''.$this->db->escape($ref_client).'\'';
1839
		$sql .= ' WHERE rowid = '.$this->id;
1840
1841
		dol_syslog(__METHOD__.' this->id='.$this->id.', ref_client='.$ref_client, LOG_DEBUG);
1842
		$resql=$this->db->query($sql);
1843
		if (!$resql)
1844
		{
1845
			$this->errors[]=$this->db->error();
1846
			$error++;
1847
		}
1848
1849
		if (! $error)
1850
		{
1851
			$this->ref_client = $ref_client;
1852
		}
1853
1854
		if (! $notrigger && empty($error))
1855
		{
1856
			// Call trigger
1857
			$result=$this->call_trigger('BILL_MODIFY',$user);
1858
			if ($result < 0) $error++;
1859
			// End call triggers
1860
		}
1861
1862
		if (! $error)
1863
		{
1864
1865
			$this->ref_client = $ref_client;
1866
1867
			$this->db->commit();
1868
			return 1;
1869
		}
1870
		else
1871
		{
1872
			foreach($this->errors as $errmsg)
1873
			{
1874
				dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
1875
				$this->error.=($this->error?', '.$errmsg:$errmsg);
1876
			}
1877
			$this->db->rollback();
1878
			return -1*$error;
1879
		}
1880
	}
1881
1882
	/**
1883
	 *	Delete invoice
1884
	 *
1885
	 *	@param     	User	$user      	    User making the deletion.
1886
	 *	@param		int		$notrigger		1=Does not execute triggers, 0= execute triggers
1887
	 *	@param		int		$idwarehouse	Id warehouse to use for stock change.
1888
	 *	@return		int						<0 if KO, 0=Refused, >0 if OK
1889
	 */
1890
	function delete($user, $notrigger=0, $idwarehouse=-1)
1891
	{
1892
		global $langs,$conf;
1893
		require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
1894
1895
		$rowid=$this->id;
1896
1897
		dol_syslog(get_class($this)."::delete rowid=".$rowid.", ref=".$this->ref.", thirdparty=".$this->thirdparty->name, LOG_DEBUG);
1898
1899
		// Test to avoid invoice deletion (allowed if draft)
1900
		$result = $this->is_erasable();
1901
1902
		if ($result <= 0) return 0;
1903
1904
		$error=0;
1905
1906
		$this->db->begin();
1907
1908
		if (! $error && ! $notrigger)
1909
		{
1910
            // Call trigger
1911
            $result=$this->call_trigger('BILL_DELETE',$user);
1912
            if ($result < 0) $error++;
1913
            // End call triggers
1914
		}
1915
1916
		// Removed extrafields
1917
		if (! $error) {
1918
			$result=$this->deleteExtraFields();
1919
			if ($result < 0)
1920
			{
1921
				$error++;
1922
				dol_syslog(get_class($this)."::delete error deleteExtraFields ".$this->error, LOG_ERR);
1923
			}
1924
		}
1925
1926
		if (! $error)
1927
		{
1928
			// Delete linked object
1929
			$res = $this->deleteObjectLinked();
1930
			if ($res < 0) $error++;
1931
		}
1932
1933
		if (! $error)
1934
		{
1935
			// If invoice was converted into a discount not yet consumed, we remove discount
1936
			$sql = 'DELETE FROM '.MAIN_DB_PREFIX.'societe_remise_except';
1937
			$sql.= ' WHERE fk_facture_source = '.$rowid;
1938
			$sql.= ' AND fk_facture_line IS NULL';
1939
			$resql=$this->db->query($sql);
1940
1941
			// If invoice has consumned discounts
1942
			$this->fetch_lines();
1943
			$list_rowid_det=array();
1944
			foreach($this->lines as $key => $invoiceline)
1945
			{
1946
				$list_rowid_det[]=$invoiceline->rowid;
1947
			}
1948
1949
			// Consumned discounts are freed
1950
			if (count($list_rowid_det))
1951
			{
1952
				$sql = 'UPDATE '.MAIN_DB_PREFIX.'societe_remise_except';
1953
				$sql.= ' SET fk_facture = NULL, fk_facture_line = NULL';
1954
				$sql.= ' WHERE fk_facture_line IN ('.join(',',$list_rowid_det).')';
1955
1956
				dol_syslog(get_class($this)."::delete", LOG_DEBUG);
1957
				if (! $this->db->query($sql))
1958
				{
1959
					$this->error=$this->db->error()." sql=".$sql;
1960
					$this->db->rollback();
1961
					return -5;
1962
				}
1963
			}
1964
1965
			// If we decrement stock on invoice validation, we increment
1966
			if ($this->type != self::TYPE_DEPOSIT && $result >= 0 && ! empty($conf->stock->enabled) && ! empty($conf->global->STOCK_CALCULATE_ON_BILL) && $idwarehouse!=-1)
1967
			{
1968
				require_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
1969
				$langs->load("agenda");
1970
1971
				$num=count($this->lines);
1972
				for ($i = 0; $i < $num; $i++)
1973
				{
1974
					if ($this->lines[$i]->fk_product > 0)
1975
					{
1976
						$mouvP = new MouvementStock($this->db);
1977
						$mouvP->origin = &$this;
1978
						// We decrease stock for product
1979
						if ($this->type == self::TYPE_CREDIT_NOTE) $result=$mouvP->livraison($user, $this->lines[$i]->fk_product, $idwarehouse, $this->lines[$i]->qty, $this->lines[$i]->subprice, $langs->trans("InvoiceDeleteDolibarr",$this->ref));
1980
						else $result=$mouvP->reception($user, $this->lines[$i]->fk_product, $idwarehouse, $this->lines[$i]->qty, 0, $langs->trans("InvoiceDeleteDolibarr",$this->ref));	// we use 0 for price, to not change the weighted average value
1981
					}
1982
				}
1983
			}
1984
1985
1986
			// Delete invoice line
1987
			$sql = 'DELETE FROM '.MAIN_DB_PREFIX.'facturedet WHERE fk_facture = '.$rowid;
1988
1989
			dol_syslog(get_class($this)."::delete", LOG_DEBUG);
1990
1991
			if ($this->db->query($sql) && $this->delete_linked_contact())
1992
			{
1993
				$sql = 'DELETE FROM '.MAIN_DB_PREFIX.'facture WHERE rowid = '.$rowid;
1994
1995
				dol_syslog(get_class($this)."::delete", LOG_DEBUG);
1996
1997
				$resql=$this->db->query($sql);
1998
				if ($resql)
1999
				{
2000
					// On efface le repertoire de pdf provisoire
2001
					$ref = dol_sanitizeFileName($this->ref);
2002
					if ($conf->facture->dir_output && !empty($this->ref))
2003
					{
2004
						$dir = $conf->facture->dir_output . "/" . $ref;
2005
						$file = $conf->facture->dir_output . "/" . $ref . "/" . $ref . ".pdf";
2006
						if (file_exists($file))	// We must delete all files before deleting directory
2007
						{
2008
							$ret=dol_delete_preview($this);
2009
2010
							if (! dol_delete_file($file,0,0,0,$this)) // For triggers
2011
							{
2012
								$langs->load("errors");
2013
								$this->error=$langs->trans("ErrorFailToDeleteFile",$file);
2014
								$this->db->rollback();
2015
								return 0;
2016
							}
2017
						}
2018
						if (file_exists($dir))
2019
						{
2020
							if (! dol_delete_dir_recursive($dir)) // For remove dir and meta
2021
							{
2022
								$langs->load("errors");
2023
								$this->error=$langs->trans("ErrorFailToDeleteDir",$dir);
2024
								$this->db->rollback();
2025
								return 0;
2026
							}
2027
						}
2028
					}
2029
2030
					$this->db->commit();
2031
					return 1;
2032
				}
2033
				else
2034
				{
2035
					$this->error=$this->db->lasterror()." sql=".$sql;
2036
					$this->db->rollback();
2037
					return -6;
2038
				}
2039
			}
2040
			else
2041
			{
2042
				$this->error=$this->db->lasterror()." sql=".$sql;
2043
				$this->db->rollback();
2044
				return -4;
2045
			}
2046
		}
2047
		else
2048
		{
2049
			$this->db->rollback();
2050
			return -2;
2051
		}
2052
	}
2053
2054
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
2055
	/**
2056
	 *  Tag la facture comme paye completement (si close_code non renseigne) => this->fk_statut=2, this->paye=1
2057
	 *  ou partiellement (si close_code renseigne) + appel trigger BILL_PAYED => this->fk_statut=2, this->paye stay 0
2058
	 *
2059
	 *  @param	User	$user      	Objet utilisateur qui modifie
2060
	 *	@param  string	$close_code	Code renseigne si on classe a payee completement alors que paiement incomplet (cas escompte par exemple)
2061
	 *	@param  string	$close_note	Commentaire renseigne si on classe a payee alors que paiement incomplet (cas escompte par exemple)
2062
	 *  @return int         		<0 if KO, >0 if OK
2063
	 */
2064
	function set_paid($user, $close_code='', $close_note='')
2065
	{
2066
        // phpcs:enable
2067
		$error=0;
2068
2069
		if ($this->paye != 1)
2070
		{
2071
			$this->db->begin();
2072
2073
			dol_syslog(get_class($this)."::set_paid rowid=".$this->id, LOG_DEBUG);
2074
2075
			$sql = 'UPDATE '.MAIN_DB_PREFIX.'facture SET';
2076
			$sql.= ' fk_statut='.self::STATUS_CLOSED;
2077
			if (! $close_code) $sql.= ', paye=1';
2078
			if ($close_code) $sql.= ", close_code='".$this->db->escape($close_code)."'";
2079
			if ($close_note) $sql.= ", close_note='".$this->db->escape($close_note)."'";
2080
			$sql.= ' WHERE rowid = '.$this->id;
2081
2082
			$resql = $this->db->query($sql);
2083
			if ($resql)
2084
			{
2085
	            // Call trigger
2086
	            $result=$this->call_trigger('BILL_PAYED',$user);
2087
	            if ($result < 0) $error++;
2088
	            // End call triggers
2089
			}
2090
			else
2091
			{
2092
				$error++;
2093
				$this->error=$this->db->lasterror();
2094
			}
2095
2096
			if (! $error)
2097
			{
2098
				$this->db->commit();
2099
				return 1;
2100
			}
2101
			else
2102
			{
2103
				$this->db->rollback();
2104
				return -1;
2105
			}
2106
		}
2107
		else
2108
		{
2109
			return 0;
2110
		}
2111
	}
2112
2113
2114
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
2115
	/**
2116
	 *  Tag la facture comme non payee completement + appel trigger BILL_UNPAYED
2117
	 *	Fonction utilisee quand un paiement prelevement est refuse,
2118
	 * 	ou quand une facture annulee et reouverte.
2119
	 *
2120
	 *  @param	User	$user       Object user that change status
2121
	 *  @return int         		<0 if KO, >0 if OK
2122
	 */
2123
	function set_unpaid($user)
2124
	{
2125
        // phpcs:enable
2126
		$error=0;
2127
2128
		$this->db->begin();
2129
2130
		$sql = 'UPDATE '.MAIN_DB_PREFIX.'facture';
2131
		$sql.= ' SET paye=0, fk_statut='.self::STATUS_VALIDATED.', close_code=null, close_note=null';
2132
		$sql.= ' WHERE rowid = '.$this->id;
2133
2134
		dol_syslog(get_class($this)."::set_unpaid", LOG_DEBUG);
2135
		$resql = $this->db->query($sql);
2136
		if ($resql)
2137
		{
2138
            // Call trigger
2139
            $result=$this->call_trigger('BILL_UNPAYED',$user);
2140
            if ($result < 0) $error++;
2141
            // End call triggers
2142
		}
2143
		else
2144
		{
2145
			$error++;
2146
			$this->error=$this->db->error();
2147
			dol_print_error($this->db);
2148
		}
2149
2150
		if (! $error)
2151
		{
2152
			$this->db->commit();
2153
			return 1;
2154
		}
2155
		else
2156
		{
2157
			$this->db->rollback();
2158
			return -1;
2159
		}
2160
	}
2161
2162
2163
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
2164
	/**
2165
	 *	Tag invoice as canceled, with no payment on it (example for replacement invoice or payment never received) + call trigger BILL_CANCEL
2166
	 *	Warning, if option to decrease stock on invoice was set, this function does not change stock (it might be a cancel because
2167
	 *  of no payment even if merchandises were sent).
2168
	 *
2169
	 *	@param	User	$user        	Object user making change
2170
	 *	@param	string	$close_code		Code of closing invoice (CLOSECODE_REPLACED, CLOSECODE_...)
2171
	 *	@param	string	$close_note		Comment
2172
	 *	@return int         			<0 if KO, >0 if OK
2173
	 */
2174
	function set_canceled($user, $close_code='', $close_note='')
2175
	{
2176
        // phpcs:enable
2177
2178
		dol_syslog(get_class($this)."::set_canceled rowid=".$this->id, LOG_DEBUG);
2179
2180
		$this->db->begin();
2181
2182
		$sql = 'UPDATE '.MAIN_DB_PREFIX.'facture SET';
2183
		$sql.= ' fk_statut='.self::STATUS_ABANDONED;
2184
		if ($close_code) $sql.= ", close_code='".$this->db->escape($close_code)."'";
2185
		if ($close_note) $sql.= ", close_note='".$this->db->escape($close_note)."'";
2186
		$sql.= ' WHERE rowid = '.$this->id;
2187
2188
		$resql = $this->db->query($sql);
2189
		if ($resql)
2190
		{
2191
			// On desaffecte de la facture les remises liees
2192
			// car elles n'ont pas ete utilisees vu que la facture est abandonnee.
2193
			$sql = 'UPDATE '.MAIN_DB_PREFIX.'societe_remise_except';
2194
			$sql.= ' SET fk_facture = NULL';
2195
			$sql.= ' WHERE fk_facture = '.$this->id;
2196
2197
			$resql=$this->db->query($sql);
2198
			if ($resql)
2199
			{
2200
	            // Call trigger
2201
	            $result=$this->call_trigger('BILL_CANCEL',$user);
2202
	            if ($result < 0)
2203
	            {
2204
					$this->db->rollback();
2205
					return -1;
2206
				}
2207
	            // End call triggers
2208
2209
				$this->db->commit();
2210
				return 1;
2211
			}
2212
			else
2213
			{
2214
				$this->error=$this->db->error()." sql=".$sql;
2215
				$this->db->rollback();
2216
				return -1;
2217
			}
2218
		}
2219
		else
2220
		{
2221
			$this->error=$this->db->error()." sql=".$sql;
2222
			$this->db->rollback();
2223
			return -2;
2224
		}
2225
	}
2226
2227
	/**
2228
	 * Tag invoice as validated + call trigger BILL_VALIDATE
2229
	 * Object must have lines loaded with fetch_lines
2230
	 *
2231
	 * @param	User	$user           Object user that validate
2232
	 * @param   string	$force_number	Reference to force on invoice
2233
	 * @param	int		$idwarehouse	Id of warehouse to use for stock decrease if option to decreasenon stock is on (0=no decrease)
2234
	 * @param	int		$notrigger		1=Does not execute triggers, 0= execute triggers
2235
     * @return	int						<0 if KO, 0=Nothing done because invoice is not a draft, >0 if OK
2236
	 */
2237
	function validate($user, $force_number='', $idwarehouse=0, $notrigger=0)
2238
	{
2239
		global $conf,$langs;
2240
		require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
2241
2242
		$now=dol_now();
2243
2244
		$error=0;
2245
		dol_syslog(get_class($this).'::validate user='.$user->id.', force_number='.$force_number.', idwarehouse='.$idwarehouse);
2246
2247
		// Force to have object complete for checks
2248
		$this->fetch_thirdparty();
2249
		$this->fetch_lines();
2250
2251
		// Check parameters
2252
		if (! $this->brouillon)
2253
		{
2254
			dol_syslog(get_class($this)."::validate no draft status", LOG_WARNING);
2255
			return 0;
2256
		}
2257
		if (count($this->lines) <= 0)
2258
		{
2259
        	$langs->load("errors");
2260
			$this->error=$langs->trans("ErrorObjectMustHaveLinesToBeValidated", $this->ref);
2261
			return -1;
2262
		}
2263
		if ((empty($conf->global->MAIN_USE_ADVANCED_PERMS) && empty($user->rights->facture->creer))
2264
       	|| (! empty($conf->global->MAIN_USE_ADVANCED_PERMS) && empty($user->rights->facture->invoice_advance->validate)))
2265
		{
2266
			$this->error='Permission denied';
2267
			dol_syslog(get_class($this)."::validate ".$this->error.' MAIN_USE_ADVANCED_PERMS='.$conf->global->MAIN_USE_ADVANCED_PERMS, LOG_ERR);
2268
			return -1;
2269
		}
2270
2271
		$this->db->begin();
2272
2273
		// Check parameters
2274
		if ($this->type == self::TYPE_REPLACEMENT)		// si facture de remplacement
2275
		{
2276
			// Controle que facture source connue
2277
			if ($this->fk_facture_source <= 0)
2278
			{
2279
				$this->error=$langs->trans("ErrorFieldRequired",$langs->trans("InvoiceReplacement"));
2280
				$this->db->rollback();
2281
				return -10;
2282
			}
2283
2284
			// Charge la facture source a remplacer
2285
			$facreplaced=new Facture($this->db);
2286
			$result=$facreplaced->fetch($this->fk_facture_source);
2287
			if ($result <= 0)
2288
			{
2289
				$this->error=$langs->trans("ErrorBadInvoice");
2290
				$this->db->rollback();
2291
				return -11;
2292
			}
2293
2294
			// Controle que facture source non deja remplacee par une autre
2295
			$idreplacement=$facreplaced->getIdReplacingInvoice('validated');
2296
			if ($idreplacement && $idreplacement != $this->id)
2297
			{
2298
				$facreplacement=new Facture($this->db);
2299
				$facreplacement->fetch($idreplacement);
2300
				$this->error=$langs->trans("ErrorInvoiceAlreadyReplaced",$facreplaced->ref,$facreplacement->ref);
2301
				$this->db->rollback();
2302
				return -12;
2303
			}
2304
2305
			$result=$facreplaced->set_canceled($user, self::CLOSECODE_REPLACED, '');
2306
			if ($result < 0)
2307
			{
2308
				$this->error=$facreplaced->error;
2309
				$this->db->rollback();
2310
				return -13;
2311
			}
2312
		}
2313
2314
		// Define new ref
2315
		if ($force_number)
2316
		{
2317
			$num = $force_number;
2318
		}
2319
		else if (preg_match('/^[\(]?PROV/i', $this->ref) || empty($this->ref)) // empty should not happened, but when it occurs, the test save life
2320
		{
2321
			if (! empty($conf->global->FAC_FORCE_DATE_VALIDATION))	// If option enabled, we force invoice date
2322
			{
2323
				$this->date=dol_now();
2324
				$this->date_lim_reglement=$this->calculate_date_lim_reglement();
2325
			}
2326
			$num = $this->getNextNumRef($this->thirdparty);
2327
		}
2328
		else
2329
		{
2330
			$num = $this->ref;
2331
		}
2332
		$this->newref = $num;
2333
2334
		if ($num)
2335
		{
2336
			$this->update_price(1);
2337
2338
			// Validate
2339
			$sql = 'UPDATE '.MAIN_DB_PREFIX.'facture';
2340
			$sql.= " SET ref='".$num."', fk_statut = ".self::STATUS_VALIDATED.", fk_user_valid = ".($user->id > 0 ? $user->id : "null").", date_valid = '".$this->db->idate($now)."'";
2341
			if (! empty($conf->global->FAC_FORCE_DATE_VALIDATION))	// If option enabled, we force invoice date
2342
			{
2343
				$sql.= ", datef='".$this->db->idate($this->date)."'";
2344
				$sql.= ", date_lim_reglement='".$this->db->idate($this->date_lim_reglement)."'";
2345
			}
2346
			$sql.= ' WHERE rowid = '.$this->id;
2347
2348
			dol_syslog(get_class($this)."::validate", LOG_DEBUG);
2349
			$resql=$this->db->query($sql);
2350
			if (! $resql)
2351
			{
2352
				dol_print_error($this->db);
2353
				$error++;
2354
			}
2355
2356
			// On verifie si la facture etait une provisoire
2357
			if (! $error && (preg_match('/^[\(]?PROV/i', $this->ref)))
2358
			{
2359
				// La verif qu'une remise n'est pas utilisee 2 fois est faite au moment de l'insertion de ligne
2360
			}
2361
2362
			if (! $error)
2363
			{
2364
				// Define third party as a customer
2365
				$result=$this->thirdparty->set_as_client();
2366
2367
				// Si active on decremente le produit principal et ses composants a la validation de facture
2368
				if ($this->type != self::TYPE_DEPOSIT && $result >= 0 && ! empty($conf->stock->enabled) && ! empty($conf->global->STOCK_CALCULATE_ON_BILL) && $idwarehouse > 0)
2369
				{
2370
					require_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
2371
					$langs->load("agenda");
2372
2373
					// Loop on each line
2374
					$cpt=count($this->lines);
2375
					for ($i = 0; $i < $cpt; $i++)
2376
					{
2377
						if ($this->lines[$i]->fk_product > 0)
2378
						{
2379
							$mouvP = new MouvementStock($this->db);
2380
							$mouvP->origin = &$this;
2381
							// We decrease stock for product
2382
							if ($this->type == self::TYPE_CREDIT_NOTE) $result=$mouvP->reception($user, $this->lines[$i]->fk_product, $idwarehouse, $this->lines[$i]->qty, 0, $langs->trans("InvoiceValidatedInDolibarr",$num));
2383
							else $result=$mouvP->livraison($user, $this->lines[$i]->fk_product, $idwarehouse, $this->lines[$i]->qty, $this->lines[$i]->subprice, $langs->trans("InvoiceValidatedInDolibarr",$num));
2384
							if ($result < 0) {
2385
								$error++;
2386
								$this->error = $mouvP->error;
2387
							}
2388
						}
2389
					}
2390
				}
2391
			}
2392
2393
			// Trigger calls
2394
			if (! $error && ! $notrigger)
2395
			{
2396
	            // Call trigger
2397
	            $result=$this->call_trigger('BILL_VALIDATE',$user);
2398
	            if ($result < 0) $error++;
2399
	            // End call triggers
2400
			}
2401
2402
			if (! $error)
2403
			{
2404
				$this->oldref = $this->ref;
2405
2406
				// Rename directory if dir was a temporary ref
2407
				if (preg_match('/^[\(]?PROV/i', $this->ref))
2408
				{
2409
					// Rename of object directory ($this->ref = old ref, $num = new ref)
2410
					// to  not lose the linked files
2411
					$oldref = dol_sanitizeFileName($this->ref);
2412
					$newref = dol_sanitizeFileName($num);
2413
					$dirsource = $conf->facture->dir_output.'/'.$oldref;
2414
					$dirdest = $conf->facture->dir_output.'/'.$newref;
2415
					if (file_exists($dirsource))
2416
					{
2417
						dol_syslog(get_class($this)."::validate rename dir ".$dirsource." into ".$dirdest);
2418
2419
						if (@rename($dirsource, $dirdest))
2420
						{
2421
							dol_syslog("Rename ok");
2422
	                        // Rename docs starting with $oldref with $newref
2423
	                        $listoffiles=dol_dir_list($conf->facture->dir_output.'/'.$newref, 'files', 1, '^'.preg_quote($oldref,'/'));
2424
	                        foreach($listoffiles as $fileentry)
2425
	                        {
2426
	                        	$dirsource=$fileentry['name'];
2427
	                        	$dirdest=preg_replace('/^'.preg_quote($oldref,'/').'/',$newref, $dirsource);
2428
	                        	$dirsource=$fileentry['path'].'/'.$dirsource;
2429
	                        	$dirdest=$fileentry['path'].'/'.$dirdest;
2430
	                        	@rename($dirsource, $dirdest);
2431
	                        }
2432
						}
2433
					}
2434
				}
2435
			}
2436
2437
			if (! $error && !$this->is_last_in_cycle())
2438
			{
2439
				if (! $this->updatePriceNextInvoice($langs))
2440
				{
2441
					$error++;
2442
				}
2443
			}
2444
2445
			// Set new ref and define current status
2446
			if (! $error)
2447
			{
2448
				$this->ref = $num;
2449
				$this->ref=$num;
2450
				$this->statut= self::STATUS_VALIDATED;
2451
				$this->brouillon=0;
2452
				$this->date_validation=$now;
2453
				$i = 0;
2454
2455
                if (!empty($conf->global->INVOICE_USE_SITUATION))
2456
                {
2457
                	$final = true;
2458
    				$nboflines = count($this->lines);
2459
    				while (($i < $nboflines) && $final) {
2460
    					$final = ($this->lines[$i]->situation_percent == 100);
2461
    					$i++;
2462
    				}
2463
2464
    				if (empty($final)) $this->situation_final = 0;
2465
    				else $this->situation_final = 1;
2466
2467
				$this->setFinal($user);
2468
                }
2469
			}
2470
		}
2471
		else
2472
		{
2473
			$error++;
2474
		}
2475
2476
		if (! $error)
2477
		{
2478
			$this->db->commit();
2479
			return 1;
2480
		}
2481
		else
2482
		{
2483
			$this->db->rollback();
2484
			return -1;
2485
		}
2486
	}
2487
2488
	/**
2489
	 * Update price of next invoice
2490
	 *
2491
	 * @param	Translate	$langs	Translate object
2492
	 * @return bool		false if KO, true if OK
2493
	 */
2494
	function updatePriceNextInvoice(&$langs)
2495
	{
2496
		foreach ($this->tab_next_situation_invoice as $next_invoice)
2497
		{
2498
			$is_last = $next_invoice->is_last_in_cycle();
2499
2500
			if ($next_invoice->brouillon && $is_last != 1)
2501
			{
2502
				$this->error = $langs->trans('updatePriceNextInvoiceErrorUpdateline', $next_invoice->ref);
2503
				return false;
2504
			}
2505
2506
			$next_invoice->brouillon = 1;
2507
			foreach ($next_invoice->lines as $line)
2508
			{
2509
				$result = $next_invoice->updateline($line->id, $line->desc, $line->subprice, $line->qty, $line->remise_percent,
2510
														$line->date_start, $line->date_end, $line->tva_tx, $line->localtax1_tx, $line->localtax2_tx, 'HT', $line->info_bits, $line->product_type,
2511
														$line->fk_parent_line, 0, $line->fk_fournprice, $line->pa_ht, $line->label, $line->special_code, $line->array_options, $line->situation_percent,
2512
														$line->fk_unit);
2513
2514
				if ($result < 0)
2515
				{
2516
					$this->error = $langs->trans('updatePriceNextInvoiceErrorUpdateline', $next_invoice->ref);
2517
					return false;
2518
				}
2519
			}
2520
2521
			break; // Only the next invoice and not each next invoice
2522
		}
2523
2524
		return true;
2525
	}
2526
2527
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
2528
	/**
2529
	 *	Set draft status
2530
	 *
2531
	 *	@param	User	$user			Object user that modify
2532
	 *	@param	int		$idwarehouse	Id warehouse to use for stock change.
2533
	 *	@return	int						<0 if KO, >0 if OK
2534
	 */
2535
	function set_draft($user,$idwarehouse=-1)
2536
	{
2537
        // phpcs:enable
2538
		global $conf,$langs;
2539
2540
		$error=0;
2541
2542
		if ($this->statut == self::STATUS_DRAFT)
2543
		{
2544
			dol_syslog(get_class($this)."::set_draft already draft status", LOG_WARNING);
2545
			return 0;
2546
		}
2547
2548
		$this->db->begin();
2549
2550
		$sql = "UPDATE ".MAIN_DB_PREFIX."facture";
2551
		$sql.= " SET fk_statut = ".self::STATUS_DRAFT;
2552
		$sql.= " WHERE rowid = ".$this->id;
2553
2554
		dol_syslog(get_class($this)."::set_draft", LOG_DEBUG);
2555
		$result=$this->db->query($sql);
2556
		if ($result)
2557
		{
2558
			// Si on decremente le produit principal et ses composants a la validation de facture, on réincrement
2559
			if ($this->type != self::TYPE_DEPOSIT && $result >= 0 && ! empty($conf->stock->enabled) && ! empty($conf->global->STOCK_CALCULATE_ON_BILL))
2560
			{
2561
				require_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
2562
				$langs->load("agenda");
2563
2564
				$num=count($this->lines);
2565
				for ($i = 0; $i < $num; $i++)
2566
				{
2567
					if ($this->lines[$i]->fk_product > 0)
2568
					{
2569
						$mouvP = new MouvementStock($this->db);
2570
						$mouvP->origin = &$this;
2571
						// We decrease stock for product
2572
						if ($this->type == self::TYPE_CREDIT_NOTE) $result=$mouvP->livraison($user, $this->lines[$i]->fk_product, $idwarehouse, $this->lines[$i]->qty, $this->lines[$i]->subprice, $langs->trans("InvoiceBackToDraftInDolibarr",$this->ref));
2573
						else $result=$mouvP->reception($user, $this->lines[$i]->fk_product, $idwarehouse, $this->lines[$i]->qty, 0, $langs->trans("InvoiceBackToDraftInDolibarr",$this->ref));	// we use 0 for price, to not change the weighted average value
2574
					}
2575
				}
2576
			}
2577
2578
			if ($error == 0)
2579
			{
2580
				$old_statut=$this->statut;
2581
				$this->brouillon = 1;
2582
				$this->statut = self::STATUS_DRAFT;
2583
	            // Call trigger
2584
	            $result=$this->call_trigger('BILL_UNVALIDATE',$user);
2585
	            if ($result < 0)
2586
				{
2587
					$error++;
2588
					$this->statut=$old_statut;
2589
					$this->brouillon=0;
2590
				}
2591
	            // End call triggers
2592
			} else {
2593
				$this->db->rollback();
2594
				return -1;
2595
			}
2596
2597
			if ($error == 0)
2598
			{
2599
				$this->db->commit();
2600
				return 1;
2601
			}
2602
			else
2603
			{
2604
				$this->db->rollback();
2605
				return -1;
2606
			}
2607
		}
2608
		else
2609
		{
2610
			$this->error=$this->db->error();
2611
			$this->db->rollback();
2612
			return -1;
2613
		}
2614
	}
2615
2616
2617
	/**
2618
	 * 		Add an invoice line into database (linked to product/service or not).
2619
	 * 		Les parametres sont deja cense etre juste et avec valeurs finales a l'appel
2620
	 *		de cette methode. Aussi, pour le taux tva, il doit deja avoir ete defini
2621
	 *		par l'appelant par la methode get_default_tva(societe_vendeuse,societe_acheteuse,produit)
2622
	 *		et le desc doit deja avoir la bonne valeur (a l'appelant de gerer le multilangue)
2623
	 *
2624
	 * 		@param    	string		$desc            	Description of line
2625
	 * 		@param    	double		$pu_ht              Unit price without tax (> 0 even for credit note)
2626
	 * 		@param    	double		$qty             	Quantity
2627
	 * 		@param    	double		$txtva           	Force Vat rate, -1 for auto (Can contain the vat_src_code too with syntax '9.9 (CODE)')
2628
	 * 		@param		double		$txlocaltax1		Local tax 1 rate (deprecated, use instead txtva with code inside)
2629
	 *  	@param		double		$txlocaltax2		Local tax 2 rate (deprecated, use instead txtva with code inside)
2630
	 *		@param    	int			$fk_product      	Id of predefined product/service
2631
	 * 		@param    	double		$remise_percent  	Percent of discount on line
2632
	 * 		@param    	int			$date_start      	Date start of service
2633
	 * 		@param    	int			$date_end        	Date end of service
2634
	 * 		@param    	int			$ventil          	Code of dispatching into accountancy
2635
	 * 		@param    	int			$info_bits			Bits de type de lignes
2636
	 *		@param    	int			$fk_remise_except	Id discount used
2637
	 *		@param		string		$price_base_type	'HT' or 'TTC'
2638
	 * 		@param    	double		$pu_ttc             Unit price with tax (> 0 even for credit note)
2639
	 * 		@param		int			$type				Type of line (0=product, 1=service). Not used if fk_product is defined, the type of product is used.
2640
	 *      @param      int			$rang               Position of line
2641
	 *      @param		int			$special_code		Special code (also used by externals modules!)
2642
	 *      @param		string		$origin				'order', ...
2643
	 *      @param		int			$origin_id			Id of origin object
2644
	 *      @param		int			$fk_parent_line		Id of parent line
2645
	 * 		@param		int			$fk_fournprice		Supplier price id (to calculate margin) or ''
2646
	 * 		@param		int			$pa_ht				Buying price of line (to calculate margin) or ''
2647
	 * 		@param		string		$label				Label of the line (deprecated, do not use)
2648
	 *		@param		array		$array_options		extrafields array
2649
	 *      @param      int         $situation_percent  Situation advance percentage
2650
	 *      @param      int         $fk_prev_id         Previous situation line id reference
2651
	 * 		@param 		string		$fk_unit 			Code of the unit to use. Null to use the default one
2652
	 * 		@param		double		$pu_ht_devise		Unit price in currency
2653
	 *    	@return    	int             				<0 if KO, Id of line if OK
2654
	 */
2655
	function addline($desc, $pu_ht, $qty, $txtva, $txlocaltax1=0, $txlocaltax2=0, $fk_product=0, $remise_percent=0, $date_start='', $date_end='', $ventil=0, $info_bits=0, $fk_remise_except='', $price_base_type='HT', $pu_ttc=0, $type=self::TYPE_STANDARD, $rang=-1, $special_code=0, $origin='', $origin_id=0, $fk_parent_line=0, $fk_fournprice=null, $pa_ht=0, $label='', $array_options=0, $situation_percent=100, $fk_prev_id=0, $fk_unit = null, $pu_ht_devise = 0)
2656
	{
2657
		// Deprecation warning
2658
		if ($label) {
2659
			dol_syslog(__METHOD__ . ": using line label is deprecated", LOG_WARNING);
2660
			//var_dump(debug_backtrace(false));exit;
2661
		}
2662
2663
		global $mysoc, $conf, $langs;
2664
2665
		dol_syslog(get_class($this)."::addline id=$this->id,desc=$desc,pu_ht=$pu_ht,qty=$qty,txtva=$txtva, txlocaltax1=$txlocaltax1, txlocaltax2=$txlocaltax2, fk_product=$fk_product,remise_percent=$remise_percent,date_start=$date_start,date_end=$date_end,ventil=$ventil,info_bits=$info_bits,fk_remise_except=$fk_remise_except,price_base_type=$price_base_type,pu_ttc=$pu_ttc,type=$type, fk_unit=$fk_unit", LOG_DEBUG);
2666
		if (! empty($this->brouillon))
2667
		{
2668
			include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
2669
2670
			// Clean parameters
2671
			if (empty($remise_percent)) $remise_percent=0;
2672
			if (empty($qty)) $qty=0;
2673
			if (empty($info_bits)) $info_bits=0;
2674
			if (empty($rang)) $rang=0;
2675
			if (empty($ventil)) $ventil=0;
2676
			if (empty($txtva)) $txtva=0;
2677
			if (empty($txlocaltax1)) $txlocaltax1=0;
2678
			if (empty($txlocaltax2)) $txlocaltax2=0;
2679
			if (empty($fk_parent_line) || $fk_parent_line < 0) $fk_parent_line=0;
2680
			if (empty($fk_prev_id)) $fk_prev_id = 'null';
2681
			if (! isset($situation_percent) || $situation_percent > 100 || (string) $situation_percent == '') $situation_percent = 100;
2682
2683
			$remise_percent=price2num($remise_percent);
2684
			$qty=price2num($qty);
2685
			$pu_ht=price2num($pu_ht);
2686
			$pu_ht_devise=price2num($pu_ht_devise);
2687
			$pu_ttc=price2num($pu_ttc);
2688
			$pa_ht=price2num($pa_ht);
2689
			if (!preg_match('/\((.*)\)/', $txtva)) {
2690
				$txtva = price2num($txtva);               // $txtva can have format '5.0(XXX)' or '5'
2691
			}
2692
			$txlocaltax1=price2num($txlocaltax1);
2693
			$txlocaltax2=price2num($txlocaltax2);
2694
2695
			if ($price_base_type=='HT')
2696
			{
2697
				$pu=$pu_ht;
2698
			}
2699
			else
2700
			{
2701
				$pu=$pu_ttc;
2702
			}
2703
2704
			// Check parameters
2705
			if ($type < 0) return -1;
2706
2707
			$this->db->begin();
2708
2709
			$product_type=$type;
2710
			if (!empty($fk_product))
2711
			{
2712
				$product=new Product($this->db);
2713
				$result=$product->fetch($fk_product);
2714
				$product_type=$product->type;
2715
2716
				if (! empty($conf->global->STOCK_MUST_BE_ENOUGH_FOR_INVOICE) && $product_type == 0 && $product->stock_reel < $qty) {
2717
                    $langs->load("errors");
2718
				    $this->error=$langs->trans('ErrorStockIsNotEnoughToAddProductOnInvoice', $product->ref);
2719
					$this->db->rollback();
2720
					return -3;
2721
				}
2722
			}
2723
2724
			$localtaxes_type=getLocalTaxesFromRate($txtva, 0, $this->thirdparty, $mysoc);
2725
2726
			// Clean vat code
2727
			$vat_src_code='';
2728
			if (preg_match('/\((.*)\)/', $txtva, $reg))
2729
			{
2730
				$vat_src_code = $reg[1];
2731
				$txtva = preg_replace('/\s*\(.*\)/', '', $txtva);    // Remove code into vatrate.
2732
			}
2733
2734
			// Calcul du total TTC et de la TVA pour la ligne a partir de
2735
			// qty, pu, remise_percent et txtva
2736
			// TRES IMPORTANT: C'est au moment de l'insertion ligne qu'on doit stocker
2737
			// la part ht, tva et ttc, et ce au niveau de la ligne qui a son propre taux tva.
2738
2739
			$tabprice = calcul_price_total($qty, $pu, $remise_percent, $txtva, $txlocaltax1, $txlocaltax2, 0, $price_base_type, $info_bits, $product_type, $mysoc, $localtaxes_type, $situation_percent, $this->multicurrency_tx, $pu_ht_devise);
2740
2741
			$total_ht  = $tabprice[0];
2742
			$total_tva = $tabprice[1];
2743
			$total_ttc = $tabprice[2];
2744
			$total_localtax1 = $tabprice[9];
2745
			$total_localtax2 = $tabprice[10];
2746
			$pu_ht = $tabprice[3];
2747
2748
			// MultiCurrency
2749
			$multicurrency_total_ht  = $tabprice[16];
2750
            $multicurrency_total_tva = $tabprice[17];
2751
            $multicurrency_total_ttc = $tabprice[18];
2752
			$pu_ht_devise = $tabprice[19];
2753
2754
			// Rank to use
2755
			$rangtouse = $rang;
2756
			if ($rangtouse == -1)
2757
			{
2758
				$rangmax = $this->line_max($fk_parent_line);
2759
				$rangtouse = $rangmax + 1;
2760
			}
2761
2762
			// Insert line
2763
			$this->line=new FactureLigne($this->db);
2764
2765
			$this->line->context = $this->context;
2766
2767
			$this->line->fk_facture=$this->id;
2768
			$this->line->label=$label;	// deprecated
2769
			$this->line->desc=$desc;
2770
2771
			$this->line->qty=            ($this->type==self::TYPE_CREDIT_NOTE?abs($qty):$qty);	    // For credit note, quantity is always positive and unit price negative
2772
			$this->line->subprice=       ($this->type==self::TYPE_CREDIT_NOTE?-abs($pu_ht):$pu_ht); // For credit note, unit price always negative, always positive otherwise
2773
2774
			$this->line->vat_src_code=$vat_src_code;
2775
			$this->line->tva_tx=$txtva;
2776
			$this->line->localtax1_tx=($total_localtax1?$localtaxes_type[1]:0);
2777
			$this->line->localtax2_tx=($total_localtax2?$localtaxes_type[3]:0);
2778
			$this->line->localtax1_type = $localtaxes_type[0];
2779
			$this->line->localtax2_type = $localtaxes_type[2];
2780
2781
			$this->line->total_ht=       (($this->type==self::TYPE_CREDIT_NOTE||$qty<0)?-abs($total_ht):$total_ht);    // For credit note and if qty is negative, total is negative
2782
			$this->line->total_ttc=      (($this->type==self::TYPE_CREDIT_NOTE||$qty<0)?-abs($total_ttc):$total_ttc);  // For credit note and if qty is negative, total is negative
2783
			$this->line->total_tva=      (($this->type==self::TYPE_CREDIT_NOTE||$qty<0)?-abs($total_tva):$total_tva);  // For credit note and if qty is negative, total is negative
2784
			$this->line->total_localtax1=(($this->type==self::TYPE_CREDIT_NOTE||$qty<0)?-abs($total_localtax1):$total_localtax1);  // For credit note and if qty is negative, total is negative
2785
			$this->line->total_localtax2=(($this->type==self::TYPE_CREDIT_NOTE||$qty<0)?-abs($total_localtax2):$total_localtax2);  // For credit note and if qty is negative, total is negative
2786
2787
			$this->line->fk_product=$fk_product;
2788
			$this->line->product_type=$product_type;
2789
			$this->line->remise_percent=$remise_percent;
2790
			$this->line->date_start=$date_start;
2791
			$this->line->date_end=$date_end;
2792
			$this->line->ventil=$ventil;
2793
			$this->line->rang=$rangtouse;
2794
			$this->line->info_bits=$info_bits;
2795
			$this->line->fk_remise_except=$fk_remise_except;
2796
2797
			$this->line->special_code=$special_code;
2798
			$this->line->fk_parent_line=$fk_parent_line;
2799
			$this->line->origin=$origin;
2800
			$this->line->origin_id=$origin_id;
2801
			$this->line->situation_percent = $situation_percent;
2802
			$this->line->fk_prev_id = $fk_prev_id;
2803
			$this->line->fk_unit=$fk_unit;
2804
2805
			// infos marge
2806
			$this->line->fk_fournprice = $fk_fournprice;
2807
			$this->line->pa_ht = $pa_ht;
2808
2809
			// Multicurrency
2810
			$this->line->fk_multicurrency			= $this->fk_multicurrency;
2811
			$this->line->multicurrency_code			= $this->multicurrency_code;
2812
			$this->line->multicurrency_subprice		= $pu_ht_devise;
2813
			$this->line->multicurrency_total_ht 	= $multicurrency_total_ht;
2814
            $this->line->multicurrency_total_tva 	= $multicurrency_total_tva;
2815
            $this->line->multicurrency_total_ttc 	= $multicurrency_total_ttc;
2816
2817
			if (is_array($array_options) && count($array_options)>0) {
2818
				$this->line->array_options=$array_options;
2819
			}
2820
2821
			$result=$this->line->insert();
2822
			if ($result > 0)
2823
			{
2824
				// Reorder if child line
2825
				if (! empty($fk_parent_line)) $this->line_order(true,'DESC');
2826
2827
				// Mise a jour informations denormalisees au niveau de la facture meme
2828
				$result=$this->update_price(1,'auto',0,$mysoc);	// The addline method is designed to add line from user input so total calculation with update_price must be done using 'auto' mode.
2829
2830
				if ($result > 0)
2831
				{
2832
					$this->db->commit();
2833
					return $this->line->id;
2834
				}
2835
				else
2836
				{
2837
					$this->error=$this->db->lasterror();
2838
					$this->db->rollback();
2839
					return -1;
2840
				}
2841
			}
2842
			else
2843
			{
2844
				$this->error=$this->line->error;
2845
				$this->db->rollback();
2846
				return -2;
2847
			}
2848
		}
2849
		else
2850
		{
2851
			dol_syslog(get_class($this)."::addline status of order must be Draft to allow use of ->addline()", LOG_ERR);
2852
			return -3;
2853
		}
2854
	}
2855
2856
	/**
2857
	 *  Update a detail line
2858
	 *
2859
	 *  @param     	int			$rowid           	Id of line to update
2860
	 *  @param     	string		$desc            	Description of line
2861
	 *  @param     	double		$pu              	Prix unitaire (HT ou TTC selon price_base_type) (> 0 even for credit note lines)
2862
	 *  @param     	double		$qty             	Quantity
2863
	 *  @param     	double		$remise_percent  	Pourcentage de remise de la ligne
2864
	 *  @param     	int		$date_start      	Date de debut de validite du service
2865
	 *  @param     	int		$date_end        	Date de fin de validite du service
2866
	 *  @param     	double		$txtva          	VAT Rate (Can be '8.5', '8.5 (ABC)')
2867
	 * 	@param		double		$txlocaltax1		Local tax 1 rate
2868
	 *  @param		double		$txlocaltax2		Local tax 2 rate
2869
	 * 	@param     	string		$price_base_type 	HT or TTC
2870
	 * 	@param     	int			$info_bits 		    Miscellaneous informations
2871
	 * 	@param		int			$type				Type of line (0=product, 1=service)
2872
	 * 	@param		int			$fk_parent_line		Id of parent line (0 in most cases, used by modules adding sublevels into lines).
2873
	 * 	@param		int			$skip_update_total	Keep fields total_xxx to 0 (used for special lines by some modules)
2874
	 * 	@param		int			$fk_fournprice		Id of origin supplier price
2875
	 * 	@param		int			$pa_ht				Price (without tax) of product when it was bought
2876
	 * 	@param		string		$label				Label of the line (deprecated, do not use)
2877
	 * 	@param		int			$special_code		Special code (also used by externals modules!)
2878
     *  @param		array		$array_options		extrafields array
2879
	 * 	@param      int         $situation_percent  Situation advance percentage
2880
	 * 	@param 		string		$fk_unit 			Code of the unit to use. Null to use the default one
2881
	 * 	@param		double		$pu_ht_devise		Unit price in currency
2882
	 * 	@param		int			$notrigger			disable line update trigger
2883
	 *  @return    	int             				< 0 if KO, > 0 if OK
2884
	 */
2885
	function updateline($rowid, $desc, $pu, $qty, $remise_percent, $date_start, $date_end, $txtva, $txlocaltax1=0, $txlocaltax2=0, $price_base_type='HT', $info_bits=0, $type= self::TYPE_STANDARD, $fk_parent_line=0, $skip_update_total=0, $fk_fournprice=null, $pa_ht=0, $label='', $special_code=0, $array_options=0, $situation_percent=100, $fk_unit = null, $pu_ht_devise = 0, $notrigger=0)
2886
	{
2887
		global $conf,$user;
2888
		// Deprecation warning
2889
		if ($label) {
2890
			dol_syslog(__METHOD__ . ": using line label is deprecated", LOG_WARNING);
2891
		}
2892
2893
		include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
2894
2895
		global $mysoc,$langs;
2896
2897
		dol_syslog(get_class($this)."::updateline rowid=$rowid, desc=$desc, pu=$pu, qty=$qty, remise_percent=$remise_percent, date_start=$date_start, date_end=$date_end, txtva=$txtva, txlocaltax1=$txlocaltax1, txlocaltax2=$txlocaltax2, price_base_type=$price_base_type, info_bits=$info_bits, type=$type, fk_parent_line=$fk_parent_line pa_ht=$pa_ht, special_code=$special_code, fk_unit=$fk_unit, pu_ht_devise=$pu_ht_devise", LOG_DEBUG);
2898
2899
		if ($this->brouillon)
2900
		{
2901
			if (!$this->is_last_in_cycle() && empty($this->error))
2902
			{
2903
				if (!$this->checkProgressLine($rowid, $situation_percent))
2904
				{
2905
					if (!$this->error) $this->error=$langs->trans('invoiceLineProgressError');
2906
					return -3;
2907
				}
2908
			}
2909
2910
			$this->db->begin();
2911
2912
			// Clean parameters
2913
			if (empty($qty)) $qty=0;
2914
			if (empty($fk_parent_line) || $fk_parent_line < 0) $fk_parent_line=0;
2915
			if (empty($special_code) || $special_code == 3) $special_code=0;
2916
			if (! isset($situation_percent) || $situation_percent > 100 || (string) $situation_percent == '') $situation_percent = 100;
2917
2918
			$remise_percent	= price2num($remise_percent);
2919
			$qty			= price2num($qty);
2920
			$pu 			= price2num($pu);
2921
        	$pu_ht_devise	= price2num($pu_ht_devise);
2922
			$pa_ht			= price2num($pa_ht);
2923
			$txtva			= price2num($txtva);
2924
			$txlocaltax1	= price2num($txlocaltax1);
2925
			$txlocaltax2	= price2num($txlocaltax2);
2926
2927
			// Check parameters
2928
			if ($type < 0) return -1;
2929
2930
			// Calculate total with, without tax and tax from qty, pu, remise_percent and txtva
2931
			// TRES IMPORTANT: C'est au moment de l'insertion ligne qu'on doit stocker
2932
			// la part ht, tva et ttc, et ce au niveau de la ligne qui a son propre taux tva.
2933
2934
			$localtaxes_type=getLocalTaxesFromRate($txtva,0,$this->thirdparty, $mysoc);
2935
2936
			// Clean vat code
2937
    		$vat_src_code='';
2938
    		if (preg_match('/\((.*)\)/', $txtva, $reg))
2939
    		{
2940
    		    $vat_src_code = $reg[1];
2941
    		    $txtva = preg_replace('/\s*\(.*\)/', '', $txtva);    // Remove code into vatrate.
2942
    		}
2943
2944
			$tabprice=calcul_price_total($qty, $pu, $remise_percent, $txtva, $txlocaltax1, $txlocaltax2, 0, $price_base_type, $info_bits, $type, $mysoc, $localtaxes_type, $situation_percent, $this->multicurrency_tx, $pu_ht_devise);
2945
2946
			$total_ht  = $tabprice[0];
2947
			$total_tva = $tabprice[1];
2948
			$total_ttc = $tabprice[2];
2949
			$total_localtax1=$tabprice[9];
2950
			$total_localtax2=$tabprice[10];
2951
			$pu_ht  = $tabprice[3];
2952
			$pu_tva = $tabprice[4];
2953
			$pu_ttc = $tabprice[5];
2954
2955
			// MultiCurrency
2956
			$multicurrency_total_ht  = $tabprice[16];
2957
            $multicurrency_total_tva = $tabprice[17];
2958
            $multicurrency_total_ttc = $tabprice[18];
2959
			$pu_ht_devise = $tabprice[19];
2960
2961
			// Old properties: $price, $remise (deprecated)
2962
			$price = $pu;
2963
			$remise = 0;
2964
			if ($remise_percent > 0)
2965
			{
2966
				$remise = round(($pu * $remise_percent / 100),2);
2967
				$price = ($pu - $remise);
2968
			}
2969
			$price    = price2num($price);
2970
2971
			//Fetch current line from the database and then clone the object and set it in $oldline property
2972
			$line = new FactureLigne($this->db);
2973
			$line->fetch($rowid);
2974
2975
			if (!empty($line->fk_product))
2976
			{
2977
				$product=new Product($this->db);
2978
				$result=$product->fetch($line->fk_product);
2979
				$product_type=$product->type;
2980
2981
				if (! empty($conf->global->STOCK_MUST_BE_ENOUGH_FOR_INVOICE) && $product_type == 0 && $product->stock_reel < $qty) {
2982
                    $langs->load("errors");
2983
				    $this->error=$langs->trans('ErrorStockIsNotEnoughToAddProductOnInvoice', $product->ref);
2984
					$this->db->rollback();
2985
					return -3;
2986
				}
2987
			}
2988
2989
			$staticline = clone $line;
2990
2991
			$line->oldline = $staticline;
2992
			$this->line = $line;
2993
            $this->line->context = $this->context;
2994
2995
			// Reorder if fk_parent_line change
2996
			if (! empty($fk_parent_line) && ! empty($staticline->fk_parent_line) && $fk_parent_line != $staticline->fk_parent_line)
2997
			{
2998
				$rangmax = $this->line_max($fk_parent_line);
2999
				$this->line->rang = $rangmax + 1;
3000
			}
3001
3002
			$this->line->rowid				= $rowid;
3003
			$this->line->label				= $label;
3004
			$this->line->desc				= $desc;
3005
			$this->line->qty				= ($this->type==self::TYPE_CREDIT_NOTE?abs($qty):$qty);	// For credit note, quantity is always positive and unit price negative
3006
3007
			$this->line->vat_src_code       = $vat_src_code;
3008
			$this->line->tva_tx				= $txtva;
3009
			$this->line->localtax1_tx		= $txlocaltax1;
3010
			$this->line->localtax2_tx		= $txlocaltax2;
3011
			$this->line->localtax1_type		= $localtaxes_type[0];
3012
			$this->line->localtax2_type		= $localtaxes_type[2];
3013
3014
			$this->line->remise_percent		= $remise_percent;
3015
			$this->line->subprice			= ($this->type==2?-abs($pu_ht):$pu_ht); // For credit note, unit price always negative, always positive otherwise
3016
			$this->line->date_start			= $date_start;
3017
			$this->line->date_end			= $date_end;
3018
			$this->line->total_ht			= (($this->type==self::TYPE_CREDIT_NOTE||$qty<0)?-abs($total_ht):$total_ht);  // For credit note and if qty is negative, total is negative
3019
			$this->line->total_tva			= (($this->type==self::TYPE_CREDIT_NOTE||$qty<0)?-abs($total_tva):$total_tva);
3020
			$this->line->total_localtax1	= $total_localtax1;
3021
			$this->line->total_localtax2	= $total_localtax2;
3022
			$this->line->total_ttc			= (($this->type==self::TYPE_CREDIT_NOTE||$qty<0)?-abs($total_ttc):$total_ttc);
3023
			$this->line->info_bits			= $info_bits;
3024
			$this->line->special_code		= $special_code;
3025
			$this->line->product_type		= $type;
3026
			$this->line->fk_parent_line		= $fk_parent_line;
3027
			$this->line->skip_update_total	= $skip_update_total;
3028
			$this->line->situation_percent  = $situation_percent;
3029
			$this->line->fk_unit				= $fk_unit;
3030
3031
			$this->line->fk_fournprice = $fk_fournprice;
3032
			$this->line->pa_ht = $pa_ht;
3033
3034
			// Multicurrency
3035
			$this->line->multicurrency_subprice		= $pu_ht_devise;
3036
			$this->line->multicurrency_total_ht 	= $multicurrency_total_ht;
3037
            $this->line->multicurrency_total_tva 	= $multicurrency_total_tva;
3038
            $this->line->multicurrency_total_ttc 	= $multicurrency_total_ttc;
3039
3040
			if (is_array($array_options) && count($array_options)>0) {
3041
				$this->line->array_options=$array_options;
3042
			}
3043
3044
			$result=$this->line->update($user, $notrigger);
3045
			if ($result > 0)
3046
			{
3047
				// Reorder if child line
3048
				if (! empty($fk_parent_line)) $this->line_order(true,'DESC');
3049
3050
				// Mise a jour info denormalisees au niveau facture
3051
				$this->update_price(1);
3052
				$this->db->commit();
3053
				return $result;
3054
			}
3055
			else
3056
			{
3057
			    $this->error=$this->line->error;
3058
				$this->db->rollback();
3059
				return -1;
3060
			}
3061
		}
3062
		else
3063
		{
3064
			$this->error="Invoice statut makes operation forbidden";
3065
			return -2;
3066
		}
3067
	}
3068
3069
	/**
3070
	 * Check if the percent edited is lower of next invoice line
3071
	 *
3072
	 * @param	int		$idline				id of line to check
3073
	 * @param	float	$situation_percent	progress percentage need to be test
3074
	 * @return false if KO, true if OK
3075
	 */
3076
	function checkProgressLine($idline, $situation_percent)
3077
	{
3078
		$sql = 'SELECT fd.situation_percent FROM '.MAIN_DB_PREFIX.'facturedet fd
3079
				INNER JOIN '.MAIN_DB_PREFIX.'facture f ON (fd.fk_facture = f.rowid)
3080
				WHERE fd.fk_prev_id = '.$idline.'
3081
				AND f.fk_statut <> 0';
3082
3083
		$result = $this->db->query($sql);
3084
		if (! $result)
3085
		{
3086
			$this->error=$this->db->error();
3087
			return false;
3088
		}
3089
3090
		$obj = $this->db->fetch_object($result);
3091
3092
		if ($obj === null) return true;
3093
		else return $situation_percent < $obj->situation_percent;
3094
	}
3095
3096
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
3097
	/**
3098
	 * Update invoice line with percentage
3099
	 *
3100
	 * @param  FactureLigne $line       Invoice line
3101
	 * @param  int          $percent    Percentage
3102
	 * @return void
3103
	 */
3104
	function update_percent($line, $percent)
3105
	{
3106
        // phpcs:enable
3107
	    global $mysoc,$user;
3108
3109
		include_once DOL_DOCUMENT_ROOT . '/core/lib/price.lib.php';
3110
3111
		// Cap percentages to 100
3112
		if ($percent > 100) $percent = 100;
3113
		$line->situation_percent = $percent;
3114
		$tabprice = calcul_price_total($line->qty, $line->subprice, $line->remise_percent, $line->tva_tx, $line->localtax1_tx, $line->localtax2_tx, 0, 'HT', 0, $line->product_type, $mysoc, '', $percent);
3115
		$line->total_ht = $tabprice[0];
3116
		$line->total_tva = $tabprice[1];
3117
		$line->total_ttc = $tabprice[2];
3118
		$line->total_localtax1 = $tabprice[9];
3119
		$line->total_localtax2 = $tabprice[10];
3120
		$line->multicurrency_total_ht  = $tabprice[16];
3121
		$line->multicurrency_total_tva = $tabprice[17];
3122
		$line->multicurrency_total_ttc = $tabprice[18];
3123
		$line->update($user);
3124
		$this->update_price(1);
3125
		$this->db->commit();
3126
	}
3127
3128
	/**
3129
	 *	Delete line in database
3130
	 *
3131
	 *	@param		int		$rowid		Id of line to delete
3132
	 *	@return		int					<0 if KO, >0 if OK
3133
	 */
3134
	function deleteline($rowid)
3135
	{
3136
        global $user;
3137
3138
		dol_syslog(get_class($this)."::deleteline rowid=".$rowid, LOG_DEBUG);
3139
3140
		if (! $this->brouillon)
3141
		{
3142
			$this->error='ErrorDeleteLineNotAllowedByObjectStatus';
3143
			return -1;
3144
		}
3145
3146
		$this->db->begin();
3147
3148
		// Libere remise liee a ligne de facture
3149
		$sql = 'UPDATE '.MAIN_DB_PREFIX.'societe_remise_except';
3150
		$sql.= ' SET fk_facture_line = NULL';
3151
		$sql.= ' WHERE fk_facture_line = '.$rowid;
3152
3153
		dol_syslog(get_class($this)."::deleteline", LOG_DEBUG);
3154
		$result = $this->db->query($sql);
3155
		if (! $result)
3156
		{
3157
			$this->error=$this->db->error();
3158
			$this->db->rollback();
3159
			return -1;
3160
		}
3161
3162
		$line=new FactureLigne($this->db);
3163
3164
        $line->context = $this->context;
3165
3166
		// For triggers
3167
		$result = $line->fetch($rowid);
3168
		if (! ($result > 0)) dol_print_error($this->db, $line->error, $line->errors);
3169
3170
		if ($line->delete($user) > 0)
3171
		{
3172
			$result=$this->update_price(1);
3173
3174
			if ($result > 0)
3175
			{
3176
				$this->db->commit();
3177
				return 1;
3178
			}
3179
			else
3180
			{
3181
				$this->db->rollback();
3182
				$this->error=$this->db->lasterror();
3183
				return -1;
3184
			}
3185
		}
3186
		else
3187
		{
3188
			$this->db->rollback();
3189
			$this->error=$line->error;
3190
			return -1;
3191
		}
3192
	}
3193
3194
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
3195
	/**
3196
	 *	Set percent discount
3197
	 *
3198
	 *	@param     	User	$user		User that set discount
3199
	 *	@param     	double	$remise		Discount
3200
	 *  @param     	int		$notrigger	1=Does not execute triggers, 0= execute triggers
3201
	 *	@return		int 		<0 if ko, >0 if ok
3202
	 */
3203
	function set_remise($user, $remise, $notrigger=0)
3204
	{
3205
        // phpcs:enable
3206
		// Clean parameters
3207
		if (empty($remise)) $remise=0;
3208
3209
		if ($user->rights->facture->creer)
3210
		{
3211
			$remise=price2num($remise);
3212
3213
			$error=0;
3214
3215
			$this->db->begin();
3216
3217
			$sql = 'UPDATE '.MAIN_DB_PREFIX.'facture';
3218
			$sql.= ' SET remise_percent = '.$remise;
3219
			$sql.= ' WHERE rowid = '.$this->id;
3220
			$sql.= ' AND fk_statut = '.self::STATUS_DRAFT;
3221
3222
			dol_syslog(__METHOD__, LOG_DEBUG);
3223
			$resql=$this->db->query($sql);
3224
			if (!$resql)
3225
			{
3226
				$this->errors[]=$this->db->error();
3227
				$error++;
3228
			}
3229
3230
			if (! $notrigger && empty($error))
3231
			{
3232
				// Call trigger
3233
				$result=$this->call_trigger('BILL_MODIFY',$user);
3234
				if ($result < 0) $error++;
3235
				// End call triggers
3236
			}
3237
3238
			if (! $error)
3239
			{
3240
				$this->remise_percent = $remise;
3241
				$this->update_price(1);
3242
3243
				$this->db->commit();
3244
				return 1;
3245
			}
3246
			else
3247
			{
3248
				foreach($this->errors as $errmsg)
3249
				{
3250
					dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
3251
					$this->error.=($this->error?', '.$errmsg:$errmsg);
3252
				}
3253
				$this->db->rollback();
3254
				return -1*$error;
3255
			}
3256
		}
3257
	}
3258
3259
3260
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
3261
	/**
3262
	 *	Set absolute discount
3263
	 *
3264
	 *	@param     	User	$user 		User that set discount
3265
	 *	@param     	double	$remise		Discount
3266
	 *  @param     	int		$notrigger	1=Does not execute triggers, 0= execute triggers
3267
	 *	@return		int 				<0 if KO, >0 if OK
3268
	 */
3269
	function set_remise_absolue($user, $remise, $notrigger=0)
3270
	{
3271
        // phpcs:enable
3272
		if (empty($remise)) $remise=0;
3273
3274
		if ($user->rights->facture->creer)
3275
		{
3276
			$error=0;
3277
3278
			$this->db->begin();
3279
3280
			$remise=price2num($remise);
3281
3282
			$sql = 'UPDATE '.MAIN_DB_PREFIX.'facture';
3283
			$sql.= ' SET remise_absolue = '.$remise;
3284
			$sql.= ' WHERE rowid = '.$this->id;
3285
			$sql.= ' AND fk_statut = '.self::STATUS_DRAFT;
3286
3287
			dol_syslog(__METHOD__, LOG_DEBUG);
3288
			$resql=$this->db->query($sql);
3289
			if (!$resql)
3290
			{
3291
				$this->errors[]=$this->db->error();
3292
				$error++;
3293
			}
3294
3295
			if (! $error)
3296
			{
3297
				$this->oldcopy= clone $this;
3298
				$this->remise_absolue = $remise;
3299
				$this->update_price(1);
3300
			}
3301
3302
			if (! $notrigger && empty($error))
3303
			{
3304
				// Call trigger
3305
				$result=$this->call_trigger('BILL_MODIFY',$user);
3306
				if ($result < 0) $error++;
3307
				// End call triggers
3308
			}
3309
3310
			if (! $error)
3311
			{
3312
				$this->db->commit();
3313
				return 1;
3314
			}
3315
			else
3316
			{
3317
				foreach($this->errors as $errmsg)
3318
				{
3319
					dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
3320
					$this->error.=($this->error?', '.$errmsg:$errmsg);
3321
				}
3322
				$this->db->rollback();
3323
				return -1*$error;
3324
			}
3325
		}
3326
	}
3327
3328
	/**
3329
	 *      Return next reference of customer invoice not already used (or last reference)
3330
	 *      according to numbering module defined into constant FACTURE_ADDON
3331
	 *
3332
	 *      @param	   Societe		$soc		object company
3333
	 *      @param     string		$mode		'next' for next value or 'last' for last value
3334
	 *      @return    string					free ref or last ref
3335
	 */
3336
	function getNextNumRef($soc,$mode='next')
3337
	{
3338
		global $conf, $langs;
3339
		$langs->load("bills");
3340
3341
		// Clean parameters (if not defined or using deprecated value)
3342
		if (empty($conf->global->FACTURE_ADDON)) $conf->global->FACTURE_ADDON='mod_facture_terre';
3343
		else if ($conf->global->FACTURE_ADDON=='terre') $conf->global->FACTURE_ADDON='mod_facture_terre';
3344
		else if ($conf->global->FACTURE_ADDON=='mercure') $conf->global->FACTURE_ADDON='mod_facture_mercure';
3345
3346
		if (! empty($conf->global->FACTURE_ADDON))
3347
		{
3348
			dol_syslog("Call getNextNumRef with FACTURE_ADDON = ".$conf->global->FACTURE_ADDON.", thirdparty=".$soc->nom.", type=".$soc->typent_code, LOG_DEBUG);
3349
3350
			$mybool=false;
3351
3352
			$file = $conf->global->FACTURE_ADDON.".php";
3353
			$classname = $conf->global->FACTURE_ADDON;
3354
3355
			// Include file with class
3356
			$dirmodels = array_merge(array('/'), (array) $conf->modules_parts['models']);
3357
3358
			foreach ($dirmodels as $reldir) {
3359
3360
				$dir = dol_buildpath($reldir."core/modules/facture/");
3361
3362
				// Load file with numbering class (if found)
3363
				if (is_file($dir.$file) && is_readable($dir.$file))
3364
				{
3365
                    $mybool |= include_once $dir . $file;
3366
                }
3367
			}
3368
3369
			// For compatibility
3370
			if (! $mybool)
3371
			{
3372
				$file = $conf->global->FACTURE_ADDON."/".$conf->global->FACTURE_ADDON.".modules.php";
3373
				$classname = "mod_facture_".$conf->global->FACTURE_ADDON;
3374
				$classname = preg_replace('/\-.*$/','',$classname);
3375
				// Include file with class
3376
				foreach ($conf->file->dol_document_root as $dirroot)
3377
				{
3378
					$dir = $dirroot."/core/modules/facture/";
3379
3380
					// Load file with numbering class (if found)
3381
					if (is_file($dir.$file) && is_readable($dir.$file)) {
3382
                        $mybool |= include_once $dir . $file;
3383
                    }
3384
				}
3385
			}
3386
3387
			if (! $mybool)
3388
			{
3389
				dol_print_error('',"Failed to include file ".$file);
3390
				return '';
3391
			}
3392
3393
			$obj = new $classname();
3394
			$numref = "";
3395
			$numref = $obj->getNextValue($soc,$this,$mode);
3396
3397
			/**
3398
			 * $numref can be empty in case we ask for the last value because if there is no invoice created with the
3399
			 * set up mask.
3400
			 */
3401
			if ($mode != 'last' && !$numref) {
3402
				$this->error=$obj->error;
3403
				//dol_print_error($this->db,"Facture::getNextNumRef ".$obj->error);
3404
				return "";
3405
			}
3406
3407
			return $numref;
3408
		}
3409
		else
3410
		{
3411
			$langs->load("errors");
3412
			print $langs->trans("Error")." ".$langs->trans("ErrorModuleSetupNotComplete");
3413
			return "";
3414
		}
3415
	}
3416
3417
	/**
3418
	 *	Load miscellaneous information for tab "Info"
3419
	 *
3420
	 *	@param  int		$id		Id of object to load
3421
	 *	@return	void
3422
	 */
3423
	function info($id)
3424
	{
3425
		$sql = 'SELECT c.rowid, datec, date_valid as datev, tms as datem,';
3426
		$sql.= ' fk_user_author, fk_user_valid';
3427
		$sql.= ' FROM '.MAIN_DB_PREFIX.'facture as c';
3428
		$sql.= ' WHERE c.rowid = '.$id;
3429
3430
		$result=$this->db->query($sql);
3431
		if ($result)
3432
		{
3433
			if ($this->db->num_rows($result))
3434
			{
3435
				$obj = $this->db->fetch_object($result);
3436
				$this->id = $obj->rowid;
3437
				if ($obj->fk_user_author)
3438
				{
3439
					$cuser = new User($this->db);
3440
					$cuser->fetch($obj->fk_user_author);
3441
					$this->user_creation     = $cuser;
3442
				}
3443
				if ($obj->fk_user_valid)
3444
				{
3445
					$vuser = new User($this->db);
3446
					$vuser->fetch($obj->fk_user_valid);
3447
					$this->user_validation = $vuser;
3448
				}
3449
				$this->date_creation     = $this->db->jdate($obj->datec);
3450
				$this->date_modification = $this->db->jdate($obj->datem);
3451
				$this->date_validation   = $this->db->jdate($obj->datev);	// Should be in log table
3452
			}
3453
			$this->db->free($result);
3454
		}
3455
		else
3456
		{
3457
			dol_print_error($this->db);
3458
		}
3459
	}
3460
3461
3462
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
3463
	/**
3464
	 *  Return list of invoices (eventually filtered on a user) into an array
3465
	 *
3466
	 *  @param		int		$shortlist		0=Return array[id]=ref, 1=Return array[](id=>id,ref=>ref,name=>name)
3467
	 *  @param      int		$draft      	0=not draft, 1=draft
3468
	 *  @param      User	$excluser      	Objet user to exclude
3469
	 *  @param    	int		$socid			Id third pary
3470
	 *  @param    	int		$limit			For pagination
3471
	 *  @param    	int		$offset			For pagination
3472
	 *  @param    	string	$sortfield		Sort criteria
3473
	 *  @param    	string	$sortorder		Sort order
3474
	 *  @return     int             		-1 if KO, array with result if OK
3475
	 */
3476
	function liste_array($shortlist=0, $draft=0, $excluser='', $socid=0, $limit=0, $offset=0, $sortfield='f.datef,f.rowid', $sortorder='DESC')
3477
	{
3478
        // phpcs:enable
3479
		global $conf,$user;
3480
3481
		$ga = array();
3482
3483
		$sql = "SELECT s.rowid, s.nom as name, s.client,";
3484
		$sql.= " f.rowid as fid, f.ref as ref, f.datef as df";
3485
		if (! $user->rights->societe->client->voir && ! $socid) $sql .= ", sc.fk_soc, sc.fk_user";
3486
		$sql.= " FROM ".MAIN_DB_PREFIX."societe as s, ".MAIN_DB_PREFIX."facture as f";
3487
		if (! $user->rights->societe->client->voir && ! $socid) $sql .= ", ".MAIN_DB_PREFIX."societe_commerciaux as sc";
3488
		$sql.= " WHERE f.entity = ".$conf->entity;
3489
		$sql.= " AND f.fk_soc = s.rowid";
3490
		if (! $user->rights->societe->client->voir && ! $socid) //restriction
3491
		{
3492
			$sql.= " AND s.rowid = sc.fk_soc AND sc.fk_user = " .$user->id;
3493
		}
3494
		if ($socid) $sql.= " AND s.rowid = ".$socid;
3495
		if ($draft) $sql.= " AND f.fk_statut = ".self::STATUS_DRAFT;
3496
		if (is_object($excluser)) $sql.= " AND f.fk_user_author <> ".$excluser->id;
3497
		$sql.= $this->db->order($sortfield,$sortorder);
3498
		$sql.= $this->db->plimit($limit,$offset);
3499
3500
		$result=$this->db->query($sql);
3501
		if ($result)
3502
		{
3503
			$numc = $this->db->num_rows($result);
3504
			if ($numc)
3505
			{
3506
				$i = 0;
3507
				while ($i < $numc)
3508
				{
3509
					$obj = $this->db->fetch_object($result);
3510
3511
					if ($shortlist == 1)
3512
					{
3513
						$ga[$obj->fid] = $obj->ref;
3514
					}
3515
					else if ($shortlist == 2)
3516
					{
3517
						$ga[$obj->fid] = $obj->ref.' ('.$obj->name.')';
3518
					}
3519
					else
3520
					{
3521
						$ga[$i]['id']	= $obj->fid;
3522
						$ga[$i]['ref'] 	= $obj->ref;
3523
						$ga[$i]['name'] = $obj->name;
3524
					}
3525
					$i++;
3526
				}
3527
			}
3528
			return $ga;
3529
		}
3530
		else
3531
		{
3532
			dol_print_error($this->db);
3533
			return -1;
3534
		}
3535
	}
3536
3537
3538
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
3539
	/**
3540
	 *	Return list of invoices qualified to be replaced by another invoice.
3541
	 *	Invoices matching the following rules are returned:
3542
	 *	(Status validated or abandonned for a reason 'other') + not payed + no payment at all + not already replaced
3543
	 *
3544
	 *	@param		int		$socid		Id thirdparty
3545
	 *	@return    	array				Array of invoices ('id'=>id, 'ref'=>ref, 'status'=>status, 'paymentornot'=>0/1)
3546
	 */
3547
	function list_replacable_invoices($socid=0)
3548
	{
3549
        // phpcs:enable
3550
		global $conf;
3551
3552
		$return = array();
3553
3554
		$sql = "SELECT f.rowid as rowid, f.ref, f.fk_statut,";
3555
		$sql.= " ff.rowid as rowidnext";
3556
		$sql.= " FROM ".MAIN_DB_PREFIX."facture as f";
3557
		$sql.= " LEFT JOIN ".MAIN_DB_PREFIX."paiement_facture as pf ON f.rowid = pf.fk_facture";
3558
		$sql.= " LEFT JOIN ".MAIN_DB_PREFIX."facture as ff ON f.rowid = ff.fk_facture_source";
3559
		$sql.= " WHERE (f.fk_statut = ".self::STATUS_VALIDATED." OR (f.fk_statut = ".self::STATUS_ABANDONED." AND f.close_code = '".self::CLOSECODE_ABANDONED."'))";
3560
		$sql.= " AND f.entity = ".$conf->entity;
3561
		$sql.= " AND f.paye = 0";					// Pas classee payee completement
3562
		$sql.= " AND pf.fk_paiement IS NULL";		// Aucun paiement deja fait
3563
		$sql.= " AND ff.fk_statut IS NULL";			// Renvoi vrai si pas facture de remplacement
3564
		if ($socid > 0) $sql.=" AND f.fk_soc = ".$socid;
3565
		$sql.= " ORDER BY f.ref";
3566
3567
		dol_syslog(get_class($this)."::list_replacable_invoices", LOG_DEBUG);
3568
		$resql=$this->db->query($sql);
3569
		if ($resql)
3570
		{
3571
			while ($obj=$this->db->fetch_object($resql))
3572
			{
3573
				$return[$obj->rowid]=array(	'id' => $obj->rowid,
3574
				'ref' => $obj->ref,
3575
				'status' => $obj->fk_statut);
3576
			}
3577
			//print_r($return);
3578
			return $return;
3579
		}
3580
		else
3581
		{
3582
			$this->error=$this->db->error();
3583
			return -1;
3584
		}
3585
	}
3586
3587
3588
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
3589
	/**
3590
	 *	Return list of invoices qualified to be corrected by a credit note.
3591
	 *	Invoices matching the following rules are returned:
3592
	 *	(validated + payment on process) or classified (payed completely or payed partiely) + not already replaced + not already a credit note
3593
	 *
3594
	 *	@param		int		$socid		Id thirdparty
3595
	 *	@return    	array				Array of invoices ($id => array('ref'=>,'paymentornot'=>,'status'=>,'paye'=>)
3596
	 */
3597
	function list_qualified_avoir_invoices($socid=0)
3598
	{
3599
        // phpcs:enable
3600
		global $conf;
3601
3602
		$return = array();
3603
3604
3605
		$sql = "SELECT f.rowid as rowid, f.ref, f.fk_statut, f.type, f.paye, pf.fk_paiement";
3606
		$sql.= " FROM ".MAIN_DB_PREFIX."facture as f";
3607
		$sql.= " LEFT JOIN ".MAIN_DB_PREFIX."paiement_facture as pf ON f.rowid = pf.fk_facture";
3608
		$sql.= " LEFT JOIN ".MAIN_DB_PREFIX."facture as ff ON (f.rowid = ff.fk_facture_source AND ff.type=".self::TYPE_REPLACEMENT.")";
3609
		$sql.= " WHERE f.entity = ".$conf->entity;
3610
		$sql.= " AND f.fk_statut in (".self::STATUS_VALIDATED.",".self::STATUS_CLOSED.")";
3611
		//  $sql.= " WHERE f.fk_statut >= 1";
3612
		//	$sql.= " AND (f.paye = 1";				// Classee payee completement
3613
		//	$sql.= " OR f.close_code IS NOT NULL)";	// Classee payee partiellement
3614
		$sql.= " AND ff.type IS NULL";			// Renvoi vrai si pas facture de remplacement
3615
		$sql.= " AND f.type != ".self::TYPE_CREDIT_NOTE;				// Type non 2 si facture non avoir
3616
3617
		if($conf->global->INVOICE_USE_SITUATION_CREDIT_NOTE){
3618
		    // Select the last situation invoice
3619
		    $sqlSit = 'SELECT MAX(fs.rowid)';
3620
		    $sqlSit.= " FROM ".MAIN_DB_PREFIX."facture as fs";
3621
		    $sqlSit.= " WHERE fs.entity = ".$conf->entity;
3622
		    $sqlSit.= " AND fs.type = ".self::TYPE_SITUATION;
3623
		    $sqlSit.= " AND fs.fk_statut in (".self::STATUS_VALIDATED.",".self::STATUS_CLOSED.")";
3624
		    $sqlSit.= " GROUP BY fs.situation_cycle_ref";
3625
		    $sqlSit.= " ORDER BY fs.situation_counter";
3626
            $sql.= " AND ( f.type != ".self::TYPE_SITUATION . " OR f.rowid IN (".$sqlSit.") )";	// Type non 5 si facture non avoir
3627
		}
3628
		else
3629
		{
3630
		    $sql.= " AND f.type != ".self::TYPE_SITUATION ; // Type non 5 si facture non avoir
3631
		}
3632
3633
		if ($socid > 0) $sql.=" AND f.fk_soc = ".$socid;
3634
		$sql.= " ORDER BY f.ref";
3635
3636
		dol_syslog(get_class($this)."::list_qualified_avoir_invoices", LOG_DEBUG);
3637
		$resql=$this->db->query($sql);
3638
		if ($resql)
3639
		{
3640
			while ($obj=$this->db->fetch_object($resql))
3641
			{
3642
				$qualified=0;
3643
				if ($obj->fk_statut == self::STATUS_VALIDATED) $qualified=1;
3644
				if ($obj->fk_statut == self::STATUS_CLOSED) $qualified=1;
3645
				if ($qualified)
3646
				{
3647
					//$ref=$obj->ref;
3648
					$paymentornot=($obj->fk_paiement?1:0);
3649
					$return[$obj->rowid]=array('ref'=>$obj->ref,'status'=>$obj->fk_statut,'type'=>$obj->type,'paye'=>$obj->paye,'paymentornot'=>$paymentornot);
3650
				}
3651
			}
3652
3653
			return $return;
3654
		}
3655
		else
3656
		{
3657
			$this->error=$this->db->error();
3658
			return -1;
3659
		}
3660
	}
3661
3662
3663
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
3664
	/**
3665
	 *	Create a withdrawal request for a standing order.
3666
	 *  Use the remain to pay excluding all existing open direct debit requests.
3667
	 *
3668
	 *	@param      User	$fuser      User asking the direct debit transfer
3669
	 *  @param		float	$amount		Amount we request direct debit for
3670
	 *	@return     int         		<0 if KO, >0 if OK
3671
	 */
3672
	function demande_prelevement($fuser, $amount=0)
3673
	{
3674
        // phpcs:enable
3675
3676
		$error=0;
3677
3678
		dol_syslog(get_class($this)."::demande_prelevement", LOG_DEBUG);
3679
3680
		if ($this->statut > self::STATUS_DRAFT && $this->paye == 0)
3681
		{
3682
	        require_once DOL_DOCUMENT_ROOT . '/societe/class/companybankaccount.class.php';
3683
	        $bac = new CompanyBankAccount($this->db);
3684
	        $bac->fetch(0,$this->socid);
3685
3686
        	$sql = 'SELECT count(*)';
3687
			$sql.= ' FROM '.MAIN_DB_PREFIX.'prelevement_facture_demande';
3688
			$sql.= ' WHERE fk_facture = '.$this->id;
3689
			$sql.= ' AND traite = 0';
3690
3691
			dol_syslog(get_class($this)."::demande_prelevement", LOG_DEBUG);
3692
			$resql=$this->db->query($sql);
3693
			if ($resql)
3694
			{
3695
				$row = $this->db->fetch_row($resql);
3696
				if ($row[0] == 0)
3697
				{
3698
					$now=dol_now();
3699
3700
                    $totalpaye  = $this->getSommePaiement();
3701
                    $totalcreditnotes = $this->getSumCreditNotesUsed();
3702
                    $totaldeposits = $this->getSumDepositsUsed();
3703
                    //print "totalpaye=".$totalpaye." totalcreditnotes=".$totalcreditnotes." totaldeposts=".$totaldeposits;
3704
3705
                    // We can also use bcadd to avoid pb with floating points
3706
                    // For example print 239.2 - 229.3 - 9.9; does not return 0.
3707
                    //$resteapayer=bcadd($this->total_ttc,$totalpaye,$conf->global->MAIN_MAX_DECIMALS_TOT);
3708
                    //$resteapayer=bcadd($resteapayer,$totalavoir,$conf->global->MAIN_MAX_DECIMALS_TOT);
3709
					if (empty($amount)) $amount = price2num($this->total_ttc - $totalpaye - $totalcreditnotes - $totaldeposits,'MT');
3710
3711
					if (is_numeric($amount) && $amount != 0)
3712
					{
3713
						$sql = 'INSERT INTO '.MAIN_DB_PREFIX.'prelevement_facture_demande';
3714
						$sql .= ' (fk_facture, amount, date_demande, fk_user_demande, code_banque, code_guichet, number, cle_rib)';
3715
						$sql .= ' VALUES ('.$this->id;
3716
						$sql .= ",'".price2num($amount)."'";
3717
						$sql .= ",'".$this->db->idate($now)."'";
3718
						$sql .= ",".$fuser->id;
3719
						$sql .= ",'".$bac->code_banque."'";
3720
						$sql .= ",'".$bac->code_guichet."'";
3721
						$sql .= ",'".$bac->number."'";
3722
						$sql .= ",'".$bac->cle_rib."')";
3723
3724
						dol_syslog(get_class($this)."::demande_prelevement", LOG_DEBUG);
3725
						$resql=$this->db->query($sql);
3726
						if (! $resql)
3727
						{
3728
						    $this->error=$this->db->lasterror();
3729
						    dol_syslog(get_class($this).'::demandeprelevement Erreur');
3730
						    $error++;
3731
						}
3732
					}
3733
					else
3734
					{
3735
						$this->error='WithdrawRequestErrorNilAmount';
3736
	                    dol_syslog(get_class($this).'::demandeprelevement WithdrawRequestErrorNilAmount');
3737
	                    $error++;
3738
					}
3739
3740
        			if (! $error)
3741
        			{
3742
        				// Force payment mode of invoice to withdraw
3743
        				$payment_mode_id = dol_getIdFromCode($this->db, 'PRE', 'c_paiement', 'code', 'id', 1);
3744
        				if ($payment_mode_id > 0)
3745
        				{
3746
        					$result=$this->setPaymentMethods($payment_mode_id);
3747
        				}
3748
        			}
3749
3750
                    if ($error) return -1;
3751
                    return 1;
3752
                }
3753
                else
3754
                {
3755
                    $this->error="A request already exists";
3756
                    dol_syslog(get_class($this).'::demandeprelevement Impossible de creer une demande, demande deja en cours');
3757
                    return 0;
3758
                }
3759
            }
3760
            else
3761
            {
3762
                $this->error=$this->db->error();
3763
                dol_syslog(get_class($this).'::demandeprelevement Erreur -2');
3764
                return -2;
3765
            }
3766
        }
3767
        else
3768
        {
3769
            $this->error="Status of invoice does not allow this";
3770
            dol_syslog(get_class($this)."::demandeprelevement ".$this->error." $this->statut, $this->paye, $this->mode_reglement_id");
3771
            return -3;
3772
        }
3773
    }
3774
3775
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
3776
	/**
3777
	 *  Supprime une demande de prelevement
3778
	 *
3779
	 *  @param  User	$fuser      User making delete
3780
	 *  @param  int		$did        id de la demande a supprimer
3781
	 *  @return	int					<0 if OK, >0 if KO
3782
	 */
3783
	function demande_prelevement_delete($fuser, $did)
3784
	{
3785
        // phpcs:enable
3786
		$sql = 'DELETE FROM '.MAIN_DB_PREFIX.'prelevement_facture_demande';
3787
		$sql .= ' WHERE rowid = '.$did;
3788
		$sql .= ' AND traite = 0';
3789
		if ( $this->db->query($sql) )
3790
		{
3791
			return 0;
3792
		}
3793
		else
3794
		{
3795
			$this->error=$this->db->lasterror();
3796
			dol_syslog(get_class($this).'::demande_prelevement_delete Error '.$this->error);
3797
			return -1;
3798
		}
3799
	}
3800
3801
3802
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
3803
	/**
3804
	 *	Load indicators for dashboard (this->nbtodo and this->nbtodolate)
3805
	 *
3806
	 *	@param  User		$user    	Object user
3807
	 *	@return WorkboardResponse|int 	<0 if KO, WorkboardResponse if OK
3808
	 */
3809
	function load_board($user)
3810
	{
3811
        // phpcs:enable
3812
		global $conf, $langs;
3813
3814
		$clause = " WHERE";
3815
3816
		$sql = "SELECT f.rowid, f.date_lim_reglement as datefin,f.fk_statut, f.total";
3817
		$sql.= " FROM ".MAIN_DB_PREFIX."facture as f";
3818
		if (!$user->rights->societe->client->voir && !$user->societe_id)
3819
		{
3820
			$sql.= " LEFT JOIN ".MAIN_DB_PREFIX."societe_commerciaux as sc ON f.fk_soc = sc.fk_soc";
3821
			$sql.= " WHERE sc.fk_user = " .$user->id;
3822
			$clause = " AND";
3823
		}
3824
		$sql.= $clause." f.paye=0";
3825
		$sql.= " AND f.entity = ".$conf->entity;
3826
		$sql.= " AND f.fk_statut = ".self::STATUS_VALIDATED;
3827
		if ($user->societe_id) $sql.= " AND f.fk_soc = ".$user->societe_id;
3828
3829
		$resql=$this->db->query($sql);
3830
		if ($resql)
3831
		{
3832
			$langs->load("bills");
3833
			$now=dol_now();
3834
3835
			$response = new WorkboardResponse();
3836
			$response->warning_delay=$conf->facture->client->warning_delay/60/60/24;
3837
			$response->label=$langs->trans("CustomerBillsUnpaid");
3838
			$response->url=DOL_URL_ROOT.'/compta/facture/list.php?search_status=1&mainmenu=billing&leftmenu=customers_bills';
3839
			$response->img=img_object('',"bill");
3840
3841
			$generic_facture = new Facture($this->db);
3842
3843
			while ($obj=$this->db->fetch_object($resql))
3844
			{
3845
				$generic_facture->date_lim_reglement = $this->db->jdate($obj->datefin);
3846
				$generic_facture->statut = $obj->fk_statut;
3847
3848
				$response->nbtodo++;
3849
				$response->total += $obj->total;
3850
3851
				if ($generic_facture->hasDelay()) {
3852
					$response->nbtodolate++;
3853
				}
3854
			}
3855
3856
			return $response;
3857
		}
3858
		else
3859
		{
3860
			dol_print_error($this->db);
3861
			$this->error=$this->db->error();
3862
			return -1;
3863
		}
3864
	}
3865
3866
3867
	/* gestion des contacts d'une facture */
3868
3869
	/**
3870
	 *	Retourne id des contacts clients de facturation
3871
	 *
3872
	 *	@return     array       Liste des id contacts facturation
3873
	 */
3874
	function getIdBillingContact()
3875
	{
3876
		return $this->getIdContact('external','BILLING');
3877
	}
3878
3879
	/**
3880
	 *	Retourne id des contacts clients de livraison
3881
	 *
3882
	 *	@return     array       Liste des id contacts livraison
3883
	 */
3884
	function getIdShippingContact()
3885
	{
3886
		return $this->getIdContact('external','SHIPPING');
3887
	}
3888
3889
3890
	/**
3891
	 *  Initialise an instance with random values.
3892
	 *  Used to build previews or test instances.
3893
	 *	id must be 0 if object instance is a specimen.
3894
	 *
3895
	 *	@param	string		$option		''=Create a specimen invoice with lines, 'nolines'=No lines
3896
	 *  @return	void
3897
	 */
3898
	function initAsSpecimen($option='')
3899
	{
3900
		global $langs;
3901
3902
		$now=dol_now();
3903
		$arraynow=dol_getdate($now);
3904
		$nownotime=dol_mktime(0, 0, 0, $arraynow['mon'], $arraynow['mday'], $arraynow['year']);
3905
3906
        // Load array of products prodids
3907
		$num_prods = 0;
3908
		$prodids = array();
3909
		$sql = "SELECT rowid";
3910
		$sql.= " FROM ".MAIN_DB_PREFIX."product";
3911
		$sql.= " WHERE entity IN (".getEntity('product').")";
3912
		$resql = $this->db->query($sql);
3913
		if ($resql)
3914
		{
3915
			$num_prods = $this->db->num_rows($resql);
3916
			$i = 0;
3917
			while ($i < $num_prods)
3918
			{
3919
				$i++;
3920
				$row = $this->db->fetch_row($resql);
3921
				$prodids[$i] = $row[0];
3922
			}
3923
		}
3924
		//Avoid php warning Warning: mt_rand(): max(0) is smaller than min(1) when no product exists
3925
		if (empty($num_prods)) {
3926
			$num_prods=1;
3927
		}
3928
3929
		// Initialize parameters
3930
		$this->id=0;
3931
		$this->entity = 1;
3932
		$this->ref = 'SPECIMEN';
3933
		$this->specimen=1;
3934
		$this->socid = 1;
3935
		$this->date = $nownotime;
3936
		$this->date_lim_reglement = $nownotime + 3600 * 24 *30;
3937
		$this->cond_reglement_id   = 1;
3938
		$this->cond_reglement_code = 'RECEP';
3939
		$this->date_lim_reglement=$this->calculate_date_lim_reglement();
3940
		$this->mode_reglement_id   = 0;		// Not forced to show payment mode CHQ + VIR
3941
		$this->mode_reglement_code = '';	// Not forced to show payment mode CHQ + VIR
3942
		$this->note_public='This is a comment (public)';
3943
		$this->note_private='This is a comment (private)';
3944
		$this->note='This is a comment (private)';
3945
		$this->fk_incoterms=0;
3946
		$this->location_incoterms='';
3947
3948
		if (empty($option) || $option != 'nolines')
3949
		{
3950
			// Lines
3951
			$nbp = 5;
3952
			$xnbp = 0;
3953
			while ($xnbp < $nbp)
3954
			{
3955
				$line=new FactureLigne($this->db);
3956
				$line->desc=$langs->trans("Description")." ".$xnbp;
3957
				$line->qty=1;
3958
				$line->subprice=100;
3959
				$line->tva_tx=19.6;
3960
				$line->localtax1_tx=0;
3961
				$line->localtax2_tx=0;
3962
				$line->remise_percent=0;
3963
				if ($xnbp == 1)        // Qty is negative (product line)
3964
				{
3965
					$prodid = mt_rand(1, $num_prods);
3966
					$line->fk_product=$prodids[$prodid];
3967
					$line->qty=-1;
3968
					$line->total_ht=-100;
3969
					$line->total_ttc=-119.6;
3970
					$line->total_tva=-19.6;
3971
					$line->multicurrency_total_ht=-200;
3972
					$line->multicurrency_total_ttc=-239.2;
3973
					$line->multicurrency_total_tva=-39.2;
3974
				}
3975
				else if ($xnbp == 2)    // UP is negative (free line)
3976
				{
3977
					$line->subprice=-100;
3978
					$line->total_ht=-100;
3979
					$line->total_ttc=-119.6;
3980
					$line->total_tva=-19.6;
3981
					$line->remise_percent=0;
3982
					$line->multicurrency_total_ht=-200;
3983
					$line->multicurrency_total_ttc=-239.2;
3984
					$line->multicurrency_total_tva=-39.2;
3985
				}
3986
				else if ($xnbp == 3)    // Discount is 50% (product line)
3987
				{
3988
					$prodid = mt_rand(1, $num_prods);
3989
					$line->fk_product=$prodids[$prodid];
3990
					$line->total_ht=50;
3991
					$line->total_ttc=59.8;
3992
					$line->total_tva=9.8;
3993
					$line->multicurrency_total_ht=100;
3994
					$line->multicurrency_total_ttc=119.6;
3995
					$line->multicurrency_total_tva=19.6;
3996
					$line->remise_percent=50;
3997
				}
3998
				else    // (product line)
3999
				{
4000
					$prodid = mt_rand(1, $num_prods);
4001
					$line->fk_product=$prodids[$prodid];
4002
					$line->total_ht=100;
4003
					$line->total_ttc=119.6;
4004
					$line->total_tva=19.6;
4005
					$line->multicurrency_total_ht=200;
4006
					$line->multicurrency_total_ttc=239.2;
4007
					$line->multicurrency_total_tva=39.2;
4008
					$line->remise_percent=0;
4009
				}
4010
4011
				$this->lines[$xnbp]=$line;
4012
4013
4014
				$this->total_ht       += $line->total_ht;
4015
				$this->total_tva      += $line->total_tva;
4016
				$this->total_ttc      += $line->total_ttc;
4017
4018
				$this->multicurrency_total_ht       += $line->multicurrency_total_ht;
4019
				$this->multicurrency_total_tva      += $line->multicurrency_total_tva;
4020
				$this->multicurrency_total_ttc      += $line->multicurrency_total_ttc;
4021
4022
				$xnbp++;
4023
			}
4024
			$this->revenuestamp = 0;
4025
4026
			// Add a line "offered"
4027
			$line=new FactureLigne($this->db);
4028
			$line->desc=$langs->trans("Description")." (offered line)";
4029
			$line->qty=1;
4030
			$line->subprice=100;
4031
			$line->tva_tx=19.6;
4032
			$line->localtax1_tx=0;
4033
			$line->localtax2_tx=0;
4034
			$line->remise_percent=100;
4035
			$line->total_ht=0;
4036
			$line->total_ttc=0;    // 90 * 1.196
4037
			$line->total_tva=0;
4038
			$line->multicurrency_total_ht=0;
4039
			$line->multicurrency_total_ttc=0;
4040
			$line->multicurrency_total_tva=0;
4041
			$prodid = mt_rand(1, $num_prods);
4042
			$line->fk_product=$prodids[$prodid];
4043
4044
			$this->lines[$xnbp]=$line;
4045
			$xnbp++;
4046
		}
4047
	}
4048
4049
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
4050
	/**
4051
	 *      Load indicators for dashboard (this->nbtodo and this->nbtodolate)
4052
	 *
4053
	 *      @return         int     <0 if KO, >0 if OK
4054
	 */
4055
	function load_state_board()
4056
	{
4057
        // phpcs:enable
4058
		global $conf, $user;
4059
4060
		$this->nb=array();
4061
4062
		$clause = "WHERE";
4063
4064
		$sql = "SELECT count(f.rowid) as nb";
4065
		$sql.= " FROM ".MAIN_DB_PREFIX."facture as f";
4066
		$sql.= " LEFT JOIN ".MAIN_DB_PREFIX."societe as s ON f.fk_soc = s.rowid";
4067
		if (!$user->rights->societe->client->voir && !$user->societe_id)
4068
		{
4069
			$sql.= " LEFT JOIN ".MAIN_DB_PREFIX."societe_commerciaux as sc ON s.rowid = sc.fk_soc";
4070
			$sql.= " WHERE sc.fk_user = " .$user->id;
4071
			$clause = "AND";
4072
		}
4073
		$sql.= " ".$clause." f.entity = ".$conf->entity;
4074
4075
		$resql=$this->db->query($sql);
4076
		if ($resql)
4077
		{
4078
			while ($obj=$this->db->fetch_object($resql))
4079
			{
4080
				$this->nb["invoices"]=$obj->nb;
4081
			}
4082
            $this->db->free($resql);
4083
			return 1;
4084
		}
4085
		else
4086
		{
4087
			dol_print_error($this->db);
4088
			$this->error=$this->db->error();
4089
			return -1;
4090
		}
4091
	}
4092
4093
	/**
4094
	 * 	Create an array of invoice lines
4095
	 *
4096
	 * 	@return int		>0 if OK, <0 if KO
4097
	 */
4098
	function getLinesArray()
4099
	{
4100
	    return $this->fetch_lines();
4101
	}
4102
4103
	/**
4104
	 *  Create a document onto disk according to template module.
4105
	 *
4106
	 *	@param	string		$modele			Generator to use. Caller must set it to obj->modelpdf or GETPOST('modelpdf') for example.
4107
	 *	@param	Translate	$outputlangs	objet lang a utiliser pour traduction
4108
	 *  @param  int			$hidedetails    Hide details of lines
4109
	 *  @param  int			$hidedesc       Hide description
4110
	 *  @param  int			$hideref        Hide ref
4111
	 *  @param   null|array  $moreparams     Array to provide more information
4112
	 *	@return int        					<0 if KO, >0 if OK
4113
	 */
4114
	public function generateDocument($modele, $outputlangs, $hidedetails=0, $hidedesc=0, $hideref=0, $moreparams=null)
4115
	{
4116
		global $conf,$langs;
4117
4118
		$langs->load("bills");
4119
4120
		if (! dol_strlen($modele))
4121
		{
4122
			$modele = 'crabe';
4123
			$thisTypeConfName = 'FACTURE_ADDON_PDF_'.$this->type;
4124
4125
			if ($this->modelpdf) {
4126
				$modele = $this->modelpdf;
4127
			} elseif (! empty($conf->global->$thisTypeConfName)) {
4128
				$modele = $conf->global->$thisTypeConfName;
4129
			} elseif (! empty($conf->global->FACTURE_ADDON_PDF)) {
4130
				$modele = $conf->global->FACTURE_ADDON_PDF;
4131
			}
4132
		}
4133
4134
		$modelpath = "core/modules/facture/doc/";
4135
4136
		return $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams);
4137
	}
4138
4139
	/**
4140
	 * Gets the smallest reference available for a new cycle
4141
	 *
4142
	 * @return int >= 1 if OK, -1 if error
4143
	 */
4144
	function newCycle()
4145
	{
4146
		$sql = 'SELECT max(situation_cycle_ref) FROM ' . MAIN_DB_PREFIX . 'facture as f';
4147
		$sql.= " WHERE f.entity in (".getEntity('invoice', 0).")";
4148
		$resql = $this->db->query($sql);
4149
		if ($resql) {
4150
			if ($resql->num_rows > 0)
4151
			{
4152
				$res = $this->db->fetch_array($resql);
4153
				$ref = $res['max(situation_cycle_ref)'];
4154
				$ref++;
4155
			} else {
4156
				$ref = 1;
4157
			}
4158
			$this->db->free($resql);
4159
			return $ref;
4160
		} else {
4161
			$this->error = $this->db->lasterror();
4162
			dol_syslog("Error sql=" . $sql . ", error=" . $this->error, LOG_ERR);
4163
			return -1;
4164
		}
4165
	}
4166
4167
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
4168
	/**
4169
	 * Checks if the invoice is the first of a cycle
4170
	 *
4171
	 * @return boolean
4172
	 */
4173
	function is_first()
4174
	{
4175
        // phpcs:enable
4176
		return ($this->situation_counter == 1);
4177
	}
4178
4179
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
4180
	/**
4181
	 * Returns an array containing the previous situations as Facture objects
4182
	 *
4183
	 * @return mixed -1 if error, array of previous situations
4184
	 */
4185
	function get_prev_sits()
4186
	{
4187
        // phpcs:enable
4188
		global $conf;
4189
4190
		$sql = 'SELECT rowid FROM ' . MAIN_DB_PREFIX . 'facture';
4191
		$sql .= ' where situation_cycle_ref = ' . $this->situation_cycle_ref;
4192
		$sql .= ' and situation_counter < ' . $this->situation_counter;
4193
		$sql .= ' AND entity = '. ($this->entity > 0 ? $this->entity : $conf->entity);
4194
		$resql = $this->db->query($sql);
4195
		$res = array();
4196
		if ($resql && $resql->num_rows > 0) {
4197
			while ($row = $this->db->fetch_object($resql)) {
4198
				$id = $row->rowid;
4199
				$situation = new Facture($this->db);
4200
				$situation->fetch($id);
4201
				$res[] = $situation;
4202
			}
4203
		} else {
4204
			$this->error = $this->db->error();
4205
			dol_syslog("Error sql=" . $sql . ", error=" . $this->error, LOG_ERR);
4206
			return -1;
4207
		}
4208
4209
		return $res;
4210
	}
4211
4212
	/**
4213
	 * Sets the invoice as a final situation
4214
	 *
4215
	 *  @param  	User	$user    	Object user
4216
	 *  @param     	int		$notrigger	1=Does not execute triggers, 0= execute triggers
4217
	 *	@return		int 				<0 if KO, >0 if OK
4218
	 */
4219
	function setFinal(User $user, $notrigger=0)
4220
	{
4221
		$error=0;
4222
4223
		$this->db->begin();
4224
4225
		$sql = 'UPDATE ' . MAIN_DB_PREFIX . 'facture SET situation_final = ' . $this->situation_final . ' where rowid = ' . $this->id;
4226
4227
		dol_syslog(__METHOD__, LOG_DEBUG);
4228
		$resql=$this->db->query($sql);
4229
		if (!$resql)
4230
		{
4231
			$this->errors[]=$this->db->error();
4232
			$error++;
4233
		}
4234
4235
		if (! $notrigger && empty($error))
4236
		{
4237
			// Call trigger
4238
			$result=$this->call_trigger('BILL_MODIFY',$user);
4239
			if ($result < 0) $error++;
4240
			// End call triggers
4241
		}
4242
4243
		if (! $error)
4244
		{
4245
			$this->db->commit();
4246
			return 1;
4247
		}
4248
		else
4249
		{
4250
			foreach($this->errors as $errmsg)
4251
			{
4252
				dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
4253
				$this->error.=($this->error?', '.$errmsg:$errmsg);
4254
			}
4255
			$this->db->rollback();
4256
			return -1*$error;
4257
		}
4258
	}
4259
4260
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
4261
	/**
4262
	 * Checks if the invoice is the last in its cycle
4263
	 *
4264
	 * @return bool Last of the cycle status
4265
	 *
4266
	 */
4267
	function is_last_in_cycle()
4268
	{
4269
        // phpcs:enable
4270
		global $conf;
4271
4272
		if (!empty($this->situation_cycle_ref)) {
4273
			// No point in testing anything if we're not inside a cycle
4274
			$sql = 'SELECT max(situation_counter) FROM ' . MAIN_DB_PREFIX . 'facture WHERE situation_cycle_ref = ' . $this->situation_cycle_ref . ' AND entity = ' . ($this->entity > 0 ? $this->entity : $conf->entity);
4275
			$resql = $this->db->query($sql);
4276
4277
			if ($resql && $resql->num_rows > 0) {
4278
				$res = $this->db->fetch_array($resql);
4279
				$last = $res['max(situation_counter)'];
4280
				return ($last == $this->situation_counter);
4281
			} else {
4282
				$this->error = $this->db->lasterror();
4283
				dol_syslog(get_class($this) . "::select Error " . $this->error, LOG_ERR);
4284
				return false;
4285
			}
4286
		} else {
4287
			return true;
4288
		}
4289
	}
4290
4291
	/**
4292
	 * Function used to replace a thirdparty id with another one.
4293
	 *
4294
	 * @param DoliDB $db Database handler
4295
	 * @param int $origin_id Old thirdparty id
4296
	 * @param int $dest_id New thirdparty id
4297
	 * @return bool
4298
	 */
4299
	public static function replaceThirdparty(DoliDB $db, $origin_id, $dest_id)
4300
	{
4301
		$tables = array(
4302
			'facture'
4303
		);
4304
4305
		return CommonObject::commonReplaceThirdparty($db, $origin_id, $dest_id, $tables);
4306
	}
4307
4308
	/**
4309
	 * Is the customer invoice delayed?
4310
	 *
4311
	 * @return bool
4312
	 */
4313
	public function hasDelay()
4314
	{
4315
		global $conf;
4316
4317
		$now = dol_now();
4318
4319
		// Paid invoices have status STATUS_CLOSED
4320
		if ($this->statut != Facture::STATUS_VALIDATED) return false;
4321
4322
		return $this->date_lim_reglement < ($now - $conf->facture->client->warning_delay);
4323
	}
4324
}
4325
4326
/**
4327
 *	Class to manage invoice lines.
4328
 *  Saved into database table llx_facturedet
4329
 */
4330
class FactureLigne extends CommonInvoiceLine
4331
{
4332
    /**
4333
	 * @var string ID to identify managed object
4334
	 */
4335
	public $element='facturedet';
4336
4337
    /**
4338
	 * @var string Name of table without prefix where object is stored
4339
	 */
4340
	public $table_element='facturedet';
4341
4342
	public $oldline;
4343
4344
	//! From llx_facturedet
4345
	//! Id facture
4346
	public $fk_facture;
4347
	//! Id parent line
4348
	public $fk_parent_line;
4349
	/**
4350
	 * @deprecated
4351
	 */
4352
	public $label;
4353
	//! Description ligne
4354
	public $desc;
4355
4356
	public $localtax1_type;	// Local tax 1 type
4357
	public $localtax2_type;	// Local tax 2 type
4358
	public $fk_remise_except;	// Link to line into llx_remise_except
4359
	public $rang = 0;
4360
4361
	public $fk_fournprice;
4362
	public $pa_ht;
4363
	public $marge_tx;
4364
	public $marque_tx;
4365
4366
	public $special_code;	// Liste d'options non cumulabels:
4367
	// 1: frais de port
4368
	// 2: ecotaxe
4369
	// 3: ??
4370
4371
	public $origin;
4372
	public $origin_id;
4373
4374
	public $fk_code_ventilation = 0;
4375
4376
	public $date_start;
4377
	public $date_end;
4378
4379
	// Ne plus utiliser
4380
	//var $price;         	// P.U. HT apres remise % de ligne (exemple 80)
4381
	//var $remise;			// Montant calcule de la remise % sur PU HT (exemple 20)
4382
4383
	// From llx_product
4384
	/**
4385
	 * @deprecated
4386
	 * @see product_ref
4387
	 */
4388
	public $ref;				// Product ref (deprecated)
4389
	public $product_ref;       // Product ref
4390
	/**
4391
	 * @deprecated
4392
	 * @see product_label
4393
	 */
4394
	public $libelle;      		// Product label (deprecated)
4395
	public $product_label;     // Product label
4396
	public $product_desc;  	// Description produit
4397
4398
	public $skip_update_total; // Skip update price total for special lines
4399
4400
	/**
4401
	 * @var int Situation advance percentage
4402
	 */
4403
	public $situation_percent;
4404
4405
	/**
4406
	 * @var int Previous situation line id reference
4407
	 */
4408
	public $fk_prev_id;
4409
4410
	// Multicurrency
4411
	public $fk_multicurrency;
4412
	public $multicurrency_code;
4413
	public $multicurrency_subprice;
4414
	public $multicurrency_total_ht;
4415
	public $multicurrency_total_tva;
4416
	public $multicurrency_total_ttc;
4417
4418
	/**
4419
	 *	Load invoice line from database
4420
	 *
4421
	 *	@param	int		$rowid      id of invoice line to get
4422
	 *	@return	int					<0 if KO, >0 if OK
4423
	 */
4424
	function fetch($rowid)
4425
	{
4426
		$sql = 'SELECT fd.rowid, fd.fk_facture, fd.fk_parent_line, fd.fk_product, fd.product_type, fd.label as custom_label, fd.description, fd.price, fd.qty, fd.vat_src_code, fd.tva_tx,';
4427
		$sql.= ' fd.localtax1_tx, fd. localtax2_tx, fd.remise, fd.remise_percent, fd.fk_remise_except, fd.subprice,';
4428
		$sql.= ' fd.date_start as date_start, fd.date_end as date_end, fd.fk_product_fournisseur_price as fk_fournprice, fd.buy_price_ht as pa_ht,';
4429
		$sql.= ' fd.info_bits, fd.special_code, fd.total_ht, fd.total_tva, fd.total_ttc, fd.total_localtax1, fd.total_localtax2, fd.rang,';
4430
		$sql.= ' fd.fk_code_ventilation,';
4431
		$sql.= ' fd.fk_unit, fd.fk_user_author, fd.fk_user_modif,';
4432
		$sql.= ' fd.situation_percent, fd.fk_prev_id,';
4433
		$sql.= ' fd.multicurrency_subprice,';
4434
		$sql.= ' fd.multicurrency_total_ht,';
4435
		$sql.= ' fd.multicurrency_total_tva,';
4436
		$sql.= ' fd.multicurrency_total_ttc,';
4437
		$sql.= ' p.ref as product_ref, p.label as product_libelle, p.description as product_desc';
4438
		$sql.= ' FROM '.MAIN_DB_PREFIX.'facturedet as fd';
4439
		$sql.= ' LEFT JOIN '.MAIN_DB_PREFIX.'product as p ON fd.fk_product = p.rowid';
4440
		$sql.= ' WHERE fd.rowid = '.$rowid;
4441
4442
		$result = $this->db->query($sql);
4443
		if ($result)
4444
		{
4445
			$objp = $this->db->fetch_object($result);
4446
4447
			$this->rowid				= $objp->rowid;
4448
			$this->id					= $objp->rowid;
4449
			$this->fk_facture			= $objp->fk_facture;
4450
			$this->fk_parent_line		= $objp->fk_parent_line;
4451
			$this->label				= $objp->custom_label;
4452
			$this->desc					= $objp->description;
4453
			$this->qty					= $objp->qty;
4454
			$this->subprice				= $objp->subprice;
4455
			$this->vat_src_code  		= $objp->vat_src_code;
4456
			$this->tva_tx				= $objp->tva_tx;
4457
			$this->localtax1_tx			= $objp->localtax1_tx;
4458
			$this->localtax2_tx			= $objp->localtax2_tx;
4459
			$this->remise_percent		= $objp->remise_percent;
4460
			$this->fk_remise_except		= $objp->fk_remise_except;
4461
			$this->fk_product			= $objp->fk_product;
4462
			$this->product_type			= $objp->product_type;
4463
			$this->date_start			= $this->db->jdate($objp->date_start);
4464
			$this->date_end				= $this->db->jdate($objp->date_end);
4465
			$this->info_bits			= $objp->info_bits;
4466
			$this->tva_npr              = ($objp->info_bits & 1 == 1) ? 1 : 0;
4467
			$this->special_code			= $objp->special_code;
4468
			$this->total_ht				= $objp->total_ht;
4469
			$this->total_tva			= $objp->total_tva;
4470
			$this->total_localtax1		= $objp->total_localtax1;
4471
			$this->total_localtax2		= $objp->total_localtax2;
4472
			$this->total_ttc			= $objp->total_ttc;
4473
			$this->fk_code_ventilation	= $objp->fk_code_ventilation;
4474
			$this->rang					= $objp->rang;
4475
			$this->fk_fournprice		= $objp->fk_fournprice;
4476
			$marginInfos				= getMarginInfos($objp->subprice, $objp->remise_percent, $objp->tva_tx, $objp->localtax1_tx, $objp->localtax2_tx, $this->fk_fournprice, $objp->pa_ht);
4477
			$this->pa_ht				= $marginInfos[0];
4478
			$this->marge_tx				= $marginInfos[1];
4479
			$this->marque_tx			= $marginInfos[2];
4480
4481
			$this->ref					= $objp->product_ref;      // deprecated
4482
			$this->product_ref			= $objp->product_ref;
4483
			$this->libelle				= $objp->product_libelle;  // deprecated
4484
			$this->product_label		= $objp->product_libelle;
4485
			$this->product_desc			= $objp->product_desc;
4486
			$this->fk_unit				= $objp->fk_unit;
4487
			$this->fk_user_modif		= $objp->fk_user_modif;
4488
			$this->fk_user_author		= $objp->fk_user_author;
4489
4490
			$this->situation_percent    = $objp->situation_percent;
4491
			$this->fk_prev_id           = $objp->fk_prev_id;
4492
4493
			$this->multicurrency_subprice = $objp->multicurrency_subprice;
4494
			$this->multicurrency_total_ht = $objp->multicurrency_total_ht;
4495
			$this->multicurrency_total_tva= $objp->multicurrency_total_tva;
4496
			$this->multicurrency_total_ttc= $objp->multicurrency_total_ttc;
4497
4498
			$this->db->free($result);
4499
4500
			return 1;
4501
		}
4502
		else
4503
		{
4504
		    $this->error = $this->db->lasterror();
4505
			return -1;
4506
		}
4507
	}
4508
4509
	/**
4510
	 *	Insert line into database
4511
	 *
4512
	 *	@param      int		$notrigger		                 1 no triggers
4513
	 *  @param      int     $noerrorifdiscountalreadylinked  1=Do not make error if lines is linked to a discount and discount already linked to another
4514
	 *	@return		int						                 <0 if KO, >0 if OK
4515
	 */
4516
	function insert($notrigger=0, $noerrorifdiscountalreadylinked=0)
4517
	{
4518
		global $langs,$user,$conf;
4519
4520
		$error=0;
4521
4522
        $pa_ht_isemptystring = (empty($this->pa_ht) && $this->pa_ht == ''); // If true, we can use a default value. If this->pa_ht = '0', we must use '0'.
4523
4524
        dol_syslog(get_class($this)."::insert rang=".$this->rang, LOG_DEBUG);
4525
4526
		// Clean parameters
4527
		$this->desc=trim($this->desc);
4528
		if (empty($this->tva_tx)) $this->tva_tx=0;
4529
		if (empty($this->localtax1_tx)) $this->localtax1_tx=0;
4530
		if (empty($this->localtax2_tx)) $this->localtax2_tx=0;
4531
		if (empty($this->localtax1_type)) $this->localtax1_type=0;
4532
		if (empty($this->localtax2_type)) $this->localtax2_type=0;
4533
		if (empty($this->total_localtax1)) $this->total_localtax1=0;
4534
		if (empty($this->total_localtax2)) $this->total_localtax2=0;
4535
		if (empty($this->rang)) $this->rang=0;
4536
		if (empty($this->remise_percent)) $this->remise_percent=0;
4537
		if (empty($this->info_bits)) $this->info_bits=0;
4538
		if (empty($this->subprice)) $this->subprice=0;
4539
		if (empty($this->special_code)) $this->special_code=0;
4540
		if (empty($this->fk_parent_line)) $this->fk_parent_line=0;
4541
		if (empty($this->fk_prev_id)) $this->fk_prev_id = 0;
4542
		if (! isset($this->situation_percent) || $this->situation_percent > 100 || (string) $this->situation_percent == '') $this->situation_percent = 100;
4543
4544
		if (empty($this->pa_ht)) $this->pa_ht=0;
4545
		if (empty($this->multicurrency_subprice)) $this->multicurrency_subprice=0;
4546
		if (empty($this->multicurrency_total_ht)) $this->multicurrency_total_ht=0;
4547
		if (empty($this->multicurrency_total_tva)) $this->multicurrency_total_tva=0;
4548
		if (empty($this->multicurrency_total_ttc)) $this->multicurrency_total_ttc=0;
4549
4550
		// if buy price not defined, define buyprice as configured in margin admin
4551
		if ($this->pa_ht == 0 && $pa_ht_isemptystring)
4552
		{
4553
			if (($result = $this->defineBuyPrice($this->subprice, $this->remise_percent, $this->fk_product)) < 0)
4554
			{
4555
				return $result;
4556
			}
4557
			else
4558
			{
4559
				$this->pa_ht = $result;
4560
			}
4561
		}
4562
4563
		// Check parameters
4564
		if ($this->product_type < 0)
4565
		{
4566
			$this->error='ErrorProductTypeMustBe0orMore';
4567
			return -1;
4568
		}
4569
		if (! empty($this->fk_product))
4570
		{
4571
			// Check product exists
4572
			$result=Product::isExistingObject('product', $this->fk_product);
4573
			if ($result <= 0)
4574
			{
4575
				$this->error='ErrorProductIdDoesNotExists';
4576
				return -1;
4577
			}
4578
		}
4579
4580
		$this->db->begin();
4581
4582
		// Insertion dans base de la ligne
4583
		$sql = 'INSERT INTO '.MAIN_DB_PREFIX.'facturedet';
4584
		$sql.= ' (fk_facture, fk_parent_line, label, description, qty,';
4585
		$sql.= ' vat_src_code, tva_tx, localtax1_tx, localtax2_tx, localtax1_type, localtax2_type,';
4586
		$sql.= ' fk_product, product_type, remise_percent, subprice, fk_remise_except,';
4587
		$sql.= ' date_start, date_end, fk_code_ventilation, ';
4588
		$sql.= ' rang, special_code, fk_product_fournisseur_price, buy_price_ht,';
4589
		$sql.= ' info_bits, total_ht, total_tva, total_ttc, total_localtax1, total_localtax2,';
4590
		$sql.= ' situation_percent, fk_prev_id,';
4591
		$sql.= ' fk_unit, fk_user_author, fk_user_modif,';
4592
		$sql.= ' fk_multicurrency, multicurrency_code, multicurrency_subprice, multicurrency_total_ht, multicurrency_total_tva, multicurrency_total_ttc';
4593
		$sql.= ')';
4594
		$sql.= " VALUES (".$this->fk_facture.",";
4595
		$sql.= " ".($this->fk_parent_line>0 ? $this->fk_parent_line:"null").",";
4596
		$sql.= " ".(! empty($this->label)?"'".$this->db->escape($this->label)."'":"null").",";
4597
		$sql.= " '".$this->db->escape($this->desc)."',";
4598
		$sql.= " ".price2num($this->qty).",";
4599
        $sql.= " ".(empty($this->vat_src_code)?"''":"'".$this->db->escape($this->vat_src_code)."'").",";
4600
		$sql.= " ".price2num($this->tva_tx).",";
4601
		$sql.= " ".price2num($this->localtax1_tx).",";
4602
		$sql.= " ".price2num($this->localtax2_tx).",";
4603
		$sql.= " '".$this->db->escape($this->localtax1_type)."',";
4604
		$sql.= " '".$this->db->escape($this->localtax2_type)."',";
4605
		$sql.= ' '.(! empty($this->fk_product)?$this->fk_product:"null").',';
4606
		$sql.= " ".$this->product_type.",";
4607
		$sql.= " ".price2num($this->remise_percent).",";
4608
		$sql.= " ".price2num($this->subprice).",";
4609
		$sql.= ' '.(! empty($this->fk_remise_except)?$this->fk_remise_except:"null").',';
4610
		$sql.= " ".(! empty($this->date_start)?"'".$this->db->idate($this->date_start)."'":"null").",";
4611
		$sql.= " ".(! empty($this->date_end)?"'".$this->db->idate($this->date_end)."'":"null").",";
4612
		$sql.= ' '.$this->fk_code_ventilation.',';
4613
		$sql.= ' '.$this->rang.',';
4614
		$sql.= ' '.$this->special_code.',';
4615
		$sql.= ' '.(! empty($this->fk_fournprice)?$this->fk_fournprice:"null").',';
4616
		$sql.= ' '.price2num($this->pa_ht).',';
4617
		$sql.= " '".$this->db->escape($this->info_bits)."',";
4618
		$sql.= " ".price2num($this->total_ht).",";
4619
		$sql.= " ".price2num($this->total_tva).",";
4620
		$sql.= " ".price2num($this->total_ttc).",";
4621
		$sql.= " ".price2num($this->total_localtax1).",";
4622
		$sql.= " ".price2num($this->total_localtax2);
4623
		$sql.= ", " . $this->situation_percent;
4624
		$sql.= ", " . (!empty($this->fk_prev_id)?$this->fk_prev_id:"null");
4625
		$sql.= ", ".(!$this->fk_unit ? 'NULL' : $this->fk_unit);
4626
		$sql.= ", ".$user->id;
4627
		$sql.= ", ".$user->id;
4628
		$sql.= ", ".(int) $this->fk_multicurrency;
4629
		$sql.= ", '".$this->db->escape($this->multicurrency_code)."'";
4630
		$sql.= ", ".price2num($this->multicurrency_subprice);
4631
		$sql.= ", ".price2num($this->multicurrency_total_ht);
4632
		$sql.= ", ".price2num($this->multicurrency_total_tva);
4633
		$sql.= ", ".price2num($this->multicurrency_total_ttc);
4634
		$sql.= ')';
4635
4636
		dol_syslog(get_class($this)."::insert", LOG_DEBUG);
4637
		$resql=$this->db->query($sql);
4638
		if ($resql)
4639
		{
4640
			$this->id=$this->db->last_insert_id(MAIN_DB_PREFIX.'facturedet');
4641
			$this->rowid=$this->id;	// For backward compatibility
4642
4643
            if (empty($conf->global->MAIN_EXTRAFIELDS_DISABLED)) // For avoid conflicts if trigger used
4644
            {
4645
            	$result=$this->insertExtraFields();
4646
            	if ($result < 0)
4647
            	{
4648
            		$error++;
4649
            	}
4650
            }
4651
4652
			// Si fk_remise_except defini, on lie la remise a la facture
4653
			// ce qui la flague comme "consommee".
4654
			if ($this->fk_remise_except)
4655
			{
4656
				$discount=new DiscountAbsolute($this->db);
4657
				$result=$discount->fetch($this->fk_remise_except);
4658
				if ($result >= 0)
4659
				{
4660
					// Check if discount was found
4661
					if ($result > 0)
4662
					{
4663
					    // Check if discount not already affected to another invoice
4664
						if ($discount->fk_facture_line > 0)
4665
						{
4666
						    if (empty($noerrorifdiscountalreadylinked))
4667
						    {
4668
    							$this->error=$langs->trans("ErrorDiscountAlreadyUsed",$discount->id);
4669
    							dol_syslog(get_class($this)."::insert Error ".$this->error, LOG_ERR);
4670
    							$this->db->rollback();
4671
    							return -3;
4672
						    }
4673
						}
4674
						else
4675
						{
4676
							$result=$discount->link_to_invoice($this->rowid,0);
4677
							if ($result < 0)
4678
							{
4679
								$this->error=$discount->error;
4680
								dol_syslog(get_class($this)."::insert Error ".$this->error, LOG_ERR);
4681
								$this->db->rollback();
4682
								return -3;
4683
							}
4684
						}
4685
					}
4686
					else
4687
					{
4688
						$this->error=$langs->trans("ErrorADiscountThatHasBeenRemovedIsIncluded");
4689
						dol_syslog(get_class($this)."::insert Error ".$this->error, LOG_ERR);
4690
						$this->db->rollback();
4691
						return -3;
4692
					}
4693
				}
4694
				else
4695
				{
4696
					$this->error=$discount->error;
4697
					dol_syslog(get_class($this)."::insert Error ".$this->error, LOG_ERR);
4698
					$this->db->rollback();
4699
					return -3;
4700
				}
4701
			}
4702
4703
			if (! $notrigger)
4704
			{
4705
                // Call trigger
4706
                $result=$this->call_trigger('LINEBILL_INSERT',$user);
4707
                if ($result < 0)
4708
                {
4709
					$this->db->rollback();
4710
					return -2;
4711
				}
4712
                // End call triggers
4713
			}
4714
4715
			$this->db->commit();
4716
			return $this->id;
4717
		}
4718
		else
4719
		{
4720
			$this->error=$this->db->lasterror();
4721
			$this->db->rollback();
4722
			return -2;
4723
		}
4724
	}
4725
4726
	/**
4727
	 *	Update line into database
4728
	 *
4729
	 *	@param		User	$user		User object
4730
	 *	@param		int		$notrigger	Disable triggers
4731
	 *	@return		int					<0 if KO, >0 if OK
4732
	 */
4733
	function update($user='',$notrigger=0)
4734
	{
4735
		global $user,$conf;
4736
4737
		$error=0;
4738
4739
		$pa_ht_isemptystring = (empty($this->pa_ht) && $this->pa_ht == ''); // If true, we can use a default value. If this->pa_ht = '0', we must use '0'.
4740
4741
		// Clean parameters
4742
		$this->desc=trim($this->desc);
4743
		if (empty($this->tva_tx)) $this->tva_tx=0;
4744
		if (empty($this->localtax1_tx)) $this->localtax1_tx=0;
4745
		if (empty($this->localtax2_tx)) $this->localtax2_tx=0;
4746
		if (empty($this->localtax1_type)) $this->localtax1_type=0;
4747
		if (empty($this->localtax2_type)) $this->localtax2_type=0;
4748
		if (empty($this->total_localtax1)) $this->total_localtax1=0;
4749
		if (empty($this->total_localtax2)) $this->total_localtax2=0;
4750
		if (empty($this->remise_percent)) $this->remise_percent=0;
4751
		if (empty($this->info_bits)) $this->info_bits=0;
4752
		if (empty($this->special_code)) $this->special_code=0;
4753
		if (empty($this->product_type)) $this->product_type=0;
4754
		if (empty($this->fk_parent_line)) $this->fk_parent_line=0;
4755
		if (! isset($this->situation_percent) || $this->situation_percent > 100 || (string) $this->situation_percent == '') $this->situation_percent = 100;
4756
		if (empty($this->pa_ht)) $this->pa_ht=0;
4757
4758
		if (empty($this->multicurrency_subprice)) $this->multicurrency_subprice=0;
4759
		if (empty($this->multicurrency_total_ht)) $this->multicurrency_total_ht=0;
4760
		if (empty($this->multicurrency_total_tva)) $this->multicurrency_total_tva=0;
4761
		if (empty($this->multicurrency_total_ttc)) $this->multicurrency_total_ttc=0;
4762
4763
		// Check parameters
4764
		if ($this->product_type < 0) return -1;
4765
4766
		// if buy price not defined, define buyprice as configured in margin admin
4767
		if ($this->pa_ht == 0 && $pa_ht_isemptystring)
4768
		{
4769
			if (($result = $this->defineBuyPrice($this->subprice, $this->remise_percent, $this->fk_product)) < 0)
4770
			{
4771
				return $result;
4772
			}
4773
			else
4774
			{
4775
				$this->pa_ht = $result;
4776
			}
4777
		}
4778
4779
		$this->db->begin();
4780
4781
        // Mise a jour ligne en base
4782
        $sql = "UPDATE ".MAIN_DB_PREFIX."facturedet SET";
4783
        $sql.= " description='".$this->db->escape($this->desc)."'";
4784
        $sql.= ", label=".(! empty($this->label)?"'".$this->db->escape($this->label)."'":"null");
4785
        $sql.= ", subprice=".price2num($this->subprice)."";
4786
        $sql.= ", remise_percent=".price2num($this->remise_percent)."";
4787
        if ($this->fk_remise_except) $sql.= ", fk_remise_except=".$this->fk_remise_except;
4788
        else $sql.= ", fk_remise_except=null";
4789
		$sql.= ", vat_src_code = '".(empty($this->vat_src_code)?'':$this->db->escape($this->vat_src_code))."'";
4790
        $sql.= ", tva_tx=".price2num($this->tva_tx)."";
4791
        $sql.= ", localtax1_tx=".price2num($this->localtax1_tx)."";
4792
        $sql.= ", localtax2_tx=".price2num($this->localtax2_tx)."";
4793
		$sql.= ", localtax1_type='".$this->db->escape($this->localtax1_type)."'";
4794
		$sql.= ", localtax2_type='".$this->db->escape($this->localtax2_type)."'";
4795
        $sql.= ", qty=".price2num($this->qty);
4796
        $sql.= ", date_start=".(! empty($this->date_start)?"'".$this->db->idate($this->date_start)."'":"null");
4797
        $sql.= ", date_end=".(! empty($this->date_end)?"'".$this->db->idate($this->date_end)."'":"null");
4798
        $sql.= ", product_type=".$this->product_type;
4799
        $sql.= ", info_bits='".$this->db->escape($this->info_bits)."'";
4800
        $sql.= ", special_code='".$this->db->escape($this->special_code)."'";
4801
        if (empty($this->skip_update_total))
4802
        {
4803
        	$sql.= ", total_ht=".price2num($this->total_ht);
4804
        	$sql.= ", total_tva=".price2num($this->total_tva);
4805
        	$sql.= ", total_ttc=".price2num($this->total_ttc);
4806
        	$sql.= ", total_localtax1=".price2num($this->total_localtax1);
4807
        	$sql.= ", total_localtax2=".price2num($this->total_localtax2);
4808
        }
4809
		$sql.= ", fk_product_fournisseur_price=".(! empty($this->fk_fournprice)?"'".$this->db->escape($this->fk_fournprice)."'":"null");
4810
		$sql.= ", buy_price_ht='".price2num($this->pa_ht)."'";
4811
		$sql.= ", fk_parent_line=".($this->fk_parent_line>0?$this->fk_parent_line:"null");
4812
		if (! empty($this->rang)) $sql.= ", rang=".$this->rang;
4813
		$sql.= ", situation_percent=" . $this->situation_percent;
4814
		$sql.= ", fk_unit=".(!$this->fk_unit ? 'NULL' : $this->fk_unit);
4815
		$sql.= ", fk_user_modif =".$user->id;
4816
4817
		// Multicurrency
4818
		$sql.= ", multicurrency_subprice=".price2num($this->multicurrency_subprice)."";
4819
        $sql.= ", multicurrency_total_ht=".price2num($this->multicurrency_total_ht)."";
4820
        $sql.= ", multicurrency_total_tva=".price2num($this->multicurrency_total_tva)."";
4821
        $sql.= ", multicurrency_total_ttc=".price2num($this->multicurrency_total_ttc)."";
4822
4823
		$sql.= " WHERE rowid = ".$this->rowid;
4824
4825
		dol_syslog(get_class($this)."::update", LOG_DEBUG);
4826
		$resql=$this->db->query($sql);
4827
		if ($resql)
4828
		{
4829
        	if (empty($conf->global->MAIN_EXTRAFIELDS_DISABLED)) // For avoid conflicts if trigger used
4830
        	{
4831
        		$this->id=$this->rowid;
4832
        		$result=$this->insertExtraFields();
4833
        		if ($result < 0)
4834
        		{
4835
        			$error++;
4836
        		}
4837
        	}
4838
4839
			if (! $error && ! $notrigger)
4840
			{
4841
                // Call trigger
4842
                $result=$this->call_trigger('LINEBILL_UPDATE',$user);
4843
                if ($result < 0)
4844
 				{
4845
					$this->db->rollback();
4846
					return -2;
4847
				}
4848
                // End call triggers
4849
			}
4850
			$this->db->commit();
4851
			return 1;
4852
		}
4853
		else
4854
		{
4855
			$this->error=$this->db->error();
4856
			$this->db->rollback();
4857
			return -2;
4858
		}
4859
	}
4860
4861
	/**
4862
	 * 	Delete line in database
4863
	 *  TODO Add param User $user and notrigger (see skeleton)
4864
     *
4865
	 *	@return	    int		           <0 if KO, >0 if OK
4866
	 */
4867
	function delete()
4868
	{
4869
		global $user;
4870
4871
		$this->db->begin();
4872
4873
		// Call trigger
4874
		$result=$this->call_trigger('LINEBILL_DELETE',$user);
4875
		if ($result < 0)
4876
		{
4877
			$this->db->rollback();
4878
			return -1;
4879
		}
4880
		// End call triggers
4881
4882
4883
		$sql = "DELETE FROM ".MAIN_DB_PREFIX."facturedet WHERE rowid = ".$this->rowid;
4884
		dol_syslog(get_class($this)."::delete", LOG_DEBUG);
4885
		if ($this->db->query($sql) )
4886
		{
4887
			$this->db->commit();
4888
			return 1;
4889
		}
4890
		else
4891
		{
4892
			$this->error=$this->db->error()." sql=".$sql;
4893
			$this->db->rollback();
4894
			return -1;
4895
		}
4896
	}
4897
4898
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
4899
	/**
4900
     *	Update DB line fields total_xxx
4901
	 *	Used by migration
4902
	 *
4903
	 *	@return		int		<0 if KO, >0 if OK
4904
	 */
4905
	function update_total()
4906
	{
4907
        // phpcs:enable
4908
		$this->db->begin();
4909
		dol_syslog(get_class($this)."::update_total", LOG_DEBUG);
4910
4911
		// Clean parameters
4912
		if (empty($this->total_localtax1)) $this->total_localtax1=0;
4913
		if (empty($this->total_localtax2)) $this->total_localtax2=0;
4914
4915
		// Mise a jour ligne en base
4916
		$sql = "UPDATE ".MAIN_DB_PREFIX."facturedet SET";
4917
		$sql.= " total_ht=".price2num($this->total_ht)."";
4918
		$sql.= ",total_tva=".price2num($this->total_tva)."";
4919
		$sql.= ",total_localtax1=".price2num($this->total_localtax1)."";
4920
		$sql.= ",total_localtax2=".price2num($this->total_localtax2)."";
4921
		$sql.= ",total_ttc=".price2num($this->total_ttc)."";
4922
		$sql.= " WHERE rowid = ".$this->rowid;
4923
4924
		dol_syslog(get_class($this)."::update_total", LOG_DEBUG);
4925
4926
		$resql=$this->db->query($sql);
4927
		if ($resql)
4928
		{
4929
			$this->db->commit();
4930
			return 1;
4931
		}
4932
		else
4933
		{
4934
			$this->error=$this->db->error();
4935
			$this->db->rollback();
4936
			return -2;
4937
		}
4938
	}
4939
4940
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
4941
	/**
4942
	 * Returns situation_percent of the previous line.
4943
	 * Warning: If invoice is a replacement invoice, this->fk_prev_id is id of the replaced line.
4944
	 *
4945
	 * @param  int     $invoiceid      Invoice id
4946
	 * @param  bool    $include_credit_note		Include credit note or not
4947
	 * @return int                     >= 0
4948
	 */
4949
	function get_prev_progress($invoiceid, $include_credit_note=true)
4950
	{
4951
        // phpcs:enable
4952
		if (is_null($this->fk_prev_id) || empty($this->fk_prev_id) || $this->fk_prev_id == "") {
4953
			return 0;
4954
		} else {
4955
		    // If invoice is not a situation invoice, this->fk_prev_id is used for something else
4956
            $tmpinvoice=new Facture($this->db);
4957
            $tmpinvoice->fetch($invoiceid);
4958
            if ($tmpinvoice->type != Facture::TYPE_SITUATION) return 0;
4959
4960
			$sql = 'SELECT situation_percent FROM ' . MAIN_DB_PREFIX . 'facturedet WHERE rowid=' . $this->fk_prev_id;
4961
			$resql = $this->db->query($sql);
4962
			if ($resql && $resql->num_rows > 0) {
4963
				$res = $this->db->fetch_array($resql);
4964
4965
				$returnPercent = floatval($res['situation_percent']);
4966
4967
				if($include_credit_note) {
4968
4969
				    $sql = 'SELECT fd.situation_percent FROM ' . MAIN_DB_PREFIX . 'facturedet fd';
4970
				    $sql.= ' JOIN ' . MAIN_DB_PREFIX . 'facture f ON (f.rowid = fd.fk_facture) ';
4971
				    $sql.= ' WHERE fd.fk_prev_id =' . $this->fk_prev_id;
4972
				    $sql.= ' AND f.situation_cycle_ref = '.$tmpinvoice->situation_cycle_ref; // Prevent cycle outed
4973
				    $sql.= ' AND f.type = '.Facture::TYPE_CREDIT_NOTE;
4974
4975
				    $res = $this->db->query($sql);
4976
				    if($res) {
4977
				        while($obj = $this->db->fetch_object($res)) {
4978
				            $returnPercent = $returnPercent + floatval($obj->situation_percent);
4979
				        }
4980
				    }
4981
				}
4982
4983
				return $returnPercent;
4984
			} else {
4985
				$this->error = $this->db->error();
4986
				dol_syslog(get_class($this) . "::select Error " . $this->error, LOG_ERR);
4987
				$this->db->rollback();
4988
				return -1;
4989
			}
4990
		}
4991
	}
4992
}
4993