tuain-bpm-lib
Version:
Servicio de gestión de manejo de procesos de la plataforma Tuain
180 lines (170 loc) • 7.61 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) {
const message = `[BPM] Se programa el evento de timer ${timerId} para ser disparado en ${leftMinutes} minutos`;
this.logger.log({ level: 'info', label: 'instanceEvent', action: 'triggerBoundaryEvent', message });
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, _id: 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;
}
let message = `[BPM] Se crea nueva instancia de temporizador ${name} para la instacia ${procId} del proceso ${this.name} con ${JSON.stringify(newTimerInstance, null, 2)}`;
this.logger.log({ level: 'debug', label: 'timerInstance', action: 'createTimerInstanceDB', message });
const timerCol = this.rwPool.collection(timersColl);
const actionResult = await timerCol.insertOne(newTimerInstance);
const timerId = actionResult?.insertedId.toString();
if (!timerId) {
message = `[BPM] No fue posible la creación de una instancia del temporizador ${name} para el proceso ${this.name}`;
this.logger.log({ level: 'error', label: 'timerInstance', action: 'createTimerInstanceDB', message });
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];
let message;
if (!eventDef) {
message = `[BPM] : 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 { timerType = 'fixed', timerVariable = '', variables, time = 1, timeUnit = 'd' } = 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]) {
message = `[BPM] : 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];
}
}
let wakeupDate;
if (timerType === 'variable') {
const { time: varTime = 1, timeUnit: varTimeUnit = 'd' } = (await this.getVar(procId, timerVariable)) ?? {};
wakeupDate = moment().add(varTime, varTimeUnit).toDate();
} else {
wakeupDate = moment().add(time, timeUnit).toDate();
}
const [eventErr, timerId] = await this.saveTimerEvent(procId, name, wakeupDate, eventVars);
if (eventErr) {
return [eventErr, null];
}
message = `[BPM] Nuevo temporizador ${name}/${timerId} para ${wakeupDate} en proceso ${this.name}/${procId} `;
this.logger.log({ level: 'info', label: 'instanceEvent', action: 'timerEvent', message });
this.addScheduledTimer(procId, name, timerId, wakeupDate, eventVars);
return [null, timerId];
}
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) {
const message = `[BPM] No se pudo efectuar modificación del temporizador ${timerId}`;
this.logger.log({ level: 'silly', label: 'timerInstance', action: 'triggerTimerEvent', message });
}
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,
};