Ir al contenido

Purchase order

El flujo de trabajo Purchase Order automatiza el ciclo de vida completo de una solicitud de compra: desde el envío de la solicitud inicial, pasando por la validación del presupuesto y la aprobación de la dirección, hasta la emisión formal del pedido de compra y la notificación por correo del resultado.

Download: purchase-order.okmflow

Caminos de ejecución admitidos:

  • Aprobación directa: la solicitud pasa Budget Validation y Manager Approval sin incidencias y se emite un pedido de compra.
  • Bucle de aclaración: el validador de presupuesto devuelve la solicitud al solicitante para que la corrija. La solicitud puede volver a Budget Validation tantas veces como sea necesario.
  • Rechazo en Budget Validation: el validador de presupuesto rechaza la solicitud; se notifica por correo al solicitante y el proceso finaliza.
  • Rechazo en Manager Approval: el responsable rechaza la solicitud; se notifica por correo al solicitante y el proceso finaliza.

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

Debe crearse una tabla de contador personalizada en la base de datos de OpenKM antes de la primera ejecución del flujo de trabajo:

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 ('purchase_order', 1);

La tabla es genérica y puede reutilizarse en otros flujos de trabajo añadiendo nuevas filas con distintos valores de counter_key.

Identificador Tipo Usado en Descripción
ROLE_FINANCE Rol Budget Validation Los miembros de este rol forman el grupo (pool) de validadores de presupuesto para solicitudes inferiores a 3.000 €. Debe tener al menos un usuario asignado.
financeManager Usuario Budget Validation Asignado directamente a las tareas de validación de presupuesto cuando el importe estimado es ≥ 3.000 €.
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.

