فهرست منبع

台账分解,改版相关:
1. 树结构调整,父项金额计算移至前端
2. 增加部位明细
3. 1、2相关其他修改

MaiXinRong 6 سال پیش
والد
کامیت
bbc90df0d0

+ 12 - 0
app/base/base_controller.js

@@ -130,6 +130,18 @@ class BaseController extends Controller {
             }));
         }
     }
+
+    async checkMeasureType (tenderId, mt) {
+        if (!this.ctx.tender) {
+            this.ctx.tender = await this.ctx.service.tender.getTender(tenderId);
+        }
+        if (!this.ctx.tender.measure_type) {
+            throw '请先确认计量模式后再录入数据';
+        }
+        if (this.ctx.tender.measure_type !== mt) {
+            throw '该模式下不可提交此数据';
+        }
+    }
 }
 
 module.exports = BaseController;

+ 11 - 6
app/const/spread.js

@@ -15,8 +15,13 @@ const ledgerSpread = {
         {title: '名称', colSpan: '1', rowSpan: '2', field: 'name', hAlign: 0, width: 230, formatter: '@'},
         {title: '单位', colSpan: '1', rowSpan: '2', field: 'unit', hAlign: 1, width: 50, formatter: '@'},
         {title: '单价', colSpan: '1', rowSpan: '2', field: 'unit_price', hAlign: 2, width: 60, type: 'Number'},
-        {title: '数量', colSpan: '1', rowSpan: '2', field: 'quantity', hAlign: 2, width: 60, type: 'Number'},
-        {title: '金额', colSpan: '1', rowSpan: '2', field: 'total_price', hAlign: 2, width: 60, type: 'Number'},
+        {title: '设计数量1',  colSpan: '1', rowSpan: '2', field: 'dgn_quantity1', hAlign: 2, width: 60, type: 'Number'},
+        {title: '设计数量2',  colSpan: '1', rowSpan: '2', field: 'dgn_quantity2', hAlign: 2, width: 60, type: 'Number'},
+        {title: '经济指标',  colSpan: '1', rowSpan: '2', field: 'dgn_price', hAlign: 2, width: 60, type: 'Number'},
+        {title: '签约|数量', colSpan: '2|1', rowSpan: '1|1', field: 'quantity', hAlign: 2, width: 60, type: 'Number'},
+        {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'total_price', hAlign: 2, width: 60, type: 'Number'},
+        {title: '施工图复核|数量', colSpan: '2|1', rowSpan: '1|1', field: 'f_quantity', hAlign: 2, width: 60, type: 'Number'},
+        {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'f_total_price', hAlign: 2, width: 60, type: 'Number'},
         {title: '图(册)号', colSpan: '1', rowSpan: '2', field: 'drawing_code', hAlign: 0, width: 80, formatter: '@'},
         {title: '备注', colSpan: '1', rowSpan: '2', field: 'memo', hAlign: 0, width: 100, formatter: '@'}
     ],
@@ -29,14 +34,14 @@ const ledgerSpread = {
 const ledgerPosSpread = {
     cols: [
         {title: '名称', colSpan: '1', rowSpan: '1', field: 'name', hAlign: 0, width: 230, formatter: '@'},
-        {title: '数量', colSpan: '1', rowSpan: '1', field: 'quantity', hAlign: 0, width: 60, type: 'Number'},
+        {title: '数量', colSpan: '1', rowSpan: '1', field: 'quantity', hAlign: 2, width: 60, type: 'Number'},
         {title: '图册号', colSpan: '1', rowSpan: '1', field: 'drawing_code', hAlign: 0, width: 80, formatter: '@'},
     ],
     emptyRows: 3,
     headRows: 1,
     headRowHeight: [40],
     defaultRowHeight: 21,
-}
+};
 
 const measureSpread = {
     cols: [
@@ -81,11 +86,11 @@ const measurePosSpread = {
     headRows: 2,
     headRowHeight: [40, 40],
     defaultRowHeight: 21,
-}
+};
 
 module.exports = {
     ledgerSpread,
     ledgerPosSpread,
     measureSpread,
     measurePosSpread,
-}
+};

+ 10 - 0
app/const/tender.js

@@ -47,6 +47,15 @@ const manageTableCol = [
     { title: '管理', field: '', },
 ];
 
+const measureType = {
+    tz: {
+        value: 1, name: '0号台账模式', title: ' 0号台帐', hint: ' 要求以台帐为计量根本,必须先有完整台帐才能持续计量。',
+    },
+    gcl: {
+        value: 2, name: '工程量清单模式', title: ' 工程量清单', hint: ' 仅需要工程量清单,详细台帐在计量过程中逐步添加,最终组成完整台帐。',
+    },
+}
+
 const typeString = [];
 typeString[type.TJ] = '土建标';
 typeString[type.XX] = 'XX标';
@@ -60,4 +69,5 @@ module.exports = {
     infoTableCol,
     progressTableCol,
     manageTableCol,
+    measureType,
 };

+ 1 - 0
app/const/tender_info.js

