Ir al contenido

Expense report approval

El flujo de trabajo Expense Report Approval automatiza el ciclo de vida completo de una solicitud de gastos de un empleado: desde el envío y la subida de recibos, pasando por la aprobación del responsable y la validación de finanzas, hasta el pago. Admite dos bucles de aclaración (responsable → empleado, y responsable → finanzas) y dos caminos de rechazo final independientes, de modo que un rechazo originado por el responsable y un rechazo originado durante la validación de finanzas se rastrean por separado.

Download: expense-report-approval.okmflow

Caminos de ejecución admitidos:

  • Aprobación directa: la solicitud pasa Manager Approval y Finance Validation sin incidencias y se paga el gasto.
  • Bucle de aclaración del empleado: el responsable (ya sea desde Manager Approval o desde Manager Review) devuelve la solicitud al empleado para su corrección. La solicitud puede volver a Manager Approval tantas veces como sea necesario.
  • Bucle Finance ↔ Manager: Finance Validation rechaza la solicitud; se enruta al mismo responsable que la aprobó originalmente (Manager Review) en lugar de finalizar el proceso. Desde ahí, el responsable puede solicitar aclaración al empleado, responder directamente a Finance o rechazar la solicitud definitivamente.
  • Rechazo en Manager Approval: el responsable rechaza la solicitud directamente; se notifica por correo al empleado y el proceso finaliza.
  • Rechazo en Manager Review (originado por Finance): el responsable rechaza la solicitud tras un rechazo de Finance; se notifica por correo al empleado y el proceso finaliza a través de una rama terminal independiente.

Antes de desplegar este flujo de trabajo, lo siguiente debe existir en OpenKM.

Se usa una tabla de contador personalizada para generar referencias de gasto secuenciales. Es genérica y se comparte con otros flujos de trabajo (por ejemplo, Purchase Order) mediante distintos valores de counter_key; cree la tabla una sola vez:

CREATE TABLE IF NOT EXISTS APP_COUNTER (
counter_key VARCHAR(50) NOT NULL,
counter_value INT NOT NULL DEFAULT 1,
CONSTRAINT pk_app_counter PRIMARY KEY (counter_key)
);
INSERT INTO APP_COUNTER (counter_key, counter_value)
VALUES ('expense_report', 1);
Identificador Tipo Usado en Descripción
ROLE_FINANCE Rol Finance Validation Pool de validadores de finanzas. Cualquier miembro puede autoasignarse la tarea la primera vez que se ejecuta. Debe haber al menos un usuario asignado.
itManager Usuario Manager Approval Asignado cuando el departamento solicitante es IT.
financeManager Usuario Manager Approval Asignado cuando el departamento solicitante es Finance.
operationsManager Usuario Manager Approval Asignado cuando el departamento solicitante es Operations.
salesManager Usuario Manager Approval Asignado cuando el departamento solicitante es Sales.
hrManager Usuario Manager Approval Asignado cuando el departamento solicitante es HR.
paymentManager Usuario Payment Processing Usuario único que procesa todos los pagos. Asignación directa y fija (no basada en rol/departamento).
(iniciador del flujo de trabajo) Expense Submission, Employee Edit & Resubmit El empleado que inició el flujo de trabajo; se resuelve dinámicamente mediante context.get("initiator"), no es un nombre de usuario fijo.

El grupo de propiedades okg:expense_report debe estar registrado en OpenKM antes de la primera ejecución. El propio flujo de trabajo lo aplica automáticamente al expediente de gasto; el usuario nunca lo rellena manualmente.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE property-groups PUBLIC "-//OpenKM//DTD Property Groups 3.12//EN"
"http://www.openkm.com/dtd/property-groups-3.12.dtd">
<property-groups>
<property-group label="Expense Report" name="okg:expense_report" readonly="true">
<input label="Expense Reference" name="okp:expense_report.expenseNumber" />
<input label="Employee" name="okp:expense_report.employee" />
<select label="Department" name="okp:expense_report.department" type="simple">
<option label="IT" value="it"/>
<option label="Finance" value="finance"/>
<option label="Operations" value="operations"/>
<option label="Sales" value="sales"/>
<option label="HR" value="hr"/>
</select>
<input label="Expense Date" name="okp:expense_report.expenseDate" type="date" />
<select label="Category" name="okp:expense_report.category" type="simple">
<option label="Travel" value="travel"/>
<option label="Meals" value="meals"/>
<option label="Accommodation" value="accommodation"/>
<option label="Office Supplies" value="office_supplies"/>
<option label="Other" value="other"/>
</select>
<input label="Amount" name="okp:expense_report.amount" />
<select label="Currency" name="okp:expense_report.currency" type="simple">
<option label="EUR" value="EUR"/>
<option label="USD" value="USD"/>
<option label="GBP" value="GBP"/>
</select>
<textarea label="Description" name="okp:expense_report.description" dbColumnSize="1024" />
<textarea label="Manager Comments" name="okp:expense_report.managerComments" dbColumnSize="1024" />
<textarea label="Finance Comments" name="okp:expense_report.financeComments" dbColumnSize="1024" />
<input label="Payment Date" name="okp:expense_report.paymentDate" type="date" />
<input label="Payment Reference" name="okp:expense_report.paymentReference" />
<select label="Status" name="okp:expense_report.status" type="simple">
<option label="Pending Manager Approval" value="pending_manager_approval"/>
<option label="Clarification Requested" value="clarification_requested"/>
<option label="Pending Finance Validation" value="pending_finance_validation"/>
<option label="Rejected - Manager" value="rejected_manager"/>
<option label="Rejected - Finance" value="rejected_finance"/>
<option label="Rejected - Finance and Manager" value="rejected_by_finance_and_manager"/>
<option label="Approved" value="approved"/>
<option label="Paid" value="paid"/>
</select>
</property-group>
</property-groups>

