From c987be86a3c2034c1a21ba8bf927ee6642e2c19c Mon Sep 17 00:00:00 2001 From: w3houde Date: Tue, 9 Jun 2026 14:46:40 +0200 Subject: [PATCH] Fix #34571 tag timespent invoice lines as service in all modes (#38545) Facture::addline()'s sixteenth positional argument is $type, which defaults to 0 (TYPE_PRODUCT). When the addline call passes a non-zero $fk_product, addline overrides $type from the fetched Product object, so the type the caller passed is just a default for the no-product case. htdocs/projet/tasks/time.php had three invoice-line creation paths for the project time-spent flow: - line 567 (one line per user per day) - line 666 (one line per time-spent declaration) - line 764 (one line per task) Only line 764 explicitly passed type=1. The other two relied on the addline default of 0, so when a time entry had no product attached and the chosen "service to use on lines by default" did not match a product row that addline could fetch (for example if the service id got reset between sessions or the product was disabled), the line was stored with product_type=0. The PDF then computed 'Delivery of goods' in the category-of-operations mention even though the invoice was for time spent. The reporter confirmed the 'one line per user' and 'one line per task' modes already worked, which lines up with mode 567 (no product on the time entry) and mode 764 (explicit type=1). Pass Product::TYPE_SERVICE as the type at the two affected call sites, matching the explicit value already used by the per-task mode. The product fetch inside addline still overrides this when a real service product is bound to the line, so the change is a no-op in the happy path and only fixes the leak when no product is resolved. Signed-off-by: Dolicraft Co-authored-by: Dolicraft --- htdocs/projet/tasks/time.php | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/htdocs/projet/tasks/time.php b/htdocs/projet/tasks/time.php index 93ef6997fd4..4f3b1395704 100644 --- a/htdocs/projet/tasks/time.php +++ b/htdocs/projet/tasks/time.php @@ -36,6 +36,7 @@ require '../../main.inc.php'; require_once DOL_DOCUMENT_ROOT . '/projet/class/project.class.php'; require_once DOL_DOCUMENT_ROOT . '/projet/class/task.class.php'; require_once DOL_DOCUMENT_ROOT . '/compta/facture/class/facture.class.php'; +require_once DOL_DOCUMENT_ROOT . '/product/class/product.class.php'; require_once DOL_DOCUMENT_ROOT . '/core/lib/project.lib.php'; require_once DOL_DOCUMENT_ROOT . '/core/lib/date.lib.php'; require_once DOL_DOCUMENT_ROOT . '/core/class/html.formother.class.php'; @@ -563,8 +564,11 @@ if ($action == 'confirm_generateinvoice') { $idprodline = $fk_product; } - // Add lines - $lineid = $tmpinvoice->addline($langs->trans("TimeSpentForInvoice", $username) . ' : ' . $qtyhourtext, $pu_htline, round($qtyhour / $prodDurationHours, 2), $txtvaline, $localtax1line, $localtax2line, ($idprodline > 0 ? $idprodline : 0), $remiseproject); + // Add lines. Pass type=1 (service) explicitly so the invoice line is tagged + // as a service even when no product is bound to the time entry. Otherwise + // the default $type=0 leaks through and the PDF labels the operation as + // "Delivery of goods" instead of "Provision of services" (issue #34571). + $lineid = $tmpinvoice->addline($langs->trans("TimeSpentForInvoice", $username) . ' : ' . $qtyhourtext, $pu_htline, round($qtyhour / $prodDurationHours, 2), $txtvaline, $localtax1line, $localtax2line, ($idprodline > 0 ? $idprodline : 0), $remiseproject, '', '', 0, 0, '', 'HT', 0, Product::TYPE_SERVICE); if ($lineid < 0) { $error++; setEventMessages(null, $tmpinvoice->errors, 'errors'); @@ -663,7 +667,11 @@ if ($action == 'confirm_generateinvoice') { } $idprodline = $value['fk_product']; } - $lineid = $tmpinvoice->addline($value['note'], $pu_htline, round($qtyhour / $prodDurationHours, 2), $txtvaline, $localtax1line, $localtax2line, ($idprodline > 0 ? $idprodline : 0), $remiseproject); + // Pass type=1 (service) explicitly so the invoice line is tagged as a service + // even when no product is bound to the time entry. Otherwise the default + // $type=0 leaks through and the PDF labels the operation as + // "Delivery of goods" instead of "Provision of services" (issue #34571). + $lineid = $tmpinvoice->addline($value['note'], $pu_htline, round($qtyhour / $prodDurationHours, 2), $txtvaline, $localtax1line, $localtax2line, ($idprodline > 0 ? $idprodline : 0), $remiseproject, '', '', 0, 0, '', 'HT', 0, Product::TYPE_SERVICE); if ($lineid < 0) { $error++; setEventMessages(null, $tmpinvoice->errors, 'errors');