dolibarr 23.0.3
commonsubtotal.class.php
1<?php
2/* Copyright (C) 2014-2017 Laurent Destailleur <eldy@users.sourceforge.net>
3 * Copyright (C) 2024 MDW <mdeweerd@users.noreply.github.com>
4 * Copyright (C) 2024-2025 Frédéric France <frederic.france@free.fr>
5 * Copyright (C) 2025 Charlene Benke <charlene@patas-monkey.com>
6
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 3 of the License, or
11 * (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program. If not, see <https://www.gnu.org/licenses/>.
20 * or see https://www.gnu.org/
21 */
22
23if (!defined('SUBTOTALS_SPECIAL_CODE')) {
24 define('SUBTOTALS_SPECIAL_CODE', 81);
25}
26
33trait CommonSubtotal
34{
39 public static $PRODUCT_TYPE = 9;
40
45 public static $TITLE_OPTIONS = ['titleshowuponpdf', 'titleshowtotalexludingvatonpdf', 'titleforcepagebreak'];
46
51 public static $SUBTOTAL_OPTIONS = ['subtotalshowtotalexludingvatonpdf'];
52
56 public static $ALLOWED_TYPES = [
57 'propal',
58 'commande',
59 'facture',
60 'facturerec',
61 'shipping',
62 ];
63
78 public function addSubtotalLine($langs, $desc, $depth, $options = array(), $parent_line = 0)
79 {
80 if (empty($desc)) {
81 $this->errors[] = $langs->trans("TitleNeedDesc");
82 return -1;
83 }
84 $current_module = $this->element;
85 // Ensure the object is one of the supported types
86 if (!in_array($current_module, self::$ALLOWED_TYPES)) {
87 $this->errors[] = $langs->trans("UnsupportedModuleError");
88 return -1; // Unsupported type
89 }
90 $error = 0;
91 $desc = dol_html_entity_decode($desc, ENT_QUOTES);
92 $rang = -1;
93 $next_line = false;
94 $result = 0;
95
96 if ($depth < 0 && $current_module != 'shipping') {
97 foreach ($this->lines as $line) {
98 if (!$next_line && $line->desc == $desc && $line->qty == -$depth) {
99 $next_line = true;
100 continue;
101 }
102 if ($next_line && $line->desc == $desc && $line->qty == $depth) {
103 $next_line = false;
104 continue;
105 }
106 if ($next_line && $line->special_code == SUBTOTALS_SPECIAL_CODE && abs($line->qty) <= abs($depth)) {
107 $rang = $line->rang;
108 break;
109 }
110 }
111 }
112
113 if ($depth > 0 && $current_module != 'shipping') {
114 $max_existing_level = 0;
115
116 foreach ($this->lines as $line) {
117 if ($line->special_code == SUBTOTALS_SPECIAL_CODE && $line->qty > $max_existing_level) {
118 $max_existing_level = $line->qty;
119 }
120 }
121
122 if ($max_existing_level+1 < $depth) {
123 $depth = $max_existing_level+1;
124 $this->errors[] = $langs->trans("TitleAddedLevelTooHigh", $depth);
125
126 $error ++;
127 }
128 }
129
130 // Add the line calling the right module
131 if ($current_module == 'facture' && $this instanceof Facture) {
132 $result = $this->addline(
133 $desc, // Description
134 0, // Unit price
135 $depth, // Quantity
136 0, // VAT rate
137 0, // Local tax 1
138 0, // Local tax 2
139 0, // FK product
140 0, // Discount percentage
141 '', // Date start
142 '', // Date end
143 0, // FK code ventilation
144 0, // Info bits
145 0, // FK remise except
146 '', // Price base type
147 0, // PU ttc
148 self::$PRODUCT_TYPE, // Type
149 $rang, // Rang
150 SUBTOTALS_SPECIAL_CODE // Special code
151 );
152 } elseif ($current_module == 'propal' && $this instanceof Propal) {
153 $result = $this->addline(
154 $desc, // Description
155 0, // Unit price
156 $depth, // Quantity
157 0, // VAT rate
158 0, // Local tax 1
159 0, // Local tax 2
160 0, // FK product
161 0, // Discount percentage
162 '', // Price base type
163 0, // PU ttc
164 0, // Info bits
165 self::$PRODUCT_TYPE, // Type
166 $rang, // Rang
167 SUBTOTALS_SPECIAL_CODE // Special code
168 );
169 } elseif ($current_module == 'commande' && $this instanceof Commande) {
170 $result = $this->addline(
171 $desc, // Description
172 0, // Unit price
173 $depth, // Quantity
174 0, // VAT rate
175 0, // Local tax 1
176 0, // Local tax 2
177 0, // FK product
178 0, // Discount percentage
179 0, // Info bits
180 0, // FK remise except
181 '', // Price base type
182 0, // PU ttc
183 '', // Date start
184 '', // Date end
185 self::$PRODUCT_TYPE, // Type
186 $rang, // Rang
187 SUBTOTALS_SPECIAL_CODE // Special code
188 );
189 } elseif ($current_module == 'shipping' && $this instanceof Expedition) {
190 $result = $this->addline(
191 0, // Warehouse ID
192 (int) $parent_line, // Source line
193 $depth // Quantity
194 );
195 } elseif ($current_module == 'facturerec' && $this instanceof FactureRec) {
196 $rang = $rang == -1 ? $rang : $rang-1;
197 $result = $this->addline(
198 $desc, // Description
199 0, // Unit price
200 $depth, // Quantity
201 0, // VAT rate
202 0, // Local tax 1
203 0, // Local tax 2
204 0, // FK product
205 0, // Discount percentage
206 '', // Price base type
207 0, // Info bits
208 0, // FK remise except
209 0, // PU ttc
210 self::$PRODUCT_TYPE, // Type
211 $rang, // Rang
212 SUBTOTALS_SPECIAL_CODE // Special code
213 );
214 $this->fetch_lines();
215 } elseif ($current_module == 'fichinter' && $this instanceof Fichinter) {
216 global $user;
217 $result = $this->addline(
218 $user, // user
219 $this->id, // fk_fichinter
220 $desc, // Description
221 0, // dateintervention
222 $depth, // duration
223 [], // arrayoption
224 self::$PRODUCT_TYPE, // Type
225 $rang, // Rang
226 SUBTOTALS_SPECIAL_CODE // Special code
227 );
228 }
229
230
231 if ($current_module != 'shipping') {
232 foreach ($this->lines as $line) {
233 '@phan-var-force CommonObjectLine $line';
235 if ($line->id == $result) {
236 $line->extraparams["subtotal"] = $options;
237 $line->setExtraParameters();
238 }
239 }
240 }
241
242 if ($result < 0) {
243 return $result;
244 }
245
246 return $error > 0 ? 0 : $result;
247 }
248
262 public function deleteSubtotalLine($langs, $id, $correspondingstline = false, $user = null)
263 {
264 $current_module = $this->element;
265 // Ensure the object is one of the supported types
266 if (!in_array($current_module, self::$ALLOWED_TYPES)) {
267 $this->errors[] = $langs->trans("UnsupportedModuleError");
268 return -1; // Unsupported type
269 }
270
271 $result = 0;
272
273 if ($correspondingstline) {
274 $oldDesc = "";
275 $oldDepth = 0;
276 foreach ($this->lines as $line) {
277 if ($line->id == $id) {
278 $oldDesc = $line->desc;
279 $oldDepth = $line->qty;
280 }
281 if ($line->special_code == SUBTOTALS_SPECIAL_CODE && $line->qty == -$oldDepth && $line->desc == $oldDesc) {
282 $this->deleteSubtotalLine($langs, $line->id, false, $user);
283 break;
284 }
285 }
286 }
287
288 // Add the line calling the right module
289 if ($current_module == 'facture' && $this instanceof Facture) {
290 $rowid = $id; // for phan suspicious parameter order...
291 $result = $this->deleteLine($rowid);
292 } elseif ($current_module == 'propal' && $this instanceof Propal) {
293 $rowid = $id; // for phan suspicious parameter order...
294 $result = $this->deleteLine($rowid);
295 } elseif ($current_module == 'commande' && $this instanceof Commande) {
296 $lineid = $id; // for phan suspicious parameter order...
297 $result = $this->deleteLine($user, $lineid);
298 } elseif ($current_module == 'facturerec') {
299 $line = new FactureLigneRec($this->db);
300 $line->id = $id;
301 $result = $line->delete($user);
302 } elseif ($current_module == 'shipping') {
303 $line = new ExpeditionLigne($this->db);
304 $line->id = $id;
305 $result = $line->delete($user);
306 }
307
308 return $result >= 0 ? $result : -1; // Return line ID or false
309 }
310
326 public function updateSubtotalLine($langs, $lineid, $desc, $depth, $options) // @phpstan-ignore-line
327 {
328 $current_module = $this->element;
329 // Ensure the object is one of the supported types
330 if (!in_array($current_module, self::$ALLOWED_TYPES)) {
331 $this->errors[] = $langs->trans("UnsupportedModuleError");
332 return -1; // Unsupported type
333 }
334
335 $result = 0;
336 $error = 0;
337
338 $max_existing_level = 0;
339
340 if ($depth>0) {
341 foreach ($this->lines as $line) {
342 if ($line->special_code == SUBTOTALS_SPECIAL_CODE && $line->qty > $max_existing_level && $line->id != $lineid) {
343 $max_existing_level = $line->qty;
344 }
345 }
346 }
347
348 if ($max_existing_level+1 < $depth) {
349 $depth = $max_existing_level+1;
350 $this->errors[] = $langs->trans("TitleEditedLevelTooHigh");
351 $error ++;
352 }
353
354 if ($depth>0) {
355 $oldDesc = "";
356 $oldDepth = 0;
357 foreach ($this->lines as $line) {
358 if ($line->id == $lineid) {
359 $oldDesc = $line->desc;
360 $oldDepth = $line->qty;
361 }
362 if ($line->special_code == SUBTOTALS_SPECIAL_CODE && $line->qty == -$oldDepth && $line->desc == $oldDesc) {
363 $this->updateSubtotalLine($langs, $line->id, $desc, -$depth, !empty($line->extraparams["subtotal"]) ? $line->extraparams["subtotal"] : array());
364 break;
365 }
366 }
367 }
368
369 // Update the line calling the right module
370 if ($current_module == 'facture' && $this instanceof Facture) {
371 $result = $this->updateline(
372 $lineid, // ID of line to change
373 $desc, // Description
374 0, // Unit price
375 $depth, // Quantity
376 0, // Discount percentage
377 '', // Date start
378 '', // Date end
379 0, // VAT rate
380 0, // Local tax 1
381 0, // Local tax 2
382 '', // Price base type
383 0, // Info bits
384 self::$PRODUCT_TYPE, // Type
385 0, // FK parent line
386 0, // Skip update total
387 0, // FK fournprice
388 0, // PA ht
389 '', // Label
390 SUBTOTALS_SPECIAL_CODE // Special code
391 );
392 } elseif ($current_module == 'propal' && $this instanceof Propal) {
393 $result = $this->updateline(
394 $lineid, // ID of line to change
395 0, // Unit price
396 $depth, // Quantity
397 0, // Discount percentage
398 0, // VAT rate
399 0, // Local tax 1
400 0, // Local tax 2
401 $desc, // Description
402 '', // Price base type
403 0, // Info bits
404 SUBTOTALS_SPECIAL_CODE, // Special code
405 0, // FK parent line
406 0, // Skip update total
407 0, // FK fournprice
408 0, // PA ht
409 '', // Label
410 self::$PRODUCT_TYPE // Type
411 );
412 } elseif ($current_module == 'commande' && $this instanceof Commande) {
413 $result = $this->updateline(
414 $lineid, // ID of line to change
415 $desc, // Description
416 0, // Unit price
417 $depth, // Quantity
418 0, // Discount percentage
419 0, // VAT rate
420 0, // Local tax 1
421 0, // Local tax 2
422 '', // Price base type
423 0, // Info bits
424 '', // Date start
425 '', // Date end
426 self::$PRODUCT_TYPE, // Type
427 0, // FK parent line
428 0, // Skip update total
429 0, // FK fournprice
430 0, // PA ht
431 '', // Label
432 SUBTOTALS_SPECIAL_CODE // Special code
433 );
434 } elseif ($current_module == 'facturerec' && $this instanceof FactureRec) {
435 $objectline = new FactureLigneRec($this->db);
436 $objectline->fetch($lineid);
437 $line_rang = $objectline->rang;
438 $result = $this->updateline(
439 $lineid, // ID of line to change
440 $desc, // Description
441 0, // Unit price
442 $depth, // Quantity
443 0, // VAT rate
444 0, // Local tax 1
445 0, // Local tax 2
446 0, // FK parent line
447 0, // Discount percentage
448 '', // Price base type
449 0, // Info bits
450 0, // FK parent line
451 0, // PU ttc
452 self::$PRODUCT_TYPE, // Type
453 $line_rang, // Rang
454 SUBTOTALS_SPECIAL_CODE // Special code
455 );
456 }
457
458 foreach ($this->lines as $line) {
459 '@phan-var-force CommonObjectLine $line';
461 if ($line->id == $lineid) {
462 $line->extraparams["subtotal"] = $options;
463 $line->setExtraParameters();
464 }
465 }
466
467 if ($result < 0) {
468 return $result;
469 }
470
471 return $error > 0 ? 0 : $result;
472 }
473
486 public function updateSubtotalLineBlockLines($langs, $linerang, $mode, $value) // @phpstan-ignore-line
487 {
488 $current_module = $this->element;
489 // Ensure the object is one of the supported types
490 if (!in_array($current_module, self::$ALLOWED_TYPES)) {
491 $this->errors[] = $langs->trans("UnsupportedModuleError");
492 return -1; // Unsupported type
493 }
494
495 $result = 0;
496 $linerang -= 1;
497
498 $nb_lines = count($this->lines)+1;
499
500 for ($i = $linerang+1; $i < $nb_lines; $i++) {
501 if ($this->lines[$i]->special_code == SUBTOTALS_SPECIAL_CODE) {
502 if (abs($this->lines[$i]->qty) <= (int) $this->lines[$linerang]->qty) {
503 return 1;
504 }
505 } else {
506 if ($current_module == 'facture' && $this instanceof Facture) {
507 $result = $this->updateline(
508 $this->lines[$i]->id,
509 $this->lines[$i]->desc,
510 $this->lines[$i]->subprice,
511 $this->lines[$i]->qty,
512 $mode == 'discount' ? $value : $this->lines[$i]->remise_percent,
513 $this->lines[$i]->date_start,
514 $this->lines[$i]->date_end,
515 $mode == 'tva' ? $value : $this->lines[$i]->tva_tx,
516 $this->lines[$i]->localtax1_tx,
517 $this->lines[$i]->localtax2_tx,
518 'HT',
519 $this->lines[$i]->info_bits,
520 $this->lines[$i]->product_type,
521 $this->lines[$i]->fk_parent_line, 0,
522 $this->lines[$i]->fk_fournprice,
523 $this->lines[$i]->pa_ht,
524 $this->lines[$i]->label,
525 $this->lines[$i]->special_code,
526 $this->lines[$i]->array_options,
527 $this->lines[$i]->situation_percent,
528 $this->lines[$i]->fk_unit,
529 $this->lines[$i]->multicurrency_subprice
530 );
531 } elseif ($current_module == 'commande' && $this instanceof Commande) {
532 $result = $this->updateline(
533 $this->lines[$i]->id,
534 $this->lines[$i]->desc,
535 $this->lines[$i]->subprice,
536 $this->lines[$i]->qty,
537 $mode == 'discount' ? $value : $this->lines[$i]->remise_percent,
538 $mode == 'tva' ? $value : $this->lines[$i]->tva_tx,
539 $this->lines[$i]->localtax1_rate,
540 $this->lines[$i]->localtax2_rate,
541 'HT',
542 $this->lines[$i]->info_bits,
543 $this->lines[$i]->date_start,
544 $this->lines[$i]->date_end,
545 $this->lines[$i]->product_type,
546 $this->lines[$i]->fk_parent_line, 0,
547 $this->lines[$i]->fk_fournprice,
548 $this->lines[$i]->pa_ht,
549 $this->lines[$i]->label,
550 $this->lines[$i]->special_code,
551 $this->lines[$i]->array_options,
552 $this->lines[$i]->fk_unit,
553 $this->lines[$i]->multicurrency_subprice
554 );
555 } elseif ($current_module == 'propal' && $this instanceof Propal) {
556 $result = $this->updateline(
557 $this->lines[$i]->id,
558 $this->lines[$i]->subprice,
559 $this->lines[$i]->qty,
560 $mode == 'discount' ? $value : $this->lines[$i]->remise_percent,
561 $mode == 'tva' ? $value : $this->lines[$i]->tva_tx,
562 $this->lines[$i]->localtax1_rate,
563 $this->lines[$i]->localtax2_rate,
564 $this->lines[$i]->desc,
565 'HT',
566 $this->lines[$i]->info_bits,
567 $this->lines[$i]->special_code,
568 $this->lines[$i]->fk_parent_line, 0,
569 $this->lines[$i]->fk_fournprice,
570 $this->lines[$i]->pa_ht,
571 $this->lines[$i]->label,
572 $this->lines[$i]->product_type,
573 $this->lines[$i]->date_start,
574 $this->lines[$i]->date_end,
575 $this->lines[$i]->array_options,
576 $this->lines[$i]->fk_unit,
577 $this->lines[$i]->multicurrency_subprice
578 );
579 }
580 if ($result < 0) {
581 return $result;
582 }
583 }
584 }
585 return 1;
586 }
587
597 public function getSubtotalLineAmount($line)
598 {
599 $final_amount = 0;
600 for ($i = $line->rang-1; $i > 0; $i--) {
601 if (is_null($this->lines[$i-1]) || $this->lines[$i-1]->rang >= $line->rang) {
602 continue;
603 }
604 if ($this->lines[$i-1]->special_code == SUBTOTALS_SPECIAL_CODE && $this->lines[$i-1]->qty > 0) {
605 if ($this->lines[$i-1]->qty <= abs($line->qty)) {
606 return price($final_amount);
607 }
608 } else {
609 $final_amount += $this->lines[$i-1]->total_ht;
610 }
611 }
612 return price($final_amount);
613 }
614
624 public function getSubtotalLineMulticurrencyAmount($line)
625 {
626 $final_amount = 0;
627 for ($i = $line->rang-1; $i > 0; $i--) {
628 if (is_null($this->lines[$i-1]) || $this->lines[$i-1]->rang >= $line->rang) {
629 continue;
630 }
631 if ($this->lines[$i-1]->special_code == SUBTOTALS_SPECIAL_CODE && $this->lines[$i-1]->qty>0) {
632 if ($this->lines[$i-1]->qty <= abs($line->qty)) {
633 return price($final_amount);
634 }
635 } else {
636 $final_amount += $this->lines[$i-1]->multicurrency_total_ht;
637 }
638 }
639 return price($final_amount);
640 }
641
648 public function getSubtotalColors($level)
649 {
650 return getDolGlobalString('SUBTOTAL_BACK_COLOR_LEVEL_'.abs($level));
651 }
652
660 public function getPossibleTitles()
661 {
662 $titles = array();
663 foreach ($this->lines as $line) {
664 if ($line->special_code == SUBTOTALS_SPECIAL_CODE && $line->qty > 0) {
665 $titles[$line->desc] = $line->desc;
666 }
667 if ($line->special_code == SUBTOTALS_SPECIAL_CODE && $line->qty < 0) {
668 unset($titles[$line->desc]);
669 }
670 }
671 return $titles;
672 }
673
682 public function getPossibleLevels($langs)
683 {
684 $depth_array = array();
685 $max_depth = getDolGlobalString('SUBTOTAL_'.strtoupper($this->element).'_MAX_DEPTH', 2);
686 for ($i = 0; $i < $max_depth; $i++) {
687 $depth_array[$i + 1] = $langs->trans("SubtotalLevel", $i + 1);
688 }
689 return $depth_array;
690 }
691
699 public function getDisabledShippmentSubtotalLines()
700 {
701 $toDisableLines = array();
702 $toDisable = true;
703 $oldDesc = "";
704 $oldDepth = 0;
705
706 foreach ($this->lines as $titleLine) {
707 if ($titleLine->special_code != SUBTOTALS_SPECIAL_CODE || $titleLine->qty <= 0) {
708 continue;
709 }
710 foreach ($this->lines as $line) {
711 if ($line->id == $titleLine->id) {
712 $oldDesc = $line->desc;
713 $oldDepth = $line->qty;
714 }
715 if ($line->special_code != SUBTOTALS_SPECIAL_CODE && $line->fk_product_type == 0 && !empty($oldDesc) && !empty($oldDepth)) {
716 $toDisable = false;
717 }
718 if ($line->special_code == SUBTOTALS_SPECIAL_CODE && $line->qty == -$oldDepth && $line->desc == $oldDesc) {
719 if ($toDisable) {
720 $toDisableLines = array_merge($toDisableLines, array($titleLine->id, $line->id));
721 }
722 $oldDesc = "";
723 $oldDepth = 0;
724 $toDisable = true;
725 break;
726 }
727 }
728 }
729 return $toDisableLines;
730 }
731}
$id
Support class for third parties, contacts, members, users or resources.
Definition account.php:47
Class to manage customers orders.
Class to manage lines of shipment.
Class to manage invoices.
Class to manage invoice lines of templates.
Class to manage invoice templates.
Class to manage proposals.
dol_html_entity_decode($a, $b, $c='UTF-8', $keepsomeentities=0)
Replace html_entity_decode functions to manage errors.
price($amount, $form=0, $outlangs='', $trunc=1, $rounding=-1, $forcerounding=-1, $currency_code='')
Function to format a value into an amount for visual output Function used into PDF and HTML pages.
getDolGlobalString($key, $default='')
Return a Dolibarr global constant string value.