El grupo de propiedades okg:purchase_order debe estar registrado en OpenKM antes de la primera ejecución. El propio flujo de trabajo lo aplica automáticamente al expediente de PO; 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="Purchase Order Request" name="okg:purchase_order" readonly="true">
<input label="PO Reference" name="okp:purchase_order.poNumber" />
<input label="Request Title" name="okp:purchase_order.requestTitle" />
<select label="Department" name="okp:purchase_order.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="Request Date" name="okp:purchase_order.requestDate" type="date" />
<input label="Estimated Amount" name="okp:purchase_order.estimatedAmount" />
<select label="Currency" name="okp:purchase_order.currency" type="simple">
<option label="EUR" value="EUR"/>
<option label="USD" value="USD"/>
<option label="GBP" value="GBP"/>
</select>
<input label="Actual Amount" name="okp:purchase_order.actualAmount" />
<select label="Priority" name="okp:purchase_order.priority" type="simple">
<option label="Low" value="low"/>
<option label="Medium" value="medium"/>
<option label="High" value="high"/>
<option label="Urgent" value="urgent"/>
</select>
<input label="Required By Date" name="okp:purchase_order.requiredByDate" type="date" />
<input label="Vendor / Supplier" name="okp:purchase_order.vendor" />
<textarea label="Justification" name="okp:purchase_order.description" dbColumnSize="1024" />
<input label="Official PO Number" name="okp:purchase_order.poOfficialNumber" />
<input label="Expected Delivery" name="okp:purchase_order.deliveryDate" type="date" />
<select label="Status" name="okp:purchase_order.status" type="simple">
<option label="Pending Budget Validation" value="pending_budget"/>
<option label="Clarification Required" value="clarification"/>
<option label="Pending Manager Approval" value="pending_approval"/>
<option label="Rejected - Budget" value="rejected_budget"/>
<option label="Rejected - Manager" value="rejected_manager"/>
<option label="Approved" value="approved"/>
<option label="Issued" value="issued"/>
</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 PO folder (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/
- Purchase Order Request/
- {year}/ - Folder (type: folder)
- PO-{year}-{NNN}/ - Record (type: record) - the main expediente
- others/ - Folder (type: folder) - for non-budget documents

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

El expediente de PO (PO-{year}-{NNN}) es el contenedor principal del pedido de compra. 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.

Destinos de los documentos dentro del expediente:

Campo de subida Destino
Budget Document (proforma, presupuesto) Raíz del expediente de PO (/PO-{year}-{NNN}/)
Other Supporting Documents Subcarpeta others (/PO-{year}-{NNN}/others/)

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 PO folder.

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

  1. Lee el valor actual del contador en APP_COUNTER (counter_key = 'purchase_order'), construye la cadena de referencia de PO (PO-{year}-{NNN}) e incrementa el contador.
  2. Crea la carpeta del año /okm:root/Purchase Order Request/{year} si no existe ya (tipo folder).
  3. Crea el expediente de PO PO-{year}-{NNN} dentro de la carpeta del año usando ws.record.create(fldUuid, poNumber, "", 0) (tipo record).
  4. Crea la subcarpeta others dentro del expediente usando ws.folder.create(recordUuid, "others").
  5. Almacena los siguientes objetos en el contexto del flujo de trabajo para que los usen los siguientes nodos.
Clave de contexto Tipo Valor
poNumber Input Cadena de referencia de PO, por ejemplo PO-2026-001.
poRecordPath String Ruta completa del repositorio al expediente de PO.
poRecordUuid String UUID del expediente de PO.
poOthersFolderUuid String UUID de la subcarpeta others.
budgetDocumentData Upload Descriptor de subida que apunta a la raíz del expediente de PO (para el documento de presupuesto).
otherDocumentsData Upload Descriptor de subida que apunta a la subcarpeta others.

Por último, vincula la instancia del proceso al expediente de PO mediante WorkflowUtils.setProcessInstanceNode(context, recordUuid).

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

El solicitante rellena los detalles de la solicitud de compra y sube los documentos justificativos.

Campo Tipo Obligatorio Notas
PO Reference Texto (solo lectura) Rellenado automáticamente desde el contexto (poNumber).
Request Title Texto Breve descripción de lo que se va a comprar.
Department Select IT, Finance, Operations, Sales, HR. Determina la asignación del responsable en un paso posterior.
Estimated Amount Número Se usa para determinar la regla de asignación del validador de presupuesto (véase Budget Validation).
Currency Select EUR, USD, GBP.
Priority Select Low, Medium, High, Urgent.
Required By Date Fecha Fecha objetivo para completar la compra.
Vendor / Supplier Texto No Proveedor preidentificado, si se conoce.
Justification / Description Área de texto Justificación de negocio de la compra.
Budget Document Upload (create) El documento financiero principal (proforma, presupuesto). Se sube a la raíz del expediente de PO.
Other Supporting Documents Upload (create, múltiple) No Cualquier documento adicional (especificaciones técnicas, comparativas, etc.). Se sube a la subcarpeta others.
Botón Transición Descripción
Submit Request submit Guarda el formulario y continúa a Save metadata.

Se ejecuta inmediatamente después de Request Submission. Lee todos los valores del formulario del contexto y los escribe como propiedades de metadatos en el expediente de PO (grupo okg:purchase_order).

Comportamiento destacable:

  • requestDate se captura automáticamente usando ISO8601.formatBasic(Calendar.getInstance()); refleja el momento en que el solicitante envió el formulario, no cuando se inició el flujo de trabajo. Este campo está pensado para informes.
  • status se establece a pending_budget.
  • Tras escribir los metadatos, la acción obtiene el UUID del documento de presupuesto llamando a ws.document.getChildren(recordUuid).get(0).getUuid() (el expediente contiene exactamente un documento en este punto: el fichero de presupuesto subido en la tarea anterior). Este UUID se almacena como budgetDocumentUuid en el contexto.
  • El UUID de la carpeta others se envuelve como un objeto Input y se almacena como othersFolderUuid en el contexto.

Tanto budgetDocumentUuid como othersFolderUuid se usan como campos de solo lectura type="folder" en las tareas Budget Validation, Clarification Request, Manager Approval y Purchase Order, dando a cada revisor acceso directo a los documentos desde el formulario de la tarea.

Lógica de asignación (reasignación inteligente):

1. Query task history: WorkflowUtils.getTaskInstances(piId, "Budget Validation")
2. If a previous instance with an assigned actor exists → return that actor (String)
(ensures the same person handles the re-entry after a Clarification loop)
3. If first time:
- estimatedAmount < 3000 → return pool of ROLE_FINANCE users (List<String>)
- estimatedAmount >= 3000 → return "financeManager" (String)

El umbral de < 3.000 enruta la tarea a cualquier miembro disponible de ROLE_FINANCE como grupo (pool); cualquier miembro debe autoasignársela. Para importes iguales o superiores a 3.000 €, va directamente a financeManager. Para modificar el umbral o añadir tramos adicionales, actualice el assignExpr de este nodo.

El formulario muestra todos los detalles de la solicitud en modo de solo lectura, además de dos campos de referencia a nodo:

Campo Tipo Descripción
Budget Document type="folder" (solo lectura) Enlace en el que se puede hacer clic al documento de presupuesto subido (proforma/presupuesto).
Other Documents Folder type="folder" (solo lectura) Enlace en el que se puede hacer clic a la subcarpeta others dentro del expediente de PO.
Budget Reviewer Comments Área de texto Obligatorio. La justificación de la decisión del validador.
Botón Transición Descripción
Approve approved Continúa a Set status - Pending ApprovalManager Approval.
Needs Clarification needs_clarification Continúa a Set status - ClarificationClarification Request.
Reject rejected Continúa directamente a Mail - Rejected by Budget → End. Requiere confirmación.

Actualiza okp:purchase_order.status a clarification en el expediente de PO.

También prepara el objeto Upload updateBudgetDocument para la tarea Clarification Request:

Input budgetDocumentUuidInput = (Input) context.get("budgetDocumentUuid");
Upload updateBudgetUpload = new Upload();
updateBudgetUpload.setType("update");
updateBudgetUpload.setDocumentUuid(budgetDocumentUuidInput.getValue());
context.put("updateBudgetDocument", updateBudgetUpload);

Esto permite al solicitante subir una nueva versión del documento de presupuesto (reemplazando el original, sin crear un duplicado) directamente desde el formulario Clarification Request.

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

El solicitante ve los comentarios del revisor y puede corregir cualquier aspecto de la solicitud antes de reenviarla. Todos los campos de la solicitud son editables y se rellenan previamente desde el contexto.

Funciones destacables de esta tarea:

  • Replace Budget Document (type="update") — sube una nueva versión del documento de presupuesto enviado en Request Submission, reemplazándolo en el mismo lugar del repositorio. El UUID del documento es el mismo; solo cambia el contenido (se crea una nueva versión).
  • Other Supporting Documents (type="create", múltiple) — sube ficheros adicionales a la subcarpeta others. Los nuevos ficheros se añaden junto a los subidos previamente; no se elimina nada.
  • El campo Budget Reviewer Comments se muestra en modo de solo lectura para que el solicitante vea exactamente qué debe corregirse.
Botón Transición Descripción
Resubmit for Validation resubmit Continúa a Set status - Pending BudgetBudget Validation.

Actualiza okp:purchase_order.status a pending_budget en el expediente de PO y, a continuación, enruta de vuelta a Budget Validation.

Actualiza okp:purchase_order.status a pending_approval en el expediente de PO y enruta a Manager Approval.

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

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

Para añadir un departamento o cambiar su responsable, actualice el assignExpr de este nodo.

El formulario muestra la solicitud completa en modo de solo lectura, incluyendo los comentarios del validador de presupuesto y los dos campos de referencia a documento (budgetDocumentUuid, othersFolderUuid).

Botón Transición Descripción
Approve approved Continúa a Set status - ApprovedPurchase Order.
Reject rejected Continúa a Mail - Rejected by Manager → End. Requiere confirmación.

Actualiza okp:purchase_order.status a approved en el expediente de PO y enruta a Purchase Order.

Asignación: el mismo usuario que gestionó la tarea Budget Validation, obtenido a través del historial de tareas:

WorkflowUtils.getTaskInstances(piId, "Budget Validation")
→ return t.getActor(); // String → direct assignment

Esto significa que el miembro del equipo financiero que validó el presupuesto también formaliza el pedido de compra. Si no se encuentra un actor previo (no debería ocurrir en el flujo normal), la tarea recae en okmAdmin como valor por defecto.

El formulario muestra el resumen completo de la solicitud en modo de solo lectura (incluyendo los comentarios del responsable y las referencias a documentos), seguido de campos editables para los detalles del pedido de compra:

Campo Tipo Obligatorio Notas
PO Number (official) Texto El número de pedido de compra oficial asignado por el departamento de compras o el sistema ERP.
Actual Amount Número El importe final confirmado (puede diferir de la estimación).
Vendor Confirmed Texto Nombre del proveedor finalmente confirmado.
Expected Delivery Date Fecha Se almacena como yyyyMMddHHmmss; se formatea a yyyy-MM-dd antes del correo de notificación.
PO Notes Área de texto No Cualquier nota o condición adicional.
Botón Transición Descripción
Issue Purchase Order issued Continúa a Set status - IssuedMail - PO Issued → End.

Actualiza okp:purchase_order.status a issued en el expediente de PO.

También convierte el valor del campo deliveryDate (almacenado internamente como yyyyMMddHHmmss) al formato yyyy-MM-dd para el correo de notificación:

SimpleDateFormat raw = new SimpleDateFormat("yyyyMMddHHmmss");
SimpleDateFormat fmt = new SimpleDateFormat("yyyy-MM-dd");
Input deliveryDateRaw = (Input) context.get("deliveryDate");
Input deliveryDateFmt = new Input();
deliveryDateFmt.setValue(fmt.format(raw.parse(deliveryDateRaw.getValue())));
context.put("deliveryDate_fmt", deliveryDateFmt);

El nodo Mail accede al valor formateado mediante ${deliveryDate_fmt.value}.

Se envía cuando el validador de presupuesto hace clic en Reject.

Campo Valor
Recipients ${initiator.email}
Subject Purchase Order {poNumber} - Rejected at Budget Validation
Body Incluye la referencia de PO y los comentarios del revisor (${budgetComments.value}).

Se envía cuando el responsable hace clic en Reject.

Campo Valor
Recipients ${initiator.email}
Subject Purchase Order {poNumber} - Rejected by Manager
Body Incluye la referencia de PO y los comentarios del responsable (${managerComments.value}).

Se envía cuando el equipo de compras emite el pedido de compra.

Campo Valor
Recipients ${initiator.email}
Subject Purchase Order {poNumber} - Approved and Issued
Body Incluye el número de PO oficial, el proveedor confirmado y la fecha de entrega prevista en formato yyyy-MM-dd (${deliveryDate_fmt.value}).

Punto de convergencia único para todos los caminos terminales (aprobado y emitido, rechazado en presupuesto, rechazado por el responsable).

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

pending_budget → clarification → pending_budget (loop, repeatable)
pending_budget → rejected_budget (terminal)
pending_budget → pending_approval
pending_approval → rejected_manager (terminal)
pending_approval → approved
approved → issued (terminal)

El campo requestDate se establece una única vez en Save metadata y nunca vuelve a actualizarse. Refleja el momento en que el solicitante envió el formulario, lo que lo hace fiable para informes.

Patrón Dónde se aplica
Tabla de contador para la numeración de PO Tabla APP_COUNTER con counter_key = 'purchase_order'. Leer → formatear → incrementar → escribir en una única acción al iniciar el flujo de trabajo. Genérico: se pueden añadir filas para otros flujos de trabajo sin cambios de esquema.
Nomenclatura de claves de contexto para evitar sobrescrituras Los descriptores Upload (budgetDocumentData, otherDocumentsData) usan nombres distintos a los nombres de los campos del formulario (budgetDocument, otherDocuments). Esto evita que el motor sobrescriba el objeto Upload con el objeto de elemento de formulario al enviar la tarea.
Reasignación directa desde pool mediante historial de tareas WorkflowUtils.getTaskInstances(piId, "Budget Validation") busca el actor anterior; devolver un String (no una List) crea una reasignación directa. Se usa tanto en Budget Validation (reentrada tras una aclaración) como en Purchase Order (asignar a la misma persona que validó el presupuesto).
Tramo de asignación basado en importe estimatedAmount < 3000 → pool ROLE_FINANCE; >= 3000financeManager directo. El umbral está codificado directamente en el assignExpr de Budget Validation.
Asignación de responsable basada en el departamento El assignExpr de Manager Approval asigna cada valor de departamento a un nombre de usuario fijo. Añada o cambie asignaciones ahí sin tocar el resto del flujo de trabajo.
Subida type="update" para el versionado de documentos En Clarification Request, el campo de subida del documento de presupuesto usa type="update" + documentUuid para crear una nueva versión del documento existente en lugar de subir un duplicado. El objeto Upload se prepara en Set status - Clarification usando setType("update") y setDocumentUuid(uuid).
type="folder" para referencias a nodo A pesar de su nombre, type="folder" se renderiza como un enlace del repositorio en el que se puede hacer clic para cualquier tipo de nodo (documento, carpeta, expediente). Se usa aquí para dar a los revisores acceso directo al documento de presupuesto y a la carpeta others desde los formularios de tarea.
Formateo de fechas antes de los nodos Mail Los campos date almacenan los valores como yyyyMMddHHmmss. Set status - Issued convierte deliveryDate a yyyy-MM-dd y lo almacena como deliveryDate_fmt. El nodo Mail accede a él mediante ${deliveryDate_fmt.value} (sintaxis FreeMarker).
Metadatos escritos por nodos Action, no por usuarios El grupo de propiedades okg:purchase_order es readonly="true". Todas las escrituras usan ws.propertyGroup.setProperties(uuid, "okg:purchase_order", properties) desde nodos Action.
Flujo de trabajo vinculado a un expediente del repositorio WorkflowUtils.setProcessInstanceNode(context, recordUuid) en Create PO folder vincula la instancia de proceso en ejecución al expediente de PO, haciendo que el flujo de trabajo sea accesible directamente desde el explorador de documentos.