2
0
forked from Wavyzz/dolibarr

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

This commit is contained in:
Laurent Destailleur
2023-02-19 18:18:38 +01:00
16 changed files with 316 additions and 180 deletions

View File

@@ -102,6 +102,7 @@ function printDropdownBookmarksList()
$listbtn .= img_picto('', 'edit', 'class="paddingright opacitymedium"').$langs->trans('EditBookmarks').'</a>';
$bookmarkList = '';
$bookmarkNb = 0;
// Menu with list of bookmarks
$sql = "SELECT rowid, title, url, target FROM ".MAIN_DB_PREFIX."bookmark";
$sql .= " WHERE (fk_user = ".((int) $user->id)." OR fk_user is NULL OR fk_user = 0)";
@@ -116,6 +117,7 @@ function printDropdownBookmarksList()
$bookmarkList .= dol_escape_htmltag($obj->title);
$bookmarkList .= '</a>';
$i++;
$bookmarkNb++;
}
$bookmarkList .= '</div>';
@@ -141,6 +143,7 @@ function printDropdownBookmarksList()
$searchForm .= dol_escape_htmltag($obj->title);
$searchForm .= '</option>';
$i++;
$bookmarkNb++;
}
$searchForm .= '</select>';
}
@@ -200,13 +203,15 @@ function printDropdownBookmarksList()
</div>
';
$html .= '
<!-- Menu Body -->
<div class="bookmark-body dropdown-body">
'.$bookmarkList.'
<span id="top-bookmark-search-nothing-found" class="hidden-search-result opacitymedium">'.dol_escape_htmltag($langs->trans("NoBookmarkFound")).'</span>
</div>
';
if ($bookmarkNb) {
$html .= '
<!-- Menu Body -->
<div class="bookmark-body dropdown-body">
'.$bookmarkList.'
<span id="top-bookmark-search-nothing-found" class="hidden-search-result opacitymedium">'.dol_escape_htmltag($langs->trans("NoBookmarkFound")).'</span>
</div>
';
}
$html .= '<!-- script to open/close the popup -->
<script>

View File

