UNPKG

tuain-bpm-lib

Version:

Servicio de gestión de manejo de procesos de la plataforma Tuain

340 lines (325 loc) 16.1 kB
const { collections: { execution: { processes: processColl, gateways: gatewayColl }, }, dbQueries: { execution: { process: processQueries }, }, // schemas: { execution: instanceSchemas }, process: { elements: { gateway: GATEWAY }, gatewayTypes: { exclusive: EXCLUSIVE, inclusive: INCLUSIVE, parallel: PARALLEL }, elementStatus: { active: ACTIVE, complete: COMPLETE }, gatewayOutputStatus: { selected: OUTPUT_SELECTED, discarded: OUTPUT_DISCARDED }, gatewayInputStatus: { complete: INPUT_COMPLETE, pending: INPUT_PENDING }, resolutionTypes: { expression: EXPRESSION, callback: CALLBACK }, emmiterTypes: { gateWayComplete: GATEWAYCOMPLETE }, }, } = require('../../../config'); const modErrs = { getGateway: { notExist: ['01', 'Tarea no encontrada'], }, newGatewayInstance: { missingDefinition: ['01', 'No existe definición para el gateway especificado'], saveFailure: ['02', 'Error creando instancia de gateway en base de datos'], }, }; async function getGatewayInstance(gatewayId) { const gatewayCol = this.roPool.collection(gatewayColl); const gatewayInstance = await gatewayCol.findOne(processQueries.findGateway({ id: gatewayId, status: ACTIVE })); if (!gatewayInstance) { const errorDetail = `Gateway ${gatewayId} del proceso ${this.name} no se encontró`; const errorObj = this.errMgr.get(modErrs.getGatewayInstance.notFound, errorDetail); return [errorObj, null]; } return [null, gatewayInstance]; } async function startNewGateway(procId, name, origin) { let message; const gatewayCol = this.roPool.collection(gatewayColl); const gatewayDef = this.processDefinition?.gateways?.[name]; message = `[BPM] Inicio gateway ${gatewayDef.type} ${name}/${procId} desde ${origin.name}`; this.logger.log({ level: 'silly', label: 'gatewayInstance', action: 'startNewGateway', message }); if (gatewayDef.type === PARALLEL) { let gatewayId; let gatewayInstance; if (gatewayDef.sources.length > 1) { message = `[BPM] Se valida múltiples origenes del gateway ${gatewayDef.type} ${name}/${procId} desde ${origin.name}`; this.logger.log({ level: 'silly', label: 'gatewayInstance', action: 'startNewGateway', message }); gatewayInstance = await gatewayCol.findOne(processQueries.findGateway({ procId, name, status: ACTIVE })); gatewayId = gatewayInstance?._id.toString() ?? null; } if (!gatewayId) { return this.createParallelGateway(procId, name, gatewayDef, origin); } const [updErr, updRes] = await this.updateParallelGateway({ gatewayId, ...gatewayInstance }, name, gatewayDef, origin); return [updErr, gatewayId]; } if (gatewayDef.type === EXCLUSIVE || gatewayDef.type === INCLUSIVE) { return this.createStandardGateway(procId, name, gatewayDef, origin); } return [null, null]; } async function createStandardGateway(procId, name, gatewayDef, origin) { const { destinations } = gatewayDef; let message = `[BPM] Creación gateway estandar ${name} para el proceso ${procId}`; this.logger.log({ level: 'silly', label: 'gatewayInstance', action: 'createStandardGateway', message }); const [nextErr, nextDestinations] = await this.resolveGatewayDestination(procId, name, gatewayDef); if (nextErr) { return [nextErr, null]; } message = `[BPM] Siguientes nodos gateway ${name}/${procId}: ${nextDestinations}`; this.logger.log({ level: 'silly', label: 'gatewayInstance', action: 'createStandardGateway', message }); const gatewayDestinations = {}; destinations.forEach((dest) => { gatewayDestinations[dest.name] = { creation: new Date(), type: dest.node?.type ?? null, status: nextDestinations.includes(dest.name) ? OUTPUT_SELECTED : OUTPUT_DISCARDED, }; }); const gatewayInstance = { ecosystem: this._ecosystem, procName: this.name, procId, name, sources: { [origin.name]: { ...origin, creation: new Date(), status: COMPLETE } }, destinations: gatewayDestinations, creation: new Date(), status: ACTIVE, }; message = `[BPM] Resultado gateway ${name}/${procId} ${JSON.stringify(gatewayDestinations, null, 2)}`; this.logger.log({ level: 'debug', label: 'gatewayInstance', action: 'createStandardGateway', message }); const gatewayCol = this.rwPool.collection(gatewayColl); const actionResult = await gatewayCol.insertOne(gatewayInstance); const gatewayId = actionResult?.insertedId.toString(); gatewayInstance.gatewayId = gatewayId; if (!gatewayId) { message = `[BPM] No fue posible la creación del gateway ${name}/${gatewayId} para el proceso ${this.name}:${procId}`; this.logger.log({ level: 'error', label: 'gatewayInstance', action: 'createStandardGateway', message }); const errorObj = this.errorObj.get(modErrs.newGatewayInstance.saveFailure); return [errorObj, null]; } for (let index = 0; index < nextDestinations.length; index++) { const nextName = nextDestinations[index]; const nodeDefinition = destinations.find((dest) => dest.name === nextName); message = `[BPM] Se dispara la transicion ${nextName} del gateway ${name}:${gatewayId}`; this.logger.log({ level: 'silly', label: 'gatewayInstance', action: 'createStandardGateway', message }); if (!nodeDefinition) { message = `[BPM] No se encontró nodo siguiente al gateway ${name}/${gatewayId} para el proceso ${this.name}:${procId}`; this.logger.log({ level: 'error', label: 'gatewayInstance', action: 'createStandardGateway', message }); const errorObj = this.errorObj.get(modErrs.newGatewayInstance.saveFailure); return [errorObj, null]; } const { node: { name: nextNodeName, type: nextNodeType }, } = nodeDefinition; this.goToNode(procId, nextNodeName, nextNodeType, GATEWAY, name, gatewayId); } this.updateGatewayInstance(gatewayId, { status: COMPLETE }); return [null, gatewayId]; } async function updateGatewayInstance(gatewayId, updateData) { const gatewayColRO = this.roPool.collection(gatewayColl); const gatewayColRW = this.rwPool.collection(gatewayColl); const gatewayInstance = await gatewayColRO.findOne(processQueries.findGateway(gatewayId)); if (!gatewayInstance) { const errorDetail = `Gateway ${gatewayId} del proceso ${this.name} no se encontró`; const errorObj = this.errMgr.get(modErrs.getGatewayInstance.notFound, errorDetail); return [errorObj, null]; } const actionResult = await gatewayColRW.updateOne(...processQueries.updateGateway(gatewayId, updateData)); const result = actionResult?.result?.nModified > 0; if (!result) { const message = `[BPM] No se pudo efectuar modificación de la instancia de gateway ${gatewayId}`; this.logger.log({ level: 'silly', label: 'gatewayInstance', action: 'updateTaskInstanceDB', message }); } return [null, { result }]; } async function updateGatewayNextNode(procId, gatewayId, nextNodeId, nextNodeType, nextNodeName) { let message = `[BPM] Se actualiza el nodo siguiente al gateway ${gatewayId} del proceso ${procId}`; this.logger.log({ level: 'silly', label: 'gatewayInstance', action: 'updateGatewayNextNode', message }); const updateData = { nextNodes: { [nextNodeName]: { id: nextNodeId, type: nextNodeType, }, }, }; return this.updateGatewayInstance(gatewayId, updateData); } async function createParallelGateway(procId, name, gatewayDef, origin) { let message = `[BPM] Se dispara la ejecución del gateway PARALLEL ${name} para la instacia ${procId}`; this.logger.log({ level: 'silly', label: 'gatewayInstance', action: 'startNewGateway', message }); const { sources, destinations } = gatewayDef; const [nextErr, nextDestinations] = await this.resolveGatewayDestination(procId, name, gatewayDef); const gatewaySources = {}; sources.forEach((src) => { const source = src.name === origin.name ? { ...origin, status: INPUT_COMPLETE, creation: new Date() } : { status: INPUT_PENDING, creation: new Date() }; gatewaySources[src.name] = source; }); const gatewayDestinations = {}; destinations.forEach((dest) => { gatewayDestinations[dest.name] = { creation: new Date(), type: dest.node?.type ?? null, status: OUTPUT_SELECTED, }; }); const gatewayInstance = { ecosystem: this._ecosystem, procName: this.name, procId, name, sources: gatewaySources, destinations: gatewayDestinations, creation: new Date(), status: ACTIVE, }; message = `[BPM] Se crea nuevo gateway ${name} con ${JSON.stringify(gatewayInstance, null, 2)}`; this.logger.log({ level: 'debug', label: 'gatewayInstance', action: 'createParallelGateway', message }); const gatewayCol = this.rwPool.collection(gatewayColl); const actionResult = await gatewayCol.insertOne(gatewayInstance); const gatewayId = actionResult?.insertedId.toString(); gatewayInstance.gatewayId = gatewayId; if (!gatewayId) { message = `[BPM] No fue posible la creación de una instancia del gateway parallel ${name} para el proceso ${this.name}`; this.logger.log({ level: 'error', label: 'gatewayInstance', action: 'createGatewayInstanceDB', message }); const errorObj = this.errorObj.get(modErrs.newGatewayInstance.saveFailure); return [errorObj, null]; } if (sources.length === 1 && destinations.length > 1) { for (let index = 0; index < nextDestinations.length; index++) { const nextName = nextDestinations[index]; const nodeDefinition = destinations.find((dest) => dest.name === nextName); message = `[BPM] Se dispara la transicion ${nextName} del gateway ${name}:${gatewayId}`; this.logger.log({ level: 'silly', label: 'gatewayInstance', action: 'createParallelGateway', message }); if (!nodeDefinition) { message = `[BPM] No se encontró nodo siguiente al gateway ${name} para el proceso ${this.name}:${procId}`; this.logger.log({ level: 'error', label: 'gatewayInstance', action: 'createParallelGateway', message }); const errorObj = this.errorObj.get(modErrs.newGatewayInstance.saveFailure); return [errorObj, null]; } const { node: { name: nextNodeName, type: nextNodeType }, } = nodeDefinition; message = `[BPM] Se inicia nodo ${nextNodeName} de tipo ${nextNodeType} desde gateway ${name}:${gatewayId}`; this.logger.log({ level: 'debug', label: 'gatewayInstance', action: 'createParallelGateway', message }); this.goToNode(procId, nextNodeName, nextNodeType, GATEWAY, name, gatewayId); } } else if (sources.length > 1 && destinations.length === 1) { this.updateParallelGateway(gatewayInstance, name, gatewayDef, origin); } return [null, gatewayId]; } async function updateParallelGateway(gatewayInstance, name, gatewayDef, origin) { const { destinations } = gatewayDef; const { gatewayId, sources, procId } = gatewayInstance; let message = `[BPM] Se debe actualizar instancia existente del gateway PARALLEL ${name}:${gatewayId} para la instacia ${procId}`; this.logger.log({ level: 'silly', label: 'gatewayInstance', action: 'startNewGateway', message }); const pendingSources = []; const sourcesNames = Object.keys(sources); for (let index = 0; index < sourcesNames.length; index++) { const sourceName = sourcesNames[index]; if (sources[sourceName].status === INPUT_PENDING) { pendingSources.push(sourceName); } } const gatewayCompleted = pendingSources.length === 1 && pendingSources[0] === origin.name; const updateGateway = gatewayCompleted ? { status: COMPLETE, completion: new Date() } : null; const completeResult = (await this.execCustomCallback(GATEWAYCOMPLETE, name, gatewayInstance)) ?? {}; const gatewayCol = this.rwPool.collection(gatewayColl); const actionResult = await gatewayCol.updateOne( ...processQueries.updateGatewaySourceStatus(gatewayId, origin, INPUT_COMPLETE, updateGateway), ); const result = actionResult?.result?.nModified > 0; if (gatewayCompleted) { message = `[BPM] Se completaron todas las entradas del gateway ${name}:${gatewayId} en la instacia ${procId}`; this.logger.log({ level: 'silly', label: 'gatewayInstance', action: 'startNewGateway', message }); const { node: { name: nextNodeName, type: nextNodeType }, } = destinations?.[0] ?? {}; this.goToNode(procId, nextNodeName, nextNodeType, GATEWAY, name, gatewayId); } return [null, { result, ...completeResult }]; } async function resolveGatewayDestination(procId, name, gatewayDef) { let message = `[BPM] Inicio gateway ${name} en proceso ${procId}`; this.logger.log({ level: 'silly', label: 'gatewayInstance', action: 'resolveGatewayDestination', message }); const nextDestinations = []; const procCol = this.roPool.collection(processColl); const procInstance = await procCol.findOne(processQueries.findProcess(procId)); if (!procInstance) { message = `[BPM] No se localizó proceso ${this.name}/${procId}`; this.logger.log({ level: 'error', label: 'gatewayInstance', action: 'resolveGatewayDestination', message }); const errorObj = this.errorObj.get(modErrs.newGatewayInstance.saveFailure); return [errorObj, null]; } const { destinations } = gatewayDef; for (let index = 0; index < destinations.length; index++) { const nextDestination = destinations[index]; const { name: destName, resolution, expression } = nextDestination; let execDestination = true; if (resolution === EXPRESSION) { let expressionToEval = expression; expressionToEval = expressionToEval.replace(/\$([a-zA-Z_][a-zA-Z0-9_]*)/g, 'procInstance.variables.$1'); expressionToEval = expressionToEval.replace(/#([a-zA-Z_][a-zA-Z0-9_]*)/g, (_, key) => { return JSON.stringify(procInstance.attributes[key]); }); execDestination = eval(expressionToEval); message = `[BPM] Evaluacion de la expresión ${expression} asociada al destino ${destName} del gateway ${name} en la instacia de proceso ${procId} con resultado ${execDestination}`; this.logger.log({ level: 'silly', label: 'gatewayInstance', action: 'resolveGatewayDestination', message }); } else if (resolution === CALLBACK) { const callback = this.getGatewayValudationFunction(name, destName); execDestination = await callback({ procId, ...procInstance }, name, destName); } if (execDestination) { nextDestinations.push(destName); message = `[BPM] Se activa destino ${destName} de gateway ${name} en proceso ${procId}`; this.logger.log({ level: 'debug', label: 'gatewayInstance', action: 'resolveGatewayDestination', message }); if (gatewayDef.type === EXCLUSIVE) { message = `[BPM] Validación exitos compuerta exclusiva para ${destName}`; this.logger.log({ level: 'silly', label: 'gatewayInstance', action: 'resolveGatewayDestination', message }); return [null, nextDestinations]; } } } return [null, nextDestinations]; } async function getProcessGatewaysHistory(procId) { const gatewayCol = this.roPool.collection(gatewayColl); const gatewayList = await gatewayCol.find(processQueries.findGateway({ procId, status: COMPLETE })).toArray(); return [null, gatewayList]; } function defineGatewayDestinationCallback(name, destination, callback) { if (!this.gatewayDestinationValidations[name]) { this.gatewayDestinationValidations[name] = {}; } const gatewayValidations = this.gatewayDestinationValidations[name]; gatewayValidations[destination] = callback; } function getGatewayValudationFunction(name, destination) { return this.gatewayDestinationValidations?.[name]?.[destination] ?? null; } module.exports = { methods: { startNewGateway, getGatewayInstance, createStandardGateway, updateGatewayInstance, updateGatewayNextNode, createParallelGateway, updateParallelGateway, getProcessGatewaysHistory, resolveGatewayDestination, // Funciones adicionadas al cierre defineGatewayDestinationCallback, getGatewayValudationFunction, }, errors: modErrs, };