Compare commits

...

11 Commits

Author SHA1 Message Date
ThomasNgr-OpenDSI
00d1bbce9c FIX: on event create/edit card, when adding a user, the reminder info was lost. (#36857)
Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>
2026-01-13 11:39:46 +01:00
Joachim Kueter
7f407a1183 FIX #36866 Project/Time Spent: changing a lined of consumed time does not store changed task assignment (#36867)
On the tab Project/Time Spent there is a list of consumed time, spent on the tasks of the project. Each line is showing a pencil icon to allow a modification of the data.

In edit mode, the assigned task can get changed to a different task of the project. Upon saving the edits, this change is getting ignored, the old task is still assigned:

We need to update the fk_element referencing the task, but we also need to adjust the values for duration_effective of both affected tasks (old and new).
2026-01-13 11:39:15 +01:00
atm-vincent-p
de96c57acf FIX contact type translation (#36863) 2026-01-13 11:35:32 +01:00
Laurent Destailleur
fcef8ec922 Merge branch '22.0' of git@github.com:Dolibarr/dolibarr.git into 22.0 2026-01-12 16:12:29 +01:00
Laurent Destailleur
4c1cde76d5 Fix warning 2026-01-12 16:12:18 +01:00
Braito
362113e653 Fix/emailcollector isanswer dolibarr references 22.0 (#36859)
* fix(emailcollector): accept replies via Dolibarr References

* style(emailcollector): fix indentation

---------

Co-authored-by: braito4 <braito4@users.noreply.github.com>
2026-01-12 15:56:50 +01:00
Alexandre SPANGARO
6b19b592ad FIX Accountancy - Add validation for overlapping fiscal year dates (#36836)
* FIX Accountancy - Add validation for overlapping fiscal year dates

* Optimize

* CI
2026-01-11 13:02:31 +01:00
Laurent Destailleur
04787d8cb0 FIX icon of mastodon social network 2026-01-11 12:57:26 +01:00
Laurent Destailleur
39ed2ece8f Backport fix for #36850 2026-01-11 12:48:58 +01:00
Expresion
6d6dfb3a5e Fix numeric input parsing to support comma as decimal separator (#36845)
* Fix numeric input parsing to support comma as decimal separator

GETPOSTINT()  only handles integer values and fails when input uses a comma as decimal

* Update dispatch.php

* Change GETPOST to GETPOSTFLOAT for price input

---------

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>
2026-01-10 18:25:06 +01:00
Laurent Destailleur
608f89e882 Fix CI 2026-01-10 18:12:56 +01:00
13 changed files with 182 additions and 79 deletions

View File

@@ -128,16 +128,19 @@ if ($action == 'confirm_delete' && $confirm == "yes" && $permissiontoadd) {
$db->begin();
$id = $object->create($user);
if ($id > 0) {
$db->commit();
header("Location: ".$_SERVER["PHP_SELF"]."?id=".$id);
exit();
header("Location: " . $_SERVER["PHP_SELF"] . "?id=" . $id);
exit;
} else {
$db->rollback();
setEventMessages($object->error, $object->errors, 'errors');
// Handle overlap error
if ($id == -5 && !empty($object->errors[0])) {
setEventMessages($langs->trans($object->error, $object->errors[0]), null, 'errors');
} else {
setEventMessages($object->error, $object->errors, 'errors');
}
$action = 'create';
}
} else {
@@ -145,7 +148,7 @@ if ($action == 'confirm_delete' && $confirm == "yes" && $permissiontoadd) {
}
} else {
header("Location: ./fiscalyear.php");
exit();
exit;
}
} elseif ($action == 'update' && $permissiontoadd) {
// Update record
@@ -158,16 +161,20 @@ if ($action == 'confirm_delete' && $confirm == "yes" && $permissiontoadd) {
$object->status = GETPOSTINT('status');
$result = $object->update($user);
if ($result > 0) {
header("Location: ".$_SERVER["PHP_SELF"]."?id=".$id);
exit();
header("Location: " . $_SERVER["PHP_SELF"] . "?id=" . $id);
exit;
} else {
setEventMessages($object->error, $object->errors, 'errors');
// Handle overlap error
if ($result == -5 && !empty($object->errors[0])) {
setEventMessages($langs->trans($object->error, $object->errors[0]), null, 'errors');
} else {
setEventMessages($object->error, $object->errors, 'errors');
}
}
} else {
header("Location: ".$_SERVER["PHP_SELF"]."?id=".$id);
exit();
header("Location: " . $_SERVER["PHP_SELF"] . "?id=" . $id);
exit;
}
} elseif ($action == 'reopen' && $permissiontoadd && getDolGlobalString('ACCOUNTING_CAN_REOPEN_CLOSED_PERIOD')) {
$result = $object->fetch($id);

View File

@@ -1836,16 +1836,16 @@ if ($action == 'create') {
//checkbox create reminder
print '<hr>';
print '<br>';
print '<label for="addreminder">'.img_picto('', 'bell', 'class="pictofixedwidth"').$langs->trans("AddReminder").'</label> <input type="checkbox" id="addreminder" name="addreminder"><br><br>';
print '<label for="addreminder">'.img_picto('', 'bell', 'class="pictofixedwidth"').$langs->trans("AddReminder").'</label> <input type="checkbox" id="addreminder" name="addreminder"'.(empty(GETPOST('addreminder')) ? '' : 'checked').'><br><br>';
print '<div class="reminderparameters" style="display: none;">';
print '<div class="reminderparameters" '.(empty(GETPOST('addreminder')) ? 'style="display: none;' : '').' ">';
print '<table class="border centpercent">';
//Reminder
print '<tr><td class="titlefieldcreate nowrap">'.$langs->trans("ReminderTime").'</td><td colspan="3">';
print '<input class="width50" type="number" name="offsetvalue" value="'.(GETPOSTISSET('offsetvalue') ? GETPOSTINT('offsetvalue') : getDolGlobalInt('AGENDA_REMINDER_DEFAULT_OFFSET', 30)).'"> ';
print $form->selectTypeDuration('offsetunit', 'i', array('y', 'm'));
print $form->selectTypeDuration('offsetunit', (empty($offsetunit) ? 'i' : $offsetunit), array('y', 'm'));
print '</td></tr>';
//Reminder Type
@@ -1856,7 +1856,7 @@ if ($action == 'create') {
//Mail Model
if (getDolGlobalString('AGENDA_REMINDER_EMAIL')) {
print '<tr><td class="titlefieldcreate nowrap">'.$langs->trans("EMailTemplates").'</td><td colspan="3">';
print $form->selectModelMail('actioncommsend', 'actioncomm_send', 1, 1);
print $form->selectModelMail('actioncommsend', 'actioncomm_send', 1, 1, (empty($modelmail) ? 0 : $modelmail));
print '</td></tr>';
}
@@ -1900,9 +1900,9 @@ if ($action == 'create') {
print "\n".'<script type="text/javascript">';
print '$(document).ready(function () {
$("#addreminder").click(function(){
console.log("Click on addreminder");
if (this.checked) {
function toggle_reminder_part(evt) {
console.log("Toggle reminder part");
if ($("#addreminder").is(":checked")) {
$(".reminderparameters").show();
} else {
$(".reminderparameters").hide();
@@ -1914,6 +1914,9 @@ if ($action == 'create') {
selectremindertype();
});
toggle_reminder_part();
$("#addreminder").click(toggle_reminder_part);
$("#selectremindertype").change(function(){
selectremindertype();
});

View File

@@ -1706,7 +1706,7 @@ abstract class CommonObject
$obj = $this->db->fetch_object($resql);
$transkey = "TypeContact_".$this->element."_".$source."_".$obj->code;
$libelle_type = ($langs->trans($transkey) != $transkey ? $langs->trans($transkey) : $obj->type_label);
$libelle_type = ($langs->trans($transkey) != $transkey ? $langs->trans($transkey) : $langs->trans($obj->type_label));
if (empty($option)) {
$tab[$obj->rowid] = $libelle_type;
} elseif ($option == 1) {

View File

@@ -1,8 +1,8 @@
<?php
/* Copyright (C) 2014-2025 Alexandre Spangaro <alexandre@inovea-conseil.com>
* Copyright (C) 2020 OScss-Shop <support@oscss-shop.fr>
* Copyright (C) 2023-2024 Frédéric France <frederic.france@free.fr>
* Copyright (C) 2024-2025 MDW <mdeweerd@users.noreply.github.com>
/* Copyright (C) 2014-2026 Alexandre Spangaro <alexandre@inovea-conseil.com>
* Copyright (C) 2020 OScss-Shop <support@oscss-shop.fr>
* Copyright (C) 2023-2024 Frédéric France <frederic.france@free.fr>
* Copyright (C) 2024-2025 MDW <mdeweerd@users.noreply.github.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
@@ -135,6 +135,12 @@ class Fiscalyear extends CommonObject
$now = dol_now();
// Check for date overlaps with existing fiscal years
$checkresult = $this->checkOverlap();
if ($checkresult < 0) {
return -5; // Overlap error detected
}
$this->db->begin();
$sql = "INSERT INTO ".$this->db->prefix()."accounting_fiscalyear (";
@@ -159,16 +165,8 @@ class Fiscalyear extends CommonObject
$result = $this->db->query($sql);
if ($result) {
$this->id = $this->db->last_insert_id($this->db->prefix()."accounting_fiscalyear");
$result = $this->update($user);
if ($result > 0) {
$this->db->commit();
return $this->id;
} else {
$this->error = $this->db->lasterror();
$this->db->rollback();
return $result;
}
$this->db->commit();
return $this->id;
} else {
$this->error = $this->db->lasterror()." sql=".$sql;
$this->db->rollback();
@@ -190,6 +188,12 @@ class Fiscalyear extends CommonObject
return -1;
}
// Check for date overlaps with existing fiscal years
$checkresult = $this->checkOverlap();
if ($checkresult < 0) {
return -5; // Overlap error detected
}
$this->db->begin();
$sql = "UPDATE ".$this->db->prefix()."accounting_fiscalyear";
@@ -269,6 +273,47 @@ class Fiscalyear extends CommonObject
}
}
/**
* Check if fiscal year dates overlap with existing fiscal years
*
* @return int Return integer <0 if overlap detected, >0 if OK
*/
public function checkOverlap()
{
global $conf;
// Get entity value
$entity = (!empty($this->entity) ? $this->entity : $conf->entity);
// Query to checks if any existing fiscal year overlaps with the current date range
$sql = "SELECT label";
$sql .= " FROM " . $this->db->prefix() . "accounting_fiscalyear";
$sql .= " WHERE entity = " . ((int) $entity);
$sql .= " AND date_start <= '" . $this->db->idate($this->date_end) . "'";
$sql .= " AND date_end >= '" . $this->db->idate($this->date_start) . "'";
// Exclude current fiscal year when updating
if (!empty($this->id)) {
$sql .= " AND rowid != " . ((int) $this->id);
}
dol_syslog(get_class($this) . "::checkOverlap", LOG_DEBUG);
$result = $this->db->query($sql);
if ($result) {
if ($this->db->num_rows($result) > 0) {
$obj = $this->db->fetch_object($result);
$this->error = 'ErrorFiscalYearOverlapWithFiscalYear';
$this->errors[] = $obj->label;
return -1;
}
return 1; // No overlap found
} else {
$this->error = $this->db->lasterror();
return -2;
}
}
/**
* getTooltipContentArray
* @param array<string,mixed> $params params to construct tooltip data

View File

@@ -12392,10 +12392,10 @@ class Form
* @param string $modelType Model type
* @param int<0,1> $default 1=Show also Default mail template
* @param int<0,1> $addjscombo Add js combobox
* @param string $selected Selected model mail
* @param int|string $selected Selected model mail
* @return string HTML select string
*/
public function selectModelMail($prefix, $modelType = '', $default = 0, $addjscombo = 0, $selected = '')
public function selectModelMail($prefix, $modelType = '', $default = 0, $addjscombo = 0, $selected = 0)
{
global $langs, $user;
@@ -12412,7 +12412,7 @@ class Form
}
if ($result > 0) {
foreach ($formmail->lines_model as $model) {
$TModels[$model->id] = $model->label;
$TModels[(int) $model->id] = $model->label;
}
}
@@ -12420,7 +12420,7 @@ class Form
foreach ($TModels as $id_model => $label_model) {
$retstring .= '<option value="' . $id_model . '"';
if (!empty($selected) && $selected == $id_model) {
if (!empty($selected) && ((int) $selected) == $id_model) {
$retstring .= "selected";
}
$retstring .= ">" . $label_model . "</option>";

View File

@@ -4252,13 +4252,13 @@ function getArrayOfSocialNetworks()
* Show social network link
*
* @param string $value Social network ID to show (only skype, without 'Name of recipient' before)
* @param int $cid Id of contact if known
* @param int $contactid Id of contact if known
* @param int $socid Id of third party if known
* @param string $type 'skype','facebook',...
* @param array<string,array{rowid:int,label:string,url:string,icon:string,active:int}> $dictsocialnetworks List of socialnetworks available
* @return string HTML Link
*/
function dol_print_socialnetworks($value, $cid, $socid, $type, $dictsocialnetworks = array())
function dol_print_socialnetworks($value, $contactid, $socid, $type, $dictsocialnetworks = array())
{
global $hookmanager, $langs, $user;
@@ -4271,61 +4271,56 @@ function dol_print_socialnetworks($value, $cid, $socid, $type, $dictsocialnetwor
if (!empty($type)) {
$htmllink = '<div class="divsocialnetwork inline-block valignmiddle">';
// Use dictionary definition for picto $dictsocialnetworks[$type]['icon']
$htmllink .= '<span class="fab pictofixedwidth '.($dictsocialnetworks[$type]['icon'] ? $dictsocialnetworks[$type]['icon'] : 'fa-link').'"></span>';
$htmllink .= '<span class="fab pictofixedwidth ' . ($dictsocialnetworks[$type]['icon'] ? $dictsocialnetworks[$type]['icon'] : 'fa-link') . '"></span>';
if ($type == 'skype') {
$htmllink .= dol_escape_htmltag($value);
$htmllink .= '&nbsp; <a href="skype:';
$htmllink .= dol_string_nospecial($value, '_', '', array('@'));
$htmllink .= '?call" alt="'.$langs->trans("Call").'&nbsp;'.$value.'" title="'.dol_escape_htmltag($langs->trans("Call").' '.$value).'">';
$htmllink .= '<img src="'.DOL_URL_ROOT.'/theme/common/skype_callbutton.png" border="0">';
$htmllink .= '?call" alt="' . $langs->trans("Call") . '&nbsp;' . $value . '" title="' . dol_escape_htmltag($langs->trans("Call") . ' ' . $value) . '">';
$htmllink .= '<img src="' . DOL_URL_ROOT . '/theme/common/skype_callbutton.png" border="0">';
$htmllink .= '</a><a href="skype:';
$htmllink .= dol_string_nospecial($value, '_', '', array('@'));
$htmllink .= '?chat" alt="'.$langs->trans("Chat").'&nbsp;'.$value.'" title="'.dol_escape_htmltag($langs->trans("Chat").' '.$value).'">';
$htmllink .= '<img class="paddingleft" src="'.DOL_URL_ROOT.'/theme/common/skype_chatbutton.png" border="0">';
$htmllink .= '?chat" alt="' . $langs->trans("Chat") . '&nbsp;' . $value . '" title="' . dol_escape_htmltag($langs->trans("Chat") . ' ' . $value) . '">';
$htmllink .= '<img class="paddingleft" src="' . DOL_URL_ROOT . '/theme/common/skype_chatbutton.png" border="0">';
$htmllink .= '</a>';
if (($cid || $socid) && isModEnabled('agenda') && $user->hasRight('agenda', 'myactions', 'create')) {
if (($contactid || $socid) && isModEnabled('agenda') && $user->hasRight('agenda', 'myactions', 'create')) {
$addlink = 'AC_SKYPE';
$link = '';
if (getDolGlobalString('AGENDA_ADDACTIONFORSKYPE')) {
$link = '<a href="'.DOL_URL_ROOT.'/comm/action/card.php?action=create&amp;backtopage=1&amp;actioncode='.$addlink.'&amp;contactid='.$cid.'&amp;socid='.$socid.'">'.img_object($langs->trans("AddAction"), "calendar").'</a>';
$link = '<a href="' . DOL_URL_ROOT . '/comm/action/card.php?action=create&backtopage=1&actioncode=' . $addlink . '&contactid=' . $contactid . '&socid=' . $socid . '">' . img_object($langs->trans("AddAction"), "calendar") . '</a>';
}
$htmllink .= ($link ? ' '.$link : '');
$htmllink .= ($link ? ' ' . $link : '');
}
} else {
$networkconstname = 'MAIN_INFO_SOCIETE_'.strtoupper($type).'_URL';
if (getDolGlobalString($networkconstname)) {
$link = str_replace('{socialid}', $value, getDolGlobalString($networkconstname));
$valuetoshow = $value;
if (preg_match('/^https?:\/\//i', $link)) {
$valuetoshow = preg_replace('/https:\/\/www\.linkedin\./', 'linkedin.', $valuetoshow);
//$valuetoshow = preg_replace('/www\.twitter\./', 'twitter.', $valuetoshow);
$htmllink .= '<a href="'.dol_sanitizeUrl($link, 0).'" target="_blank" rel="noopener noreferrer">'.dol_escape_htmltag($valuetoshow).'</a>';
} elseif ($link) {
$htmllink .= '<a href="'.dol_sanitizeUrl($link, 1).'" target="_blank" rel="noopener noreferrer">'.dol_escape_htmltag($valuetoshow).'</a>';
}
} elseif (!empty($dictsocialnetworks[$type]['url'])) {
if (!empty($dictsocialnetworks[$type]['url'])) {
$tmpvirginurl = preg_replace('/\/?{socialid}/', '', $dictsocialnetworks[$type]['url']);
if ($tmpvirginurl) {
$value = preg_replace('/^www\.'.preg_quote($tmpvirginurl, '/').'\/?/', '', $value);
$value = preg_replace('/^'.preg_quote($tmpvirginurl, '/').'\/?/', '', $value);
$value = preg_replace('/^www\.' . preg_quote($tmpvirginurl, '/') . '\/?/', '', $value);
$value = preg_replace('/^' . preg_quote($tmpvirginurl, '/') . '\/?/', '', $value);
$tmpvirginurl3 = preg_replace('/^https:\/\//i', 'https://www.', $tmpvirginurl);
if ($tmpvirginurl3) {
$value = preg_replace('/^www\.'.preg_quote($tmpvirginurl3, '/').'\/?/', '', $value);
$value = preg_replace('/^'.preg_quote($tmpvirginurl3, '/').'\/?/', '', $value);
$value = preg_replace('/^www\.' . preg_quote($tmpvirginurl3, '/') . '\/?/', '', $value);
$value = preg_replace('/^' . preg_quote($tmpvirginurl3, '/') . '\/?/', '', $value);
}
$tmpvirginurl2 = preg_replace('/^https?:\/\//i', '', $tmpvirginurl);
if ($tmpvirginurl2) {
$value = preg_replace('/^www\.'.preg_quote($tmpvirginurl2, '/').'\/?/', '', $value);
$value = preg_replace('/^'.preg_quote($tmpvirginurl2, '/').'\/?/', '', $value);
$value = preg_replace('/^www\.' . preg_quote($tmpvirginurl2, '/') . '\/?/', '', $value);
$value = preg_replace('/^' . preg_quote($tmpvirginurl2, '/') . '\/?/', '', $value);
}
}
$link = str_replace('{socialid}', $value, $dictsocialnetworks[$type]['url']);
if (preg_match('/^https?:\/\//i', $link)) {
$htmllink .= '<a href="'.dol_sanitizeUrl($link, 0).'" target="_blank" rel="noopener noreferrer">'.dol_escape_htmltag($value).'</a>';
if (preg_match('/^https?:\/\//i', $value)) {
$link = $value;
} else {
$htmllink .= '<a href="'.dol_sanitizeUrl($link, 1).'" target="_blank" rel="noopener noreferrer">'.dol_escape_htmltag($value).'</a>';
$link = str_replace('{socialid}', $value, $dictsocialnetworks[$type]['url']);
}
$valuetoshow = $value;
$valuetoshow = preg_replace('/https:\/\/www\.(twitter|x|linkedin)\.com\/?/', '', $valuetoshow);
if (preg_match('/^https?:\/\//i', $link)) {
$htmllink .= '<a href="' . dol_sanitizeUrl($link, 0) . '" target="_blank" rel="noopener noreferrer">' . dol_escape_htmltag($valuetoshow) . '</a>';
} else {
$htmllink .= '<a href="' . dol_sanitizeUrl($link, 1) . '" target="_blank" rel="noopener noreferrer">' . dol_escape_htmltag($valuetoshow) . '</a>';
}
} else {
$htmllink .= dol_escape_htmltag($value);
@@ -4340,7 +4335,7 @@ function dol_print_socialnetworks($value, $cid, $socid, $type, $dictsocialnetwor
if ($hookmanager) {
$parameters = array(
'value' => $value,
'cid' => $cid,
'cid' => $contactid,
'socid' => $socid,
'type' => $type,
'dictsocialnetworks' => $dictsocialnetworks,

View File

@@ -1873,9 +1873,28 @@ class EmailCollector extends CommonObject
}
if ($searchfilterisanswer > 0) {
if (empty($headers['In-Reply-To'])) {
$referencesforanswer = '';
if (!empty($headers['References'])) {
$referencesforanswer .= $headers['References'].' ';
}
if (!empty($headers['In-Reply-To'])) {
$referencesforanswer .= $headers['In-Reply-To'];
}
$hasdolibarrreference = 0;
if (!empty($referencesforanswer)) {
if (preg_match('/dolibarr-([a-z]+)([0-9]+)@'.preg_quote($host, '/').'/', $referencesforanswer)) {
$hasdolibarrreference = 1;
} elseif (getDolGlobalString('EMAIL_ALTERNATIVE_HOST_SIGNATURE')) {
if (preg_match('/dolibarr-([a-z]+)([0-9]+)@'.preg_quote(getDolGlobalString('EMAIL_ALTERNATIVE_HOST_SIGNATURE'), '/').'/', $referencesforanswer)) {
$hasdolibarrreference = 1;
}
}
}
if (empty($headers['In-Reply-To']) && empty($hasdolibarrreference)) {
$nbemailprocessed++;
dol_syslog(" Discarded - Email is not an answer (no In-Reply-To header)");
dol_syslog(" Discarded - Email is not an answer (no In-Reply-To header and no Dolibarr reference)");
continue; // Exclude email
}
$isanswer = 0;
@@ -1889,6 +1908,9 @@ class EmailCollector extends CommonObject
$isanswer = 1;
}
}
if ($hasdolibarrreference) {
$isanswer = 1;
}
//if ($headers['In-Reply-To'] != $headers['Message-ID'] && empty($headers['References'])) $isanswer = 1; // If in-reply-to differs of message-id, this is a reply
//if ($headers['In-Reply-To'] != $headers['Message-ID'] && !empty($headers['References']) && strpos($headers['References'], $headers['Message-ID']) !== false) $isanswer = 1;

View File

@@ -1194,7 +1194,7 @@ class FactureFournisseurRec extends CommonInvoice
$this->multicurrency_total_tva = empty($this->multicurrency_total_tva) ? 0 : $this->multicurrency_total_tva;
$this->multicurrency_total_ttc = empty($this->multicurrency_total_ttc) ? 0 : $this->multicurrency_total_ttc;
$pu = $price_base_type == 'HT' ? $pu_ht : $pu_ttc;
$pu = ($price_base_type == 'HT' ? $pu_ht : $pu_ttc);
// Calculate total with, without tax and tax from qty, pu, remise_percent and txtva
@@ -1211,7 +1211,7 @@ class FactureFournisseurRec extends CommonInvoice
$txtva = preg_replace('/\s*\(.*\)/', '', $txtva); // Remove code into vatrate.
}
$tabprice = calcul_price_total($qty, $pu, $remise_percent, $txtva, $txlocaltax1, $txlocaltax2, 0, $price_base_type, $info_bits, $type, $mysoc, $localtaxes_type, 100, $this->multicurrency_tx, $pu_ht_devise);
$tabprice = calcul_price_total((float) $qty, (float) $pu, $remise_percent, $txtva, $txlocaltax1, $txlocaltax2, 0, $price_base_type, $info_bits, $type, $mysoc, $localtaxes_type, 100, $this->multicurrency_tx, (float) $pu_ht_devise);
$total_ht = $tabprice[0];
$total_tva = $tabprice[1];

View File

@@ -286,7 +286,7 @@ if ($action == 'dispatch' && $permissiontoreceive) {
if (!$error && getDolGlobalString('SUPPLIER_ORDER_CAN_UPDATE_BUYINGPRICE_DURING_RECEIPT')) {
if (!isModEnabled("multicurrency") && empty($conf->dynamicprices->enabled)) {
$dto = price2num(GETPOSTINT("dto_".$reg[1].'_'.$reg[2]), '');
$dto = price2num(GETPOST("dto_".$reg[1].'_'.$reg[2]), '');
if (empty($dto)) {
$dto = 0;
}
@@ -328,7 +328,7 @@ if ($action == 'dispatch' && $permissiontoreceive) {
if (getDolGlobalString('SUPPLIER_ORDER_CAN_UPDATE_BUYINGPRICE_DURING_RECEIPT')) {
if (!isModEnabled("multicurrency") && empty($conf->dynamicprices->enabled)) {
$dto = GETPOSTINT("dto_".$reg[1].'_'.$reg[2]);
$dto = GETPOSTFLOAT("dto_".$reg[1].'_'.$reg[2]);
if (!empty($dto)) {
$unit_price = price2num((float) GETPOST("pu_".$reg[1]) * (100 - $dto) / 100, 'MU');
}
@@ -374,7 +374,7 @@ if ($action == 'dispatch' && $permissiontoreceive) {
if (!$error && getDolGlobalString('SUPPLIER_ORDER_CAN_UPDATE_BUYINGPRICE_DURING_RECEIPT')) {
if (!isModEnabled("multicurrency") && empty($conf->dynamicprices->enabled)) {
$dto = GETPOSTINT("dto_".$reg[1].'_'.$reg[2]);
$dto = GETPOSTFLOAT("dto_".$reg[1].'_'.$reg[2]);
//update supplier price
if (GETPOSTISSET($saveprice)) {
// TODO Use class

View File

@@ -329,3 +329,5 @@ ALTER TABLE llx_blockedlog ADD COLUMN debuginfo mediumtext;
ALTER TABLE llx_webhook_history ADD COLUMN trigger_code text NOT NULL;
ALTER TABLE llx_webhook_history ADD COLUMN error_message text;
ALTER TABLE llx_webhook_history MODIFY COLUMN url varchar(255);
UPDATE llx_c_socialnetworks SET icon = 'fa-mastodon' WHERE icon = '' AND code = 'mastodon';

View File

@@ -290,6 +290,7 @@ FiscalYearOpened=Fiscal year opened
FiscalYearClosed=Fiscal year closed
FiscalYearOpenedShort=Opened
FiscalYearClosedShort=Closed
ErrorFiscalYearOverlapWithFiscalYear=Fiscal year dates overlap with fiscal year: %s
ListSocialContributionAssociatedProject=List of social contributions associated with the project
DeleteFromCat=Remove from accounting group
AccountingAffectation=Accounting assignment

View File

@@ -2101,6 +2101,7 @@ class Task extends CommonObjectLine
$timespent = new TimeSpent($this->db);
$timespent->fetch($this->timespent_id);
$old_fk_element = $timespent->fk_element; // Store old task ID before potential change
$timespent->element_date = $this->timespent_date;
$timespent->element_datehour = $this->timespent_datehour;
@@ -2110,6 +2111,7 @@ class Task extends CommonObjectLine
$timespent->fk_user = $this->timespent_fk_user;
}
$timespent->fk_product = $this->timespent_fk_product;
$timespent->fk_element = $this->id; // Update task assignment (may be changed)
$timespent->note = $this->timespent_note;
$timespent->invoice_id = $this->timespent_invoiceid;
$timespent->invoice_line_id = $this->timespent_invoicelineid;
@@ -2171,6 +2173,32 @@ class Task extends CommonObjectLine
}
}
// If task assignment changed, recalculate duration_effective for both old and new tasks
if ($ret == 1 && $old_fk_element != $this->id) {
// Recalculate duration_effective for the OLD task
$sql = "UPDATE " . MAIN_DB_PREFIX . "projet_task";
$sql .= " SET duration_effective = (SELECT COALESCE(SUM(element_duration), 0) FROM " . MAIN_DB_PREFIX . "element_time as ptt where ptt.elementtype = 'task' AND ptt.fk_element = " . ((int) $old_fk_element) . ")";
$sql .= " WHERE rowid = " . ((int) $old_fk_element);
dol_syslog(get_class($this) . "::updateTimeSpent update old task", LOG_DEBUG);
if (!$this->db->query($sql)) {
$this->error = $this->db->lasterror();
$this->db->rollback();
$ret = -2;
}
// Recalculate duration_effective for the NEW task
if ($ret == 1) {
$sql = "UPDATE " . MAIN_DB_PREFIX . "projet_task";
$sql .= " SET duration_effective = (SELECT COALESCE(SUM(element_duration), 0) FROM " . MAIN_DB_PREFIX . "element_time as ptt where ptt.elementtype = 'task' AND ptt.fk_element = " . ((int) $this->id) . ")";
$sql .= " WHERE rowid = " . ((int) $this->id);
dol_syslog(get_class($this) . "::updateTimeSpent update new task", LOG_DEBUG);
if (!$this->db->query($sql)) {
$this->error = $this->db->lasterror();
$this->db->rollback();
$ret = -2;
}
}
}
if ($ret >= 0) {
$this->db->commit();
}

View File

@@ -265,7 +265,7 @@ if ($action == 'updatelines' && $permissiontoreceive) {
if (!$error && getDolGlobalString('SUPPLIER_ORDER_CAN_UPDATE_BUYINGPRICE_DURING_RECEIPT')) {
if (!isModEnabled("multicurrency") && empty($conf->dynamicprices->enabled)) {
$dto = price2num(GETPOSTINT("dto_".$reg[1].'_'.$reg[2]), '');
$dto = price2num(GETPOST("dto_".$reg[1].'_'.$reg[2]), '');
if (empty($dto)) {
$dto = 0;
}