Merge branch 'develop' of git@github.com:Dolibarr/dolibarr.git into

develop
This commit is contained in:
Laurent Destailleur
2026-02-08 13:15:16 +01:00
7 changed files with 47 additions and 25 deletions

View File

@@ -26,6 +26,7 @@
* Copyright (C) 2024 Josep Lluís Amador Teruel <joseplluis@lliuretic.cat>
* Copyright (C) 2024 Benoît PASCAL <contact@p-ben.com>
* Copyright (C) 2025 Vincent Maury <vmaury@timgroup.fr>
* Copyright (C) 2026 Benjamin Falière <benjamin@faliere.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -16723,10 +16724,11 @@ function show_actions_messaging($conf, $langs, $db, $filterobj, $objcon = null,
}
$libelle = '';
if (!empty($actionstatic->code) && preg_match('/^TICKET_MSG/', $actionstatic->code)) {
if (!empty($actionstatic->code) && preg_match('/^TICKET_MSG_PRIVATE/', $actionstatic->code)) {
$out .= $langs->trans('TicketNewMessage').' <em>('.$langs->trans('Private').')</em>';
} elseif (!empty($actionstatic->code) && preg_match('/^TICKET_MSG/', $actionstatic->code)) {
$out .= $langs->trans('TicketNewMessage');
} elseif (!empty($actionstatic->code) && preg_match('/^TICKET_MSG_PRIVATE/', $actionstatic->code)) {
$out .= $langs->trans('TicketNewMessage') . ' <em>(' . $langs->trans('Private') . ')</em>';
} elseif (isset($histo[$key]['type'])) {
if ($histo[$key]['type'] == 'action') {
$transcode = $langs->transnoentitiesnoconv("Action" . $histo[$key]['acode']);

View File

@@ -2658,7 +2658,7 @@ function getModuleDirForApiClass($moduleobject)
$moduledirforclass = 'api';
} elseif (in_array($moduleobject, ['contact', 'contacts', 'customer', 'thirdparty', 'thirdparties'])) {
$moduledirforclass = 'societe';
} elseif ($moduleobject == 'propale' || $moduleobject == 'proposals') {
} elseif ($moduleobject == 'propal' || $moduleobject == 'propale' || $moduleobject == 'proposals') {
$moduledirforclass = 'comm/propal';
} elseif ($moduleobject == 'agenda' || $moduleobject == 'agendaevents') {
$moduledirforclass = 'comm/action';

View File

@@ -737,6 +737,11 @@ class pdf_espadon extends ModelePdfExpedition
$nexY = max($pdf->GetY(), $nexY);
}
if ($this->getColumnStatus('totalexcltax') && $object->lines[$i]->special_code != SUBTOTALS_SPECIAL_CODE) {
$this->printStdColumnContent($pdf, $curY, 'totalexcltax', price($object->lines[$i]->total_ht, 0, $outputlangs));
$nexY = max($pdf->GetY(), $nexY);
}
// Extrafields
if (!empty($object->lines[$i]->array_options) && $object->lines[$i]->special_code != SUBTOTALS_SPECIAL_CODE) {
foreach ($object->lines[$i]->array_options as $extrafieldColKey => $extrafieldValue) {
@@ -954,8 +959,8 @@ class pdf_espadon extends ModelePdfExpedition
$this->printStdColumnContent($pdf, $tab2_top, 'qty_shipped', $totalToShip);
}
if ($this->getColumnStatus('subprice')) {
$this->printStdColumnContent($pdf, $tab2_top, 'subprice', price($object->total_ht, 0, $outputlangs));
if ($this->getColumnStatus('totalexcltax')) {
$this->printStdColumnContent($pdf, $tab2_top, 'totalexcltax', price($object->total_ht, 0, $outputlangs));
}
$pdf->SetTextColor(0, 0, 0);

View File

@@ -1959,7 +1959,7 @@ if ($action == 'create' && $usercancreate) {
if ($res < 0) {
dol_print_error($db, $product->error, $product->errors);
}
if (getDolGlobalInt('PRODUIT_SOUSPRODUITS')) {
if (getDolGlobalInt('PRODUIT_SOUSPRODUITS') && !getDolGlobalInt('PRODUIT_SOUSPRODUITS_ALSO_ENABLE_PARENT_STOCK_MOVE')) {
$productChildrenNb = $product->hasFatherOrChild(1);
}
if ($productChildrenNb > 0) {
@@ -2168,13 +2168,13 @@ if ($action == 'create' && $usercancreate) {
$subj = 0;
// Define nb of lines suggested for this order line
$nbofsuggested = 0;
if (is_object($product->stock_warehouse[$warehouse_id]) && count($product->stock_warehouse[$warehouse_id]->detail_batch)) {
if (is_object($product->stock_warehouse[$warehouse_id]) && !empty($product->stock_warehouse[$warehouse_id]->detail_batch)) {
foreach ($product->stock_warehouse[$warehouse_id]->detail_batch as $dbatch) {
$nbofsuggested++;
}
}
print '<input name="idl' . $indiceAsked . '" type="hidden" value="' . $line->id . '">';
if (is_object($product->stock_warehouse[$warehouse_id]) && count($product->stock_warehouse[$warehouse_id]->detail_batch)) {
print '<input name="idl'.$indiceAsked.'" type="hidden" value="'.$line->id.'">';
if (is_object($product->stock_warehouse[$warehouse_id]) && !empty($product->stock_warehouse[$warehouse_id]->detail_batch)) {
foreach ($product->stock_warehouse[$warehouse_id]->detail_batch as $dbatch) { // $dbatch is instance of Productbatch
//var_dump($dbatch);
$batchStock = +$dbatch->qty; // To get a numeric
@@ -2414,7 +2414,7 @@ if ($action == 'create' && $usercancreate) {
// Define nb of lines suggested for this order line
$nbofsuggested = 0;
foreach ($product->stock_warehouse as $warehouse_id => $stock_warehouse) {
if (($stock_warehouse->real > 0 || !getDolGlobalInt('STOCK_DISALLOW_NEGATIVE_TRANSFER')) && (count($stock_warehouse->detail_batch))) {
if (($stock_warehouse->real > 0 || !getDolGlobalInt('STOCK_DISALLOW_NEGATIVE_TRANSFER')) && (!empty($stock_warehouse->detail_batch))) {
$nbofsuggested += count($stock_warehouse->detail_batch);
}
}
@@ -2427,7 +2427,7 @@ if ($action == 'create' && $usercancreate) {
}
$tmpwarehouseObject->fetch($warehouse_id);
if (($stock_warehouse->real > 0 || !getDolGlobalInt('STOCK_DISALLOW_NEGATIVE_TRANSFER')) && (count($stock_warehouse->detail_batch))) {
if (($stock_warehouse->real > 0 || !getDolGlobalInt('STOCK_DISALLOW_NEGATIVE_TRANSFER')) && (!empty($stock_warehouse->detail_batch))) {
foreach ($stock_warehouse->detail_batch as $dbatch) {
$batchStock = +$dbatch->qty; // To get a numeric
if (isset($alreadyQtyBatchSetted[$line->fk_product][$dbatch->batch][intval($warehouse_id)])) {

View File

@@ -569,9 +569,9 @@ class Expedition extends CommonObject
if (empty($line->product_type) || getDolGlobalString('STOCK_SUPPORTS_SERVICES') || getDolGlobalString('SHIPMENT_SUPPORTS_SERVICES')) {
$line_id = 0;
if (!isset($kits_id_cached[$line->fk_product])) {
if (!isset($line->detail_batch) || isset($kits_list[$line->fk_product])) { // no batch management or is kit
if (!isset($line->detail_batch) || (isset($kits_list[$line->fk_product]) && !getDolGlobalInt('PRODUIT_SOUSPRODUITS_ALSO_ENABLE_PARENT_STOCK_MOVE'))) { // no batch management or is kit
$qty = isset($kits_list[$line->fk_product]) ? $kits_list[$line->fk_product]['total_qty'] : $line->qty;
$warehouse_id = isset($kits_list[$line->fk_product]) ? 0 : $line->entrepot_id;
$warehouse_id = (isset($kits_list[$line->fk_product]) && !getDolGlobalInt('PRODUIT_SOUSPRODUITS_ALSO_ENABLE_PARENT_STOCK_MOVE')) ? 0 : $line->entrepot_id;
$line_id = $this->create_line($warehouse_id, $line->origin_line_id, $qty, $line->rang, $line->array_options, 0, $line->fk_product);
if ($line_id <= 0) {
$error++;
@@ -589,7 +589,7 @@ class Expedition extends CommonObject
}
// virtual products
if (isset($kits_list[$line->fk_product])) {
if (isset($kits_list[$line->fk_product]) && !getDolGlobalInt('PRODUIT_SOUSPRODUITS_ALSO_ENABLE_PARENT_STOCK_MOVE')) {
$prods_arbo = $kits_list[$line->fk_product]['arbo'];
$total_qty = $kits_list[$line->fk_product]['total_qty'];
@@ -650,7 +650,7 @@ class Expedition extends CommonObject
// create line for a child of virtual product
if (!isset($sub_kits_id_cached[$product_child_id]) || $warehouse_id > 0) {
$line_id = $this->create_line($warehouse_id, 0, $product_child_qty, $line->rang, $line->array_options, $parent_line_id, $product_child_id);
$line_id = $this->create_line($warehouse_id, ($parent_line_id ? 0 : $line->origin_line_id), $product_child_qty, $line->rang, $line->array_options, $parent_line_id, $product_child_id);
if ($line_id <= 0) {
$error++;
dol_syslog(__METHOD__ . ' : ' . $this->errorsToString(), LOG_ERR);
@@ -1749,7 +1749,7 @@ class Expedition extends CommonObject
$obj = $this->db->fetch_object($resql);
$line_id = (int) $obj->expeditiondet_id;
if ($can_update_stock && empty($obj->iskit) && !empty($obj->incdec)) {
if ($can_update_stock && (empty($obj->iskit) || getDolGlobalInt('PRODUIT_SOUSPRODUITS_ALSO_ENABLE_PARENT_STOCK_MOVE')) && !empty($obj->incdec)) {
$mouvS = new MouvementStock($this->db);
// we do not log origin because it will be deleted
$mouvS->origin = '';
@@ -1954,7 +1954,7 @@ class Expedition extends CommonObject
$obj = $this->db->fetch_object($resql);
$line_id = (int) $obj->expeditiondet_id;
if ($can_update_stock && empty($obj->iskit) && !empty($obj->incdec)) {
if ($can_update_stock && (empty($obj->iskit) || getDolGlobalInt('PRODUIT_SOUSPRODUITS_ALSO_ENABLE_PARENT_STOCK_MOVE')) && !empty($obj->incdec)) {
$mouvS = new MouvementStock($this->db);
// we do not log origin because it will be deleted
$mouvS->origin = '';

View File

@@ -913,7 +913,9 @@ if ($object->id > 0 || !empty($object->ref)) {
$resql_child = $db->query($sql_child);
if ($resql_child) {
if ($child_obj = $db->fetch_object($resql_child)) {
$line_obj->iskit = (int) $child_obj->iskit;
if (!getDolGlobalInt('PRODUIT_SOUSPRODUITS_ALSO_ENABLE_PARENT_STOCK_MOVE')) {
$line_obj->iskit = (int) $child_obj->iskit;
}
if ($can_manage_stock) {
$line_obj->incdec = (int) $child_obj->incdec; // reset value to 0 or 1 if stock can be managed
}

View File

@@ -99,7 +99,7 @@ class Odf
* Class constructor
*
* @param string $filename The name of the odt file
* @param array $config Array of config data
* @param array $config Array of config data
* @throws OdfException
*/
public function __construct($filename, $config = array())
@@ -957,19 +957,32 @@ IMG;
// $result = $utils->executeCLI($command, $outputfile); and replace test on $execmethod.
// $retval will be $result['result']
// $errorstring will be $result['output']
// Protect parentheses from being double-escaped by escapeshellcmd().
// The $command is already built with escapeshellarg() for all arguments,
// but escapeshellcmd() escapes parentheses inside quoted strings, breaking
// filenames like "(PROV35)_invoice.odt" for draft invoices.
// Security: reject if placeholder strings already exist to prevent injection.
if (strpos($command, '__PARENTHESIS_OPEN__') !== false || strpos($command, '__PARENTHESIS_CLOSE__') !== false) {
dol_syslog(get_class($this).'::exportAsAttachedPDF Invalid characters in command path: '.$command, LOG_WARNING);
throw new OdfException('Invalid characters in command path');
}
$commandprotected = str_replace(array('(', ')'), array('__PARENTHESIS_OPEN__', '__PARENTHESIS_CLOSE__'), $command);
$commandescaped = escapeshellcmd($commandprotected);
$commandescapedtoexec = str_replace(array('__PARENTHESIS_OPEN__', '__PARENTHESIS_CLOSE__'), array('(', ')'), $commandescaped);
$retval=0; $output_arr=array();
if ($execmethod == 1) {
exec(escapeshellcmd($command), $output_arr, $retval);
}
if ($execmethod == 2) {
exec($commandescapedtoexec, $output_arr, $retval);
} elseif ($execmethod == 2) {
$outputfile = DOL_DATA_ROOT.'/odt2pdf.log';
$handle = fopen($outputfile, 'w');
if ($handle) {
dol_syslog(get_class($this)."Run command ".$command, LOG_DEBUG);
dol_syslog(get_class($this)."escapeshellcmd(command) = ".escapeshellcmd($command), LOG_DEBUG);
dol_syslog(get_class($this)."escapeshellcmd(command) = ".$commandescapedtoexec, LOG_DEBUG);
fwrite($handle, $command."\n");
$handlein = popen(escapeshellcmd($command), 'r');
$handlein = popen($commandescapedtoexec, 'r');
while (!feof($handlein)) {
$read = fgets($handlein);
fwrite($handle, $read);