El grupo es readonly="true"; todas las escrituras se realizan de forma programática desde nodos Action del flujo de trabajo.

La acción Create Expense Record (el primer nodo tras Start) construye la siguiente estructura en el repositorio de OpenKM cada vez que se inicia una nueva instancia del flujo de trabajo:

/okm:root/
--- Expense Report Approval/
--- {year}/ - Folder (type: folder)
--- RECEIPTS-{year}-{NNNNN}/ - Record (type: record) - the main expediente

Formato de la referencia: RECEIPTS-{year}-{NNNNN}, donde NNNNN es un número secuencial de 5 dígitos con ceros a la izquierda (por ejemplo, RECEIPTS-2026-00001). El contador se lee de APP_COUNTER.counter_key = 'expense_report', se incrementa y se guarda de nuevo de forma atómica al iniciar el flujo de trabajo.

La instancia del proceso de flujo de trabajo se vincula a este expediente mediante WorkflowUtils.setProcessInstanceNode(context, recordUuid), de modo que el flujo de trabajo es accesible directamente desde el explorador de documentos.

Inicia el flujo de trabajo. No se presenta ningún formulario al usuario en este punto; el proceso se inicia inmediatamente y pasa a la acción Create Expense Record.

Se ejecuta inmediatamente después de Start. Realiza toda la inicialización del repositorio:

  1. Lee e incrementa el contador de APP_COUNTER (counter_key = 'expense_report'), construye RECEIPTS-{year}-{NNNNN}.
  2. Crea la carpeta del año /okm:root/Expense Report Approval/{year} si no existe ya.
  3. Crea el expediente de gasto dentro de la carpeta del año usando ws.record.create(fldUuid, expenseNumber, "", 0).
  4. Almacena expenseNumber, expenseRecordPath, expenseRecordUuid y un descriptor Upload (receiptsUploadData, que apunta a la raíz del expediente) en el contexto del flujo de trabajo.
  5. Vincula la instancia del proceso al expediente mediante WorkflowUtils.setProcessInstanceNode(context, recordUuid).

Todavía no se escribe ningún metadato; el expediente no tiene grupo de metadatos en este punto.

Asignación: el iniciador del flujo de trabajo (initiator.getId()).

Campo Tipo Obligatorio Notas
Department Select IT, Finance, Operations, Sales, HR. Determina la asignación del responsable.
Expense Date Fecha
Category Select Travel, Meals, Accommodation, Office Supplies, Other.
Amount Texto (numérico, > 0)
Currency Select EUR, USD, GBP.
Description Área de texto
Receipts Upload (create, múltiple) No Se sube directamente a la raíz del expediente.
Botón Transición Descripción
Submit submit Continúa a Save Expense Metadata.

Se ejecuta inmediatamente después de Expense Submission. Lee todos los valores del formulario del contexto, resuelve el nombre para mostrar del empleado a partir de initiator.getName(), y escribe la primera versión de okg:expense_report (usando addGroup, ya que el grupo todavía no existe). Establece status = pending_manager_approval.

Lógica de asignación (basada en el departamento):

Departamento Usuario asignado
IT itManager
Finance financeManager
Operations operationsManager
Sales salesManager
HR hrManager

El formulario muestra el gasto completo en modo de solo lectura, un enlace en el que se puede hacer clic al expediente de gasto (type="folder"), y un único campo de texto libre Manager Comments usado en todos los resultados (nota de aprobación, motivo de aclaración o motivo de rechazo).

Botón Transición Descripción
Approve approved Continúa a Set status - Pending FinanceFinance Validation.
Request Clarification clarification Continúa a Set status - Clarification RequestedMail - Clarification RequestedEmployee Edit & Resubmit.
Reject rejected Continúa a Set status - Rejected by managerMail - Final Rejection by managerEnd by manager. Terminal.