@@ -68,6 +68,7 @@ const defaultInfo = {
     // 合同参数
     deal_param: {
         contractPrice: 0,
+        zanLiePrice: 0,
         startAdvance: 0,
         materialAdvance: 0,
     },

+ 1 - 1
app/controller/deal_bills_controller.js

@@ -27,7 +27,7 @@ module.exports = app => {
                 data: [],
             };
             try {
-                const tenderId = ctx.session.sessionUser.tenderId;
+                const tenderId = ctx.params.id;
                 if (isNaN(tenderId) || tenderId <= 0) {
                     throw '参数错误';
                 }

+ 57 - 6
app/controller/ledger_controller.js

@@ -15,6 +15,7 @@ const stdDataAddType = {
 const auditConst = require('../const/audit').flow;
 const spreadConst = require('../const/spread');
 const tenderMenu = require('../../config/menu').tenderMenu;
+const measureType = require('../const/tender').measureType;
 
 module.exports = app => {
 
@@ -42,7 +43,8 @@ module.exports = app => {
         async explode(ctx) {
             try {
                 const tenderId = ctx.params.id;
-                const tender = await this.service.tender.getDataById(tenderId);
+                const tender = await this.service.tender.getTender(tenderId);
+                const tenderInfo = await this.service.tenderInfo.getTenderInfo(tenderId);
 
                 if (!tender.ledger_status) {
                     tender.ledger_status = auditConst.status.uncheck;
@@ -56,9 +58,10 @@ module.exports = app => {
                 const content = auditors.length > 0 ? await ctx.service.ledgerAuditContent.getAllDataByCondition({
                     where: {tender_id: tenderId, times: times, audit_id: auditors[0].audit_id}
                 }) : null;
-                const ledgerData = await ctx.service.ledger.getDataByTenderId(tenderId, 4);
+                const ledgerData = await ctx.service.ledger.getDataByTenderId(tenderId, -1);
                 const renderData = {
                     tender,
+                    tenderInfo,
                     auditConst,
                     auditors,
                     curAuditor,
@@ -68,6 +71,7 @@ module.exports = app => {
                     posSpreadSetting: JSON.stringify(spreadConst.ledgerPosSpread),
                     tenderMenu,
                     preUrl: '/tender/' + tenderId,
+                    measureType,
                 };
                 await this.layout('ledger/explode.ejs', renderData, 'ledger/explode_modal.ejs');
             } catch (err) {
@@ -208,14 +212,13 @@ module.exports = app => {
         }
 
         async update(ctx) {
-            console.log(ctx.body);
             const responseData = {
                 err: 0,
                 msg: '',
                 data: [],
             };
             try {
-                const tenderId = ctx.params.id;
+                const tenderId = parseInt(ctx.params.id);
                 if (!tenderId) {
                     throw '当前未打开标段';
                 }
@@ -226,6 +229,7 @@ module.exports = app => {
                 const data = JSON.parse(ctx.request.body.data);
                 responseData.data = await ctx.service.ledger.updateCalc(tenderId, data);
             } catch (err) {
+                this.log(err);
                 responseData.err = 1;
                 responseData.msg = err;
             }
@@ -357,16 +361,17 @@ module.exports = app => {
                 }
 
                 switch (data.batchType) {
-                    case 'batchInsertChild':
+                    case 'child':
                         responseData.data = await ctx.service.ledger.batchInsertChild(tenderId, data.id, data.batchData);
                         break;
-                    case 'batchInsertNext':
+                    case 'next':
                         responseData.data = await ctx.service.ledger.batchInsertNext(tenderId, data.id, data.batchData);
                         break;
                     default:
                         throw '参数错误';
                 }
             } catch (err) {
+                this.log(err);
                 responseData.err = 1;
                 responseData.msg = err;
             }
@@ -472,6 +477,52 @@ module.exports = app => {
         }
 
         /**
+         * 获取部位明细数据(Ajax)
+         *
+         * @param ctx
+         * @returns {Promise<void>}
+         */
+        async pos(ctx) {
+            try {
+                const tenderId = ctx.params.id;
+                if (!tenderId) {
+                    throw '当前未打开标段';
+                }
+                await this.checkMeasureType(tenderId, measureType.tz.value);
+                const condition = JSON.parse(ctx.request.body.data) || {};
+                condition.tid = tenderId;
+
+                const posData = await ctx.service.pos.getPosData(condition);
+                ctx.body = {err: 0, msg: '', data: posData};
+            } catch (err) {
+                this.log(err);
+                ctx.body = {err: 1, msg: err.toString(), data: []};
+            }
+        }
+
+        /**
+         * 更新部位明细数据
+         *
+         * @param ctx
+         * @returns {Promise<void>}
+         */
+        async posUpdate(ctx) {
+            try {
+                const tenderId = ctx.params.id;
+                if (!tenderId) {
+                    throw '当前未打开标段';
+                }
+                await this.checkMeasureType(tenderId, measureType.tz.value);
+                const data = JSON.parse(ctx.request.body.data);
+                const responseData = await ctx.service.pos.savePosData(data, tenderId);
+                ctx.body = { err: 0, msg: '', data: responseData };
+            } catch(err) {
+                this.log(err);
+                ctx.body = { err: 1, msg: err.toString(), data: null };
+            }
+        }
+
+        /**
          * 台账变更页面 (Get)
          *
          * @param {object} ctx - egg全局变量

+ 30 - 0
app/controller/sum_controller.js

@@ -0,0 +1,30 @@
+'use strict';
+
+/**
+ * 总分包
+ *
+ * @author Mai
+ * @date 2018/11/5
+ * @version
+ */
+
+const sumMenu = require('../../config/menu').sumMenu;
+
+module.exports = app => {
+    class SumController extends app.BaseController {
+
+        /**
+         * 总包概况
+         *
+         * @param ctx
+         * @returns {Promise<void>}
+         */
+        async index(ctx) {
+            const renderData = {
+
+            }
+        }
+    }
+
+    return SumController;
+};

+ 24 - 0
app/controller/tender_controller.js

@@ -40,6 +40,7 @@ module.exports = app => {
                     settingConst,
                     categoryData,
                     tableColSetting: setting,
+                    measureType: tenderConst.measureType,
                 };
                 await this.layout(view, renderData, modal);
             } catch(err) {
@@ -133,6 +134,9 @@ module.exports = app => {
             try {
                 const tenderId = ctx.params.id;
                 const renderData = await this.getCommonRenderData(ctx);
+                if (!renderData.tender.measure_type) {
+                    throw '请先选择计量模式';
+                }
                 renderData.tenderInfo = await ctx.service.tenderInfo.getTenderInfo(tenderId);
                 await this.layout('tender/detail.ejs', renderData);
             } catch (error) {
@@ -160,6 +164,26 @@ module.exports = app => {
         }
 
         /**
+         * 设置标段计量类型并调整到标段概况(Get)
+         *
+         * @param ctx
+         * @returns {Promise<void>}
+         */
+        async tenderType(ctx) {
+            try {
+                const tenderId = ctx.params.id, type = ctx.params.type;
+                const tender = await ctx.service.tender.getTender(tenderId);
+                if (!tender.measure_type) {
+                    await ctx.service.tender.update({measure_type: type}, {id: tenderId});
+                }
+                ctx.redirect('/tender/' + tenderId);
+            } catch(error) {
+                this.log(error);
+                ctx.redirect('/list');
+            }
+        }
+
+        /**
          * 添加标段操作
          *
          * @param {Object} ctx - egg全局变量

+ 4 - 1
app/public/css/main.css

@@ -54,7 +54,7 @@ body {
 	background-color: #999;
 	-webkit-border-radius: 6px;
 }
-.sjs-height-1,.sjs-height-2{
+.sjs-height-1,.sjs-height-2,.sjs-sh-1,.sjs-sh-2,.sjs-sh-3,.sjs-sh-4,.sjs-sh-5{
   overflow: hidden;
 }
 .sjs-height-4,.sjs-height-5,.sjs-height-6{
@@ -220,6 +220,9 @@ body {
   line-height: 16px;
   font-size: 14px
 }
+.side-bar{
+  height: 31px;
+}
 .sub-content{
   margin:0;
 }

+ 13 - 0
app/public/js/global.js

@@ -1,6 +1,13 @@
 /*全局自适应高度*/
 /*全局自适应高度*/
 function autoFlashHeight(){
+    /*侧栏高度*/
+    var sBar1 = $(".sjs-bar-1").height();
+    var sBar2 = $(".sjs-bar-2").height();
+    var sBar3 = $(".sjs-bar-3").height();
+    var sBar4 = $(".sjs-bar-4").height();
+    var sBar5 = $(".sjs-bar-5").height();
+    /*侧栏高度*/
     var cHeader = $(".c-header").height();
     var sBar = $(".sjs-bar").height();
     var sBart = $(".sjs-bart").height();
@@ -17,6 +24,12 @@ function autoFlashHeight(){
     $(".sjs-height-6").height($(window).height()-cHeader-bdtopc-156);
     $(".sjs-height-7").height($(window).height()-cHeader-sBar-110);
     $(".sp-wrap").height(bcontent-40);
+    /*侧栏高度*/
+    $(".sjs-sh-1").height($(window).height()-cHeader-sBar1-110);
+    $(".sjs-sh-2").height($(window).height()-cHeader-sBar1-110);
+    $(".sjs-sh-3").height($(window).height()-cHeader-sBar1-110);
+    $(".sjs-sh-4").height($(window).height()-cHeader-sBar1-110);
+    $(".sjs-sh-5").height($(window).height()-cHeader-sBar1-110);
 };
 $(window).resize(autoFlashHeight);
 /*全局自适应高度结束*/

+ 360 - 262
app/public/js/ledger.js

@@ -5,6 +5,9 @@
  * @date 2018/02/05
  * @version
  */
+function checkTzMeasureType () {
+    return tender.measure_type === measureType.tz.value;
+}
 
 function loadContent(data) {
     const html = [];
@@ -39,6 +42,7 @@ function loadContents(data) {
 
 $(document).ready(function() {
     autoFlashHeight();
+    // 初始化台账
     const ledgerSpread = SpreadJsObj.createNewSpread($('#ledger-spread')[0]);
     SpreadJsObj.addDeleteBind(ledgerSpread);
     const ledgerTree = createNewPathTree('ledger', {
@@ -51,12 +55,19 @@ $(document).ready(function() {
         preUrl: '/ledger'
     });
     ledgerTree.loadDatas(ledger);
+    treeCalc.calculateAll(ledgerTree, ['total_price']);
     SpreadJsObj.initSheet(ledgerSpread.getActiveSheet(), ledgerSpreadSetting);
+    // 加载台账数据到界面
     SpreadJsObj.loadSheetData(ledgerSpread.getActiveSheet(), 'tree', ledgerTree);
 
+
+    // 初始化 部位明细
+    const pos = new PosData({
+        id: 'id', masterId: 'lid',
+    });
     const posSpread = SpreadJsObj.createNewSpread($('#pos-spread')[0]);
-    SpreadJsObj.initSheet(posSpread.getActiveSheet(), posSpreadSetting);
 
+    // 定义事件
     const treeOperationObj = {
         getSelectNode: function (sheet) {
             if (!sheet || !sheet.zh_tree) {
@@ -84,12 +95,18 @@ $(document).ready(function() {
             const tree = sheet.zh_tree;
             if (!tree) { return; }
             const node = sheet.zh_tree.nodes[row];
+            const valid = !sheet.zh_setting.readOnly;
 
-            setObjEnable($('#delete'), !sheet.zh_setting.readOnly && node);
-            setObjEnable($('#up-move'), !sheet.zh_setting.readOnly && node && node.order > 1);
-            setObjEnable($('#down-move'), !sheet.zh_setting.readOnly && node && !tree.isLastSibling(node));
-            setObjEnable($('#up-level'), !sheet.zh_setting.readOnly && node && tree.getParent(node));
-            setObjEnable($('#down-level'), !sheet.zh_setting.readOnly && node && node.order > 1);
+            setObjEnable($('#delete'), valid && node);
+            setObjEnable($('#up-move'), valid && node && node.order > 1);
+            setObjEnable($('#down-move'), valid && node && !tree.isLastSibling(node));
+            if (checkTzMeasureType()) {
+                const posRange = pos.getMasterRange(node.id);
+                setObjEnable($('#up-level'), valid && node && tree.getParent(node) && (!posRange || posRange.length === 0));
+            } else {
+                setObjEnable($('#up-level'), valid && node && tree.getParent(node));
+            }
+            setObjEnable($('#down-level'), valid && node && node.order > 1);
         },
         refreshTree: function (sheet, data) {
             SpreadJsObj.massOperationSheet(sheet, function () {
@@ -162,7 +179,7 @@ $(document).ready(function() {
             if (!node) { return; }
 
             SpreadJsObj.massOperationSheet(sheet, function () {
-                tree.baseOperation('base-operation', node, 'add', function (result) {
+                tree.baseOperation(preUrl + '/ledger/base-operation', node, 'add', function (result) {
                     self.refreshTree(sheet, result);
                     self.refreshOperationValid(sheet, sheet.getSelections());
                 });
@@ -184,12 +201,16 @@ $(document).ready(function() {
             if (!node) { return; }
 
             const count = ledgerTree.getPosterity(node).length;
-            tree.baseOperation('base-operation', node, 'delete', function (result) {
+            tree.baseOperation(preUrl + '/ledger/base-operation', node, 'delete', function (result) {
                 sheet.deleteRows(row, count + 1);
                 for (const data of result.update) {
                     SpreadJsObj.reLoadRowData(sheet, tree.nodes.indexOf(data), tree.getPosterity(data).length + 1);
                 }
                 self.refreshOperationValid(sheet, sheet.getSelections());
+                for (const data of result.delete) {
+                    pos.removeDatasByMasterId(data.id);
+                }
+                posOperationObj.loadCurPosData();
             });
         },
         /**
@@ -208,7 +229,7 @@ $(document).ready(function() {
             const node = tree.nodes[row];
             if (!node) { return; }
 
-            tree.baseOperation('base-operation', node, 'up-move', function (result) {
+            tree.baseOperation(preUrl + '/ledger/base-operation', node, 'up-move', function (result) {
                 for (const data of result.update) {
                     SpreadJsObj.reLoadRowData(sheet, tree.nodes.indexOf(data), tree.getPosterity(data).length + 1);
                 }
@@ -234,7 +255,7 @@ $(document).ready(function() {
             const node = tree.nodes[row];
             if (!node) { return; }
 
-            tree.baseOperation('base-operation', node, 'down-move', function (result) {
+            tree.baseOperation(preUrl + '/ledger/base-operation', node, 'down-move', function (result) {
                 for (const data of result.update) {
                     SpreadJsObj.reLoadRowData(sheet, tree.nodes.indexOf(data), tree.getPosterity(data).length + 1);
                 }
@@ -258,7 +279,7 @@ $(document).ready(function() {
             const node = tree.nodes[row];
             if (!node) { return; }
 
-            tree.baseOperation('base-operation', node, 'up-level', function (result) {
+            tree.baseOperation(preUrl + '/ledger/base-operation', node, 'up-level', function (result) {
                 self.refreshTree(sheet, result);
                 self.refreshOperationValid(sheet, sheet.getSelections());
             });
@@ -279,7 +300,7 @@ $(document).ready(function() {
             const node = tree.nodes[row];
             if (!node) { return; }
 
-            tree.baseOperation('base-operation', node, 'down-level', function (result) {
+            tree.baseOperation(preUrl + '/ledger/base-operation', node, 'down-level', function (result) {
                 self.refreshTree(sheet, result);
                 self.refreshOperationValid(sheet, sheet.getSelections());
             });
@@ -301,12 +322,8 @@ $(document).ready(function() {
                 };
                 data[col.field] = col.type === 'Number' ? parseFloat(info.editingText) : info.editingText;
 
-                info.sheet.zh_tree.update('update', data, function (result) {
-                    const rows = [];
-                    for (const r of result) {
-                        rows.push(sortData.indexOf(r));
-                    }
-                    SpreadJsObj.reLoadRowsData(info.sheet, rows);
+                info.sheet.zh_tree.update(preUrl + '/ledger/update', data, function (result) {
+                    treeOperationObj.refreshTree(info.sheet, result);
                 });
             }
         },
@@ -333,7 +350,7 @@ $(document).ready(function() {
                         nodes.push(node);
                     }
                 }
-                info.sheet.zh_tree.update('update', datas, function (result) {
+                info.sheet.zh_tree.update('/ledger/update', datas, function (result) {
                     const rows = [];
                     for (const data of result) {
                         rows.push(sortData.indexOf(data));
@@ -363,7 +380,7 @@ $(document).ready(function() {
                         nodes.push(node);
                     }
                 }
-                sheet.zh_tree.update('update-info', datas, function (result) {
+                sheet.zh_tree.update(preUrl + '/ledger/update-info', datas, function (result) {
                     const rows = [];
                     for (const data of result) {
                         rows.push(sortData.indexOf(data));
@@ -388,30 +405,24 @@ $(document).ready(function() {
             const node = tree.nodes[row];
             if (!node) { return; }
 
-            tree.pasteBlock('paste-block', node, block, function (result) {
-                self.refreshTree(sheet, result);
-                self.refreshOperationValid(sheet, sheet.getSelections());
+            postData(preUrl + '/ledger/paste-block', {
+                id: node[tree.setting.id],
+                block: block,
+            }, function (data) {
+                pos.updateDatas(data.pos);
+                tree.loadPostData(data.ledger, function (result) {
+                    self.refreshTree(sheet, result);
+                    self.refreshOperationValid(sheet, sheet.getSelections());
+                });
             });
         },
-        batchInsertData: function (spread, data, fun) {
-            const self = this;
-            const sheet = spread.getActiveSheet();
-            const row = sheet.getSelections()[0].row;
-
-            const tree = sheet.zh_tree;
-            if (!tree) { return; }
-
-            const node = tree.nodes[row];
-            if (!node) { return; }
-
-            tree.batchInsert('batch-insert', node, data, function (result) {
-                self.refreshTree(sheet, result);
-                self.refreshOperationValid(sheet, sheet.getSelections());
-                fun();
-            });
-        }
+        selectionChanged: function (e, info) {
+            if (info.newSelections[0].row !== info.oldSelections[0].row) {
+                posOperationObj.loadCurPosData();
+            }
+        },
     };
-
+    // 绑定事件
     if (!ledgerSpreadSetting.readOnly) {
         ledgerSpread.bind(GC.Spread.Sheets.Events.SelectionChanged, function (e, info) {
             treeOperationObj.refreshOperationValid(info.sheet, info.newSelections);
@@ -427,6 +438,7 @@ $(document).ready(function() {
         });
         ledgerSpread.bind(GC.Spread.Sheets.Events.ClipboardPasting, function (e, info) {
         });
+        ledgerSpread.bind(GC.Spread.Sheets.Events.SelectionChanged, treeOperationObj.selectionChanged);
 
         // 绑定 删除等 顶部按钮
         $('#delete').click(function () {
@@ -516,7 +528,7 @@ $(document).ready(function() {
                     }
                 },
                 'pasteBlock': {
-                    name: '粘贴',
+                    name: '粘贴整块',
                     icon: 'fa-clipboard',
                     disabled: function (key, opt) {
                         const block = treeOperationObj.block || [];
@@ -531,63 +543,129 @@ $(document).ready(function() {
                         }
                     }
                 },
-                'batchInsertChild': {
-                    name: '批量插入子项',
+                'batchInsertBillsPos': {
+                    name: '批量插入清单部位',
                     icon: 'fa-sign-in',
                     disabled: function (key, opt) {
-                        return false;
-                    },
-                    callback: function(key, opt) {
-                        $('h5', $('#batch')).text('批量插入子项');
-                        if (!batchInsertObj) {
-                            batchInsertObj = new BatchInsertObj($('#batch'), key);
+                        const sheet = ledgerSpread.getActiveSheet();
+                        const selection = sheet.getSelections();
+                        const row = selection[0].row;
+                        const select = ledgerTree.nodes[row];
+                        if (select) {
+                            if (select.code && select.code !== '') {
+                                return !ledgerTree.isLeafXmj(select);
+                            } else {
+                                const parent = ledgerTree.getParent(select);
+                                return !(parent && ledgerTree.isLeafXmj(parent));
+                            }
                         } else {
-                            batchInsertObj.batchType = key;
-                            batchInsertObj.initView();
+                            return false;
                         }
-                        $('#batch').modal('show');
-                    },
-                },
-                'batchInsertNext': {
-                    name: '批量插入后项',
-                    icon: 'fa-sign-in',
-                    disabled: function (key, opt) {
-                        return false;
                     },
                     callback: function (key, opt) {
-                        $('h5', $('#batch')).text('批量插入后项');
                         if (!batchInsertObj) {
-                            batchInsertObj = new BatchInsertObj($('#batch'), key);
+                            batchInsertObj = new BatchInsertBillsPosObj($('#batch'));
                         } else {
-                            batchInsertObj.batchType = key;
                             batchInsertObj.initView();
                         }
                         $('#batch').modal('show');
-                    },
-                },
-                'loadAll': {
-                    name: '加载全部叶子节点',
-                    callback: function (key, opt) {
-                        const sheet = ledgerSpread.getActiveSheet();
-                        const selection = sheet.getSelections();
-                        const row = selection[0].row;
-                        const select = ledgerTree.nodes[row];
-                        ledgerTree.postData('posterity', null, {id: ledgerTree.getNodeKey(select)}, function (result) {
-                            let time = new Date();
-                            select.expanded = true;
-                            treeOperationObj.refreshTree(sheet, result);
-                            time = new Date() - time;
-                            console.log('refreshSpreadView: '+ time);
-                            treeOperationObj.refreshOperationValid(sheet, sheet.getSelections());
-                        });
                     }
                 }
             }
         });
     }
-
     treeOperationObj.refreshOperationValid(ledgerSpread.getActiveSheet(), ledgerSpread.getActiveSheet().getSelections());
 
+
+    // 台账模式加载部位明细数据
+    if (checkTzMeasureType()) {
+        SpreadJsObj.initSheet(posSpread.getActiveSheet(), posSpreadSetting);
+        postData(preUrl + '/pos', null, function (data) {
+            pos.loadDatas(data);
+        });
+    }
+    // 绑定部位明细编辑事件
+    const posOperationObj = {
+        loadCurPosData: function () {
+            const node = treeOperationObj.getSelectNode(ledgerSpread.getActiveSheet());
+            if (node) {
+                const posData = pos.masterRange[itemsPre + node.id] || [];
+                SpreadJsObj.loadSheetData(posSpread.getActiveSheet(), 'data', posData);
+            } else {
+                SpreadJsObj.loadSheetData(posSpread.getActiveSheet(), 'data', []);
+            }
+        },
+        /**
+         * 编辑单元格响应事件
+         * @param {Object} e
+         * @param {Object} info
+         */
+        editEnding: function (e, info) {
+            if (info.sheet.zh_setting) {
+                const orgText = info.sheet.getCell(info.row, info.row).value();
+                if (orgText === info.editingText || ((!orgText || orgText === '') && (info.editingText === ''))) {
+                    console.log(3);
+                    return;
+                }
+                const node = treeOperationObj.getSelectNode(ledgerSpread.getActiveSheet());
+                if (info.editingText !== '' && node.children && node.children > 0) {
+                    toast('父节点不可插入部位明细', 'error');
+                    info.cancel = true;
+                    return;
+                } else if (info.editingText !== '' && node.code || node.code !== '') {
+                    toast('项目节不可插入部位明细', 'error');
+                    info.cancel = true;
+                    return;
+                }
+                const curPosData = pos.getMasterRange(node.id);
+                const position = curPosData ? curPosData[info.row] : null;
+                const col = info.sheet.zh_setting.cols[info.col];
+                const data = {};
+                if (col.field === 'name') {
+                    console.log(1);
+                    if (info.editingText === '' && position) {
+                        console.log(2);
+                        toast('部位名称不可为空', 'error', 'exclamation-circle');
+                        info.cancel = true;
+                        return;
+                    } else if (!position) {
+                        if (info.editingText !== '') {
+                            data.updateType = 'add';
+                            data.updateData = {name: info.editingText, lid: node.id, tid: tender.id};
+                        } else {
+                            return;
+                        }
+                    } else {
+                        data.updateType = 'update';
+                        data.updateData = {id: position.id, name: info.editingText};
+                    }
+                } else if (!position) {
+                    toast('新增部位请先输入名称', 'warning');
+                } else {
+                    data.updateType = 'update';
+                    data.updateData = {id: position.id};
+                    data.updateData[col.field] = col.type === 'Number' ? parseFloat(info.editingText) : info.editingText;
+                }
+                postData(preUrl+'/pos/update', data, function (result) {
+                    pos.updateDatas(result.pos);
+                    ledgerTree.loadPostData(result.ledger, function (loadResult) {
+                        treeOperationObj.refreshTree(ledgerSpread.getActiveSheet(), loadResult);
+                    });
+                }, function () {
+                    posOperationObj.loadCurPosData();
+                });
+            }
+        },
+    };
+    posOperationObj.loadCurPosData();
+    if (!posSpreadSetting.readOnly) {
+        SpreadJsObj.addDeleteBind(posSpread, treeOperationObj.deletePress);
+        posSpread.bind(GC.Spread.Sheets.Events.EditEnding, posOperationObj.editEnding);
+        posSpread.bind(GC.Spread.Sheets.Events.CellClick, function (e, info) {
+            console.log(info.sheet.getCell(info.row, info.col));
+        });
+    }
+
     let stdChapter, stdBills, dealBills, searchLedger;
     // 展开收起标准清单
     $('a', '#side-menu').bind('click', function () {
@@ -600,87 +678,101 @@ $(document).ready(function() {
                 $('.c-body.col-8').removeClass('col-8').addClass('col-12');
                 $('.c-body.col-4').removeClass('col-4').addClass('col-0').hide();
             }
-        }
+        };
+        // 展开工具栏、切换标签
         if (!tab.hasClass('active')) {
             $('a', '#side-menu').removeClass('active');
             tab.addClass('active');
             showSideTools(tab.hasClass('active'));
             $('.tab-content .tab-pane').hide();
             tabPanel.show();
-            if (tab.attr('content') === '#std-chapter' && !stdChapter) {
-                stdChapter = new stdLib($('#std-chapter-spread')[0], 'chapter', {
-                    id: 'chapter_id',
-                    pid: 'pid',
-                    order: 'order',
-                    level: 'level',
-                    rootId: -1,
-                    keys: ['id', 'list_id', 'chapter_id'],
-                }, {
-                    cols: [
-                        {title: '项目节编号', field: 'code', width: 120, formatter: '@', readOnly: true, cellType: 'tree'},
-                        {title: '名称', field: 'name', width: 230, formatter: '@', readOnly: true},
-                        {title: '单位', field: 'unit', width: 50, formatter: '@', readOnly: true}
-                    ],
-                    treeCol: 0,
-                    emptyRows: 0,
-                    headRows: 1,
-                    headRowHeight: [40],
-                    defaultRowHeight: 21,
-                });
-                stdChapter.loadLib(1);
-            } else if (tab.attr('content') === '#std-bills' && !stdBills) {
-                stdBills = new stdLib($('#std-bills-spread')[0], 'bills', {
-                    id: 'bill_id',
-                    pid: 'pid',
-                    order: 'order',
-                    level: 'level',
-                    rootId: -1,
-                    keys: ['id', 'list_id', 'bill_id']
-                }, {
-                    cols: [
-                        {title: '清单编号', field: 'code', width: 120, formatter: '@', readOnly: true, cellType: 'tree'},
-                        {title: '名称', field: 'name', width: 230, formatter: '@', readOnly: true},
-                        {title: '单位', field: 'unit', width: 50, formatter: '@', readOnly: true}
-                    ],
-                    treeCol: 0,
-                    emptyRows: 0,
-                    headRows: 1,
-                    headRowHeight: [40],
-                    defaultRowHeight: 21,
-                });
-                stdBills.loadLib(1);
-            } else if (tab.attr('content') === '#deal-bills' && !dealBills) {
-                dealBills = new DealBills($('#deal-bills-spread')[0], {
-                    cols: [
-                        {title: '清单编号', field: 'code', width: 120, formatter: '@', readOnly: true},
-                        {title: '名称', field: 'name', width: 230, formatter: '@', readOnly: true},
-                        {title: '单位', field: 'unit', width: 50, formatter: '@', readOnly: true},
-                        {title: '单价', field: 'unit_price', width: 50, readOnly: true},
-                        {title: '数量', field: 'quantity', width: 50, readOnly: true},
-                    ],
-                    emptyRows: 0,
-                    headRows: 1,
-                    headRowHeight: [40],
-                    defaultRowHeight: 21,
-                });
-                dealBills.loadData();
+            autoFlashHeight();
+            if (tab.attr('content') === '#std-chapter') {
+                if (!stdChapter) {
+                    stdChapter = new stdLib($('#std-chapter-spread')[0], 'chapter', {
+                        id: 'chapter_id',
+                        pid: 'pid',
+                        order: 'order',
+                        level: 'level',
+                        rootId: -1,
+                        keys: ['id', 'list_id', 'chapter_id'],
+                    }, {
+                        cols: [
+                            {title: '项目节编号', field: 'code', width: 120, formatter: '@', readOnly: true, cellType: 'tree'},
+                            {title: '名称', field: 'name', width: 230, formatter: '@', readOnly: true},
+                            {title: '单位', field: 'unit', width: 50, formatter: '@', readOnly: true}
+                        ],
+                        treeCol: 0,
+                        emptyRows: 0,
+                        headRows: 1,
+                        headRowHeight: [40],
+                        defaultRowHeight: 21,
+                    });
+                    stdChapter.loadLib(1);
+                }
+                stdChapter.spread.refresh();
+            } else if (tab.attr('content') === '#std-bills') {
+                if (!stdBills) {
+                    stdBills = new stdLib($('#std-bills-spread')[0], 'bills', {
+                        id: 'bill_id',
+                        pid: 'pid',
+                        order: 'order',
+                        level: 'level',
+                        rootId: -1,
+                        keys: ['id', 'list_id', 'bill_id']
+                    }, {
+                        cols: [
+                            {title: '清单编号', field: 'code', width: 120, formatter: '@', readOnly: true, cellType: 'tree'},
+                            {title: '名称', field: 'name', width: 230, formatter: '@', readOnly: true},
+                            {title: '单位', field: 'unit', width: 50, formatter: '@', readOnly: true}
+                        ],
+                        treeCol: 0,
+                        emptyRows: 0,
+                        headRows: 1,
+                        headRowHeight: [40],
+                        defaultRowHeight: 21,
+                    });
+                    stdBills.loadLib(1);
+                }
+                stdBills.spread.refresh();
+            } else if (tab.attr('content') === '#deal-bills') {
+                if (!dealBills) {
+                    dealBills = new DealBills($('#deal-bills-spread')[0], {
+                        cols: [
+                            {title: '清单编号', field: 'code', width: 120, formatter: '@', readOnly: true},
+                            {title: '名称', field: 'name', width: 230, formatter: '@', readOnly: true},
+                            {title: '单位', field: 'unit', width: 50, formatter: '@', readOnly: true},
+                            {title: '单价', field: 'unit_price', width: 50, readOnly: true},
+                            {title: '数量', field: 'quantity', width: 50, readOnly: true},
+                        ],
+                        emptyRows: 0,
+                        headRows: 1,
+                        headRowHeight: [40],
+                        defaultRowHeight: 21,
+                    });
+                    dealBills.loadData();
+                }
+                dealBills.spread.refresh();
             } else if (tab.attr('content') === '#search' && !searchLedger) {
-                searchLedger = new SearchLedger($('#search'), {
-                    cols: [
-                        {title: '项目节编号', field: 'code', width: 120, formatter: '@', readOnly: true},
-                        {title: '清单编号', field: 'b_code', width: 120, formatter: '@', readOnly: true},
-                        {title: '名称', field: 'name', width: 230, formatter: '@', readOnly: true},
-                        {title: '单位', field: 'unit', width: 50, formatter: '@', readOnly: true},
-                        {title: '单价', field: 'unit_price', width: 50, readOnly: true},
-                        {title: '数量', field: 'quantity', width: 50, readOnly: true},
-                    ],
-                    emptyRows: 3,
-                    headRows: 1,
-                    headRowHeight: [40],
-                    defaultRowHeight: 21,
-                })
+                if (!searchLedger) {
+                    searchLedger = new SearchLedger($('#search'), {
+                        cols: [
+                            {title: '项目节编号', field: 'code', width: 120, formatter: '@', readOnly: true},
+                            {title: '清单编号', field: 'b_code', width: 120, formatter: '@', readOnly: true},
+                            {title: '名称', field: 'name', width: 230, formatter: '@', readOnly: true},
+                            {title: '单位', field: 'unit', width: 50, formatter: '@', readOnly: true},
+                            {title: '单价', field: 'unit_price', width: 50, readOnly: true},
+                            {title: '数量', field: 'quantity', width: 50, readOnly: true},
+                        ],
+                        emptyRows: 3,
+                        headRows: 1,
+                        headRowHeight: [40],
+                        defaultRowHeight: 21,
+                    });
+                }
+                searchLedger.spread.refresh();
             }
-        } else {
+        } else { // 收起工具栏
             tab.removeClass('active');
             showSideTools(tab.hasClass('active'));
             tabPanel.hide();
@@ -692,7 +784,7 @@ $(document).ready(function() {
     class stdLib {
         constructor(obj, stdType, treeSetting, spreadSetting) {
             this.obj = obj;
-            this.url = '/std/' + stdType;
+            this.url = '/api/std/' + stdType;
             this.treeSetting = treeSetting;
             treeSetting.preUrl = this.url;
             this.spreadSetting = spreadSetting;
@@ -734,7 +826,7 @@ $(document).ready(function() {
         constructor (obj, spreadSetting) {
             const self = this;
             this.obj = obj;
-            this.url = '/deal';
+            this.url = preUrl + '/deal';
             this.spreadSetting = spreadSetting;
             this.spread = SpreadJsObj.createNewSpread(this.obj);
             SpreadJsObj.initSheet(this.spread.getActiveSheet(), this.spreadSetting);
@@ -748,39 +840,24 @@ $(document).ready(function() {
                 }, function () {
                     $('#upload-deal').modal('hide');
                 });
-            })
+            });
         }
         loadData () {
             const self = this;
             postData(this.url+'/get-data', {}, function (data) {
+                this.data = data;
                 SpreadJsObj.loadSheetData(self.spread.getActiveSheet(), 'data', data);
             });
         }
     }
-    class BatchInsertObj {
-        constructor (obj, batchType) {
+    class BatchInsertBillsPosObj {
+        constructor (obj) {
             const self = this;
             this.obj = obj;
-            this.batchType = batchType;
             this.billsCount = 6;
-
-            this.xmSpreadSetting = {
-                cols: [
-                    {title: '部位', field: 'bw', width: 80, formatter: '@'},
-                    {title: '图册号', field: 'drawingCode', formatter: '@', width: 60},
-                ],
-                emptyRows: 1000,
-                headRows: 1,
-                headRowHeight: [40],
-            };
-            for (let iNum = 1; iNum <= this.billsCount; iNum++) {
-                this.xmSpreadSetting.cols.push(
-                    {title: '清单' + iNum, field: 'bills' + iNum, width: 50}
-                )
-            }
-            this.xmSpread = SpreadJsObj.createNewSpread($('.batch-l-t')[0]);
-
-            this.gclSpreadSetting = {
+            this.posCount = 1000;
+            // 初始化 清单编号窗口 参数
+            this.qdSpreadSetting = {
                 cols: [
                     {title: '编号', field: 'code', width: 80, formatter: '@'},
                     {title: '名称', field: 'name', width: 120, formatter: '@'},
@@ -792,10 +869,26 @@ $(document).ready(function() {
                 headRows: 1,
                 headRowHeight: [40],
             };
-            this.gclSpread = SpreadJsObj.createNewSpread($('.batch-l-b')[0]);
-
+            this.qdSpread = SpreadJsObj.createNewSpread($('.batch-l-t', this.obj)[0]);
+            // 初始化 部位数量复核表 参数
+            this.posSpreadSetting = {
+                cols: [
+                    {title: '部位', field: 'bw', width: 80, formatter: '@'},
+                    {title: '图册号', field: 'drawingCode', formatter: '@', width: 60},
+                ],
+                emptyRows: this.posCount,
+                headRows: 1,
+                headRowHeight: [40],
+            };
+            for (let iNum = 1; iNum <= this.billsCount; iNum++) {
+                this.posSpreadSetting.cols.push(
+                    {title: '清单' + iNum, field: 'bills' + iNum, width: 50}
+                )
+            }
+            this.posSpread = SpreadJsObj.createNewSpread($('.batch-l-b', this.obj)[0]);
+            // 初始化 清单编号、部位数量复核表 表格
             this.initView();
-
+            // 初始化 签约清单 参数
             this.dealSpreadSetting = {
                 cols: [
                     {title: '清单编号', field: 'code', width: 80, formatter: '@', readOnly: true},
@@ -807,107 +900,112 @@ $(document).ready(function() {
                 headRows: 1,
                 headRowHeight: [40],
             };
-            this.dealSpread = SpreadJsObj.createNewSpread($('.batch-r')[0]);
+            this.dealSpread = SpreadJsObj.createNewSpread($('.batch-r', this.obj)[0]);
             SpreadJsObj.initSheet(this.dealSpread.getActiveSheet(), this.dealSpreadSetting);
-            postData('/deal/get-data', {}, function (data) {
-                SpreadJsObj.loadSheetData(self.dealSpread.getActiveSheet(), 'data', data);
-
-                self.dealSpread.bind(GC.Spread.Sheets.Events.CellDoubleClick, function (e, info) {
-                    const deal = info.sheet.zh_data[info.row];
-                    const sel = self.gclSpread.getActiveSheet().getSelections()[0];
-                    self.gclSpread.getActiveSheet().getCell(sel.row, 0).value(deal.code);
-                    self.gclSpread.getActiveSheet().getCell(sel.row, 1).value(deal.name);
-                    self.gclSpread.getActiveSheet().getCell(sel.row, 2).value(deal.unit);
-                    self.gclSpread.getActiveSheet().getCell(sel.row, 3).value(deal.unit_price);
-                    if (sel.row + 1 === self.gclSpread.getActiveSheet().getRowCount()) {
-                        const count = sel.row + 2;
-                        self.gclSpread.getActiveSheet().setRowCount(count);
-                        self.gclSpread.getActiveSheet().getCell(sel.row + 1, 0, GC.Spread.Sheets.SheetArea.rowHeader).text('清单' + count);
-
-                        const colCount = self.xmSpread.getActiveSheet().getColumnCount() + 1
-                        self.xmSpread.getActiveSheet().setColumnCount(colCount);
-                        self.xmSpread.getActiveSheet().getCell(0, colCount - 1, GC.Spread.Sheets.SheetArea.colHeader).text('数量' + count);
-                    }
-                    self.gclSpread.getActiveSheet().setSelection(sel.row + 1, sel.col, 1, 1);
-                })
+            // 拉取签约清单数据
+            if (dealBills) {
+                SpreadJsObj.loadSheetData(this.dealSpread.getActiveSheet(), 'data', dealBills.data);
+            } else {
+                postData(preUrl + '/deal/get-data', {}, function (data) {
+                    SpreadJsObj.loadSheetData(self.dealSpread.getActiveSheet(), 'data', data);
+                });
+            }
+            // 双击签约清单,自动添加到清单编号窗口
+            this.dealSpread.bind(GC.Spread.Sheets.Events.CellDoubleClick, function (e, info) {
+                const deal = info.sheet.zh_data[info.row];
+                const sel = self.gclSpread.getActiveSheet().getSelections()[0];
+                self.qdSpread.getActiveSheet().getCell(sel.row, 0).value(deal.code);
+                self.qdSpread.getActiveSheet().getCell(sel.row, 1).value(deal.name);
+                self.qdSpread.getActiveSheet().getCell(sel.row, 2).value(deal.unit);
+                self.qdSpread.getActiveSheet().getCell(sel.row, 3).value(deal.unit_price);
+                if (sel.row + 1 === self.gclSpread.getActiveSheet().getRowCount()) {
+                    const count = sel.row + 2;
+                    self.qdSpread.getActiveSheet().setRowCount(count);
+                    self.qdSpread.getActiveSheet().getCell(sel.row + 1, 0, GC.Spread.Sheets.SheetArea.rowHeader).text('清单' + count);
+
+                    const colCount = self.posSpread.getActiveSheet().getColumnCount() + 1
+                    self.posSpread.getActiveSheet().setColumnCount(colCount);
+                    self.posSpread.getActiveSheet().getCell(0, colCount - 1, GC.Spread.Sheets.SheetArea.colHeader).text('数量' + count);
+                }
+                self.qdSpread.getActiveSheet().setSelection(sel.row + 1, sel.col, 1, 1);
             });
 
             this.obj.bind('shown.bs.modal', function () {
-                self.xmSpread.refresh();
-                self.gclSpread.refresh();
+                self.qdSpread.refresh();
+                self.posSpread.refresh();
                 self.dealSpread.refresh();
             });
 
             $('#batch-ok').click(function () {
-                treeOperationObj.batchInsertData(ledgerSpread, {
-                    batchData: self.getBatchData(),
-                    batchType: self.batchType,
-                }, function () {
-                    self.obj.modal('hide');
-                });
+                const sheet = ledgerSpread.getActiveSheet();
+                const selection = sheet.getSelections();
+                const row = selection[0].row;
+                const select = ledgerTree.nodes[row];
+                if (select) {
+                    const insertData = {};
+                    insertData.batchType = (select.code && select.code !== '') ? 'child' : 'next';
+                    insertData.id = select[ledgerTree.setting.id];
+                    insertData.batchData = self.getBatchData();
+                    postData(preUrl+'/ledger/batch-insert', insertData, function (data) {
+                        pos.updateDatas(data.pos);
+                        ledgerTree.loadPostData(data.ledger, function (result) {
+                            treeOperationObj.refreshTree(sheet, result);
+                            treeOperationObj.refreshOperationValid(sheet, selection);
+                        });
+                        self.obj.modal('hide');
+                    });
+                }
             });
         }
-        initView() {
-            let time = new Date();
-            const xmSheet = this.xmSpread.getActiveSheet();
-            SpreadJsObj.initSheet(xmSheet, this.xmSpreadSetting);
-            console.log(new Date() - time);
-            time = new Date();
-
-            SpreadJsObj.beginMassOperation(xmSheet);
-            xmSheet.clear(0, 0, xmSheet.getRowCount(), xmSheet.getColumnCount(), GC.Spread.Sheets.SheetArea.viewport, GC.Spread.Sheets.StorageType.data);
-            SpreadJsObj.endMassOperation(xmSheet);
-            console.log(new Date() - time);
-            time = new Date();
-
-            const gclSheet = this.gclSpread.getActiveSheet();
-            SpreadJsObj.initSheet(gclSheet, this.gclSpreadSetting);
-            SpreadJsObj.beginMassOperation(gclSheet);
-            gclSheet.setColumnWidth(0, 45, GC.Spread.Sheets.SheetArea.rowHeader);
+        // 初始化左侧表格
+        initView () {
+            // 初始化 清单编号
+            const qdSheet = this.qdSpread.getActiveSheet();
+            SpreadJsObj.initSheet(qdSheet, this.qdSpreadSetting);
+            // 清理原有数据
+            SpreadJsObj.beginMassOperation(qdSheet);
+            qdSheet.clear(0, 0, qdSheet.getRowCount(), qdSheet.getColumnCount(), GC.Spread.Sheets.SheetArea.viewport, GC.Spread.Sheets.StorageType.data);
             for (let iRow = 1; iRow <= this.billsCount; iRow ++) {
-                gclSheet.getCell(iRow - 1, 0, GC.Spread.Sheets.SheetArea.rowHeader).text('清单' + iRow);
+                qdSheet.getCell(iRow - 1, 0, GC.Spread.Sheets.SheetArea.rowHeader).text('清单' + iRow);
             }
-            gclSheet.clear(0, 0, gclSheet.getRowCount(), gclSheet.getColumnCount(), GC.Spread.Sheets.SheetArea.viewport, GC.Spread.Sheets.StorageType.data);
-            SpreadJsObj.endMassOperation(gclSheet);
-            console.log(new Date() - time);
-            time = new Date();
+            SpreadJsObj.endMassOperation(qdSheet);
+            // 初始化 部位数量复核表
+            const posSheet = this.posSpread.getActiveSheet();
+            SpreadJsObj.initSheet(posSheet, this.posSpreadSetting);
+            // 清理原有数据
+            SpreadJsObj.beginMassOperation(posSheet);
+            posSheet.setColumnWidth(0, 45, GC.Spread.Sheets.SheetArea.rowHeader);
+            posSheet.clear(0, 0, posSheet.getRowCount(), posSheet.getColumnCount(), GC.Spread.Sheets.SheetArea.viewport, GC.Spread.Sheets.StorageType.data);
+            SpreadJsObj.endMassOperation(posSheet);
         }
+        // 获取界面数据
         getBatchData () {
             const result = [];
-            const xmSheet = this.xmSpread.getActiveSheet(), gclSheet = this.gclSpread.getActiveSheet();
-            for (let iRow = 0; iRow < xmSheet.getRowCount(); iRow++) {
-                if (xmSheet.getText(iRow, 0) === '') { continue; }
-                const xmj = {
-                    name: xmSheet.getText(iRow, 0),
-                    children: [],
+            const qdSheet = this.qdSpread.getActiveSheet(), posSheet = this.posSpread.getActiveSheet();
+            for (let iRow = 0; iRow < qdSheet.getRowCount(); iRow++) {
+                if (qdSheet.getText(iRow, 0) === '') { continue; }
+                const qd = {
+                    b_code: qdSheet.getText(iRow, 0),
+                    name: qdSheet.getText(iRow, 1),
+                    unit: qdSheet.getText(iRow, 2),
+                    price: _.toNumber(qdSheet.getText(iRow, 3)),
+                    pos: [],
                 };
-                result.push(xmj);
-                for (let iCol = 2; iCol < xmSheet.getColumnCount(); iCol++) {
-                    let value;
-                    try {
-                        value = parseFloat(xmSheet.getText(iRow, iCol));
-                        if (value !== 0 && !isNaN(value)) {
-                            const gcl = {
-                                b_code: gclSheet.getText(iCol - 2, 0),
-                                name: gclSheet.getText(iCol - 2, 1),
-                                unit: gclSheet.getText(iCol - 2, 2),
-                                quantity: value,
-                            }
-                            try {
-                                gcl.unit_price = parseFloat(gclSheet.getText(iCol - 2, 3));
-                            } catch (err) {
-                            }
-                            if (gcl.b_code !== '' || gcl.name !== '') {
-                                xmj.children.push(gcl);
-                            }
-                        }
-                    } catch (err) {
+                result.push(qd);
+                for (let iPosRow = 0; iPosRow < posSheet.getRowCount(); iPosRow++) {
+                    const value = _.toNumber(posSheet.getText(iPosRow, iRow + 2));
+                    if (value !== 0 && !isNaN(value)) {
+                        qd.pos.push({
+                            name: posSheet.getText(iPosRow, 0),
+                            drawing_code: posSheet.getText(iPosRow, 1),
+                            quantity: value,
+                        });
                     }
                 }
             }
             return result;
         }
-    }
+     }
     class SearchLedger {
         constructor(obj, spreadSetting) {
             const self = this;
@@ -946,7 +1044,7 @@ $(document).ready(function() {
         const data = {
             keyword: $('#searchName').val(),
         }
-        postData('/search/user', data, (data) => {
+        postData('/api/search/user', data, (data) => {
             const resultDiv = $('#searchResult');
             $('h5>span', resultDiv).text(data.name);
             $('#addAuditor').attr('auditorId', data.id);

+ 285 - 0
app/public/js/lz-string/base64-string.js

@@ -0,0 +1,285 @@
+// Copyright (c) 2013 Pieroxy <pieroxy@pieroxy.net>
+// This work is free. You can redistribute it and/or modify it
+// under the terms of the WTFPL, Version 2
+// For more information see LICENSE.txt or http://www.wtfpl.net/
+//
+// This lib is part of the lz-string project.
+// For more information, the home page:
+// http://pieroxy.net/blog/pages/lz-string/index.html
+//
+// Base64 compression / decompression for already compressed content (gif, png, jpg, mp3, ...) 
+// version 1.4.1
+var Base64String = {
+  
+  compressToUTF16 : function (input) {
+    var output = [],
+        i,c,
+        current,
+        status = 0;
+    
+    input = this.compress(input);
+    
+    for (i=0 ; i<input.length ; i++) {
+      c = input.charCodeAt(i);
+      switch (status++) {
+        case 0:
+          output.push(String.fromCharCode((c >> 1)+32));
+          current = (c & 1) << 14;
+          break;
+        case 1:
+          output.push(String.fromCharCode((current + (c >> 2))+32));
+          current = (c & 3) << 13;
+          break;
+        case 2:
+          output.push(String.fromCharCode((current + (c >> 3))+32));
+          current = (c & 7) << 12;
+          break;
+        case 3:
+          output.push(String.fromCharCode((current + (c >> 4))+32));
+          current = (c & 15) << 11;
+          break;
+        case 4:
+          output.push(String.fromCharCode((current + (c >> 5))+32));
+          current = (c & 31) << 10;
+          break;
+        case 5:
+          output.push(String.fromCharCode((current + (c >> 6))+32));
+          current = (c & 63) << 9;
+          break;
+        case 6:
+          output.push(String.fromCharCode((current + (c >> 7))+32));
+          current = (c & 127) << 8;
+          break;
+        case 7:
+          output.push(String.fromCharCode((current + (c >> 8))+32));
+          current = (c & 255) << 7;
+          break;
+        case 8:
+          output.push(String.fromCharCode((current + (c >> 9))+32));
+          current = (c & 511) << 6;
+          break;
+        case 9:
+          output.push(String.fromCharCode((current + (c >> 10))+32));
+          current = (c & 1023) << 5;
+          break;
+        case 10:
+          output.push(String.fromCharCode((current + (c >> 11))+32));
+          current = (c & 2047) << 4;
+          break;
+        case 11:
+          output.push(String.fromCharCode((current + (c >> 12))+32));
+          current = (c & 4095) << 3;
+          break;
+        case 12:
+          output.push(String.fromCharCode((current + (c >> 13))+32));
+          current = (c & 8191) << 2;
+          break;
+        case 13:
+          output.push(String.fromCharCode((current + (c >> 14))+32));
+          current = (c & 16383) << 1;
+          break;
+        case 14:
+          output.push(String.fromCharCode((current + (c >> 15))+32, (c & 32767)+32));
+          status = 0;
+          break;
+      }
+    }
+    output.push(String.fromCharCode(current + 32));
+    return output.join('');
+  },
+  
+
+  decompressFromUTF16 : function (input) {
+    var output = [],
+        current,c,
+        status=0,
+        i = 0;
+    
+    while (i < input.length) {
+      c = input.charCodeAt(i) - 32;
+      
+      switch (status++) {
+        case 0:
+          current = c << 1;
+          break;
+        case 1:
+          output.push(String.fromCharCode(current | (c >> 14)));
+          current = (c&16383) << 2;
+          break;
+        case 2:
+          output.push(String.fromCharCode(current | (c >> 13)));
+          current = (c&8191) << 3;
+          break;
+        case 3:
+          output.push(String.fromCharCode(current | (c >> 12)));
+          current = (c&4095) << 4;
+          break;
+        case 4:
+          output.push(String.fromCharCode(current | (c >> 11)));
+          current = (c&2047) << 5;
+          break;
+        case 5:
+          output.push(String.fromCharCode(current | (c >> 10)));
+          current = (c&1023) << 6;
+          break;
+        case 6:
+          output.push(String.fromCharCode(current | (c >> 9)));
+          current = (c&511) << 7;
+          break;
+        case 7:
+          output.push(String.fromCharCode(current | (c >> 8)));
+          current = (c&255) << 8;
+          break;
+        case 8:
+          output.push(String.fromCharCode(current | (c >> 7)));
+          current = (c&127) << 9;
+          break;
+        case 9:
+          output.push(String.fromCharCode(current | (c >> 6)));
+          current = (c&63) << 10;
+          break;
+        case 10:
+          output.push(String.fromCharCode(current | (c >> 5)));
+          current = (c&31) << 11;
+          break;
+        case 11:
+          output.push(String.fromCharCode(current | (c >> 4)));
+          current = (c&15) << 12;
+          break;
+        case 12:
+          output.push(String.fromCharCode(current | (c >> 3)));
+          current = (c&7) << 13;
+          break;
+        case 13:
+          output.push(String.fromCharCode(current | (c >> 2)));
+          current = (c&3) << 14;
+          break;
+        case 14:
+          output.push(String.fromCharCode(current | (c >> 1)));
+          current = (c&1) << 15;
+          break;
+        case 15:
+          output.push(String.fromCharCode(current | c));
+          status=0;
+          break;
+      }
+      
+      
+      i++;
+    }
+    
+    return this.decompress(output.join(''));
+    //return output;
+    
+  },
+
+
+  // private property
+  _keyStr : "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",
+  
+  decompress : function (input) {
+    var output = [];
+    var chr1, chr2, chr3, enc1, enc2, enc3, enc4;
+    var i = 1;
+    var odd = input.charCodeAt(0) >> 8;
+    
+    while (i < input.length*2 && (i < input.length*2-1 || odd==0)) {
+      
+      if (i%2==0) {
+        chr1 = input.charCodeAt(i/2) >> 8;
+        chr2 = input.charCodeAt(i/2) & 255;
+        if (i/2+1 < input.length) 
+          chr3 = input.charCodeAt(i/2+1) >> 8;
+        else 
+          chr3 = NaN;
+      } else {
+        chr1 = input.charCodeAt((i-1)/2) & 255;
+        if ((i+1)/2 < input.length) {
+          chr2 = input.charCodeAt((i+1)/2) >> 8;
+          chr3 = input.charCodeAt((i+1)/2) & 255;
+        } else 
+          chr2=chr3=NaN;
+      }
+      i+=3;
+      
+      enc1 = chr1 >> 2;
+      enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
+      enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
+      enc4 = chr3 & 63;
+      
+      if (isNaN(chr2) || (i==input.length*2+1 && odd)) {
+        enc3 = enc4 = 64;
+      } else if (isNaN(chr3) || (i==input.length*2 && odd)) {
+        enc4 = 64;
+      }
+      
+      output.push(this._keyStr.charAt(enc1));
+      output.push(this._keyStr.charAt(enc2));
+      output.push(this._keyStr.charAt(enc3));
+      output.push(this._keyStr.charAt(enc4));
+    }
+    
+    return output.join('');
+  },
+  
+  compress : function (input) {
+    var output = [],
+        ol = 1, 
+        output_,
+        chr1, chr2, chr3,
+        enc1, enc2, enc3, enc4,
+        i = 0, flush=false;
+    
+    input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");
+    
+    while (i < input.length) {
+      
+      enc1 = this._keyStr.indexOf(input.charAt(i++));
+      enc2 = this._keyStr.indexOf(input.charAt(i++));
+      enc3 = this._keyStr.indexOf(input.charAt(i++));
+      enc4 = this._keyStr.indexOf(input.charAt(i++));
+      
+      chr1 = (enc1 << 2) | (enc2 >> 4);
+      chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
+      chr3 = ((enc3 & 3) << 6) | enc4;
+      
+      if (ol%2==0) {
+        output_ = chr1 << 8;
+        flush = true;
+        
+        if (enc3 != 64) {
+          output.push(String.fromCharCode(output_ | chr2));
+          flush = false;
+        }
+        if (enc4 != 64) {
+          output_ = chr3 << 8;
+          flush = true;
+        }
+      } else {
+        output.push(String.fromCharCode(output_ | chr1));
+        flush = false;
+        
+        if (enc3 != 64) {
+          output_ = chr2 << 8;
+          flush = true;
+        }
+        if (enc4 != 64) {
+          output.push(String.fromCharCode(output_ | chr3));
+          flush = false;
+        }
+      }
+      ol+=3;
+    }
+    
+    if (flush) {
+      output.push(String.fromCharCode(output_));
+      output = output.join('');
+      output = String.fromCharCode(output.charCodeAt(0)|256) + output.substring(1);
+    } else {
+      output = output.join('');
+    }
+    
+    return output;
+    
+  }
+}

+ 506 - 0
app/public/js/lz-string/lz-string.js

@@ -0,0 +1,506 @@
+// Copyright (c) 2013 Pieroxy <pieroxy@pieroxy.net>
+// This work is free. You can redistribute it and/or modify it
+// under the terms of the WTFPL, Version 2
+// For more information see LICENSE.txt or http://www.wtfpl.net/
+//
+// For more information, the home page:
+// http://pieroxy.net/blog/pages/lz-string/testing.html
+//
+// LZ-based compression algorithm, version 1.4.4
+var LZString = (function() {
+
+// private property
+var f = String.fromCharCode;
+var keyStrBase64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
+var keyStrUriSafe = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+-$";
+var baseReverseDic = {};
+
+function getBaseValue(alphabet, character) {
+  if (!baseReverseDic[alphabet]) {
+    baseReverseDic[alphabet] = {};
+    for (var i=0 ; i<alphabet.length ; i++) {
+      baseReverseDic[alphabet][alphabet.charAt(i)] = i;
+    }
+  }
+  return baseReverseDic[alphabet][character];
+}
+
+var LZString = {
+  compressToBase64 : function (input) {
+    if (input == null) return "";
+    var res = LZString._compress(input, 6, function(a){return keyStrBase64.charAt(a);});
+    switch (res.length % 4) { // To produce valid Base64
+    default: // When could this happen ?
+    case 0 : return res;
+    case 1 : return res+"===";
+    case 2 : return res+"==";
+    case 3 : return res+"=";
+    }
+  },
+
+  decompressFromBase64 : function (input) {
+    if (input == null) return "";
+    if (input == "") return null;
+    return LZString._decompress(input.length, 32, function(index) { return getBaseValue(keyStrBase64, input.charAt(index)); });
+  },
+
+  compressToUTF16 : function (input) {
+    if (input == null) return "";
+    return LZString._compress(input, 15, function(a){return f(a+32);}) + " ";
+  },
+
+  decompressFromUTF16: function (compressed) {
+    if (compressed == null) return "";
+    if (compressed == "") return null;
+    return LZString._decompress(compressed.length, 16384, function(index) { return compressed.charCodeAt(index) - 32; });
+  },
+
+  //compress into uint8array (UCS-2 big endian format)
+  compressToUint8Array: function (uncompressed) {
+    var compressed = LZString.compress(uncompressed);
+    var buf=new Uint8Array(compressed.length*2); // 2 bytes per character
+
+    for (var i=0, TotalLen=compressed.length; i<TotalLen; i++) {
+      var current_value = compressed.charCodeAt(i);
+      buf[i*2] = current_value >>> 8;
+      buf[i*2+1] = current_value % 256;
+    }
+    return buf;
+  },
+
+  //decompress from uint8array (UCS-2 big endian format)
+  decompressFromUint8Array:function (compressed) {
+    if (compressed===null || compressed===undefined){
+        return LZString.decompress(compressed);
+    } else {
+        var buf=new Array(compressed.length/2); // 2 bytes per character
+        for (var i=0, TotalLen=buf.length; i<TotalLen; i++) {
+          buf[i]=compressed[i*2]*256+compressed[i*2+1];
+        }
+
+        var result = [];
+        buf.forEach(function (c) {
+          result.push(f(c));
+        });
+        return LZString.decompress(result.join(''));
+
+    }
+
+  },
+
+
+  //compress into a string that is already URI encoded
+  compressToEncodedURIComponent: function (input) {
+    if (input == null) return "";
+    return LZString._compress(input, 6, function(a){return keyStrUriSafe.charAt(a);});
+  },
+
+  //decompress from an output of compressToEncodedURIComponent
+  decompressFromEncodedURIComponent:function (input) {
+    if (input == null) return "";
+    if (input == "") return null;
+    input = input.replace(/ /g, "+");
+    return LZString._decompress(input.length, 32, function(index) { return getBaseValue(keyStrUriSafe, input.charAt(index)); });
+  },
+
+  compress: function (uncompressed) {
+    return LZString._compress(uncompressed, 16, function(a){return f(a);});
+  },
+  _compress: function (uncompressed, bitsPerChar, getCharFromInt) {
+    if (uncompressed == null) return "";
+    var i, value,
+        context_dictionary= {},
+        context_dictionaryToCreate= {},
+        context_c="",
+        context_wc="",
+        context_w="",
+        context_enlargeIn= 2, // Compensate for the first entry which should not count
+        context_dictSize= 3,
+        context_numBits= 2,
+        context_data=[],
+        context_data_val=0,
+        context_data_position=0,
+        ii;
+
+    for (ii = 0; ii < uncompressed.length; ii += 1) {
+      context_c = uncompressed.charAt(ii);
+      if (!Object.prototype.hasOwnProperty.call(context_dictionary,context_c)) {
+        context_dictionary[context_c] = context_dictSize++;
+        context_dictionaryToCreate[context_c] = true;
+      }
+
+      context_wc = context_w + context_c;
+      if (Object.prototype.hasOwnProperty.call(context_dictionary,context_wc)) {
+        context_w = context_wc;
+      } else {
+        if (Object.prototype.hasOwnProperty.call(context_dictionaryToCreate,context_w)) {
+          if (context_w.charCodeAt(0)<256) {
+            for (i=0 ; i<context_numBits ; i++) {
+              context_data_val = (context_data_val << 1);
+              if (context_data_position == bitsPerChar-1) {
+                context_data_position = 0;
+                context_data.push(getCharFromInt(context_data_val));
+                context_data_val = 0;
+              } else {
+                context_data_position++;
+              }
+            }
+            value = context_w.charCodeAt(0);
+            for (i=0 ; i<8 ; i++) {
+              context_data_val = (context_data_val << 1) | (value&1);
+              if (context_data_position == bitsPerChar-1) {
+                context_data_position = 0;
+                context_data.push(getCharFromInt(context_data_val));
+                context_data_val = 0;
+              } else {
+                context_data_position++;
+              }
+              value = value >> 1;
+            }
+          } else {
+            value = 1;
+            for (i=0 ; i<context_numBits ; i++) {
+              context_data_val = (context_data_val << 1) | value;
+              if (context_data_position ==bitsPerChar-1) {
+                context_data_position = 0;
+                context_data.push(getCharFromInt(context_data_val));
+                context_data_val = 0;
+              } else {
+                context_data_position++;
+              }
+              value = 0;
+            }
+            value = context_w.charCodeAt(0);
+            for (i=0 ; i<16 ; i++) {
+              context_data_val = (context_data_val << 1) | (value&1);
+              if (context_data_position == bitsPerChar-1) {
+                context_data_position = 0;
+                context_data.push(getCharFromInt(context_data_val));
+                context_data_val = 0;
+              } else {
+                context_data_position++;
+              }
+              value = value >> 1;
+            }
+          }
+          context_enlargeIn--;
+          if (context_enlargeIn == 0) {
+            context_enlargeIn = Math.pow(2, context_numBits);
+            context_numBits++;
+          }
+          delete context_dictionaryToCreate[context_w];
+        } else {
+          value = context_dictionary[context_w];
+          for (i=0 ; i<context_numBits ; i++) {
+            context_data_val = (context_data_val << 1) | (value&1);
+            if (context_data_position == bitsPerChar-1) {
+              context_data_position = 0;
+              context_data.push(getCharFromInt(context_data_val));
+              context_data_val = 0;
+            } else {
+              context_data_position++;
+            }
+            value = value >> 1;
+          }
+
+
+        }
+        context_enlargeIn--;
+        if (context_enlargeIn == 0) {
+          context_enlargeIn = Math.pow(2, context_numBits);
+          context_numBits++;
+        }
+        // Add wc to the dictionary.
+        context_dictionary[context_wc] = context_dictSize++;
+        context_w = String(context_c);
+      }
+    }
+
+    // Output the code for w.
+    if (context_w !== "") {
+      if (Object.prototype.hasOwnProperty.call(context_dictionaryToCreate,context_w)) {
+        if (context_w.charCodeAt(0)<256) {
+          for (i=0 ; i<context_numBits ; i++) {
+            context_data_val = (context_data_val << 1);
+            if (context_data_position == bitsPerChar-1) {
+              context_data_position = 0;
+              context_data.push(getCharFromInt(context_data_val));
+              context_data_val = 0;
+            } else {
+              context_data_position++;
+            }
+          }
+          value = context_w.charCodeAt(0);
+          for (i=0 ; i<8 ; i++) {
+            context_data_val = (context_data_val << 1) | (value&1);
+            if (context_data_position == bitsPerChar-1) {
+              context_data_position = 0;
+              context_data.push(getCharFromInt(context_data_val));
+              context_data_val = 0;
+            } else {
+              context_data_position++;
+            }
+            value = value >> 1;
+          }
+        } else {
+          value = 1;
+          for (i=0 ; i<context_numBits ; i++) {
+            context_data_val = (context_data_val << 1) | value;
+            if (context_data_position == bitsPerChar-1) {
+              context_data_position = 0;
+              context_data.push(getCharFromInt(context_data_val));
+              context_data_val = 0;
+            } else {
+              context_data_position++;
+            }
+            value = 0;
+          }
+          value = context_w.charCodeAt(0);
+          for (i=0 ; i<16 ; i++) {
+            context_data_val = (context_data_val << 1) | (value&1);
+            if (context_data_position == bitsPerChar-1) {
+              context_data_position = 0;
+              context_data.push(getCharFromInt(context_data_val));
+              context_data_val = 0;
+            } else {
+              context_data_position++;
+            }
+            value = value >> 1;
+          }
+        }
+        context_enlargeIn--;
+        if (context_enlargeIn == 0) {
+          context_enlargeIn = Math.pow(2, context_numBits);
+          context_numBits++;
+        }
+        delete context_dictionaryToCreate[context_w];
+      } else {
+        value = context_dictionary[context_w];
+        for (i=0 ; i<context_numBits ; i++) {
+          context_data_val = (context_data_val << 1) | (value&1);
+          if (context_data_position == bitsPerChar-1) {
+            context_data_position = 0;
+            context_data.push(getCharFromInt(context_data_val));
+            context_data_val = 0;
+          } else {
+            context_data_position++;
+          }
+          value = value >> 1;
+        }
+
+
+      }
+      context_enlargeIn--;
+      if (context_enlargeIn == 0) {
+        context_enlargeIn = Math.pow(2, context_numBits);
+        context_numBits++;
+      }
+    }
+
+    // Mark the end of the stream
+    value = 2;
+    for (i=0 ; i<context_numBits ; i++) {
+      context_data_val = (context_data_val << 1) | (value&1);
+      if (context_data_position == bitsPerChar-1) {
+        context_data_position = 0;
+        context_data.push(getCharFromInt(context_data_val));
+        context_data_val = 0;
+      } else {
+        context_data_position++;
+      }
+      value = value >> 1;
+    }
+
+    // Flush the last char
+    while (true) {
+      context_data_val = (context_data_val << 1);
+      if (context_data_position == bitsPerChar-1) {
+        context_data.push(getCharFromInt(context_data_val));
+        break;
+      }
+      else context_data_position++;
+    }
+    return context_data.join('');
+  },
+
+  decompress: function (compressed) {
+    if (compressed == null) return "";
+    if (compressed == "") return null;
+    return LZString._decompress(compressed.length, 32768, function(index) { return compressed.charCodeAt(index); });
+  },
+
+  _decompress: function (length, resetValue, getNextValue) {
+    var dictionary = [],
+        next,
+        enlargeIn = 4,
+        dictSize = 4,
+        numBits = 3,
+        entry = "",
+        result = [],
+        i,
+        w,
+        bits, resb, maxpower, power,
+        c,
+        data = {val:getNextValue(0), position:resetValue, index:1};
+
+    for (i = 0; i < 3; i += 1) {
+      dictionary[i] = i;
+    }
+
+    bits = 0;
+    maxpower = Math.pow(2,2);
+    power=1;
+    while (power!=maxpower) {
+      resb = data.val & data.position;
+      data.position >>= 1;
+      if (data.position == 0) {
+        data.position = resetValue;
+        data.val = getNextValue(data.index++);
+      }
+      bits |= (resb>0 ? 1 : 0) * power;
+      power <<= 1;
+    }
+
+    switch (next = bits) {
+      case 0:
+          bits = 0;
+          maxpower = Math.pow(2,8);
+          power=1;
+          while (power!=maxpower) {
+            resb = data.val & data.position;
+            data.position >>= 1;
+            if (data.position == 0) {
+              data.position = resetValue;
+              data.val = getNextValue(data.index++);
+            }
+            bits |= (resb>0 ? 1 : 0) * power;
+            power <<= 1;
+          }
+        c = f(bits);
+        break;
+      case 1:
+          bits = 0;
+          maxpower = Math.pow(2,16);
+          power=1;
+          while (power!=maxpower) {
+            resb = data.val & data.position;
+            data.position >>= 1;
+            if (data.position == 0) {
+              data.position = resetValue;
+              data.val = getNextValue(data.index++);
+            }
+            bits |= (resb>0 ? 1 : 0) * power;
+            power <<= 1;
+          }
+        c = f(bits);
+        break;
+      case 2:
+        return "";
+    }
+    dictionary[3] = c;
+    w = c;
+    result.push(c);
+    while (true) {
+      if (data.index > length) {
+        return "";
+      }
+
+      bits = 0;
+      maxpower = Math.pow(2,numBits);
+      power=1;
+      while (power!=maxpower) {
+        resb = data.val & data.position;
+        data.position >>= 1;
+        if (data.position == 0) {
+          data.position = resetValue;
+          data.val = getNextValue(data.index++);
+        }
+        bits |= (resb>0 ? 1 : 0) * power;
+        power <<= 1;
+      }
+
+      switch (c = bits) {
+        case 0:
+          bits = 0;
+          maxpower = Math.pow(2,8);
+          power=1;
+          while (power!=maxpower) {
+            resb = data.val & data.position;
+            data.position >>= 1;
+            if (data.position == 0) {
+              data.position = resetValue;
+              data.val = getNextValue(data.index++);
+            }
+            bits |= (resb>0 ? 1 : 0) * power;
+            power <<= 1;
+          }
+
+          dictionary[dictSize++] = f(bits);
+          c = dictSize-1;
+          enlargeIn--;
+          break;
+        case 1:
+          bits = 0;
+          maxpower = Math.pow(2,16);
+          power=1;
+          while (power!=maxpower) {
+            resb = data.val & data.position;
+            data.position >>= 1;
+            if (data.position == 0) {
+              data.position = resetValue;
+              data.val = getNextValue(data.index++);
+            }
+            bits |= (resb>0 ? 1 : 0) * power;
+            power <<= 1;
+          }
+          dictionary[dictSize++] = f(bits);
+          c = dictSize-1;
+          enlargeIn--;
+          break;
+        case 2:
+          return result.join('');
+      }
+
+      if (enlargeIn == 0) {
+        enlargeIn = Math.pow(2, numBits);
+        numBits++;
+      }
+
+      if (dictionary[c]) {
+        entry = dictionary[c];
+      } else {
+        if (c === dictSize) {
+          entry = w + w.charAt(0);
+        } else {
+          return null;
+        }
+      }
+      result.push(entry);
+
+      // Add w+entry[0] to the dictionary.
+      dictionary[dictSize++] = w + entry.charAt(0);
+      enlargeIn--;
+
+      w = entry;
+
+      if (enlargeIn == 0) {
+        enlargeIn = Math.pow(2, numBits);
+        numBits++;
+      }
+
+    }
+  }
+};
+  return LZString;
+})();
+
+if (typeof define === 'function' && define.amd) {
+  define(function () { return LZString; });
+} else if( typeof module !== 'undefined' && module != null ) {
+  module.exports = LZString
+} else if( typeof angular !== 'undefined' && angular != null ) {
+  angular.module('LZString', [])
+  .factory('LZString', function () {
+    return LZString;
+  });
+}

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 1 - 0
app/public/js/lz-string/lz-string.min.js


+ 1 - 1
app/public/js/measure_work.js

@@ -106,7 +106,7 @@ $(document).ready(() => {
         const data = {
             keyword: $('#searchName').val(),
         }
-        postData('/search/user', data, (data) => {
+        postData('/api/search/user', data, (data) => {
             const resultDiv = $('#searchResult');
             $('h5>span', resultDiv).text(data.name);
             $('#addAuditor').attr('auditorId', data.id);

+ 677 - 0
app/public/js/path_tree.bak2.js

@@ -0,0 +1,677 @@
+/**
+ * 构建pathTree
+ * 可动态加载子节点,要求子节点获取接口按/xxx/get-children定义
+ * @param {Object} setting - 设置
+ * @returns {PathTree}
+ */
+
+'use strict';
+
+const itemsPre = 'id_';
+
+const createNewPathTree = function (type, setting) {
+    class BaseTree {
+        /**
+         * 构造函数
+         */
+        constructor (setting) {
+            // 无索引
+            this.datas = [];
+            // 以key为索引
+            this.items = {};
+            // 以排序为索引
+            this.nodes = [];
+            // 索引
+            this.children = {};
+            // 树设置
+            this.setting = JSON.parse(JSON.stringify(setting));
+        }
+        /**
+         * 树结构根据显示排序
+         */
+        sortTreeNode (isResort) {
+            const self = this;
+            const addSortNodes = function (nodes) {
+                if (!nodes) { return }
+                for (let i = 0; i < nodes.length; i++) {
+                    self.nodes.push(nodes[i]);
+                    nodes[i].index = self.nodes.length - 1;
+                    if (!isResort) {
+                        nodes[i].children = self.getChildren(nodes[i]);
+                    }
+                    addSortNodes(nodes[i].children);
+                }
+            };
+            self.nodes = [];
+            addSortNodes(this.getChildren(null));
+        }
+        /**
+         * 加载数据(初始化), 并给数据添加部分树结构必须数据
+         * @param datas
+         */
+        loadDatas (datas) {
+            // 清空旧数据
+            this.items = {};
+            this.nodes = [];
+            // 加载全部数据
+            for (const data of datas) {
+                const keyName = itemsPre + data[this.setting.id];
+                this.items[keyName] = JSON.parse(JSON.stringify(data));
+                this.datas.push(this.items[keyName]);
+            }
+            this.sortTreeNode();
+            for (const node of this.nodes) {
+                node.expanded = node.children.length > 0;
+                node.visible = true;
+            }
+        }
+
+        /**
+         * 根据id获取树结构节点数据
+         * @param {Number} id
+         * @returns {Object}
+         */
+        getItems (id) {
+            return this.items[itemsPre + id];
+        };
+        /**
+         * 查找node的parent
+         * @param {Object} node
+         * @returns {Object}
+         */
+        getParent (node) {
+            return this.getItems(node[this.setting.pid]);
+        };
+
+        /**
+         * 根据path查找完整节点
+         * @param {Number} path
+         */
+        getFullPathNodes (path) {
+            const self = this, ids = path.split('.');
+            if (ids.length > 0) {
+                return this.nodes.filter((x) => {
+                    return ids.indexOf('' + x[self.setting.id]) >= 0;
+                });
+            } else {
+                return [];
+            }
+        };
+        /**
+         * 查询node的已下载子节点
+         * @param {Object} node
+         * @returns {Array}
+         */
+        getChildren (node) {
+            const setting = this.setting;
+            const pid = node ? node[setting.id] : setting.rootId;
+            const children = this.datas.filter(function (x) {
+                return x[setting.pid] === pid;
+            });
+            children.sort(function (a, b) {
+                return a.order - b.order;
+            });
+            return children;
+        };
+        /**
+         * 查询node的已下载的全部后代
+         * @param {Object} node
+         * @returns {Array}
+         */
+        getPosterity (node) {
+            const reg = new RegExp('^' + node.full_path + '.');
+            return this.datas.filter(function (x) {
+                return reg.test(x.full_path);
+            })
+        };
+        /**
+         * 查询node是否是父节点的最后一个子节点
+         * @param {Object} node
+         * @returns {boolean}
+         */
+        isLastSibling (node) {
+            const siblings = this.getChildren(this.getParent(node));
+            return node.order === siblings[siblings.length - 1].order;
+        };
+        /**
+         * 刷新子节点是否可见
+         * @param {Object} node
+         * @private
+         */
+        _refreshChildrenVisible (node) {
+            if (!node.children) {
+                node.children = this.getChildren(node);
+            }
+            if (node.children && node.children.length > 0) {
+                for (const child of node.children) {
+                    child.visible = node.expanded && node.visible;
+                    this._refreshChildrenVisible(child);
+                }
+            }
+        };
+        /**
+         * 设置节点是否展开, 并控制子节点可见
+         * @param {Object} node
+         * @param {Boolean} expanded
+         */
+        setExpanded (node, expanded) {
+            node.expanded = expanded;
+            this._refreshChildrenVisible(node);
+        };
+
+        /**
+         * 提取节点key和索引数据
+         * @param {Object} node - 节点
+         * @returns {key}
+         */
+        getNodeKeyData (node) {
+            const data = {};
+            for (const key of this.setting.keys) {
+                data[key] = node[key];
+            }
+            return data;
+        };
+        /**
+         * 得到树结构构成id
+         * @param node
+         * @returns {*}
+         */
+        getNodeKey (node) {
+            return node[this.setting.id];
+        };
+    }
+
+    class MeasureTree extends BaseTree {
+        addData (datas) {
+            const loadedData = [];
+            for (const data of datas) {
+                let node = this.getItems(data[this.setting.id]);
+                if (node) {
+                    for (const prop in node) {
+                        if (data[prop] !== undefined) {
+                            node[prop] = data[prop];
+                        }
+                    }
+                    loadedData.push(node);
+                } else {
+                    const keyName = itemsPre + data[this.setting.id];
+                    const node = JSON.parse(JSON.stringify(data));
+                    this.items[keyName] = node;
+                    this.datas.push(node);
+                    node.expanded = false;
+                    node.visible = true;
+                    loadedData.push(node);
+                }
+            }
+            this.sortTreeNode();
+            for (const node of loadedData) {
+                const children = node.children;
+                if (!node.expanded && children.length > 0) {
+                    node.expanded = true;
+                    this._refreshChildrenVisible(node);
+                }
+            }
+            return loadedData;
+        }
+        removeData (datas) {
+            datas.sort(function (a, b) {
+                return b.level - a.level;
+            });
+            console.log(datas);
+            const removeArrayData = function (array, data) {
+                const index = array.indexOf(data);
+                array.splice(index, 1);
+            };
+            for (const data of datas) {
+                const node = this.getItems(data[this.setting.id]);
+                if (node && this.getChildren(node).length === 0) {
+                    delete this.items[itemsPre + node[this.setting.id]];
+                    if (node[this.setting.pid] !== this.setting.rootId) {
+                        const parent = this.items[itemsPre + node[this.setting.pid]];
+                        removeArrayData(parent.children, node);
+                    }
+                    removeArrayData(this.datas, node);
+                    removeArrayData(this.nodes, node);
+                }
+            }
+        };
+        loadLeafData (data) {
+            const datas = data instanceof Array ? data : [data];
+            for (const d of datas) {
+                let node = this.getItems(d[this.setting.id]);
+                if (node && node.is_leaf) {
+                    for (const prop in node) {
+                        if (data[prop] !== undefined) {
+                            node[prop] = d[prop];
+                        }
+                    }
+                }
+            }
+        };
+    }
+
+    class ActiveTree extends BaseTree {
+
+        /**
+         * 加载数据(动态),只加载不同部分
+         * @param {Array} datas
+         * @return {Array} 加载到树的数据
+         * @privateA
+         */
+        _loadData (datas) {
+            const loadedData = [];
+            for (const data of datas) {
+                let node = this.getItems(data[this.setting.id]);
+                if (node) {
+                    for (const prop in node) {
+                        if (data[prop] !== undefined) {
+                            node[prop] = data[prop];
+                        }
+                    }
+                    loadedData.push(node);
+                } else {
+                    const keyName = itemsPre + data[this.setting.id];
+                    const node = JSON.parse(JSON.stringify(data));
+                    this.items[keyName] = node;
+                    this.datas.push(node);
+                    node.expanded = false;
+                    node.visible = true;
+                    loadedData.push(node);
+                }
+            }
+            this.sortTreeNode();
+            for (const node of loadedData) {
+                const children = node.children;
+                if (!node.expanded && children.length > 0) {
+                    node.expanded = true;
+                    this._refreshChildrenVisible(node);
+                }
+            }
+            return loadedData;
+        };
+
+        /**
+         * 以下方法需等待响应, 通过callback刷新界面
+         */
+        /**
+         * 加载子节点
+         * @param {Object} node
+         * @param {function} callback
+         */
+        loadChildren (node, callback) {
+            const self = this;
+            const url = this.setting.preUrl ? this.setting.preUrl + '/get-children' : 'get-children';
+            console.log(url);
+            postData(url, this.getNodeKeyData(node), function (data) {
+                self._loadData(data);
+                callback();
+            });
+        };
+    }
+
+    class LedgerTree extends BaseTree {
+
+        /**
+         * 加载数据(动态),只加载不同部分
+         * @param {Array} datas
+         * @return {Array} 加载到树的数据
+         * @privateA
+         */
+        _updateData (datas) {
+            const loadedData = [];
+            for (const data of datas) {
+                let node = this.getItems(data[this.setting.id]);
+                if (node) {
+                    for (const prop in node) {
+                        if (data[prop] !== undefined) {
+                            node[prop] = data[prop];
+                        }
+                    }
+                    loadedData.push(node);
+                }
+            }
+            for (const node of loadedData) {
+                const children = this.getChildren(node);
+                node.expanded = children.length > 0 && children[0].visible;
+            }
+            this.sortTreeNode(true);
+            return loadedData;
+        };
+        /**
+         * 加载数据(动态),只加载不同部分
+         * @param {Array} datas
+         * @return {Array} 加载到树的数据
+         * @privateA
+         */
+        _loadData (datas) {
+            const loadedData = [], resortData = [];
+            for (const data of datas) {
+                let node = this.getItems(data[this.setting.id]);
+                if (node) {
+                    const parent = this.getItems(node[this.setting.pid]);
+                    for (const prop in node) {
+                        if (data[prop] !== undefined && data[prop] !== node[prop]) {
+                            node[prop] = data[prop];
+                            if (parent && resortData.indexOf(parent) === -1) {
+                                resortData.push(parent);
+                            }
+                        }
+                    }
+                    loadedData.push(node);
+                } else {
+                    const keyName = itemsPre + data[this.setting.id];
+                    const node = JSON.parse(JSON.stringify(data));
+                    this.items[keyName] = node;
+                    this.datas.push(node);
+                    node.expanded = false;
+                    node.visible = true;
+                    loadedData.push(node);
+                    if (resortData.indexOf(node) === -1) {
+                        resortData.push(node);
+                    }
+                    const parent = this.getItems(node[this.setting.pid]);
+                    if (parent && resortData.indexOf(parent) === -1) {
+                        resortData.push(parent);
+                    }
+                }
+            }
+            for (const node of resortData) {
+                node.children = this.getChildren(node);
+            }
+            this.sortTreeNode(true);
+            for (const node of loadedData) {
+                if (!node.expanded) {
+                    this.setExpanded(node, true);
+                }
+            }
+            return loadedData;
+        };
+        /**
+         * 清理数据(动态)
+         * @param datas
+         * @private
+         */
+        _freeData (datas) {
+            const removeArrayData = function (array, data) {
+                const index = array.indexOf(data);
+                array.splice(index, 1);
+            };
+            for (const data of datas) {
+                const node = this.getItems(data[this.setting.id]);
+                if (node) {
+                    delete this.items[itemsPre + node[this.setting.id]];
+                    if (node[this.setting.pid] !== this.setting.rootId) {
+                        const parent = this.getItems(node[this.setting.pid]);
+                        if (parent) {
+                            removeArrayData(parent.children, node);
+                        }
+                    }
+                    removeArrayData(this.datas, node);
+                    removeArrayData(this.nodes, node);
+                }
+            }
+        };
+        /**
+         * 加载需展开的数据
+         * @param {Array} datas
+         * @returns {Array}
+         * @private
+         */
+        _loadExpandData (datas) {
+            const loadedData = [], existData = [], expandData = [], resortData = [];
+            for (const data of datas) {
+                let node = this.getItems(data[this.setting.id]);
+                if (node) {
+                    existData.push(node);
+                } else {
+                    const keyName = itemsPre + data[this.setting.id];
+                    const node = JSON.parse(JSON.stringify(data));
+                    this.items[keyName] = node;
+                    this.datas.push(node);
+                    node.expanded = false;
+                    node.visible = true;
+                    loadedData.push(node);
+
+                    if (resortData.indexOf(node) === -1) {
+                        resortData.push(node);
+                    }
+                    const parent = this.getItems(node[this.setting.pid]);
+                    if (parent && resortData.indexOf(parent) === -1) {
+                        resortData.push(parent);
+                    }
+                }
+            }
+            for (const node of resortData) {
+                node.children = this.getChildren(node);
+            }
+            this.sortTreeNode(true);
+            for (const node of loadedData) {
+                if (!node.expanded) {
+                    this.setExpanded(node, true);
+                }
+            }
+            for (const node of existData) {
+                const parent = this.getItems(node[this.setting.pid]);
+                if (expandData.indexOf(parent) === -1) {
+                    expandData.push(parent);
+                    if (!parent.expanded) {
+                        this.setExpanded(parent, true);
+                    }
+                }
+                if (!node.expanded) {
+                    this.setExpanded(node, true);
+                }
+            }
+            return [loadedData, expandData];
+        };
+
+        /**
+         * 以下方法需等待响应, 通过callback刷新界面
+         */
+        /**
+         * 加载子节点
+         * @param {Object} node
+         * @param {function} callback
+         */
+        loadChildren (node, callback) {
+            const self = this;
+            const url = this.setting.preUrl ? this.setting.preUrl + '/get-children' : 'get-children';
+            console.log(url);
+            postData(url, this.getNodeKeyData(node), function (data) {
+                self._loadData(data);
+                callback();
+            });
+        };
+        /**
+         * 树结构基本操作
+         * @param {String} url - 请求地址
+         * @param {Object} node - 操作节点
+         * @param {String} type - 操作类型
+         * @param {function} callback - 界面刷新
+         */
+        baseOperation (url, node, type, callback) {
+            const self = this;
+            const data = {
+                id: node[this.setting.id],
+                postType: type
+            };
+            postData(url, data, function (datas) {
+                const result = {};
+                if (datas.update) {
+                    result.update = self._updateData(datas.update);
+                }
+                if (datas.create) {
+                    result.create = self._loadData(datas.create);
+                }
+                if (datas.delete) {
+                    result.delete = self._freeData(datas.delete);
+                }
+                callback(result);
+            });
+        };
+        /**
+         * 节点数据编辑
+         * @param {String} url - 请求地址
+         * @param {Array|Object} updateData - 需更新的数据
+         * @param {function} callback - 界面刷新
+         */
+        update (url, updateData, callback) {
+            const self = this;
+            postData(url, updateData, function (datas) {
+                const result = self._updateData(datas);
+                callback(result);
+            }, function () {
+                if (updateData instanceof Array) {
+                    const result = [];
+                    for (const data of updateData) {
+                        result.push(self.getItems(data[self.setting.id]));
+                    }
+                    callback(result)
+                } else {
+                    callback([self.getItems(updateData[self.setting.id])]);
+                }
+            });
+        };
+        /**
+         * 复制粘贴整块(目前仅可粘贴为后项)
+         * @param {String} url - 请求地址
+         * @param {Object} node - 操作节点
+         * @param {Array} block - 被复制整块的节点列表
+         * @param {function} callback - 界面刷新
+         */
+        pasteBlock (url, node, block, callback) {
+            const self = this;
+            const data = {
+                id: node[self.setting.id],
+                block: block
+            };
+            postData(url, data, function (datas) {
+                const result = {};
+                if (datas.update) {
+                    result.update = self._updateData(datas.update);
+                }
+                if (datas.create) {
+                    result.create = self._loadData(datas.create);
+                }
+                if (datas.delete) {
+                    result.delete = self._freeData(datas.delete);
+                }
+                callback(result);
+            });
+        };
+        /**
+         * 提交数据
+         * @param {String} url - 请求地址
+         * @param {Object} node - 当前选中节点
+         * @param {Object} data - 提交的数据
+         * @param {function} callback - 界面刷新
+         */
+        postData (url, node, data, callback) {
+            const self = this;
+            if (node) {
+                data.id = node[self.setting.id];
+            }
+            postData(url, data, function (datas) {
+                const result = {};
+                console.log('childrenCount: ' + datas.expand.length);
+                let time = new Date();
+                if (datas.update) {
+                    result.update = self._updateData(datas.update);
+                }
+                if (datas.create) {
+                    result.create = self._loadData(datas.create);
+                }
+                if (datas.delete) {
+                    result.delete = self._freeData(datas.delete);
+                }
+                if (datas.expand) {
+                    const [create, update] = self._loadExpandData(datas.expand);
+                    result.create = result.create ? result.create.concat(create) : create;
+                    result.expand = update;
+                }
+                time = new Date() - time;
+                console.log('analysisData: ' + time);
+                callback(result);
+            });
+        };
+
+        batchInsert (url, node, data, callback) {
+            const self = this;
+            data.id = node[self.setting.id];
+            postData(url, data, function (datas) {
+                const result = {};
+                if (datas.update) {
+                    result.update = self._updateData(datas.update);
+                }
+                if (datas.create) {
+                    result.create = self._loadData(datas.create);
+                }
+                if (datas.delete) {
+                    result.delete = self._freeData(datas.delete);
+                }
+                callback(result);
+            });
+        };
+    }
+
+    if (type === 'base') {
+        return new BaseTree(setting);
+    } else if (type === 'active') {
+        return new ActiveTree(setting);
+    } else if (type === 'ledger') {
+        return new LedgerTree(setting);
+    } else if (type === 'measure') {
+        return new MeasureTree(setting);
+    }
+};
+
+const treeCalc = {
+    getMaxLevel: function (tree) {
+        return Math.max.apply(Math, tree.datas.map(function(o) {return o.level}));
+    },
+    calculateNode: function (tree, node, calcFields) {
+        const children = tree.getChildren(node);
+        if (children.length > 0) {
+            const gather = children.reduce(function (rst, x) {
+                const result = {};
+                const fieldCalc = function (field) {
+                    if (rst[field]) {
+                        result[field] = x[field] ? rst[field] + x[field] : rst[field];
+                    } else {
+                        result[field] = x[field] ? x[field] : undefined;
+                    }
+                }
+                for (const cf of calcFields) {
+                    fieldCalc(cf);
+                }
+                return result;
+            });
+            for (const cf of calcFields) {
+                if (gather[cf]) {
+                    node[cf] = gather[cf];
+                }
+            }
+        }
+    },
+    calculateLevelNode: function (tree, level, calcFields) {
+        const nodes = tree.datas.filter((n) => { return n.level === level });
+        for (const node of nodes) {
+            this.calculateNode(tree, node, calcFields);
+        }
+    },
+    calculateAll: function (tree, calcFields) {
+        for (let i = this.getMaxLevel(tree); i >= 0; i--) {
+            this.calculateLevelNode(tree, i, calcFields);
+        }
+    },
+    calculateParent: function (tree, node, calcFields) {
+        const nodes = tree.getFullPathNodes(node.full_path);
+        nodes.sort((a, b) => {
+            return b.level - a.level;
+        });
+        for (const n of nodes) {
+            this.calculateNode(tree, n, calcFields);
+        }
+        return nodes;
+    }
+}

+ 174 - 47
app/public/js/path_tree.js

@@ -7,6 +7,110 @@
 
 'use strict';
 
+class PosData {
+    /**
+     * 构造函数
+     * @param {id|Number, masterId|Number} setting
+     */
+    constructor (setting) {
+        // 无索引
+        this.datas = null;
+        // 以key为索引
+        this.items = {};
+        // 以分类id为索引的有序
+        this.masterRange = {};
+        // pos设置
+        this.setting = JSON.parse(JSON.stringify(setting));
+    }
+
+    /**
+     * 加载部位明细数据
+     * @param datas
+     */
+    loadDatas(datas) {
+        this.datas = datas;
+        for (const data of this.datas) {
+            const key = itemsPre + data[this.setting.id];
+            this.items[key] = data;
+
+            const masterKey = itemsPre + data[this.setting.masterId];
+            if (!this.masterRange[masterKey]) {
+                this.masterRange[masterKey] = [];
+            }
+            this.masterRange[masterKey].push(data);
+        }
+    }
+
+    /**
+     * 更新数据
+     * @param datas
+     */
+    updateDatas(data) {
+        const datas = data instanceof Array ? data : [data];
+        for (const d of datas) {
+            const key = itemsPre + d[this.setting.id];
+            if (!this.items[key]) {
+                this.datas.push(d);
+                this.items[key] = d;
+
+                const masterKey = itemsPre + d[this.setting.masterId];
+                if (!this.masterRange[masterKey]) {
+                    this.masterRange[masterKey] = [];
+                }
+                this.masterRange[masterKey].push(d);
+            } else {
+                const pos = this.items[key];
+                for (const prop in d) {
+                    pos[prop] = d[prop];
+                }
+            }
+        }
+    }
+
+    /**
+     * 移除数据
+     * @param datas
+     */
+    removeDatas(data) {
+        const datas = data instanceof Array ? data : [data];
+        for (let i = datas.length - 1; i >= 0; i--) {
+            const d = datas[i];
+            this.datas.splice(this.datas.indexOf(d), 1);
+            const key = itemsPre + d[this.setting.id];
+            delete this.items[key];
+            const masterKey = itemsPre + d[this.setting.masterId];
+            const range = this.masterRange[masterKey];
+            range.splice(range.indexOf(d), 1);
+            if (range.length === 0) {
+                delete this.masterRange[masterKey];
+            }
+        }
+    }
+
+    /**
+     * 移除数据 - 根据分类id
+     * @param mid
+     */
+    removeDatasByMasterId (mid) {
+        const masterKey = itemsPre + mid;
+        const range = this.masterRange(mid);
+        delete this.masterRange[masterKey];
+        for (const r of range) {
+            this.datas.splice(this.datas.indexOf(r), 1);
+            const key = itemsPre + r[this.setting.id];
+            delete this.items[key];
+        }
+    }
+
+    getPos(id) {
+        return this.items[itemsPre + id];
+    }
+
+    getMasterRange(mid) {
+        return this.masterRange[itemsPre + mid];
+    }
+}
+
 const itemsPre = 'id_';
 
 const createNewPathTree = function (type, setting) {
@@ -318,6 +422,7 @@ const createNewPathTree = function (type, setting) {
          * @privateA
          */
         _updateData (datas) {
+            datas = datas instanceof Array ? datas : [datas];
             const loadedData = [];
             for (const data of datas) {
                 let node = this.getItems(data[this.setting.id]);
@@ -344,6 +449,7 @@ const createNewPathTree = function (type, setting) {
          * @privateA
          */
         _loadData (datas) {
+            datas = datas instanceof Array ? datas : [datas];
             const loadedData = [], resortData = [];
             for (const data of datas) {
                 let node = this.getItems(data[this.setting.id]);
@@ -392,6 +498,8 @@ const createNewPathTree = function (type, setting) {
          * @private
          */
         _freeData (datas) {
+            datas = datas instanceof Array ? datas : [datas];
+            const freeDatas = [];
             const removeArrayData = function (array, data) {
                 const index = array.indexOf(data);
                 array.splice(index, 1);
@@ -399,6 +507,7 @@ const createNewPathTree = function (type, setting) {
             for (const data of datas) {
                 const node = this.getItems(data[this.setting.id]);
                 if (node) {
+                    freeDatas.push(node);
                     delete this.items[itemsPre + node[this.setting.id]];
                     if (node[this.setting.pid] !== this.setting.rootId) {
                         const parent = this.getItems(node[this.setting.pid]);
@@ -410,6 +519,7 @@ const createNewPathTree = function (type, setting) {
                     removeArrayData(this.nodes, node);
                 }
             }
+            return freeDatas;
         };
         /**
          * 加载需展开的数据
@@ -418,6 +528,7 @@ const createNewPathTree = function (type, setting) {
          * @private
          */
         _loadExpandData (datas) {
+            datas = datas instanceof Array ? datas : [datas];
             const loadedData = [], existData = [], expandData = [], resortData = [];
             for (const data of datas) {
                 let node = this.getItems(data[this.setting.id]);
@@ -466,6 +577,65 @@ const createNewPathTree = function (type, setting) {
         };
 
         /**
+         *
+         * @param parent
+         * @param node
+         * @private
+         */
+        _getNodesParents(parents, nodes) {
+            for (const node of nodes) {
+                const parent = this.getParent(node);
+                if (parent) {
+                    const paths = this.getFullPathNodes(parent.full_path);
+                    for (const p of paths) {
+                        if (parents.indexOf(p) === -1) {
+                            parents.push(p);
+                        }
+                    }
+                }
+            }
+        }
+
+        isLeafXmj(node) {
+            for (const child of node.children) {
+                if (child.code !== '') {
+                    return false;
+                }
+            }
+            return true;
+        }
+
+        /**
+         * 因为提交其他数据,引起的树结构数据更新,调用该方法
+         *
+         * @param data - 更新的数据 {update, create, delete}
+         * @param {function} callback - 界面刷新
+         */
+        loadPostData(data, callback) {
+            const result = {}, parents = [];
+            if (data.update) {
+                result.update = this._updateData(data.update);
+                this._getNodesParents(parents, result.update);
+            }
+            if (data.create) {
+                result.create = this._loadData(data.create);
+                this._getNodesParents(parents, result.create);
+            }
+            if (data.delete) {
+                result.delete = this._freeData(data.delete);
+                this._getNodesParents(parents, result.delete);
+            }
+            parents.sort((a, b) => {
+                return b.level - a.level;
+            });
+            for (const parent of parents) {
+                treeCalc.calculateNode(this, parent, ['total_price']);
+            }
+            result.update = result.update ? result.update.concat(parents) : parents;
+            callback(result);
+        }
+
+        /**
          * 以下方法需等待响应, 通过callback刷新界面
          */
         /**
@@ -496,17 +666,7 @@ const createNewPathTree = function (type, setting) {
                 postType: type
             };
             postData(url, data, function (datas) {
-                const result = {};
-                if (datas.update) {
-                    result.update = self._updateData(datas.update);
-                }
-                if (datas.create) {
-                    result.create = self._loadData(datas.create);
-                }
-                if (datas.delete) {
-                    result.delete = self._freeData(datas.delete);
-                }
-                callback(result);
+                self.loadPostData(datas, callback);
             });
         };
         /**
@@ -518,8 +678,7 @@ const createNewPathTree = function (type, setting) {
         update (url, updateData, callback) {
             const self = this;
             postData(url, updateData, function (datas) {
-                const result = self._updateData(datas);
-                callback(result);
+                self.loadPostData(datas, callback);
             }, function () {
                 if (updateData instanceof Array) {
                     const result = [];
@@ -546,17 +705,7 @@ const createNewPathTree = function (type, setting) {
                 block: block
             };
             postData(url, data, function (datas) {
-                const result = {};
-                if (datas.update) {
-                    result.update = self._updateData(datas.update);
-                }
-                if (datas.create) {
-                    result.create = self._loadData(datas.create);
-                }
-                if (datas.delete) {
-                    result.delete = self._freeData(datas.delete);
-                }
-                callback(result);
+                self.loadPostData(datas, callback);
             });
         };
         /**
@@ -573,8 +722,6 @@ const createNewPathTree = function (type, setting) {
             }
             postData(url, data, function (datas) {
                 const result = {};
-                console.log('childrenCount: ' + datas.expand.length);
-                let time = new Date();
                 if (datas.update) {
                     result.update = self._updateData(datas.update);
                 }
@@ -589,26 +736,6 @@ const createNewPathTree = function (type, setting) {
                     result.create = result.create ? result.create.concat(create) : create;
                     result.expand = update;
                 }
-                time = new Date() - time;
-                console.log('analysisData: ' + time);
-                callback(result);
-            });
-        };
-
-        batchInsert (url, node, data, callback) {
-            const self = this;
-            data.id = node[self.setting.id];
-            postData(url, data, function (datas) {
-                const result = {};
-                if (datas.update) {
-                    result.update = self._updateData(datas.update);
-                }
-                if (datas.create) {
-                    result.create = self._loadData(datas.create);
-                }
-                if (datas.delete) {
-                    result.delete = self._freeData(datas.delete);
-                }
                 callback(result);
             });
         };
@@ -674,4 +801,4 @@ const treeCalc = {
         }
         return nodes;
     }
-}
+};

+ 28 - 0
app/public/js/tender.js

@@ -75,6 +75,8 @@ function loadCalculateProperty () {
 
     // 合同参数
     $('#contract-price').val(property.deal_param.contractPrice);
+    $('#zan-lie-price').val(property.deal_param.zanLiePrice);
+    $('#c-zl').val(property.deal_param.contractPrice - property.deal_param.zanLiePrice);
     $('#start-advance').val(property.deal_param.startAdvance);
     $('#material-advance').val(property.deal_param.materialAdvance);
 }
@@ -103,6 +105,31 @@ function loadTenderProperty() {
     // 设置只读
     setReadOnly('#shuxing', true);
 }
+// 获取当前合同支付应该使用的小数位数
+function getDealTpDecimal() {
+    const spec = $('#decimal-pay')[0].checked;
+    return spec ? _.toNumber($('#decimal-pay-tp').val()) : _.toNumber($('#decimal-tp').val());
+}
+// 四舍五入
+function roundPrice(obj) {
+    const iDecimal = getDealTpDecimal();
+    obj.val(_.round(_.toNumber(obj.val()), iDecimal));
+}
+// 计算签约合同价(不含暂列金额)
+function calculateC2() {
+    const constract = _.toNumber($('#contract-price').val());
+    const zanLie = _.toNumber($('#zan-lie-price').val());
+    const iDecimal = getDealTpDecimal();
+    $('#c-zl').val(_.round(constract - zanLie, iDecimal));
+}
+// 根据小数位数,计算全部的合同参数
+function CalculateAllDealParam() {
+    roundPrice($('#contract-price'));
+    roundPrice($('#zan-lie-price'));
+    roundPrice($('#start-advance'));
+    roundPrice($('#material-advance'));
+    calculateC2();
+}
 
 $(document).ready(function() {
     loadTenderProperty();
@@ -212,6 +239,7 @@ $(document).ready(function() {
             },
             deal_param: {
                 contractPrice: _.toNumber($('#contract-price').val()),
+                zanLiePrice: _.toNumber($('#zan-lie-price').val()),
                 startAdvance: _.toNumber($('#start-advance').val()),
                 materialAdvance: _.toNumber($('#material-advance').val()),
             }

+ 23 - 4
app/public/js/tender_list.js

@@ -92,7 +92,7 @@ function onDropNode(event, treeId, treeNodes, targetNode, moveType) {
 // 查询方法
 function findNode (key, value, arr) {
     for (const a of arr) {
-        if (a[key] && a[key] == value) {
+        if (a[key] && a[key] === value) {
             return a;
         }
     }
@@ -181,7 +181,7 @@ function initTenderTree () {
     });
     function findCategoryNode(cid, value, array) {
         for (const a of array) {
-            if (a.cid === cid && a.vid == value) {
+            if (a.cid === cid && a.vid === value) {
                 return a;
             }
         }
@@ -242,7 +242,8 @@ function recursiveGetTenderNodeHtml (node, arr) {
                 html.push('<span class="text-muted mr-2">');
                 html.push(arr.indexOf(node) === arr.length - 1 ? '└' : '├');
                 html.push('</span>');
-                html.push('<a href="/tender/' + node.id + '">', node[c.field], '</a>');
+                //html.push('<a href="/tender/' + node.id + '">', node[c.field], '</a>');
+                html.push('<a href="javascript: void(0)" id="' + node.id + '">', node[c.field], '</a>');
             }
             html.push('</td>');
         } else {
@@ -278,6 +279,22 @@ function getTenderTreeHtml () {
         return EmptyTenderHtml.join('');
     }
 }
+function bindTenderUrl() {
+    $('a', '.c-body').bind('click', function () {
+        const tenderId = parseInt($(this).attr('id'));
+        const tender = _.find(tenders, function (t) {
+            return t.id === tenderId;
+        });
+        if (tender.measure_type) {
+            window.location.href = '/tender/' + tenderId;
+        } else {
+            for (const a of $('a', '#jlms')) {
+                a.href = '/tender/' + tenderId + '/' + $(a).attr('mst');
+            }
+            $('#jlms').modal('show');
+        }
+    });
+}
 
 $(document).ready(() => {
     sortCategory();
@@ -288,6 +305,7 @@ $(document).ready(() => {
     initTenderTree();
     console.log(tenderTree);
     $('.c-body').html(getTenderTreeHtml());
+    bindTenderUrl();
     // 分类
     $('#cate-set').on('show.bs.modal', function () {
         createTree();
@@ -335,10 +353,11 @@ $(document).ready(() => {
             }
             data.category.push(cate);
         }
-        postData('/list/add', data, function (result) {
+        postData('/api/list/add', data, function (result) {
             tenders.push(result);
             initTenderTree();
             $('.c-body').html(getTenderTreeHtml());
+            bindTenderUrl();
         });
     });
 });

+ 30 - 27
app/router.js

@@ -58,44 +58,47 @@ module.exports = app => {
     app.get('/list/progress', sessionAuth, 'tenderController.listProgress');
     // 管理标段
     app.get('/list/manage', sessionAuth, 'tenderController.listManage');
-    app.post('/list/add', sessionAuth, 'tenderController.addTender');
-    app.post('/list/update', sessionAuth, 'tenderController.updateTender');
-    app.post('/list/del', sessionAuth, 'tenderController.deleteTender');
+    app.post('/api/list/add', sessionAuth, 'tenderController.addTender');
+    app.post('/api/list/update', sessionAuth, 'tenderController.updateTender');
+    app.post('/api/list/del', sessionAuth, 'tenderController.deleteTender');
     // 标段概况
     app.get('/tender/:id', sessionAuth, 'tenderController.tenderInfo');
+    app.get('/tender/:id/:type', sessionAuth, 'tenderController.tenderType');
     app.post('/api/tender/:id/save', sessionAuth, 'tenderController.saveTenderInfo');
     app.post('/tender/rule', sessionAuth, 'tenderController.rule');
 
 
     // 台账管理相关
     app.get('/tender/:id/ledger/explode', sessionAuth, 'ledgerController.explode');
-    app.post('/tender/:id/ledger/get-children', sessionAuth, 'ledgerController.getChildren');
-    app.post('/tender/:id/ledger/base-operation', sessionAuth, 'ledgerController.baseOperation');
-    app.post('/tender/:id/ledger/update', sessionAuth, 'ledgerController.update');
-    app.post('/tender/:id/ledger/update-info', sessionAuth, 'ledgerController.updateInfo');
-    app.post('/tender/:id/ledger/paste-block', sessionAuth, 'ledgerController.pasteBlock');
-    app.post('/tender/:id/ledger/add-by-std', sessionAuth, 'ledgerController.addFromStandardLib');
-    app.post('/tender/:id/ledger/batch-insert', sessionAuth, 'ledgerController.batchInsert');
-    app.post('/tender/:id/ledger/search', sessionAuth, 'ledgerController.search');
-    app.post('/tender/:id/ledger/locate', sessionAuth, 'ledgerController.locate');
-    app.post('/tender/:id/ledger/posterity', sessionAuth, 'ledgerController.posterity');
+    app.post('/api/tender/:id/ledger/get-children', sessionAuth, 'ledgerController.getChildren');
+    app.post('/api/tender/:id/ledger/base-operation', sessionAuth, 'ledgerController.baseOperation');
+    app.post('/api/tender/:id/ledger/update', sessionAuth, 'ledgerController.update');
+    app.post('/api/tender/:id/ledger/update-info', sessionAuth, 'ledgerController.updateInfo');
+    app.post('/api/tender/:id/ledger/paste-block', sessionAuth, 'ledgerController.pasteBlock');
+    app.post('/api/tender/:id/ledger/add-by-std', sessionAuth, 'ledgerController.addFromStandardLib');
+    app.post('/api/tender/:id/ledger/batch-insert', sessionAuth, 'ledgerController.batchInsert');
+    //app.post('/api/tender/:id/ledger/search', sessionAuth, 'ledgerController.search');
+    //app.post('/api/tender/:id/ledger/locate', sessionAuth, 'ledgerController.locate');
+    //app.post('/api/tender/:id/ledger/posterity', sessionAuth, 'ledgerController.posterity');
+    app.post('/api/tender/:id/pos', sessionAuth, 'ledgerController.pos');
+    app.post('/api/tender/:id/pos/update', sessionAuth, 'ledgerController.posUpdate');
 
     app.get('/tender/:id/ledger/change', sessionAuth, 'ledgerController.change');
     app.get('/tender/:id/ledger/index', sessionAuth, 'ledgerController.index');
 
     // 台账审批相关
     app.get('/tender/:id/ledger/audit', sessionAuth, 'ledgerAuditController.index');
-    app.post('/tender/:id/ledger/audit/add', sessionAuth, 'ledgerAuditController.add');
-    app.post('/tender/:id/ledger/audit/delete', sessionAuth, 'ledgerAuditController.remove');
-    app.post('/tender/:id/ledger/audit/start', sessionAuth, 'ledgerAuditController.start');
-    app.post('/tender/:id/ledger/audit/check', sessionAuth, 'ledgerAuditController.check');
-    app.post('/tender/:id/ledger/audit/addContent', sessionAuth, 'ledgerAuditController.addContent');
-    app.post('/tender/:id/ledger/audit/getContent', sessionAuth, 'ledgerAuditController.getContent');
+    app.post('/api/tender/:id/ledger/audit/add', sessionAuth, 'ledgerAuditController.add');
+    app.post('/api/tender/:id/ledger/audit/delete', sessionAuth, 'ledgerAuditController.remove');
+    app.post('/api/tender/:id/ledger/audit/start', sessionAuth, 'ledgerAuditController.start');
+    app.post('/api/tender/:id/ledger/audit/check', sessionAuth, 'ledgerAuditController.check');
+    app.post('/api/tender/:id/ledger/audit/addContent', sessionAuth, 'ledgerAuditController.addContent');
+    app.post('/api/tender/:id/ledger/audit/getContent', sessionAuth, 'ledgerAuditController.getContent');
 
     // 签约清单
-    app.post('/tender/:id/deal/get-data', sessionAuth, 'dealBillsController.getData');
-    app.post('/tender/:id/deal/upload-excel', sessionAuth, 'dealBillsController.loadExcel');
-    app.get('/tender/:id/deal/download/:file', sessionAuth, 'dealBillsController.download');
+    app.post('/api/tender/:id/deal/get-data', sessionAuth, 'dealBillsController.getData');
+    app.post('/api/tender/:id/deal/upload-excel', sessionAuth, 'dealBillsController.loadExcel');
+    app.get('/api/tender/:id/deal/download/:file', sessionAuth, 'dealBillsController.download');
 
     // 变更管理
     app.get('/tender/:id/change', sessionAuth, 'changeController.index');
@@ -143,11 +146,11 @@ module.exports = app => {
 
 
     //标准库相关
-    app.post('/std/bills/get-data', sessionAuth, 'stdBillsController.getData');
-    app.post('/std/bills/get-children', sessionAuth, 'stdBillsController.getChildren');
-    app.post('/std/chapter/get-data', sessionAuth, 'stdChapterController.getData');
-    app.post('/std/chapter/get-children', sessionAuth, 'stdChapterController.getChildren');
+    app.post('/api/std/bills/get-data', sessionAuth, 'stdBillsController.getData');
+    app.post('/api/std/bills/get-children', sessionAuth, 'stdBillsController.getChildren');
+    app.post('/api/std/chapter/get-data', sessionAuth, 'stdChapterController.getData');
+    app.post('/api/std/chapter/get-children', sessionAuth, 'stdChapterController.getChildren');
 
     // 查询
-    app.post('/search/user', sessionAuth, 'projectController.searchAccount');
+    app.post('/api/search/user', sessionAuth, 'projectController.searchAccount');
 };

+ 116 - 113
app/service/ledger.js

@@ -877,13 +877,8 @@ module.exports = app => {
                 }
                 // 选中节点--全部后节点 order--
                 await this._updateSelectNextsOrder(selectData, -1);
-                // 更新父项金额
-                if (this.ctx.helper.checkZero(selectData.total_price)) {
-                    const parentFullPath = selectData.full_path.replace('.' + selectData.ledger_id, '');
-                    const updateMap = {};
-                    updateMap[parentFullPath] = -selectData.total_price;
-                    await this._increCalcParent(tenderId, updateMap);
-                }
+                // 删除部位明细
+                await this.transaction.delete(this.ctx.service.pos.tableName, {tid: tenderId, lid: this._.map(deleteData, 'id')});
                 await this.transaction.commit();
             } catch (err) {
                 deleteData = [];
@@ -897,10 +892,7 @@ module.exports = app => {
                 updateData = updateData ? updateData : [];
                 if (selectData.ledger_pid !== rootId) {
                     const updateData1 = await this.getDataByNodeId(tenderId, selectData.ledger_pid);
-                    if (this.ctx.helper.checkZero(selectData.total_price)) {
-                        const updateData2 = await this.getFullLevelDataByFullPath(tenderId, updateData1.full_path);
-                        updateData = updateData.concat(updateData2);
-                    } else if (updateData1.is_leaf !== parentData.is_leaf) {
+                    if (updateData1.is_leaf !== parentData.is_leaf) {
                         updateData.push(updateData1);
                     }
                 }
@@ -1093,27 +1085,17 @@ module.exports = app => {
                 if (selectData.order === 1) {
                     await this.transaction.update(this.tableName, {
                         id: parentData.id,
-                        is_leaf: true,
-                        total_price: 0
-                    });
-                } else {
-                    await this.transaction.update(this.tableName, {
-                        id: parentData.id,
-                        total_price: await this.addUpChildren(tenderId, selectData.ledger_pid, selectData.order, '<')
+                        is_leaf: true
                     });
                 }
                 // 选中节点--父节点--全部后兄弟节点 order+1
                 await this._updateSelectNextsOrder(parentData);
                 // 选中节点 修改pid, order, full_path
-                let totalPrice = selectData.total_price ? selectData.total_price : 0;
-                const plus = await this.addUpChildren(tenderId, selectData.ledger_pid, selectData.order, '>');
-                totalPrice = plus ? totalPrice + plus : totalPrice;
                 const updateData = { id: selectData.id,
                     ledger_pid: parentData.ledger_pid,
                     order: parentData.order + 1,
                     level: selectData.level - 1,
-                    full_path: newFullPath,
-                    total_price: totalPrice
+                    full_path: newFullPath
                 };
                 await this.transaction.update(this.tableName, updateData);
                 // 选中节点--全部子节点(含孙) level-1, full_path变更
@@ -1206,14 +1188,11 @@ module.exports = app => {
                 // 选中节点--全部子节点(含孙) level++, full_path
                 await this._syncDownlevelChildren(selectData, preData);
                 // 选中节点--前兄弟节点 is_leaf应为false
-                if (preData.is_leaf || this.ctx.helper.checkZero(selectData.total_price)) {
+                if (preData.is_leaf) {
                     const updateData2 = {
                         id: preData.id,
                         is_leaf: false
                     };
-                    if (this.ctx.helper.checkZero(selectData.total_price)) {
-                        updateData2['total_price'] = preData.total_price ? preData.total_price + selectData.total_price : selectData.total_price;
-                    }
                     await this.transaction.update(this.tableName, updateData2);
                 }
                 await this.transaction.commit();
@@ -1393,14 +1372,13 @@ module.exports = app => {
             }
             const orgParentPath = copyNodes[0].full_path.replace(copyNodes[0].ledger_id, '');
 
-            let incre = 0;
+            const newIds = [];
             this.transaction = await this.db.beginTransaction();
             try {
                 // 选中节点的所有后兄弟节点,order+粘贴节点个数
                 await this._updateSelectNextsOrder(selectData, copyNodes.length);
                 // 数据库创建新增节点数据
                 for (const node of copyNodes) {
-                    incre += node.total_price ? node.total_price : 0;
                     const datas = await this.getDataByFullPath(tenderId, node.full_path + '%');
 
                     const cacheKey = keyPre + tenderId;
@@ -1410,10 +1388,14 @@ module.exports = app => {
                     }
                     this.cache.set(cacheKey, maxId + datas.length, 'EX', this.ctx.app.config.cacheTime);
 
+                    const leafBillsId = [];
                     // 计算粘贴数据中需更新部分
                     for (let index = 0; index < datas.length; index++) {
                         const data = datas[index];
                         const newId = maxId + index + 1;
+                        const idChange = {
+                            org: data.id,
+                        }
                         delete data.id;
                         if (!data.is_leaf) {
                             for (const children of datas) {
@@ -1432,19 +1414,20 @@ module.exports = app => {
                             data.order = selectData.order + index + 1;
                         }
                         data.level = data.level + selectData.level - copyNodes[0].level;
+                        const newData = await this.transaction.insert(this.tableName, data);
+                        if (data.is_leaf) {
+                            idChange.new = newData.insertId;
+                            leafBillsId.push(idChange);
+                        }
+                        newIds.push(newData.insertId);
+                    }
+                    for (const id of leafBillsId) {
+                        await this.ctx.service.pos.copyBillsPosData(id.org, id.new, this.transaction);
                     }
-
-                    // 插入粘贴数据
-                    await this.transaction.insert(this.tableName, datas);
-                }
-                // 更新父节点金额
-                if (this.ctx.helper.checkZero(incre)) {
-                    const updateMap = {};
-                    updateMap[newParentPath] = incre;
-                    await this._increCalcParent(tenderId, updateMap);
                 }
                 await this.transaction.commit();
             } catch (err) {
+                console.log(err);
                 await this.transaction.rollback();
                 throw err;
             }
@@ -1454,14 +1437,13 @@ module.exports = app => {
             for (let i = 1; i <= copyNodes.length; i++) {
                 order.push(selectData.order + i);
             }
-            const createData = await this.getDataByParentAndOrder(selectData.tender_id, selectData.ledger_pid, order);
+            const createData = await this.getDataByIds(newIds);
             const updateData = await this.getNextsData(selectData.tender_id, selectData.ledger_pid, selectData.order + copyNodes.length);
-            if (this.ctx.helper.checkZero(incre)) {
-                const updateData1 = await this.getFullLevelDataByFullPath(selectData.tender_id, newParentPath);
-                return { create: createData, update: updateData.concat(updateData1) };
-            } else {
-                return { create: createData, update: updateData };
-            }
+            const posData = await this.ctx.service.pos.getPosData({lid: newIds});
+            return {
+                ledger: { create: createData, update: updateData },
+                pos: posData,
+            };
         }
 
         /**
@@ -1502,14 +1484,6 @@ module.exports = app => {
          * @returns {Promise<*>}
          */
         async updateCalc(tenderId, data) {
-            const findData = function (id, datas) {
-                for (const d of datas) {
-                    if (d.id === id) {
-                        return d;
-                    }
-                }
-                return undefined;
-            }
             // 简单验证数据
             if (tenderId <= 0) {
                 throw '标段不存在';
@@ -1526,7 +1500,6 @@ module.exports = app => {
                 ids.push(row.id);
             }
 
-            const updateMap = {}, updateFullPath = [];
             this.transaction = await this.db.beginTransaction();
             try {
                 for (const row of datas) {
@@ -1540,15 +1513,6 @@ module.exports = app => {
                         if (updateNode.is_leaf) {
                             calcData.total_price = calcData.quantity * calcData.unit_price;
                         }
-                        if (updateNode.total_price === undefined || this.ctx.helper.checkZero(calcData.total_price - updateNode.total_price)) {
-                            const pfp = updateNode.full_path.replace('.' + updateNode.ledger_id, '');
-                            if (updateMap[pfp]) {
-                                updateMap[pfp] = updateMap[pfp] + calcData.total_price - updateNode.total_price;
-                            } else {
-                                updateMap[pfp] = calcData.total_price - updateNode.total_price;
-                                updateFullPath.push(pfp);
-                            }
-                        }
                         const data1 = this._filterChangedField(updateNode, calcData);
                         updateData = this._filterUpdateInvalidField(updateNode.id, data1);
                     } else {
@@ -1556,20 +1520,13 @@ module.exports = app => {
                     }
                     await this.transaction.update(this.tableName, updateData);
                 }
-                await this._increCalcParent(tenderId, updateMap);
                 await this.transaction.commit();
             } catch (err) {
                 await this.transaction.rollback();
                 throw err;
             }
 
-            const result1 = await this.getDataByIds(ids);
-            if (updateFullPath.length > 0) {
-                const result2 = await this.getFullLevelDataByFullPath(tenderId, updateFullPath);
-                return result1.concat(result2);
-            } else {
-                return result1;
-            }
+            return {update: await this.getDataByIds(ids)};
         }
 
         /**
@@ -1637,8 +1594,9 @@ module.exports = app => {
          * @returns {Promise<void>}
          */
         async batchInsertChild(tenderId, selectId, data) {
+            const result = { ledger: {}, pos: null };
             if ((tenderId <= 0) || (selectId <= 0)) {
-                return [];
+                return result;
             }
             const selectData = await this.getDataByNodeId(tenderId, selectId);
             if (!selectData) {
@@ -1646,9 +1604,10 @@ module.exports = app => {
             }
 
             this.transaction = await this.db.beginTransaction();
-            let incre = 0, newIds = [];
+            let newIds = [];
             try {
                 const lastChild = await this.getLastChildData(tenderId, selectId);
+                // 更新父项isLeaf
                 if (!lastChild) {
                     await this.transaction.update(this.tableName, {
                         id: selectData.id,
@@ -1656,21 +1615,34 @@ module.exports = app => {
                     });
                 }
                 const order = lastChild ? lastChild.order : 0;
+                // 计算id
+                const cacheKey = keyPre + tenderId;
+                let maxId = parseInt(await this.cache.get(cacheKey));
+                if (!maxId) {
+                    maxId = await this._getMaxNodeId(tenderId);
+                }
                 // 数据库创建新增节点数据
                 for (let i = 0, iLen = data.length; i < iLen; i++) {
-                    const xmj = data[i];
-                    const [insertData, tp, ids] = await this._sortBatchInsertData(tenderId, xmj, order + i + 1, selectData);
-                    incre = incre + tp;
-                    newIds = newIds.concat(ids);
-                    await this.transaction.insert(this.tableName, insertData);
-                }
-
-                // 更新父节点金额
-                if (this.ctx.helper.checkZero(incre)) {
-                    const updateMap = {};
-                    updateMap[selectData.full_path] = incre;
-                    await this._increCalcParent(tenderId, updateMap);
+                    // 合并新增数据
+                    const qd = {
+                        tender_id: tenderId,
+                        ledger_id: maxId + i + 1,
+                        ledger_pid: selectData.ledger_id,
+                        is_leaf: true,
+                        order: order + i + 1,
+                        level: selectData.level + 1,
+                        b_code: data[i].b_code,
+                        name: data[i].name,
+                        unit: data[i].unit,
+                        unit_price: data[i].price,
+                    };
+                    qd.full_path = selectData.full_path + '.' + qd.ledger_id;
+                    const insertResult = await this.transaction.insert(this.tableName, qd);
+                    newIds.push(insertResult.insertId);
+                    await this.ctx.service.pos.insertLedgerPosData(this.transaction, tenderId, insertResult.insertId, data[i].pos);
+                    await this.calc(tenderId, insertResult.insertId, this.transaction);
                 }
+                this.cache.set(cacheKey, maxId + data.length + 1, 'EX', this.ctx.app.config.cacheTime);
                 await this.transaction.commit();
             } catch(err) {
                 await this.transaction.rollback();
@@ -1678,13 +1650,9 @@ module.exports = app => {
             }
 
             // 查询应返回的结果
-            const createData = await this.getDataByNodeIds(selectData.tender_id, newIds);
-            if (this.ctx.helper.checkZero(incre) || selectData.is_leaf) {
-                const updateData = await this.getFullLevelDataByFullPath(selectData.tender_id, selectData.full_path);
-                return { create: createData, update: updateData };
-            } else {
-                return { create: createData };
-            }
+            result.ledger.create = await this.getDataByIds(newIds);
+            result.pos = await this.ctx.service.pos.getPosData({lid: newIds});
+            return result;
         }
 
         /**
@@ -1695,8 +1663,9 @@ module.exports = app => {
          * @returns {Promise<void>}
          */
         async batchInsertNext(tenderId, selectId, data) {
+            const result = { ledger: {}, pos: null };
             if ((tenderId <= 0) || (selectId <= 0)) {
-                return [];
+                return result;
             }
             const selectData = await this.getDataByNodeId(tenderId, selectId);
             if (!selectData) {
@@ -1708,26 +1677,39 @@ module.exports = app => {
             }
 
             this.transaction = await this.db.beginTransaction();
-            let incre = 0, newIds = [];
+            const newIds = [];
             try {
                 // 选中节点的所有后兄弟节点,order+粘贴节点个数
                 await this._updateSelectNextsOrder(selectData, data.length);
+                // 计算id和order
+                const cacheKey = keyPre + tenderId;
+                let maxId = parseInt(await this.cache.get(cacheKey));
+                if (!maxId) {
+                    maxId = await this._getMaxNodeId(tenderId);
+                }
                 const order = selectData.order;
                 // 数据库创建新增节点数据
                 for (let i = 0, iLen = data.length; i < iLen; i++) {
-                    const xmj = data[i];
-                    const [insertData, tp, ids] = await this._sortBatchInsertData(tenderId, xmj, order + i + 1, parentData);
-                    incre = incre + tp;
-                    newIds = newIds.concat(ids);
-                    await this.transaction.insert(this.tableName, insertData);
-                }
-
-                // 更新父节点金额
-                if (this.ctx.helper.checkZero(incre)) {
-                    const updateMap = {};
-                    updateMap[parentData.full_path] = incre;
-                    await this._increCalcParent(tenderId, updateMap);
+                    // 合并新增数据
+                    const qd = {
+                        tender_id: tenderId,
+                        ledger_id: maxId + i + 1,
+                        ledger_pid: parentData.ledger_id,
+                        is_leaf: true,
+                        order: order + i + 1,
+                        level: parentData.level + 1,
+                        b_code: data[i].b_code,
+                        name: data[i].name,
+                        unit: data[i].unit,
+                        unit_price: data[i].price,
+                    };
+                    qd.full_path = parentData.full_path + '.' + qd.ledger_id;
+                    const insertResult = await this.transaction.insert(this.tableName, qd);
+                    newIds.push(insertResult.insertId);
+                    await this.ctx.service.pos.insertLedgerPosData(this.transaction, tenderId, insertResult.insertId, data[i].pos);
+                    await this.calc(tenderId, insertResult.insertId, this.transaction);
                 }
+                this.cache.set(cacheKey, maxId + data.length + 1, 'EX', this.ctx.app.config.cacheTime);
                 await this.transaction.commit();
             } catch(err) {
                 await this.transaction.rollback();
@@ -1735,16 +1717,37 @@ module.exports = app => {
             }
 
             // 查询应返回的结果
-            const createData = await this.getDataByNodeIds(parentData.tender_id, newIds);
-            const updateData = await this.getNextsData(selectData.tender_id, selectData.ledger_pid, selectData.order + data.length);
-            if (this.ctx.helper.checkZero(incre)) {
-                const updateData1 = await this.getFullLevelDataByFullPath(tenderId, parentData.full_path);
-                return { create: createData, update: updateData.concat(updateData1) };
-            } else {
-                return { create: createData, update: updateData };
+            result.ledger.create = await this.getDataByIds(newIds);
+            result.ledger.update = await this.getNextsData(selectData.tender_id, selectData.ledger_pid, selectData.order + data.length);
+            result.pos = await this.ctx.service.pos.getPosData({lid: newIds});
+            return result;
+        }
+
+        /**
+         *
+         * @param {Number} tid - 标段id
+         * @param {Number} id - 需要计算的节点的id
+         * @param {Object} transaction - 操作所属事务,没有则创建
+         * @returns {Promise<void>}
+         */
+        async calc(tid, id, transaction) {
+            const node = await this.getAllDataByCondition({id: id});
+            if (!node) {
+                throw '数据错误';
             }
+
+            const calcQtySql = 'UPDATE ??' +
+                ' SET `quantity` = (SELECT SUM(`quantity`) FROM ?? WHERE `lid` = ?), `total_price` = `quantity` * `unit_price`' +
+                ' WHERE `id` = ?';
+            await transaction.query(this.db.format(calcQtySql, [this.tableName, this.ctx.service.pos.tableName, id, id]));
         }
 
+        /**
+         * 查找定位 --> 废弃
+         * @param tenderId
+         * @param nodeId
+         * @returns {Promise<{expand: *}>}
+         */
         async locateNode(tenderId, nodeId) {
             const node = await this.getDataByNodeId(tenderId, nodeId);
             if (!node) {

+ 109 - 0
app/service/pos.js

@@ -0,0 +1,109 @@
+'use strict';
+
+/**
+ * 部位明细
+ *
+ * @author Mai
+ * @date
+ * @version
+ */
+
+module.exports = app => {
+
+    class Pos extends app.BaseService {
+        /**
+         * 构造函数
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        constructor(ctx) {
+            super(ctx);
+            this.tableName = 'pos';
+        }
+
+        async getPosData(condition) {
+            return await this.db.select(this.tableName, {
+                where: condition,
+                columns: ['id', 'tid', 'lid', 'name', 'quantity', 'drawing_code'],
+            });
+        }
+
+        async savePosData(data, tid) {
+            const transaction = await this.db.beginTransaction();
+            try {
+                const result = { ledger: {}, pos: null };
+                if (data.updateType === 'add') {
+                    const tender = await this.ctx.service.tender.getTender(tid);
+                    data.updateData.tid = tid;
+                    // todo 新增期
+                    data.updateData.add_stage = 0;
+                    data.updateData.add_times = 0;
+                    data.updateData.add_user = this.ctx.session.sessionUser.accountId;
+                    const addRst = await transaction.insert(this.tableName, data.updateData);
+                    data.updateData.id = addRst.insertId;
+                } else if (data.updateType === 'update') {
+                    await transaction.update(this.tableName, data.updateData, {id: data.updateData.id});
+                    // 计算台账最底层
+                    if (data.updateData.quantity) {
+                        const pos = await this.getDataByCondition({id: data.updateData.id});
+                        await this.ctx.service.ledger.calc(tid, pos.lid, transaction);
+                        result.ledger.update = [pos.lid];
+                    }
+                } else if (data.updateType === 'delete') {
+                    const pos = await this.getPosData({id: data.updateData.id});
+                    await transaction.delete(this.tableName, {id: data.updateData.id});
+                    await this.ctx.service.ledger.calc(tid, pos.lid, transaction);
+                    result.ledger.update = [pos.lid];
+                } else {
+                    throw '数据错误';
+                }
+                await transaction.commit();
+                result.pos = data.updateData;
+                result.ledger.update = await this.ctx.service.ledger.getDataByIds(result.ledger.update);
+                return result;
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
+        }
+
+        async deletePosData(transaction, tid, lid) {
+            await transaction.delete(this.tableName, {tid: tid, lid: lid});
+        }
+
+        async copyBillsPosData(orgLid, newLid, transaction) {
+            const posData = await this.getAllDataByCondition({ where: { lid: orgLid } });
+            if (posData.length > 0) {
+                for (const pd of posData) {
+                    delete pd.id;
+                    pd.lid = newLid;
+                }
+                await transaction.insert(this.tableName, posData);
+            }
+        }
+
+        /**
+         * 批量插入部位数据 - 仅供批量插入清单部位调用
+         * @param transaction - 所属事务
+         * @param {Number} tid - 标段id
+         * @param {Number} lid - 台账id
+         * @param {Array} data - 新增数据
+         * @returns {Promise<void>}
+         */
+        async insertLedgerPosData(transaction, tid, lid, data) {
+            for (const d of data) {
+                d.tid = tid;
+                d.lid = lid;
+                // todo 新增期
+                d.add_stage = 0;
+                d.add_times = 0;
+                d.add_user = this.ctx.session.sessionUser.accountId;
+            }
+            await transaction.insert(this.tableName, data);
+        }
+
+    }
+
+    return Pos;
+};

+ 4 - 2
app/service/tender.js

@@ -9,6 +9,8 @@
  */
 
 const tenderConst = require('../const/tender');
+const commonQueryColumns = ['id', 'project_id', 'name', 'status', 'category', 'ledger_times', 'ledger_status', 'measure_type', 'user_id'];
+
 module.exports = app => {
 
     class Tender extends app.BaseService {
@@ -80,7 +82,7 @@ module.exports = app => {
                 value: sessionProject.id,
                 operate: '=',
             });
-            this.sqlBuilder.columns = ['id', 'project_id', 'name', 'status', 'category', 'ledger_times', 'ledger_status'];
+            this.sqlBuilder.columns = commonQueryColumns;
             const [sql, sqlParam] = this.sqlBuilder.build(this.tableName);
 
             const list = await this.db.query(sql, sqlParam);
@@ -97,7 +99,7 @@ module.exports = app => {
                 value: id,
                 operate: '=',
             });
-            this.sqlBuilder.columns = ['id', 'project_id', 'name', 'status', 'category', 'ledger_times', 'ledger_status'];
+            this.sqlBuilder.columns = commonQueryColumns;
             const [sql, sqlParam] = this.sqlBuilder.build(this.tableName);
 
             const tender = await this.db.queryOne(sql, sqlParam);

+ 26 - 0
app/view/layout/sub_menu.ejs

@@ -0,0 +1,26 @@
+<div class="panel-sidebar">
+    <div class="panel-title">
+        <div class="title-bar">
+            <h2><%- tender.name %></h2>
+        </div>
+    </div>
+    <div class="scrollbar-auto">
+        <% for (const m in tenderMenu) { %>
+        <div class="nav-box">
+            <% const menu = tenderMenu[m]; %>
+            <% if (menu.display) { %>
+            <h3><%- menu.icon %><%- menu.name %></h3>
+            <% } %>
+            <% if (menu.children && menu.children.length > 0) { %>
+            <ul class="nav-list list-unstyled <% if (menu.display) { %>sub-list<% } %>">
+                <% for (const mc of menu.children) { %>
+                <li <% if (ctx.url === preUrl + mc.url) { %>class="active"<% } %>>
+                    <a href="<%- preUrl %><%- mc.url %>"><%- mc.icon %><span><%- mc.name %></span></a>
+                </li>
+                <% } %>
+            </ul>
+            <% } %>
+        </div>
+        <% } %>
+    </div>
+</div>

+ 22 - 9
app/view/ledger/explode.ejs

@@ -55,7 +55,8 @@
         <div class="row w-100 sub-content">
             <!--左栏-->
             <div class="c-body col-12">
-                <div id="ledger-spread" class="sjs-height-1"></div>
+                <div id="ledger-spread" class="<% if (tender.measure_type === measureType.tz.value) { %>sjs-height-1<% } else { %>sjs-height-0<% } %>"></div>
+                <% if (tender.measure_type === measureType.tz.value) { %>
                 <div class="bcontent-wrap">
                     <div class="bc-bar mb-1">
                         <ul class="nav nav-tabs">
@@ -67,11 +68,12 @@
                     <div class="sp-wrap" id="pos-spread">
                     </div>
                 </div>
+                <% } %>
             </div>
             <div class="c-body col-0" style="display: none;">
                 <div class="tab-content">
                     <div id="search" class="tab-pane active">
-                        <div class="side-bar">
+                        <div class="side-bar-1">
                             <div class="input-group input-group-sm">
                                 <input id="searchKeyword" type="text" class="form-control" placeholder="项目节编号/清单编号/名称" aria-label="Recipient's username" aria-describedby="button-addon2">
                                 <div class="input-group-append">
@@ -79,22 +81,28 @@
                                 </div>
                             </div>
                         </div>
-                        <div id="search-result" class="sjs-height-2">
+                        <div id="search-result" class="sjs-sh-1">
                         </div>
                     </div>
                     <div id="std-chapter" class="tab-pane active">
-                        <select class="form-control form-control-sm"><option>0号计量台帐部位参考(项目节)</option></select>
-                        <div id="std-chapter-spread" class="sjs-height-2">
+                        <div class="side-bar-2">
+                            <select class="form-control form-control-sm"><option>0号计量台帐部位参考(项目节)</option></select>
+                        </div>
+                        <div id="std-chapter-spread" class="sjs-sh-2">
                         </div>
                     </div>
                     <div id="std-bills" class="tab-pane">
-                        <select class="form-control form-control-sm"><option>0号计量台帐部位参考(项目节)</option></select>
-                        <div id="std-bills-spread" class="sjs-height-2">
+                        <div class="side-bar-3">
+                            <select class="form-control form-control-sm"><option>0号计量台帐部位参考(项目节)</option></select>
+                        </div>
+                        <div id="std-bills-spread" class="sjs-sh-3">
                         </div>
                     </div>
                     <div id="deal-bills" class="tab-pane">
-                        <a href="#upload-deal" data-toggle="modal" data-target="#upload-deal" class="btn btn-sm btn-primary">上传签约清单</a>
-                        <div id="deal-bills-spread" class="sjs-height-2">
+                        <div class="side-bar-4">
+                            <a href="#upload-deal" data-toggle="modal" data-target="#upload-deal" class="btn btn-sm btn-primary">上传签约清单</a>
+                        </div>
+                        <div id="deal-bills-spread" class="sjs-sh-4">
                         </div>
                     </div>
                     <% if (tender.ledger_status !== auditConst.status.uncheck) { %>
@@ -153,12 +161,17 @@
     </div>
 </div>
 <script type="text/javascript">
+    const tender = JSON.parse('<%- JSON.stringify(tender) %>');
+    const tenderInfo = JSON.parse('<%- JSON.stringify(tenderInfo) %>');
+    const preUrl = '/api<%- preUrl %>';
+    const measureType = JSON.parse('<%- JSON.stringify(measureType) %>');
     let ledger = '<%- ledger %>';
     ledger = JSON.parse(ledger);
     let ledgerSpreadSetting = '<%- ledgerSpreadSetting %>';
     ledgerSpreadSetting = JSON.parse(ledgerSpreadSetting);
     ledgerSpreadSetting.readOnly = <%- tender.user_id !== ctx.session.sessionUser.accountId || tender.ledger_status === auditConst.status.checking || tender.ledger_status === auditConst.status.checked %>;
     let posSpreadSetting = JSON.parse('<%- posSpreadSetting %>');
+    posSpreadSetting.readOnly = ledgerSpreadSetting.readOnly;
 </script>
 <script src="/public/js/spreadjs/sheets/gc.spread.sheets.all.10.0.1.min.js"></script>
 <script>

+ 5 - 11
app/view/ledger/explode_modal.ejs

@@ -22,32 +22,26 @@
         </div>
     </div>
 </div>
-<!--批量添加子项弹窗-->
+<!--批量添加清单部位-->
 <div class="modal fade" id="batch" data-backdrop="static">
     <div class="modal-dialog modal-lg" role="document">
         <div class="modal-content">
             <div class="modal-header">
-                <h5 class="modal-title">批量插入子项</h5>
+                <h5 class="modal-title">批量插入清单部位</h5>
             </div>
             <div class="modal-body">
                 <div class="row">
                     <div class="col-6">
-                        <h6 class="row"><span class="col">部位数量复合表</span>
-                            <div class="input-group input-group-sm col-5 align-self-end">
-                                <div class="input-group-prepend">
-                                    <span class="input-group-text" id="basic-addon1">起始编号</span>
-                                </div>
-                                <input type="num" class="form-control form-control-sm"  step="1" value="1">
-                            </div></h6>
+                        <h6>清单编号</h6>
                         <div class="batch-l-t">
                         </div>
-                        <h6>清单编号</h6>
+                        <h6>部位数量复核表</h6>
                         <div class="batch-l-b">
                         </div>
                     </div>
                     <div class="col-6">
                         <h6>签约清单</h6>
-                        <div id="batch-deal-spread" class="batch-r">
+                        <div class="batch-r">
                         </div>
                     </div>
                 </div>

+ 17 - 0
app/view/sum/index.ejs

@@ -0,0 +1,17 @@
+<div class="panel-content">
+    <div class="panel-title">
+        <div class="title-main d-flex justify-content-between">
+            <div>
+                计量台帐
+            </div>
+            <div>
+                <a href="#add-qi" data-toggle="modal" data-target="#add-qi" class="btn btn-primary btn-sm pull-right">开始新一期</a>
+            </div>
+        </div>
+    </div>
+    <div class="content-wrap">
+        <div class="c-body">
+
+        </div>
+    </div>
+</div>

+ 28 - 0
app/view/sum/modal.ejs

@@ -0,0 +1,28 @@
+<!--弹出添加标段-->
+<div class="modal fade" id="add-qi" data-backdrop="static">
+    <div class="modal-dialog" role="document">
+        <div class="modal-content">
+            <div class="modal-header">
+                <h5 class="modal-title">添加新一期</h5>
+            </div>
+            <div class="modal-body">
+                <div class="form-group">
+                    <label>期</label>
+                    <input class="form-control" value="第 4 期" type="text" readonly="">
+                </div>
+                <div class="form-group">
+                    <label>计量年月</label>
+                    <input class="datepicker-here form-control" placeholder="点击选择年月" data-view="months" data-min-view="months" data-date-format="yyyy-MM" data-language="zh" type="text">
+                </div>
+                <div class="form-group">
+                    <label>开始-截止日期</label>
+                    <input class="datepicker-here form-control" placeholder="点击选择时间" data-range="true" data-multiple-dates-separator=" ~ " data-language="zh" type="text">
+                </div>
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-secondary" data-dismiss="modal">关闭</button>
+                <button type="button" class="btn btn-primary">确定添加</button>
+            </div>
+        </div>
+    </div>
+</div>

+ 25 - 17
app/view/tender/detail.ejs

@@ -391,7 +391,7 @@
                                                     <div class="input-group-prepend">
                                                         <span class="input-group-text">数量</span>
                                                     </div>
-                                                    <input type="number" class="form-control" value="3" id="decimal-qty">
+                                                    <input type="number" class="form-control" value="3" id="decimal-qty" min="0" max="6">
                                                 </div>
                                             </div>
                                             <div class="col-2">
@@ -399,7 +399,7 @@
                                                     <div class="input-group-prepend">
                                                         <span class="input-group-text">金额</span>
                                                     </div>
-                                                    <input type="number" class="form-control" value="2" id="decimal-tp">
+                                                    <input type="number" class="form-control" value="2" id="decimal-tp" onchange="CalculateAllDealParam()"  min="0" max="4">
                                                 </div>
                                             </div>
                                         </div>
@@ -418,7 +418,7 @@
                                                     <div class="input-group-prepend">
                                                         <span class="input-group-text">数量</span>
                                                     </div>
-                                                    <input type="number" class="form-control" value="3" id="decimal-deal-qty">
+                                                    <input type="number" class="form-control" value="3" id="decimal-deal-qty" min="0" max="6">
                                                 </div>
                                             </div>
                                             <div class="col-2">
@@ -426,31 +426,23 @@
                                                     <div class="input-group-prepend">
                                                         <span class="input-group-text">金额</span>
                                                     </div>
-                                                    <input type="number" class="form-control" value="2" id="decimal-deal-tp">
+                                                    <input type="number" class="form-control" value="2" id="decimal-deal-tp" min="0" max="4">
                                                 </div>
                                             </div>
                                         </div>
                                         <div class="row">
                                             <div class="col-auto">
                                                 <div class="form-group form-check mt-1">
-                                                    <input type="checkbox" class="form-check-input" id="decimal-pay">
+                                                    <input type="checkbox" class="form-check-input" id="decimal-pay" onchange="CalculateAllDealParam()">
                                                     <label class="form-check-label" for="exampleCheck2">合同支付</label>
                                                 </div>
                                             </div>
                                             <div class="col-2">
                                                 <div class="input-group input-group-sm">
                                                     <div class="input-group-prepend">
-                                                        <span class="input-group-text">数量</span>
-                                                    </div>
-                                                    <input type="number" class="form-control" value="3" readonly="" id="decimal-pay-qty">
-                                                </div>
-                                            </div>
-                                            <div class="col-2">
-                                                <div class="input-group input-group-sm">
-                                                    <div class="input-group-prepend">
                                                         <span class="input-group-text">金额</span>
                                                     </div>
-                                                    <input type="number" class="form-control" value="2" readonly="" id="decimal-pay-tp">
+                                                    <input type="number" class="form-control" value="2" readonly="" id="decimal-pay-tp" onchange="CalculateAllDealParam()" min="0" max="4">
                                                 </div>
                                             </div>
                                         </div>
@@ -464,7 +456,23 @@
                                                 <div class="input-group-prepend">
                                                     <span class="input-group-text">签约合同价</span>
                                                 </div>
-                                                <input type="number" class="form-control" value="" id="contract-price">
+                                                <input type="number" class="form-control" value="" id="contract-price" onchange="roundPrice($('#contract-price'));calculateC2()">
+                                            </div>
+                                        </div>
+                                        <div class="col-3 mt-3">
+                                            <div class="input-group input-group-sm">
+                                                <div class="input-group-prepend">
+                                                    <span class="input-group-text">暂列金额</span>
+                                                </div>
+                                                <input type="number" class="form-control" value="" id="zan-lie-price" onchange="roundPrice($('#zan-lie-price'));calculateC2()">
+                                            </div>
+                                        </div>
+                                        <div class="col-5 mt-3">
+                                            <div class="input-group input-group-sm">
+                                                <div class="input-group-prepend">
+                                                    <span class="input-group-text">签约合同价(不含暂列金)</span>
+                                                </div>
+                                                <input type="number" class="form-control" value="" id="c-zl" disabled="">
                                             </div>
                                         </div>
                                         <div class="col-3 my-3">
@@ -472,7 +480,7 @@
                                                 <div class="input-group-prepend">
                                                     <span class="input-group-text">签约开工预付款</span>
                                                 </div>
-                                                <input type="number" class="form-control" value="" id="start-advance">
+                                                <input type="number" class="form-control" value="" id="start-advance" onchange="roundPrice($('#start-advance'));">
                                             </div>
                                         </div>
                                         <div class="col-3">
@@ -480,7 +488,7 @@
                                                 <div class="input-group-prepend">
                                                     <span class="input-group-text">签约材料预付款</span>
                                                 </div>
-                                                <input type="number" class="form-control" value="" id="material-advance">
+                                                <input type="number" class="form-control" value="" id="material-advance" onchange="roundPrice($('#material-advance'));">
                                             </div>
                                         </div>
                                     </div>

+ 25 - 0
app/view/tender/modal.ejs

@@ -37,6 +37,31 @@
         </div>
     </div>
 </div>
+<!--弹出计量模式选择-->
+<div class="modal fade" id="jlms" data-backdrop="static">
+    <div class="modal-dialog modal-lg" role="document">
+        <div class="modal-content">
+            <div class="modal-header">
+                <h5 class="modal-title">选择计量模式</h5>
+            </div>
+            <div class="modal-body">
+                <div class="row">
+                    <% for (const mt in measureType) { %>
+                    <div class="col-6">
+                        <div class="card">
+                            <div class="card-body">
+                                <h5 class="card-title"><i class="fa fa-bookmark"></i><%- measureType[mt].title %></h5>
+                                <p class="card-text"><b><%- measureType[mt].name %></b><%- measureType[mt].hint%></p>
+                                <a href="#" mst="<%- measureType[mt].value %>" class="btn btn-primary">选择他</a>
+                            </div>
+                        </div>
+                    </div>
+                    <% } %>
+                </div>
+            </div>
+        </div>
+    </div>
+</div>
 
 <script type="text/javascript" src="/public/js/ztree/jquery.ztree.core.js"></script>
 <script type="text/javascript" src="/public/js/ztree/jquery.ztree.exedit.js"></script>

+ 28 - 0
config/menu.js

@@ -114,6 +114,33 @@ const tenderMenu = {
     },
 };
 
+const sumMenu = {
+    info: {
+        name: '总包概况',
+        display: false,
+        children: [
+            {
+                name: '总包概况',
+                icon: '<i class="fa fa-pie-chart"></i> ',
+                display: true,
+                url: '/sum',
+            }
+        ]
+    },
+    stage: {
+        name: '上报期',
+        display: false,
+        children: [
+            {
+                name: '上报期',
+                icon: '<i class="fa fa-calendar-check-o"></i> ',
+                display: true,
+                url: '/sum/stage',
+            }
+        ]
+    },
+}
+
 const settingMenu = {
     info: {
         name: '项目信息',
@@ -138,5 +165,6 @@ const settingMenu = {
 module.exports = {
     menu,
     tenderMenu,
+    sumMenu,
     settingMenu,
 };

+ 1 - 0
package.json

@@ -16,6 +16,7 @@
     "gt3-sdk": "^2.0.0",
     "koa-is-json": "^1.0.0",
     "lodash": "^4.17.11",
+    "lz-string": "^1.4.4",
     "moment": "^2.20.1",
     "node-uuid": "^1.4.8",
     "node-xlsx": "^0.12.0",

+ 167 - 211
test/app/service/ledger.test.js

@@ -46,6 +46,7 @@ const testNodeData = [
     { id: 5, pid: 1, order: 4, level: 2, full_path: '1.5', code: '1-4', is_leaf: true },
 ];
 const testTenderId = 3;
+const testUserId = 2;
 
 const { app, assert } = require('egg-mock/bootstrap');
 const findById = function(nodes, Id) {
@@ -415,17 +416,20 @@ describe('test/app/service/ledger.test.js', () => {
         // 选中1-2-1, 粘贴1-1-1和new
         const resultData = yield ctx.service.ledger.pasteBlock(testTenderId, 15, [6, 17]);
 
-        assert(resultData.create.length === 2);
-        assert(resultData.create[0].order = 2);
-        assert(resultData.create[0].level = 3);
-        assert(resultData.create[0].full_path = '1.3.18');
+        const ledger = resultData.ledger;
+        assert(ledger.create.length === 5);
+        assert(ledger.create[0].order = 2);
+        assert(ledger.create[0].level = 3);
+        assert(ledger.create[0].full_path = '1.3.18');
 
-        assert(resultData.create[1].order = 3);
-        assert(resultData.create[1].level = 3);
-        assert(resultData.create[1].full_path = '1.3.22');
+        assert(ledger.create[1].order = 3);
+        assert(ledger.create[1].level = 3);
+        assert(ledger.create[1].full_path = '1.3.22');
 
-        assert(resultData.update.length === 1);
-        assert(resultData.update[0].order = 3);
+        assert(ledger.update.length === 1);
+        assert(ledger.update[0].order = 3);
+
+        assert(resultData.pos.length === 0);
     });
     /* 期望运行结果:
         1
@@ -545,23 +549,15 @@ describe('test/app/service/ledger.test.js', () => {
             quantity: qty,
             unit_price: up
         });
-        assert(resultData.length === 5);
-        let node = findById(resultData, 12);
-        assert(node.total_price.toFixed(8) == tp);
-        node = findById(resultData, 8);
-        assert(node.total_price.toFixed(8) == tp);
-        node = findById(resultData, 6);
-        assert(node.total_price.toFixed(8) == tp);
-        node = findById(resultData, 2);
-        assert(node.total_price.toFixed(8) == tp);
-        node = findById(resultData, 1);
+        assert(resultData.update.length === 1);
+        let node = findById(resultData.update, 12);
         assert(node.total_price.toFixed(8) == tp);
     });
     /* 期望运行结果:
-        1                                                   6.00000103
-        ├── 1-1                                             6.00000103
-        │   ├── 1-1-1                                       6.00000103
-        │   │   └── 202-2                                   6.00000103
+        1
+        ├── 1-1
+        │   ├── 1-1-1
+        │   │   └── 202-2
         │   │       ├── 202-2-c
         │   │       └── 202-2-e     2.00000001  3.0000005   6.00000103
         │   └── 1-1-4
@@ -614,47 +610,29 @@ describe('test/app/service/ledger.test.js', () => {
             unit_price: up[2]
         }]);
 
-        assert(resultData.length === 10);
+        assert(resultData.update.length === 3);
 
-        let node = findById(resultData, 11);
+        let node = findById(resultData.update, 11);
         assert(node.total_price.toFixed(8) == tp[0]);
-
-        node = findById(resultData, 8);
-        assert(node.total_price.toFixed(8) == sum[0]);
-        node = findById(resultData, 6);
-        assert(node.total_price.toFixed(8) == sum[0]);
-        node = findById(resultData, 2);
-        assert(node.total_price.toFixed(8) == sum[0]);
-
-        node = findById(resultData, 20);
+        node = findById(resultData.update, 20);
         assert(node.total_price.toFixed(8) == tp[1]);
-        node = findById(resultData, 21);
+        node = findById(resultData.update, 21);
         assert(node.total_price.toFixed(8) == tp[2]);
-
-        node = findById(resultData, 19);
-        assert(node.total_price.toFixed(8) == sum[1]);
-        node = findById(resultData, 18);
-        assert(node.total_price.toFixed(8) == sum[1]);
-        node = findById(resultData, 3);
-        assert(node.total_price.toFixed(8) == sum[1]);
-
-        node = findById(resultData, 1);
-        assert(node.total_price.toFixed(8) == sum[2]);
     });
     /* 期望运行结果:
-        1                                                   72.00046663
-        ├── 1-1                                             30.00003573
-        │   ├── 1-1-1                                       30.00003573
-        │   │   └── 202-2                                   30.00003573
+        1
+        ├── 1-1
+        │   ├── 1-1-1
+        │   │   └── 202-2
         │   │       ├── 202-2-c     4.00000025  6.0000083   24.0000347
         │   │       └── 202-2-e     2.00000001  3.0000005   6.00000103
         │   └── 1-1-4
         ├── 1-1-2
         │   └── 1-1-3
-        ├── 1-2                                             42.0004309
+        ├── 1-2
         │   ├── 1-2-1
-        │   ├── 1-2-2                                       42.0004309
-        │   │   └── 202-2                                   42.0004309
+        │   ├── 1-2-2
+        │   │   └── 202-2
         │   │       ├── 202-2-c     2.0000001   5.000065    10.0001305
         │   │       └── 202-2-e     8.0000579   4.0000086   32.0003004
         │   ├── 1-2-3
@@ -668,31 +646,26 @@ describe('test/app/service/ledger.test.js', () => {
         // 选中1-1-4, 粘贴202-2(1-1-1下)
         const resultData = yield ctx.service.ledger.pasteBlock(testTenderId, 17, [8]);
 
-        assert(resultData.update.length === 2);
-
-        let node = findById(resultData.update, 2);
-        assert(node.total_price.toFixed(8) == 60.00007146);
-
-        node = findById(resultData.update, 1);
-        assert(node.total_price.toFixed(8) == 102.00050236);
+        assert(resultData.ledger.create.length === 3);
+        assert(resultData.ledger.update.length === 0);
     });
     /* 期望运行结果:
-        1                                                   102.00050236
-        ├── 1-1                                             60.00007146
-        │   ├── 1-1-1                                       30.00003573
-        │   │   └── 202-2                                   30.00003573
+        1
+        ├── 1-1
+        │   ├── 1-1-1
+        │   │   └── 202-2
         │   │       ├── 202-2-c     4.00000025  6.0000083   24.0000347
         │   │       └── 202-2-e     2.00000001  3.0000005   6.00000103
         │   ├── 1-1-4
-        │   └── 202-2                                       30.00003573
+        │   └── 202-2
         │       ├── 202-2-c         4.00000025  6.0000083   24.0000347
         │       └── 202-2-e         2.00000001  3.0000005   6.00000103
         ├── 1-1-2
         │   └── 1-1-3
-        ├── 1-2                                             42.0004309
+        ├── 1-2
         │   ├── 1-2-1
-        │   ├── 1-2-2                                       42.0004309
-        │   │   └── 202-2                                   42.0004309
+        │   ├── 1-2-2
+        │   │   └── 202-2
         │   │       ├── 202-2-c     2.0000001   5.000065    10.0001305
         │   │       └── 202-2-e     8.0000579   4.0000086   32.0003004
         │   ├── 1-2-3
@@ -706,26 +679,24 @@ describe('test/app/service/ledger.test.js', () => {
         // 选中202-2(1-1-4后兄弟节点) 降级
         const resultData = yield ctx.service.ledger.downLevelNode(testTenderId, 23);
         assert(resultData.update.length === 4);
-        const node = findById(resultData.update, 17);
-        assert(node.total_price.toFixed(8) == 30.00003573);
     });
     /* 期望运行结果:
-        1                                                   102.00050236
-        ├── 1-1                                             60.00007146
-        │   ├── 1-1-1                                       30.00003573
-        │   │   └── 202-2                                   30.00003573
+        1
+        ├── 1-1
+        │   ├── 1-1-1
+        │   │   └── 202-2
         │   │       ├── 202-2-c     4.00000025  6.0000083   24.0000347
         │   │       └── 202-2-e     2.00000001  3.0000005   6.00000103
-        │   └── 1-1-4                                       30.00003573
-        │       └── 202-2                                   30.00003573
+        │   └── 1-1-4
+        │       └── 202-2
         │           ├── 202-2-c     4.00000025  6.0000083   24.0000347
         │           └── 202-2-e     2.00000001  3.0000005   6.00000103
         ├── 1-1-2
         │   └── 1-1-3
-        ├── 1-2                                             42.0004309
+        ├── 1-2
         │   ├── 1-2-1
-        │   ├── 1-2-2                                       42.0004309
-        │   │   └── 202-2                                   42.0004309
+        │   ├── 1-2-2
+        │   │   └── 202-2
         │   │       ├── 202-2-c     2.0000001   5.000065    10.0001305
         │   │       └── 202-2-e     8.0000579   4.0000086   32.0003004
         │   ├── 1-2-3
@@ -737,25 +708,25 @@ describe('test/app/service/ledger.test.js', () => {
         const ctx = app.mockContext();
         yield ctx.service.ledger.pasteBlock(testTenderId, 23, [23]);
         /* 期望运行结果:
-            1                                                   132.00053809
-            ├── 1-1                                             90.00009719
-            │   ├── 1-1-1                                       30.00003573
-            │   │   └── 202-2                                   30.00003573
+            1
+            ├── 1-1
+            │   ├── 1-1-1
+            │   │   └── 202-2
             │   │       ├── 202-2-c     4.00000025  6.0000083   24.0000347
             │   │       └── 202-2-e     2.00000001  3.0000005   6.00000103
-            │   └── 1-1-4                                       60.00007146
-            │       ├── 202-2                                   30.00003573
+            │   └── 1-1-4
+            │       ├── 202-2
             │       │   ├── 202-2-c     4.00000025  6.0000083   24.0000347
             │       │   └── 202-2-e     2.00000001  3.0000005   6.00000103
-            │       └── 202-2                                   30.00003573
+            │       └── 202-2
             │           ├── 202-2-c     4.00000025  6.0000083   24.0000347
             │           └── 202-2-e     2.00000001  3.0000005   6.00000103
             ├── 1-1-2
             │   └── 1-1-3
-            ├── 1-2                                             42.0004309
+            ├── 1-2
             │   ├── 1-2-1
-            │   ├── 1-2-2                                       42.0004309
-            │   │   └── 202-2                                   42.0004309
+            │   ├── 1-2-2
+            │   │   └── 202-2
             │   │       ├── 202-2-c     2.0000001   5.000065    10.0001305
             │   │       └── 202-2-e     8.0000579   4.0000086   32.0003004
             │   ├── 1-2-3
@@ -766,32 +737,27 @@ describe('test/app/service/ledger.test.js', () => {
         // 选中202-2-c(1-1-4下)(id=23)升级
         const resultData = yield ctx.service.ledger.upLevelNode(testTenderId, 23);
         assert(resultData.update.length === 7);
-
-        let node = findById(resultData.update, 23);
-        assert(node.total_price.toFixed(8) == 60.00007146);
-        node = findById(resultData.update, 17);
-        assert(node.total_price.toFixed(8) == 0);
     });
     /* 期望运行结果:
-        1                                                   132.00053809
-        ├── 1-1                                             90.00009719
-        │   ├── 1-1-1                                       30.00003573
-        │   │   └── 202-2                                   30.00003573
+        1
+        ├── 1-1
+        │   ├── 1-1-1
+        │   │   └── 202-2
         │   │       ├── 202-2-c     4.00000025  6.0000083   24.0000347
         │   │       └── 202-2-e     2.00000001  3.0000005   6.00000103
         │   ├── 1-1-4
-        │   └── 202-2                                       60.00007146
+        │   └── 202-2
         │       ├── 202-2-c         4.00000025  6.0000083   24.0000347
         │       ├── 202-2-e         2.00000001  3.0000005   6.00000103
-        │       └── 202-2                                   30.00003573
+        │       └── 202-2
         │           ├── 202-2-c     4.00000025  6.0000083   24.0000347
         │           └── 202-2-e     2.00000001  3.0000005   6.00000103
         ├── 1-1-2
         │   └── 1-1-3
-        ├── 1-2                                             42.0004309
+        ├── 1-2
         │   ├── 1-2-1
-        │   ├── 1-2-2                                       42.0004309
-        │   │   └── 202-2                                   42.0004309
+        │   ├── 1-2-2
+        │   │   └── 202-2
         │   │       ├── 202-2-c     2.0000001   5.000065    10.0001305
         │   │       └── 202-2-e     8.0000579   4.0000086   32.0003004
         │   ├── 1-2-3
@@ -805,43 +771,28 @@ describe('test/app/service/ledger.test.js', () => {
         // 选中202-2-c(1-2-2下)(id=20),删除节点
         const resultData = yield ctx.service.ledger.deleteNode(testTenderId, 20);
         assert(resultData.delete.length === 1);
-        assert(resultData.update.length === 5);
-
-        let node = findById(resultData.update, 21);
-        assert(node.order === 1);
-
-        node = findById(resultData.update, 19);
-        assert(node.total_price.toFixed(8) == 32.0003004);
-
-        node = findById(resultData.update, 18);
-        assert(node.total_price.toFixed(8) == 32.0003004);
-
-        node = findById(resultData.update, 3);
-        assert(node.total_price.toFixed(8) == 32.0003004);
-
-        node = findById(resultData.update, 1);
-        assert(node.total_price.toFixed(8) == 122.00040759);
+        assert(resultData.update.length === 1);
     });
     /* 期望运行结果:
-        1                                                   122.00040759
-        ├── 1-1                                             90.00009719
-        │   ├── 1-1-1                                       30.00003573
-        │   │   └── 202-2                                   30.00003573
+        1
+        ├── 1-1
+        │   ├── 1-1-1
+        │   │   └── 202-2
         │   │       ├── 202-2-c     4.00000025  6.0000083   24.0000347
         │   │       └── 202-2-e     2.00000001  3.0000005   6.00000103
         │   ├── 1-1-4
-        │   └── 202-2                                       60.00007146
+        │   └── 202-2
         │       ├── 202-2-c         4.00000025  6.0000083   24.0000347
         │       ├── 202-2-e         2.00000001  3.0000005   6.00000103
-        │       └── 202-2                                   30.00003573
+        │       └── 202-2
         │           ├── 202-2-c     4.00000025  6.0000083   24.0000347
         │           └── 202-2-e     2.00000001  3.0000005   6.00000103
         ├── 1-1-2
         │   └── 1-1-3
-        ├── 1-2                                             32.0003004
+        ├── 1-2
         │   ├── 1-2-1
-        │   ├── 1-2-2                                       32.0003004
-        │   │   └── 202-2                                   32.0003004
+        │   ├── 1-2-2
+        │   │   └── 202-2
         │   │       └── 202-2-e     8.0000579   4.0000086   32.0003004
         │   ├── 1-2-3
         │   └── 1-3
@@ -975,61 +926,62 @@ describe('test/app/service/ledger.test.js', () => {
     // 批量插入
     it('test batchInsertChild', function* () {
         const ctx = app.mockContext();
+        // pos需要记录createUserId
+        ctx.session = {
+            sessionUser: {
+                accountId: testUserId,
+            },
+        };
 
         const batchData = [
             {
-                name: 'X1',
-                children: [
-                    {b_code: 401-1, name: 'A1', unit: 'B1', unit_price: 2, quantity: 3},
-                    {b_code: 402-1, name: 'A2', unit: 'B2', unit_price: 3, quantity: 4},
-                ]
-            }, {
-                name: 'X2',
-                children: [
-                    {b_code: 401-1, name: 'A1', unit: 'B1', unit_price: 2, quantity: 3},
-                    {b_code: 402-1, name: 'A2', unit: 'B2', unit_price: 3, quantity: 4},
-                ]
-            }
-        ]
+                b_code: 401-1, name: 'A1', unit: 'B1', price: 2,
+                pos: [{name: 'X1', quantity: 1}, {name: 'X2', quantity: 2}],
+            },
+            {
+                b_code: 402-1, name: 'A2', unit: 'B2', price: 3,
+                pos: [{name: 'X1', quantity: 3}, {name: 'X2', quantity: 4}],
+            },
+        ];
         // 选中1-1-3(id=14)
         const result = yield ctx.service.ledger.batchInsertChild(testTenderId, 14, batchData);
-        assert(result.create.length === 6);
-
-        assert(result.update.length === 3);
-        let node = findById(result.update, 1);
-        assert(node.total_price.toFixed(8) == 158.00040759);
-        node = findById(result.update, 13);
-        assert(node.total_price.toFixed(8) == 36);
-        node = findById(result.update, 14);
-        assert(node.total_price.toFixed(8) == 36);
+        assert(result.ledger.create.length === 2);
+        let node = findById(result.ledger.create, 36);
+        assert(node.quantity.toFixed(8) == 3);
+        assert(node.total_price.toFixed(8) == 6);
+        node = findById(result.ledger.create, 37);
+        assert(node.quantity.toFixed(8) == 7);
+        assert(node.total_price.toFixed(8) == 21);
+
+        assert(result.pos.length === 4);
     });
     /* 期望运行结果:
-        1                                                   158.00040759
-        ├── 1-1                                             90.00009719
-        │   ├── 1-1-1                                       30.00003573
-        │   │   └── 202-2                                   30.00003573
+        1
+        ├── 1-1
+        │   ├── 1-1-1
+        │   │   └── 202-2
         │   │       ├── 202-2-c     4.00000025  6.0000083   24.0000347
         │   │       └── 202-2-e     2.00000001  3.0000005   6.00000103
         │   ├── 1-1-4
         │   ├── 1-1-5
-        │   └── 202-2                                       60.00007146
+        │   └── 202-2
         │       ├── 202-2-c         4.00000025  6.0000083   24.0000347
         │       ├── 202-2-e         2.00000001  3.0000005   6.00000103
-        │       └── 202-2                                   30.00003573
+        │       └── 202-2
         │           ├── 202-2-c     4.00000025  6.0000083   24.0000347
         │           └── 202-2-e     2.00000001  3.0000005   6.00000103
-        ├── 1-1-2                                           36
-        │   └── 1-1-3                                       36
-        │       ├──     X1                                  18
-        │       │   ├── 401-1       2           3           6
-        │       │   └── 402-1       3           4           12
-        │       └──     X2                                  18
-        │           ├── 401-1       2           3           6
-        │           └── 402-1       3           4           12
-        ├── 1-2                                             32.0003004
+        ├── 1-1-2
+        │   └── 1-1-3
+        │       ├── 401-1           2           3           6
+        │       │   ├── (pos)X1                 1
+        │       │   └── (pos)X1                 2
+        │       └── 402-1           3           7           21
+        │           ├── (pos)X1                 3
+        │           └── (pos)X2                 4
+        ├── 1-2
         │   ├── 1-2-1
-        │   ├── 1-2-2                                       32.0003004
-        │   │   └── 202-2                                   32.0003004
+        │   ├── 1-2-2
+        │   │   └── 202-2
         │   │       └── 202-2-e     8.0000579   4.0000086   32.0003004
         │   ├── 1-2-3
         │   └── 1-3
@@ -1043,66 +995,69 @@ describe('test/app/service/ledger.test.js', () => {
     // 批量插入
     it('test batchInsertNext', function* () {
         const ctx = app.mockContext();
+        // pos需要记录createUserId
+        ctx.session = {
+            sessionUser: {
+                accountId: testUserId,
+            },
+        };
 
         const batchData = [
             {
-                name: 'Y1',
-                children: [
-                    {b_code: 401-1, name: 'A1', unit: 'B1', unit_price: 2, quantity: 3},
-                    {b_code: 402-1, name: 'A2', unit: 'B2', unit_price: 3, quantity: 4},
-                ]
-            }, {
-                name: 'Y2',
-                children: [
-                    {b_code: 401-1, name: 'A1', unit: 'B1', unit_price: 2, quantity: 3},
-                    {b_code: 402-1, name: 'A2', unit: 'B2', unit_price: 3, quantity: 4},
-                ]
-            }
-        ]
+                b_code: 403-1, name: 'A1', unit: 'B1', price: 2,
+                pos: [{name: 'Y1', quantity: 5}, {name: 'Y2', quantity: 6}],
+            },
+            {
+                b_code: 404-1, name: 'A2', unit: 'B2', price: 3,
+                pos: [{name: 'Y1', quantity: 7}, {name: 'Y2', quantity: 8}],
+            },
+        ];
         // 选中1-1-3(id=14)
         const result = yield ctx.service.ledger.batchInsertNext(testTenderId, 14, batchData);
 
-        assert(result.create.length === 6);
+        assert(result.ledger.create.length === 2);
+        let node = findById(result.ledger.create, 39);
+        assert(node.quantity.toFixed(8) == 11);
+        assert(node.total_price.toFixed(8) == 22);
+        node = findById(result.ledger.create, 40);
+        assert(node.quantity.toFixed(8) == 15);
+        assert(node.total_price.toFixed(8) == 45);
 
-        assert(result.update.length === 2);
-        let node = findById(result.update, 1);
-        assert(node.total_price.toFixed(8) == 194.00040759);
-        node = findById(result.update, 13);
-        assert(node.total_price.toFixed(8) == 72);
+        assert(result.pos.length === 4);
     });
     /* 期望运行结果:
-        1                                                   194.00040759
-        ├── 1-1                                             90.00009719
-        │   ├── 1-1-1                                       30.00003573
-        │   │   └── 202-2                                   30.00003573
+        1
+        ├── 1-1
+        │   ├── 1-1-1
+        │   │   └── 202-2
         │   │       ├── 202-2-c     4.00000025  6.0000083   24.0000347
         │   │       └── 202-2-e     2.00000001  3.0000005   6.00000103
         │   ├── 1-1-4
         │   ├── 1-1-5
-        │   └── 202-2                                       60.00007146
+        │   └── 202-2
         │       ├── 202-2-c         4.00000025  6.0000083   24.0000347
         │       ├── 202-2-e         2.00000001  3.0000005   6.00000103
-        │       └── 202-2                                   30.00003573
+        │       └── 202-2
         │           ├── 202-2-c     4.00000025  6.0000083   24.0000347
         │           └── 202-2-e     2.00000001  3.0000005   6.00000103
-        ├── 1-1-2                                           72
-        │   ├── 1-1-3                                       36
-        │   │   ├──     X1                                  18
-        │   │   │   ├── 401-1       2           3           6
-        │   │   │   └── 402-1       3           4           12
-        │   │   └──     X2                                  18
-        │   │       ├── 401-1       2           3           6
-        │   │       └── 402-1       3           4           12
-        │   ├──     X1                                      18
+        ├── 1-1-2
+        │   ├── 1-1-3
         │   │   ├── 401-1           2           3           6
-        │   │   └── 402-1           3           4           12
-        │   └──     X2                                      18
-        │       ├── 401-1           2           3           6
-        │       └── 402-1           3           4           12
-        ├── 1-2                                             32.0003004
+        │   │   │   ├── (pos)X1                 1
+        │   │   │   └── (pos)X1                 2
+        │   │   └── 402-1           3           7           21
+        │   │       ├── (pos)X1                 3
+        │   │       └── (pos)X2                 4
+        │   ├── 403-1               2           11          22
+        │   │   ├── (pos)Y1                     5
+        │   │   └── (pos)Y2                     6
+        │   └── 404-1               3           15          45
+        │       ├── (pos)Y1                     7
+        │       └── (pos)Y2                     8
+        ├── 1-2
         │   ├── 1-2-1
-        │   ├── 1-2-2                                       32.0003004
-        │   │   └── 202-2                                   32.0003004
+        │   ├── 1-2-2
+        │   │   └── 202-2
         │   │       └── 202-2-e     8.0000579   4.0000086   32.0003004
         │   ├── 1-2-3
         │   └── 1-3
@@ -1121,12 +1076,13 @@ describe('test/app/service/ledger.test.js', () => {
         // 统计202-2(id=23)前两个子节点的金额
         const result1 = yield ctx.service.ledger.addUpChildren(testTenderId, 23, 2, '<=');
         assert(result1 && result1.toFixed(8) == 30.00003573);
+        // 数据库不再存储父项金额,以下两个查询不能获得实际金额
         // 统计202-2(id=23)后两个子节点的金额
-        const result2 = yield ctx.service.ledger.addUpChildren(testTenderId, 23, 2, '>=');
-        assert(result2 && result2.toFixed(8) == 36.00003676);
+        //const result2 = yield ctx.service.ledger.addUpChildren(testTenderId, 23, 2, '>=');
+        //assert(result2 && result2.toFixed(8) == 36.00003676);
         // 统计202-2(id=23)全部子节点的金额
-        const result3 = yield ctx.service.ledger.addUpChildren(testTenderId, 23, 0, '>');
-        assert(result3 && result3.toFixed(8) == 60.00007146);
+        //const result3 = yield ctx.service.ledger.addUpChildren(testTenderId, 23, 0, '>');
+        //assert(result3 && result3.toFixed(8) == 60.00007146);
     });
 
     // 测试搜索类方法