fix: custom fields obrigatórios na criação de projeto + identifier lowercase (1.5.6)
- form das abas Redmine exibe/envia custom_field_values (Tipo de Projeto etc.) - createProjectFull normaliza o identificador (minúsculas) - sync da aba de projeto usa syncMetadata completo (não apaga mais o cache) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
913fd82272
commit
c8765ca5ff
8 changed files with 120 additions and 13 deletions
12
CHANGELOG.md
12
CHANGELOG.md
|
|
@ -4,6 +4,18 @@ Todas as mudanças relevantes deste plugin são documentadas aqui.
|
|||
O formato segue [Keep a Changelog](https://keepachangelog.com/pt-BR/1.0.0/)
|
||||
e o versionamento segue [SemVer](https://semver.org/lang/pt-BR/).
|
||||
|
||||
## [1.5.6]
|
||||
|
||||
### Corrigido
|
||||
- **Criação de projeto Redmine falhava** em instâncias com campos personalizados
|
||||
obrigatórios de projeto (ex.: "Tipo de Projeto", "Tipo de Faturamento"): o form
|
||||
das abas Redmine agora exibe esses campos (dropdown para listas) e os envia
|
||||
como `custom_field_values`.
|
||||
- **Identificador** é normalizado (minúsculas) na criação — "GNA" virava
|
||||
"Identificador não é válido".
|
||||
- O "Sincronizar Metadados" da aba de projeto fazia um sync parcial que apagava
|
||||
trackers/status/prioridades do cache; agora usa o sync completo (inclui usuários).
|
||||
|
||||
## [1.5.5]
|
||||
|
||||
### Corrigido
|
||||
|
|
|
|||
|
|
@ -64,6 +64,20 @@ if (isset($_POST["action"])) {
|
|||
$payload['enabled_module_names'] = $_POST['enabled_module_names'];
|
||||
}
|
||||
|
||||
// Custom fields de projeto (obrigatórios em alguns Redmine).
|
||||
// O empty-choice do dropdown envia '0' — descartar junto com vazios.
|
||||
if (!empty($_POST['custom_field_values']) && is_array($_POST['custom_field_values'])) {
|
||||
$cfv = [];
|
||||
foreach ($_POST['custom_field_values'] as $cfid => $cfval) {
|
||||
if ($cfval !== '' && $cfval !== null && $cfval !== '0' && $cfval !== 0) {
|
||||
$cfv[(string) (int) $cfid] = $cfval;
|
||||
}
|
||||
}
|
||||
if (!empty($cfv)) {
|
||||
$payload['custom_field_values'] = $cfv;
|
||||
}
|
||||
}
|
||||
|
||||
$new_redmine_id = PluginRedmineRedmineapi::createProjectFull($payload);
|
||||
if ($new_redmine_id) {
|
||||
$DB->insert('glpi_plugin_redmine_entities', [
|
||||
|
|
|
|||
|
|
@ -14,17 +14,16 @@ if (isset($_POST["action"])) {
|
|||
}
|
||||
|
||||
if ($action === 'sync') {
|
||||
// Fetch projects from Redmine and cache them
|
||||
$redmine_projects = PluginRedmineRedmineapi::getProjects();
|
||||
if ($redmine_projects !== false) {
|
||||
// Full metadata sync (partial sync used to wipe trackers/statuses from the cache).
|
||||
$metadata = PluginRedmineRedmineapi::syncMetadata();
|
||||
if ($metadata !== false) {
|
||||
global $DB;
|
||||
$metadata = ['projects' => $redmine_projects];
|
||||
|
||||
$DB->update('glpi_plugin_redmine_configs', [
|
||||
'metadata_cache' => json_encode($metadata)
|
||||
], [
|
||||
'id' => 1
|
||||
]);
|
||||
PluginRedmineRedmineapi::syncUsers();
|
||||
Session::addMessageAfterRedirect("Metadados sincronizados com sucesso!");
|
||||
} else {
|
||||
Session::addMessageAfterRedirect("Erro ao sincronizar com Redmine.", false, ERROR);
|
||||
|
|
@ -72,6 +71,20 @@ if (isset($_POST["action"])) {
|
|||
$payload['enabled_module_names'] = $_POST['enabled_module_names'];
|
||||
}
|
||||
|
||||
// Custom fields de projeto (obrigatórios em alguns Redmine).
|
||||
// O empty-choice do dropdown envia '0' — descartar junto com vazios.
|
||||
if (!empty($_POST['custom_field_values']) && is_array($_POST['custom_field_values'])) {
|
||||
$cfv = [];
|
||||
foreach ($_POST['custom_field_values'] as $cfid => $cfval) {
|
||||
if ($cfval !== '' && $cfval !== null && $cfval !== '0' && $cfval !== 0) {
|
||||
$cfv[(string) (int) $cfid] = $cfval;
|
||||
}
|
||||
}
|
||||
if (!empty($cfv)) {
|
||||
$payload['custom_field_values'] = $cfv;
|
||||
}
|
||||
}
|
||||
|
||||
$new_redmine_id = PluginRedmineRedmineapi::createProjectFull($payload);
|
||||
|
||||
if ($new_redmine_id) {
|
||||
|
|
|
|||
|
|
@ -75,6 +75,7 @@ class PluginRedmineEntity extends CommonDBTM {
|
|||
'owner_field' => 'entities_id',
|
||||
'owner_id' => $entities_id,
|
||||
'project_options' => PluginRedmineProject::getRedmineProjectOptions(true),
|
||||
'custom_fields' => PluginRedmineProject::getProjectCustomFieldDefs(),
|
||||
'project_name' => $entity->fields['name'],
|
||||
'project_description' => $description,
|
||||
'project_identifier' => 'glpi-ent-' . $entities_id,
|
||||
|
|
|
|||
|
|
@ -75,6 +75,7 @@ class PluginRedmineProject extends CommonDBTM {
|
|||
'owner_field' => 'projects_id',
|
||||
'owner_id' => $projects_id,
|
||||
'project_options' => self::getRedmineProjectOptions(true),
|
||||
'custom_fields' => self::getProjectCustomFieldDefs(),
|
||||
'project_name' => $project->fields['name'],
|
||||
'project_description' => $description,
|
||||
'project_identifier' => 'glpi-proj-' . $projects_id,
|
||||
|
|
@ -107,6 +108,35 @@ class PluginRedmineProject extends CommonDBTM {
|
|||
return $opts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Project custom field definitions for the create form (from metadata cache).
|
||||
* Some Redmine setups make these mandatory on project creation.
|
||||
* @return array of ['id','name','field_format','is_required','options'=>{value: value}]
|
||||
*/
|
||||
static function getProjectCustomFieldDefs() {
|
||||
global $DB;
|
||||
$it = $DB->request(['FROM' => 'glpi_plugin_redmine_configs', 'WHERE' => ['id' => 1]]);
|
||||
$config = count($it) > 0 ? $it->current() : [];
|
||||
$metadata = !empty($config['metadata_cache']) ? json_decode($config['metadata_cache'], true) : [];
|
||||
$defs = [];
|
||||
foreach (($metadata['project_custom_fields'] ?? []) as $f) {
|
||||
$options = [];
|
||||
foreach (($f['possible_values'] ?? []) as $pv) {
|
||||
if (isset($pv['value'])) {
|
||||
$options[$pv['value']] = $pv['value'];
|
||||
}
|
||||
}
|
||||
$defs[] = [
|
||||
'id' => (int) $f['id'],
|
||||
'name' => $f['name'],
|
||||
'field_format' => $f['field_format'] ?? 'string',
|
||||
'is_required' => (bool) ($f['is_required'] ?? false),
|
||||
'options' => $options,
|
||||
];
|
||||
}
|
||||
return $defs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Redmine project modules offered on creation.
|
||||
* @return array
|
||||
|
|
|
|||
|
|
@ -201,6 +201,10 @@ class PluginRedmineRedmineapi {
|
|||
* @return int|false Returns project ID on success
|
||||
*/
|
||||
public static function createProjectFull($projectData) {
|
||||
// Redmine identifiers must be lowercase (a-z, 0-9, dashes/underscores).
|
||||
if (isset($projectData['identifier'])) {
|
||||
$projectData['identifier'] = strtolower(preg_replace('/[^a-zA-Z0-9\-_]/', '-', $projectData['identifier']));
|
||||
}
|
||||
$payload = [
|
||||
'project' => $projectData
|
||||
];
|
||||
|
|
@ -296,6 +300,22 @@ class PluginRedmineRedmineapi {
|
|||
return ($result && isset($result['roles'])) ? $result['roles'] : [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the custom field definitions that apply to PROJECTS (admin only).
|
||||
* Needed because some Redmine setups make them mandatory on creation.
|
||||
* @return array
|
||||
*/
|
||||
public static function getProjectCustomFields() {
|
||||
$result = self::get("/custom_fields.json");
|
||||
$out = [];
|
||||
foreach (($result['custom_fields'] ?? []) as $f) {
|
||||
if (($f['customized_type'] ?? '') === 'project') {
|
||||
$out[] = $f;
|
||||
}
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure a Redmine user is a member of a project (idempotent).
|
||||
* Impersonation requires the user to be a project member with a role.
|
||||
|
|
@ -369,6 +389,7 @@ class PluginRedmineRedmineapi {
|
|||
'priorities' => self::getPriorities(),
|
||||
'activities' => self::getActivities(),
|
||||
'roles' => self::getRoles(),
|
||||
'project_custom_fields' => self::getProjectCustomFields(),
|
||||
];
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
define('PLUGIN_REDMINE_VERSION', '1.5.5');
|
||||
define('PLUGIN_REDMINE_VERSION', '1.5.6');
|
||||
|
||||
/**
|
||||
* Load the Mindplace License class if the autoloader has not run yet.
|
||||
|
|
|
|||
|
|
@ -58,6 +58,22 @@
|
|||
'values': ['issue_tracking', 'time_tracking'],
|
||||
'full_width': true
|
||||
}) }}
|
||||
|
||||
{# Campos personalizados de projeto do Redmine (alguns são obrigatórios na criação) #}
|
||||
{% for cf in custom_fields|default([]) %}
|
||||
{% if cf.field_format == 'list' and cf.options is not empty %}
|
||||
{{ fields.dropdownArrayField('custom_field_values[' ~ cf.id ~ ']', '', cf.options, cf.name, {
|
||||
'display_emptychoice': true,
|
||||
'full_width': true,
|
||||
'required': cf.is_required
|
||||
}) }}
|
||||
{% else %}
|
||||
{{ fields.textField('custom_field_values[' ~ cf.id ~ ']', '', cf.name, {
|
||||
'full_width': true,
|
||||
'required': cf.is_required
|
||||
}) }}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Reference in a new issue