| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618 | '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<void>}     */    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: prePhase.audit_max_sort },        });        for (const pd of preData) {            delete pd.id;            pd.tp = 0;            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;
 |