Actualiza okp:expense_report.status a pending_finance_validation y almacena la nota de aprobación del responsable (managerComments), y a continuación enruta a Finance Validation.

Lógica de asignación (reasignación de pool a directa mediante historial de tareas):

1. Query task history: WorkflowUtils.getTaskInstances(piId, "Finance Validation")
2. If a previous instance with an assigned actor exists → return that actor (String, direct assignment)
(ensures the same finance user handles re-entry after the "Reply to Finance" loop)
3. If first time → return the pool of all ROLE_FINANCE users (List<String>), any of whom must self-assign

El formulario muestra el gasto en modo de solo lectura, la respuesta del responsable si se trata de una reentrada (precargada desde managerComments), y un campo editable Finance Comments.

Botón Transición Descripción
Approve approved Continúa a Set status - ApprovedPayment Processing.
Reject rejected Continúa a Manager Review (no es un estado terminal; se enruta de vuelta al responsable que aprobó).

Actualiza okp:expense_report.status a approved y almacena financeComments, y a continuación enruta a Payment Processing.

Se alcanza únicamente cuando Finance Validation rechaza la solicitud. No es una nueva asignación basada en departamento: siempre se enruta a la misma persona que completó Manager Approval, mediante una búsqueda en el historial de tareas (véase Patrones clave), porque ese responsable ya validó la solicitud una vez y es quien mejor puede juzgar si la objeción de Finance necesita la intervención del empleado o puede resolverse directamente.

El formulario muestra el gasto en modo de solo lectura, el motivo de rechazo de Finance (solo lectura) y un campo editable Manager Comments.

Botón Transición Descripción
Request Employee Clarification clarification Continúa a Set status - Clarification RequestedMail - Clarification RequestedEmployee Edit & Resubmit (el mismo camino compartido usado por Manager Approval).
Reject rejected Continúa a Set status - Rejected by finance and managerMail - Final Rejection by finance and managerEnd by finance and manager. Terminal, independiente de un rechazo directo de Manager Approval.
Reply to Finance replyFinance Continúa a Set status - Pending Finance (Manager Reply) → de vuelta a Finance Validation (reasignado al mismo usuario de finanzas).

Set status - Clarification Requested - Action

Sección titulada «Set status - Clarification Requested - Action»

Compartido tanto por Manager Approval como por Manager Review. Actualiza okp:expense_report.status a clarification_requested y almacena managerComments, y a continuación enruta a Mail - Clarification Requested. Centralizar esto en un único nodo significa que la lógica de escritura de metadatos de “solicitud de aclaración” solo existe una vez, independientemente de qué tarea la haya activado.

Campo Valor
Recipients ${initiator.email}
Subject Clarification requested for expense report {expenseNumber}
Body Incluye la referencia del gasto y los comentarios del responsable (${managerComments.value}).

Asignación: el iniciador del flujo de trabajo (initiator.getId()).

Muestra la solicitud de aclaración del responsable (solo lectura) y todos los campos de Expense Submission, precargados y editables (mismos nombres de campo que el envío original, de modo que los valores que el empleado reenvía sobrescriben los originales en el contexto). También permite subir recibos adicionales al mismo expediente (los ficheros subidos previamente se conservan).

Botón Transición Descripción
Resubmit resubmit Continúa a Set status - Pending ManagerManager Approval.

Vuelve a leer los campos del formulario (posiblemente editados) del contexto y reescribe el conjunto completo de metadatos (departamento, fecha del gasto, categoría, importe, moneda, descripción), además de status = pending_manager_approval. Esto es lo que hace que el bucle de reenvío realmente persista las ediciones del empleado en el expediente, y no solo en el contexto en memoria del flujo de trabajo.

Set status - Pending Finance (Manager Reply) - Action

Sección titulada «Set status - Pending Finance (Manager Reply) - Action»

Actualiza okp:expense_report.status a pending_finance_validation y almacena la respuesta del responsable (managerComments), y a continuación enruta de vuelta a Finance Validation (que reasigna al mismo usuario de finanzas mediante el historial de tareas).

Asignación: directa, fija a un único usuario, paymentManager.

Muestra el gasto completo en modo de solo lectura, con “Pay to (Employee)” como primer campo para que quien realiza el pago conozca inmediatamente al destinatario, además de los campos editables Payment Date y Payment Reference.

Botón Transición Descripción
Mark as Paid paid Continúa a Set status - PaidMail - Payment Completed → End.

Actualiza okp:expense_report.status a paid, y almacena paymentDate (en bruto, yyyyMMddHHmmss) y paymentReference. Por separado, formatea paymentDate a yyyy-MM-dd y lo almacena en el contexto como paymentDate_fmt, exclusivamente para su visualización en el correo de notificación; la propiedad de metadatos almacenada siempre mantiene el formato en bruto.

