calc_tmpl.js 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530
  1. 'use strict';
  2. /**
  3. * 计算模板
  4. *
  5. * 如需修改请联系作者,常量&私有方法(以下划线开头)勿动
  6. *
  7. * @author Mai
  8. * @date
  9. * @version
  10. */
  11. const math = require('mathjs');
  12. math.config({
  13. number: 'BigNumber',
  14. });
  15. const ValidTemplateType = ['posCalc', 'cost'];
  16. const PosCalc = (function(){
  17. const EmptySpreadCache = {
  18. cols: [],
  19. emptyRows: 6,
  20. headRows: 1,
  21. headRowHeight: [32],
  22. defaultRowHeight: 21,
  23. headerFont: '12px 微软雅黑',
  24. font: '12px 微软雅黑',
  25. frozenLineColor: '#93b5e4',
  26. };
  27. const BaseSpreadColSetting = {
  28. str: { title: '', colSpan: '1', rowSpan: '1', field: '', hAlign: 0, width: 80, formatter: '@' },
  29. num: { title: '', colSpan: '1', rowSpan: '1', field: '', hAlign: 2, width: 80, type: 'Number' },
  30. spec: { title: '规格', colSpan: '1', rowSpan: '1', field: 'spec', hAlign: 1, width: 80, formatter: '@', cellType: 'spec', relaCol: '' },
  31. qty: { title: '数量', colSpan: '1', rowSpan: '1', field: 'qty', hAlign: 2, width: 80, type: 'Number', readOnly: true },
  32. };
  33. const ValidColInfo = [
  34. { key: 'str', name: '文本', fields: ['str1', 'str2', 'str3', 'str4'], valid: ['title', 'width'], def: { title: '文字', width: 80, type: 'str'} },
  35. { 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: ''} },
  36. { key: 'spec', name: '规格', fields: ['spec'], valid: ['title', 'width', 'rela_col', 'spec_set'], def: { title: '规格', width: 80, rela_col: '', type: 'spec'} },
  37. { key: 'qty', name: '数量', fields: ['qty'], valid: ['title', 'width', 'expr', 'decimal'], def: { title: '数量', width: 80, decimal: 2, expr: '', type: 'qty' } },
  38. ];
  39. ValidColInfo.forEach(vci => { return vci.count = vci.fields.length; });
  40. const TestData = {
  41. col_set: [
  42. { title: '名称', width: 80, type: 'str', field: 'str1' },
  43. { title: '长度', width: 80, type: 'num', field: 'num_a', decimal: 2, unit: 'm', calc_code: 'A' },
  44. { title: '宽度', width: 80, type: 'num', field: 'num_b', decimal: 2, unit: 'm', calc_code: 'B' },
  45. { title: '高度', width: 80, type: 'num', field: 'num_c', decimal: 2, unit: 'm', calc_code: 'C' },
  46. { title: '数量', width: 80, type: 'num', field: 'qty', decimal: 2, },
  47. ]
  48. };
  49. return { EmptySpreadCache, BaseSpreadColSetting, ValidColInfo, TestData, ValidCount: 50 };
  50. })();
  51. const Cost = (function(){
  52. const EmptySpreadCache = {
  53. cols: [],
  54. emptyRows: 3,
  55. headRows: 1,
  56. headRowHeight: [32],
  57. defaultRowHeight: 21,
  58. headerFont: '12px 微软雅黑',
  59. font: '12px 微软雅黑',
  60. frozenLineColor: '#93b5e4',
  61. };
  62. const BaseSpreadColSetting = {
  63. str: { title: '', colSpan: '1', rowSpan: '1', field: '', hAlign: 0, width: 80, formatter: '@' },
  64. num: { title: '', colSpan: '1', rowSpan: '1', field: '', hAlign: 2, width: 80, type: 'Number' },
  65. };
  66. const ValidColInfo = [
  67. { key: 'str', name: '文本', fields: ['str1', 'str2', 'str3', 'str4', 'str5', 'str6'], valid: ['title', 'width'], def: { title: '文本', width: 80, type: 'str'} },
  68. {
  69. key: 'num', name: '数值/计算',
  70. fields: ['num_a', 'num_b', 'num_c', 'num_d', 'num_e', 'num_f', 'num_g', 'num_h', 'num_i', 'num_j', 'num_k', 'num_l', 'num_m', 'num_n', 'num_o', 'num_p'],
  71. valid: ['title', 'width', 'calc_code', 'decimal', 'unit', 'expr'],
  72. def: { title: '数值/计算', width: 80, decimal: 2, type: 'num', unit: ''}
  73. },
  74. { key: 'memo', name: '长文本', fields: ['memo1', 'memo2'], valid: ['title', 'width'], def: { title: '长文本', width: 120, type: 'memo'} },
  75. ];
  76. ValidColInfo.forEach(vci => { return vci.count = vci.fields.length; });
  77. return { EmptySpreadCache, BaseSpreadColSetting, ValidColInfo, ValidCount: 10 };
  78. })();
  79. function randomWord(randomFlag, min, max){
  80. let str = "",
  81. range = min,
  82. arr = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'],
  83. pos;
  84. // 随机产生
  85. if(randomFlag){
  86. range = Math.round(Math.random() * (max-min)) + min;
  87. }
  88. for(var i=0; i<range; i++){
  89. pos = Math.round(Math.random() * (arr.length-1));
  90. str += arr[pos];
  91. }
  92. return str;
  93. }
  94. module.exports = app => {
  95. class CalcTmpl extends app.BaseService {
  96. /**
  97. * 构造函数
  98. *
  99. * @param {Object} ctx - egg全局变量
  100. * @return {void}
  101. */
  102. constructor(ctx) {
  103. super(ctx);
  104. this.tableName = 'calc_tmpl';
  105. this.TemplateRela = { posCalc: PosCalc, cost: Cost };
  106. }
  107. async getAllTemplate(pid, type, sort = 'asc') {
  108. return this.getAllDataByCondition({
  109. columns: ['id', 'pid', 'name', 'create_user_id', 'is_locked'],
  110. where: { pid, type },
  111. orders: [['create_time', sort]]
  112. });
  113. }
  114. async analysisTemplate(data) {
  115. const datas = data instanceof Array ? data : [data];
  116. datas.forEach(x => {
  117. x.col_set = x.col_set ? JSON.parse(x.col_set) : [];
  118. x.spread_cache = x.spread_cache ? JSON.parse(x.spread_cache) : {};
  119. x.field_cache = x.field_cache ? JSON.parse(x.field_cache) : {};
  120. x.decimal = x.decimal ? JSON.parse(x.decimal) : {};
  121. x.multi_header = x.multi_header ? JSON.parse(x.multi_header) : undefined;
  122. x.calc_expr = x.calc_expr ? JSON.parse(x.calc_expr) : [];
  123. x.calc_order = x.calc_order ? x.calc_order.split(',') : [];
  124. });
  125. for (const d of datas) {
  126. if (d.spec_set) {
  127. d.specValue = await this.ctx.service.stdExtra.getAllDataByCondition({ columns: ['spec', 'value'], where: { list_id: d.spec_set }, orders:[['order', 'asc']]});
  128. } else {
  129. d.specValue = [];
  130. }
  131. }
  132. }
  133. async getAllTemplateDetail(pid, type) {
  134. const result = await this.getAllDataByCondition({
  135. where: { pid, type },
  136. orders: [['create_time', 'desc']]
  137. });
  138. await this.analysisTemplate(result);
  139. return result;
  140. }
  141. async getTemplate(id) {
  142. const result = await this.getDataById(id);
  143. await this.analysisTemplate(result);
  144. return result;
  145. }
  146. //-------------------------- 以下方法不可随意修改 --------------------------
  147. // 根据列设置计算spreadjs配置相关,方法命名须严格按照【方法_类型】命名
  148. _getFillRowCount(rowSpan) {
  149. let count = 0;
  150. rowSpan.forEach(x => {
  151. count = count + x ? x : 1;
  152. });
  153. return count;
  154. }
  155. _calcMultiColHeader(iCol, spreadCol, colTitle, multiHeader) {
  156. if (multiHeader) {
  157. const colSpan = [], rowSpan = [], title = [];
  158. for (let iRow = 0; iRow < multiHeader.headRows - 1; iRow ++) {
  159. const setSpan = multiHeader.headSpan.find(x => { return x.col === iCol && x.row === iRow; });
  160. if (setSpan) {
  161. colSpan.push(setSpan.colCount);
  162. rowSpan.push(setSpan.rowCount);
  163. title.push(setSpan.title);
  164. } else {
  165. const relaSpan = multiHeader.headSpan.find(x => { return iCol >= x.col && iCol < x.col + x.colCount && iRow >= x.row && iRow < x.row + x.rowCount; });
  166. if (relaSpan) {
  167. colSpan.push('');
  168. rowSpan.push('');
  169. title.push('');
  170. }
  171. }
  172. }
  173. if (colSpan.length === 0) {
  174. spreadCol.rowSpan = multiHeader.headRows + '';
  175. spreadCol.title = colTitle;
  176. } else {
  177. colSpan.push(1);
  178. rowSpan.push(multiHeader.headRows - this._getFillRowCount(rowSpan));
  179. title.push(colTitle);
  180. spreadCol.colSpan = colSpan.join('|');
  181. spreadCol.rowSpan = rowSpan.join('|');
  182. spreadCol.title = title.join('|');
  183. }
  184. } else {
  185. spreadCol.title = colTitle;
  186. }
  187. }
  188. _transExpr(expr, colSet) {
  189. if (!expr) return '';
  190. let newExpr = expr;
  191. colSet.forEach(x => {
  192. if (x.type === 'num' && x.calc_code) newExpr = newExpr.replace(new RegExp(x.calc_code, 'gm'), x.field);
  193. });
  194. return newExpr;
  195. }
  196. _calcSpreadCache_posCalc(colSet) {
  197. const relaConst = this.TemplateRela.posCalc;
  198. const result = JSON.parse(JSON.stringify(relaConst.EmptySpreadCache));
  199. for (const col of colSet) {
  200. const info = relaConst.ValidColInfo.find(x => { return x.key === col.type });
  201. const spreadCol = JSON.parse(JSON.stringify(relaConst.BaseSpreadColSetting[col.type]));
  202. spreadCol.field = col.field;
  203. info.valid.forEach(v => {
  204. if (v === 'unit' && col.unit) {
  205. spreadCol.title = col.title + '(' + col.unit + ')';
  206. } else if (v === 'expr') {
  207. spreadCol.expr = this._transExpr(col.expr, colSet);
  208. spreadCol.readOnly = true;
  209. } else {
  210. if (col[v] !== undefined) spreadCol[v] = col[v];
  211. }
  212. });
  213. if (col.calc_code) spreadCol.title = spreadCol.title + '\n' + col.calc_code;
  214. if (col.expr) spreadCol.title = spreadCol.title + '\n' + col.expr;
  215. result.cols.push(spreadCol);
  216. }
  217. const specCol = colSet.find(x => { return x.type === 'spec'; });
  218. if (specCol) {
  219. const relaSet = colSet.find(x => { return x.calc_code === specCol.rela_col; });
  220. const relaCol = result.cols.find(x => { return x.field === relaSet.field; });
  221. relaCol.readOnly = true;
  222. }
  223. return result;
  224. }
  225. _calcSpreadCache_cost(colSet, multiHeader) {
  226. const relaConst = this.TemplateRela.cost;
  227. const result = JSON.parse(JSON.stringify(relaConst.EmptySpreadCache));
  228. if (multiHeader) {
  229. result.headRows = multiHeader.headRows;
  230. result.headRowHeight = multiHeader.headRowHeight;
  231. }
  232. for (const [iCol, col] of colSet.entries()) {
  233. const info = relaConst.ValidColInfo.find(x => { return x.key === col.type });
  234. const spreadCol = JSON.parse(JSON.stringify(relaConst.BaseSpreadColSetting[col.type]));
  235. spreadCol.field = col.field;
  236. let colTitle = col.title;
  237. info.valid.forEach(v => {
  238. if (v === 'unit' && col.unit) {
  239. colTitle = colTitle + '(' + col.unit + ')';
  240. } else if (v === 'expr') {
  241. spreadCol.expr = this._transExpr(col.expr, colSet);
  242. spreadCol.readOnly = true;
  243. } else {
  244. if (col[v] !== undefined) spreadCol[v] = col[v];
  245. }
  246. });
  247. if (col.calc_code) {
  248. if (col.expr) {
  249. colTitle = colTitle + '\n' + col.calc_code + '(' + col.expr + ')';
  250. } else {
  251. colTitle = colTitle + '\n' + col.calc_code;
  252. }
  253. }
  254. this._calcMultiColHeader(iCol, spreadCol, colTitle, multiHeader);
  255. result.cols.push(spreadCol);
  256. }
  257. return result;
  258. }
  259. calcSpreadCache(type, colSet, multiHeader) {
  260. const funName = '_calcSpreadCache_' + type;
  261. if (this[funName]) return this[funName](colSet, multiHeader);
  262. return this.TemplateRela[type] ? this.TemplateRela[type].EmptySpreadCache : {};
  263. }
  264. async getCalcTestData_posCalc(colSet, count) {
  265. const result = [];
  266. const specCol = colSet.find(x => { return x.type === 'spec'; });
  267. const specSet = specCol && specCol.spec_set ? await this.ctx.service.stdExtra.getAllDataByCondition({ columns: ['spec', 'value'], where: { list_id: specCol.spec_set }, orders:[['order', 'asc']]}) : [];
  268. const relaCol = specCol && specCol.rela_col ? colSet.find(x => { return x.calc_code === specCol.rela_col; }) : null;
  269. for (let i = 0; i < count; i++) {
  270. const testData = {};
  271. colSet.forEach(x => {
  272. if (x.type === 'num') testData[x.field] = Math.max(Math.floor(Math.random()*10), 1);
  273. if (x.type === 'str') testData[x.field] = randomWord(true, 3, 6);
  274. });
  275. if (specCol && specSet.length > 0) {
  276. testData.spec = specSet[0].spec;
  277. if (relaCol) testData[relaCol.field] = specSet[0].value;
  278. }
  279. result.push(testData);
  280. }
  281. return result;
  282. }
  283. async getCalcTestData_cost(colSet, count) {
  284. const result = [];
  285. for (let i = 0; i < count; i++) {
  286. const testData = {};
  287. colSet.forEach(x => {
  288. if (x.type === 'num') testData[x.field] = Math.max(Math.floor(Math.random()*10), 1);
  289. if (x.type === 'str') testData[x.field] = randomWord(true, 3, 6);
  290. });
  291. result.push(testData);
  292. }
  293. return result;
  294. }
  295. async getCalcTestData(colSet, type, count = 1) {
  296. const funName = 'getCalcTestData_' + type;
  297. if (this[funName]) return await this[funName](colSet, count);
  298. return [];
  299. }
  300. async _checkTemplateUsed_posCalc(template) {
  301. const templates = template instanceof Array ? template : [template];
  302. templates.forEach(x => { x.used = []; x.used_count = 0; });
  303. const tender = await this.ctx.service.tender.getAllDataByCondition({ columns: ['id'], where: { project_id: templates[0].pid }});
  304. for (const t of tender) {
  305. const used = await this.ctx.service.ledgerExtra.getUsedCalcTemplate(t.id);
  306. templates.forEach(x => {
  307. const u = used.find(ut => { return ut.calc_template === x.id; });
  308. if (u) {
  309. x.used.push(t.id);
  310. x.used_count = this.ctx.helper.add(x.used_count + u.count);
  311. }
  312. });
  313. }
  314. }
  315. async _checkTemplateUsed_cost(template) {
  316. const templates = template instanceof Array ? template : [template];
  317. templates.forEach(x => { x.used = []; x.used_count = 0; });
  318. // todo 检查使用情况
  319. }
  320. async checkTemplateUsed(template, type) {
  321. if (!template || template.length === 0) return;
  322. const funName = '_checkTemplateUsed_' + type;
  323. if (this[funName]) return await this[funName](template);
  324. }
  325. // ----------------------------------------------------------------------
  326. async _addTemplate(name, type) {
  327. if (ValidTemplateType.indexOf(type) < 0) throw '新增的模板类型非法';
  328. const count = await this.count({pid: this.ctx.session.sessionProject.id, type});
  329. if (count > this.TemplateRela[type].ValidCount) throw '已达模板使用上限';
  330. const relaConst = this.TemplateRela[type];
  331. const insertData = {
  332. id: this.uuid.v4(),
  333. pid: this.ctx.session.sessionProject.id,
  334. // spid: this.ctx.subProject.id,
  335. // tid: this.ctx.tender.id,
  336. name: name || '新增计算模板',
  337. type,
  338. create_user_id: this.ctx.session.sessionUser.accountId,
  339. update_user_id: this.ctx.session.sessionUser.accountId,
  340. // 关键默认值
  341. col_set: '[]',
  342. spread_cache: JSON.stringify(relaConst.EmptySpreadCache),
  343. field_cache: '{}',
  344. decimal: JSON.stringify([{ def: 2 }]),
  345. };
  346. await this.db.insert(this.tableName, insertData);
  347. return await this.getTemplate(insertData.id);
  348. }
  349. async checkTemplateEdit(id) {
  350. const template = await this.getDataById(id);
  351. if (!template) throw '编辑的模板不存在';
  352. if (template.pid !== this.ctx.session.sessionProject.id) throw '模板不属于当前项目,请刷新后重试';
  353. if (template.create_user_id !== this.ctx.session.sessionUser.accountId) throw '非您创建的模板';
  354. if (template.is_locked) throw '模板已被锁定';
  355. await this.checkTemplateUsed(template, template.type);
  356. if (template.used.length > 0) throw '模板已被使用';
  357. return template;
  358. }
  359. _getCalcLeaf(cur, cols, parentField) {
  360. if (!cur || !cur.expr) return [];
  361. const codeReg = /num_[a-z]{1}/gm;
  362. const codeParam = cur.expr.match(codeReg);
  363. if (!codeParam || codeParam.length === 0) return [];
  364. const result = [...codeParam];
  365. for (const op of codeParam) {
  366. const relaCol = cols.find(x => { return x.field === op; });
  367. if (relaCol.field === cur.field || op === parentField) {
  368. result.push(op);
  369. } else {
  370. const sub = this._getCalcLeaf(relaCol, cols, cur.field);
  371. if (sub.length > 0) {
  372. result.push(...sub);
  373. } else {
  374. result.push(op);
  375. }
  376. }
  377. }
  378. return this.ctx.helper._.uniq(result);
  379. }
  380. _getCalcExpr(cols) {
  381. const calcOrder = [];
  382. for (const c of cols) {
  383. if (c.type !== 'Number') continue;
  384. calcOrder.push({ field: c.field, expr: c.expr || '', decimal: c.decimal });
  385. }
  386. for (const co of calcOrder) {
  387. co.leaf = this._getCalcLeaf(co, calcOrder);
  388. }
  389. calcOrder.sort((x, y) => {return x.leaf.length - y.leaf.length; });
  390. return calcOrder;
  391. }
  392. async _saveTemplate(id, data) {
  393. const org = await this.checkTemplateEdit(id);
  394. const updateData = { id };
  395. if (data.name !== undefined) updateData.name = data.name;
  396. if (data.col_set !== undefined || data.multi_header !== undefined) {
  397. await this.checkTemplateUsed(org, org.type);
  398. if (org.used.length > 0) throw '模板已使用,不可修改配置!';
  399. updateData.col_set = JSON.stringify(data.col_set);
  400. updateData.multi_header = JSON.stringify(data.multi_header);
  401. const spread_cache = this.calcSpreadCache(org.type, data.col_set, data.multi_header);
  402. updateData.spread_cache = JSON.stringify(spread_cache);
  403. const field_cache = {};
  404. data.col_set.forEach(x => { field_cache[x.field] = x.title; });
  405. updateData.field_cache = JSON.stringify(field_cache);
  406. const decimal = { def: 2 };
  407. spread_cache.cols.forEach(x => { if (x.decimal !== null && x.decimal !== undefined) decimal[x.field] = x.decimal; });
  408. updateData.decimal = JSON.stringify(decimal);
  409. const calc_expr = this._getCalcExpr(spread_cache.cols);
  410. updateData.calc_expr = JSON.stringify(calc_expr);
  411. updateData.calc_order = calc_expr.map(x => { return x.field; }).join(',');
  412. const specCol = data.col_set.find(x => { return x.field === 'spec' });
  413. if (specCol) {
  414. updateData.spec_set = specCol.spec_set;
  415. updateData.spec_rela = data.col_set.find(x => { return x.calc_code === specCol.rela_col; }).field;
  416. } else {
  417. updateData.spec_set = 0;
  418. updateData.spec_rela = '';
  419. }
  420. }
  421. await this.db.update(this.tableName, updateData);
  422. return await this.getTemplate(id);
  423. }
  424. async _delTemplate(id) {
  425. const template = await this.checkTemplateEdit(id);
  426. await this.db.delete(this.tableName, { id });
  427. return template;
  428. }
  429. async saveTemplate(data) {
  430. const result = {};
  431. if (data.add) result.add = await this._addTemplate(data.add.name, data.type);
  432. if (data.del) result.del = await this._delTemplate(data.del);
  433. if (data.update) result.update = await this._saveTemplate(data.update.id, data.update);
  434. return result;
  435. }
  436. async reCalcTemplate(id, force) {
  437. const data = await this.checkTemplateEdit(id);
  438. const updateData = { id };
  439. if (!force) {
  440. await this.checkTemplateUsed(data);
  441. if (data.used.length > 0) throw '模板已使用,不可修改配置!';
  442. }
  443. await this.analysisTemplate(data);
  444. updateData.col_set = JSON.stringify(data.col_set);
  445. updateData.multi_header = JSON.stringify(data.multi_header);
  446. const spread_cache = this.calcSpreadCache(data.type, data.col_set, data.multi_header);
  447. updateData.spread_cache = JSON.stringify(spread_cache);
  448. const field_cache = {};
  449. data.col_set.forEach(x => { field_cache[x.field] = x.title; });
  450. updateData.field_cache = JSON.stringify(field_cache);
  451. const decimal = { def: 2 };
  452. spread_cache.cols.forEach(x => { if (x.decimal !== null && x.decimal !== undefined) decimal[x.field] = x.decimal; });
  453. updateData.decimal = JSON.stringify(decimal);
  454. const calc_expr = this._getCalcExpr(spread_cache.cols);
  455. updateData.calc_expr = JSON.stringify(calc_expr);
  456. updateData.calc_order = calc_expr.map(x => { return x.field; }).join(',');
  457. const specCol = data.col_set.find(x => { return x.field === 'spec' });
  458. if (specCol) {
  459. updateData.spec_set = specCol.spec_set;
  460. updateData.spec_rela = data.col_set.find(x => { return x.calc_code === specCol.rela_col; }).field;
  461. } else {
  462. updateData.spec_set = 0;
  463. updateData.spec_rela = '';
  464. }
  465. await this.db.update(this.tableName, updateData);
  466. }
  467. _calcExpr(formula) {
  468. const percentReg = /((\d+)|((\d+)(\.\d+)))%/g;
  469. const percent = formula.match(percentReg);
  470. if (percent) {
  471. for (const p of percent) {
  472. const v = math.eval(p.replace(new RegExp('%', 'gm'), '/100'));
  473. formula = formula.replace(p, v);
  474. }
  475. }
  476. try {
  477. // 使用mathjs计算 math.eval('17259401.95*0.9') = 15533461.754999999,正确应为 15533461.755
  478. // const value = math.eval(formula);
  479. // 使用逆波兰法四则运算,可防止出现误差,但是只支持四则运算,不支持科学运算
  480. // const value = this.ctx.helper.calcExprStrRpn(formula);
  481. // 使用mathjs的大数运算,可支持所有
  482. const value = parseFloat(math.eval(formula));
  483. return Number.isFinite(value) ? value : 0;
  484. } catch(err) {
  485. return 0;
  486. }
  487. }
  488. calcByTemplate(data, updateData, orgData, calcExpr, updateExprInfo) {
  489. const codeReg = /num_[a-z]{1}/gm;
  490. for (const ce of calcExpr) {
  491. if (ce.expr) {
  492. let calcExpr = ce.expr;
  493. const codeParam = calcExpr.match(codeReg);
  494. for (const cp of codeParam) {
  495. calcExpr = calcExpr.replace(new RegExp(cp, 'gm'), data[cp] || orgData[cp] || 0);
  496. }
  497. if (updateExprInfo && updateExprInfo[ce.field]) updateExprInfo[ce.field] = calcExpr;
  498. try {
  499. data[ce.field] = this.ctx.helper.round(this._calcExpr(calcExpr.replace(new RegExp('%', 'gm'), '/100')), ce.decimal);
  500. } catch(err) {
  501. data[ce.field] = 0;
  502. }
  503. } else {
  504. if (updateData[ce.field] === undefined) continue;
  505. data[ce.field] = this.ctx.helper.round(updateData[ce.field], ce.decimal || 2);
  506. }
  507. }
  508. }
  509. }
  510. return CalcTmpl;
  511. };