Quellcode durchsuchen

台账修订,清单对比

MaiXinRong vor 4 Jahren
Ursprung
Commit
05c67f0e78

+ 4 - 4
app/controller/revise_controller.js

@@ -985,6 +985,7 @@ module.exports = app => {
                     } else {
                         return await this._loadLastStagePosData(ctx);
                     }
+                case 'dealBills': return await ctx.service.dealBills.getAllDataByCondition({where: {tender_id: ctx.tender.id}});
             }
         }
 
@@ -997,7 +998,6 @@ module.exports = app => {
                 const filter = data.filter ? data.filter.split(';') : [];
 
                 const responseData = { err: 0, msg: '', data: {} };
-                console.log(filter);
                 for (const f of filter) {
                     if (!f) continue;
                     responseData.data[f] = await this._loadDataByFilter(ctx, f);
@@ -1009,15 +1009,15 @@ module.exports = app => {
             }
         }
 
-        async gather(ctx) {
+        async gclCompare(ctx) {
             const revise = await ctx.service.ledgerRevise.getLastestRevise(ctx.tender.id);
             if (!revise) throw '台账修订数据有误';
 
             const renderData = {
                 revise,
-                jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.revise.gather),
+                jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.revise.gclCompare),
             };
-            await this.layout('revise/gather.ejs', renderData);
+            await this.layout('revise/gcl_compare.ejs', renderData);
         }
 
         async bwtz(ctx) {

+ 220 - 0
app/public/js/revise_gcl_compare.js

@@ -0,0 +1,220 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date 2020/10/28
+ * @version
+ */
+const showTools = function (show) {
+    const left = $('#left-view'), right = $('#right-view'), parent = left.parent();
+    if (show) {
+        right.show();
+        autoFlashHeight();
+        /**
+         * right.show()后, parent被撑开成2倍left.height, 导致parent.width减少了10px
+         * 第一次left.width调整后,parent的缩回left.height, 此时parent.width又增加了10px
+         * 故需要通过最终的parent.width再计算一次left.width
+         *
+         * Q: 为什么不通过先计算left.width的宽度,以避免计算两次left.width?
+         * A: 右侧工具栏不一定显示,当右侧工具栏显示过一次后,就必须使用parent和right来计算left.width
+         *
+         */
+            //left.css('width', parent.width() - right.outerWidth());
+            //left.css('width', parent.width() - right.outerWidth());
+        const percent = 100 - right.outerWidth() /parent.width() * 100;
+        left.css('width', percent + '%');
+    } else {
+        right.hide();
+        left.css('width', '100%');
+    }
+};
+
+$(document).ready(() => {
+    showTools(true);
+    autoFlashHeight();
+    const gclSpread = SpreadJsObj.createNewSpread($('#gcl-spread')[0]);
+    const gclSpreadSetting = {
+        cols: [
+            {title: '清单编号', colSpan: '1', rowSpan: '2', field: 'b_code', hAlign: 0, width: 120, formatter: '@'},
+            {title: '名称', colSpan: '1', rowSpan: '2', field: 'name', hAlign: 0, width: 230, formatter: '@'},
+            {title: '单位', colSpan: '1', rowSpan: '2', field: 'unit', hAlign: 1, width: 60, formatter: '@', cellType: 'unit'},
+            {title: '单价', colSpan: '1', rowSpan: '2', field: 'unit_price', hAlign: 2, width: 80, type: 'Number'},
+            {title: '签约清单|数量', colSpan: '2|1', rowSpan: '1|1', field: 'deal_bills_qty', hAlign: 2, width: 80, type: 'Number'},
+            {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'deal_bills_tp', hAlign: 2, width: 80, type: 'Number'},
+            {title: '台账修订|数量', colSpan: '2|1', rowSpan: '1|1', field: 'new_quantity', hAlign: 2, width: 80, type: 'Number'},
+            {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'new_total_price', hAlign: 2, width: 80, type: 'Number'},
+            {title: '台账|数量', colSpan: '2|1', rowSpan: '1|1', field: 'org_quantity', hAlign: 2, width: 80, type: 'Number'},
+            {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'org_total_price', hAlign: 2, width: 80, type: 'Number'},
+        ],
+        emptyRows: 0,
+        headRows: 2,
+        headRowHeight: [25, 25],
+        headColWidth: [30],
+        defaultRowHeight: 21,
+        headerFont: '12px 微软雅黑',
+        font: '12px 微软雅黑',
+        readOnly: true,
+        localCache: {
+            key: 'ledger-gather-gcl',
+            colWidth: true,
+        },
+        getColor: function (sheet, data, row, col, defaultColor) {
+            return data
+                ? (data.differ ? '#FFE699' : '#ffffff')
+                : '#ffffff';
+        }
+    };
+    if (thousandth) sjsSettingObj.setTpThousandthFormat(gclSpreadSetting);
+    SpreadJsObj.initSheet(gclSpread.getActiveSheet(), gclSpreadSetting);
+    const gclSheet = gclSpread.getActiveSheet();
+    const leafXmjSpread = SpreadJsObj.createNewSpread($('#leaf-xmj-spread')[0]);
+    const leafXmjSpreadSetting = {
+        cols: [
+            {title: '项目节编号', colSpan: '1', rowSpan: '2', field: 'code', hAlign: 0, width: 120, formatter: '@'},
+            {title: '修订台账|数量', colSpan: '1', rowSpan: '1|1', field: 'new_quantity', hAlign: 2, width: 80, type: 'Number'},
+            {title: '原台账|数量', colSpan: '1', rowSpan: '1|1', field: 'org_quantity', hAlign: 2, width: 80, type: 'Number'},
+            {title: '单位工程', colSpan: '1', rowSpan: '2', field: 'dwgc', hAlign: 0, width: 100, formatter: '@'},
+            {title: '分部工程', colSpan: '1', rowSpan: '2', field: 'fbgc', hAlign: 0, width: 100, formatter: '@'},
+            {title: '分项工程', colSpan: '1', rowSpan: '2', field: 'fxgc', hAlign: 0, width: 100, formatter: '@'},
+            {title: '细目', colSpan: '1', rowSpan: '2', field: 'jldy', hAlign: 0, width: 100, formatter: '@'},
+            {title: '计量单元', colSpan: '1', rowSpan: '2', field: 'bwmx', hAlign: 0, width: 100, formatter: '@'},
+            {title: '图册号', colSpan: '1', rowSpan: '2', field: 'drawing_code', hAlign: 0, width: 120, formatter: '@'},
+        ],
+        emptyRows: 0,
+        headRows: 1,
+        headRowHeight: [25, 25],
+        headColWidth: [30],
+        defaultRowHeight: 21,
+        headerFont: '12px 微软雅黑',
+        font: '12px 微软雅黑',
+        readOnly: true,
+        localCache: {
+            key: 'ledger-gather-leafXmj',
+            colWidth: true,
+        },
+    };
+    if (thousandth) sjsSettingObj.setTpThousandthFormat(leafXmjSpreadSetting);
+    SpreadJsObj.initSheet(leafXmjSpread.getActiveSheet(), leafXmjSpreadSetting);
+    const leafXmjSheet = leafXmjSpread.getActiveSheet();
+
+    let gclData = [];
+    // 获取项目节数据
+    function loadLeafXmjData(iGclRow) {
+        const gcl = gclData[iGclRow];
+        SpreadJsObj.resetTopAndSelect(leafXmjSheet);
+        if (gcl) {
+            SpreadJsObj.loadSheetData(leafXmjSheet, SpreadJsObj.DataType.Data, gcl.leafXmjs);
+        } else {
+            SpreadJsObj.loadSheetData(leafXmjSheet, SpreadJsObj.DataType.Data, []);
+        }
+    }
+    // 切换清单行,读取所属项目节数据
+    gclSpread.getActiveSheet().bind(spreadNS.Events.SelectionChanged, function (e, info) {
+        const iNewRow = info.newSelections[0].row;
+        if (!info.oldSelections || iNewRow !== info.oldSelections[0].row) {
+            loadLeafXmjData(iNewRow);
+        }
+    });
+
+    function generateChapterHtml(data) {
+        const html = [];
+        if (data) {
+            for (const d of data) {
+                if (['1000', '1100', '1200', '1300'].indexOf(d.code) >= 0) {
+                    if (checkZero(d.total_price) && checkZero(d.deal_bills_tp)) {
+                        continue;
+                    }
+                }
+                html.push('<tr>');
+                if (d.code) {
+                    html.push('<td>', d.code, '</td>');
+                    html.push('<td>', d.name, '</td>');
+                } else {
+                    html.push('<td colspan="2">', d.name, '</td>');
+                }
+                html.push('<td class="text-right">', d.deal_bills_tp ? d.deal_bills_tp : '', '</td>');
+                html.push('<td class="text-right">', d.new_total_price ? d.new_total_price : '', '</td>');
+                html.push('<td class="text-right">', d.org_total_price ? d.org_total_price : '', '</td>');
+                html.push('</tr>');
+            }
+        }
+        $('#chapter-list').html(html.join(''));
+    }
+
+    postData('/tender/' + window.location.pathname.split('/')[2] + '/revise/load', {filter: 'bills;pos;reviseBills;revisePos;dealBills'}, function (data) {
+        const setting = {
+            tree: {
+                id: 'ledger_id',
+                pid: 'ledger_pid',
+                order: 'order',
+                level: 'level',
+                rootId: -1,
+                keys: ['id', 'tender_id', 'ledger_id'],
+                stageId: 'id',
+            },
+            pos: { id: 'id', ledgerId: 'lid' },
+            billsFields: ['quantity', 'total_price'],
+            posFields: ['quantity'],
+            chapterFields: ['total_price'],
+        };
+        gclCompareModel.init(gclData, chapter);
+        setting.prefix = 'new_';
+        gclCompareModel.gatherLedgerData(data.reviseBills, data.revisePos, setting);
+        setting.prefix = 'org_';
+        gclCompareModel.gatherLedgerData(data.bills, data.pos, setting);
+        gclCompareModel.gatherDealBills(data.dealBills);
+        gclCompareModel.checkDiffer();
+        SpreadJsObj.loadSheetData(gclSheet, SpreadJsObj.DataType.Data, gclData);
+        loadLeafXmjData(0);
+
+        const chapterData = gclCompareModel.chapterData();
+        generateChapterHtml(chapterData);
+    }, null, true);
+
+    // 展开收起附件
+    $('a', '.right-nav').bind('click', function () {
+        const tab = $(this), tabPanel = $(tab.attr('content'));
+        if (!tab.hasClass('active')) {
+            $('a', '.side-menu').removeClass('active');
+            $('.tab-content .tab-select-show').removeClass('active');
+            tab.addClass('active');
+            tabPanel.addClass('active');
+            showTools(tab.hasClass('active'));
+        } else {
+            tab.removeClass('active');
+            tabPanel.removeClass('active');
+            showTools(tab.hasClass('active'));
+        }
+        gclSpread.refresh();
+        leafXmjSpread.refresh();
+    });
+    $.subMenu({
+        menu: '#sub-menu', miniMenu: '#sub-mini-menu', miniMenuList: '#mini-menu-list',
+        toMenu: '#to-menu', toMiniMenu: '#to-mini-menu',
+        key: 'menu.1.0.0',
+        miniHint: '#sub-mini-hint', hintKey: 'menu.hint.1.0.1',
+        callback: function (info) {
+            if (info.mini) {
+                $('.panel-title').addClass('fluid');
+                $('#sub-menu').removeClass('panel-sidebar');
+            } else {
+                $('.panel-title').removeClass('fluid');
+                $('#sub-menu').addClass('panel-sidebar');
+            }
+            autoFlashHeight();
+            gclSpread.refresh();
+            leafXmjSpread.refresh();
+        }
+    });
+    $.divResizer({
+        select: '#main-resize',
+        callback: function () {
+            gclSpread.refresh();
+            let bcontent = $(".bcontent-wrap") ? $(".bcontent-wrap").height() : 0;
+            $(".sp-wrap").height(bcontent-30);
+            leafXmjSpread.refresh();
+        }
+    });
+});

+ 407 - 0
app/public/js/shares/gcl_gather_compare.js

@@ -0,0 +1,407 @@
+'use strict';
+
+/**
+ *
+ * 清单汇总对比(需使用 decimal.min.js, zh_calc.js, path_tree.js, lodash.js)
+ *
+ * @author Mai
+ * @date 2020/10/28
+ * @version
+ */
+
+const gclCompareModel = (function () {
+    const leafXmjs = [], mergeChar = ';';
+    let gclList, gclChapter, otherChapter;
+    let ledgerSetting, gsTree;
+
+    function gatherfields(obj, src, fields, prefix = '') {
+        if (obj && src) {
+            for (const f of fields) {
+                obj[prefix + f] = ZhCalc.add(obj[prefix + f], src[f]);
+            }
+        }
+    }
+    /**
+     * 新建 清单汇总节点
+     * @param node - 最底层 工程量清单节点
+     * @returns {obj}
+     */
+    function newGclNode(node) {
+        const gcl = {
+            b_code: node.b_code,
+            name: node.name,
+            unit: node.unit,
+            unit_price: node.unit_price,
+            leafXmjs: [],
+        };
+        gclList.push(gcl);
+        return gcl;
+    }
+
+    /**
+     * 获取清单汇总节点
+     *
+     * @param node - 最底层清单节点
+     * @returns {*}
+     */
+    function getGclNode(node) {
+        const gcl = gclList.find(function (g) {
+            return g.b_code === node.b_code &&
+                (g.name || node.name ? g.name === node.name : true) &&
+                (g.unit || node.unit ? g.unit === node.unit : true) &&
+                checkZero(ZhCalc.sub(g.unit_price, node.unit_price));
+        });
+        if (gcl) {
+            return gcl
+        } else {
+            return newGclNode(node);
+        }
+    }
+
+    /**
+     * 检查 text 是否是Peg
+     * e.g. K123+000(true) Kab+123(false) K123.234+234(false) K12+324.234(true)
+     *
+     * @param text
+     * @returns {*}
+     * @constructor
+     */
+    function CheckPeg(text) {
+        const pegReg = /[a-zA-Z]?[kK][0-9]+[++][0-9]{3}([.][0-9]+)?/;
+        return pegReg.test(text);
+    }
+
+    /**
+     * 获取 桩号节点
+     * @param node - 检索起始节点
+     * @returns {*}
+     */
+    function getPegNode (node) {
+        if (node) {
+            if (CheckPeg(node.name)) {
+                return node;
+            } else {
+                const parent = gsTree.getParent(node);
+                return parent ? getPegNode(parent) : null;
+            }
+        }
+    }
+
+    /**
+     * 获取节点的第N层父节点
+     *
+     * @param node - 节点(检索起点)
+     * @param level - 第N层
+     * @returns {*}
+     */
+    function getNodeByLevel(node, level) {
+        let cur = node;
+        while (cur && cur.level > level) {
+            cur = gsTree.getParent(cur);
+        }
+        return cur;
+    }
+
+    /**
+     * 获取 单位工程
+     *
+     * @param xmj - 计量单元(最底层项目节)
+     * @returns {string}
+     */
+    function getDwgc(peg, xmj) {
+        if (peg) {
+            return peg.name;
+        } else {
+            const node = getNodeByLevel(xmj, 2);
+            return node ? node.name : '';
+        }
+    }
+
+    /**
+     * 获取 分部工程
+     *
+     * @param peg - 桩号节点
+     * @param xmj - 计量单元(最底层项目节)
+     * @returns {string}
+     */
+    function getFbgc(peg, xmj) {
+        if (peg && peg.id !== xmj.id) {
+            const node = getNodeByLevel(xmj, peg.level + 1);
+            return node ? node.name : '';
+        } else {
+            const node = getNodeByLevel(xmj, 3);
+            return node ? node.name : '';
+        }
+    }
+
+    /**
+     * 获取 分项工程
+     *
+     * @param peg - 桩号节点
+     * @param xmj - 计量单元(最底层项目节)
+     * @returns {string}
+     */
+    function getFxgc(peg, xmj) {
+        if (!peg) {
+            const node = getNodeByLevel(xmj, 4);
+            return node ? node.name : '';
+        } else if (peg.id === xmj.id) {
+            if (xmj.level > 4) {
+                let value = '';
+                for (let level = 4; level < xmj.level; level++) {
+                    const node = getNodeByLevel(xmj, level);
+                    value = value === '' ? node.name : value + mergeChar + node.name;
+                }
+                return value;
+            } else {
+                return '';
+            }
+        } else {
+            if (peg.level + 2 < xmj.level) {
+                let value = '';
+                for (let level = peg.level + 2; level < xmj.level; level++) {
+                    const node = getNodeByLevel(xmj, level);
+                    value = value === '' ? node.name : value + mergeChar + node.name;
+                }
+                return value;
+            } else {
+                return '';
+            }
+        }
+    }
+
+    /**
+     * 新建 最底层项目节 缓存数据
+     * @param leafXmj
+     * @returns {{id, code: *|string[], jldy, fbgc: string, fxgc: string, dwgc: string, bwmx: string, drawing_code: string}}
+     */
+    function newCacheLeafXmj(leafXmj) {
+        const peg = getPegNode(leafXmj);
+        const cacheLX = {
+            id: leafXmj.id,
+            code: leafXmj.code,
+            jldy: leafXmj.name,
+            fbgc: getFbgc(peg, leafXmj),
+            fxgc: getFxgc(peg, leafXmj),
+            dwgc: getDwgc(peg, leafXmj),
+            drawing_code: leafXmj.drawing_code,
+        };
+        leafXmjs.push(cacheLX);
+        return cacheLX;
+    }
+
+    /**
+     * 获取缓存的最底层项目节数据
+     *
+     * @param leafXmj - 最底层项目节
+     * @returns {*}
+     */
+    function getCacheLeafXmj(leafXmj) {
+        const cacheLX = leafXmjs.find(function (lx) {
+            return lx.id === leafXmj.id;
+        });
+        if (!cacheLX) {
+            return newCacheLeafXmj(leafXmj);
+        } else {
+            return cacheLX;
+        }
+    }
+
+    /**
+     * 汇总节点
+     * @param node - 最底层 工程量清单 节点
+     * @param leafXmj - 所属 最底层 项目节
+     */
+    function loadGatherGclNode(node, leafXmj, gsPos) {
+        const gcl = getGclNode(node);
+        gatherfields(gcl, node, ledgerSetting.billsFields, ledgerSetting.prefix);
+        const cacheLeafXmj = getCacheLeafXmj(leafXmj);
+        const posRange = gsPos.getLedgerPos(node.id);
+        const detail = posRange && posRange.length > 0 ? posRange : [node];
+        for (const d of detail) {
+            const dx = _.assign({}, cacheLeafXmj);
+            gatherfields(dx, d, ledgerSetting.posFields, ledgerSetting.prefix);
+            dx.gcl_id = node.id;
+            if (d.name !== node.name) {
+                dx.bwmx = d.name;
+                dx.mx_id = d.id;
+            }
+            if (d.drawing_code) {
+                dx.drawing_code = d.drawing_code;
+            }
+            gcl.leafXmjs.push(dx);
+        }
+    }
+
+    /**
+     * (递归)汇总树节点
+     * @param nodes - 汇总节点列表
+     * @param leafXmj - 汇总节点所属的底层项目节
+     */
+    function recursiveGatherGclData(nodes, leafXmj, gsPos) {
+        for (const node of nodes) {
+            if (node.b_code) {
+                if (node.children.length > 0) {
+                    recursiveGatherGclData(node.children, leafXmj, gsPos);
+                } else {
+                    loadGatherGclNode(node, leafXmj, gsPos);
+                }
+            } else if (node.children.length > 0) {
+                recursiveGatherGclData(node.children, node, gsPos);
+            }
+        }
+    }
+
+    function _getCalcChapter(chapter) {
+        const gclChapter = [], otherChapter = [];
+        let serialNo = 1;
+        for (const c of chapter) {
+            const cc = { code: c.code, name: c.name, cType: 1 };
+            cc.serialNo = serialNo++;
+            cc.filter = '^[^0-9]*' + c.code.substr(0, c.code.length - 2) + '[0-9]{2}-';
+            gclChapter.push(cc);
+        }
+        gclChapter.push({ name: '未计入章节清单合计', cType: 21, serialNo: serialNo++, });
+        otherChapter.push({ name: '清单小计(A)', cType: 11, serialNo: serialNo++ });
+        otherChapter.push({ name: '非清单项费用(B)', cType: 31, serialNo: serialNo++ });
+        otherChapter.push({ name: '合计(C=A+B)', cType: 41, serialNo: serialNo });
+        return [gclChapter, otherChapter];
+    }
+
+    function _gatherChapterFields(chapter, data, fields) {
+        for (const f of fields) {
+            chapter[f] = ZhCalc.add(chapter[f], data[f]);
+        }
+    }
+
+    function _getGclChapter(chapter, data) {
+        for (const c of chapter) {
+            if (c.filter) {
+                const reg = new RegExp(c.filter);
+                if (reg.test(data.b_code)) {
+                    return c;
+                }
+            } else {
+                return c;
+            }
+        }
+    }
+
+    function _gatherChapter() {
+        for (const d of gsTree.datas) {
+            if (d.children && d.children.length > 0) continue;
+
+            for (const c of otherChapter) {
+                if (c.cType === 41) {
+                    gatherfields(c, d, ledgerSetting.chapterFields, ledgerSetting.prefix);
+                } else if (c.cType === 31 && (!d.b_code || d.b_code === '')) {
+                    gatherfields(c, d, ledgerSetting.chapterFields, ledgerSetting.prefix);
+                } else if (c.cType === 11 && (d.b_code)) {
+                    gatherfields(c, d, ledgerSetting.chapterFields, ledgerSetting.prefix);
+                }
+            }
+            if (d.b_code) {
+                const c = _getGclChapter(gclChapter, d);
+                gatherfields(c, d, ledgerSetting.chapterFields, ledgerSetting.prefix);
+            }
+        }
+    }
+
+    function init (gclData, chapter) {
+        gclList = gclData;
+        [gclChapter, otherChapter] = _getCalcChapter(chapter);
+    }
+
+    /**
+     *
+     * @param bills - 项目节+清单数据
+     * @param pos - 计量单元数据
+     * @param setting 配置
+     *  e.g.
+     *  {
+     *      tree: {
+     *          id: 'ledger_id',
+     *          pid: 'ledger_pid',
+     *          order: 'order',
+     *          level: 'level',
+     *          rootId: -1,
+     *          keys: ['id', 'tender_id', 'ledger_id'],
+     *          stageId: 'id',
+     *      },
+     *      pos: { id: 'id', ledgerId: 'lid', },
+     *      billsFields: ['quantity', 'total_price', 'deal_qty', 'deal_tp'],
+     *      posFields: ['quantity'],
+     *      chapterFields: [new_total_price],
+     *      prefix: 'org' // 'new'
+     *  }
+     */
+    function gatherLedgerData(bills, pos, setting) {
+        ledgerSetting = setting;
+        try {
+            if (leafXmjs.length > 0) leafXmjs.length = 0;
+            gsTree = createNewPathTree('ledger', setting.tree);
+            gsTree.loadDatas(bills);
+            const gsPos = new PosData(setting.pos);
+            gsPos.loadDatas(pos);
+            recursiveGatherGclData(gsTree.children, null, gsPos);
+            _gatherChapter();
+        } catch(err) {
+            console.log(err);
+        }
+        ledgerSetting = null;
+    }
+
+    /**
+     *
+     * @param data {Array} - 签约清单数据
+     * @returns <void>
+     */
+    function gatherDealBills(data) {
+        if (data instanceof Array && data.length > 0) {
+            for (const node of data) {
+                node.b_code = node.code;
+                const gcl = getGclNode(node);
+                if (!d.quantity || !d.unit_price) continue;
+                gcl.deal_bills_qty = node.quantity || 0;
+                gcl.deal_bills_tp = node.total_price || 0;
+
+                for (const c of otherChapter) {
+                    if (c.cType === 41 || c.cType === 11) {
+                        c.deal_bills_tp = ZhCalc.add(c.deal_bills_tp, d.total_price);
+                    }
+                }
+                const c = _getGclChapter(gclChapter, d);
+                c.deal_bills_tp = ZhCalc.add(c.deal_bills_tp, d.total_price);
+            }
+        }
+    }
+
+    /**
+     * 检查汇总完的工程量清单中,同编号 & 不同名称/单位/单价 的清单并标记
+     */
+    function checkDiffer() {
+        for (const gcl of gclList) {
+            gcl.differ = false;
+        }
+        for (const [i, gcl] of gclList.entries()) {
+            if (i === gclList.length - 1) continue;
+            const next = gclList[i+1];
+            if (gcl.b_code === next.b_code) {
+                if (gcl.name !== next.name || gcl.unit !== next.unit || !checkZero(gcl.unit_price - next.unit_price)) {
+                    gcl.differ = true;
+                    next.differ = true;
+                }
+            }
+        }
+    }
+
+    function chapterData () {
+        return gclChapter.concat(otherChapter);
+    }
+
+    return {
+        init,
+        gatherLedgerData, gatherDealBills, checkDiffer,
+        chapterData
+    };
+})();

+ 1 - 2
app/router.js

@@ -176,8 +176,7 @@ module.exports = app => {
     app.post('/tender/:id/revise/info/check', sessionAuth, tenderCheck, uncheckTenderCheck, 'reviseController.checkData');
 
     app.get('/tender/:id/revise/compare', sessionAuth, tenderCheck, uncheckTenderCheck, reviseAuditCheck, 'reviseController.compare');
-    // app.get('/tender/:id/revise/gather', sessionAuth, tenderCheck, uncheckTenderCheck, reviseAuditCheck, 'reviseController.gather');
-    // app.get('/tender/:id/revise/bwtz', sessionAuth, tenderCheck, uncheckTenderCheck, reviseAuditCheck, 'reviseController.bwtz');
+    app.get('/tender/:id/revise/gcl-compare', sessionAuth, tenderCheck, uncheckTenderCheck, reviseAuditCheck, 'reviseController.gclCompare');
     app.post('/tender/:id/revise/load', sessionAuth, tenderCheck, uncheckTenderCheck, reviseAuditCheck, 'reviseController.loadData');
 
     // 查看修订数据

+ 3 - 2
app/service/tender_info.js

@@ -125,9 +125,9 @@ module.exports = app => {
         async _getLedgerService() {
             try {
                 if (this.ctx.tender.data.ledger_status === auditConst.ledger.status.checked) {
-                    const stageCount = this.ctx.service.stage.count({tid: this.ctx.tender.id});
+                    const stageCount = await this.ctx.service.stage.count({tid: this.ctx.tender.id});
                     if (stageCount === 0) {
-                        const revise = await ctx.service.ledgerRevise.getLastestRevise(this.ctx.tender.id);
+                        const revise = await this.ctx.service.ledgerRevise.getLastestRevise(this.ctx.tender.id);
                         if (revise.status === auditConst.revise.status.uncheck || revise.status === auditConst.revise.status.checkNo) {
                             return [this.ctx.service.reviseBills, this.ctx.service.revisePos];
                         }
@@ -307,6 +307,7 @@ module.exports = app => {
             }
 
             const [billsService] = await this._getLedgerService();
+            console.log(billsService);
 
             const changeBills = await this._reCalcLedger(tenderId, billsService, newDecimal, oldDecimal);
             const [changeSj, changeSb, changeSo] = await this._reCalcStageExtra(tenderId, newDecimal, oldDecimal);

+ 66 - 0
app/view/revise/gcl_compare.ejs

@@ -0,0 +1,66 @@
+<% include ./sub_menu.ejs %>
+<div class="panel-content">
+    <div class="panel-title">
+        <div class="title-main d-flex">
+            <% include ./sub_mini_menu.ejs %>
+            <div>
+                工程量清单对比
+            </div>
+            <div class="ml-auto">
+            </div>
+        </div>
+    </div>
+    <div class="content-wrap row pr-46">
+        <div class="c-header p-0 col-12">
+        </div>
+        <div class="row w-100 sub-content">
+            <div class="c-body" id="left-view" style="width: 100%">
+                <div class="sjs-height-1" id="gcl-spread">
+                </div>
+                <div class="bcontent-wrap" id="main-bottom">
+                    <div id="main-resize" class="resize-y" r-Type="height" div1="#gcl-spread" div2="#main-bottom" store-id="ledger-gather" store-version="1.0.0" min="100"></div>
+                    <div class="bc-bar mb-1">
+                        <ul class="nav nav-tabs">
+                            <li class="nav-item">
+                                <a class="nav-link active" data-toggle="tab" href="#xmujie" role="tab">所属项目节</a>
+                            </li>
+                        </ul>
+                    </div>
+                    <div class="tab-content">
+                        <div class="tab-pane active" id="xmujie">
+                            <div class="sp-wrap" id="leaf-xmj-spread">
+                            </div>
+                        </div>
+                    </div>
+                </div>
+            </div>
+            <div class="c-body" id="right-view" style="display: none; width: 33%;">
+                <div class="resize-x" id="right-spr" r-Type="width" div1="#left-view" div2="#right-view" title="调整大小" a-type="percent"><!--调整左右高度条--></div>
+                <div class="tab-content">
+                    <div id="chapter" class="tab-pane active">
+                        <div class="side-bar-1"></div>
+                        <div class="sjs-sh-1">
+                            <table class="table table-bordered">
+                                <tr class="text-center"><th>章节</th><th>章节名称</th><th>签约金额</th><th>修订台账金额</th><th>原台账金额</th></tr>
+                                <tbody id="chapter-list"></tbody>
+                            </table>
+                        </div>
+                    </div>
+                </div>
+            </div>
+        </div>
+        <!--右侧菜单-->
+        <div class="side-menu">
+            <!--右侧菜单-->
+            <ul class="nav flex-column right-nav" id="side-menu">
+                <li class="nav-item">
+                    <a class="nav-link active" content="#chapter" href="javascript: void(0);">章节合计</a>
+                </li>
+            </ul>
+        </div>
+    </div>
+</div>
+<script>
+    const chapter = JSON.parse('<%- JSON.stringify(ctx.tender.info.chapter) %>');
+    const thousandth = <%- ctx.tender.info.display.thousandth %>;
+</script>

+ 1 - 2
app/view/revise/sub_menu_list.ejs

@@ -1,5 +1,4 @@
 <nav-menu title="返回" url="/tender/<%= ctx.tender.id %>/revise" tclass="text-primary" ml="1" icon="fa-chevron-left"></nav-menu>
 <nav-menu title="台账修订" url="/tender/<%= ctx.tender.id %>/revise/info" ml="3" active="<%= ctx.url.indexOf('revise/info') %>"></nav-menu>
 <nav-menu title="台账对比" url="/tender/<%= ctx.tender.id %>/revise/compare" ml="3" active="<%= ctx.url.indexOf('revise/compare') %>"></nav-menu>
-<!--<nav-menu title="清单汇总" url="/tender/<%= ctx.tender.id %>/revise/gather" ml="3" active="<%= ctx.url.indexOf('revise/gather') %>"></nav-menu>
-<nav-menu title="部位台账" url="/tender/<%= ctx.tender.id %>/revise/bwtz" ml="3" active="<%= ctx.url.indexOf('revise/bwtz') %>"></nav-menu>-->
+<nav-menu title="清单对比" url="/tender/<%= ctx.tender.id %>/revise/gcl-compare" ml="3" active="<%= ctx.url.indexOf('revise/gcl-compare') %>"></nav-menu>

+ 18 - 0
config/web.js

@@ -260,6 +260,24 @@ const JsFiles = {
                     '/public/js/revise_compare.js',
                 ],
                 mergeFile: 'revise_compare',
+            },
+            gclCompare: {
+                files: [
+                    '/public/js/spreadjs/sheets/v11/gc.spread.sheets.all.11.2.2.min.js',
+                    '/public/js/decimal.min.js',
+                    '/public/js/math.min.js',
+                    '/public/js/component/menu.js',
+                ],
+                mergeFiles: [
+                    '/public/js/sub_menu.js',
+                    '/public/js/div_resizer.js',
+                    '/public/js/spreadjs_rela/spreadjs_zh.js',
+                    '/public/js/zh_calc.js',
+                    '/public/js/path_tree.js',
+                    '/public/js/shares/gcl_gather_compare.js',
+                    '/public/js/revise_gcl_compare.js',
+                ],
+                mergeFile: 'revise_gcl_compare',
             }
         },
         stage: {