Ir al contenido

Vacation request

El flujo de trabajo Vacation Request automatiza el ciclo de vida completo de una solicitud de vacaciones de un empleado: desde la selección inicial de fechas, pasando por uno o dos niveles de revisión por parte de la dirección, hasta la notificación final por correo del resultado.

Download: vacation-request.okmflow

Caminos de ejecución admitidos:

  • Aprobación directa por parte del responsable de primer nivel (pool ROLE_RRHH).
  • Escalado a un revisor de segundo nivel cuando el responsable lo considera necesario.
  • Solicitud de cambios en cualquiera de los dos niveles: devuelve la solicitud al empleado y, a continuación, la enruta de vuelta al mismo revisor que solicitó originalmente los cambios.
  • Denegación en cualquiera de los dos niveles, con notificación automática por correo al empleado.

Antes de desplegar este flujo de trabajo, lo siguiente debe existir en el directorio de usuarios y roles de OpenKM:

Requisito Descripción
Rol ROLE_RRHH Debe existir y tener al menos un usuario asignado. Todos los miembros de este rol forman el pool de revisores de primer nivel. La tarea se asigna al pool en la primera entrada; en las reentradas se reasigna directamente a quien la gestionó anteriormente.
Usuario rrhhSupervisor Debe existir en OpenKM. Es el usuario fijo asignado directamente a la tarea de aprobación de segundo nivel.

Se muestra al empleado antes de que se inicie el flujo de trabajo. Recopila las fechas del periodo de vacaciones. No está conectado al diagrama; el motor lo presenta automáticamente al iniciarse el flujo de trabajo.

Campo Tipo Obligatorio Notas
Start date Fecha Se almacena en el contexto como yyyyMMddHHmmss (por ejemplo, 20260601000000).
End date Fecha Se almacena en el contexto como yyyyMMddHHmmss.

Se ejecuta inmediatamente después de Start. Crea un elemento de formulario Text que contiene el nombre del empleado formateado como HTML, y lo almacena en el contexto para que los formularios de las tareas de aprobación puedan mostrarlo como encabezado dinámico.

import com.openkm.okmflow.bean.*;
Actor actor = context.get("initiator");
String userName = actor.getName();
Text userText = new Text();
userText.setName("user_text");
userText.setLabel("The user <span class=\"font-weight-bold\">" + userName + "</span> has requested vacations");
context.put("user_text", userText);

El objeto Text es después consumido por los formularios de aprobación:

<text label="" name="user_description" data="user_text" />

Cuando se establece data="user_text", el motor carga el objeto Text del contexto y renderiza su propiedad label como HTML dentro del formulario. Así es como los flujos de trabajo muestran contenido dinámico y específico de la persona en los formularios de tarea sin necesidad de valores fijos en el código.

Se asigna al pool de usuarios de ROLE_RRHH en la primera entrada. En la reentrada, tras la corrección de la solicitud por parte del empleado, se reasigna directamente a quien la gestionó previamente, saltándose el pool para que el mismo revisor continúe la revisión.

Reasignación inteligente mediante el historial de tareas:

import com.openkm.okmflow.util.*;
import com.openkm.okmflow.rest.dto.*;
import java.util.*;
def procIns = context.get("processInstance");
long piId = procIns.getId();
// Check if this task has been handled before in this process instance
List<TaskInstanceDTO> prevTasks = WorkflowUtils.getTaskInstances(piId, "Manager approval");
if (prevTasks != null && !prevTasks.isEmpty()) {
for (TaskInstanceDTO t : prevTasks) {
if (t.getActor() != null && !t.getActor().isEmpty()) {
return t.getActor(); // String → direct assignment (not a pool)
}
}
}
// First entry: assign to pool of ROLE_RRHH users
OKMWebservices ws = WebservicesHelper.getInstance();
List<String> users = new ArrayList();
for (CommonUser user : ws.auth.getUsersByRole("ROLE_RRHH")) {
users.add(user.getId());
}
return users; // List → pool assignment
Botón Transición Descripción
Approve approve Solicitud aprobada; continúa al formateo de fechas y la notificación por correo.
Request changes request_changes Devuelve la solicitud al empleado para su corrección.
Escalate escalate La reenvía al revisor de segundo nivel.
Deny deny Solicitud denegada. Requiere un diálogo de confirmación antes de ejecutarse.

Se activa cuando el responsable selecciona request_changes. Almacena la clave de enrutado y propaga el comentario del revisor a la tarea de corrección compartida.

// Store routing key so the Decision node can route back to Manager approval
Input sender = new Input();
sender.setValue("manager");
context.put("request_sender", sender);
// Propagate the comment — use def (no explicit cast) to handle any form element type
def managerComment = context.get("manager_comment");
context.put("reviewer_comment", managerComment);

Asignación: return "rrhhSupervisor";

Se alcanza cuando el responsable de primer nivel escala la solicitud. Muestra el comentario del responsable en modo de solo lectura para que el revisor de segundo nivel tenga el contexto completo antes de decidir.

Botón Transición Descripción
Approve approve Solicitud aprobada.
Request changes request_changes Devuelve la solicitud al empleado para su corrección.
Deny deny Solicitud denegada. Requiere un diálogo de confirmación.

