tuain-bpm-lib
Version:
Servicio de gestión de manejo de procesos de la plataforma Tuain
195 lines (185 loc) • 7.35 kB
JavaScript
const moment = require('moment');
const InstanceEventData = require('./instance-event');
const {
collections: { execution: { timers: timersColl, timerLocks: timerLocksColl } },
dbQueries: { execution: { process: processQueries } },
process: {
elements: { event: EVENT },
timerStatus: { scheduled: SCHEDULED, executed: EXECUTED },
timersRefreshTime: REFRESH_TIME, scheduledTimeWindow: TIME_WINDOW, collisionWindow: COLLISION_WINDOW,
emmiterTypes: { eventClose: EVENTCLOSE, eventComplete: EVENTCOMPLETE },
},
} = require('../../../config');
const modErrs = {
timerEvent: {
notDefined: ['01', 'Evento no se encuentra en la definición del proceso'],
missingRequiredVariable: ['02', 'Variable requerida para el evento no se recibió'],
},
newTimerInstance: {
saveFailure: ['01', 'Error registrando evento en base de datos'],
},
};
function addScheduledTimer(procId, name, timerId, wakeupDate, eventVars) {
const currentlyScheduled = this.scheduledTimers.find(timer => timer.timerId === timerId);
if (currentlyScheduled) {
return currentlyScheduled;
}
const wakeUpMoment = moment(wakeupDate);
const duration = moment.duration(wakeUpMoment.diff(moment()));
const leftHours = duration.asHours();
const leftMinutes = duration.asMinutes();
const leftSeconds = duration.asSeconds() + (Math.random() * COLLISION_WINDOW);
if (leftHours < TIME_WINDOW) {
this.logger.log({
level: 'info',
label: 'instanceEvent',
action: 'triggerEvent',
message: `Se programa el evento de timer ${timerId} para ser disparado en ${leftMinutes} minutos`,
});
this.scheduledTimers.push({ procId, name, timerId });
setTimeout(() => this.triggerTimerEvent(procId, name, eventVars, timerId), leftSeconds * 1000);
}
// Ojo: Revisar qué se retorna
return null;
}
async function refreshProcessTimers() {
// Se obtienen los siguientes timers de la base de datos
const timerCol = this.roPool.collection(timersColl);
const beforeDate = moment().add(1, 'days').toDate();
const timerCursor = await timerCol.find(processQueries.findTimers(SCHEDULED, beforeDate));
while (await timerCursor.hasNext()) {
const scheduledTimer = await timerCursor.next();
const { procId, name, eventVars, timerId, wakeupDate } = scheduledTimer;
this.addScheduledTimer(procId, name, timerId, wakeupDate, eventVars);
}
}
function updateTimers() {
this.refreshProcessTimers();
clearInterval(this.refreshTimer);
this.refreshTimer = setInterval(() => this.refreshProcessTimers, REFRESH_TIME);
}
async function saveTimerEvent(procId, name, wakeupDate, eventVars) {
const eventVarNames = Object.keys(this.processDefinition?.events?.[name]?.variables);
const newTimerInstance = {
ecosystem: this._ecosystem,
procId,
processName: this.name,
name,
creation: new Date(),
wakeupDate,
variables: {},
status: SCHEDULED,
};
for (let index = 0; index < eventVarNames.length; index++) {
const eventVarName = eventVarNames[index];
newTimerInstance.variables[eventVarName] = eventVars[eventVarName] ?? null;
}
this.logger.log({
level: 'debug',
label: 'timerInstance',
action: 'createTimerInstanceDB',
message: `Se crea nueva instancia de temporizador ${name} para la instacia ${procId} del proceso ${this.name} con ${JSON.stringify(newTimerInstance, null, 2)}`,
});
const timerCol = this.rwPool.collection(timersColl);
const actionResult = await timerCol.insertOne(newTimerInstance);
const timerId = actionResult?.insertedId.toString();
if (!timerId) {
this.logger.log({
level: 'error',
label: 'timerInstance',
action: 'createTimerInstanceDB',
message: `No fue posible la creación de una instancia del temporizador ${name} para el proceso ${this.name}`,
});
const errorObj = this.errorObj.get(modErrs.newTimerInstance.saveFailure);
return [errorObj, null];
}
return [null, timerId];
}
async function registerTimerEvent(procId, name, eventVars) {
const eventDef = this.processDefinition?.events?.[name];
if (!eventDef) {
const message = `El evento ${name} no se encuentra en la definición del proceso`;
this.logger.log({
level: 'error',
label: 'timerInstance',
action: 'registerTimerEvent',
message,
});
const errorObj = this.errMgr.get(modErrs.timerEvent.notDefined, message);
return [errorObj, null];
}
const { variables, time, timeUnit } = eventDef;
const variableNames = Object.keys(variables);
for (let index = 0; index < variableNames.length; index++) {
const variableName = variableNames[index];
const { required } = variables[variableName];
if (required && !eventVars?.[variableName]) {
const message = `Variable ${variableName} requerida para programar el evento timer ${name}`;
this.logger.log({
level: 'error',
label: 'timerInstance',
action: 'timerEvent',
message,
});
const errorObj = this.errMgr.get(modErrs.timerEvent.missingRequiredVariable, message);
return [errorObj, null];
}
}
const inputVarNames = Object.keys(eventVars);
for (let index = 0; index < inputVarNames.length; index++) {
const inputVarName = inputVarNames[index];
if (!variables[inputVarName]) {
delete eventVars[inputVarName];
}
}
const wakeupDate = moment().add(time, timeUnit).toDate();
const [eventErr, timerId] = await this.saveTimerEvent(procId, name, wakeupDate, eventVars);
if (eventErr) { return [eventErr, null]; }
this.logger.log({
level: 'info',
label: 'instanceEvent',
action: 'timerEvent',
message: `Se registra nueva instanca ${timerId} del temporizador ${name} en la instancia ${procId} del proceso ${this.name}`,
});
this.addScheduledTimer(procId, name, timerId, wakeupDate, eventVars);
return [null, true];
}
async function triggerTimerEvent(procId, name, eventVars, timerId) {
if (!this.lockInit) {
this.rwPool.collection(timerLocksColl).createIndex({ timerId: -1 }, { expireAfterSeconds: 120 });
this.lockInit = true;
}
const timerLock = await this.rwPool.collection(timerLocksColl).insertOne({ procId, name, timerId });
const lockId = timerLock?.insertedId?.toString() ?? null;
if (!lockId) {
return [null, null];
}
const eventDef = this.processDefinition?.events?.[name];
const processEvent = new InstanceEventData(this.name, procId, timerId, name, eventVars);
await this.execCustomCallback(EVENTCOMPLETE, name, processEvent);
await this.execCustomCallback(EVENTCLOSE, name, processEvent);
const timerCol = this.rwPool.collection(timersColl);
const actionResult = await timerCol.updateOne(...processQueries.changeTimerStatus(timerId, EXECUTED));
const result = actionResult?.result?.nModified > 0;
if (!result) {
this.logger.log({
level: 'warn',
label: 'timerInstance',
action: 'triggerTimerEvent',
message: `No se pudo efectuar modificación del temporizador ${timerId}`,
});
}
this.goToNode(procId, eventDef.next.name, eventDef.next.type, EVENT, name, timerId);
return [null, { result }];
}
module.exports = {
methods: {
refreshProcessTimers,
updateTimers,
saveTimerEvent,
registerTimerEvent,
triggerTimerEvent,
addScheduledTimer,
},
errors: modErrs,
};