UNPKG

softseti-sale-calculator-library

Version:

Sales calculation engine by Softseti

114 lines (99 loc) 3.61 kB
// src/rules/RuleEngine.js // Resolve a dotted path ("sale.dwSaleProducts") against an object. function resolvePath(obj, path) { return String(path).split('.').reduce((o, k) => (o == null ? undefined : o[k]), obj); } class RuleEngine { constructor(zenEngine) { this.engine = zenEngine ? new zenEngine() : null; this.ruleCache = new Map(); } /** * Evaluates a business rule with given context * @param {Object} ruleContent - GoRules JSON rule content * @param {Object} context - Execution context * @returns {Promise<Object>} Rule evaluation result */ async evaluateRule(ruleContent, context) { try { // Verificar si el motor está disponible if (!this.engine) { throw new Error('Rule engine not initialized. ZenEngine dependency not provided.'); } const decision = this.engine.createDecision(ruleContent); const result = await decision.safeEvaluate(context); return result; } catch (error) { console.error('Rule evaluation error:', error); throw new Error(`Rule evaluation failed: ${error.message}`); } } /** * Executes multiple rules for a trigger event * @param {Array} rules - Business rules to execute * @param {Object} context - Execution context * @returns {Promise<Array>} Array of rule execution results */ async executeRules(rules, context) { if (!this.engine || !rules || rules.length === 0) { return []; } const results = []; for (const rule of rules) { // Tag every result with its source rule so the caller can build the // rule_tracking (which rule was evaluated / applied / skipped). const meta = { id: rule.id, name: rule.name }; // Loop mode (opt-in): when the rule declares { loop: { over, as } } and // that path resolves to an array in the context, evaluate the decision // once per element, exposing the element under its alias. Rules without a // loop behave exactly as before. const loop = rule.loop; const list = loop && loop.over ? resolvePath(context, loop.over) : null; if (loop && loop.over && Array.isArray(list)) { const alias = loop.as || 'item'; for (let i = 0; i < list.length; i++) { const itemContext = Object.assign({}, context, { [alias]: list[i] }); try { const result = await this.evaluateRule(rule.rule_content, itemContext); result._rule = Object.assign({}, meta, { loop_index: i }); results.push(result); } catch (error) { results.push({ ruleId: rule.id, _rule: Object.assign({}, meta, { loop_index: i }), success: false, error: error.message }); } } continue; } try { const result = await this.evaluateRule(rule.rule_content, context); result._rule = meta; results.push(result); } catch (error) { results.push({ ruleId: rule.id, _rule: meta, success: false, error: error.message }); } } return results; } /** * Validates rule structure * @param {Object} ruleContent - Rule content to validate * @returns {boolean} True if rule is valid */ validateRule(ruleContent) { if (!ruleContent || typeof ruleContent !== 'object') { return false; } const requiredProperties = ['nodes', 'edges', 'contentType']; return requiredProperties.every(prop => ruleContent.hasOwnProperty(prop)); } } module.exports = RuleEngine;