'use strict'; /** * * * @author Mai * @date * @version */ const TreeExprCalc = (function(){ const calcRela = { calcType: {zero: 0, direct: 1, cache: 2, sortCache: 4}, SerialReg: new RegExp('<>', 'ig'), SerialFirstReg: new RegExp('^<>', 'i'), IdReg: new RegExp('<<[a-z0-9\-]+\\$[a-z]+>>', 'ig'), OrderReg: new RegExp('f[0-9]+', 'ig'), valueChar: '$', calcCache: {}, error: { loop: '', deep: '' }, // 计算必须项 tree: null, decimal: null, calcField: null, getCalcNum: null, calcCacheFun: null, exprCacheKey: [], exprFieldsIndex: {}, exprCacheFields: [], errorMsg: true, }; const getCalcField = function(value) { return calcRela.exprFieldsIndex[value]; }; const init = function(setting) { calcRela.tree = setting.tree; calcRela.decimal = setting.decimal || { qty: 2, tp: 0 }; calcRela.calcField = setting.calcField || { qty: 'quantity', tp: 'total_price' }; setCalcType(setting.calcType || calcRela.calcType.zero); calcRela.calcCacheFun = setting.calcCacheFun; if (setting.exprFieldsIndex) calcRela.exprFieldsIndex = setting.exprFieldsIndex; if (setting.exprCacheKey) { calcRela.exprCacheKey = setting.exprCacheKey; calcRela.exprCacheFields = calcRela.exprCacheKey.map(x => { return getCalcField(x); }); } calcRela.errorMsg = setting.errorMsg || true; calcRela.precision = setting.precision; }; const findPrecision = function(list, unit) { if (!unit) return list.other; for (const p in list) { if (list[p].unit && list[p].unit === unit) { return list[p]; } } return list.other; }; const checkExprValid = function(expr, invalidOrders = []) { if (!expr) return [true, '']; const param = []; let num = ''; for (let i = 0, iLen = expr.length; i < iLen; i++) { const subExpr = expr.substring(i, expr.length); if (/^[\d\.%]+/.test(expr[i])) { num = num + expr[i]; } else if (calcRela.SerialFirstReg.test(subExpr)) { if (num !== '') { param.push({type: 'num', value: num}); num = ''; } const order = calcRela.SerialFirstReg.exec(subExpr); param.push({type: 'order', value: order[0]}); i = i + order[0].length - 1; } else if (expr[i] === '(') { if (num !== '') { param.push({type: 'num', value: num}); num = ''; } param.push({type: 'left', value: '('}); } else if (expr[i] === ')') { if (num !== '') { param.push({type: 'num', value: num}); num = ''; } param.push({type: 'right', value: ')'}); } else if (/^[\+\-*\/]/.test(expr[i])) { if (num !== '') { param.push({type: 'num', value: num}); num = ''; } param.push({type: 'calc', value: expr[i]}); } else { return [false, '输入的表达式含有非法字符: ' + expr[i]]; } } if (num !== '') { param.push({type: 'num', value: num}); num = ''; } if (param.length === 0) return [true, '']; if (param.length > 1) { if (param[0].value === '-' && param[1].type === 'num') { param[1].value = '-' + param[1].value; param.shift(); } } const iLen = param.length; let iLeftCount = 0, iRightCount = 0; for (const [i, p] of param.entries()) { if (p.type === 'calc') { if (i === 0 || i === iLen - 1) return [false, '输入的表达式非法:计算符号' + p.value + '前后应有数字或计算基数']; } if (p.type === 'num') { num = p.value.replace('%', ''); if (p.value.length - num.length > 1) return [false, '输入的表达式非法:' + p.value + '不是一个有效的数字']; num = _.toNumber(num); if (num === undefined || num === null || _.isNaN(num)) return [false, '输入的表达式非法:' + p.value + '不是一个有效的数字']; if (i > 0) { if (param[i - 1].type !== 'calc' && param[i - 1].type !== 'left') { return [false, '输入的表达式非法:' + p.value + '前应有运算符']; } else if (param[i - 1].value === '/' && num === 0) { return [false, '输入的表达式非法:请勿除0']; } } } if (p.type === 'order') { const match = invalidOrders.find(x => { p.value.indexOf(`f${x.orderStr}$`) > 0; }); if (match) return [false, `输入的表达式非法:循环引用,请勿引用${match.orderStr}`]; if (i > 0) { if (param[i - 1].type !== 'calc' && param[i - 1].type !== 'left') { return [false, '输入的表达式非法:' + p.value.replace(//ig, '>') + '前应有运算符']; } } } if (p.type === 'left') { iLeftCount += 1; if (i !== 0 && param[i-1].type !== 'calc') return [false, '输入的表达式非法:(前应有运算符']; } if (p.type === 'right') { iRightCount += 1; if (i !== iLen - 1 && param[i+1].type !== 'calc') return [false, '输入的表达式非法:)后应有运算符']; if (iRightCount > iLeftCount) return [false, '输入的表达式非法:")"前无对应的"("']; } } if (iLeftCount > iRightCount) return [false, '输入的表达式非法:"("后无对应的")"']; return [true, '']; }; const _getCalcNumZero = function (node, field) { return node.calc_expr && calcRela.exprInitFields.indexOf(field) >= 0 ? 0 : node[field] || 0; }; const _getCalcNumDirect = function (node, field) { return node[field] || 0; }; const _getCalcNumCache = function (node, field) { if (node.calc_expr && calcRela.exprCacheFields.indexOf(field) >= 0) { return calcRela.calcCache[node.id] ? calcRela.calcCache[node.id][field] || 0 : 0; } return node[field] || 0; }; const getIdParamValue = function(idParam) { const [id, value] = idParam.substring(2, idParam.length - 2).split(calcRela.valueChar); const node = calcRela.tree.nodes.find(x => { return x.id === id; }); if (!node) return 0; const calcField = getCalcField(value); if (!node.children || node.children.length === 0) { return calcRela.getCalcNum(node, calcField); } else { const posterity = calcRela.tree.getLeafPosterity(node); const calcMap = posterity.map(x => { return calcRela.getCalcNum(x, calcField) }); return ZhCalc.sum(calcMap); } }; const calcExpr = function(expr) { if (!expr) return 0; let formula = expr; const idParam = expr.match(calcRela.IdReg); if (idParam) { for (const ip of idParam) { formula = formula.replace(ip, getIdParamValue(ip)); } } return [formula, math.evaluate(formula)]; }; const addCache = function(expr) { const cache = { id: expr.id }; if (expr.calcField === 'qty') { const precision = findPrecision(calcRela.precision, expr.unit); cache[calcRela.calcField.qty] = ZhCalc.round(expr.value, precision.value); cache[calcRela.calcField.tp] = ZhCalc.mul(cache[calcRela.calcField.qty], expr.unit_price, calcRela.decimal.tp); } else if (expr.calcField === 'tp') { cache[calcRela.calcField.qty] = 0; cache[calcRela.calcField.tp] = ZhCalc.round(expr.value, calcRela.decimal.tp); } if (calcRela.calcCacheFun) calcRela.calcCacheFun(cache, expr); calcRela.calcCache[expr.id] = cache; }; const sortExprList = function(exprList) { const exprIds = exprList.map(x => { return x.id; }); exprList.forEach(x => { x.sort = calcRela.tree.nodes.findIndex(n => { return n.id === x.id; }); x.relaExprId = []; const idParam = x.expr.match(calcRela.IdReg); if (idParam) { for (const ip of idParam) { const [id, value] = ip.substring(2, ip.length - 2).split(calcRela.valueChar); if (value.indexOf('bq') < 0) continue; const node = calcRela.tree.nodes.find(x => { return x.id === id; }); if (!node) continue; if (!node.children || node.children.length === 0) { if (exprIds.indexOf(node.id) >= 0) x.relaExprId.push(node.id); } else { const posterity = calcRela.tree.getLeafPosterity(node); posterity.forEach(p => { if (exprIds.indexOf(p.id) >= 0) x.relaExprId.push(p.id); }); } } } }); exprList.forEach(x => { if (x.relaExprId.length === 0) { x.calcSort = 1; return; } let calcSort = 1; let calcExprId = [...x.relaExprId]; while (calcExprId.length > 0 && calcSort <= 100) { const sortIds = []; for (const id of calcExprId) { const relaNode = exprList.find(y => { return y.id === id; }); if (relaNode.relaExprId.length > 0) { sortIds.push(...relaNode.relaExprId); } } calcSort++; if (sortIds.indexOf(x.id) >= 0) { calcSort = 'max'; break; } calcExprId = sortIds; } if (calcSort > 100 && !calcRela.error.deep) calcRela.error.deep = x.id; if (calcSort === 'max' && !calcRela.error.loop) calcRela.error.loop = x.id; x.calcSort = calcSort; }); exprList.sort((x, y) => { if (x.calcSort === 'max') return 1000; if (x.calcSort === 'max') return -1000; return x.calcSort - y.calcSort; }); }; const _initCache = function() { calcRela.calcCache = {}; calcRela.error.deep = ''; calcRela.error.loop = ''; }; const checkError = function() { if (!calcRela.errorMsg) return; if (calcRela.error.loop) { const nodeIndex = calcRela.tree.nodes.findIndex(x => { return x.id === calcRela.error.loop; }); const node = calcRela.tree.nodes[nodeIndex]; const hintCode = (node.code || '') + (node.b_code || ''); toastr.warning(`第${nodeIndex + 1}行 ${hintCode} 公式存在循环计算,请检查`); return; } if (calcRela.error.deep) { const nodeIndex = calcRela.tree.nodes.findIndex(x => { return x.id === calcRela.error.deep; }); const node = calcRela.tree.nodes[nodeIndex]; const hintCode = (node.code || '') + (node.b_code || ''); toastr.warning(`第${nodeIndex + 1}行 ${hintCode} 公式引用层次过深,请检查`); return; } }; const calcAllExpr = function(exprList) { _initCache(); if (calcRela.sort) sortExprList(exprList); for (const expr of exprList) { [expr.formula, expr.value] = calcExpr(expr.expr); addCache(expr); } checkError(); const result = JSON.parse(JSON.stringify(calcRela.error)); _initCache(); return result; }; const expr2ExprStr = function(expr) { if (!expr) return ''; let formula = expr; const idParam = expr.match(calcRela.IdReg); if (idParam) { for (const ip of idParam) { const [id, value] = ip.substring(2, ip.length - 2).split(calcRela.valueChar); const order = calcRela.tree.nodes.findIndex(x => { return x.id === id }); const orderParam = `<>`; formula = formula.replace(ip, orderParam); } } return formula; }; const exprStr2Expr = function(exprStr) { if (!exprStr) return ''; let formula = exprStr; const orderParam = exprStr.match(calcRela.SerialReg); if (orderParam) { for (const op of orderParam) { const orderStr = op.match(calcRela.OrderReg)[0]; const order = parseInt(orderStr.substring(1, op.length)); const node = calcRela.tree.nodes[order - 1]; const idParam = op.replace(orderStr, node.id); formula = formula.replace(op, idParam); } } return formula; }; const setCalcType = function (calcType) { if (calcType === calcRela.calcType.zero) calcRela.getCalcNum = _getCalcNumZero; if (calcType === calcRela.calcType.direct) calcRela.getCalcNum = _getCalcNumDirect; if (calcType === calcRela.calcType.cache) calcRela.getCalcNum = _getCalcNumCache; if (calcType === calcRela.calcType.sortCache) calcRela.getCalcNum = _getCalcNumCache; calcRela.sort = calcType === calcRela.calcType.sortCache; }; return { calcType: calcRela.calcType, init, setCalcType, checkExprValid, expr2ExprStr, exprStr2Expr, calcExpr, calcAllExpr, } })();