'use strict'; /** * * * @author Mai * @date * @version */ const payType = { bqsf: 'bqsf', bqyf: 'bqyf', gcjl: 'gcjl', qtfk: 'qtfk', qtkk: 'qtkk' }; const defaultPays = [ { tree_id: 1, tree_pid: -1, tree_level: 1, tree_order: 1, tree_is_leaf: 1, tree_full_path: '1', pay_type: 'bqyf', is_minus: 0, is_fixed: 1, name: '本期应付', expr: '' }, { tree_id: 2, tree_pid: -1, tree_level: 1, tree_order: 2, tree_is_leaf: 1, tree_full_path: '2', pay_type: 'bqsf', is_minus: 0, is_fixed: 1, name: '本期实付', expr: '' }, { tree_id: 3, tree_pid: -1, tree_level: 1, tree_order: 3, tree_is_leaf: 0, tree_full_path: '3', pay_type: 'gcjl', is_minus: 0, is_fixed: 1, name: '工程计量款', expr: '' }, { tree_id: 4, tree_pid: -1, tree_level: 1, tree_order: 4, tree_is_leaf: 0, tree_full_path: '4', pay_type: 'qtfk', is_minus: 0, is_fixed: 1, name: '其他付款项', expr: '' }, { tree_id: 5, tree_pid: -1, tree_level: 1, tree_order: 5, tree_is_leaf: 0, tree_full_path: '5', pay_type: 'qtkk', is_minus: 1, is_fixed: 1, name: '其他扣款项', expr: '' }, { tree_id: 6, tree_pid: 3, tree_level: 2, tree_order: 1, tree_is_leaf: 1, tree_full_path: '3-6', pay_type: '', is_minus: 0, is_fixed: 0, name: '本期完成计量', expr: 'bqwc' }, { tree_id: 7, tree_pid: 4, tree_level: 2, tree_order: 1, tree_is_leaf: 1, tree_full_path: '4-7', pay_type: '', is_minus: 0, is_fixed: 0, name: '新增付款项', expr: '' }, { tree_id: 8, tree_pid: 5, tree_level: 2, tree_order: 1, tree_is_leaf: 1, tree_full_path: '5-8', pay_type: '', is_minus: 1, is_fixed: 0, name: '新增扣款项', expr: '' }, ]; const TreeService = require('../base/base_tree_service'); const Ledger = require('../lib/ledger'); const deadlineType = { phaseCount: { key: 'phaseCount', type: '计量期数', name: '合同支付期数' }, stageCount: { key: 'stageCount', type: '计量期数', name: '计量期数' }, gather: { key: 'gather', type: '计量金额', name: '累计完成金额' }, contract: { key: 'contract', type: '计量金额', name: '累计合同金额' }, qc: { key: 'qc', type: '计量金额', name: '累计变更金额' }, }; const math = require('mathjs'); math.config({ number: 'BigNumber', }); const validField = ['name', 'is_pause', 'is_gather', 'start_tp', 'start_expr', 'range_tp', 'range_expr', 'tp', 'expr', 'postil', 'dl_type', 'dl_value']; const infoField = ['name', 'postil']; const calcField = validField.filter(x => { return infoField.indexOf(x) < 0; }); class PayCalculator { constructor (ctx, phasePay) { this.ctx = ctx; this.phasePay = phasePay; this.percentReg = /((\d+)|((\d+)(\.\d+)))%/g; this.tenderInfo = ctx.tender.info; this.decimal = this.tenderInfo.decimal.pay ? this.tenderInfo.decimal.payTp : this.tenderInfo.decimal.tp; this.orderReg = /f\d+/ig; this.nodeReg = /<<[a-z0-9\-]+>>/ig; /* 以下变量在调用calculate方法后获得 this.add; this.pre; this.cur; this.yf; */ } /** * 获取 计算基数 * @returns {Promise} */ getCalcBase () { if (this.bases) return; const bases = this.ctx.service.phasePay.getPhasePayCalcBase(this.phasePay, this.tenderInfo); this.bases = bases.sort(function (a, b) { return a.sort - b.sort; }); for (const b of this.bases) { b.reg = new RegExp(b.code, 'igm'); } } getCalcAdd() { if (this.addBase) return; const calc_base = this.phasePay.calc_base; this.addBase = { contract_tp: this.ctx.helper.add(calc_base.contract_tp, calc_base.pre_contract_tp), qc_tp: this.ctx.helper.add(calc_base.qc_tp, calc_base.pre_qc_tp), gather_tp: this.ctx.helper.add(calc_base.gather_tp, calc_base.pre_gather_tp), }; } _calculateTpExpr(pay, pays) { let formula = pay.expr; const nodeParam = pay.expr.match(this.nodeReg); if (nodeParam) { for (const op of nodeParam) { const id = op.substring(2, op.length - 2); const payNode = pays.find(x => { return x.uuid === id; }); formula = formula.replace(op, payNode ? payNode.tp || 0 : 0); } } for (const b of this.bases) { const value = b.checkStart && (!pay.pre_used && pay.start_tp) ? this.ctx.helper.sub(this.addBase[pay.checkStart], pay.start_tp) : b.value; formula = formula.replace(b.reg, value); } const percent = formula.match(this.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; } } _calculateExpr(expr) { let formula = expr; for (const b of this.bases) { formula = formula.replace(b.reg, b.value ? b.value : 0); } const percent = formula.match(this.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; } } /** * 计算起扣金额、付(扣)款限额 * * @param {Array} pays - (标段)合同支付数据 */ calculateStartRangePrice (payTree) { for (const p of payTree.nodes) { // 上一期已计量的合同支付项,不予计算起扣金额、扣款限额 if (p.pre_used) continue; if (p.start_expr) { p.start_tp = this.ctx.helper.round(this._calculateExpr(p.start_expr), this.decimal); } else if (p.start_tp && !p.start_expr) { p.start_tp = this.ctx.helper.round(p.start_tp, this.decimal); } if (p.range_expr) { p.range_tp = this.ctx.helper.round(this._calculateExpr(p.range_expr), this.decimal); } else if (p.range_tp && !p.range_expr) { p.range_tp = this.ctx.helper.round(p.range_tp, this.decimal); } } } /** * 检查是否到达 计提期限 * @param pay */ _checkDeadline(pay) { switch (pay.dl_type) { case deadlineType.phaseCount.key: return this.phasePay.phase_order >= pay.dl_value; case deadlineType.stageCount.key: const maxStageOrder = this.ctx.helper._.max(this.phasePay.rela_stage.map(x => { return x.stage_order; })); return maxStageOrder >= pay.dl_value; case deadlineType.gather.key: case deadlineType.contract.key: case deadlineType.qc.key: const deadlineTp = this.addBase[pay.dl_type + '_tp']; return deadlineTp >= pay.dl_value; default : return false; } } getLeafOrder(data, pays, parentId) { if (!data || !data.expr) return []; const nodeParam = data.expr.match(this.nodeReg); if (!nodeParam || nodeParam.length === 0) return []; const result = [...nodeParam]; for (const op of nodeParam) { const id = op.substring(2, op.length - 2); if (id === data.uuid || id === parentId) { result.push(op); } else { const subPay = pays.find(x => { return x.uuid === id; }); const sub = this.getLeafOrder(subPay, pays, data.uuid); if (sub.length > 0) { result.push(...sub); } else { result.push(op); } } } return this.ctx.helper._.uniq(result); } sortPaysByCalc(payTree) { const calcArr = []; for (const pay of payTree.nodes) { if (pay.pay_type === payType.bqyf) { pay.calcSort = 3; } else if (pay.pay_type === payType.bqsf) { pay.calcSort = 4; } else if (pay.children && pay.children.length > 0) { pay.calcSort = 2; } else { pay.calcSort = 1; pay.calcLeaf = this.getLeafOrder(pay, payTree.nodes); } calcArr.push(pay); } calcArr.sort((x, y) => { const calcSort = x.calcSort - y.calcSort; if (calcSort) return calcSort; if (x.calcSort === 2) return -(x.tree_level - y.tree_level); return x.calcLeaf.length - y.calcLeaf.length; }); return calcArr; } _calculateYf(yf, pays) { yf.tp = 0; for (const p of pays) { if (p.pay_type || !p.is_gather || !p.tree_is_leaf) continue; yf.tp = !p.is_minus ? this.ctx.helper.add(yf.tp, p.tp) : this.ctx.helper.sub(yf.tp, p.tp); } const bqyf = this.bases.find(function (x) {return x.code === 'bqyf'}); if (bqyf) bqyf.value = yf.tp; } _calculateSf(sf, pays) { if (sf.expr) { sf.tp = this.ctx.helper.round(this._calculateExpr(sf.expr, pays), this.decimal); } else { const yf = pays.find(x => { return x.pay_type === payType.bqyf; }); sf.tp = yf.tp; } sf.tp = sf.range_tp ? Math.min(this.ctx.helper.sub(sf.range_tp, sf.pre_tp), sf.tp) : sf.tp; } _calculateGather(pay) { pay.tp = 0; for (const c of pay.children) { if (c.children && c.children.length > 0) this._calculateGather(c); if (!c.is_gather) continue; pay.tp = this.ctx.helper.add(pay.tp, c.tp); } } _calculateCommon(pay, pays) { // 暂停计量|未达起扣金额时,不计算 if (pay.is_pause || (pay.start_tp && this.addBase.gather_tp < pay.start_tp)) { pay.tp = 0; return; } if (this._checkDeadline(pay)) { pay.tp = this.ctx.helper.sub(pay.range_tp, pay.pre_tp); } else if (pay.expr) { pay.tp = this.ctx.helper.round(this._calculateTpExpr(pay, pays), this.decimal); } else { pay.tp = this.ctx.helper.round(pay.tp || 0, this.decimal); } pay.tp = pay.range_tp ? Math.min(this.ctx.helper.sub(pay.range_tp, pay.pre_tp), pay.tp) : pay.tp; } /** * 计算本期、截止本期金额 * @param {Array} pays - (标段&期)合同支付数据 */ calculate(pays) { for (const p of pays) { switch (p.pay_type) { case payType.bqyf: this._calculateYf(p, pays); break; case payType.bqsf: this._calculateSf(p, pays); break; case payType.gcjl: case payType.qtfk: case payType.qtkk: this._calculateGather(p); break; default: this._calculateCommon(p, pays); break; } p.end_tp = this.ctx.helper.add(p.pre_tp, p.tp); } } calculateAll(payTree) { payTree.nodes.forEach(x => { x.org_tp = x.tp || 0; x.org_start_tp = x.start_tp || 0; x.org_range_tp = x.range_tp || 0; x.org_end_tp = x.end_tp || 0; }); this.getCalcBase(); this.getCalcAdd(); this.calculateStartRangePrice(payTree); const calcPays = this.sortPaysByCalc(payTree); this.calculate(calcPays); payTree.nodes.forEach(p => { p.calcUpdate = !this.ctx.helper.numEqual(p.org_tp, p.tp) || !this.ctx.helper.numEqual(p.org_end_tp, p.end_tp) || !this.ctx.helper.numEqual(p.org_start_tp, p.start_tp) || !this.ctx.helper.numEqual(p.org_range_tp, p.range_tp) ; }); } } class PhasePayDetail extends TreeService { /** * 构造函数 * * @param {Object} ctx - egg全局变量 * @param {String} tableName - 表名 * @return {void} */ constructor(ctx, setting) { super(ctx, { mid: 'master_id', kid: 'tree_id', pid: 'tree_pid', order: 'tree_order', level: 'tree_level', isLeaf: 'tree_is_leaf', fullPath: 'tree_full_path', keyPre: '', uuid: true, }); this.tableName = 'phase_pay_detail'; this.deadlineType = deadlineType; } getMasterKey(phasePay) { return phasePay.curTimes ? `${phasePay.id}-${phasePay.curTimes}-${phasePay.curSort}` : `${phasePay.id}-${phasePay.audit_times}-${phasePay.audit_max_sort}`; } async initPhaseDataEmpty(conn, phasePay) { const user_id = this.ctx.session.sessionUser.accountId; const insertData = []; for (const dp of defaultPays) { insertData.push({ tid: phasePay.tid, phase_id: phasePay.id, create_phase_id: phasePay.id, master_id: phasePay.id + '-1-0', create_user_id: user_id, update_user_id: user_id, uuid: this.uuid.v4(), ...dp, }); } await conn.insert(this.tableName, insertData); } async initPhaseDataByPre(conn, phasePay, prePhase) { const preData = await this.getAllDataByCondition({ where: { phase_id: prePhase.id, audit_times: prePhase.audit_times, audit_sort: audit_max_sort }, }); for (const pd of preData) { delete pd.id; pd.phase_id = phasePay.id; pd.master_id = phasePay.id + '-1-0'; pd.audit_times = 1; pd.audit_sort = 0; } await conn.insert(this.tableName, preData); } async initPhaseDataByAudit(conn, phasePay, newTimes, newSort) { const preData = await conn.select(this.tableName, { where: { master_id: this.getMasterKey(phasePay)}}); for (const pd of preData) { delete pd.id; pd.master_id = `${phasePay.id}-${newTimes}-${newSort}`; pd.audit_times = newTimes; pd.audit_sort = newSort; } await conn.insert(this.tableName, preData); } async initPhaseDataByAuditCancel(conn, phasePay, oldTimes, oldSort, newTimes, newSort) { const masterId = `${phasePay.id}-${oldTimes}-${oldSort}`; const preData = await conn.select(this.tableName, { where: { master_id: masterId } }); for (const pd of preData) { delete pd.id; pd.master_id = `${phasePay.id}-${newTimes}-${newSort}`; pd.audit_times = newTimes; pd.audit_sort = newSort; } await conn.insert(this.tableName, preData); } async initPhaseData(conn, phasePay, prePhase){ if (!conn) throw '内部错误'; if (prePhase) { await this.initPhaseDataByPre(conn, phasePay, prePhase); } else { await this.initPhaseDataEmpty(conn, phasePay); } } async getDetailData(phasePay) { return await this.getAllDataByCondition({ where: { master_id: this.getMasterKey(phasePay)} }); } calculate(phasePay, details) { const payTree = new Ledger.baseTree(this.ctx, { id: 'tree_id', pid: 'tree_pid', order: 'tree_order', level: 'tree_level', isLeaf: 'tree_is_leaf', fullPath: 'tree_full_path', rootId: -1, calcField: [], }); payTree.loadDatas(details); const payCalculator = new PayCalculator(this.ctx, phasePay); payCalculator.calculateAll(payTree); return payTree.getDefaultDatas(); } getPayTp(datas, type) { const pay = datas.find(x => { return x.pay_type === type }); return pay ? pay.tp || 0 : 0; } getPaySum(datas) { const result = {}; result.yf_tp = this.getPayTp(datas, payType.bqyf); result.sf_tp = this.getPayTp(datas, payType.bqsf); result.calc_tp = this.getPayTp(datas, payType.gcjl); result.pay_tp = this.ctx.helper.add(this.getPayTp(datas, payType.qtfk), result.calc_tp); result.cut_tp = this.getPayTp(datas, payType.qtkk); return result; } async calculateSave(phasePay, transaction) { const details = await this.getDetailData(phasePay); if (details.length === 0) return false; const calcResult = this.calculate(phasePay, details); const updateData = calcResult.filter(x => { return x.calcUpdate; }).map(x => { return { id: x.id, tp: x.tp, start_tp: x.start_tp, range_tp: x.range_tp, end_tp: x.end_tp }; }); if (updateData.length === 0) return this.getPaySum(calcResult); if (transaction) { await transaction.updateRows(this.tableName, updateData); } else { await this.defaultUpdateRows(updateData); } return this.getPaySum(calcResult); } _getDefaultData(data, phasePay, parent) { data.uuid = this.uuid.v4(); data.tid = phasePay.tid; data.phase_id = phasePay.id; data.create_user_id = this.ctx.session.sessionUser.accountId; data.update_user_id = this.ctx.session.sessionUser.accountId; data.audit_times = phasePay.audit_times; data.audit_sort = phasePay.audit_max_sort; data.master_id = this.getMasterKey(phasePay); if (parent) { data.is_minus = parent.is_minus; } } async addChild(phasePay, select, count = 1) { if (select.pay_type === payType.bqsf || select.pay_type === payType.bqyf) throw '不可新增子项'; if (select.tree_level >= 2) throw '不可新增子项'; const masterId = this.getMasterKey(phasePay); const children = await this.getChildrenByParentId(masterId, select[this.setting.kid]); const maxId = await this._getMaxLid(masterId); const insertData = []; for (let i = 1; i <= count ; i++) { const data = { tree_id: maxId + i, tree_pid: select.tree_id, tree_order: children.length + i, tree_level: select.tree_level + 1, tree_is_leaf: 1 }; data.tree_full_path = select.tree_full_path + '-' + data.tree_id; this._getDefaultData(data, phasePay, select); insertData.push(data); } const conn = await this.db.beginTransaction(); try { const result = await conn.insert(this.tableName, insertData); if (children.length === 0) { await conn.update(this.tableName, { id: select.id, is_leaf: false }); } await conn.commit(); } catch(err) { conn.rollback(); throw err; } this._cacheMaxLid(masterId, maxId + 1); // 查询应返回的结果 const resultData = {}; resultData.create = await this.getNextsData(masterId, select.tree_id, children.length); if (children.length === 0) resultData.update = await this.getDataByKid(masterId, select.tree_id); return resultData; } async addNext(phasePay, select, count = 1) { const masterId = this.getMasterKey(phasePay); this.transaction = await this.db.beginTransaction(); try { if (select) await this._updateChildrenOrder(masterId, select[this.setting.pid], select[this.setting.order] + 1, count); const newDatas = []; const maxId = await this._getMaxLid(masterId); for (let i = 1; i < count + 1; i++) { const newData = {}; newData[this.setting.kid] = maxId + i; newData[this.setting.pid] = select ? select[this.setting.pid] : this.rootId; newData[this.setting.level] = select ? select[this.setting.level] : 1; newData[this.setting.order] = select ? select[this.setting.order] + i : i; newData[this.setting.fullPath] = newData[this.setting.level] > 1 ? select[this.setting.fullPath].replace('-' + select[this.setting.kid], '-' + newData[this.setting.kid]) : newData[this.setting.kid] + ''; newData[this.setting.isLeaf] = 1; this._getDefaultData(newData, phasePay); newDatas.push(newData); } const insertResult = await this.transaction.insert(this.tableName, newDatas); this._cacheMaxLid(masterId, maxId + count); if (insertResult.affectedRows !== count) throw '新增节点数据错误'; await this.transaction.commit(); this.transaction = null; } catch (err) { await this.transaction.rollback(); this.transaction = null; throw err; } if (select) { const createData = await this.getChildBetween(masterId, select[this.setting.pid], select[this.setting.order], select[this.setting.order] + count + 1); const updateData = await this.getNextsData(masterId, select[this.setting.pid], select[this.setting.order] + count); return {create: createData, update: updateData}; } else { const createData = await this.getChildBetween(masterId, -1, 0, count + 1); return {create: createData}; } } async addDetailNode(phasePay, targetId, count = 1) { if (!phasePay) return null; const select = targetId ? await this.getDataByKid(this.getMasterKey(phasePay), targetId) : null; if (targetId && !select) throw '新增节点数据错误'; if (select[this.setting.level] === 1) { return await this.addChild(phasePay, select, count); } else { return await this.addNext(phasePay, select, count); } } async upMoveDetailNode(phasePay, targetId, count = 1) { const masterId = this.getMasterKey(phasePay); return await this.upMoveNode(masterId, targetId, count); } async downMoveDetailNode(phasePay, targetId, count = 1) { const masterId = this.getMasterKey(phasePay); return await this.downMoveNode(masterId, targetId, count); } async deleteDetailNode(phasePay, targetId, count = 1) { const masterId = this.getMasterKey(phasePay); return await this.delete(masterId, targetId, count); } _filterValidField(id, data) { const ud = { id }; for (const prop in data) { if (validField.indexOf(prop) >= 0) { ud[prop] = data[prop]; } } return ud; } checkCalc(data) { const datas = data instanceof Array ? data : [data]; for (const d of datas) { for (const prop in d) { if (calcField.indexOf(prop) >= 0) return true; } } return false; } async updateDetail(phasePay, data) { const masterId = this.getMasterKey(phasePay); if (Array.isArray(data)) { const orgData = await this.getAllDataByCondition({ where: { id: data.map(d => { return d.id; }) } }); const updateDatas = []; for (const d of data) { const node = orgData.find(x => { return x.id === d.id; }); if (!node || masterId !== node[this.setting.mid]) throw '提交数据错误'; const ud = this._filterValidField(node.id, d); ud.update_user_id = this.ctx.session.sessionUser.accountId; updateDatas.push(ud); } if (updateDatas.length > 0) await this.db.updateRows(this.tableName, updateDatas); } else { const node = await this.getDataById(data.id); if (!node || masterId !== node[this.setting.mid]) throw '提交数据错误'; const updateData = this._filterValidField(node.id, data); updateData.update_user_id = this.ctx.session.sessionUser.accountId; await this.db.update(this.tableName, updateData); } } } module.exports = PhasePayDetail;