dolibarr 23.0.3
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-2025 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 $situation_percent;
177
181 public $fk_prev_id;
182
186 public $packaging;
187
193 public function __construct($db)
194 {
195 $this->db = $db;
196 }
197
204 public function fetch($rowid)
205 {
206 global $conf, $extrafields;
207
208 $extraFieldsCheck = false;
209 $doFetchInOneSqlRequest = getDolGlobalInt('MAIN_DO_FETCH_IN_ONE_SQL_REQUEST');
210
211 if ($doFetchInOneSqlRequest) {
212 // If $extrafields is not a known object, we initialize it
213 if (!isset($extrafields) || !is_object($extrafields)) {
214 require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php';
215 $extrafields = new ExtraFields($this->db);
216 }
217
218 // Load array of extrafields for elementype = $this->table_element
219 if (empty($extrafields->attributes[$this->table_element]['loaded'])) {
220 $extrafields->fetch_name_optionals_label($this->table_element);
221 }
222
223 $extraFieldsCheck = (
224 !empty($extrafields->attributes[$this->table_element]['label'])
225 && is_array($extrafields->attributes[$this->table_element]['label'])
226 && count($extrafields->attributes[$this->table_element]['label']) > 0
227 );
228 }
229
230 $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,';
231 $sql .= ' fd.localtax1_tx, fd. localtax2_tx, fd.remise, fd.remise_percent, fd.fk_remise_except, fd.subprice, fd.ref_ext,';
232 $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,';
233 $sql .= ' fd.info_bits, fd.special_code, fd.total_ht, fd.total_tva, fd.total_ttc, fd.total_localtax1, fd.total_localtax2, fd.rang,';
234 $sql .= ' fd.fk_code_ventilation,';
235 $sql .= ' fd.batch, fd.fk_warehouse,';
236 $sql .= ' fd.fk_unit, fd.fk_user_author, fd.fk_user_modif,';
237 $sql .= ' fd.situation_percent, fd.fk_prev_id, fd.extraparams,';
238 $sql .= ' fd.multicurrency_subprice,';
239 $sql .= ' fd.multicurrency_total_ht,';
240 $sql .= ' fd.multicurrency_total_tva,';
241 $sql .= ' fd.multicurrency_total_ttc,';
242 $sql .= ' p.ref as product_ref, p.label as product_label, p.description as product_desc,';
243 $sql .= ' p.packaging';
244
245 if ($doFetchInOneSqlRequest && $extraFieldsCheck) {
246 foreach ($extrafields->attributes[$this->table_element]['label'] as $key => $val) {
247 $type = !empty($extrafields->attributes[$this->table_element]['type'][$key])
248 ? $extrafields->attributes[$this->table_element]['type'][$key]
249 : '';
250
251 if ($type !== 'separate') {
252 if (in_array($type, array('point','multipts','linestrg','polygon'))) {
253 $sql .= ", ST_AsWKT(ef.".$key.") as ".$key;
254 } else {
255 $sql .= ", ef.".$key;
256 }
257 }
258 }
259 }
260
261 $sql .= ' FROM '.$this->db->prefix().'facturedet as fd';
262
263 if ($doFetchInOneSqlRequest && $extraFieldsCheck) {
264 // Add LEFT JOIN for extrafields
265 $sql .= ' LEFT JOIN '.$this->db->prefix().$this->table_element.'_extrafields as ef ON fd.rowid = ef.fk_object';
266 }
267
268 $sql .= ' LEFT JOIN '.$this->db->prefix().'product as p ON fd.fk_product = p.rowid';
269 $sql .= ' WHERE fd.rowid = '.((int) $rowid);
270
271 $result = $this->db->query($sql);
272 if ($result) {
273 $objp = $this->db->fetch_object($result);
274
275 if (!$objp) {
276 $this->error = 'InvoiceLine with id '. $rowid .' not found sql='.$sql;
277 return 0;
278 }
279
280 $this->rowid = $objp->rowid;
281 $this->id = $objp->rowid;
282 $this->fk_facture = $objp->fk_facture;
283 $this->fk_parent_line = $objp->fk_parent_line;
284 $this->label = $objp->custom_label;
285 $this->desc = $objp->description;
286 $this->qty = $objp->qty;
287 $this->subprice = $objp->subprice;
288 $this->ref_ext = $objp->ref_ext;
289 $this->vat_src_code = $objp->vat_src_code;
290 $this->tva_tx = $objp->tva_tx;
291 $this->localtax1_tx = $objp->localtax1_tx;
292 $this->localtax2_tx = $objp->localtax2_tx;
293 $this->remise_percent = $objp->remise_percent;
294 $this->fk_remise_except = $objp->fk_remise_except;
295 $this->fk_product = $objp->fk_product;
296 $this->product_type = $objp->product_type;
297 $this->date_start = $this->db->jdate($objp->date_start);
298 $this->date_end = $this->db->jdate($objp->date_end);
299 $this->info_bits = $objp->info_bits;
300 $this->tva_npr = ((($objp->info_bits & 1) == 1) ? 1 : 0);
301 $this->special_code = $objp->special_code;
302 $this->total_ht = $objp->total_ht;
303 $this->total_tva = $objp->total_tva;
304 $this->total_localtax1 = $objp->total_localtax1;
305 $this->total_localtax2 = $objp->total_localtax2;
306 $this->total_ttc = $objp->total_ttc;
307 $this->fk_code_ventilation = $objp->fk_code_ventilation;
308 $this->batch = $objp->batch;
309 $this->fk_warehouse = $objp->fk_warehouse;
310 $this->rang = $objp->rang;
311 $this->fk_fournprice = $objp->fk_fournprice;
312 $marginInfos = getMarginInfos($objp->subprice, $objp->remise_percent, $objp->tva_tx, $objp->localtax1_tx, $objp->localtax2_tx, $this->fk_fournprice, $objp->pa_ht);
313 $this->pa_ht = $marginInfos[0];
314 $this->marge_tx = (string) $marginInfos[1];
315 $this->marque_tx = (string) $marginInfos[2];
316
317 $this->ref = $objp->product_ref; // deprecated
318
319 $this->product_ref = $objp->product_ref;
320 $this->product_label = $objp->product_label;
321 $this->product_desc = $objp->product_desc;
322
323 $this->fk_unit = $objp->fk_unit;
324 $this->fk_user_modif = $objp->fk_user_modif;
325 $this->fk_user_author = $objp->fk_user_author;
326
327 $this->situation_percent = $objp->situation_percent;
328 $this->fk_prev_id = $objp->fk_prev_id;
329
330 $this->extraparams = !empty($objp->extraparams) ? (array) json_decode($objp->extraparams, true) : array();
331
332 $this->multicurrency_subprice = $objp->multicurrency_subprice;
333 $this->multicurrency_total_ht = $objp->multicurrency_total_ht;
334 $this->multicurrency_total_tva = $objp->multicurrency_total_tva;
335 $this->multicurrency_total_ttc = $objp->multicurrency_total_ttc;
336
337 $this->packaging = $objp->packaging;
338
339 if ($doFetchInOneSqlRequest && $extraFieldsCheck) {
340 $this->array_options = array();
341
342 foreach ($extrafields->attributes[$this->table_element]['label'] as $key => $val) {
343 $type = !empty($extrafields->attributes[$this->table_element]['type'][$key])
344 ? $extrafields->attributes[$this->table_element]['type'][$key]
345 : '';
346
347 if ($type !== 'separate') {
348 $rawval = $objp->$key;
349
350 // date/datetime
351 if (in_array($type, array('date', 'datetime'))) {
352 $this->array_options['options_' . $key] = $this->db->jdate($rawval);
353 } elseif ($type == 'password') {
354 if (!empty($rawval) && preg_match('/^dolcrypt:/', $rawval)) {
355 $this->array_options['options_' . $key] = dolDecrypt($rawval);
356 } else {
357 $this->array_options['options_' . $key] = $rawval;
358 }
359 } else {
360 $this->array_options['options_' . $key] = $rawval;
361 }
362 }
363 }
364
365 // Champs "computed"
366 foreach ($extrafields->attributes[$this->table_element]['label'] as $key => $val) {
367 if (!empty($extrafields->attributes[$this->table_element]['computed'][$key])) {
368 if (empty($conf->disable_compute)) {
369 global $objectoffield;
370 $objectoffield = $this;
371 $this->array_options['options_' . $key] = dol_eval((string) $extrafields->attributes[$this->table_element]['computed'][$key], 1, 0, '2');
372 }
373 }
374 }
375 }
376
377 if (!$doFetchInOneSqlRequest) {
378 // Retrieve all extrafield
379 $this->fetch_optionals();
380 }
381
382 $this->db->free($result);
383
384 return 1;
385 } else {
386 $this->error = $this->db->lasterror();
387 return -1;
388 }
389 }
390
398 public function insert($notrigger = 0, $noerrorifdiscountalreadylinked = 0)
399 {
400 global $langs, $user;
401
402 $error = 0;
403
404 $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'.
405 $this->pa_ht = (float) $this->pa_ht; // convert to float but after checking if value is empty
406
407 dol_syslog(get_class($this)."::insert rang=".$this->rang, LOG_DEBUG);
408
409 // Clean parameters
410 $this->desc = trim($this->desc);
411 if (empty($this->tva_tx)) {
412 $this->tva_tx = 0;
413 }
414 if (empty($this->localtax1_tx)) {
415 $this->localtax1_tx = 0;
416 }
417 if (empty($this->localtax2_tx)) {
418 $this->localtax2_tx = 0;
419 }
420 if (empty($this->localtax1_type)) {
421 $this->localtax1_type = 0;
422 }
423 if (empty($this->localtax2_type)) {
424 $this->localtax2_type = 0;
425 }
426 if (empty($this->total_localtax1)) {
427 $this->total_localtax1 = 0;
428 }
429 if (empty($this->total_localtax2)) {
430 $this->total_localtax2 = 0;
431 }
432 if (empty($this->rang)) {
433 $this->rang = 0;
434 }
435 if (empty($this->remise_percent)) {
436 $this->remise_percent = 0;
437 }
438 if (empty($this->info_bits)) {
439 $this->info_bits = 0;
440 }
441 if (empty($this->subprice)) {
442 $this->subprice = 0;
443 }
444 if (empty($this->ref_ext)) {
445 $this->ref_ext = '';
446 }
447 if (empty($this->special_code)) {
448 $this->special_code = 0;
449 }
450 if (empty($this->fk_parent_line)) {
451 $this->fk_parent_line = 0;
452 }
453 if (empty($this->fk_prev_id)) {
454 $this->fk_prev_id = 0;
455 }
456 if (!isset($this->situation_percent) || $this->situation_percent > 100 || (string) $this->situation_percent == '') {
457 $this->situation_percent = 100;
458 }
459
460 if (empty($this->multicurrency_subprice)) {
461 $this->multicurrency_subprice = 0;
462 }
463 if (empty($this->multicurrency_total_ht)) {
464 $this->multicurrency_total_ht = 0;
465 }
466 if (empty($this->multicurrency_total_tva)) {
467 $this->multicurrency_total_tva = 0;
468 }
469 if (empty($this->multicurrency_total_ttc)) {
470 $this->multicurrency_total_ttc = 0;
471 }
472
473 // if buy price not defined, define buyprice as configured in margin admin
474 if ($this->pa_ht == 0 && $pa_ht_isemptystring) {
475 $result = $this->defineBuyPrice($this->subprice, $this->remise_percent, $this->fk_product);
476 if ($result < 0) {
477 return $result;
478 } else {
479 $this->pa_ht = $result;
480 }
481 }
482
483 // Check parameters
484 if ($this->product_type < 0) {
485 $this->error = 'ErrorProductTypeMustBe0orMore';
486 return -1;
487 }
488 if (!empty($this->fk_product) && $this->fk_product > 0) {
489 // Check product exists
490 $result = Product::isExistingObject('product', $this->fk_product);
491 if ($result <= 0) {
492 $this->error = 'ErrorProductIdDoesNotExists';
493 dol_syslog(get_class($this)."::insert Error ".$this->error, LOG_ERR);
494 return -1;
495 }
496 }
497
498 $this->db->begin();
499
500 // Update line in database
501 $sql = 'INSERT INTO '.MAIN_DB_PREFIX.'facturedet';
502 $sql .= ' (fk_facture, fk_parent_line, label, description, qty,';
503 $sql .= ' vat_src_code, tva_tx, localtax1_tx, localtax2_tx, localtax1_type, localtax2_type,';
504 $sql .= ' fk_product, product_type, remise_percent, subprice, ref_ext, fk_remise_except,';
505 $sql .= ' date_start, date_end, fk_code_ventilation,';
506 $sql .= ' rang, special_code, fk_product_fournisseur_price, buy_price_ht,';
507 $sql .= ' info_bits, total_ht, total_tva, total_ttc, total_localtax1, total_localtax2,';
508 $sql .= ' situation_percent, fk_prev_id,';
509 $sql .= ' fk_unit, fk_user_author, fk_user_modif,';
510 $sql .= ' fk_multicurrency, multicurrency_code, multicurrency_subprice, multicurrency_total_ht, multicurrency_total_tva, multicurrency_total_ttc,';
511 $sql .= ' batch, fk_warehouse';
512 $sql .= ')';
513 $sql .= " VALUES (".$this->fk_facture.",";
514 $sql .= " ".($this->fk_parent_line > 0 ? $this->fk_parent_line : "null").",";
515 $sql .= " ".(!empty($this->label) ? "'".$this->db->escape($this->label)."'" : "null").",";
516 $sql .= " '".$this->db->escape($this->desc)."',";
517 $sql .= " ".price2num($this->qty).",";
518 $sql .= " ".(empty($this->vat_src_code) ? "''" : "'".$this->db->escape($this->vat_src_code)."'").",";
519 $sql .= " ".price2num($this->tva_tx).",";
520 $sql .= " ".price2num($this->localtax1_tx).",";
521 $sql .= " ".price2num($this->localtax2_tx).",";
522 $sql .= " '".$this->db->escape((string) $this->localtax1_type)."',";
523 $sql .= " '".$this->db->escape((string) $this->localtax2_type)."',";
524 $sql .= ' '.((!empty($this->fk_product) && $this->fk_product > 0) ? $this->fk_product : "null").',';
525 $sql .= " ".((int) $this->product_type).",";
526 $sql .= " ".price2num($this->remise_percent).",";
527 $sql .= " ".price2num($this->subprice).",";
528 $sql .= " '".$this->db->escape($this->ref_ext)."',";
529 $sql .= ' '.(!empty($this->fk_remise_except) ? ((int) $this->fk_remise_except) : "null").',';
530 $sql .= " ".(!empty($this->date_start) ? "'".$this->db->idate($this->date_start)."'" : "null").",";
531 $sql .= " ".(!empty($this->date_end) ? "'".$this->db->idate($this->date_end)."'" : "null").",";
532 $sql .= ' '.((int) $this->fk_code_ventilation).',';
533 $sql .= ' '.((int) $this->rang).',';
534 $sql .= ' '.((int) $this->special_code).',';
535 $sql .= ' '.(!empty($this->fk_fournprice) ? $this->fk_fournprice : "null").',';
536 $sql .= ' '.price2num($this->pa_ht).',';
537 $sql .= " '".$this->db->escape((string) $this->info_bits)."',";
538 $sql .= " ".price2num($this->total_ht).",";
539 $sql .= " ".price2num($this->total_tva).",";
540 $sql .= " ".price2num($this->total_ttc).",";
541 $sql .= " ".price2num($this->total_localtax1).",";
542 $sql .= " ".price2num($this->total_localtax2);
543 $sql .= ", ".((float) $this->situation_percent);
544 $sql .= ", ".(!empty($this->fk_prev_id) ? $this->fk_prev_id : "null");
545 $sql .= ", ".(!$this->fk_unit ? 'NULL' : $this->fk_unit);
546 $sql .= ", ".((int) $user->id);
547 $sql .= ", ".((int) $user->id);
548 $sql .= ", ".(int) $this->fk_multicurrency;
549 $sql .= ", '".$this->db->escape($this->multicurrency_code)."'";
550 $sql .= ", ".price2num($this->multicurrency_subprice);
551 $sql .= ", ".price2num($this->multicurrency_total_ht);
552 $sql .= ", ".price2num($this->multicurrency_total_tva);
553 $sql .= ", ".price2num($this->multicurrency_total_ttc);
554 $sql .= ", '".$this->db->escape($this->batch)."'";
555 $sql .= ", ".((int) $this->fk_warehouse);
556 $sql .= ')';
557
558 dol_syslog(get_class($this)."::insert", LOG_DEBUG);
559 $resql = $this->db->query($sql);
560 if ($resql) {
561 $this->id = $this->db->last_insert_id(MAIN_DB_PREFIX.'facturedet');
562 $this->rowid = $this->id; // For backward compatibility
563
564 if (!$error) {
565 $result = $this->insertExtraFields();
566 if ($result < 0) {
567 $error++;
568 }
569 }
570
571 // If fk_remise_except is defined, the discount is linked to the invoice
572 // which flags it as "consumed".
573 if ($this->fk_remise_except && empty($error)) {
574 $discount = new DiscountAbsolute($this->db);
575 $result = $discount->fetch($this->fk_remise_except);
576 if ($result >= 0) {
577 // Check if discount was found
578 if ($result > 0) {
579 // Check if discount not already affected to another invoice
580 if ($discount->fk_facture_line > 0) {
581 if (empty($noerrorifdiscountalreadylinked)) {
582 $this->error = $langs->trans("ErrorDiscountAlreadyUsed", $discount->id);
583 dol_syslog(get_class($this)."::insert Error ".$this->error, LOG_ERR);
584 $this->db->rollback();
585 return -3;
586 }
587 } else {
588 $result = $discount->link_to_invoice($this->rowid, 0);
589 if ($result < 0) {
590 $this->error = $discount->error;
591 dol_syslog(get_class($this)."::insert Error ".$this->error, LOG_ERR);
592 $this->db->rollback();
593 return -3;
594 }
595 }
596 } else {
597 $this->error = $langs->trans("ErrorADiscountThatHasBeenRemovedIsIncluded");
598 dol_syslog(get_class($this)."::insert Error ".$this->error, LOG_ERR);
599 $this->db->rollback();
600 return -3;
601 }
602 } else {
603 $this->error = $discount->error;
604 dol_syslog(get_class($this)."::insert Error ".$this->error, LOG_ERR);
605 $this->db->rollback();
606 return -3;
607 }
608 }
609
610 if (!$notrigger && empty($error)) {
611 // Call trigger
612 $result = $this->call_trigger('LINEBILL_INSERT', $user);
613 if ($result < 0) {
614 $this->db->rollback();
615 return -2;
616 }
617 // End call triggers
618 }
619
620 if (!$error) {
621 $this->db->commit();
622 return $this->id;
623 }
624
625 foreach ($this->errors as $errmsg) {
626 dol_syslog(get_class($this)."::insert ".$errmsg, LOG_ERR);
627 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
628 }
629 $this->db->rollback();
630 return -3;
631 } else {
632 $this->error = $this->db->lasterror();
633 $this->db->rollback();
634 return -2;
635 }
636 }
637
645 public function update($user = null, $notrigger = 0)
646 {
647 global $user;
648
649 $error = 0;
650
651 $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'.
652
653 // Clean parameters
654 $this->desc = trim($this->desc);
655 if (empty($this->ref_ext)) {
656 $this->ref_ext = '';
657 }
658 if (empty($this->tva_tx)) {
659 $this->tva_tx = 0;
660 }
661 if (empty($this->localtax1_tx)) {
662 $this->localtax1_tx = 0;
663 }
664 if (empty($this->localtax2_tx)) {
665 $this->localtax2_tx = 0;
666 }
667 if (empty($this->localtax1_type)) {
668 $this->localtax1_type = 0;
669 }
670 if (empty($this->localtax2_type)) {
671 $this->localtax2_type = 0;
672 }
673 if (empty($this->total_localtax1)) {
674 $this->total_localtax1 = 0;
675 }
676 if (empty($this->total_localtax2)) {
677 $this->total_localtax2 = 0;
678 }
679 if (empty($this->remise_percent)) {
680 $this->remise_percent = 0;
681 }
682 if (empty($this->info_bits)) {
683 $this->info_bits = 0;
684 }
685 if (empty($this->special_code)) {
686 $this->special_code = 0;
687 }
688 if (empty($this->product_type)) {
689 $this->product_type = 0;
690 }
691 if (empty($this->fk_parent_line)) {
692 $this->fk_parent_line = 0;
693 }
694 if (!isset($this->situation_percent) || $this->situation_percent > 100 || (string) $this->situation_percent == '') {
695 $this->situation_percent = 100;
696 }
697 if (empty($this->pa_ht)) {
698 $this->pa_ht = 0;
699 }
700
701 if (empty($this->multicurrency_subprice)) {
702 $this->multicurrency_subprice = 0;
703 }
704 if (empty($this->multicurrency_total_ht)) {
705 $this->multicurrency_total_ht = 0;
706 }
707 if (empty($this->multicurrency_total_tva)) {
708 $this->multicurrency_total_tva = 0;
709 }
710 if (empty($this->multicurrency_total_ttc)) {
711 $this->multicurrency_total_ttc = 0;
712 }
713
714 // Check parameters
715 if ($this->product_type < 0) {
716 return -1;
717 }
718
719 // if buy price not provided, define buyprice as configured in margin admin
720 if ($this->pa_ht == 0 && $pa_ht_isemptystring) {
721 // We call defineBuyPrice only if data was not provided (if input was '0', we will not go here and value will remaine '0')
722 $result = $this->defineBuyPrice($this->subprice, $this->remise_percent, $this->fk_product);
723 if ($result < 0) {
724 return $result;
725 } else {
726 $this->pa_ht = $result;
727 }
728 }
729
730 $this->db->begin();
731
732 // Update line in database
733 $sql = "UPDATE ".MAIN_DB_PREFIX."facturedet SET";
734 $sql .= " description='".$this->db->escape($this->desc)."'";
735 $sql .= ", ref_ext='".$this->db->escape($this->ref_ext)."'";
736 $sql .= ", label=".(!empty($this->label) ? "'".$this->db->escape($this->label)."'" : "null");
737 $sql .= ", subprice=".price2num($this->subprice);
738 $sql .= ", remise_percent=".price2num($this->remise_percent);
739 if ($this->fk_remise_except) {
740 $sql .= ", fk_remise_except = ".((int) $this->fk_remise_except);
741 } else {
742 $sql .= ", fk_remise_except = null";
743 }
744 $sql .= ", vat_src_code = '".(empty($this->vat_src_code) ? '' : $this->db->escape($this->vat_src_code))."'";
745 $sql .= ", tva_tx=".price2num($this->tva_tx);
746 $sql .= ", localtax1_tx=".price2num($this->localtax1_tx);
747 $sql .= ", localtax2_tx=".price2num($this->localtax2_tx);
748 $sql .= ", localtax1_type='".$this->db->escape((string) $this->localtax1_type)."'";
749 $sql .= ", localtax2_type='".$this->db->escape((string) $this->localtax2_type)."'";
750 $sql .= ", qty = ".((float) price2num($this->qty));
751 $sql .= ", date_start = ".(!empty($this->date_start) ? "'".$this->db->idate($this->date_start)."'" : "null");
752 $sql .= ", date_end = ".(!empty($this->date_end) ? "'".$this->db->idate($this->date_end)."'" : "null");
753 $sql .= ", product_type = ".((int) $this->product_type);
754 $sql .= ", info_bits = '".$this->db->escape((string) $this->info_bits)."'";
755 $sql .= ", special_code = " . (int) $this->special_code;
756 if (empty($this->skip_update_total)) {
757 $sql .= ", total_ht = ".price2num($this->total_ht);
758 $sql .= ", total_tva = ".price2num($this->total_tva);
759 $sql .= ", total_ttc = ".price2num($this->total_ttc);
760 $sql .= ", total_localtax1 = ".price2num($this->total_localtax1);
761 $sql .= ", total_localtax2 = ".price2num($this->total_localtax2);
762 }
763 $sql .= ", fk_product_fournisseur_price = ".(!empty($this->fk_fournprice) ? "'".$this->db->escape((string) $this->fk_fournprice)."'" : "null");
764 $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)
765 $sql .= ", fk_parent_line = ".($this->fk_parent_line > 0 ? $this->fk_parent_line : "null");
766 if (!empty($this->rang)) {
767 $sql .= ", rang = ".((int) $this->rang);
768 }
769 $sql .= ", situation_percent = ".((float) $this->situation_percent);
770 $sql .= ", fk_unit = ".(!$this->fk_unit ? 'NULL' : ((int) $this->fk_unit));
771 $sql .= ", fk_user_modif = ".((int) $user->id);
772
773 // Multicurrency
774 $sql .= ", multicurrency_subprice=".price2num($this->multicurrency_subprice);
775 $sql .= ", multicurrency_total_ht=".price2num($this->multicurrency_total_ht);
776 $sql .= ", multicurrency_total_tva=".price2num($this->multicurrency_total_tva);
777 $sql .= ", multicurrency_total_ttc=".price2num($this->multicurrency_total_ttc);
778
779 $sql .= ", batch = '".$this->db->escape($this->batch)."'";
780 $sql .= ", fk_warehouse = ".((int) $this->fk_warehouse);
781
782 $sql .= " WHERE rowid = ".((int) $this->rowid);
783
784 dol_syslog(get_class($this)."::update", LOG_DEBUG);
785 $resql = $this->db->query($sql);
786 if ($resql) {
787 if (!$error) {
788 $this->id = $this->rowid;
789 $result = $this->insertExtraFields();
790 if ($result < 0) {
791 $error++;
792 }
793 }
794
795 if (!$error && !$notrigger) {
796 // Call trigger
797 $result = $this->call_trigger('LINEBILL_MODIFY', $user);
798 if ($result < 0) {
799 $this->db->rollback();
800 return -2;
801 }
802 // End call triggers
803 }
804
805 if (!$error) {
806 $this->db->commit();
807 return 1;
808 }
809
810 foreach ($this->errors as $errmsg) {
811 dol_syslog(get_class($this)."::update ".$errmsg, LOG_ERR);
812 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
813 }
814
815 $this->db->rollback();
816 return -3;
817 } else {
818 $this->error = $this->db->error();
819 $this->db->rollback();
820 return -2;
821 }
822 }
823
831 public function delete($tmpuser = null, $notrigger = 0)
832 {
833 global $user;
834
835 $this->db->begin();
836
837 // Call trigger
838 if (empty($notrigger)) {
839 $result = $this->call_trigger('LINEBILL_DELETE', $user);
840 if ($result < 0) {
841 $this->db->rollback();
842 return -1;
843 }
844 }
845 // End call triggers
846
847 // extrafields
848 $result = $this->deleteExtraFields();
849 if ($result < 0) {
850 $this->db->rollback();
851 return -1;
852 }
853
854 // Free discount linked to invoice line
855 $sql = 'UPDATE '.MAIN_DB_PREFIX.'societe_remise_except';
856 $sql .= ' SET fk_facture_line = NULL';
857 $sql .= ' WHERE fk_facture_line = '.((int) $this->id);
858
859 dol_syslog(get_class($this)."::deleteline", LOG_DEBUG);
860 $result = $this->db->query($sql);
861 if (!$result) {
862 $this->error = $this->db->error();
863 $this->errors[] = $this->error;
864 $this->db->rollback();
865 return -1;
866 }
867
868 $sql = 'UPDATE '.MAIN_DB_PREFIX.'element_time';
869 $sql .= ' SET invoice_id = NULL, invoice_line_id = NULL';
870 $sql .= ' WHERE invoice_line_id = '.((int) $this->id);
871 if (!$this->db->query($sql)) {
872 $this->error = $this->db->error()." sql=".$sql;
873 $this->errors[] = $this->error;
874 $this->db->rollback();
875 return -1;
876 }
877
878 $sql = "DELETE FROM ".MAIN_DB_PREFIX."facturedet WHERE rowid = ".((int) $this->id);
879
880 if ($this->db->query($sql)) {
881 $this->db->commit();
882 return 1;
883 } else {
884 $this->error = $this->db->error()." sql=".$sql;
885 $this->errors[] = $this->error;
886 $this->db->rollback();
887 return -1;
888 }
889 }
890
891 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
898 public function update_total()
899 {
900 // phpcs:enable
901 $this->db->begin();
902 dol_syslog(get_class($this)."::update_total", LOG_DEBUG);
903
904 // Clean parameters
905 if (empty($this->total_localtax1)) {
906 $this->total_localtax1 = 0;
907 }
908 if (empty($this->total_localtax2)) {
909 $this->total_localtax2 = 0;
910 }
911
912 // Update line in database
913 $sql = "UPDATE ".MAIN_DB_PREFIX."facturedet SET";
914 $sql .= " total_ht=".price2num($this->total_ht);
915 $sql .= ",total_tva=".price2num($this->total_tva);
916 $sql .= ",total_localtax1=".price2num($this->total_localtax1);
917 $sql .= ",total_localtax2=".price2num($this->total_localtax2);
918 $sql .= ",total_ttc=".price2num($this->total_ttc);
919 $sql .= " WHERE rowid = ".((int) $this->rowid);
920
921 dol_syslog(get_class($this)."::update_total", LOG_DEBUG);
922
923 $resql = $this->db->query($sql);
924 if ($resql) {
925 $this->db->commit();
926 return 1;
927 } else {
928 $this->error = $this->db->error();
929 $this->db->rollback();
930 return -2;
931 }
932 }
933
934 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
944 public function get_prev_progress($invoiceid, $include_credit_note = true)
945 {
946 // phpcs:enable
947 global $invoicecache;
948
949 if (is_null($this->fk_prev_id) || empty($this->fk_prev_id) || $this->fk_prev_id == "") {
950 return 0;
951 } else {
952 // If invoice is not a situation invoice, this->fk_prev_id is used for something else
953 if (!isset($invoicecache[$invoiceid])) {
954 $invoicecache[$invoiceid] = new Facture($this->db);
955 $invoicecache[$invoiceid]->fetch($invoiceid);
956 }
957 if ($invoicecache[$invoiceid]->type != Facture::TYPE_SITUATION) {
958 return 0;
959 }
960
961 $sql = "SELECT situation_percent FROM ".MAIN_DB_PREFIX."facturedet";
962 $sql .= " WHERE rowid = ".((int) $this->fk_prev_id);
963
964 $resql = $this->db->query($sql);
965
966 if ($resql && $this->db->num_rows($resql) > 0) {
967 $returnPercent = 0;
968
969 $obj = $this->db->fetch_object($resql);
970 if ($obj) {
971 $returnPercent = (float) $obj->situation_percent;
972 }
973
974 if ($include_credit_note) {
975 $sql = 'SELECT fd.situation_percent FROM '.MAIN_DB_PREFIX.'facturedet fd';
976 $sql .= ' JOIN '.MAIN_DB_PREFIX.'facture f ON (f.rowid = fd.fk_facture) ';
977 $sql .= " WHERE fd.fk_prev_id = ".((int) $this->fk_prev_id);
978 $sql .= " AND f.situation_cycle_ref = ".((int) $invoicecache[$invoiceid]->situation_cycle_ref); // Prevent cycle outed
979 $sql .= " AND f.type = ".Facture::TYPE_CREDIT_NOTE;
980
981 $res = $this->db->query($sql);
982 if ($res) {
983 while ($obj = $this->db->fetch_object($res)) {
984 $returnPercent += (float) $obj->situation_percent;
985 }
986 } else {
987 dol_print_error($this->db);
988 }
989 }
990
991 return $returnPercent;
992 } else {
993 $this->error = $this->db->error();
994 dol_syslog(get_class($this)."::select Error ".$this->error, LOG_ERR);
995 $this->db->rollback();
996 return -1;
997 }
998 }
999 }
1000
1001 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1011 public function getAllPrevProgress($invoiceid, $include_credit_note = true)
1012 {
1013 // phpcs:enable
1014 global $invoicecache;
1015
1016 if (is_null($this->fk_prev_id) || empty($this->fk_prev_id) || $this->fk_prev_id == "") {
1017 return 0;
1018 } else {
1019 // If invoice is not a situation invoice, this->fk_prev_id is used for something else
1020 if (!isset($invoicecache[$invoiceid])) {
1021 $invoicecache[$invoiceid] = new Facture($this->db);
1022 $invoicecache[$invoiceid]->fetch($invoiceid);
1023 }
1024 if (!$invoicecache[$invoiceid]->isSituationInvoice()) {
1025 return 0;
1026 }
1027
1028 $all_found = false;
1029 $lastprevid = $this->fk_prev_id;
1030 $cumulated_percent = 0.0;
1031
1032 while (!$all_found) {
1033 $sql = "SELECT situation_percent, fk_prev_id FROM ".MAIN_DB_PREFIX."facturedet WHERE rowid = ".((int) $lastprevid);
1034 $resql = $this->db->query($sql);
1035
1036 if ($resql && $this->db->num_rows($resql) > 0) {
1037 $obj = $this->db->fetch_object($resql);
1038 $cumulated_percent += (float) $obj->situation_percent;
1039
1040 if ($include_credit_note) {
1041 $sql_credit_note = 'SELECT fd.situation_percent FROM '.MAIN_DB_PREFIX.'facturedet fd';
1042 $sql_credit_note .= ' JOIN '.MAIN_DB_PREFIX.'facture f ON (f.rowid = fd.fk_facture) ';
1043 $sql_credit_note .= " WHERE fd.fk_prev_id = ".((int) $lastprevid);
1044 $sql_credit_note .= " AND f.situation_cycle_ref = ".((int) $invoicecache[$invoiceid]->situation_cycle_ref); // Prevent cycle outed
1045 $sql_credit_note .= " AND f.type = ".Facture::TYPE_CREDIT_NOTE;
1046
1047 $res_credit_note = $this->db->query($sql_credit_note);
1048 if ($res_credit_note) {
1049 while ($cn = $this->db->fetch_object($res_credit_note)) {
1050 $cumulated_percent += (float) $cn->situation_percent;
1051 }
1052 } else {
1053 dol_print_error($this->db);
1054 }
1055 }
1056
1057 // Si fk_prev_id, on continue
1058 if ($obj->fk_prev_id) {
1059 $lastprevid = $obj->fk_prev_id;
1060 } else { // Sinon on stoppe la boucle
1061 $all_found = true;
1062 }
1063 } else {
1064 $this->error = $this->db->error();
1065 dol_syslog(get_class($this)."::select Error ".$this->error, LOG_ERR);
1066 $this->db->rollback();
1067 return -1;
1068 }
1069 }
1070 return $cumulated_percent;
1071 }
1072 }
1073
1087 public function getSituationRatio()
1088 {
1089 if (getDolGlobalInt('INVOICE_USE_SITUATION') === 1) {
1090 // in legacy mode, the situation invoice line stores the (cumulative) state of the
1091 // cycle at the current situation. To get the delta, we need to subtract the
1092 // state at the previous situation (if applicable).
1093 $prevProgress = $this->get_prev_progress($this->fk_facture);
1094
1095 if ($this->situation_percent == 0) {
1096 // should not happen
1097 return 0;
1098 }
1099
1100 return ($this->situation_percent - $prevProgress) / $this->situation_percent;
1101 }
1102 // new mode (INVOICE_USE_SITUATION == 2):
1103 // no ratio needed (data stored on line is already a delta)
1104 // or not a situation invoice: no ratio needed either
1105 return 1;
1106 }
1107}
$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_SITUATION
Situation 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.
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.
getMarginInfos($pv_ht, $remise_percent, $tva_tx, $localtax1_tx, $localtax2_tx, $fk_pa, $pa_ht)
Return an array with margins information of a line.
if(preg_match('/(crypted|dolcrypt):/i', $dolibarr_main_db_pass)||!empty($dolibarr_main_db_encrypted_pass)) $conf db type
'integer', 'integer:ObjectClass:PathToClass[:AddCreateButtonOrNot[:Filter[:Sortfield]]]',...
Definition repair.php:125
dolDecrypt($chain, $key='')
Decode a string with a symmetric encryption.