|
|
@@ -10,6 +10,10 @@
|
|
|
* @version
|
|
|
*/
|
|
|
|
|
|
+const math = require('mathjs');
|
|
|
+math.config({
|
|
|
+ number: 'BigNumber',
|
|
|
+});
|
|
|
const ValidTemplateType = ['posCalc', 'cost'];
|
|
|
const PosCalc = (function(){
|
|
|
const EmptySpreadCache = {
|
|
|
@@ -30,7 +34,7 @@ const PosCalc = (function(){
|
|
|
};
|
|
|
const ValidColInfo = [
|
|
|
{ key: 'str', name: '文本', fields: ['str1', 'str2', 'str3', 'str4'], valid: ['title', 'width'], def: { title: '文字', width: 80, type: 'str'} },
|
|
|
- { key: 'num', name: '数值', fields: ['num1', 'num2', 'num3', 'num4', 'num5', 'num6', 'num7', 'num8', 'num9'], valid: ['title', 'width', 'calc_code', 'decimal', 'unit'], def: { title: '数值', width: 80, decimal: 2, type: 'num', unit: ''} },
|
|
|
+ { key: 'num', name: '数值', fields: ['num_a', 'num_b', 'num_c', 'num_d', 'num_e', 'num_f', 'num_g', 'num_h', 'num_i'], valid: ['title', 'width', 'calc_code', 'decimal', 'unit'], def: { title: '数值', width: 80, decimal: 2, type: 'num', unit: ''} },
|
|
|
{ key: 'spec', name: '规格', fields: ['spec'], valid: ['title', 'width', 'rela_col', 'spec_set'], def: { title: '规格', width: 80, rela_col: '', type: 'spec'} },
|
|
|
{ key: 'qty', name: '数量', fields: ['qty'], valid: ['title', 'width', 'expr', 'decimal'], def: { title: '数量', width: 80, decimal: 2, expr: '', type: 'qty' } },
|
|
|
];
|
|
|
@@ -120,6 +124,8 @@ module.exports = app => {
|
|
|
x.field_cache = x.field_cache ? JSON.parse(x.field_cache) : {};
|
|
|
x.decimal = x.decimal ? JSON.parse(x.decimal) : {};
|
|
|
x.multi_header = x.multi_header ? JSON.parse(x.multi_header) : undefined;
|
|
|
+ x.calc_expr = x.calc_expr ? JSON.parse(x.calc_expr) : {};
|
|
|
+ x.calc_order = x.calc_order ? x.calc_order.split(',') : [];
|
|
|
});
|
|
|
for (const d of datas) {
|
|
|
if (d.spec_set) {
|
|
|
@@ -284,10 +290,22 @@ module.exports = app => {
|
|
|
}
|
|
|
return result;
|
|
|
}
|
|
|
+ async getCalcTestData_cost(colSet, count) {
|
|
|
+ const result = [];
|
|
|
+ for (let i = 0; i < count; i++) {
|
|
|
+ const testData = {};
|
|
|
+ colSet.forEach(x => {
|
|
|
+ if (x.type === 'num') testData[x.field] = Math.max(Math.floor(Math.random()*10), 1);
|
|
|
+ if (x.type === 'str') testData[x.field] = randomWord(true, 3, 6);
|
|
|
+ });
|
|
|
+ result.push(testData);
|
|
|
+ }
|
|
|
+ return result;
|
|
|
+ }
|
|
|
async getCalcTestData(colSet, type, count = 1) {
|
|
|
const funName = 'getCalcTestData_' + type;
|
|
|
if (this[funName]) return await this[funName](colSet, count);
|
|
|
- return null;
|
|
|
+ return [];
|
|
|
}
|
|
|
|
|
|
async _checkTemplateUsed_posCalc(template) {
|
|
|
@@ -305,12 +323,18 @@ module.exports = app => {
|
|
|
});
|
|
|
}
|
|
|
}
|
|
|
+ async _checkTemplateUsed_cost(template) {
|
|
|
+ const templates = template instanceof Array ? template : [template];
|
|
|
+ templates.forEach(x => { x.used = []; x.used_count = 0; });
|
|
|
+ // todo 检查使用情况
|
|
|
+ }
|
|
|
async checkTemplateUsed(template, type) {
|
|
|
+ if (!template || template.length === 0) return;
|
|
|
+
|
|
|
const funName = '_checkTemplateUsed_' + type;
|
|
|
if (this[funName]) return await this[funName](template);
|
|
|
- return false;
|
|
|
}
|
|
|
- // -----------------------------------------------------------------------
|
|
|
+ // ----------------------------------------------------------------------
|
|
|
|
|
|
async _addTemplate(name, type) {
|
|
|
if (ValidTemplateType.indexOf(type) < 0) throw '新增的模板类型非法';
|
|
|
@@ -342,6 +366,40 @@ module.exports = app => {
|
|
|
if (template.create_user_id !== this.ctx.session.sessionUser.accountId) throw '非您创建的模板';
|
|
|
return template;
|
|
|
}
|
|
|
+ _getCalcLeaf(cur, cols, parentField) {
|
|
|
+ if (!cur || !cur.expr) return [];
|
|
|
+ const codeReg = /num_[a-z]{1}/gm;
|
|
|
+ const codeParam = cur.expr.match(codeReg);
|
|
|
+ if (!codeParam || codeParam.length === 0) return [];
|
|
|
+
|
|
|
+ const result = [...codeParam];
|
|
|
+ for (const op of codeParam) {
|
|
|
+ const relaCol = cols.find(x => { return x.field === op; });
|
|
|
+ if (relaCol.field === cur.field || op === parentField) {
|
|
|
+ result.push(op);
|
|
|
+ } else {
|
|
|
+ const sub = this._getCalcLeaf(relaCol, cols, cur.field);
|
|
|
+ if (sub.length > 0) {
|
|
|
+ result.push(...sub);
|
|
|
+ } else {
|
|
|
+ result.push(op);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return this.ctx.helper._.uniq(result);
|
|
|
+ }
|
|
|
+ _getCalcExpr(cols) {
|
|
|
+ const calcOrder = [];
|
|
|
+ for (const c of cols) {
|
|
|
+ if (c.type !== 'Number') continue;
|
|
|
+ calcOrder.push({ field: c.field, expr: c.expr || '', decimal: c.decimal });
|
|
|
+ }
|
|
|
+ for (const co of calcOrder) {
|
|
|
+ co.leaf = this._getCalcLeaf(co, calcOrder);
|
|
|
+ }
|
|
|
+ calcOrder.sort((x, y) => {return x.leaf.length - y.leaf.length; });
|
|
|
+ return calcOrder;
|
|
|
+ }
|
|
|
async _saveTemplate(id, data) {
|
|
|
const org = await this.checkTemplateEdit(id);
|
|
|
const updateData = { id };
|
|
|
@@ -360,8 +418,9 @@ module.exports = app => {
|
|
|
const decimal = { def: 2 };
|
|
|
spread_cache.cols.forEach(x => { if (x.decimal !== null && x.decimal !== undefined) decimal[x.field] = x.decimal; });
|
|
|
updateData.decimal = JSON.stringify(decimal);
|
|
|
- const qtyCol = spread_cache.cols.find(x => { return x.field === 'qty'; });
|
|
|
- updateData.calc_expr = qtyCol ? qtyCol.expr : '';
|
|
|
+ const calc_expr = this._getCalcExpr(spread_cache.cols);
|
|
|
+ updateData.calc_expr = JSON.stringify(calc_expr);
|
|
|
+ updateData.calc_order = calc_expr.map(x => { return x.field; }).join(',');
|
|
|
const specCol = data.col_set.find(x => { return x.field === 'spec' });
|
|
|
if (specCol) {
|
|
|
updateData.spec_set = specCol.spec_set;
|
|
|
@@ -386,6 +445,81 @@ module.exports = app => {
|
|
|
if (data.update) result.update = await this._saveTemplate(data.update.id, data.update);
|
|
|
return result;
|
|
|
}
|
|
|
+ async reCalcTemplate(id, force) {
|
|
|
+ const data = await this.checkTemplateEdit(id);
|
|
|
+ const updateData = { id };
|
|
|
+ if (!force) {
|
|
|
+ await this.checkTemplateUsed(data);
|
|
|
+ if (data.used.length > 0) throw '模板已使用,不可修改配置!';
|
|
|
+ }
|
|
|
+ await this.analysisTemplate(data);
|
|
|
+
|
|
|
+ updateData.col_set = JSON.stringify(data.col_set);
|
|
|
+ updateData.multi_header = JSON.stringify(data.multi_header);
|
|
|
+ const spread_cache = this.calcSpreadCache(data.type, data.col_set, data.multi_header);
|
|
|
+ updateData.spread_cache = JSON.stringify(spread_cache);
|
|
|
+ const field_cache = {};
|
|
|
+ data.col_set.forEach(x => { field_cache[x.field] = x.title; });
|
|
|
+ updateData.field_cache = JSON.stringify(field_cache);
|
|
|
+ const decimal = { def: 2 };
|
|
|
+ spread_cache.cols.forEach(x => { if (x.decimal !== null && x.decimal !== undefined) decimal[x.field] = x.decimal; });
|
|
|
+ updateData.decimal = JSON.stringify(decimal);
|
|
|
+ const calc_expr = this._getCalcExpr(spread_cache.cols);
|
|
|
+ updateData.calc_expr = JSON.stringify(calc_expr);
|
|
|
+ updateData.calc_order = calc_expr.map(x => { return x.field; }).join(',');
|
|
|
+ const specCol = data.col_set.find(x => { return x.field === 'spec' });
|
|
|
+ if (specCol) {
|
|
|
+ updateData.spec_set = specCol.spec_set;
|
|
|
+ updateData.spec_rela = data.col_set.find(x => { return x.calc_code === specCol.rela_col; }).field;
|
|
|
+ } else {
|
|
|
+ updateData.spec_set = 0;
|
|
|
+ updateData.spec_rela = '';
|
|
|
+ }
|
|
|
+ await this.db.update(this.tableName, updateData);
|
|
|
+ }
|
|
|
+
|
|
|
+ _calcExpr(formula) {
|
|
|
+ const percentReg = /((\d+)|((\d+)(\.\d+)))%/g;
|
|
|
+ const percent = formula.match(percentReg);
|
|
|
+ if (percent) {
|
|
|
+ for (const p of percent) {
|
|
|
+ const v = math.eval(p.replace(new RegExp('%', 'gm'), '/100'));
|
|
|
+ formula = formula.replace(p, v);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ try {
|
|
|
+ // 使用mathjs计算 math.eval('17259401.95*0.9') = 15533461.754999999,正确应为 15533461.755
|
|
|
+ // const value = math.eval(formula);
|
|
|
+ // 使用逆波兰法四则运算,可防止出现误差,但是只支持四则运算,不支持科学运算
|
|
|
+ // const value = this.ctx.helper.calcExprStrRpn(formula);
|
|
|
+ // 使用mathjs的大数运算,可支持所有
|
|
|
+ const value = parseFloat(math.eval(formula));
|
|
|
+ return Number.isFinite(value) ? value : 0;
|
|
|
+ } catch(err) {
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ calcByTemplate(data, updateData, orgData, calcExpr, updateExprInfo) {
|
|
|
+ const codeReg = /num_[a-z]{1}/gm;
|
|
|
+ for (const ce of calcExpr) {
|
|
|
+ if (ce.expr) {
|
|
|
+ let calcExpr = ce.expr;
|
|
|
+ const codeParam = calcExpr.match(codeReg);
|
|
|
+ for (const cp of codeParam) {
|
|
|
+ calcExpr = calcExpr.replace(new RegExp(cp, 'gm'), data[cp] || orgData[cp] || 0);
|
|
|
+ }
|
|
|
+ if (updateExprInfo && updateExprInfo[ce.field]) updateExprInfo[ce.field] = calcExpr;
|
|
|
+ try {
|
|
|
+ data[ce.field] = this.ctx.helper.round(this._calcExpr(calcExpr.replace(new RegExp('%', 'gm'), '/100')), ce.decimal);
|
|
|
+ } catch(err) {
|
|
|
+ data[ce.field] = 0;
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ if (updateData[ce.field] === undefined) continue;
|
|
|
+ data[ce.field] = this.ctx.helper.round(updateData[ce.field], ce.decimal || 2);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
return CalcTmpl;
|