1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
/* Copyright (C) 2002-2003 Rodolphe Quiedeville <[email protected]> |
4
|
|
|
* Copyright (C) 2002-2003 Jean-Louis Bergamo <[email protected]> |
5
|
|
|
* Copyright (C) 2004-2012 Laurent Destailleur <[email protected]> |
6
|
|
|
* Copyright (C) 2004 Sebastien Di Cintio <[email protected]> |
7
|
|
|
* Copyright (C) 2004 Benoit Mortier <[email protected]> |
8
|
|
|
* Copyright (C) 2009-2017 Regis Houssin <[email protected]> |
9
|
|
|
* Copyright (C) 2014-2018 Alexandre Spangaro <[email protected]> |
10
|
|
|
* Copyright (C) 2015 Marcos García <[email protected]> |
11
|
|
|
* Copyright (C) 2015-2024 Frédéric France <[email protected]> |
12
|
|
|
* Copyright (C) 2015 Raphaël Doursenaud <[email protected]> |
13
|
|
|
* Copyright (C) 2016 Juanjo Menent <[email protected]> |
14
|
|
|
* Copyright (C) 2018-2019 Thibault FOUCART <[email protected]> |
15
|
|
|
* Copyright (C) 2019 Nicolas ZABOURI <[email protected]> |
16
|
|
|
* Copyright (C) 2020 Josep Lluís Amador <[email protected]> |
17
|
|
|
* Copyright (C) 2021 Waël Almoman <[email protected]> |
18
|
|
|
* Copyright (C) 2021 Philippe Grand <[email protected]> |
19
|
|
|
* Copyright (C) 2024 MDW <[email protected]> |
20
|
|
|
* Copyright (C) 2024 Rafael San José <[email protected]> |
21
|
|
|
* |
22
|
|
|
* This program is free software; you can redistribute it and/or modify |
23
|
|
|
* it under the terms of the GNU General Public License as published by |
24
|
|
|
* the Free Software Foundation; either version 3 of the License, or |
25
|
|
|
* (at your option) any later version. |
26
|
|
|
* |
27
|
|
|
* This program is distributed in the hope that it will be useful, |
28
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
29
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
30
|
|
|
* GNU General Public License for more details. |
31
|
|
|
* |
32
|
|
|
* You should have received a copy of the GNU General Public License |
33
|
|
|
* along with this program. If not, see <https://www.gnu.org/licenses/>. |
34
|
|
|
*/ |
35
|
|
|
|
36
|
|
|
/** |
37
|
|
|
* \file htdocs/compta/facture/class/facture.class.php |
38
|
|
|
* \ingroup facture |
39
|
|
|
* \brief File of class to manage invoices |
40
|
|
|
*/ |
41
|
|
|
|
42
|
|
|
namespace DoliModules\Billing\Model; |
43
|
|
|
|
44
|
|
|
use DoliDB; |
45
|
|
|
|
46
|
|
|
/** |
47
|
|
|
* Class to manage invoice lines. |
48
|
|
|
* Saved into database table llx_facturedet |
49
|
|
|
*/ |
50
|
|
|
class InvoiceLine extends CommonInvoiceLine |
51
|
|
|
{ |
52
|
|
|
/** |
53
|
|
|
* @var string ID to identify managed object |
54
|
|
|
*/ |
55
|
|
|
public $element = 'facturedet'; |
56
|
|
|
|
57
|
|
|
/** |
58
|
|
|
* @var string Name of table without prefix where object is stored |
59
|
|
|
*/ |
60
|
|
|
public $table_element = 'facturedet'; |
61
|
|
|
|
62
|
|
|
/** |
63
|
|
|
* @var FactureLigne |
64
|
|
|
*/ |
65
|
|
|
public $oldline; |
66
|
|
|
|
67
|
|
|
//! From llx_facturedet |
68
|
|
|
//! Id facture |
69
|
|
|
public $fk_facture; |
70
|
|
|
//! Id parent line |
71
|
|
|
public $fk_parent_line; |
72
|
|
|
|
73
|
|
|
//! Description ligne |
74
|
|
|
public $desc; |
75
|
|
|
public $ref_ext; // External reference of the line |
76
|
|
|
|
77
|
|
|
public $localtax1_type; // Local tax 1 type |
78
|
|
|
public $localtax2_type; // Local tax 2 type |
79
|
|
|
public $fk_remise_except; // Link to line into llx_remise_except |
80
|
|
|
public $rang = 0; |
81
|
|
|
|
82
|
|
|
public $fk_fournprice; |
83
|
|
|
public $pa_ht; |
84
|
|
|
public $marge_tx; |
85
|
|
|
public $marque_tx; |
86
|
|
|
|
87
|
|
|
/** |
88
|
|
|
* @var int |
89
|
|
|
*/ |
90
|
|
|
public $tva_npr; |
91
|
|
|
|
92
|
|
|
public $remise_percent; |
93
|
|
|
|
94
|
|
|
/** |
95
|
|
|
* List of special options to define line: |
96
|
|
|
* 1: shipment cost lines |
97
|
|
|
* 2: ecotaxe |
98
|
|
|
* 3: ?? |
99
|
|
|
* idofmodule: a meaning for the module |
100
|
|
|
*/ |
101
|
|
|
public $special_code; |
102
|
|
|
|
103
|
|
|
/** |
104
|
|
|
* @var string To store the batch to consume in stock when using a POS module |
105
|
|
|
*/ |
106
|
|
|
public $batch; |
107
|
|
|
/** |
108
|
|
|
* @var string To store the warehouse where to consume stock when using a POS module |
109
|
|
|
*/ |
110
|
|
|
public $fk_warehouse; |
111
|
|
|
|
112
|
|
|
|
113
|
|
|
public $origin; |
114
|
|
|
public $origin_id; |
115
|
|
|
|
116
|
|
|
/** |
117
|
|
|
* @var integer Id in table llx_accounting_bookeeping to know accounting account for product line |
118
|
|
|
*/ |
119
|
|
|
public $fk_code_ventilation = 0; |
120
|
|
|
|
121
|
|
|
|
122
|
|
|
public $date_start; |
123
|
|
|
public $date_end; |
124
|
|
|
|
125
|
|
|
public $skip_update_total; // Skip update price total for special lines |
126
|
|
|
|
127
|
|
|
/** |
128
|
|
|
* @var int Situation advance percentage |
129
|
|
|
*/ |
130
|
|
|
public $situation_percent; |
131
|
|
|
|
132
|
|
|
/** |
133
|
|
|
* @var int Previous situation line id reference |
134
|
|
|
*/ |
135
|
|
|
public $fk_prev_id; |
136
|
|
|
|
137
|
|
|
/** |
138
|
|
|
* Constructor |
139
|
|
|
* |
140
|
|
|
* @param DoliDB $db handler d'acces base de donnee |
141
|
|
|
*/ |
142
|
|
|
public function __construct($db) |
143
|
|
|
{ |
144
|
|
|
$this->db = $db; |
145
|
|
|
} |
146
|
|
|
|
147
|
|
|
/** |
148
|
|
|
* Load invoice line from database |
149
|
|
|
* |
150
|
|
|
* @param int $rowid id of invoice line to get |
151
|
|
|
* @return int Return integer <0 if KO, >0 if OK |
152
|
|
|
*/ |
153
|
|
|
public function fetch($rowid) |
154
|
|
|
{ |
155
|
|
|
$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,'; |
156
|
|
|
$sql .= ' fd.localtax1_tx, fd. localtax2_tx, fd.remise, fd.remise_percent, fd.fk_remise_except, fd.subprice, fd.ref_ext,'; |
157
|
|
|
$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,'; |
158
|
|
|
$sql .= ' fd.info_bits, fd.special_code, fd.total_ht, fd.total_tva, fd.total_ttc, fd.total_localtax1, fd.total_localtax2, fd.rang,'; |
159
|
|
|
$sql .= ' fd.fk_code_ventilation,'; |
160
|
|
|
$sql .= ' fd.fk_unit, fd.fk_user_author, fd.fk_user_modif,'; |
161
|
|
|
$sql .= ' fd.situation_percent, fd.fk_prev_id,'; |
162
|
|
|
$sql .= ' fd.multicurrency_subprice,'; |
163
|
|
|
$sql .= ' fd.multicurrency_total_ht,'; |
164
|
|
|
$sql .= ' fd.multicurrency_total_tva,'; |
165
|
|
|
$sql .= ' fd.multicurrency_total_ttc,'; |
166
|
|
|
$sql .= ' p.ref as product_ref, p.label as product_label, p.description as product_desc'; |
167
|
|
|
$sql .= ' FROM ' . MAIN_DB_PREFIX . 'facturedet as fd'; |
168
|
|
|
$sql .= ' LEFT JOIN ' . MAIN_DB_PREFIX . 'product as p ON fd.fk_product = p.rowid'; |
169
|
|
|
$sql .= ' WHERE fd.rowid = ' . ((int) $rowid); |
170
|
|
|
|
171
|
|
|
$result = $this->db->query($sql); |
172
|
|
|
if ($result) { |
173
|
|
|
$objp = $this->db->fetch_object($result); |
174
|
|
|
|
175
|
|
|
if (!$objp) { |
176
|
|
|
$this->error = 'InvoiceLine with id ' . $rowid . ' not found sql=' . $sql; |
177
|
|
|
return 0; |
178
|
|
|
} |
179
|
|
|
|
180
|
|
|
$this->rowid = $objp->rowid; |
|
|
|
|
181
|
|
|
$this->id = $objp->rowid; |
182
|
|
|
$this->fk_facture = $objp->fk_facture; |
183
|
|
|
$this->fk_parent_line = $objp->fk_parent_line; |
184
|
|
|
$this->label = $objp->custom_label; |
185
|
|
|
$this->desc = $objp->description; |
186
|
|
|
$this->qty = $objp->qty; |
187
|
|
|
$this->subprice = $objp->subprice; |
188
|
|
|
$this->ref_ext = $objp->ref_ext; |
189
|
|
|
$this->vat_src_code = $objp->vat_src_code; |
190
|
|
|
$this->tva_tx = $objp->tva_tx; |
191
|
|
|
$this->localtax1_tx = $objp->localtax1_tx; |
192
|
|
|
$this->localtax2_tx = $objp->localtax2_tx; |
193
|
|
|
$this->remise_percent = $objp->remise_percent; |
194
|
|
|
$this->fk_remise_except = $objp->fk_remise_except; |
195
|
|
|
$this->fk_product = $objp->fk_product; |
196
|
|
|
$this->product_type = $objp->product_type; |
197
|
|
|
$this->date_start = $this->db->jdate($objp->date_start); |
198
|
|
|
$this->date_end = $this->db->jdate($objp->date_end); |
199
|
|
|
$this->info_bits = $objp->info_bits; |
200
|
|
|
$this->tva_npr = (($objp->info_bits & 1) == 1) ? 1 : 0; |
201
|
|
|
$this->special_code = $objp->special_code; |
202
|
|
|
$this->total_ht = $objp->total_ht; |
203
|
|
|
$this->total_tva = $objp->total_tva; |
204
|
|
|
$this->total_localtax1 = $objp->total_localtax1; |
205
|
|
|
$this->total_localtax2 = $objp->total_localtax2; |
206
|
|
|
$this->total_ttc = $objp->total_ttc; |
207
|
|
|
$this->fk_code_ventilation = $objp->fk_code_ventilation; |
208
|
|
|
$this->rang = $objp->rang; |
209
|
|
|
$this->fk_fournprice = $objp->fk_fournprice; |
210
|
|
|
$marginInfos = getMarginInfos($objp->subprice, $objp->remise_percent, $objp->tva_tx, $objp->localtax1_tx, $objp->localtax2_tx, $this->fk_fournprice, $objp->pa_ht); |
211
|
|
|
$this->pa_ht = $marginInfos[0]; |
212
|
|
|
$this->marge_tx = $marginInfos[1]; |
213
|
|
|
$this->marque_tx = $marginInfos[2]; |
214
|
|
|
|
215
|
|
|
$this->ref = $objp->product_ref; // deprecated |
216
|
|
|
|
217
|
|
|
$this->product_ref = $objp->product_ref; |
218
|
|
|
$this->product_label = $objp->product_label; |
219
|
|
|
$this->product_desc = $objp->product_desc; |
220
|
|
|
|
221
|
|
|
$this->fk_unit = $objp->fk_unit; |
222
|
|
|
$this->fk_user_modif = $objp->fk_user_modif; |
|
|
|
|
223
|
|
|
$this->fk_user_author = $objp->fk_user_author; |
|
|
|
|
224
|
|
|
|
225
|
|
|
$this->situation_percent = $objp->situation_percent; |
226
|
|
|
$this->fk_prev_id = $objp->fk_prev_id; |
227
|
|
|
|
228
|
|
|
$this->multicurrency_subprice = $objp->multicurrency_subprice; |
229
|
|
|
$this->multicurrency_total_ht = $objp->multicurrency_total_ht; |
230
|
|
|
$this->multicurrency_total_tva = $objp->multicurrency_total_tva; |
231
|
|
|
$this->multicurrency_total_ttc = $objp->multicurrency_total_ttc; |
232
|
|
|
|
233
|
|
|
$this->fetch_optionals(); |
234
|
|
|
|
235
|
|
|
$this->db->free($result); |
236
|
|
|
|
237
|
|
|
return 1; |
238
|
|
|
} else { |
239
|
|
|
$this->error = $this->db->lasterror(); |
240
|
|
|
return -1; |
241
|
|
|
} |
242
|
|
|
} |
243
|
|
|
|
244
|
|
|
/** |
245
|
|
|
* Insert line into database |
246
|
|
|
* |
247
|
|
|
* @param int $notrigger 1 no triggers |
248
|
|
|
* @param int $noerrorifdiscountalreadylinked 1=Do not make error if lines is linked to a discount and discount already linked to another |
249
|
|
|
* @return int Return integer <0 if KO, >0 if OK |
250
|
|
|
*/ |
251
|
|
|
public function insert($notrigger = 0, $noerrorifdiscountalreadylinked = 0) |
252
|
|
|
{ |
253
|
|
|
global $langs, $user; |
254
|
|
|
|
255
|
|
|
$error = 0; |
256
|
|
|
|
257
|
|
|
$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'. |
258
|
|
|
|
259
|
|
|
dol_syslog(get_class($this) . "::insert rang=" . $this->rang, LOG_DEBUG); |
260
|
|
|
|
261
|
|
|
// Clean parameters |
262
|
|
|
$this->desc = trim($this->desc); |
263
|
|
|
if (empty($this->tva_tx)) { |
264
|
|
|
$this->tva_tx = 0; |
265
|
|
|
} |
266
|
|
|
if (empty($this->localtax1_tx)) { |
267
|
|
|
$this->localtax1_tx = 0; |
268
|
|
|
} |
269
|
|
|
if (empty($this->localtax2_tx)) { |
270
|
|
|
$this->localtax2_tx = 0; |
271
|
|
|
} |
272
|
|
|
if (empty($this->localtax1_type)) { |
273
|
|
|
$this->localtax1_type = 0; |
274
|
|
|
} |
275
|
|
|
if (empty($this->localtax2_type)) { |
276
|
|
|
$this->localtax2_type = 0; |
277
|
|
|
} |
278
|
|
|
if (empty($this->total_localtax1)) { |
279
|
|
|
$this->total_localtax1 = 0; |
280
|
|
|
} |
281
|
|
|
if (empty($this->total_localtax2)) { |
282
|
|
|
$this->total_localtax2 = 0; |
283
|
|
|
} |
284
|
|
|
if (empty($this->rang)) { |
285
|
|
|
$this->rang = 0; |
286
|
|
|
} |
287
|
|
|
if (empty($this->remise_percent)) { |
288
|
|
|
$this->remise_percent = 0; |
289
|
|
|
} |
290
|
|
|
if (empty($this->info_bits)) { |
291
|
|
|
$this->info_bits = 0; |
292
|
|
|
} |
293
|
|
|
if (empty($this->subprice)) { |
294
|
|
|
$this->subprice = 0; |
295
|
|
|
} |
296
|
|
|
if (empty($this->ref_ext)) { |
297
|
|
|
$this->ref_ext = ''; |
298
|
|
|
} |
299
|
|
|
if (empty($this->special_code)) { |
300
|
|
|
$this->special_code = 0; |
301
|
|
|
} |
302
|
|
|
if (empty($this->fk_parent_line)) { |
303
|
|
|
$this->fk_parent_line = 0; |
304
|
|
|
} |
305
|
|
|
if (empty($this->fk_prev_id)) { |
306
|
|
|
$this->fk_prev_id = 0; |
307
|
|
|
} |
308
|
|
|
if (!isset($this->situation_percent) || $this->situation_percent > 100 || (string) $this->situation_percent == '') { |
309
|
|
|
$this->situation_percent = 100; |
310
|
|
|
} |
311
|
|
|
|
312
|
|
|
if (empty($this->pa_ht)) { |
313
|
|
|
$this->pa_ht = 0; |
314
|
|
|
} |
315
|
|
|
if (empty($this->multicurrency_subprice)) { |
316
|
|
|
$this->multicurrency_subprice = 0; |
317
|
|
|
} |
318
|
|
|
if (empty($this->multicurrency_total_ht)) { |
319
|
|
|
$this->multicurrency_total_ht = 0; |
320
|
|
|
} |
321
|
|
|
if (empty($this->multicurrency_total_tva)) { |
322
|
|
|
$this->multicurrency_total_tva = 0; |
323
|
|
|
} |
324
|
|
|
if (empty($this->multicurrency_total_ttc)) { |
325
|
|
|
$this->multicurrency_total_ttc = 0; |
326
|
|
|
} |
327
|
|
|
|
328
|
|
|
// if buy price not defined, define buyprice as configured in margin admin |
329
|
|
|
if ($this->pa_ht == 0 && $pa_ht_isemptystring) { |
330
|
|
|
$result = $this->defineBuyPrice($this->subprice, $this->remise_percent, $this->fk_product); |
331
|
|
|
if ($result < 0) { |
332
|
|
|
return $result; |
333
|
|
|
} else { |
334
|
|
|
$this->pa_ht = $result; |
335
|
|
|
} |
336
|
|
|
} |
337
|
|
|
|
338
|
|
|
// Check parameters |
339
|
|
|
if ($this->product_type < 0) { |
340
|
|
|
$this->error = 'ErrorProductTypeMustBe0orMore'; |
341
|
|
|
return -1; |
342
|
|
|
} |
343
|
|
|
if (!empty($this->fk_product) && $this->fk_product > 0) { |
344
|
|
|
// Check product exists |
345
|
|
|
$result = Product::isExistingObject('product', $this->fk_product); |
|
|
|
|
346
|
|
|
if ($result <= 0) { |
347
|
|
|
$this->error = 'ErrorProductIdDoesNotExists'; |
348
|
|
|
dol_syslog(get_class($this) . "::insert Error " . $this->error, LOG_ERR); |
349
|
|
|
return -1; |
350
|
|
|
} |
351
|
|
|
} |
352
|
|
|
|
353
|
|
|
$this->db->begin(); |
354
|
|
|
|
355
|
|
|
// Update line in database |
356
|
|
|
$sql = 'INSERT INTO ' . MAIN_DB_PREFIX . 'facturedet'; |
357
|
|
|
$sql .= ' (fk_facture, fk_parent_line, label, description, qty,'; |
358
|
|
|
$sql .= ' vat_src_code, tva_tx, localtax1_tx, localtax2_tx, localtax1_type, localtax2_type,'; |
359
|
|
|
$sql .= ' fk_product, product_type, remise_percent, subprice, ref_ext, fk_remise_except,'; |
360
|
|
|
$sql .= ' date_start, date_end, fk_code_ventilation,'; |
361
|
|
|
$sql .= ' rang, special_code, fk_product_fournisseur_price, buy_price_ht,'; |
362
|
|
|
$sql .= ' info_bits, total_ht, total_tva, total_ttc, total_localtax1, total_localtax2,'; |
363
|
|
|
$sql .= ' situation_percent, fk_prev_id,'; |
364
|
|
|
$sql .= ' fk_unit, fk_user_author, fk_user_modif,'; |
365
|
|
|
$sql .= ' fk_multicurrency, multicurrency_code, multicurrency_subprice, multicurrency_total_ht, multicurrency_total_tva, multicurrency_total_ttc'; |
366
|
|
|
$sql .= ')'; |
367
|
|
|
$sql .= " VALUES (" . $this->fk_facture . ","; |
368
|
|
|
$sql .= " " . ($this->fk_parent_line > 0 ? $this->fk_parent_line : "null") . ","; |
369
|
|
|
$sql .= " " . (!empty($this->label) ? "'" . $this->db->escape($this->label) . "'" : "null") . ","; |
370
|
|
|
$sql .= " '" . $this->db->escape($this->desc) . "',"; |
371
|
|
|
$sql .= " " . price2num($this->qty) . ","; |
372
|
|
|
$sql .= " " . (empty($this->vat_src_code) ? "''" : "'" . $this->db->escape($this->vat_src_code) . "'") . ","; |
373
|
|
|
$sql .= " " . price2num($this->tva_tx) . ","; |
374
|
|
|
$sql .= " " . price2num($this->localtax1_tx) . ","; |
375
|
|
|
$sql .= " " . price2num($this->localtax2_tx) . ","; |
376
|
|
|
$sql .= " '" . $this->db->escape($this->localtax1_type) . "',"; |
377
|
|
|
$sql .= " '" . $this->db->escape($this->localtax2_type) . "',"; |
378
|
|
|
$sql .= ' ' . ((!empty($this->fk_product) && $this->fk_product > 0) ? $this->fk_product : "null") . ','; |
379
|
|
|
$sql .= " " . ((int) $this->product_type) . ","; |
380
|
|
|
$sql .= " " . price2num($this->remise_percent) . ","; |
381
|
|
|
$sql .= " " . price2num($this->subprice) . ","; |
382
|
|
|
$sql .= " '" . $this->db->escape($this->ref_ext) . "',"; |
383
|
|
|
$sql .= ' ' . (!empty($this->fk_remise_except) ? $this->fk_remise_except : "null") . ','; |
384
|
|
|
$sql .= " " . (!empty($this->date_start) ? "'" . $this->db->idate($this->date_start) . "'" : "null") . ","; |
385
|
|
|
$sql .= " " . (!empty($this->date_end) ? "'" . $this->db->idate($this->date_end) . "'" : "null") . ","; |
386
|
|
|
$sql .= ' ' . ((int) $this->fk_code_ventilation) . ','; |
387
|
|
|
$sql .= ' ' . ((int) $this->rang) . ','; |
388
|
|
|
$sql .= ' ' . ((int) $this->special_code) . ','; |
389
|
|
|
$sql .= ' ' . (!empty($this->fk_fournprice) ? $this->fk_fournprice : "null") . ','; |
390
|
|
|
$sql .= ' ' . price2num($this->pa_ht) . ','; |
391
|
|
|
$sql .= " '" . $this->db->escape($this->info_bits) . "',"; |
392
|
|
|
$sql .= " " . price2num($this->total_ht) . ","; |
393
|
|
|
$sql .= " " . price2num($this->total_tva) . ","; |
394
|
|
|
$sql .= " " . price2num($this->total_ttc) . ","; |
395
|
|
|
$sql .= " " . price2num($this->total_localtax1) . ","; |
396
|
|
|
$sql .= " " . price2num($this->total_localtax2); |
397
|
|
|
$sql .= ", " . ((float) $this->situation_percent); |
398
|
|
|
$sql .= ", " . (!empty($this->fk_prev_id) ? $this->fk_prev_id : "null"); |
399
|
|
|
$sql .= ", " . (!$this->fk_unit ? 'NULL' : $this->fk_unit); |
400
|
|
|
$sql .= ", " . ((int) $user->id); |
401
|
|
|
$sql .= ", " . ((int) $user->id); |
402
|
|
|
$sql .= ", " . (int) $this->fk_multicurrency; |
403
|
|
|
$sql .= ", '" . $this->db->escape($this->multicurrency_code) . "'"; |
404
|
|
|
$sql .= ", " . price2num($this->multicurrency_subprice); |
405
|
|
|
$sql .= ", " . price2num($this->multicurrency_total_ht); |
406
|
|
|
$sql .= ", " . price2num($this->multicurrency_total_tva); |
407
|
|
|
$sql .= ", " . price2num($this->multicurrency_total_ttc); |
408
|
|
|
$sql .= ')'; |
409
|
|
|
|
410
|
|
|
dol_syslog(get_class($this) . "::insert", LOG_DEBUG); |
411
|
|
|
$resql = $this->db->query($sql); |
412
|
|
|
if ($resql) { |
413
|
|
|
$this->id = $this->db->last_insert_id(MAIN_DB_PREFIX . 'facturedet'); |
414
|
|
|
$this->rowid = $this->id; // For backward compatibility |
415
|
|
|
|
416
|
|
|
if (!$error) { |
417
|
|
|
$result = $this->insertExtraFields(); |
418
|
|
|
if ($result < 0) { |
419
|
|
|
$error++; |
420
|
|
|
} |
421
|
|
|
} |
422
|
|
|
|
423
|
|
|
// If fk_remise_except is defined, the discount is linked to the invoice |
424
|
|
|
// which flags it as "consumed". |
425
|
|
|
if ($this->fk_remise_except) { |
426
|
|
|
$discount = new DiscountAbsolute($this->db); |
|
|
|
|
427
|
|
|
$result = $discount->fetch($this->fk_remise_except); |
428
|
|
|
if ($result >= 0) { |
429
|
|
|
// Check if discount was found |
430
|
|
|
if ($result > 0) { |
431
|
|
|
// Check if discount not already affected to another invoice |
432
|
|
|
if ($discount->fk_facture_line > 0) { |
433
|
|
|
if (empty($noerrorifdiscountalreadylinked)) { |
434
|
|
|
$this->error = $langs->trans("ErrorDiscountAlreadyUsed", $discount->id); |
435
|
|
|
dol_syslog(get_class($this) . "::insert Error " . $this->error, LOG_ERR); |
436
|
|
|
$this->db->rollback(); |
437
|
|
|
return -3; |
438
|
|
|
} |
439
|
|
|
} else { |
440
|
|
|
$result = $discount->link_to_invoice($this->rowid, 0); |
441
|
|
|
if ($result < 0) { |
442
|
|
|
$this->error = $discount->error; |
443
|
|
|
dol_syslog(get_class($this) . "::insert Error " . $this->error, LOG_ERR); |
444
|
|
|
$this->db->rollback(); |
445
|
|
|
return -3; |
446
|
|
|
} |
447
|
|
|
} |
448
|
|
|
} else { |
449
|
|
|
$this->error = $langs->trans("ErrorADiscountThatHasBeenRemovedIsIncluded"); |
450
|
|
|
dol_syslog(get_class($this) . "::insert Error " . $this->error, LOG_ERR); |
451
|
|
|
$this->db->rollback(); |
452
|
|
|
return -3; |
453
|
|
|
} |
454
|
|
|
} else { |
455
|
|
|
$this->error = $discount->error; |
456
|
|
|
dol_syslog(get_class($this) . "::insert Error " . $this->error, LOG_ERR); |
457
|
|
|
$this->db->rollback(); |
458
|
|
|
return -3; |
459
|
|
|
} |
460
|
|
|
} |
461
|
|
|
|
462
|
|
|
if (!$notrigger) { |
463
|
|
|
// Call trigger |
464
|
|
|
$result = $this->call_trigger('LINEBILL_INSERT', $user); |
465
|
|
|
if ($result < 0) { |
466
|
|
|
$this->db->rollback(); |
467
|
|
|
return -2; |
468
|
|
|
} |
469
|
|
|
// End call triggers |
470
|
|
|
} |
471
|
|
|
|
472
|
|
|
$this->db->commit(); |
473
|
|
|
return $this->id; |
474
|
|
|
} else { |
475
|
|
|
$this->error = $this->db->lasterror(); |
476
|
|
|
$this->db->rollback(); |
477
|
|
|
return -2; |
478
|
|
|
} |
479
|
|
|
} |
480
|
|
|
|
481
|
|
|
/** |
482
|
|
|
* Update line into database |
483
|
|
|
* |
484
|
|
|
* @param User $user User object |
|
|
|
|
485
|
|
|
* @param int $notrigger Disable triggers |
486
|
|
|
* @return int Return integer <0 if KO, >0 if OK |
487
|
|
|
*/ |
488
|
|
|
public function update($user = null, $notrigger = 0) |
489
|
|
|
{ |
490
|
|
|
global $user, $conf; |
491
|
|
|
|
492
|
|
|
$error = 0; |
493
|
|
|
|
494
|
|
|
$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'. |
495
|
|
|
|
496
|
|
|
// Clean parameters |
497
|
|
|
$this->desc = trim($this->desc); |
498
|
|
|
if (empty($this->ref_ext)) { |
499
|
|
|
$this->ref_ext = ''; |
500
|
|
|
} |
501
|
|
|
if (empty($this->tva_tx)) { |
502
|
|
|
$this->tva_tx = 0; |
503
|
|
|
} |
504
|
|
|
if (empty($this->localtax1_tx)) { |
505
|
|
|
$this->localtax1_tx = 0; |
506
|
|
|
} |
507
|
|
|
if (empty($this->localtax2_tx)) { |
508
|
|
|
$this->localtax2_tx = 0; |
509
|
|
|
} |
510
|
|
|
if (empty($this->localtax1_type)) { |
511
|
|
|
$this->localtax1_type = 0; |
512
|
|
|
} |
513
|
|
|
if (empty($this->localtax2_type)) { |
514
|
|
|
$this->localtax2_type = 0; |
515
|
|
|
} |
516
|
|
|
if (empty($this->total_localtax1)) { |
517
|
|
|
$this->total_localtax1 = 0; |
518
|
|
|
} |
519
|
|
|
if (empty($this->total_localtax2)) { |
520
|
|
|
$this->total_localtax2 = 0; |
521
|
|
|
} |
522
|
|
|
if (empty($this->remise_percent)) { |
523
|
|
|
$this->remise_percent = 0; |
524
|
|
|
} |
525
|
|
|
if (empty($this->info_bits)) { |
526
|
|
|
$this->info_bits = 0; |
527
|
|
|
} |
528
|
|
|
if (empty($this->special_code)) { |
529
|
|
|
$this->special_code = 0; |
530
|
|
|
} |
531
|
|
|
if (empty($this->product_type)) { |
532
|
|
|
$this->product_type = 0; |
533
|
|
|
} |
534
|
|
|
if (empty($this->fk_parent_line)) { |
535
|
|
|
$this->fk_parent_line = 0; |
536
|
|
|
} |
537
|
|
|
if (!isset($this->situation_percent) || $this->situation_percent > 100 || (string) $this->situation_percent == '') { |
538
|
|
|
$this->situation_percent = 100; |
539
|
|
|
} |
540
|
|
|
if (empty($this->pa_ht)) { |
541
|
|
|
$this->pa_ht = 0; |
542
|
|
|
} |
543
|
|
|
|
544
|
|
|
if (empty($this->multicurrency_subprice)) { |
545
|
|
|
$this->multicurrency_subprice = 0; |
546
|
|
|
} |
547
|
|
|
if (empty($this->multicurrency_total_ht)) { |
548
|
|
|
$this->multicurrency_total_ht = 0; |
549
|
|
|
} |
550
|
|
|
if (empty($this->multicurrency_total_tva)) { |
551
|
|
|
$this->multicurrency_total_tva = 0; |
552
|
|
|
} |
553
|
|
|
if (empty($this->multicurrency_total_ttc)) { |
554
|
|
|
$this->multicurrency_total_ttc = 0; |
555
|
|
|
} |
556
|
|
|
|
557
|
|
|
// Check parameters |
558
|
|
|
if ($this->product_type < 0) { |
559
|
|
|
return -1; |
560
|
|
|
} |
561
|
|
|
|
562
|
|
|
// if buy price not provided, define buyprice as configured in margin admin |
563
|
|
|
if ($this->pa_ht == 0 && $pa_ht_isemptystring) { |
564
|
|
|
// We call defineBuyPrice only if data was not provided (if input was '0', we will not go here and value will remaine '0') |
565
|
|
|
$result = $this->defineBuyPrice($this->subprice, $this->remise_percent, $this->fk_product); |
566
|
|
|
if ($result < 0) { |
567
|
|
|
return $result; |
568
|
|
|
} else { |
569
|
|
|
$this->pa_ht = $result; |
570
|
|
|
} |
571
|
|
|
} |
572
|
|
|
|
573
|
|
|
$this->db->begin(); |
574
|
|
|
|
575
|
|
|
// Update line in database |
576
|
|
|
$sql = "UPDATE " . MAIN_DB_PREFIX . "facturedet SET"; |
577
|
|
|
$sql .= " description='" . $this->db->escape($this->desc) . "'"; |
578
|
|
|
$sql .= ", ref_ext='" . $this->db->escape($this->ref_ext) . "'"; |
579
|
|
|
$sql .= ", label=" . (!empty($this->label) ? "'" . $this->db->escape($this->label) . "'" : "null"); |
580
|
|
|
$sql .= ", subprice=" . price2num($this->subprice); |
581
|
|
|
$sql .= ", remise_percent=" . price2num($this->remise_percent); |
582
|
|
|
if ($this->fk_remise_except) { |
583
|
|
|
$sql .= ", fk_remise_except=" . $this->fk_remise_except; |
584
|
|
|
} else { |
585
|
|
|
$sql .= ", fk_remise_except=null"; |
586
|
|
|
} |
587
|
|
|
$sql .= ", vat_src_code = '" . (empty($this->vat_src_code) ? '' : $this->db->escape($this->vat_src_code)) . "'"; |
588
|
|
|
$sql .= ", tva_tx=" . price2num($this->tva_tx); |
589
|
|
|
$sql .= ", localtax1_tx=" . price2num($this->localtax1_tx); |
590
|
|
|
$sql .= ", localtax2_tx=" . price2num($this->localtax2_tx); |
591
|
|
|
$sql .= ", localtax1_type='" . $this->db->escape($this->localtax1_type) . "'"; |
592
|
|
|
$sql .= ", localtax2_type='" . $this->db->escape($this->localtax2_type) . "'"; |
593
|
|
|
$sql .= ", qty=" . price2num($this->qty); |
594
|
|
|
$sql .= ", date_start=" . (!empty($this->date_start) ? "'" . $this->db->idate($this->date_start) . "'" : "null"); |
595
|
|
|
$sql .= ", date_end=" . (!empty($this->date_end) ? "'" . $this->db->idate($this->date_end) . "'" : "null"); |
596
|
|
|
$sql .= ", product_type=" . $this->product_type; |
597
|
|
|
$sql .= ", info_bits='" . $this->db->escape($this->info_bits) . "'"; |
598
|
|
|
$sql .= ", special_code='" . $this->db->escape($this->special_code) . "'"; |
599
|
|
|
if (empty($this->skip_update_total)) { |
600
|
|
|
$sql .= ", total_ht=" . price2num($this->total_ht); |
601
|
|
|
$sql .= ", total_tva=" . price2num($this->total_tva); |
602
|
|
|
$sql .= ", total_ttc=" . price2num($this->total_ttc); |
603
|
|
|
$sql .= ", total_localtax1=" . price2num($this->total_localtax1); |
604
|
|
|
$sql .= ", total_localtax2=" . price2num($this->total_localtax2); |
605
|
|
|
} |
606
|
|
|
$sql .= ", fk_product_fournisseur_price=" . (!empty($this->fk_fournprice) ? "'" . $this->db->escape($this->fk_fournprice) . "'" : "null"); |
607
|
|
|
$sql .= ", buy_price_ht=" . (($this->pa_ht || (string) $this->pa_ht === '0') ? price2num($this->pa_ht) : "null"); // $this->pa_ht should always be defined (set to 0 or to sell price depending on option) |
608
|
|
|
$sql .= ", fk_parent_line=" . ($this->fk_parent_line > 0 ? $this->fk_parent_line : "null"); |
609
|
|
|
if (!empty($this->rang)) { |
610
|
|
|
$sql .= ", rang=" . ((int) $this->rang); |
611
|
|
|
} |
612
|
|
|
$sql .= ", situation_percent = " . ((float) $this->situation_percent); |
613
|
|
|
$sql .= ", fk_unit = " . (!$this->fk_unit ? 'NULL' : $this->fk_unit); |
614
|
|
|
$sql .= ", fk_user_modif = " . ((int) $user->id); |
615
|
|
|
|
616
|
|
|
// Multicurrency |
617
|
|
|
$sql .= ", multicurrency_subprice=" . price2num($this->multicurrency_subprice); |
618
|
|
|
$sql .= ", multicurrency_total_ht=" . price2num($this->multicurrency_total_ht); |
619
|
|
|
$sql .= ", multicurrency_total_tva=" . price2num($this->multicurrency_total_tva); |
620
|
|
|
$sql .= ", multicurrency_total_ttc=" . price2num($this->multicurrency_total_ttc); |
621
|
|
|
|
622
|
|
|
$sql .= " WHERE rowid = " . ((int) $this->rowid); |
623
|
|
|
|
624
|
|
|
dol_syslog(get_class($this) . "::update", LOG_DEBUG); |
625
|
|
|
$resql = $this->db->query($sql); |
626
|
|
|
if ($resql) { |
627
|
|
|
if (!$error) { |
628
|
|
|
$this->id = $this->rowid; |
629
|
|
|
$result = $this->insertExtraFields(); |
630
|
|
|
if ($result < 0) { |
631
|
|
|
$error++; |
632
|
|
|
} |
633
|
|
|
} |
634
|
|
|
|
635
|
|
|
if (!$error && !$notrigger) { |
636
|
|
|
// Call trigger |
637
|
|
|
$result = $this->call_trigger('LINEBILL_MODIFY', $user); |
638
|
|
|
if ($result < 0) { |
639
|
|
|
$this->db->rollback(); |
640
|
|
|
return -2; |
641
|
|
|
} |
642
|
|
|
// End call triggers |
643
|
|
|
} |
644
|
|
|
$this->db->commit(); |
645
|
|
|
return 1; |
646
|
|
|
} else { |
647
|
|
|
$this->error = $this->db->error(); |
648
|
|
|
$this->db->rollback(); |
649
|
|
|
return -2; |
650
|
|
|
} |
651
|
|
|
} |
652
|
|
|
|
653
|
|
|
/** |
654
|
|
|
* Delete line in database |
655
|
|
|
* |
656
|
|
|
* @param User $tmpuser User that deletes |
657
|
|
|
* @param int $notrigger 0=launch triggers after, 1=disable triggers |
658
|
|
|
* @return int Return integer <0 if KO, >0 if OK |
659
|
|
|
*/ |
660
|
|
|
public function delete($tmpuser = null, $notrigger = 0) |
661
|
|
|
{ |
662
|
|
|
global $user; |
663
|
|
|
|
664
|
|
|
$this->db->begin(); |
665
|
|
|
|
666
|
|
|
// Call trigger |
667
|
|
|
if (empty($notrigger)) { |
668
|
|
|
$result = $this->call_trigger('LINEBILL_DELETE', $user); |
669
|
|
|
if ($result < 0) { |
670
|
|
|
$this->db->rollback(); |
671
|
|
|
return -1; |
672
|
|
|
} |
673
|
|
|
} |
674
|
|
|
// End call triggers |
675
|
|
|
|
676
|
|
|
// extrafields |
677
|
|
|
$result = $this->deleteExtraFields(); |
678
|
|
|
if ($result < 0) { |
679
|
|
|
$this->db->rollback(); |
680
|
|
|
return -1; |
681
|
|
|
} |
682
|
|
|
|
683
|
|
|
// Free discount linked to invoice line |
684
|
|
|
$sql = 'UPDATE ' . MAIN_DB_PREFIX . 'societe_remise_except'; |
685
|
|
|
$sql .= ' SET fk_facture_line = NULL'; |
686
|
|
|
$sql .= ' WHERE fk_facture_line = ' . ((int) $this->id); |
687
|
|
|
|
688
|
|
|
dol_syslog(get_class($this) . "::deleteline", LOG_DEBUG); |
689
|
|
|
$result = $this->db->query($sql); |
690
|
|
|
if (!$result) { |
691
|
|
|
$this->error = $this->db->error(); |
692
|
|
|
$this->errors[] = $this->error; |
693
|
|
|
$this->db->rollback(); |
694
|
|
|
return -1; |
695
|
|
|
} |
696
|
|
|
|
697
|
|
|
$sql = 'UPDATE ' . MAIN_DB_PREFIX . 'element_time'; |
698
|
|
|
$sql .= ' SET invoice_id = NULL, invoice_line_id = NULL'; |
699
|
|
|
$sql .= ' WHERE invoice_line_id = ' . ((int) $this->id); |
700
|
|
|
if (!$this->db->query($sql)) { |
701
|
|
|
$this->error = $this->db->error() . " sql=" . $sql; |
702
|
|
|
$this->errors[] = $this->error; |
703
|
|
|
$this->db->rollback(); |
704
|
|
|
return -1; |
705
|
|
|
} |
706
|
|
|
|
707
|
|
|
$sql = "DELETE FROM " . MAIN_DB_PREFIX . "facturedet WHERE rowid = " . ((int) $this->id); |
708
|
|
|
|
709
|
|
|
if ($this->db->query($sql)) { |
710
|
|
|
$this->db->commit(); |
711
|
|
|
return 1; |
712
|
|
|
} else { |
713
|
|
|
$this->error = $this->db->error() . " sql=" . $sql; |
714
|
|
|
$this->errors[] = $this->error; |
715
|
|
|
$this->db->rollback(); |
716
|
|
|
return -1; |
717
|
|
|
} |
718
|
|
|
} |
719
|
|
|
|
720
|
|
|
// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps |
721
|
|
|
/** |
722
|
|
|
* Update DB line fields total_xxx |
723
|
|
|
* Used by migration |
724
|
|
|
* |
725
|
|
|
* @return int Return integer <0 if KO, >0 if OK |
726
|
|
|
*/ |
727
|
|
|
public function update_total() |
728
|
|
|
{ |
729
|
|
|
// phpcs:enable |
730
|
|
|
$this->db->begin(); |
731
|
|
|
dol_syslog(get_class($this) . "::update_total", LOG_DEBUG); |
732
|
|
|
|
733
|
|
|
// Clean parameters |
734
|
|
|
if (empty($this->total_localtax1)) { |
735
|
|
|
$this->total_localtax1 = 0; |
736
|
|
|
} |
737
|
|
|
if (empty($this->total_localtax2)) { |
738
|
|
|
$this->total_localtax2 = 0; |
739
|
|
|
} |
740
|
|
|
|
741
|
|
|
// Update line in database |
742
|
|
|
$sql = "UPDATE " . MAIN_DB_PREFIX . "facturedet SET"; |
743
|
|
|
$sql .= " total_ht=" . price2num($this->total_ht); |
744
|
|
|
$sql .= ",total_tva=" . price2num($this->total_tva); |
745
|
|
|
$sql .= ",total_localtax1=" . price2num($this->total_localtax1); |
746
|
|
|
$sql .= ",total_localtax2=" . price2num($this->total_localtax2); |
747
|
|
|
$sql .= ",total_ttc=" . price2num($this->total_ttc); |
748
|
|
|
$sql .= " WHERE rowid = " . ((int) $this->rowid); |
|
|
|
|
749
|
|
|
|
750
|
|
|
dol_syslog(get_class($this) . "::update_total", LOG_DEBUG); |
751
|
|
|
|
752
|
|
|
$resql = $this->db->query($sql); |
753
|
|
|
if ($resql) { |
754
|
|
|
$this->db->commit(); |
755
|
|
|
return 1; |
756
|
|
|
} else { |
757
|
|
|
$this->error = $this->db->error(); |
758
|
|
|
$this->db->rollback(); |
759
|
|
|
return -2; |
760
|
|
|
} |
761
|
|
|
} |
762
|
|
|
|
763
|
|
|
// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps |
764
|
|
|
/** |
765
|
|
|
* Returns situation_percent of the previous line. |
766
|
|
|
* Warning: If invoice is a replacement invoice, this->fk_prev_id is id of the replaced line. |
767
|
|
|
* |
768
|
|
|
* @param int $invoiceid Invoice id |
769
|
|
|
* @param bool $include_credit_note Include credit note or not |
770
|
|
|
* @return float|int Reurrn previous situation percent, 0 or -1 if error |
771
|
|
|
*/ |
772
|
|
|
public function get_prev_progress($invoiceid, $include_credit_note = true) |
773
|
|
|
{ |
774
|
|
|
// phpcs:enable |
775
|
|
|
global $invoicecache; |
776
|
|
|
|
777
|
|
|
if (is_null($this->fk_prev_id) || empty($this->fk_prev_id) || $this->fk_prev_id == "") { |
778
|
|
|
return 0; |
779
|
|
|
} else { |
780
|
|
|
// If invoice is not a situation invoice, this->fk_prev_id is used for something else |
781
|
|
|
if (!isset($invoicecache[$invoiceid])) { |
782
|
|
|
$invoicecache[$invoiceid] = new Facture($this->db); |
783
|
|
|
$invoicecache[$invoiceid]->fetch($invoiceid); |
784
|
|
|
} |
785
|
|
|
if ($invoicecache[$invoiceid]->type != Facture::TYPE_SITUATION) { |
786
|
|
|
return 0; |
787
|
|
|
} |
788
|
|
|
|
789
|
|
|
$sql = "SELECT situation_percent FROM " . MAIN_DB_PREFIX . "facturedet WHERE rowid = " . ((int) $this->fk_prev_id); |
790
|
|
|
$resql = $this->db->query($sql); |
791
|
|
|
if ($resql && $this->db->num_rows($resql) > 0) { |
792
|
|
|
$res = $this->db->fetch_array($resql); |
793
|
|
|
|
794
|
|
|
$returnPercent = (float) $res['situation_percent']; |
795
|
|
|
|
796
|
|
|
if ($include_credit_note) { |
797
|
|
|
$sql = 'SELECT fd.situation_percent FROM ' . MAIN_DB_PREFIX . 'facturedet fd'; |
798
|
|
|
$sql .= ' JOIN ' . MAIN_DB_PREFIX . 'facture f ON (f.rowid = fd.fk_facture) '; |
799
|
|
|
$sql .= " WHERE fd.fk_prev_id = " . ((int) $this->fk_prev_id); |
800
|
|
|
$sql .= " AND f.situation_cycle_ref = " . ((int) $invoicecache[$invoiceid]->situation_cycle_ref); // Prevent cycle outed |
801
|
|
|
$sql .= " AND f.type = " . Facture::TYPE_CREDIT_NOTE; |
802
|
|
|
|
803
|
|
|
$res = $this->db->query($sql); |
804
|
|
|
if ($res) { |
805
|
|
|
while ($obj = $this->db->fetch_object($res)) { |
806
|
|
|
$returnPercent = $returnPercent + (float) $obj->situation_percent; |
807
|
|
|
} |
808
|
|
|
} else { |
809
|
|
|
dol_print_error($this->db); |
810
|
|
|
} |
811
|
|
|
} |
812
|
|
|
|
813
|
|
|
return $returnPercent; |
814
|
|
|
} else { |
815
|
|
|
$this->error = $this->db->error(); |
816
|
|
|
dol_syslog(get_class($this) . "::select Error " . $this->error, LOG_ERR); |
817
|
|
|
$this->db->rollback(); |
818
|
|
|
return -1; |
819
|
|
|
} |
820
|
|
|
} |
821
|
|
|
} |
822
|
|
|
} |
823
|
|
|
|
This property has been deprecated. The supplier of the class has supplied an explanatory message.
The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.