Expense report approval
Resumen
Sección titulada «Resumen»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.
Diagrama
Sección titulada «Diagrama»
Requisitos previos
Sección titulada «Requisitos previos»Antes de desplegar este flujo de trabajo, lo siguiente debe existir en OpenKM.
Base de datos
Sección titulada «Base de datos»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);Usuarios y roles
Sección titulada «Usuarios y roles»| 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. |
Grupo de metadatos
Sección titulada «Grupo de metadatos»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.
Estructura del repositorio
Sección titulada «Estructura del repositorio»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 expedienteFormato 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.
Referencia de nodos
Sección titulada «Referencia de nodos»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.
Create Expense Record - Action
Sección titulada «Create Expense Record - Action»Se ejecuta inmediatamente después de Start. Realiza toda la inicialización del repositorio:
- Lee e incrementa el contador de
APP_COUNTER(counter_key = 'expense_report'), construyeRECEIPTS-{year}-{NNNNN}. - Crea la carpeta del año
/okm:root/Expense Report Approval/{year}si no existe ya. - Crea el expediente de gasto dentro de la carpeta del año usando
ws.record.create(fldUuid, expenseNumber, "", 0). - Almacena
expenseNumber,expenseRecordPath,expenseRecordUuidy un descriptorUpload(receiptsUploadData, que apunta a la raíz del expediente) en el contexto del flujo de trabajo. - 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.
Expense Submission - Task
Sección titulada «Expense Submission - Task»Asignación: el iniciador del flujo de trabajo (initiator.getId()).
| Campo | Tipo | Obligatorio | Notas |
|---|---|---|---|
| Department | Select | Sí | IT, Finance, Operations, Sales, HR. Determina la asignación del responsable. |
| Expense Date | Fecha | Sí | — |
| Category | Select | Sí | Travel, Meals, Accommodation, Office Supplies, Other. |
| Amount | Texto (numérico, > 0) | Sí | — |
| Currency | Select | Sí | EUR, USD, GBP. |
| Description | Área de texto | Sí | — |
| 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. |
Save Expense Metadata - Action
Sección titulada «Save Expense Metadata - Action»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.
Manager Approval - Task
Sección titulada «Manager Approval - Task»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 Finance → Finance Validation. |
| Request Clarification | clarification |
Continúa a Set status - Clarification Requested → Mail - Clarification Requested → Employee Edit & Resubmit. |
| Reject | rejected |
Continúa a Set status - Rejected by manager → Mail - Final Rejection by manager → End by manager. Terminal. |
Set status - Pending Finance - Action
Sección titulada «Set status - Pending Finance - Action»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.
Finance Validation - Task
Sección titulada «Finance Validation - Task»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-assignEl 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 - Approved → Payment Processing. |
| Reject | rejected |
Continúa a Manager Review (no es un estado terminal; se enruta de vuelta al responsable que aprobó). |
Set status - Approved - Action
Sección titulada «Set status - Approved - Action»Actualiza okp:expense_report.status a approved y almacena financeComments, y a continuación enruta a Payment Processing.
Manager Review - Task
Sección titulada «Manager Review - Task»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 Requested → Mail - Clarification Requested → Employee Edit & Resubmit (el mismo camino compartido usado por Manager Approval). |
| Reject | rejected |
Continúa a Set status - Rejected by finance and manager → Mail - Final Rejection by finance and manager → End 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.
Mail - Clarification Requested - Mail
Sección titulada «Mail - Clarification Requested - Mail»| 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}). |
Employee Edit & Resubmit - Task
Sección titulada «Employee Edit & Resubmit - Task»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 Manager → Manager Approval. |
Set status - Pending Manager - Action
Sección titulada «Set status - Pending Manager - Action»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).
Payment Processing - Task
Sección titulada «Payment Processing - Task»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 - Paid → Mail - Payment Completed → End. |
Set status - Paid - Action
Sección titulada «Set status - Paid - Action»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.
Mail - Payment Completed - Mail
Sección titulada «Mail - Payment Completed - Mail»| 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.
Ciclo de vida de los metadatos
Sección titulada «Ciclo de vida de los metadatos»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_validationpending_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 → approvedapproved → paid (terminal)Patrones clave
Sección titulada «Patrones clave»| 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. |