Campo Valor
Recipients ${initiator.email}
Subject Your expense report {expenseNumber} has been paid
Body Fecha de pago (${paymentDate_fmt.value}) y referencia de pago (${paymentReference.value}).

Set status - Rejected by manager / Mail - Final Rejection by manager / End by manager

Sección titulada «Set status - Rejected by manager / Mail - Final Rejection by manager / End by manager»

Rama terminal de rechazo directo desde Manager Approval. Actualiza okp:expense_report.status a rejected_manager y almacena managerComments, envía un correo al empleado y finaliza el proceso.

Set status - Rejected by finance and manager / Mail - Final Rejection by finance and manager / End by finance and manager

Sección titulada «Set status - Rejected by finance and manager / Mail - Final Rejection by finance and manager / End by finance and manager»

Rama terminal desde Manager Review (es decir, un rechazo que se originó en Finance Validation). Se mantiene como una rama independiente de la anterior para que los dos orígenes de rechazo puedan distinguirse e informarse por separado.

El campo okp:expense_report.status se actualiza automáticamente en cada transición de etapa. El ciclo de vida completo es:

pending_manager_approval → clarification_requested → pending_manager_approval (loop, repeatable)
pending_manager_approval → rejected_manager (terminal)
pending_manager_approval → pending_finance_validation
pending_finance_validation → pending_finance_validation (via Manager Review → Reply to Finance loop)
pending_finance_validation → clarification_requested → pending_manager_approval (via Manager Review)
pending_finance_validation → rejected_by_finance_and_manager (terminal, via Manager Review)
pending_finance_validation → approved
approved → paid (terminal)
Patrón Dónde se aplica
Tabla de contador para la numeración de referencias APP_COUNTER con counter_key = 'expense_report'. Tabla compartida, reutilizable entre flujos de trabajo.
Sin subcarpeta de subida cuando todo es un único tipo de documento A diferencia de Purchase Order (subcarpeta others), los recibos se almacenan directamente en la raíz del expediente; más sencillo cuando no es necesario separar categorías de documentos.
Formato de fecha en bruto para las propiedades de metadatos Los campos de fecha deben escribirse en los grupos de propiedades okg:* exactamente como se almacenan en el contexto (yyyyMMddHHmmss). Reformatear a yyyy-MM-dd antes de setProperties/addGroup lanza un error de validación. Formatear a yyyy-MM-dd solo es válido para valores colocados en el contexto para su visualización en un nodo Mail (por ejemplo, paymentDate_fmt), nunca para la propiedad en sí.
Reasignación directa desde pool mediante historial de tareas WorkflowUtils.getTaskInstances(piId, "Finance Validation"): se usa en Finance Validation para su propio bucle de reentrada.
Reasignación entre tareas mediante historial de tareas WorkflowUtils.getTaskInstances(piId, "Manager Approval") en Manager Review: siempre enruta de vuelta al mismo responsable que gestionó la aprobación original, independientemente de la asignación por departamento.
Acción única compartida de “establecer estado” para varios puntos de entrada Set status - Clarification Requested es el destino de una transición clarification tanto desde Manager Approval como desde Manager Review; evita duplicar la lógica de escritura de metadatos.
Asignación de responsable basada en el departamento El assignExpr de Manager Approval asigna cada valor de departamento a un nombre de usuario fijo.
Asignación fija a un único usuario Payment Processing usa return "paymentManager";; sin lógica de rol ni de departamento, siempre la misma persona.
Ramas terminales independientes para el mismo resultado conceptual El rechazo desde Manager Approval y el rechazo desde Manager Review usan tríos independientes de nodos Action/Mail/End, de modo que los dos orígenes siguen siendo distinguibles por el nodo End alcanzado, aunque (en el momento de escribir esto) el valor de estado escrito todavía no sea distinto; véase la nota bajo la rama de rechazo de Manager Review anterior.
type="folder" para referencias a nodo Enlace de solo lectura, en el que se puede hacer clic, al expediente de gasto, usado en todos los formularios de tarea de tipo revisión.
Reenvío con todos los campos editables Employee Edit & Resubmit reutiliza exactamente los mismos nombres de campo que Expense Submission con data="..." apuntando a las mismas claves de contexto, de modo que el formulario se precarga y los valores reenviados sobrescriben de forma natural los originales al enviarlo.
Metadatos escritos por nodos Action, no por usuarios okg:expense_report es readonly="true". Todas las escrituras usan ws.propertyGroup.setProperties() / addGroup() desde nodos Action.
Flujo de trabajo vinculado a un expediente del repositorio WorkflowUtils.setProcessInstanceNode(context, recordUuid) en Create Expense Record vincula la instancia de proceso en ejecución al expediente de gasto, haciendo que el flujo de trabajo sea accesible directamente desde el explorador de documentos.