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 <contact@dolicraft.com>
Co-authored-by: Dolicraft <contact@dolicraft.com>
This commit is contained in:
w3houde 2026-06-09 14:46:40 +02:00 committed by GitHub
parent 263eb01d48
commit c987be86a3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -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');