dolibarr 24.0.0-beta
factureligne.class.php
Go to the documentation of this file.
1<?php
2/* Copyright (C) 2002-2007 Rodolphe Quiedeville <rodolphe@quiedeville.org>
3 * Copyright (C) 2004-2013 Laurent Destailleur <eldy@users.sourceforge.net>
4 * Copyright (C) 2004 Sebastien Di Cintio <sdicintio@ressource-toi.org>
5 * Copyright (C) 2004 Benoit Mortier <benoit.mortier@opensides.be>
6 * Copyright (C) 2005 Marc Barilley / Ocebo <marc@ocebo.com>
7 * Copyright (C) 2005-2014 Regis Houssin <regis.houssin@inodbox.com>
8 * Copyright (C) 2006 Andre Cianfarani <acianfa@free.fr>
9 * Copyright (C) 2007 Franky Van Liedekerke <franky.van.liedekerke@telenet.be>
10 * Copyright (C) 2010-2020 Juanjo Menent <jmenent@2byte.es>
11 * Copyright (C) 2012-2014 Christophe Battarel <christophe.battarel@altairis.fr>
12 * Copyright (C) 2012-2015 Marcos García <marcosgdf@gmail.com>
13 * Copyright (C) 2012 Cédric Salvador <csalvador@gpcsolutions.fr>
14 * Copyright (C) 2012-2014 Raphaël Doursenaud <rdoursenaud@gpcsolutions.fr>
15 * Copyright (C) 2013 Cedric Gross <c.gross@kreiz-it.fr>
16 * Copyright (C) 2013 Florian Henry <florian.henry@open-concept.pro>
17 * Copyright (C) 2016-2022 Ferran Marcet <fmarcet@2byte.es>
18 * Copyright (C) 2018-2024 Alexandre Spangaro <alexandre@inovea-conseil.com>
19 * Copyright (C) 2018 Nicolas ZABOURI <info@inovea-conseil.com>
20 * Copyright (C) 2022 Sylvain Legrand <contact@infras.fr>
21 * Copyright (C) 2023 Gauthier VERDOL <gauthier.verdol@atm-consulting.fr>
22 * Copyright (C) 2023 Nick Fragoulis
23 * Copyright (C) 2024-2026 MDW <mdeweerd@users.noreply.github.com>
24 * Copyright (C) 2024-2025 Frédéric France <frederic.france@free.fr>
25 *
26 * This program is free software; you can redistribute it and/or modify
27 * it under the terms of the GNU General Public License as published by
28 * the Free Software Foundation; either version 3 of the License, or
29 * (at your option) any later version.
30 *
31 * This program is distributed in the hope that it will be useful,
32 * but WITHOUT ANY WARRANTY; without even the implied warranty of
33 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
34 * GNU General Public License for more details.
35 *
36 * You should have received a copy of the GNU General Public License
37 * along with this program. If not, see <https://www.gnu.org/licenses/>.
38 */
39
46require_once DOL_DOCUMENT_ROOT.'/core/class/commoninvoice.class.php';
47require_once DOL_DOCUMENT_ROOT.'/core/class/commonobjectline.class.php';
48require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
49require_once DOL_DOCUMENT_ROOT.'/margin/lib/margins.lib.php';
50
56{
60 public $element = 'facturedet';
61
65 public $table_element = 'facturedet';
66
70 public $oldline;
71
73
76 public $fk_facture;
80 public $fk_parent_line;
81
85 public $desc;
89 public $ref_ext;
90
94 public $localtax1_type; // Local tax 1 type
98 public $localtax2_type; // Local tax 2 type
102 public $fk_remise_except; // Link to line into llx_remise_except
106 public $rang = 0;
110 public $fk_fournprice;
114 public $pa_ht;
118 public $marge_tx;
122 public $marque_tx;
123
127 public $tva_npr;
128
132 public $remise_percent;
133
137 public $batch;
141 public $fk_warehouse;
142
143
147 public $origin;
151 public $origin_id;
152
156 public $fk_code_ventilation = 0;
157
158
162 public $date_start;
166 public $date_end;
167
171 public $skip_update_total; // Skip update price total for special lines
172
176 public $fk_prev_id;
177
181 public $packaging;
182
188 public function __construct($db)
189 {
190 $this->db = $db;
191 }
192
199 public function fetch($rowid)
200 {
201 global $conf, $extrafields;
202
203 $extraFieldsCheck = false;
204 $doFetchInOneSqlRequest = getDolGlobalInt('MAIN_DO_FETCH_IN_ONE_SQL_REQUEST');
205
206 if ($doFetchInOneSqlRequest) {
207 // If $extrafields is not a known object, we initialize it
208 if (!isset($extrafields) || !is_object($extrafields)) {
209 require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php';
210 $extrafields = new ExtraFields($this->db);
211 }
212
213 // Load array of extrafields for elementype = $this->table_element
214 if (empty($extrafields->attributes[$this->table_element]['loaded'])) {
215 $extrafields->fetch_name_optionals_label($this->table_element);
216 }
217
218 $extraFieldsCheck = (
219 !empty($extrafields->attributes[$this->table_element]['label'])
220 && is_array($extrafields->attributes[$this->table_element]['label'])
221 && count($extrafields->attributes[$this->table_element]['label']) > 0
222 );
223 }
224
225 $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,';
226 $sql .= ' fd.localtax1_tx, fd. localtax2_tx, fd.remise, fd.remise_percent, fd.fk_remise_except, fd.subprice, fd.ref_ext,';
227 $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,';
228 $sql .= ' fd.info_bits, fd.special_code, fd.total_ht, fd.total_tva, fd.total_ttc, fd.total_localtax1, fd.total_localtax2, fd.rang,';
229 $sql .= ' fd.fk_code_ventilation,';
230 $sql .= ' fd.batch, fd.fk_warehouse,';
231 $sql .= ' fd.fk_unit, fd.fk_user_author, fd.fk_user_modif,';
232 $sql .= ' fd.situation_percent, fd.fk_prev_id, fd.extraparams,';
233 $sql .= ' fd.multicurrency_subprice,';
234 $sql .= ' fd.multicurrency_total_ht,';
235 $sql .= ' fd.multicurrency_total_tva,';
236 $sql .= ' fd.multicurrency_total_ttc,';
237 $sql .= ' p.ref as product_ref, p.label as product_label, p.description as product_desc,';
238 $sql .= ' p.packaging';
239
240 if ($doFetchInOneSqlRequest && $extraFieldsCheck) {
241 foreach ($extrafields->attributes[$this->table_element]['label'] as $key => $val) {
242 $type = !empty($extrafields->attributes[$this->table_element]['type'][$key])
243 ? $extrafields->attributes[$this->table_element]['type'][$key]
244 : '';
245
246 if ($type !== 'separate') {
247 if (in_array($type, array('point','multipts','linestrg','polygon'))) {
248 $sql .= ", ST_AsWKT(ef.".$this->db->sanitize($key).") as ".$this->db->sanitize($key);
249 } else {
250 $sql .= ", ef.".$this->db->sanitize($key);
251 }
252 }
253 }
254 }
255
256 $sql .= ' FROM '.$this->db->prefix().'facturedet as fd';
257
258 if ($doFetchInOneSqlRequest && $extraFieldsCheck) {
259 // Add LEFT JOIN for extrafields
260 $sql .= ' LEFT JOIN '.$this->db->prefix().$this->table_element.'_extrafields as ef ON fd.rowid = ef.fk_object';
261 }
262
263 $sql .= ' LEFT JOIN '.$this->db->prefix().'product as p ON fd.fk_product = p.rowid';
264 $sql .= ' WHERE fd.rowid = '.((int) $rowid);
265
266 $result = $this->db->query($sql);
267 if ($result) {
268 $objp = $this->db->fetch_object($result);
269
270 if (!$objp) {
271 $this->error = 'InvoiceLine with id '. $rowid .' not found sql='.$sql;
272 return 0;
273 }
274
275 $this->rowid = $objp->rowid;
276 $this->id = $objp->rowid;
277 $this->fk_facture = $objp->fk_facture;
278 $this->fk_parent_line = $objp->fk_parent_line;
279 $this->label = $objp->custom_label;
280 $this->desc = $objp->description;
281 $this->qty = $objp->qty;
282 $this->subprice = $objp->subprice;
283 $this->ref_ext = $objp->ref_ext;
284 $this->vat_src_code = $objp->vat_src_code;
285 $this->tva_tx = $objp->tva_tx;
286 $this->localtax1_tx = $objp->localtax1_tx;
287 $this->localtax2_tx = $objp->localtax2_tx;
288 $this->remise_percent = $objp->remise_percent;
289 $this->fk_remise_except = $objp->fk_remise_except;
290 $this->fk_product = $objp->fk_product;
291 $this->product_type = $objp->product_type;
292 $this->date_start = $this->db->jdate($objp->date_start);
293 $this->date_end = $this->db->jdate($objp->date_end);
294 $this->info_bits = $objp->info_bits;
295 $this->tva_npr = ((($objp->info_bits & 1) == 1) ? 1 : 0);
296 $this->special_code = $objp->special_code;
297 $this->total_ht = $objp->total_ht;
298 $this->total_tva = $objp->total_tva;
299 $this->total_localtax1 = $objp->total_localtax1;
300 $this->total_localtax2 = $objp->total_localtax2;
301 $this->total_ttc = $objp->total_ttc;
302 $this->fk_code_ventilation = $objp->fk_code_ventilation;
303 $this->batch = $objp->batch;
304 $this->fk_warehouse = $objp->fk_warehouse;
305 $this->rang = $objp->rang;
306 $this->fk_fournprice = $objp->fk_fournprice;
307 $marginInfos = getMarginInfos($objp->subprice, $objp->remise_percent, $objp->tva_tx, $objp->localtax1_tx, $objp->localtax2_tx, $this->fk_fournprice, $objp->pa_ht);
308 $this->pa_ht = $marginInfos[0];
309 $this->marge_tx = (string) $marginInfos[1];
310 $this->marque_tx = (string) $marginInfos[2];
311
312 $this->ref = $objp->product_ref; // deprecated
313
314 $this->product_ref = $objp->product_ref;
315 $this->product_label = $objp->product_label;
316 $this->product_desc = $objp->product_desc;
317
318 $this->fk_unit = $objp->fk_unit;
319 $this->fk_user_modif = $objp->fk_user_modif;
320 $this->fk_user_author = $objp->fk_user_author;
321
322 $this->situation_percent = $objp->situation_percent;
323 $this->fk_prev_id = $objp->fk_prev_id;
324
325 $this->extraparams = !empty($objp->extraparams) ? (array) json_decode($objp->extraparams, true) : array();
326
327 $this->multicurrency_subprice = $objp->multicurrency_subprice;
328 $this->multicurrency_total_ht = $objp->multicurrency_total_ht;
329 $this->multicurrency_total_tva = $objp->multicurrency_total_tva;
330 $this->multicurrency_total_ttc = $objp->multicurrency_total_ttc;
331
332 $this->packaging = $objp->packaging;
333
334 if ($doFetchInOneSqlRequest && $extraFieldsCheck) {
335 $this->array_options = array();
336
337 foreach ($extrafields->attributes[$this->table_element]['label'] as $key => $val) {
338 $type = !empty($extrafields->attributes[$this->table_element]['type'][$key])
339 ? $extrafields->attributes[$this->table_element]['type'][$key]
340 : '';
341
342 if ($type !== 'separate') {
343 $rawval = $objp->$key;
344
345 // date/datetime
346 if (in_array($type, array('date', 'datetime'))) {
347 $this->array_options['options_' . $key] = $this->db->jdate($rawval);
348 } elseif ($type == 'password') {
349 if (!empty($rawval) && preg_match('/^dolcrypt:/', $rawval)) {
350 $this->array_options['options_' . $key] = dolDecrypt($rawval);
351 } else {
352 $this->array_options['options_' . $key] = $rawval;
353 }
354 } else {
355 $this->array_options['options_' . $key] = $rawval;
356 }
357 }
358 }
359
360 // Champs "computed"
361 foreach ($extrafields->attributes[$this->table_element]['label'] as $key => $val) {
362 if (!empty($extrafields->attributes[$this->table_element]['computed'][$key])) {
363 if (empty($conf->disable_compute)) {
364 global $objectoffield;
365 $objectoffield = $this;
366 $this->array_options['options_' . $key] = dol_eval((string) $extrafields->attributes[$this->table_element]['computed'][$key], 1, 0, '2');
367 }
368 }
369 }
370 }
371
372 if (!$doFetchInOneSqlRequest) {
373 // Retrieve all extrafield
374 $this->fetch_optionals();
375 }
376
377 $this->db->free($result);
378
379 return 1;
380 } else {
381 $this->error = $this->db->lasterror();
382 return -1;
383 }
384 }
385
393 public function insert($notrigger = 0, $noerrorifdiscountalreadylinked = 0)
394 {
395 global $langs, $user;
396
397 $error = 0;
398
399 $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'.
400 $this->pa_ht = (float) $this->pa_ht; // convert to float but after checking if value is empty
401
402 dol_syslog(get_class($this)."::insert rang=".$this->rang, LOG_DEBUG);
403
404 // Clean parameters
405 $this->desc = trim($this->desc);
406 if (empty($this->tva_tx)) {
407 $this->tva_tx = 0;
408 }
409 if (empty($this->localtax1_tx)) {
410 $this->localtax1_tx = 0;
411 }
412 if (empty($this->localtax2_tx)) {
413 $this->localtax2_tx = 0;
414 }
415 if (empty($this->localtax1_type)) {
416 $this->localtax1_type = 0;
417 }
418 if (empty($this->localtax2_type)) {
419 $this->localtax2_type = 0;
420 }
421 if (empty($this->total_localtax1)) {
422 $this->total_localtax1 = 0;
423 }
424 if (empty($this->total_localtax2)) {
425 $this->total_localtax2 = 0;
426 }
427 if (empty($this->rang)) {
428 $this->rang = 0;
429 }
430 if (empty($this->remise_percent)) {
431 $this->remise_percent = 0;
432 }
433 if (empty($this->info_bits)) {
434 $this->info_bits = 0;
435 }
436 if (empty($this->subprice)) {
437 $this->subprice = 0;
438 }
439 if (empty($this->ref_ext)) {
440 $this->ref_ext = '';
441 }
442 if (empty($this->special_code)) {
443 $this->special_code = 0;
444 }
445 if (empty($this->fk_parent_line)) {
446 $this->fk_parent_line = 0;
447 }
448 if (empty($this->fk_prev_id)) {
449 $this->fk_prev_id = 0;
450 }
451 if (!isset($this->situation_percent) || $this->situation_percent > 100 || (string) $this->situation_percent == '') {
452 $this->situation_percent = 100;
453 }
454
455 if (empty($this->multicurrency_subprice)) {
456 $this->multicurrency_subprice = 0;
457 }
458 if (empty($this->multicurrency_total_ht)) {
459 $this->multicurrency_total_ht = 0;
460 }
461 if (empty($this->multicurrency_total_tva)) {
462 $this->multicurrency_total_tva = 0;
463 }
464 if (empty($this->multicurrency_total_ttc)) {
465 $this->multicurrency_total_ttc = 0;
466 }
467
468 // if buy price not defined, define buyprice as configured in margin admin
469 if ($this->pa_ht == 0 && $pa_ht_isemptystring) {
470 $result = $this->defineBuyPrice($this->subprice, $this->remise_percent, $this->fk_product);
471 if ($result < 0) {
472 return $result;
473 } else {
474 $this->pa_ht = $result;
475 }
476 }
477
478 // Check parameters
479 if ($this->product_type < 0) {
480 $this->error = 'ErrorProductTypeMustBe0orMore';
481 return -1;
482 }
483 if (!empty($this->fk_product) && $this->fk_product > 0) {
484 // Check product exists
485 $result = Product::isExistingObject('product', $this->fk_product);
486 if ($result <= 0) {
487 $this->error = 'ErrorProductIdDoesNotExists';
488 dol_syslog(get_class($this)."::insert Error ".$this->error, LOG_ERR);
489 return -1;
490 }
491 }
492
493 $this->db->begin();
494
495 // Update line in database
496 $sql = 'INSERT INTO '.MAIN_DB_PREFIX.'facturedet';
497 $sql .= ' (fk_facture, fk_parent_line, label, description, qty,';
498 $sql .= ' vat_src_code, tva_tx, localtax1_tx, localtax2_tx, localtax1_type, localtax2_type,';
499 $sql .= ' fk_product, product_type, remise_percent, subprice, ref_ext, fk_remise_except,';
500 $sql .= ' date_start, date_end, fk_code_ventilation,';
501 $sql .= ' rang, special_code, fk_product_fournisseur_price, buy_price_ht,';
502 $sql .= ' info_bits, total_ht, total_tva, total_ttc, total_localtax1, total_localtax2,';
503 $sql .= ' situation_percent, fk_prev_id,';
504 $sql .= ' fk_unit, fk_user_author, fk_user_modif,';
505 $sql .= ' fk_multicurrency, multicurrency_code, multicurrency_subprice, multicurrency_total_ht, multicurrency_total_tva, multicurrency_total_ttc,';
506 $sql .= ' batch, fk_warehouse';
507 $sql .= ')';
508 $sql .= " VALUES (".$this->fk_facture.",";
509 $sql .= " ".($this->fk_parent_line > 0 ? $this->fk_parent_line : "null").",";
510 $sql .= " ".(!empty($this->label) ? "'".$this->db->escape($this->label)."'" : "null").",";
511 $sql .= " '".$this->db->escape($this->desc)."',";
512 $sql .= " ".price2num($this->qty).",";
513 $sql .= " ".(empty($this->vat_src_code) ? "''" : "'".$this->db->escape($this->vat_src_code)."'").",";
514 $sql .= " ".price2num($this->tva_tx).",";
515 $sql .= " ".price2num($this->localtax1_tx).",";
516 $sql .= " ".price2num($this->localtax2_tx).",";
517 $sql .= " '".$this->db->escape((string) $this->localtax1_type)."',";
518 $sql .= " '".$this->db->escape((string) $this->localtax2_type)."',";
519 $sql .= ' '.((!empty($this->fk_product) && $this->fk_product > 0) ? $this->fk_product : "null").',';
520 $sql .= " ".((int) $this->product_type).",";
521 $sql .= " ".price2num($this->remise_percent).",";
522 $sql .= " ".price2num($this->subprice).",";
523 $sql .= " '".$this->db->escape($this->ref_ext)."',";
524 $sql .= ' '.(!empty($this->fk_remise_except) ? ((int) $this->fk_remise_except) : "null").',';
525 $sql .= " ".(!empty($this->date_start) ? "'".$this->db->idate($this->date_start)."'" : "null").",";
526 $sql .= " ".(!empty($this->date_end) ? "'".$this->db->idate($this->date_end)."'" : "null").",";
527 $sql .= ' '.((int) $this->fk_code_ventilation).',';
528 $sql .= ' '.((int) $this->rang).',';
529 $sql .= ' '.((int) $this->special_code).',';
530 $sql .= ' '.(!empty($this->fk_fournprice) ? ((int) $this->fk_fournprice) : "null").',';
531 $sql .= ' '.price2num($this->pa_ht).',';
532 $sql .= " '".$this->db->escape((string) $this->info_bits)."',";
533 $sql .= " ".price2num($this->total_ht).",";
534 $sql .= " ".price2num($this->total_tva).",";
535 $sql .= " ".price2num($this->total_ttc).",";
536 $sql .= " ".price2num($this->total_localtax1).",";
537 $sql .= " ".price2num($this->total_localtax2);
538 $sql .= ", ".((float) $this->situation_percent);
539 $sql .= ", ".(!empty($this->fk_prev_id) ? ((int) $this->fk_prev_id) : "null");
540 $sql .= ", ".(!$this->fk_unit ? 'NULL' : ((int) $this->fk_unit));
541 $sql .= ", ".((int) $user->id);
542 $sql .= ", ".((int) $user->id);
543 $sql .= ", ".(int) $this->fk_multicurrency;
544 $sql .= ", '".$this->db->escape($this->multicurrency_code)."'";
545 $sql .= ", ".price2num($this->multicurrency_subprice);
546 $sql .= ", ".price2num($this->multicurrency_total_ht);
547 $sql .= ", ".price2num($this->multicurrency_total_tva);
548 $sql .= ", ".price2num($this->multicurrency_total_ttc);
549 $sql .= ", '".$this->db->escape($this->batch)."'";
550 $sql .= ", ".((int) $this->fk_warehouse);
551 $sql .= ')';
552
553 dol_syslog(get_class($this)."::insert", LOG_DEBUG);
554 $resql = $this->db->query($sql);
555 if ($resql) {
556 $this->id = $this->db->last_insert_id(MAIN_DB_PREFIX.'facturedet');
557 $this->rowid = $this->id; // For backward compatibility
558
559 if (!$error) {
560 $result = $this->insertExtraFields();
561 if ($result < 0) {
562 $error++;
563 }
564 }
565
566 // If fk_remise_except is defined, the discount is linked to the invoice
567 // which flags it as "consumed".
568 if ($this->fk_remise_except && empty($error)) {
569 $discount = new DiscountAbsolute($this->db);
570 $result = $discount->fetch($this->fk_remise_except);
571 if ($result >= 0) {
572 // Check if discount was found
573 if ($result > 0) {
574 // Check if discount not already affected to another invoice
575 if ($discount->fk_facture_line > 0) {
576 if (empty($noerrorifdiscountalreadylinked)) {
577 $this->error = $langs->trans("ErrorDiscountAlreadyUsed", $discount->id);
578 dol_syslog(get_class($this)."::insert Error ".$this->error, LOG_ERR);
579 $this->db->rollback();
580 return -3;
581 }
582 } else {
583 $result = $discount->link_to_invoice($this->rowid, 0);
584 if ($result < 0) {
585 $this->error = $discount->error;
586 dol_syslog(get_class($this)."::insert Error ".$this->error, LOG_ERR);
587 $this->db->rollback();
588 return -3;
589 }
590 }
591 } else {
592 $this->error = $langs->trans("ErrorADiscountThatHasBeenRemovedIsIncluded");
593 dol_syslog(get_class($this)."::insert Error ".$this->error, LOG_ERR);
594 $this->db->rollback();
595 return -3;
596 }
597 } else {
598 $this->error = $discount->error;
599 dol_syslog(get_class($this)."::insert Error ".$this->error, LOG_ERR);
600 $this->db->rollback();
601 return -3;
602 }
603 }
604
605 if (!$notrigger && empty($error)) {
606 // Call trigger
607 $result = $this->call_trigger('LINEBILL_INSERT', $user);
608 if ($result < 0) {
609 $this->db->rollback();
610 return -2;
611 }
612 // End call triggers
613 }
614
615 if (!$error) {
616 $this->db->commit();
617 return $this->id;
618 }
619
620 foreach ($this->errors as $errmsg) {
621 dol_syslog(get_class($this)."::insert ".$errmsg, LOG_ERR);
622 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
623 }
624 $this->db->rollback();
625 return -3;
626 } else {
627 $this->error = $this->db->lasterror();
628 $this->db->rollback();
629 return -2;
630 }
631 }
632
640 public function update($user = null, $notrigger = 0)
641 {
642 global $user;
643
644 $error = 0;
645
646 $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'.
647
648 // Clean parameters
649 $this->desc = trim($this->desc);
650 if (empty($this->ref_ext)) {
651 $this->ref_ext = '';
652 }
653 if (empty($this->tva_tx)) {
654 $this->tva_tx = 0;
655 }
656 if (empty($this->localtax1_tx)) {
657 $this->localtax1_tx = 0;
658 }
659 if (empty($this->localtax2_tx)) {
660 $this->localtax2_tx = 0;
661 }
662 if (empty($this->localtax1_type)) {
663 $this->localtax1_type = 0;
664 }
665 if (empty($this->localtax2_type)) {
666 $this->localtax2_type = 0;
667 }
668 if (empty($this->total_localtax1)) {
669 $this->total_localtax1 = 0;
670 }
671 if (empty($this->total_localtax2)) {
672 $this->total_localtax2 = 0;
673 }
674 if (empty($this->remise_percent)) {
675 $this->remise_percent = 0;
676 }
677 if (empty($this->info_bits)) {
678 $this->info_bits = 0;
679 }
680 if (empty($this->special_code)) {
681 $this->special_code = 0;
682 }
683 if (empty($this->product_type)) {
684 $this->product_type = 0;
685 }
686 if (empty($this->fk_parent_line)) {
687 $this->fk_parent_line = 0;
688 }
689 if (!isset($this->situation_percent) || $this->situation_percent > 100 || (string) $this->situation_percent == '') {
690 $this->situation_percent = 100;
691 }
692 if (empty($this->pa_ht)) {
693 $this->pa_ht = 0;
694 }
695
696 if (empty($this->multicurrency_subprice)) {
697 $this->multicurrency_subprice = 0;
698 }
699 if (empty($this->multicurrency_total_ht)) {
700 $this->multicurrency_total_ht = 0;
701 }
702 if (empty($this->multicurrency_total_tva)) {
703 $this->multicurrency_total_tva = 0;
704 }
705 if (empty($this->multicurrency_total_ttc)) {
706 $this->multicurrency_total_ttc = 0;
707 }
708
709 // Check parameters
710 if ($this->product_type < 0) {
711 return -1;
712 }
713
714 // if buy price not provided, define buyprice as configured in margin admin
715 if ($this->pa_ht == 0 && $pa_ht_isemptystring) {
716 // We call defineBuyPrice only if data was not provided (if input was '0', we will not go here and value will remaine '0')
717 $result = $this->defineBuyPrice($this->subprice, $this->remise_percent, $this->fk_product);
718 if ($result < 0) {
719 return $result;
720 } else {
721 $this->pa_ht = $result;
722 }
723 }
724
725 $this->db->begin();
726
727 // Update line in database
728 $sql = "UPDATE ".MAIN_DB_PREFIX."facturedet SET";
729 $sql .= " description='".$this->db->escape($this->desc)."'";
730 $sql .= ", ref_ext='".$this->db->escape($this->ref_ext)."'";
731 $sql .= ", label=".(!empty($this->label) ? "'".$this->db->escape($this->label)."'" : "null");
732 $sql .= ", subprice=".price2num($this->subprice);
733 $sql .= ", remise_percent=".price2num($this->remise_percent);
734 if ($this->fk_remise_except) {
735 $sql .= ", fk_remise_except = ".((int) $this->fk_remise_except);
736 } else {
737 $sql .= ", fk_remise_except = null";
738 }
739 $sql .= ", vat_src_code = '".(empty($this->vat_src_code) ? '' : $this->db->escape($this->vat_src_code))."'";
740 $sql .= ", tva_tx=".price2num($this->tva_tx);
741 $sql .= ", localtax1_tx=".price2num($this->localtax1_tx);
742 $sql .= ", localtax2_tx=".price2num($this->localtax2_tx);
743 $sql .= ", localtax1_type='".$this->db->escape((string) $this->localtax1_type)."'";
744 $sql .= ", localtax2_type='".$this->db->escape((string) $this->localtax2_type)."'";
745 $sql .= ", qty = ".((float) price2num($this->qty));
746 $sql .= ", date_start = ".(!empty($this->date_start) ? "'".$this->db->idate($this->date_start)."'" : "null");
747 $sql .= ", date_end = ".(!empty($this->date_end) ? "'".$this->db->idate($this->date_end)."'" : "null");
748 $sql .= ", product_type = ".((int) $this->product_type);
749 $sql .= ", info_bits = '".$this->db->escape((string) $this->info_bits)."'";
750 $sql .= ", special_code = " . (int) $this->special_code;
751 if (empty($this->skip_update_total)) {
752 $sql .= ", total_ht = ".price2num($this->total_ht);
753 $sql .= ", total_tva = ".price2num($this->total_tva);
754 $sql .= ", total_ttc = ".price2num($this->total_ttc);
755 $sql .= ", total_localtax1 = ".price2num($this->total_localtax1);
756 $sql .= ", total_localtax2 = ".price2num($this->total_localtax2);
757 }
758 $sql .= ", fk_product_fournisseur_price = ".(!empty($this->fk_fournprice) ? "'".$this->db->escape((string) $this->fk_fournprice)."'" : "null");
759 $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)
760 $sql .= ", fk_parent_line = ".($this->fk_parent_line > 0 ? $this->fk_parent_line : "null");
761 if (!empty($this->rang)) {
762 $sql .= ", rang = ".((int) $this->rang);
763 }
764 $sql .= ", situation_percent = ".((float) $this->situation_percent);
765 $sql .= ", fk_unit = ".(!$this->fk_unit ? 'NULL' : ((int) $this->fk_unit));
766 $sql .= ", fk_user_modif = ".((int) $user->id);
767
768 // Multicurrency
769 $sql .= ", multicurrency_subprice=".price2num($this->multicurrency_subprice);
770 $sql .= ", multicurrency_total_ht=".price2num($this->multicurrency_total_ht);
771 $sql .= ", multicurrency_total_tva=".price2num($this->multicurrency_total_tva);
772 $sql .= ", multicurrency_total_ttc=".price2num($this->multicurrency_total_ttc);
773
774 $sql .= ", batch = '".$this->db->escape($this->batch)."'";
775 $sql .= ", fk_warehouse = ".((int) $this->fk_warehouse);
776
777 $sql .= " WHERE rowid = ".((int) $this->rowid);
778
779 dol_syslog(get_class($this)."::update", LOG_DEBUG);
780 $resql = $this->db->query($sql);
781 if ($resql) {
782 if (!$error) {
783 $this->id = $this->rowid;
784 $result = $this->insertExtraFields();
785 if ($result < 0) {
786 $error++;
787 }
788 }
789
790 if (!$error && !$notrigger) {
791 // Call trigger
792 $result = $this->call_trigger('LINEBILL_MODIFY', $user);
793 if ($result < 0) {
794 $this->db->rollback();
795 return -2;
796 }
797 // End call triggers
798 }
799
800 if (!$error) {
801 $this->db->commit();
802 return 1;
803 }
804
805 foreach ($this->errors as $errmsg) {
806 dol_syslog(get_class($this)."::update ".$errmsg, LOG_ERR);
807 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
808 }
809
810 $this->db->rollback();
811 return -3;
812 } else {
813 $this->error = $this->db->error();
814 $this->db->rollback();
815 return -2;
816 }
817 }
818
826 public function delete($tmpuser = null, $notrigger = 0)
827 {
828 global $user;
829
830 $this->db->begin();
831
832 // Call trigger
833 if (empty($notrigger)) {
834 $result = $this->call_trigger('LINEBILL_DELETE', $user);
835 if ($result < 0) {
836 $this->db->rollback();
837 return -1;
838 }
839 }
840 // End call triggers
841
842 // extrafields
843 $result = $this->deleteExtraFields();
844 if ($result < 0) {
845 $this->db->rollback();
846 return -1;
847 }
848
849 // Free discount linked to invoice line
850 $sql = 'UPDATE '.MAIN_DB_PREFIX.'societe_remise_except';
851 $sql .= ' SET fk_facture_line = NULL';
852 $sql .= ' WHERE fk_facture_line = '.((int) $this->id);
853
854 dol_syslog(get_class($this)."::deleteline", LOG_DEBUG);
855 $result = $this->db->query($sql);
856 if (!$result) {
857 $this->error = $this->db->error();
858 $this->errors[] = $this->error;
859 $this->db->rollback();
860 return -1;
861 }
862
863 $sql = 'UPDATE '.MAIN_DB_PREFIX.'element_time';
864 $sql .= ' SET invoice_id = NULL, invoice_line_id = NULL';
865 $sql .= ' WHERE invoice_line_id = '.((int) $this->id);
866 if (!$this->db->query($sql)) {
867 $this->error = $this->db->error()." sql=".$sql;
868 $this->errors[] = $this->error;
869 $this->db->rollback();
870 return -1;
871 }
872
873 $sql = "DELETE FROM ".MAIN_DB_PREFIX."facturedet WHERE rowid = ".((int) $this->id);
874
875 if ($this->db->query($sql)) {
876 $this->db->commit();
877 return 1;
878 } else {
879 $this->error = $this->db->error()." sql=".$sql;
880 $this->errors[] = $this->error;
881 $this->db->rollback();
882 return -1;
883 }
884 }
885
886 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
893 public function update_total()
894 {
895 // phpcs:enable
896 $this->db->begin();
897 dol_syslog(get_class($this)."::update_total", LOG_DEBUG);
898
899 // Clean parameters
900 if (empty($this->total_localtax1)) {
901 $this->total_localtax1 = 0;
902 }
903 if (empty($this->total_localtax2)) {
904 $this->total_localtax2 = 0;
905 }
906
907 // Update line in database
908 $sql = "UPDATE ".MAIN_DB_PREFIX."facturedet SET";
909 $sql .= " total_ht=".price2num($this->total_ht);
910 $sql .= ",total_tva=".price2num($this->total_tva);
911 $sql .= ",total_localtax1=".price2num($this->total_localtax1);
912 $sql .= ",total_localtax2=".price2num($this->total_localtax2);
913 $sql .= ",total_ttc=".price2num($this->total_ttc);
914 $sql .= " WHERE rowid = ".((int) $this->rowid);
915
916 dol_syslog(get_class($this)."::update_total", LOG_DEBUG);
917
918 $resql = $this->db->query($sql);
919 if ($resql) {
920 $this->db->commit();
921 return 1;
922 } else {
923 $this->error = $this->db->error();
924 $this->db->rollback();
925 return -2;
926 }
927 }
928
929 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
939 public function get_prev_progress($invoiceid, $include_credit_note = true)
940 {
941 // phpcs:enable
942 global $invoicecache;
943
944 if (is_null($this->fk_prev_id) || empty($this->fk_prev_id) || $this->fk_prev_id == "") {
945 return 0;
946 } else {
947 // If invoice is not a situation invoice, this->fk_prev_id is used for something else
948 if (!isset($invoicecache[$invoiceid])) {
949 $invoicecache[$invoiceid] = new Facture($this->db);
950 $invoicecache[$invoiceid]->fetch($invoiceid);
951 }
952 if (empty($invoicecache[$invoiceid]->situation_cycle_ref)) {
953 return 0;
954 }
955
956 $sql = "SELECT situation_percent FROM ".MAIN_DB_PREFIX."facturedet";
957 $sql .= " WHERE rowid = ".((int) $this->fk_prev_id);
958
959 $resql = $this->db->query($sql);
960
961 if ($resql && $this->db->num_rows($resql) > 0) {
962 $returnPercent = 0;
963
964 $obj = $this->db->fetch_object($resql);
965 if ($obj) {
966 $returnPercent = (float) $obj->situation_percent;
967 }
968
969 if ($include_credit_note) {
970 $sql = 'SELECT fd.situation_percent FROM '.MAIN_DB_PREFIX.'facturedet fd';
971 $sql .= ' JOIN '.MAIN_DB_PREFIX.'facture f ON (f.rowid = fd.fk_facture) ';
972 $sql .= " WHERE fd.fk_prev_id = ".((int) $this->fk_prev_id);
973 $sql .= " AND f.situation_cycle_ref = ".((int) $invoicecache[$invoiceid]->situation_cycle_ref); // Prevent cycle outed
974 $sql .= " AND f.type = ".((int) Facture::TYPE_CREDIT_NOTE);
975
976 $res = $this->db->query($sql);
977 if ($res) {
978 while ($obj = $this->db->fetch_object($res)) {
979 $returnPercent += (float) $obj->situation_percent;
980 }
981 } else {
982 dol_print_error($this->db);
983 }
984 }
985
986 return $returnPercent;
987 } else {
988 $this->error = $this->db->error();
989 dol_syslog(get_class($this)."::select Error ".$this->error, LOG_ERR);
990 $this->db->rollback();
991 return -1;
992 }
993 }
994 }
995
996 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1006 public function getAllPrevProgress($invoiceid, $include_credit_note = true)
1007 {
1008 // phpcs:enable
1009 global $invoicecache;
1010
1011 if (is_null($this->fk_prev_id) || empty($this->fk_prev_id) || $this->fk_prev_id == "") {
1012 return 0;
1013 } else {
1014 // If invoice is not a situation invoice, this->fk_prev_id is used for something else
1015 if (!isset($invoicecache[$invoiceid])) {
1016 $invoicecache[$invoiceid] = new Facture($this->db);
1017 $invoicecache[$invoiceid]->fetch($invoiceid);
1018 }
1019 if (!$invoicecache[$invoiceid]->isSituationInvoice()) {
1020 return 0;
1021 }
1022
1023 $all_found = false;
1024 $lastprevid = $this->fk_prev_id;
1025 $cumulated_percent = 0.0;
1026
1027 while (!$all_found) {
1028 $sql = "SELECT situation_percent, fk_prev_id FROM ".MAIN_DB_PREFIX."facturedet WHERE rowid = ".((int) $lastprevid);
1029 $resql = $this->db->query($sql);
1030
1031 if ($resql && $this->db->num_rows($resql) > 0) {
1032 $obj = $this->db->fetch_object($resql);
1033 $cumulated_percent += (float) $obj->situation_percent;
1034
1035 if ($include_credit_note) {
1036 $sql_credit_note = 'SELECT fd.situation_percent FROM '.MAIN_DB_PREFIX.'facturedet fd';
1037 $sql_credit_note .= ' JOIN '.MAIN_DB_PREFIX.'facture f ON (f.rowid = fd.fk_facture) ';
1038 $sql_credit_note .= " WHERE fd.fk_prev_id = ".((int) $lastprevid);
1039 $sql_credit_note .= " AND f.situation_cycle_ref = ".((int) $invoicecache[$invoiceid]->situation_cycle_ref); // Prevent cycle outed
1040 $sql_credit_note .= " AND f.type = ".Facture::TYPE_CREDIT_NOTE;
1041
1042 $res_credit_note = $this->db->query($sql_credit_note);
1043 if ($res_credit_note) {
1044 while ($cn = $this->db->fetch_object($res_credit_note)) {
1045 $cumulated_percent += (float) $cn->situation_percent;
1046 }
1047 } else {
1048 dol_print_error($this->db);
1049 }
1050 }
1051
1052 // Si fk_prev_id, on continue
1053 if ($obj->fk_prev_id) {
1054 $lastprevid = $obj->fk_prev_id;
1055 } else { // else we stop the loop
1056 $all_found = true;
1057 }
1058 } else {
1059 $this->error = $this->db->error();
1060 dol_syslog(get_class($this)."::select Error ".$this->error, LOG_ERR);
1061 $this->db->rollback();
1062 return -1;
1063 }
1064 }
1065 return $cumulated_percent;
1066 }
1067 }
1068
1082 public function getSituationRatio()
1083 {
1084 if (getDolGlobalInt('INVOICE_USE_SITUATION') === 1) {
1085 // in legacy mode, the situation invoice line stores the (cumulative) state of the
1086 // cycle at the current situation. To get the delta, we need to subtract the
1087 // state at the previous situation (if applicable).
1088 $prevProgress = $this->get_prev_progress($this->fk_facture);
1089
1090 if ($this->situation_percent == 0) {
1091 // should not happen
1092 return 0;
1093 }
1094
1095 return ($this->situation_percent - $prevProgress) / $this->situation_percent;
1096 }
1097 // new mode (INVOICE_USE_SITUATION == 2):
1098 // no ratio needed (data stored on line is already a delta)
1099 // or not a situation invoice: no ratio needed either
1100 return 1;
1101 }
1102}
$object ref
Definition info.php:90
Parent class of all other business classes for details of elements (invoices, contracts,...
fetch_optionals($rowid=null, $optionsArray=null)
Function to get extra fields of an object into $this->array_options This method is in most cases call...
defineBuyPrice($unitPrice=0.0, $discountPercent=0.0, $fk_product=0)
Get buy price to use for margin calculation.
static isExistingObject($element, $id, $ref='', $ref_ext='')
Check if an object id or ref exists If you don't need or want to instantiate the object and just need...
deleteExtraFields()
Delete all extra fields values for the current object.
insertExtraFields($trigger='', $userused=null)
Add/Update all extra fields values for the current object.
Class to manage absolute discounts.
Class to manage standard extra fields.
Class to manage invoices.
const TYPE_CREDIT_NOTE
Credit note invoice.
Class to manage invoice lines.
insert($notrigger=0, $noerrorifdiscountalreadylinked=0)
Insert line into database.
get_prev_progress($invoiceid, $include_credit_note=true)
Returns situation_percent of the previous line.
fetch($rowid)
Load invoice line from database.
update($user=null, $notrigger=0)
Update line into database.
__construct($db)
Constructor.
getSituationRatio()
Determines if we are using situation invoices.
getAllPrevProgress($invoiceid, $include_credit_note=true)
Returns situation_percent of all the previous line.
update_total()
Update DB line fields total_xxx Used by migration.
if(!isModEnabled('ai')||!getDolGlobalString('AI_ASSISTANT_ENABLED')) global $conf
The main.inc.php has been included so the following variable are now defined:
price2num($amount, $rounding='', $option=0)
Function that return a number with universal decimal format (decimal separator is '.
dol_eval($s, $returnvalue=1, $hideerrors=1, $onlysimplestring='1')
Replace eval function to add more security.
getDolGlobalInt($key, $default=0)
Return a Dolibarr global constant int value.
dol_print_error($db=null, $error='', $errors=null)
Displays error message system with all the information to facilitate the diagnosis and the escalation...
dol_syslog($message, $level=LOG_INFO, $ident=0, $suffixinfilename='', $restricttologhandler='', $logcontext=null)
Write log message into outputs.
print $langs trans("Show") . '< td style="' . $timeColor . '" align="center"> s</td > badge status0 badge status4 badge status3 Error badge status8< td align="center">< span class="badge ' . $badge . '"></span ></td >< td align="center">< a href="#" class="button button-small" onclick="openLogModal(this)" data-req="' . dol_escape_htmltag($reqSafe) . '" data-res="' . dol_escape_htmltag($resSafe) . '" data-err="' . dol_escape_htmltag($errSafe) . '">< span class="fa fa-search-plus"></span ></a ></td ></tr >< tr >< td colspan="' . $colspan . '" class="opacitymedium"></td ></tr ></table ></div ></form > logModal none logModal none s a JSON string
buildzip.php
getMarginInfos($pv_ht, $remise_percent, $tva_tx, $localtax1_tx, $localtax2_tx, $fk_pa, $pa_ht)
Return an array with margins information of a line.
dolDecrypt($chain, $key='', $patterntotest='')
Decode a string with a symmetric encryption.