tuain-bpm-lib
Version:
Servicio de gestión de manejo de procesos de la plataforma Tuain
340 lines (325 loc) • 16.1 kB
JavaScript
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,
};