@@ -163,7 +163,7 @@ if (isModEnabled("propal") && $user->hasRight("propal", "lire")) {
$obj = $db->fetch_object($resql);
if ($i >= $max) {
$othernb += 1;
$othernb++;
$i++;
$total += (!empty($conf->global->MAIN_DASHBOARD_USE_TOTAL_HT) ? $obj->total_ht : $obj->total_ttc);
continue;
@@ -868,7 +868,7 @@ if (isModEnabled('contrat') && $user->hasRight("contrat", "lire") && 0) { // TOD
print '<tr class="oddeven">';
print '<td class="nowraponall">'.$staticcontrat->getNomUrl(1).'</td>';
print '<td class="tdoverflowmax150">'.$companystatic->getNomUrl(1, 'customer', 44).'</td>';
print '<td class="tdoverflowmax150">'.$companystatic->getNomUrl(1, 'customer').'</td>';
print '<td class="right">'.$staticcontrat->LibStatut($obj->statut, 3).'</td>';
print '</tr>';
@@ -971,7 +971,7 @@ if (isModEnabled("propal") && $user->hasRight("propal", "lire")) {
print '</table>';
print '</td>';
print '<td class="nowrap">'.$companystatic->getNomUrl(1, 'customer', 44).'</td>';
print '<td class="tdoverflowmax150">'.$companystatic->getNomUrl(1, 'customer').'</td>';
$datem = $db->jdate($obj->dp);
print '<td class="center tddate" title="'.dol_escape_htmltag($langs->trans("Date").': '.dol_print_date($datem, 'day', 'tzserver')).'">';
print dol_print_date($datem, 'day', 'tzserver');
@@ -1091,7 +1091,7 @@ if (isModEnabled('commande') && $user->hasRight('commande', 'lire')) {
print '</table>';
print '</td>';
print '<td class="tdoverflowmax150">'.$companystatic->getNomUrl(1, 'customer', 44).'</td>';
print '<td class="tdoverflowmax150">'.$companystatic->getNomUrl(1, 'customer').'</td>';
$datem = $db->jdate($obj->dv);
print '<td class="center tddate" title="'.dol_escape_htmltag($langs->trans("DateValue").': '.dol_print_date($datem, 'day', 'tzserver')).'">';
print dol_print_date($datem, 'day', 'tzserver');

View File

@@ -261,7 +261,7 @@ class RemiseCheque extends CommonObject
$this->errno = $this->db->lasterrno();
}
if (!$this->errno && !empty($conf->global->MAIN_DISABLEDRAFTSTATUS)) {
if (!$this->errno && (getDolGlobalString('MAIN_DISABLEDRAFTSTATUS') || getDolGlobalString('MAIN_DISABLEDRAFTSTATUS_CHEQUE'))) {
$res = $this->validate($user);
//if ($res < 0) $error++;
}

View File

@@ -954,7 +954,14 @@ if (!$error && $massaction == 'validate' && $permissiontoadd) {
foreach ($toselect as $toselectid) {
$result = $objecttmp->fetch($toselectid);
if ($result > 0) {
$result = $objecttmp->validate($user);
if (method_exists($objecttmp, 'validate')) {
$result = $objecttmp->validate($user);
} elseif (method_exists($objecttmp, 'setValid')) {
$result = $objecttmp->setValid($user);
} else {
$objecttmp->error = 'No method validate or setValid on this object';
$result = -1;
}
if ($result == 0) {
$langs->load("errors");
setEventMessages($langs->trans("ErrorObjectMustHaveStatusDraftToBeValidated", $objecttmp->ref), null, 'errors');
@@ -973,8 +980,13 @@ if (!$error && $massaction == 'validate' && $permissiontoadd) {
if (getDolGlobalInt('MAIN_MULTILANGS') && empty($newlang) && GETPOST('lang_id', 'aZ09')) {
$newlang = GETPOST('lang_id', 'aZ09');
}
if (getDolGlobalInt('MAIN_MULTILANGS') && empty($newlang)) {
$newlang = $objecttmp->thirdparty->default_lang;
if (getDolGlobalInt('MAIN_MULTILANGS') && empty($newlang) && property_exists($objecttmp, 'thirdparty')) {
if ((property_exists($objecttmp, 'socid') || property_exists($objecttmp, 'fk_soc')) && empty($objecttmp->thirdparty)) {
$objecttmp->fetch_thirdparty();
}
if (!empty($objecttmp->thirdparty)) {
$newlang = $objecttmp->thirdparty->default_lang;
}
}
if (!empty($newlang)) {
$outputlangs = new Translate("", $conf);

View File

@@ -794,6 +794,12 @@ function GETPOST($paramname, $check = 'alphanohtml', $method = 0, $filter = null
}
}
} else {
// If field name is 'search_xxx' then we force the add of space after each < and > (when following char is numeric) because it means
// we use the < or > to make a search on a numeric value to do higher or lower so we can add a space to break html tags
if (strpos($paramname, 'search_') === 0) {
$out = preg_replace('/([<>])([-+]?\d)/', '\1 \2', $out);
}
$out = sanitizeVal($out, $check, $filter, $options);
}
@@ -9801,7 +9807,7 @@ function dol_getmypid()
* If param $mode is 0, can contains several keywords separated with a space or |
* like "keyword1 keyword2" = We want record field like keyword1 AND field like keyword2
* or like "keyword1|keyword2" = We want record field like keyword1 OR field like keyword2
* If param $mode is 1, can contains an operator <, > or = like "<10" or ">=100.5 < 1000"
* If param $mode is 1, can contains an operator <, > or = like "<10" or ">=100.5 < -1000"
* If param $mode is 2, can contains a list of int id separated by comma like "1,3,4"
* If param $mode is 3, can contains a list of string separated by comma like "a,b,c"
* @param integer $mode 0=value is list of keyword strings, 1=value is a numeric test (Example ">5.5 <10"), 2=value is a list of ID separated with comma (Example '1,3,4')
@@ -9839,23 +9845,35 @@ function natural_search($fields, $value, $mode = 0, $nofirstand = 0)
$newres = '';
foreach ($fields as $field) {
if ($mode == 1) {
$operator = '=';
$newcrit = preg_replace('/([!<>=]+)/', '', $crit);
$reg = array();
preg_match('/([!<>=]+)/', $crit, $reg);
if (!empty($reg[1])) {
$operator = $reg[1];
}
if ($newcrit != '') {
$numnewcrit = price2num($newcrit);
if (is_numeric($numnewcrit)) {
$newres .= ($i2 > 0 ? ' OR ' : '').$field.' '.$operator.' '.((float) $numnewcrit); // should be a numeric
} else {
$newres .= ($i2 > 0 ? ' OR ' : '').'1 = 2'; // force false
$tmpcrits = explode('|', $crit);
$i3 = 0; // count the nb of valid criteria added for this field
foreach ($tmpcrits as $tmpcrit) {
if ($tmpcrit !== '0' && empty($tmpcrit)) {
continue;
}
$tmpcrit = trim($tmpcrit);
$newres .= (($i2 > 0 || $i3 > 0) ? ' OR ' : '');
$operator = '=';
$newcrit = preg_replace('/([!<>=]+)/', '', $tmpcrit);
$reg = array();
preg_match('/([!<>=]+)/', $tmpcrit, $reg);
if (!empty($reg[1])) {
$operator = $reg[1];
}
if ($newcrit != '') {
$numnewcrit = price2num($newcrit);
if (is_numeric($numnewcrit)) {
$newres .= $field.' '.$operator.' '.((float) $numnewcrit); // should be a numeric
} else {
$newres .= '1 = 2'; // force false, we received a corrupted data
}
$i3++; // a criteria was added to string
}
$i2++; // a criteria was added to string
}
$i2++;
} elseif ($mode == 2 || $mode == -2) {
$crit = preg_replace('/[^0-9,]/', '', $crit); // ID are always integer
$newres .= ($i2 > 0 ? ' OR ' : '').$field." ".($mode == -2 ? 'NOT ' : '');
@@ -9897,28 +9915,36 @@ function natural_search($fields, $value, $mode = 0, $nofirstand = 0)
}
}
}
} else // $mode=0
{
} else { // $mode=0
$tmpcrits = explode('|', $crit);
$i3 = 0;
$i3 = 0; // count the nb of valid criteria added for this field
foreach ($tmpcrits as $tmpcrit) {
if ($tmpcrit !== '0' && empty($tmpcrit)) {
continue;
}
$tmpcrit = trim($tmpcrit);
$newres .= (($i2 > 0 || $i3 > 0) ? ' OR ' : '');
if ($tmpcrit == '^$') { // If we search empty, we must combined different fields with AND
$newres .= (($i2 > 0 || $i3 > 0) ? ' AND ' : '');
} else {
$newres .= (($i2 > 0 || $i3 > 0) ? ' OR ' : '');
}
if (preg_match('/\.(id|rowid)$/', $field)) { // Special case for rowid that is sometimes a ref so used as a search field
$newres .= $field." = ".(is_numeric(trim($tmpcrit)) ? ((float) trim($tmpcrit)) : '0');
$newres .= $field." = ".(is_numeric($tmpcrit) ? ((float) $tmpcrit) : '0');
} else {
$tmpcrit = trim($tmpcrit);
$tmpcrit2 = $tmpcrit;
$tmpbefore = '%';
$tmpafter = '%';
$tmps = '';
if (preg_match('/^!/', $tmpcrit)) {
$newres .= $field." NOT LIKE '"; // ! as exclude character
$tmps .= $field." NOT LIKE "; // ! as exclude character
$tmpcrit2 = preg_replace('/^!/', '', $tmpcrit2);
} else $newres .= $field." LIKE '";
} else {
$tmps .= $field." LIKE ";
}
$tmps .= "'";
if (preg_match('/^[\^\$]/', $tmpcrit)) {
$tmpbefore = '';
@@ -9928,12 +9954,17 @@ function natural_search($fields, $value, $mode = 0, $nofirstand = 0)
$tmpafter = '';
$tmpcrit2 = preg_replace('/[\^\$]$/', '', $tmpcrit2);
}
if ($tmpcrit2 == '' || preg_match('/^!/', $tmpcrit)) {
$tmps = "(".$tmps;
}
$newres .= $tmps;
$newres .= $tmpbefore;
$newres .= $db->escape($tmpcrit2);
$newres .= $tmpafter;
$newres .= "'";
if ($tmpcrit2 == '') {
$newres .= " OR ".$field." IS NULL";
if ($tmpcrit2 == '' || preg_match('/^!/', $tmpcrit)) {
$newres .= " OR ".$field." IS NULL)";
}
}
@@ -9943,13 +9974,14 @@ function natural_search($fields, $value, $mode = 0, $nofirstand = 0)
}
$i++;
}
if ($newres) {
$res = $res.($res ? ' AND ' : '').($i2 > 1 ? '(' : '').$newres.($i2 > 1 ? ')' : '');
}
$j++;
}
$res = ($nofirstand ? "" : " AND ")."(".$res.")";
//print 'xx'.$res.'yy';
return $res;
}

View File

@@ -2420,13 +2420,16 @@ function searchTaskInChild(&$inc, $parent, &$lines, &$taskrole)
* @param int $status -1=No filter on statut, 0 or 1 = Filter on status
* @param array $listofoppstatus List of opportunity status
* @param array $hiddenfields List of info to not show ('projectlabel', 'declaredprogress', '...', )
* @param int $max Max nb of record to show in HTML list
* @return void
*/
function print_projecttasks_array($db, $form, $socid, $projectsListId, $mytasks = 0, $status = -1, $listofoppstatus = array(), $hiddenfields = array())
function print_projecttasks_array($db, $form, $socid, $projectsListId, $mytasks = 0, $status = -1, $listofoppstatus = array(), $hiddenfields = array(), $max = 0)
{
global $langs, $conf, $user;
global $theme_datacolor;
$maxofloop = (empty($conf->global->MAIN_MAXLIST_OVERLOAD) ? 500 : $conf->global->MAIN_MAXLIST_OVERLOAD);
require_once DOL_DOCUMENT_ROOT.'/projet/class/project.class.php';
$listofstatus = array_keys($listofoppstatus);
@@ -2457,8 +2460,6 @@ function print_projecttasks_array($db, $form, $socid, $projectsListId, $mytasks
$title = $langs->trans("Projects").' '.$langs->trans($projectstatic->statuts_long[$status]);
}
$arrayidtypeofcontact = array();
print '<!-- print_projecttasks_array -->';
print '<div class="div-table-responsive-no-min">';
print '<table class="noborder centpercent">';
@@ -2537,11 +2538,15 @@ function print_projecttasks_array($db, $form, $socid, $projectsListId, $mytasks
$resql = $db->query($sql2);
if ($resql) {
$othernb = 0;
$total_task = 0;
$total_opp_amount = 0;
$ponderated_opp_amount = 0;
$total_plannedworkload = 0;
$total_declaredprogressworkload = 0;
$num = $db->num_rows($resql);
$nbofloop = min($num, (empty($conf->global->MAIN_MAXLIST_OVERLOAD) ? 500 : $conf->global->MAIN_MAXLIST_OVERLOAD));
$i = 0;
print '<tr class="liste_titre">';
@@ -2568,11 +2573,23 @@ function print_projecttasks_array($db, $form, $socid, $projectsListId, $mytasks
}
print "</tr>\n";
$total_plannedworkload = 0;
$total_declaredprogressworkload = 0;
while ($i < $num) {
while ($i < $nbofloop) {
$objp = $db->fetch_object($resql);
if ($max && $i >= $max) {
$othernb++;
$i++;
$total_task += $objp->nb;
$total_opp_amount += $objp->opp_amount;
$opp_weighted_amount = $objp->opp_percent * $objp->opp_amount / 100;
$ponderated_opp_amount += price2num($opp_weighted_amount);
$plannedworkload = $objp->planned_workload;
$total_plannedworkload += $plannedworkload;
$declaredprogressworkload = $objp->declared_progess_workload;
$total_declaredprogressworkload += $declaredprogressworkload;
continue;
}
$projectstatic->id = $objp->projectid;
$projectstatic->user_author_id = $objp->fk_user_creat;
$projectstatic->public = $objp->public;
@@ -2679,13 +2696,21 @@ function print_projecttasks_array($db, $form, $socid, $projectsListId, $mytasks
print "</tr>\n";
$total_task = $total_task + $objp->nb;
$total_opp_amount = $total_opp_amount + $objp->opp_amount;
$total_task += $objp->nb;
$total_opp_amount += $objp->opp_amount;
}
$i++;
}
if ($othernb) {
print '<tr class="oddeven">';
print '<td class="nowrap" colspan="5">';
print '<span class="opacitymedium">'.$langs->trans("More").'...'.($othernb < $maxofloop ? ' ('.$othernb.')' : '').'</span>';
print '</td>';
print "</tr>\n";
}
print '<tr class="liste_total">';
print '<td>'.$langs->trans("Total")."</td><td></td>";
if (!empty($conf->global->PROJECT_USE_OPPORTUNITIES)) {

View File

@@ -438,7 +438,7 @@ class Don extends CommonObject
}
}
if (!$error && !empty($conf->global->MAIN_DISABLEDRAFTSTATUS)) {
if (!$error && (getDolGlobalString('MAIN_DISABLEDRAFTSTATUS') || getDolGlobalString('MAIN_DISABLEDRAFTSTATUS_DONATION'))) {
//$res = $this->setValid($user);
//if ($res < 0) $error++;
}

View File

@@ -1010,17 +1010,17 @@ if (!empty($arrayfields['f.ref_supplier']['checked'])) {
if (!empty($arrayfields['f.type']['checked'])) {
print '<td class="liste_titre maxwidthonsmartphone">';
$listtype = array(
FactureFournisseur::TYPE_STANDARD=>$langs->trans("InvoiceStandard"),
FactureFournisseur::TYPE_REPLACEMENT=>$langs->trans("InvoiceReplacement"),
FactureFournisseur::TYPE_CREDIT_NOTE=>$langs->trans("InvoiceAvoir"),
FactureFournisseur::TYPE_DEPOSIT=>$langs->trans("InvoiceDeposit"),
FactureFournisseur::TYPE_STANDARD=>$langs->trans("InvoiceStandard"),
FactureFournisseur::TYPE_REPLACEMENT=>$langs->trans("InvoiceReplacement"),
FactureFournisseur::TYPE_CREDIT_NOTE=>$langs->trans("InvoiceAvoir"),
FactureFournisseur::TYPE_DEPOSIT=>$langs->trans("InvoiceDeposit"),
);
/*
if (!empty($conf->global->INVOICE_USE_SITUATION))
{
$listtype[Facture::TYPE_SITUATION] = $langs->trans("InvoiceSituation");
}
*/
if (!empty($conf->global->INVOICE_USE_SITUATION))
{
$listtype[Facture::TYPE_SITUATION] = $langs->trans("InvoiceSituation");
}
*/
//$listtype[Facture::TYPE_PROFORMA]=$langs->trans("InvoiceProForma"); // A proformat invoice is not an invoice but must be an order.
print $form->selectarray('search_type', $listtype, $search_type, 1, 0, 0, '', 0, 0, 0, 'ASC', 'maxwidth100');
print '</td>';
@@ -1047,11 +1047,11 @@ if (!empty($arrayfields['f.date_lim_reglement']['checked'])) {
print '<td class="liste_titre center">';
print '<div class="nowrap">';
/*
print $langs->trans('From').' ';
print $form->selectDate($search_datelimit_start ? $search_datelimit_start : -1, 'search_datelimit_start', 0, 0, 1);
print '</div>';
print '<div class="nowrap">';
print $langs->trans('to').' ';*/
print $langs->trans('From').' ';
print $form->selectDate($search_datelimit_start ? $search_datelimit_start : -1, 'search_datelimit_start', 0, 0, 1);
print '</div>';
print '<div class="nowrap">';
print $langs->trans('to').' ';*/
print $form->selectDate($search_datelimit_end ? $search_datelimit_end : -1, 'search_datelimit_end', 0, 0, 1, '', 1, 0, 0, '', '', '', '', 1, '', $langs->trans("Before"));
print '<br><input type="checkbox" name="search_option" value="late"'.($option == 'late' ? ' checked' : '').'> '.$langs->trans("Alert");
print '</div>';
@@ -1412,6 +1412,7 @@ if ($num > 0) {
$remaintopay = -$facturestatic->getSumFromThisCreditNotesNotUsed();
}
}
if ($mode == 'kanban') {
if ($i == 0) {
print '<tr><td colspan="12">';
@@ -1483,7 +1484,7 @@ if ($num > 0) {
// Label
if (!empty($arrayfields['f.label']['checked'])) {
print '<td class="nowrap">';
print $obj->label;
print dol_escape_htmltag($obj->label);
print '</td>';
if (!$i) {
$totalarray['nbfield']++;
@@ -1539,7 +1540,7 @@ if ($num > 0) {
// Alias
if (!empty($arrayfields['s.name_alias']['checked'])) {
print '<td class="tdoverflowmax150">';
print $thirdparty->name_alias;
print dol_escape_htmltag($thirdparty->name_alias);
print '</td>';
if (!$i) {
$totalarray['nbfield']++;
@@ -1547,8 +1548,8 @@ if ($num > 0) {
}
// Town
if (!empty($arrayfields['s.town']['checked'])) {
print '<td class="nocellnopadd">';
print $obj->town;
print '<td class="tdoverflowmax100" title="'.dol_escape_htmltag($obj->town).'">';
print dol_escape_htmltag($obj->town);
print '</td>';
if (!$i) {
$totalarray['nbfield']++;
@@ -1556,7 +1557,7 @@ if ($num > 0) {
}
// Zip
if (!empty($arrayfields['s.zip']['checked'])) {
print '<td class="nocellnopadd center tdoverflowmax100" title="'.dol_escape_htmltag($obj->zip).'">';
print '<td class="tdoverflowmax100" title="'.dol_escape_htmltag($obj->zip).'">';
print dol_escape_htmltag($obj->zip);
print '</td>';
if (!$i) {
@@ -1565,7 +1566,9 @@ if ($num > 0) {
}
// State
if (!empty($arrayfields['state.nom']['checked'])) {
print "<td>".$obj->state_name."</td>\n";
print '<td class="tdoverflowmax100" title="'.dol_escape_htmltag($obj->state_name).'">';
print dol_escape_htmltag($obj->state_name);
print "</td>\n";
if (!$i) {
$totalarray['nbfield']++;
}
@@ -1829,6 +1832,7 @@ if ($num > 0) {
print "</tr>\n";
}
$i++;
}

View File

@@ -398,3 +398,7 @@ ALTER TABLE llx_c_tva ADD COLUMN use_default tinyint DEFAULT 0;
ALTER TABLE llx_commande_fournisseurdet MODIFY COLUMN ref varchar(128);
ALTER TABLE llx_facture_fourn_det MODIFY COLUMN ref varchar(128);
ALTER TABLE llx_projet ADD COLUMN extraparams varchar(255);

View File

@@ -77,7 +77,7 @@ create table llx_commande
fk_incoterms integer, -- for incoterms
location_incoterms varchar(255), -- for incoterms
import_key varchar(14),
extraparams varchar(255), -- for stock other parameters with json format
extraparams varchar(255), -- to save other parameters with json format
fk_multicurrency integer,
multicurrency_code varchar(3),

View File

@@ -19,45 +19,46 @@
create table llx_projet
(
rowid integer AUTO_INCREMENT PRIMARY KEY,
fk_soc integer,
datec datetime, -- date creation project
tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
dateo date, -- date start project
datee date, -- date end project
ref varchar(50),
entity integer DEFAULT 1 NOT NULL, -- multi company id
title varchar(255) NOT NULL,
description text,
fk_user_creat integer NOT NULL, -- createur du projet
fk_user_modif integer,
public integer, -- project is public or not
fk_statut integer DEFAULT 0 NOT NULL, -- open or close
fk_opp_status integer DEFAULT NULL, -- if project is used to manage opportunities
opp_percent double(5,2),
fk_opp_status_end integer DEFAULT NULL, -- if project is used to manage opportunities (the opportunity status the project has when set to lose)
date_close datetime DEFAULT NULL,
fk_user_close integer DEFAULT NULL,
note_private text,
note_public text,
email_msgid varchar(175), -- if project or lead is created by email collector, we store here MSG ID. Do not use a too large value, it generates trouble with unique index
--budget_days real, -- budget in days is sum of field planned_workload of tasks
opp_amount double(24,8),
budget_amount double(24,8),
rowid integer AUTO_INCREMENT PRIMARY KEY,
fk_soc integer,
datec datetime, -- date creation project
tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
dateo date, -- date start project
datee date, -- date end project
ref varchar(50),
entity integer DEFAULT 1 NOT NULL, -- multi company id
title varchar(255) NOT NULL,
description text,
fk_user_creat integer NOT NULL, -- createur du projet
fk_user_modif integer,
public integer, -- project is public or not
fk_statut integer DEFAULT 0 NOT NULL, -- open or close
fk_opp_status integer DEFAULT NULL, -- if project is used to manage opportunities
opp_percent double(5,2),
fk_opp_status_end integer DEFAULT NULL, -- if project is used to manage opportunities (the opportunity status the project has when set to lose)
date_close datetime DEFAULT NULL,
fk_user_close integer DEFAULT NULL,
note_private text,
note_public text,
email_msgid varchar(175), -- if project or lead is created by email collector, we store here MSG ID. Do not use a too large value, it generates trouble with unique index
--budget_days real, -- budget in days is sum of field planned_workload of tasks
opp_amount double(24,8),
budget_amount double(24,8),
usage_opportunity integer DEFAULT 0, -- Set to 1 if project is used to follow an opportunity
usage_task integer DEFAULT 1, -- Set to 1 if project is used to manage tasks and/or record timesheet
usage_bill_time integer DEFAULT 0, -- Set to 1 if time spent must be converted into invoices
usage_organize_event integer DEFAULT 0, -- Set to 1 if you want to use project to organize an event or receive attendees registration
date_start_event datetime, -- date start event
date_end_event datetime, -- date end event
location varchar(255), -- location
date_start_event datetime, -- date start event
date_end_event datetime, -- date end event
location varchar(255), -- location
accept_conference_suggestions integer DEFAULT 0, -- Set to 1 if you want to allow unknown people to suggest conferences
accept_booth_suggestions integer DEFAULT 0, -- Set to 1 if you want to Allow unknown people to suggest booth
accept_booth_suggestions integer DEFAULT 0, -- Set to 1 if you want to Allow unknown people to suggest booth
max_attendees integer DEFAULT 0,
price_registration double(24,8),
price_booth double(24,8),
model_pdf varchar(255),
ip varchar(250), --ip used to create record (for public submission page)
last_main_doc varchar(255), -- relative filepath+filename of last main generated document
import_key varchar(14) -- Import key
price_booth double(24,8),
model_pdf varchar(255),
ip varchar(250), -- ip used to create record (for public submission page)
last_main_doc varchar(255), -- relative filepath+filename of last main generated document
import_key varchar(14), -- Import key
extraparams varchar(255) -- to save other parameters with json format
)ENGINE=innodb;

View File

@@ -464,7 +464,7 @@ OtherStatistics=Weitere Statistiken
Status=Status
Favorite=Favorit
ShortInfo=Info.
Ref=Artikelnummer
Ref=Ref.Nr.
ExternalRef=Externe-ID
RefSupplier=Lieferanten-Zeichen
RefPayment=Zahlungsref.-Nr.

View File

@@ -103,6 +103,8 @@ if ($id == '' && $ref == '' && ($action != "create" && $action != "add" && $acti
accessforbidden();
}
$permissiontoadd = $user->rights->projet->creer;
$permissiontodelete = $user->rights->projet->supprimer;
$permissiondellink = $user->rights->projet->creer; // Used by the include of actions_dellink.inc.php
@@ -156,7 +158,20 @@ if (empty($reshook)) {
include DOL_DOCUMENT_ROOT.'/core/actions_dellink.inc.php'; // Must be include, not include_once
if ($action == 'add' && $user->rights->projet->creer) {
// Action setdraft object
if ($action == 'confirm_setdraft' && $confirm == 'yes' && $permissiontoadd) {
$result = $object->setStatut($object::STATUS_DRAFT, null, '', 'PROJECT_MODIFY');
if ($result >= 0) {
// Nothing else done
} else {
$error++;
setEventMessages($object->error, $object->errors, 'errors');
}
$action = '';
}
// Action add
if ($action == 'add' && $permissiontoadd) {
$error = 0;
if (!GETPOST('ref')) {
setEventMessages($langs->trans("ErrorFieldRequired", $langs->transnoentities("Ref")), null, 'errors');
@@ -264,7 +279,7 @@ if (empty($reshook)) {
}
}
if ($action == 'update' && empty(GETPOST('cancel')) && $user->rights->projet->creer) {
if ($action == 'update' && empty(GETPOST('cancel')) && $permissiontoadd) {
$error = 0;
if (empty($ref)) {
@@ -380,7 +395,7 @@ if (empty($reshook)) {
}
// Build doc
if ($action == 'builddoc' && $user->rights->projet->creer) {
if ($action == 'builddoc' && $permissiontoadd) {
// Save last template used to generate document
if (GETPOST('model')) {
$object->setDocModel($user, GETPOST('model', 'alpha'));
@@ -399,7 +414,7 @@ if (empty($reshook)) {
}
// Delete file in doc form
if ($action == 'remove_file' && $user->rights->projet->creer) {
if ($action == 'remove_file' && $permissiontoadd) {
if ($object->id > 0) {
require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
@@ -417,28 +432,28 @@ if (empty($reshook)) {
}
if ($action == 'confirm_validate' && $confirm == 'yes') {
if ($action == 'confirm_validate' && $confirm == 'yes' && $permissiontoadd) {
$result = $object->setValid($user);
if ($result <= 0) {
setEventMessages($object->error, $object->errors, 'errors');
}
}
if ($action == 'confirm_close' && $confirm == 'yes') {
if ($action == 'confirm_close' && $confirm == 'yes' && $permissiontoadd) {
$result = $object->setClose($user);
if ($result <= 0) {
setEventMessages($object->error, $object->errors, 'errors');
}
}
if ($action == 'confirm_reopen' && $confirm == 'yes') {
if ($action == 'confirm_reopen' && $confirm == 'yes' && $permissiontoadd) {
$result = $object->setValid($user);
if ($result <= 0) {
setEventMessages($object->error, $object->errors, 'errors');
}
}
if ($action == 'confirm_delete' && GETPOST("confirm") == "yes" && $user->rights->projet->supprimer) {
if ($action == 'confirm_delete' && $confirm == 'yes' && $permissiontodelete) {
$object->fetch($id);
$result = $object->delete($user);
if ($result > 0) {
@@ -451,7 +466,7 @@ if (empty($reshook)) {
}
}
if ($action == 'confirm_clone' && $user->rights->projet->creer && $confirm == 'yes') {
if ($action == 'confirm_clone' && $permissiontoadd && $confirm == 'yes') {
$clone_contacts = GETPOST('clone_contacts') ? 1 : 0;
$clone_tasks = GETPOST('clone_tasks') ? 1 : 0;
$clone_project_files = GETPOST('clone_project_files') ? 1 : 0;
@@ -1107,7 +1122,7 @@ if ($action == 'create' && $user->rights->projet->creer) {
print '<tr class="classuseopportunity'.$classfortr.'"><td>'.$langs->trans("OpportunityStatus").'</td>';
print '<td>';
print '<div>';
print $formproject->selectOpportunityStatus('opp_status', $object->opp_status, 1, 0, 0, 0, 'inline-block valignmiddle', 1, 1);
print $formproject->selectOpportunityStatus('opp_status', $object->opp_status, 1, 0, 0, 0, 'minwidth150 inline-block valignmiddle', 1, 1);
// Opportunity probability
print ' <input class="width50 right" type="text" id="opp_percent" name="opp_percent" title="'.dol_escape_htmltag($langs->trans("OpportunityProbability")).'" value="'.(GETPOSTISSET('opp_percent') ? GETPOST('opp_percent') : (strcmp($object->opp_percent, '') ?vatrate($object->opp_percent) : '')).'"> %';
@@ -1488,6 +1503,17 @@ if ($action == 'create' && $user->rights->projet->creer) {
}
*/
// Back to draft
if (!getDolGlobalString('MAIN_DISABLEDRAFTSTATUS') && !getDolGlobalString('MAIN_DISABLEDRAFTSTATUS_PROJECT')) {
if ($object->statut != Project::STATUS_DRAFT && $user->rights->projet->creer) {
if ($userWrite > 0) {
print dolGetButtonAction('', $langs->trans('SetToDraft'), 'default', $_SERVER["PHP_SELF"].'?action=confirm_setdraft&amp;confirm=yes&amp;token='.newToken().'&amp;id='.$object->id, '');
} else {
print dolGetButtonAction($langs->trans('NotOwnerOfProject'), $langs->trans('SetToDraft'), 'default', $_SERVER['PHP_SELF']. '#', '', false);
}
}
}
// Modify
if ($object->statut != Project::STATUS_CLOSED && $user->rights->projet->creer) {
if ($userWrite > 0) {

View File

@@ -498,7 +498,7 @@ class Project extends CommonObject
}
}
if (!$error && !empty($conf->global->MAIN_DISABLEDRAFTSTATUS)) {
if (!$error && (getDolGlobalString('MAIN_DISABLEDRAFTSTATUS') || getDolGlobalString('MAIN_DISABLEDRAFTSTATUS_PROJECT'))) {
$res = $this->setValid($user);
if ($res < 0) {
$error++;
@@ -1098,7 +1098,7 @@ class Project extends CommonObject
*
* @param User $user User that validate
* @param int $notrigger 1=Disable triggers
* @return int <0 if KO, >0 if OK
* @return int <0 if KO, 0=Nothing done, >0 if KO
*/
public function setValid($user, $notrigger = 0)
{
@@ -1106,47 +1106,51 @@ class Project extends CommonObject
$error = 0;
if ($this->statut != 1) {
// Check parameters
if (preg_match('/^'.preg_quote($langs->trans("CopyOf").' ').'/', $this->title)) {
$this->error = $langs->trans("ErrorFieldFormat", $langs->transnoentities("Label")).'. '.$langs->trans('RemoveString', $langs->transnoentitiesnoconv("CopyOf"));
return -1;
// Protection
if ($this->status == self::STATUS_VALIDATED) {
dol_syslog(get_class($this)."::validate action abandonned: already validated", LOG_WARNING);
return 0;
}
// Check parameters
if (preg_match('/^'.preg_quote($langs->trans("CopyOf").' ').'/', $this->title)) {
$this->error = $langs->trans("ErrorFieldFormat", $langs->transnoentities("Label")).'. '.$langs->trans('RemoveString', $langs->transnoentitiesnoconv("CopyOf"));
return -1;
}
$this->db->begin();
$sql = "UPDATE ".MAIN_DB_PREFIX."projet";
$sql .= " SET fk_statut = ".self::STATUS_VALIDATED;
$sql .= " WHERE rowid = ".((int) $this->id);
//$sql .= " AND entity = ".((int) $conf->entity); // Disabled, when we use the ID for the where, we must not add any other search condition
dol_syslog(get_class($this)."::setValid", LOG_DEBUG);
$resql = $this->db->query($sql);
if ($resql) {
// Call trigger
if (empty($notrigger)) {
$result = $this->call_trigger('PROJECT_VALIDATE', $user);
if ($result < 0) {
$error++;
}
// End call triggers
}
$this->db->begin();
$sql = "UPDATE ".MAIN_DB_PREFIX."projet";
$sql .= " SET fk_statut = 1";
$sql .= " WHERE rowid = ".((int) $this->id);
$sql .= " AND entity = ".((int) $conf->entity);
dol_syslog(get_class($this)."::setValid", LOG_DEBUG);
$resql = $this->db->query($sql);
if ($resql) {
// Call trigger
if (empty($notrigger)) {
$result = $this->call_trigger('PROJECT_VALIDATE', $user);
if ($result < 0) {
$error++;
}
// End call triggers
}
if (!$error) {
$this->statut = 1;
$this->db->commit();
return 1;
} else {
$this->db->rollback();
$this->error = join(',', $this->errors);
dol_syslog(get_class($this)."::setValid ".$this->error, LOG_ERR);
return -1;
}
if (!$error) {
$this->statut = 1;
$this->db->commit();
return 1;
} else {
$this->db->rollback();
$this->error = $this->db->lasterror();
$this->error = join(',', $this->errors);
dol_syslog(get_class($this)."::setValid ".$this->error, LOG_ERR);
return -1;
}
} else {
$this->db->rollback();
$this->error = $this->db->lasterror();
return -1;
}
}

View File

@@ -193,9 +193,8 @@ print '<div class="fichecenter"><div class="fichethirdleft">';
// Statistics
include DOL_DOCUMENT_ROOT.'/projet/graph_opportunities.inc.php';
// List of draft projects
print_projecttasks_array($db, $form, $socid, $projectsListId, 0, 0, $listofoppstatus, array('projectlabel', 'plannedworkload', 'declaredprogress', 'prospectionstatus', 'projectstatus'));
print_projecttasks_array($db, $form, $socid, $projectsListId, 0, 0, $listofoppstatus, array('projectlabel', 'plannedworkload', 'declaredprogress', 'prospectionstatus', 'projectstatus'), $max);
print '</div><div class="fichetwothirdright">';

View File

@@ -54,6 +54,7 @@ $massaction = GETPOST('massaction', 'alpha');
$show_files = GETPOST('show_files', 'int');
$confirm = GETPOST('confirm', 'alpha');
$toselect = GETPOST('toselect', 'array');
$optioncss = GETPOST('optioncss', 'alpha');
$contextpage = GETPOST('contextpage', 'aZ') ?GETPOST('contextpage', 'aZ') : 'projectlist';
$title = $langs->trans("Projects");
@@ -113,6 +114,7 @@ $search_accept_booth_suggestions = GETPOST('search_accept_booth_suggestions', 'i
$search_price_registration = GETPOST("search_price_registration", 'alpha');
$search_price_booth = GETPOST("search_price_booth", 'alpha');
$search_login = GETPOST('search_login', 'alpha');
$search_import_key = GETPOST('search_import_key', 'alpha');
$searchCategoryCustomerOperator = 0;
if (GETPOSTISSET('formfilteraction')) {
$searchCategoryCustomerOperator = GETPOST('search_category_customer_operator', 'int');
@@ -120,7 +122,7 @@ if (GETPOSTISSET('formfilteraction')) {
$searchCategoryCustomerOperator = $conf->global->MAIN_SEARCH_CAT_OR_BY_DEFAULT;
}
$searchCategoryCustomerList = GETPOST('search_category_customer_list', 'array');
$optioncss = GETPOST('optioncss', 'alpha');
$mine = ((GETPOST('mode') == 'mine') ? 1 : 0);
if ($mine) {
@@ -134,7 +136,6 @@ $search_eday = GETPOST('search_eday', 'int');
$search_emonth = GETPOST('search_emonth', 'int');
$search_eyear = GETPOST('search_eyear', 'int');
$search_date_start_startmonth = GETPOST('search_date_start_startmonth', 'int');
$search_date_start_startyear = GETPOST('search_date_start_startyear', 'int');
$search_date_start_startday = GETPOST('search_date_start_startday', 'int');
@@ -152,6 +153,7 @@ $search_date_end_endmonth = GETPOST('search_date_end_endmonth', 'int');
$search_date_end_endyear = GETPOST('search_date_end_endyear', 'int');
$search_date_end_endday = GETPOST('search_date_end_endday', 'int');
$search_date_end_end = dol_mktime(23, 59, 59, $search_date_end_endmonth, $search_date_end_endday, $search_date_end_endyear); // Use tzserver
if (isModEnabled('categorie')) {
$search_category_array = GETPOST("search_category_".Categorie::TYPE_PROJECT."_list", "array");
}
@@ -301,6 +303,7 @@ if (empty($reshook)) {
$search_price_registration = '';
$search_price_booth = '';
$search_login = '';
$search_import_key = '';
$toselect = array();
$search_array_options = array();
$search_category_array = array();
@@ -368,6 +371,8 @@ if (empty($reshook)) {
$form = new Form($db);
$formcompany = new FormCompany($db);
$now = dol_now();
$companystatic = new Societe($db);
$taskstatic = new Task($db);
$formother = new FormOther($db);
@@ -415,7 +420,7 @@ if (count($listofprojectcontacttypeexternal) == 0) {
$varpage = empty($contextpage) ? $_SERVER["PHP_SELF"] : $contextpage;
$selectedfields = $form->multiSelectArrayWithCheckbox('selectedfields', $arrayfields, $varpage); // This also change content of $arrayfields
$distinct = 'DISTINCT'; // We add distinct until we are added a protection to be sure a contact of a project and task is only once.
$distinct = 'DISTINCT'; // We add distinct until we have added a protection to be sure a contact of a project and task is only once.
$sql = "SELECT ".$distinct." p.rowid as id, p.ref, p.title, p.fk_statut as status, p.fk_opp_status, p.public, p.fk_user_creat,";
$sql .= " p.datec as date_creation, p.dateo as date_start, p.datee as date_end, p.opp_amount, p.opp_percent, (p.opp_amount*p.opp_percent/100) as opp_weighted_amount, p.tms as date_update, p.budget_amount,";
$sql .= " p.usage_opportunity, p.usage_task, p.usage_bill_time, p.usage_organize_event,";
@@ -436,6 +441,9 @@ $parameters = array();
$reshook = $hookmanager->executeHooks('printFieldListSelect', $parameters, $object); // Note that $action and $object may have been modified by hook
$sql .= preg_replace('/^,/', '', $hookmanager->resPrint);
$sql = preg_replace('/,\s*$/', '', $sql);
$sqlfields = $sql; // $sql fields to remove for count total
$sql .= " FROM ".MAIN_DB_PREFIX.$object->table_element." as p";
if (!empty($extrafields->attributes[$object->table_element]['label']) &&is_array($extrafields->attributes[$object->table_element]['label']) && count($extrafields->attributes[$object->table_element]['label'])) {
$sql .= " LEFT JOIN ".MAIN_DB_PREFIX.$object->table_element."_extrafields as ef on (p.rowid = ef.fk_object)";
@@ -546,9 +554,11 @@ if ($search_sale > 0) {
// No check is done on company permission because readability is managed by public status of project and assignement.
//if (! $user->rights->societe->client->voir && ! $socid) $sql.= " AND ((s.rowid = sc.fk_soc AND sc.fk_user = ".((int) $user->id).") OR (s.rowid IS NULL))";
if ($search_project_user > 0) {
// TODO Replace this with a EXISTS and remove the link to table + DISTINCT
$sql .= " AND ecp.fk_c_type_contact IN (".$db->sanitize(join(',', array_keys($listofprojectcontacttype))).") AND ecp.element_id = p.rowid AND ecp.fk_socpeople = ".((int) $search_project_user);
}
if ($search_project_contact > 0) {
// TODO Replace this with a EXISTS and remove the link to table + DISTINCT
$sql .= " AND ecp_contact.fk_c_type_contact IN (".$db->sanitize(join(',', array_keys($listofprojectcontacttypeexternal))).") AND ecp_contact.element_id = p.rowid AND ecp_contact.fk_socpeople = ".((int) $search_project_contact);
}
if ($search_opp_amount != '') {
@@ -584,6 +594,9 @@ if ($search_price_booth != '') {
if ($search_login) {
$sql .= natural_search(array('u.login', 'u.firstname', 'u.lastname'), $search_login);
}
if ($search_import_key) {
$sql .= natural_search(array('p.import_key'), $search_import_key);
}
// Search for tag/category ($searchCategoryProjectList is an array of ID)
$searchCategoryProjectList = $search_category_array;
$searchCategoryProjectOperator = 0;
@@ -664,37 +677,45 @@ include DOL_DOCUMENT_ROOT.'/core/tpl/extrafields_list_search_sql.tpl.php';
$parameters = array();
$reshook = $hookmanager->executeHooks('printFieldListWhere', $parameters, $object); // Note that $action and $object may have been modified by hook
$sql .= $hookmanager->resPrint;
$sql .= $db->order($sortfield, $sortorder);
//print $sql;
// Count total nb of records
$nbtotalofrecords = '';
if (empty($conf->global->MAIN_DISABLE_FULL_SCANLIST)) {
$resql = $db->query($sql);
$nbtotalofrecords = $db->num_rows($resql);
if (($page * $limit) > $nbtotalofrecords) { // if total of record found is smaller than page * limit, goto and load page 0
/* The fast and low memory method to get and count full list converts the sql into a sql count */
$sqlforcount = preg_replace('/^'.preg_quote($sqlfields, '/').'/', 'SELECT COUNT(*) as nbtotalofrecords', $sql);
$sqlforcount = preg_replace('/GROUP BY .*$/', '', $sqlforcount);
$resql = $db->query($sqlforcount);
if ($resql) {
$objforcount = $db->fetch_object($resql);
$nbtotalofrecords = $objforcount->nbtotalofrecords;
} else {
dol_print_error($db);
}
if (($page * $limit) > $nbtotalofrecords) { // if total resultset is smaller then paging size (filtering), goto and load page 0
$page = 0;
$offset = 0;
}
$db->free($resql);
}
// if total of record found is smaller than limit, no need to do paging and to restart another select with limits set.
if (is_numeric($nbtotalofrecords) && ($limit > $nbtotalofrecords || empty($limit))) {
$num = $nbtotalofrecords;
} else {
if (!empty($limit)) {
$sql .= $db->plimit($limit + 1, $offset);
}
$resql = $db->query($sql);
if (!$resql) {
dol_print_error($db);
exit;
}
$num = $db->num_rows($resql);
// Complete request and execute it with limit
$sql .= $db->order($sortfield, $sortorder);
if ($limit) {
$sql .= $db->plimit($limit + 1, $offset);
}
$resql = $db->query($sql);
if (!$resql) {
dol_print_error($db);
exit;
}
$num = $db->num_rows($resql);
// Direct jump if only one record found
if ($num == 1 && !empty($conf->global->MAIN_SEARCH_DIRECT_OPEN_IF_ONLY_ONE) && $search_all) {
if ($num == 1 && !empty($conf->global->MAIN_SEARCH_DIRECT_OPEN_IF_ONLY_ONE) && $search_all && !$page) {
$obj = $db->fetch_object($resql);
header("Location: ".DOL_URL_ROOT.'/projet/card.php?id='.$obj->id);
exit;
@@ -704,8 +725,6 @@ if ($num == 1 && !empty($conf->global->MAIN_SEARCH_DIRECT_OPEN_IF_ONLY_ONE) && $
// Output page
// --------------------------------------------------------------------
dol_syslog("list allowed project", LOG_DEBUG);
llxHeader('', $title, $help_url);
$arrayofselected = is_array($toselect) ? $toselect : array();
@@ -857,6 +876,9 @@ if ($search_price_booth != '') {
if ($search_login) {
$param .= '&search_login='.urlencode($search_login);
}
if ($search_import_key) {
$param .= '&search_import_key='.urlencode($search_import_key);
}
if ($optioncss != '') {
$param .= '&optioncss='.urlencode($optioncss);
}
@@ -868,6 +890,7 @@ include DOL_DOCUMENT_ROOT.'/core/tpl/extrafields_list_search_param.tpl.php';
// List of mass actions available
$arrayofmassactions = array(
'validate'=>img_picto('', 'check', 'class="pictofixedwidth"').$langs->trans("Validate"),
'generate_doc'=>img_picto('', 'pdf', 'class="pictofixedwidth"').$langs->trans("ReGeneratePDF"),
//'builddoc'=>img_picto('', 'pdf', 'class="pictofixedwidth"').$langs->trans("PDFMerge"),
//'presend'=>img_picto('', 'email', 'class="pictofixedwidth"').$langs->trans("SendByMail"),
@@ -1188,6 +1211,7 @@ if (!empty($arrayfields['p.email_msgid']['checked'])) {
if (!empty($arrayfields['p.import_key']['checked'])) {
// Import key
print '<td class="liste_titre">';
print '<input class="flat width75" type="text" name="search_import_key" value="'.dol_escape_htmltag($search_import_key).'">';
print '</td>';
}
if (!empty($arrayfields['p.fk_statut']['checked'])) {