Refleja la lógica de Set sender manager, pero escribe "second_level" como clave de enrutado y copia el comentario de segundo nivel en reviewer_comment.

Input sender = new Input();
sender.setValue("second_level");
context.put("request_sender", sender);
def secondComment = context.get("second_level_comment");
context.put("reviewer_comment", secondComment);

Submit vacation request - Task (tarea de corrección compartida)

Sección titulada «Submit vacation request - Task (tarea de corrección compartida)»

Asignación: return ((Actor) context.get("initiator")).getId();

Nodo único y compartido usado para las correcciones tanto del primer como del segundo nivel. El empleado puede ajustar las fechas y siempre ve el comentario de quien solicitó los cambios, independientemente del nivel de revisión.

<workflow-form>
<input label="Start date" name="start_date" type="date" timeFormat="none" data="start_date">
<validator type="req"/>
</input>
<input label="End date" name="end_date" type="date" timeFormat="none" data="end_date">
<validator type="req"/>
</input>
<textarea label="Reviewer comment" name="reviewer_comment" data="reviewer_comment" readonly="true"/>
<button name="btn_submit" label="Submit" transition="" color="primary" style="yes" validate="true"/>
</workflow-form>

El atributo data="reviewer_comment" carga el comentario que haya escrito el nodo Action precedente (Set sender manager o Set sender second level). Los dos nodos Action escriben objetos distintos en la misma clave de contexto, por lo que el formulario siempre muestra el comentario del revisor correspondiente sin necesidad de lógica de bifurcación.

Enruta la solicitud corregida de vuelta al revisor adecuado según la variable de contexto request_sender.

import com.openkm.bean.form.*;
def sender = context.get("request_sender");
return sender.getValue(); // returns "manager" or "second_level"
Transición Destino
manager Manager approval
second_level Second level approval

Format dates approved / Format dates denied - Actions

Sección titulada «Format dates approved / Format dates denied - Actions»

Se ejecutan dos nodos simétricos inmediatamente antes de cada nodo Mail. Convierten el formato de fecha en bruto (yyyyMMddHHmmss) a un formato legible para personas (dd/MM/yyyy) y almacenan los resultados como nuevos objetos Input para usarlos en la plantilla del cuerpo del correo.

import com.openkm.bean.form.*;
import java.text.*;
SimpleDateFormat raw = new SimpleDateFormat("yyyyMMddHHmmss");
SimpleDateFormat fmt = new SimpleDateFormat("dd/MM/yyyy");
Input startRaw = context.get("start_date");
Input endRaw = context.get("end_date");
Input startFmt = new Input();
startFmt.setValue(fmt.format(raw.parse(startRaw.getValue())));
context.put("start_date_fmt", startFmt);
Input endFmt = new Input();
endFmt.setValue(fmt.format(raw.parse(endRaw.getValue())));
context.put("end_date_fmt", endFmt);

El cuerpo del nodo Mail accede a estos valores usando el accesor FreeMarker .value: ${start_date_fmt.value}, ${end_date_fmt.value}.

Envía un correo de aprobación al iniciador del flujo de trabajo.

Campo Valor
Recipients ${initiator.email}
Subject Your vacation request has been approved
Body Incluye las fechas de inicio y fin formateadas: ${start_date_fmt.value}, ${end_date_fmt.value}.

Envía un correo de denegación al iniciador del flujo de trabajo. Compartido por las denegaciones tanto del primer como del segundo nivel.

Campo Valor
Recipients ${initiator.email}
Subject Your vacation request has been denied
Body Incluye las fechas formateadas y el motivo del revisor: ${reviewer_comment.value}.

Dos nodos End con nombre representan los dos resultados finales. Usar nombres distintos permite que los sistemas externos o los informes de auditoría distingan si una instancia de proceso terminó en aprobación o en denegación.

Patrón Dónde se aplica
Reasignación directa desde pool mediante historial de tareas WorkflowUtils.getTaskInstances(piId, taskName) busca el actor anterior; devolver un String (no una List) crea una reasignación directa.
Elemento de formulario Text como encabezado HTML dinámico El nodo Action crea new Text() con setLabel(html); el formulario de la tarea lo vincula con <text data="user_text"/>.
Tarea de corrección única compartida Submit vacation request se usa tras request_changes tanto del primer como del segundo nivel. La clave reviewer_comment se unifica mediante los dos nodos Action precedentes.
def para la copia neutra de tipo en el contexto def comment = context.get("manager_comment"); evita ClassCastException cuando el tipo exacto del elemento de formulario no es crítico.
Conversión de formato de fecha para el cuerpo de los correos Los campos date almacenan los valores como yyyyMMddHHmmss; analícelos y reformateelos a dd/MM/yyyy antes de usarlos en las plantillas de los nodos Mail.
Accesor ${element.value} del nodo Mail Sintaxis FreeMarker para leer el valor de un elemento de formulario dentro del cuerpo de un nodo Mail. Use ${key.value}, no .getValue().
Dos nodos End con nombre Approved y Denied: caminos de finalización distinguibles para los informes.