浏览代码

Merge branch 'dev' of http://192.168.1.41:3000/maixinrong/Calculation into dev

TonyKang 3 年之前
父节点
当前提交
100af8add8
共有 100 个文件被更改,包括 4666 次插入960 次删除
  1. 2 0
      app.js
  2. 10 4
      app/base/base_bills_service.js
  3. 465 0
      app/base/base_budget_service.js
  4. 1 0
      app/base/base_controller.js
  5. 1 100
      app/base/base_tree_service.js
  6. 15 1
      app/const/material.js
  7. 1 0
      app/const/page_show.js
  8. 442 0
      app/controller/budget_controller.js
  9. 56 12
      app/controller/change_controller.js
  10. 14 5
      app/controller/material_controller.js
  11. 37 5
      app/controller/revise_controller.js
  12. 1 0
      app/controller/stage_rela_controller.js
  13. 1 1
      app/extend/helper.js
  14. 101 57
      app/lib/analysis_excel.js
  15. 2 2
      app/lib/bills_pos_convert.js
  16. 3 3
      app/lib/stage_im.js
  17. 45 0
      app/middleware/budget_check.js
  18. 2 0
      app/middleware/material_check.js
  19. 5 0
      app/middleware/session_auth.js
  20. 二进制
      app/public/css/bg/bg_01.png
  21. 二进制
      app/public/css/bg/bg_02.png
  22. 二进制
      app/public/css/bg/bg_03.png
  23. 二进制
      app/public/css/bg/bg_04.png
  24. 二进制
      app/public/css/bg/bg_05.png
  25. 二进制
      app/public/css/bg/bg_06.png
  26. 二进制
      app/public/css/image_nav_logo.png
  27. 94 0
      app/public/css/main.css
  28. 二进制
      app/public/images/icon_login_folder.png
  29. 二进制
      app/public/images/icon_login_lock.png
  30. 二进制
      app/public/images/icon_login_pc.png
  31. 二进制
      app/public/images/icon_login_qr.png
  32. 二进制
      app/public/images/icon_login_user.png
  33. 二进制
      app/public/images/image_nav_logo.png
  34. 16 1
      app/public/js/advance.js
  35. 18 1
      app/public/js/advance_audit.js
  36. 241 0
      app/public/js/budget_compare.js
  37. 832 0
      app/public/js/budget_detail.js
  38. 217 0
      app/public/js/budget_list.js
  39. 3 0
      app/public/js/change_information_approval.js
  40. 103 30
      app/public/js/change_information_set.js
  41. 52 25
      app/public/js/change_revise.js
  42. 4 1
      app/public/js/ledger.js
  43. 21 0
      app/public/js/material.js
  44. 49 1
      app/public/js/material_exponent.js
  45. 26 0
      app/public/js/material_list.js
  46. 100 1
      app/public/js/path_tree.js
  47. 5 3
      app/public/js/revise.js
  48. 10 4
      app/public/js/shares/cs_tools.js
  49. 14 1
      app/public/js/shares/sjs_setting.js
  50. 10 5
      app/public/js/spreadjs_rela/spreadjs_zh.js
  51. 11 23
      app/public/js/sr_detail.js
  52. 31 44
      app/public/js/stage.js
  53. 2 1
      app/public/js/std_lib.js
  54. 18 0
      app/router.js
  55. 12 18
      app/service/advance_audit.js
  56. 250 0
      app/service/budget.js
  57. 30 0
      app/service/budget_gai.js
  58. 30 0
      app/service/budget_gu.js
  59. 100 0
      app/service/budget_permission.js
  60. 62 0
      app/service/budget_std.js
  61. 28 0
      app/service/budget_yu.js
  62. 43 16
      app/service/change_audit_list.js
  63. 33 20
      app/service/change_ledger.js
  64. 16 2
      app/service/change_pos.js
  65. 17 20
      app/service/deal_bills.js
  66. 108 132
      app/service/ledger.js
  67. 19 42
      app/service/ledger_audit.js
  68. 4 4
      app/service/ledger_revise.js
  69. 14 14
      app/service/material_audit.js
  70. 2 6
      app/service/material_exponent.js
  71. 1 0
      app/service/material_list.js
  72. 7 58
      app/service/pos.js
  73. 2 1
      app/service/project.js
  74. 18 27
      app/service/revise_audit.js
  75. 5 47
      app/service/revise_bills.js
  76. 0 18
      app/service/revise_pos.js
  77. 27 51
      app/service/stage_audit.js
  78. 1 3
      app/service/stage_change.js
  79. 4 4
      app/service/stage_pay.js
  80. 9 27
      app/service/stage_pos.js
  81. 27 0
      app/service/tender.js
  82. 1 0
      app/view/advance/detail.ejs
  83. 1 0
      app/view/advance/index.ejs
  84. 36 0
      app/view/budget/compare.ejs
  85. 39 0
      app/view/budget/compare_modal.ejs
  86. 122 0
      app/view/budget/detail.ejs
  87. 49 0
      app/view/budget/detail_modal.ejs
  88. 47 0
      app/view/budget/list.ejs
  89. 157 0
      app/view/budget/list_modal.ejs
  90. 14 0
      app/view/budget/sub_menu.ejs
  91. 8 0
      app/view/budget/sub_menu_list.ejs
  92. 16 0
      app/view/budget/sub_mini_menu.ejs
  93. 2 0
      app/view/change/information.ejs
  94. 23 8
      app/view/change/information_modal.ejs
  95. 37 0
      app/view/change/revise_modal.ejs
  96. 66 65
      app/view/login/login.ejs
  97. 51 41
      app/view/login/login_port.ejs
  98. 39 0
      app/view/material/audit_modal.ejs
  99. 8 5
      app/view/material/exponent.ejs
  100. 0 0
      app/view/material/file.ejs

+ 2 - 0
app.js

@@ -16,6 +16,7 @@ const _ = require('lodash');
 const BaseService = require('./app/base/base_service');
 const BaseTreeService = require('./app/base/base_tree_service');
 const BaseBillsService = require('./app/base/base_bills_service');
+const BaseBudgetService = require('./app/base/base_budget_service');
 const BaseController = require('./app/base/base_controller');
 
 const menu = require('./config/menu');
@@ -31,6 +32,7 @@ module.exports = app => {
     app.BaseService = BaseService;
     app.BaseTreeService = BaseTreeService;
     app.BaseBillsService = BaseBillsService;
+    app.BaseBudgetService = BaseBudgetService;
 
     // 控制器基类
     app.BaseController = BaseController;

+ 10 - 4
app/base/base_bills_service.js

@@ -26,6 +26,13 @@ class BaseBillsSerivce extends TreeService {
         this.relaPosService = relaPosName;
     }
 
+    async getFinalData(mid, columns) {
+        return await this.db.select(this.departTableName(mid), {
+            where: this.getCondition({mid: mid}),
+            columns,
+        });
+    }
+
     set relaPosService(posName) {
         this._posName = posName;
     }
@@ -485,7 +492,7 @@ class BaseBillsSerivce extends TreeService {
         const maxId = await this._getMaxLid(this.ctx.tender.id);
 
         const AnalysisExcel = require('../lib/analysis_excel').AnalysisGclExcelTree;
-        const analysisExcel = new AnalysisExcel(this.ctx);
+        const analysisExcel = new AnalysisExcel(this.ctx, this.setting);
         const cacheData = analysisExcel.analysisData(sheet, node, maxId, data);
         if (!cacheData) throw '导入数据错误,请检查Excel文件后重试';
 
@@ -650,8 +657,8 @@ class BaseBillsSerivce extends TreeService {
                 newBills.quantity = this.ctx.helper.add(newBills.sgfh_qty,
                     this.ctx.helper.add(newBills.sjcl_qty, newBills.qtcl_qty));
                 newBills.sgfh_tp = this.ctx.helper.mul(newBills.sgfh_qty, newBills.unit_price, tpDecimal);
-                newBills.sjcl_tp = this.ctx.helper.mul(newBills.qtcl_qty, newBills.unit_price, tpDecimal);
-                newBills.qtcl_tp = this.ctx.helper.mul(newBills.sjcl_qty, newBills.unit_price, tpDecimal);
+                newBills.sjcl_tp = this.ctx.helper.mul(newBills.sjcl_qty, newBills.unit_price, tpDecimal);
+                newBills.qtcl_tp = this.ctx.helper.mul(newBills.qtcl_qty, newBills.unit_price, tpDecimal);
                 newBills.total_price = this.ctx.helper.mul(newBills.quantity, newBills.unit_price, tpDecimal);
                 newBills.deal_tp = this.ctx.helper.mul(newBills.deal_qty, newBills.unit_price, tpDecimal);
                 if (defaultData) this.ctx.helper._.assignIn(newBills, defaultData);
@@ -695,7 +702,6 @@ class BaseBillsSerivce extends TreeService {
             pos: pastePosData,
         };
     }
-
 }
 
 module.exports = BaseBillsSerivce;

+ 465 - 0
app/base/base_budget_service.js

@@ -0,0 +1,465 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date
+ * @version
+ */
+
+
+const TreeService = require('./base_tree_service');
+const billsUtils = require('../lib/bills_utils');
+const readOnlyFields = ['id', 'bid', 'tree_id', 'tree_pid', 'order', 'level', 'full_path', 'is_leaf'];
+const calcFields = ['unit_price', 'quantity', 'total_price'];
+
+class BaseBudget extends TreeService {
+
+    /**
+     * 构造函数
+     *
+     * @param {Object} ctx - egg全局变量
+     * @param {String} tableName - 表名
+     * @return {void}
+     */
+    constructor(ctx, setting, tablename) {
+        super(ctx, {
+            mid: 'bid',
+            kid: 'tree_id',
+            pid: 'tree_pid',
+            order: 'order',
+            level: 'level',
+            isLeaf: 'is_leaf',
+            fullPath: 'full_path',
+            keyPre: setting.keyPre,
+            uuid: true,
+        });
+        this.tableName = tablename;
+    }
+
+    async initByTemplate(conn, budgetId, templateId){
+        if (!conn) throw '内部错误';
+        if (budgetId <= 0) throw '概算项目id错误';
+        const data = await this.ctx.service.tenderNodeTemplate.getData(templateId);
+        if (!data.length) throw '模板数据有误';
+
+        // 整理数据
+        const insertData = [];
+        for (const tmp of data) {
+            insertData.push({
+                id: this.uuid.v4(),
+                bid: budgetId,
+                tree_id: tmp.template_id,
+                tree_pid: tmp.pid,
+                level: tmp.level,
+                order: tmp.order,
+                full_path: tmp.full_path,
+                is_leaf: tmp.is_leaf,
+                code: tmp.code,
+                name: tmp.name,
+                unit: tmp.unit,
+                node_type: tmp.node_type,
+            });
+        }
+        const operate = await conn.insert(this.tableName, insertData);
+        return operate.affectedRows === data.length;
+    }
+
+    async addChild(budgetId, selectId, data) {
+        if ((budgetId <= 0) || (selectId <= 0)) return [];
+        const selectData = await this.getDataByKid(budgetId, selectId);
+        if (!selectData) {
+            throw '新增节点数据错误';
+        }
+        const children = await this.getChildrenByParentId(budgetId, selectId);
+
+        const maxId = await this._getMaxLid(budgetId);
+
+        data.id = this.uuid.v4();
+        data.bid = budgetId;
+        data.tree_id = maxId + 1;
+        data.tree_pid = selectData.tree_id;
+        data.level = selectData.level + 1;
+        data.order = children.length + 1;
+        data.full_path = selectData.full_path + '-' + data.tree_id;
+        data.is_leaf = true;
+
+        this.transaction = await this.db.beginTransaction();
+        try {
+            const result = await this.transaction.insert(this.tableName, data);
+            if (children.length === 0) {
+                await this.transaction.update(this.tableName,
+                    { is_leaf: false, quantity: 0, unit_price: 0, total_price: 0 },
+                    { where: { bid: budgetId, tree_id: selectData.tree_id } });
+            }
+            await this.transaction.commit();
+        } catch(err) {
+            this.transaction.rollback();
+            throw err;
+        }
+
+        this._cacheMaxLid(budgetId, maxId + 1);
+
+        // 查询应返回的结果
+        const resultData = {};
+        resultData.create = await this.getDataByKid(budgetId, data.tree_id);
+        if (children.length === 0) resultData.update = await this.getDataByKid(budgetId, selectId);
+        return resultData;
+    }
+
+    _filterStdData(stdData) {
+        const result = {
+            name: stdData.name,
+            unit: stdData.unit,
+            node_type: stdData.node_type,
+        };
+        result.code = stdData.code ? stdData.code : '';
+        result.b_code = stdData.b_code ? stdData.b_code : '';
+        return result;
+    }
+
+    async _addChildNodeData(budgetId, parentData, data) {
+        if (budgetId <= 0) return undefined;
+        if (!data) data = {};
+        const pid = parentData ? parentData.tree_id : this.rootId;
+
+        const maxId = await this._getMaxLid(budgetId);
+
+        data.id = this.uuid.v4();
+        data.bid = budgetId;
+        data.tree_id = maxId + 1;
+        data.tree_pid = pid;
+        if (data.order === undefined) data.order = 1;
+        data.level = parentData ? parentData.level + 1 : 1;
+        data.full_path = parentData ? parentData.full_path + '-' + data.tree_id : '' + data.tree_id;
+        if (data.is_leaf === undefined) data.is_leaf = true;
+        const result = await this.transaction.insert(this.tableName, data);
+
+        this._cacheMaxLid(budgetId, maxId + 1);
+
+        return [result, data];
+    }
+
+    async _addChildAutoOrder(budgetId, parentData, data) {
+        const findPreData = function(list, a) {
+            if (!list || list.length === 0) { return null; }
+            for (let i = 0, iLen = list.length; i < iLen; i++) {
+                if (billsUtils.compareCode(list[i].code, a.code) > 0) {
+                    return i > 0 ? list[i - 1] : null;
+                }
+            }
+            return list[list.length - 1];
+        };
+
+        const pid = parentData ? parentData.tree_id : this.rootId;
+        const children = await this.getChildrenByParentId(budgetId, pid);
+        const preData = findPreData(children, data);
+        if (!preData || children.indexOf(preData) < children.length - 1) {
+            await this._updateChildrenOrder(budgetId, pid, preData ? preData.order + 1 : 1);
+        }
+        data.order = preData ? preData.order + 1 : 1;
+        const [addResult, node] = await this._addChildNodeData(budgetId, parentData, data);
+
+        return [addResult, node];
+    }
+
+    async addStdNodeWithParent(budgetId, stdData, stdLib) {
+        // 查询完整标准清单,并按层次排序
+        const fullLevel = await stdLib.getFullLevelDataByFullPath(stdData.list_id, stdData.full_path);
+        fullLevel.sort(function(x, y) {
+            return x.level - y.level;
+        });
+
+        let isNew = false,
+            node,
+            firstNew,
+            updateParent,
+            addResult;
+        const expandIds = [];
+        this.transaction = await this.db.beginTransaction();
+        try {
+            // 从最顶层节点依次查询是否存在,否则添加
+            for (let i = 0, len = fullLevel.length; i < len; i++) {
+                const stdNode = fullLevel[i];
+
+                if (isNew) {
+                    const newData = this._filterStdData(stdNode);
+                    newData.is_leaf = (i === len - 1);
+                    [addResult, node] = await this._addChildNodeData(budgetId, node, newData);
+                } else {
+                    const parent = node;
+                    node = await this.getDataByCondition({
+                        bid: budgetId,
+                        tree_pid: parent ? parent.tree_id : this.rootId,
+                        code: stdNode.code,
+                        name: stdNode.name,
+                    });
+                    if (!node) {
+                        isNew = true;
+                        const newData = this._filterStdData(stdNode);
+                        newData.is_leaf = (i === len - 1);
+                        [addResult, node] = await this._addChildAutoOrder(budgetId, parent, newData);
+                        if (parent && parent.is_leaf) {
+                            await this.transaction.update(this.tableName, { id: parent.id, is_leaf: false,
+                                unit_price: 0, quantity: 0, total_price: 0});
+                            updateParent = parent;
+                        }
+                        firstNew = node;
+                    } else {
+                        expandIds.push(node.tree_id);
+                    }
+                }
+            }
+            await this.transaction.commit();
+        } catch (err) {
+            await this.transaction.rollback();
+            throw err;
+        }
+
+        // 查询应返回的结果
+        let createData = [],
+            updateData = [];
+        if (firstNew) {
+            createData = await this.getDataByFullPath(budgetId, firstNew.full_path + '%');
+            updateData = await this.getNextsData(budgetId, firstNew.tree_pid, firstNew.order);
+            if (updateParent) {
+                updateData.push(await this.getDataByCondition({ id: updateParent.id }));
+            }
+        }
+        return { create: createData, update: updateData };
+    }
+
+    async addBillsNode(budgetId, selectId, data) {
+        return await this.addNode(budgetId, selectId, data ? data : {});
+    }
+
+    async addStdNode(budgetId, selectId, stdData) {
+        const newData = this._filterStdData(stdData);
+        const result = await this.addBillsNode(budgetId, selectId, newData);
+        return result;
+    }
+
+    async addStdNodeAsChild(budgetId, selectId, stdData) {
+        const newData = this._filterStdData(stdData);
+        const result = await this.addChild(budgetId, selectId, newData);
+        return result;
+    }
+
+    _filterUpdateInvalidField(id, data) {
+        const result = { id };
+        for (const prop in data) {
+            if (readOnlyFields.indexOf(prop) === -1) result[prop] = data[prop];
+        }
+        return result;
+    }
+    _checkCalcField(data) {
+        for (const prop in data) {
+            if (calcFields.indexOf(prop) >= 0) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    async updateCalc(budgetId, data) {
+        const helper = this.ctx.helper;
+        // 简单验证数据
+        if (budgetId <= 0 || !this.ctx.budget) throw '标段不存在';
+        if (!data) throw '提交数据错误';
+        const datas = data instanceof Array ? data : [data];
+        const ids = [];
+        for (const row of datas) {
+            if (budgetId !== row.bid) throw '提交数据错误';
+            ids.push(row.id);
+        }
+
+        const conn = await this.db.beginTransaction();
+        try {
+            for (const row of datas) {
+                const updateNode = await this.getDataById(row.id);
+                if (!updateNode || budgetId !== updateNode.bid || row.tree_id !== updateNode.tree_id) throw '提交数据错误';
+                let updateData;
+
+                // 项目节、工程量清单相关
+                if (row.b_code) {
+                    row.dgn_qty1 = 0;
+                    row.dgn_qty2 = 0;
+                    row.code = '';
+                }
+                if (row.code) row.b_code = '';
+
+                if (this._checkCalcField(row)) {
+                    let calcData = JSON.parse(JSON.stringify(row));
+                    if (row.quantity !== undefined || row.unit_price !== undefined) {
+                        calcData.quantity = row.quantity === undefined ? updateNode.quantity : helper.round(row.quantity, this.ctx.budget.decimal.qty);
+                        calcData.unit_price = row.unit_price === undefined ? updateNode.unit_price : helper.round(row.unit_price, this.ctx.budget.decimal.up);
+                        calcData.total_price = helper.mul(calcData.quantity, calcData.unit_price, this.ctx.budget.decimal.tp);
+                    } else if (row.total_price !== undefined ) {
+                        calcData.quantity = 0;
+                        calcData.unit_price = 0;
+                        calcData.total_price = helper.round(row.total_price, this.ctx.budget.decimal.tp);
+                    }
+                    updateData = this._filterUpdateInvalidField(updateNode.id, this._.defaults(calcData, row));
+                } else {
+                    updateData = this._filterUpdateInvalidField(updateNode.id, row);
+                }
+                await conn.update(this.tableName, updateData);
+            }
+            await conn.commit();
+        } catch (err) {
+            await conn.rollback();
+            throw err;
+        }
+
+        return { update: await this.getDataById(ids) };
+    }
+    async pasteBlockData (bid, sid, pasteData, defaultData) {
+        if ((bid <= 0) || (sid <= 0)) return [];
+
+        if (!pasteData || pasteData.length <= 0) throw '复制数据错误';
+        for (const pd of pasteData) {
+            if (!pd || pd.length <= 0) throw '复制数据错误';
+            pd.sort(function (x, y) {
+                return x.level - y.level
+            });
+            if (pd[0].tree_pid !== pasteData[0][0].tree_pid) throw '复制数据错误:仅可操作同层节点';
+        }
+        this.newBills = false;
+        const selectData = await this.getDataByKid(bid, sid);
+        if (!selectData) throw '粘贴数据错误';
+        const newParentPath = selectData.full_path.replace(selectData.tree_id, '');
+
+        const pasteBillsData = [];
+        let maxId = await this._getMaxLid(bid);
+        for (const [i, pd] of pasteData.entries()) {
+            for (const d of pd) {
+                d.children = pd.filter(function (x) {
+                    return x.tree_pid === d.tree_id;
+                });
+            }
+            const pbd = [];
+            for (const [j, d] of pd.entries()) {
+                const newBills = {
+                    id: this.uuid.v4(),
+                    bid: bid,
+                    tree_id: maxId + j + 1,
+                    tree_pid: j === 0 ? selectData.tree_pid : d.tree_pid,
+                    level: d.level + selectData.level - pd[0].level,
+                    order: j === 0 ? selectData.order + i + 1 : d.order,
+                    is_leaf: d.is_leaf,
+                    code: d.code,
+                    b_code: d.b_code,
+                    name: d.name,
+                    unit: d.unit,
+                    source: d.source,
+                    remark: d.remark,
+                    drawing_code: d.drawing_code,
+                    memo: d.memo,
+                    node_type: d.node_type,
+                };
+                for (const c of d.children) {
+                    c.tree_pid = newBills.tree_id;
+                }
+                if (d.b_code && d.is_leaf) {
+                    newBills.dgn_qty1 = 0;
+                    newBills.dgn_qty2 = 0;
+                    newBills.unit_price = this.ctx.helper.round(d.unit_price, this.ctx.budget.decimal.up);
+                    newBills.quantity = this.ctx.helper.round(d.quantity, this.ctx.budget.decimal.qty);
+                    newBills.total_price = this.ctx.helper.mul(newBills.quantity, newBills.unit_price, this.ctx.budget.decimal.tp);
+                } else {
+                    newBills.dgn_qty1 = this.ctx.helper.round(d.dgn_qty1, this.ctx.budget.decimal.qty);
+                    newBills.dgn_qty2 = this.ctx.helper.round(d.dgn_qty2, this.ctx.budget.decimal.qty);
+                    newBills.unit_price = 0;
+                    newBills.quantity = 0;
+                    newBills.total_price = d.is_leaf ? this.ctx.helper.round(d.total_price, this.ctx.budget.decimal.tp) : 0;
+                }
+                if (defaultData) this.ctx.helper._.assignIn(newBills, defaultData);
+                pbd.push(newBills);
+            }
+            for (const d of pbd) {
+                const parent = pbd.find(function (x) {
+                    return x.tree_id === d.tree_pid;
+                });
+                d.full_path = parent
+                    ? parent.full_path + '-' + d.tree_id
+                    : newParentPath + d.tree_id;
+                if (defaultData) this.ctx.helper._.assignIn(pbd, defaultData);
+                pasteBillsData.push(d);
+            }
+            maxId = maxId + pbd.length;
+        }
+
+        this.transaction = await this.db.beginTransaction();
+        try {
+            // 选中节点的所有后兄弟节点,order+粘贴节点个数
+            await this._updateChildrenOrder(bid, selectData.tree_pid, selectData.order + 1, pasteData.length);
+            // 数据库创建新增节点数据
+            if (pasteBillsData.length > 0) await this.transaction.insert(this.tableName, pasteBillsData);
+            this._cacheMaxLid(bid, maxId);
+            await this.transaction.commit();
+        } catch (err) {
+            await this.transaction.rollback();
+            throw err;
+        }
+
+        // 查询应返回的结果
+        const updateData = await this.getNextsData(selectData.bid, selectData.tree_pid, selectData.order + pasteData.length);
+        return { create: pasteBillsData, update: updateData };
+    }
+
+    async importExcel(templateId, excelData, needGcl, filter) {
+        const AnalysisExcel = require('../lib/analysis_excel').AnalysisExcelTree;
+        const analysisExcel = new AnalysisExcel(this.ctx, this.setting, needGcl ? ['code', 'b_code'] : ['code']);
+        const tempData = await this.ctx.service.tenderNodeTemplate.getData(templateId, true);
+        const cacheTree = analysisExcel.analysisData(excelData, tempData, {
+            filterZeroGcl: filter, filterPos: true, filterGcl: !needGcl, filterCalc: true,
+        });
+        const orgMaxId = await this._getMaxLid(this.ctx.budget.id);
+        const conn = await this.db.beginTransaction();
+        try {
+            await conn.delete(this.tableName, { bid: this.ctx.budget.id });
+            const datas = [];
+            for (const node of cacheTree.items) {
+                const data = {
+                    id: node.id,
+                    bid: this.ctx.budget.id,
+                    tree_id: node.tree_id,
+                    tree_pid: node.tree_pid,
+                    level: node.level,
+                    order: node.order,
+                    is_leaf: !node.children || node.children.length === 0,
+                    full_path: node.full_path,
+                    code: node.code || '',
+                    b_code: node.b_code || '',
+                    name: node.name || '',
+                    unit: node.unit || '',
+                    unit_price: !node.children || node.children.length === 0 ? node.unit_price || 0 : 0,
+                    dgn_qty1: node.dgn_qty1 || 0,
+                    dgn_qty2: node.dgn_qty2 || 0,
+                    memo: node.memo,
+                    drawing_code: node.drawing_code,
+                    node_type: node.node_type,
+                    quantity: !node.children || node.children.length === 0 ? node.quantity || 0 : 0,
+                    total_price: !node.children || node.children.length === 0 ? node.total_price || 0 : 0,
+                };
+                datas.push(data);
+            }
+            await conn.insert(this.tableName, datas);
+            await conn.commit();
+            if (orgMaxId) this._cacheMaxLid(this.ctx.budget.id, cacheTree.keyNodeId);
+            return datas;
+        } catch (err) {
+            await conn.rollback();
+            throw err;
+        }
+    }
+
+    async getSumTp(bid) {
+        const sql = 'SELECT Sum(total_price) As total_price FROM ' + this.tableName + ' Where bid = ? and is_leaf = true';
+        const result = await this.db.queryOne(sql, [bid]);
+        return result.total_price;
+    }
+}
+
+module.exports = BaseBudget;

+ 1 - 0
app/base/base_controller.js

@@ -68,6 +68,7 @@ class BaseController extends Controller {
                 message: postError,
             };
         }
+        this.ctx.menuList.budget.display = this.ctx.session.sessionProject.showBudget;
 
         try {
             data.min = this.app.config.min;

+ 1 - 100
app/base/base_tree_service.js

@@ -33,6 +33,7 @@ class TreeService extends Service {
      */
     constructor(ctx, setting) {
         super(ctx);
+        this.rootId = -1;
         this.tableName = setting.tableName;
         this.setting = setting;
         // 以下字段仅可通过树结构操作改变,不可直接通过update方式从接口提交,发现时过滤
@@ -1010,106 +1011,6 @@ class TreeService extends Service {
             return await this.getDataById(datas.id);
         }
     }
-
-    async pasteBlockRelaData(relaData) {
-        if (!this.transaction) throw '更新数据错误';
-        // for (const id of relaData) {
-        //     await this.ctx.service.pos.copyBillsPosData(id.org, id.new, this.transaction);
-        // }
-    }
-
-    async getPasteBlockResult(select, copyNodes) {
-        const createData = await this.getDataByIds(newIds);
-        const updateData = await this.getNextsData(selectData[this.setting.mid], selectData[this.setting.pid], selectData[this.setting.order] + copyNodes.length);
-        //const posData = await this.ctx.service.pos.getPosData({ lid: newIds });
-        return {
-            ledger: { create: createData, update: updateData },
-            //pos: posData,
-        };
-    }
-
-    /**
-     * 复制粘贴整块
-     * @param {Number} tenderId - 标段Id
-     * @param {Number} selectId - 选中几点Id
-     * @param {Array} block - 复制节点Id
-     * @return {Object} - 提价后的数据(其中新增粘贴数据,只返回第一层)
-     */
-    async pasteBlock(mid, kid, paste) {
-        if ((mid <= 0) || (kid <= 0)) return [];
-
-        const selectData = await this.getDataByKid(mid, kid);
-        if (!selectData) throw '数据错误';
-
-        const copyNodes = await this.getDataByNodeIds(paste.tid, paste.block);
-        if (!copyNodes || copyNodes.length <= 0)  throw '复制数据错误';
-        for (const node of copyNodes) {
-            if (node[this.setting.pid] !== copyNodes[0][this.setting.pid]) throw '复制数据错误:仅可操作同层节点';
-        }
-
-        const newParentPath = selectData[this.setting.fullPath].replace(selectData[this.setting.kid], '');
-        const orgParentPath = copyNodes[0][this.setting.fullPath].replace(copyNodes[0][this.setting.kid], '');
-        const newIds = [];
-        this.transaction = await this.db.beginTransaction();
-        try {
-            // 选中节点的所有后兄弟节点,order+粘贴节点个数
-            await this._updateChildrenOrder(mid, selectData[this.setting.pid], selectData[this.setting.order], copyNodes.length);
-            for (let iNode = 0; iNode < copyNodes.length; iNode++) {
-                const node = copyNodes[iNode];
-                let datas = await this.getDataByFullPath(mid, node[this.setting.fullPath] + '%');
-                datas = this._.sortBy(datas, 'level');
-
-                const maxId = await this._getMaxLid(mid);
-                this._cacheMaxLid(mid, maxId + datas.length);
-
-                const billsId = [];
-                // 计算粘贴数据中需更新部分
-                for (let index = 0; index < datas.length; index++) {
-                    const data = datas[index];
-                    const newId = maxId + index + 1;
-                    const idChange = {
-                        org: data.id,
-                    };
-                    if (this.setting.uuid) data.id = this.uuid.v4();
-                    idChange.new = data.id;
-                    data[this.setting.mid] = mid;
-                    if (!data[this.setting.isLeaf]) {
-                        for (const children of datas) {
-                            children[this.setting.fullPath] = children[this.setting.fullPath].replace('-' + data[this.setting.kid], '-' + newId);
-                            if (children[this.setting.pid] === data[this.setting.kid]) {
-                                children[this.setting.pid] = newId;
-                            }
-                        }
-                    } else {
-                        data[this.setting.fullPath] = data[this.setting.fullPath].replace('-' + data[this.setting.kid], '-' + newId);
-                    }
-                    data[this.setting.kid] = newId;
-                    data[this.setting.fullPath] = data[this.setting.fullPath].replace(orgParentPath, newParentPath);
-                    if (data[this.setting.pid] === node[this.setting.pid]) {
-                        data[this.setting.pid] = selectData[this.setting.pid];
-                        data[this.setting.order] = selectData[this.setting.order] + iNode + 1;
-                    }
-                    data[this.setting.level] = data[this.setting.level] + selectData[this.setting.level] - copyNodes[0][this.setting.level];
-                    idChange.isLeaf = data[this.setting.isLeaf];
-                    billsId.push(idChange);
-                    newIds.push(data.id);
-                }
-                const newData = await this.transaction.insert(this.tableName, datas);
-                await this.pasteBlockRelaData(billsId);
-            }
-            // 数据库创建新增节点数据
-            await this.transaction.commit();
-            this.transaction = null;
-        } catch (err) {
-            await this.transaction.rollback();
-            this.transaction = null;
-            throw err;
-        }
-
-        // 查询应返回的结果
-        return await this.getPasteBlockResult(selectData, copyNodes);
-    }
-
 }
 
 module.exports = TreeService;

+ 15 - 1
app/const/material.js

@@ -32,7 +32,14 @@ const ex_basic_text = {
     bqht: '所选期合同计量金额',
     bqbg: '所选期变更计量金额',
     bqwc: '所选期完成计量金额',
-}
+    zdy: '自定义金额',
+};
+// 金额
+const decimal = {
+    up: 3, // 单价
+    qty: 3, // 数量
+    tp: 2, // 金额
+};
 const ex_calc = [
     {
         code: 'bqht',
@@ -52,6 +59,12 @@ const ex_calc = [
         value: '',
         select: true,
     },
+    {
+        code: 'zdy',
+        text: '自定义金额',
+        value: '',
+        select: false,
+    },
 ];
 
 const is_summary = {
@@ -66,4 +79,5 @@ module.exports = {
     is_summary,
     ex_calc,
     ex_basic_text,
+    decimal,
 };

+ 1 - 0
app/const/page_show.js

@@ -33,6 +33,7 @@ const defaultSetting = {
     closeWatermark: 0,
     openSign: 0,
     openNetCaSign: 0,
+    openChangeRevise: 0,
 };
 
 

+ 442 - 0
app/controller/budget_controller.js

@@ -0,0 +1,442 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date 2021/10/27
+ * @version
+ */
+const stdDataAddType = {
+    withParent: 1,
+    child: 2,
+    next: 3,
+};
+const auditConst = require('../const/audit');
+const LzString = require('lz-string');
+const accountGroup = require('../const/account_group').group;
+module.exports = app => {
+    class BudgetController extends app.BaseController {
+
+        /**
+         * 概算投资
+         *
+         * @param ctx
+         * @returns {Promise<void>}
+         */
+        async list(ctx) {
+            try {
+                const renderData = {
+                    jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.budget.list),
+                    auditConst,
+                };
+                renderData.budgetList = await ctx.service.budget.getBudget(ctx.session.sessionUser.is_admin);
+                renderData.budgetStd = await ctx.service.budgetStd.getDataByProjectId(ctx.session.sessionProject.id);
+                for (const bl of renderData.budgetList) {
+                    const std = renderData.budgetStd.find(x => { return x.id === bl.std_id; });
+                    bl.std_name = std ? std.name : '';
+                    bl.gu_tp = await ctx.service.budgetGu.getSumTp(bl.id);
+                    bl.gai_tp = await ctx.service.budgetGai.getSumTp(bl.id);
+                    bl.yu_tp = await ctx.service.budgetYu.getSumTp(bl.id);
+                }
+                renderData.tenderList = await ctx.service.tender.getList4Select('stage');
+                const accountList = await ctx.service.projectAccount.getAllDataByCondition({
+                    where: { project_id: ctx.session.sessionProject.id, enable: 1 },
+                    columns: ['id', 'name', 'company', 'role', 'enable', 'is_admin', 'account_group', 'mobile'],
+                });
+                renderData.accountList = accountList;
+                renderData.accountGroup = accountGroup.map((item, idx) => {
+                    const groupList = accountList.filter(item => item.account_group === idx);
+                    return { groupName: item, groupList };
+                });
+                renderData.permissionConst = ctx.service.budgetPermission.PermissionConst;
+                await this.layout('budget/list.ejs', renderData, 'budget/list_modal.ejs');
+            } catch (err) {
+                ctx.log(err);
+            }
+        }
+
+        async add(ctx) {
+            try {
+                const data = JSON.parse(ctx.request.body.data);
+                if (!data.name || data.name.length > 100) throw '项目名称有误';
+                if (!data.std_id) throw '概预算标准有误';
+                const result = await ctx.service.budget.add(data);
+                ctx.body = { err: 0, msg: '', data: result };
+            } catch (err) {
+                ctx.log(err);
+                ctx.ajaxErrorBody(err, '新建项目失败');
+            }
+        }
+
+        async del(ctx) {
+            try {
+                const data = JSON.parse(ctx.request.body.data);
+                if (!data.id) throw '参数有误';
+                const result = await ctx.service.budget.deleteBudgetNoBackup(data.id);
+                ctx.body = { err: 0, msg: '', data: result };
+            } catch(err) {
+                ctx.log(err);
+                ctx.ajaxErrorBody(err, '删除项目失败');
+            }
+        }
+
+        async save(ctx) {
+            try {
+                const data = JSON.parse(ctx.request.body.data);
+                if (!data.id) throw '参数有误';
+                let result = null;
+                if (data.name !== undefined) {
+                    if (!data.name || data.name.length > 100) throw '项目名称有误';
+                    result = await ctx.service.budget.save({ id: data.id, name: data.name });
+                } else if (data.rela_tender !== undefined) {
+                    result = await ctx.service.budget.save({ id: data.id, rela_tender: data.rela_tender });
+                }
+                ctx.body = { err: 0, msg: '', data: result };
+            } catch(err) {
+                ctx.log(err);
+                ctx.ajaxErrorBody(err, '保存数据失败');
+            }
+        }
+
+        async rela(ctx) {
+            try {
+                const id = parseInt(ctx.query.id);
+                const budgetList = await ctx.service.budget.getBudget(true);
+                const otherBudget = budgetList.filter(x => { return x.id !== id || !x.rela_tender });
+                const _ = ctx.helper._;
+                const otherRela = _.map(_.map(otherBudget, 'rela_tender').join(',').split(','), _.toInteger);
+                const tenderList = await ctx.service.tender.getList4Select('stage');
+                ctx.body = {
+                    err: 0,
+                    msg: '',
+                    data: tenderList.filter(x => { return otherRela.indexOf(x.id) === -1})
+                        .map(y => { return {id: y.id, name: y.name, lastStageOrder: y.lastStage.order, lastStageStatus: auditConst.stage.statusString[y.lastStage.status]}}),
+                };
+            } catch (err) {
+                ctx.log(err);
+                ctx.postError(err, '获取数据失败');
+            }
+        }
+
+        async member(ctx) {
+            try {
+                const data = JSON.parse(ctx.request.body.data);
+                const member = await ctx.service.budgetPermission.getBudgetPermission(data.id);
+                ctx.body = { err: 0, msg: '', data: member };
+            } catch (err) {
+                ctx.log(err);
+                ctx.ajaxErrorBody(err, '查询项目成员失败');
+            }
+        }
+
+        async memberSave(ctx) {
+            try {
+                const data = JSON.parse(ctx.request.body.data);
+                if (!data.id) throw '参数有误';
+                await ctx.service.budgetPermission.saveBudgetPermission(data.id, data.member);
+                ctx.body = { err: 0, msg: '', data: '' };
+            } catch (err) {
+                ctx.log(err);
+                ctx.ajaxErrorBody(err, '保存数据失败');
+            }
+        }
+
+        async compare(ctx) {
+            try {
+                const renderData = {
+                    jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.budget.compare),
+                    auditConst,
+                };
+                const relaTenderId = ctx.helper._.map(ctx.budget.rela_tender.split(','), ctx.helper._.toInteger);
+                const tenderList = await ctx.service.tender.getList4Select('stage');
+                renderData.tenderList = relaTenderId.length > 0 ? tenderList.filter(x => {
+                    return relaTenderId.indexOf(x.id) >= 0;
+                }) : tenderList;
+                await this.layout('budget/compare.ejs', renderData, 'budget/compare_modal.ejs');
+            } catch (err) {
+                ctx.log(err);
+            }
+        }
+
+        async compareLoad(ctx) {
+            try {
+                const gu = await ctx.service.budgetGu.getData(ctx.budget.id);
+                const gai = await ctx.service.budgetGai.getData(ctx.budget.id);
+                const yu = await ctx.service.budgetYu.getData(ctx.budget.id);
+                ctx.body = { err: 0, msg: '', data: { gu, gai, yu } }
+            } catch (err) {
+                ctx.log(err);
+                ctx.ajaxErrorBody(err, '获取数据错误');
+            }
+        }
+        async compareFinal(ctx) {
+            try {
+                const data = JSON.parse(ctx.request.body.data);
+                const final = [], helper = this.ctx.helper;
+                for (const id of data.id) {
+                    const bills = await ctx.service.ledger.getFinalData(id, ['id', 'ledger_id', 'ledger_pid', 'level', 'order', 'full_path', 'is_leaf',
+                        'code', 'b_code', 'name', 'unit', 'dgn_qty1', 'dgn_qty2', 'total_price']);
+                    // 使用完成数据对比
+                    // const stage = ctx.service.stage.getLastestCompleteStage(id);
+                    // const finalBills = ctx.service.stageBillsFinal.getFinalData(id, stage.id);
+                    // ctx.helper.assignRelaData(bills, [
+                    //     { data: finalBills, fields: ['contract_tp', 'qc_tp', 'used'], prefix: 'end_', relaId: 'lid' },
+                    // ]);
+
+                    const dgnData = await ctx.service.stageBillsDgn.getDgnData(id);
+                    // 使用最新一期对比
+                    const stage = await ctx.service.stage.getLastestStage(id);
+                    if (stage.status === auditConst.stage.status.checked) {
+                        const finalBills = await ctx.service.stageBillsFinal.getFinalData({id}, stage.order);
+                        ctx.helper.assignRelaData(bills, [
+                            { data: finalBills, fields: ['contract_tp', 'qc_tp'], prefix: 'end_', relaId: 'lid' },
+                            { data: dgnData, fields: ['deal_dgn_qty1', 'deal_dgn_qty2', 'c_dgn_qty1', 'c_dgn_qty2'], prefix: '', relaId: 'id' },
+                        ]);
+                        bills.forEach(b => {
+                            b.end_gather_tp = helper.add(b.end_qc_tp, b.end_contract_tp);
+                            delete b.end_contract_tp;
+                            delete b.end_qc_tp;
+                        });
+                    } else {
+                        await ctx.service.stage.doCheckStage(stage);
+                        const curBills = stage.readOnly
+                            ? await ctx.service.stageBills.getAuditorStageData2(id, stage.id, stage.curTimes, stage.curOrder)
+                            : await ctx.service.stageBills.getLastestStageData2(id, stage.id);
+                        const preBills = stage.order > 1 ? await ctx.service.stageBillsFinal.getFinalData({id}, stage.order - 1) : [];
+                        ctx.helper.assignRelaData(bills, [
+                            { data: curBills, fields: ['contract_tp', 'qc_tp'], prefix: '', relaId: 'lid' },
+                            { data: preBills, fields: ['contract_tp', 'qc_tp'], prefix: 'pre_', relaId: 'lid' },
+                            { data: dgnData, fields: ['deal_dgn_qty1', 'deal_dgn_qty2', 'c_dgn_qty1', 'c_dgn_qty2'], prefix: '', relaId: 'id' },
+                        ]);
+                        bills.forEach(b => {
+                            b.end_gather_tp = helper.sum([b.qc_tp, b.contract_tp, b.pre_qc_tp, b.pre_contract_tp]);
+                            delete b.contract_tp;
+                            delete b.qc_tp;
+                            delete b.pre_contract_tp;
+                            delete b.pre_qc_tp;
+                        });
+                    }
+                    final.push(bills);
+                }
+                ctx.body = { err: 0, msg: '', data: final }
+            } catch (err) {
+                ctx.log(err);
+                ctx.ajaxErrorBody(err, '获取决算数据错误');
+            }
+        }
+
+        _getSpreadSetting(needGcl) {
+            const spreadSetting = {
+                cols: [
+                    {title: '项目节编号', colSpan: '1', rowSpan: '2', field: 'code', hAlign: 0, width: 180, formatter: '@', cellType: 'tree'},
+                    {title: '清单编号', colSpan: '1', rowSpan: '2', field: 'b_code', hAlign: 0, width: 120, formatter: '@'},
+                    {title: '名称', colSpan: '1', rowSpan: '2', field: 'name', hAlign: 0, width: 230, formatter: '@'},
+                    {title: '单位', colSpan: '1', rowSpan: '2', field: 'unit', hAlign: 1, width: 50, formatter: '@', cellType: 'unit'},
+                    {title: '设计数量|数量1', colSpan: '2|1', rowSpan: '1|1', field: 'dgn_qty1', hAlign: 2, width: 80, type: 'Number'},
+                    {title: '|数量2', colSpan: '|1', rowSpan: '|1', field: 'dgn_qty2', hAlign: 2, width: 80, type: 'Number'},
+                    {title: '经济指标', colSpan: '1', rowSpan: '2', field: 'dgn_price', hAlign: 2, width: 80, type: 'Number', readOnly: true},
+                    {title: '清单数量', colSpan: '1', rowSpan: '2', field: 'quantity', hAlign: 2, width: 80, type: 'Number'},
+                    {title: '单价', colSpan: '1', rowSpan: '2', field: 'unit_price', hAlign: 2, width: 80, type: 'Number'},
+                    {title: '金额', colSpan: '1', rowSpan: '2', field: 'total_price', hAlign: 2, width: 80, type: 'Number'},
+                    {title: '图册号', colSpan: '1', rowSpan: '2', field: 'drawing_code', hAlign: 0, width: 100, formatter: '@'},
+                    {title: '备注', colSpan: '1', rowSpan: '2', field: 'memo', hAlign: 0, width: 100, formatter: '@'},
+                ],
+                emptyRows: 3,
+                headRows: 2,
+                headRowHeight: [25, 25],
+                defaultRowHeight: 21,
+                headerFont: '12px 微软雅黑',
+                font: '12px 微软雅黑',
+            };
+            if (!needGcl) {
+                spreadSetting.cols = spreadSetting.cols.filter(x => {
+                    return ['b_code', 'quantity', 'unit_price'].indexOf(x.field) < 0;
+                });
+            }
+            return spreadSetting;
+        }
+        _getRelaService(type) {
+            switch(type) {
+                case 'gu': return this.ctx.service.budgetGu;
+                case 'gai': return this.ctx.service.budgetGai;
+                case 'yu': return this.ctx.service.budgetYu;
+                default: return null;
+            }
+        }
+        async _getNeedGcl() {
+            if (!this.ctx.params.btype) throw '参数错误';
+            const funRela = await this.ctx.service.project.getFunRela(this.ctx.session.sessionProject.id);
+            return this.ctx.params.btype === 'yu' && !!funRela.needGcl;
+        }
+
+        async detail(ctx) {
+            try {
+                const needGcl = await this._getNeedGcl();
+                const renderData = {
+                    spreadSetting: this._getSpreadSetting(needGcl),
+                    jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.budget.detail),
+                    needGcl,
+                };
+                [renderData.stdBills, renderData.stdChapters] = await ctx.service.budgetStd.getStdList(ctx.budget.std_id, ctx.params.btype);
+                await this.layout('budget/detail.ejs', renderData, 'budget/detail_modal.ejs');
+            } catch (err) {
+                ctx.log(err);
+            }
+        }
+
+        async detailLoad(ctx) {
+            try {
+                const relaService = this._getRelaService(ctx.params.btype);
+                ctx.body = {
+                    err: 0, msg: '',
+                    data: await relaService.getData(ctx.budget.id),
+                }
+            } catch (err) {
+                ctx.log(err);
+                ctx.ajaxErrorBody(err, '获取数据错误');
+            }
+        }
+
+        async _billsBase(relaService, type, data) {
+            if (isNaN(data.id) || data.id <= 0) throw '数据错误';
+            if (type !== 'add') {
+                if (isNaN(data.count) || data.count <= 0) data.count = 1;
+            }
+            switch (type) {
+                case 'add':
+                    return await relaService.addNodeBatch(this.ctx.budget.id, data.id, {}, data.count);
+                case 'delete':
+                    return await relaService.delete(this.ctx.budget.id, data.id, data.count);
+                case 'up-move':
+                    return await relaService.upMoveNode(this.ctx.budget.id, data.id, data.count);
+                case 'down-move':
+                    return await relaService.downMoveNode(this.ctx.budget.id, data.id, data.count);
+                case 'up-level':
+                    return await relaService.upLevelNode(this.ctx.budget.id, data.id, data.count);
+                case 'down-level':
+                    return await relaService.downLevelNode(this.ctx.budget.id, data.id, data.count);
+            }
+        }
+        async _addStd(relaService, data) {
+            if ((isNaN(data.id) || data.id <= 0) || !data.stdType || !data.stdNode) throw '参数错误';
+
+            let stdLib, addType;
+            switch (data.stdType) {
+                case 'xmj':
+                    stdLib = this.ctx.service.stdXmj;
+                    addType = stdDataAddType.withParent;
+                    break;
+                case 'gcl':
+                    stdLib = this.ctx.service.stdGcl;
+                    const selectNode = await relaService.getDataByKid(this.ctx.budget.id, data.id);
+                    addType = selectNode.b_code ? stdDataAddType.next : stdDataAddType.child;
+                    break;
+                default:
+                    throw '未知标准库';
+            }
+            const stdData = await stdLib.getDataByDataId(data.stdLibId, data.stdNode);
+            switch (addType) {
+                case stdDataAddType.child:
+                    return await relaService.addStdNodeAsChild(this.ctx.budget.id, data.id, stdData);
+                case stdDataAddType.next:
+                    return await relaService.addStdNode(this.ctx.budget.id, data.id, stdData);
+                case stdDataAddType.withParent:
+                    return await relaService.addStdNodeWithParent(this.ctx.budget.id, stdData, stdLib);
+                default:
+                    throw '未知添加方式';
+            }
+        }
+        async _pasteBlock(relaService, data) {
+            if ((isNaN(data.id) || data.id <= 0) ||
+                (!data.tid && data.tid <= 0) ||
+                (!data.block || data.block.length <= 0)) throw '参数错误';
+            return await relaService.pasteBlockData(this.ctx.budget.id, data.id, data.block);
+        }
+        async detailUpdate(ctx) {
+            try {
+                if (!ctx.budget) throw '项目数据错误';
+                if (ctx.budget.readOnly) throw '你无权修改数据';
+
+                const relaService = this._getRelaService(ctx.params.btype);
+                const data = JSON.parse(ctx.request.body.data);
+                if (!data.postType || !data.postData) throw '数据错误';
+                const responseData = { err: 0, msg: '', data: {} };
+
+                switch (data.postType) {
+                    case 'add':
+                    case 'delete':
+                    case 'up-move':
+                    case 'down-move':
+                    case 'up-level':
+                    case 'down-level':
+                        responseData.data = await this._billsBase(relaService, data.postType, data.postData);
+                        break;
+                    case 'update':
+                        ctx.helper.checkDgnQtyPrecision(data.postData);
+                        responseData.data = await relaService.updateCalc(ctx.budget.id, data.postData);
+                        break;
+                    case 'add-std':
+                        responseData.data = await this._addStd(relaService, data.postData);
+                        break;
+                    case 'paste-block':
+                        responseData.data = await this._pasteBlock(relaService, data.postData);
+                        break;
+                    default:
+                        throw '未知操作';
+                }
+                ctx.body = responseData;
+            } catch (err) {
+                this.log(err);
+                ctx.body = this.ajaxErrorBody(err, '数据错误');
+            }
+        }
+
+        async detailUploadExcel(ctx) {
+            try {
+                if (!ctx.budget) throw '项目数据错误';
+                if (ctx.budget.readOnly) throw '你无权导入数据';
+
+                const relaService = this._getRelaService(ctx.params.btype);
+                const needGcl = await this._getNeedGcl();
+                const ueType = ctx.params.ueType;
+                const compressData = ctx.request.body.data;
+                const data = JSON.parse(LzString.decompressFromUTF16(compressData));
+                const responseData = { err: 0, msg: '', data: {} };
+                switch (ueType) {
+                    case 'tz':
+                        const templateId = await this.ctx.service.budgetStd.getTemplateId(this.ctx.budget.std_id, ctx.params.btype);
+                        responseData.data = await relaService.importExcel(templateId, data.sheet, needGcl, data.filter);
+                        break;
+                    case 'gcl2xmj':
+                        responseData.data = await relaService.importGclExcel(data.id, data.sheet);
+                        break;
+                    default:
+                        throw '数据错误';
+                }
+                ctx.body = responseData;
+            } catch (err) {
+                this.log(err);
+                ctx.body = { err: 1, msg: err.toString(), data: null };
+            }
+        }
+
+        async decimal(ctx) {
+            try {
+                if (!ctx.budget) throw '项目数据错误';
+                if (ctx.budget.readOnly) throw '你无权修改小数位数';
+
+                const data = JSON.parse(ctx.request.body.data);
+                if (!data || !data.decimal || !data.page) throw '缺少必要参数';
+
+                const refreshData = await ctx.service.budget.saveDecimal(data.decimal, data.page);
+                ctx.body = { err: 0, msg: '', data: refreshData };
+            } catch (err) {
+                this.log(err);
+                this.ajaxErrorBody(err, '保存小数位数失败');
+            }
+        }
+    }
+
+    return BudgetController;
+};

+ 56 - 12
app/controller/change_controller.js

@@ -724,6 +724,8 @@ module.exports = app => {
                         }
                     }
                     renderData.changeList = changeList;
+                    renderData.changeLedgerList = await ctx.service.changeLedger.getAllDataByCondition({ where: { tender_id: ctx.tender.id } });
+                    renderData.changePosList = await ctx.service.changePos.getAllDataByCondition({ where: { tid: ctx.tender.id } });
                 }
                 renderData.auditList = auditList;
 
@@ -934,11 +936,31 @@ module.exports = app => {
 
         async defaultBills(ctx) {
             try {
+                // 判断是否台账修订中,修订中则不获取changeLedger及changePos值
+                const lastRevise = await ctx.service.ledgerRevise.getLastestRevise(ctx.tender.id);
+                const data = JSON.parse(ctx.request.body.data);
                 const ledgerData = await ctx.service.ledger.getData(ctx.tender.id);
-                const changeLedgerData = await ctx.service.changeLedger.getData(ctx.tender.id);
+                const changeLedgerData = !ctx.session.sessionProject.page_show.openChangeRevise ? [] : (lastRevise && lastRevise.status !== audit.revise.status.checked && data.from !== 'revise' ? [] : await ctx.service.changeLedger.getData(ctx.tender.id));
                 const posData = await ctx.service.pos.getPosData({ tid: ctx.tender.id });
-                const changePosData = await ctx.service.changePos.getPosData({ tid: ctx.tender.id });
+                const changePosData = !ctx.session.sessionProject.page_show.openChangeRevise ? [] : (lastRevise && lastRevise.status !== audit.revise.status.checked && data.from !== 'revise' ? [] : await ctx.service.changePos.getPosData({ tid: ctx.tender.id }));
                 const dealBills = await ctx.service.dealBills.getAllDataByCondition({ where: { tender_id: ctx.tender.id } });
+                // 标记ledger,搜索需求
+                if (changePosData.length > 0) {
+                    const cplIdList = ctx.helper._.uniq(ctx.helper._.map(changePosData, 'lid'));
+                    const cLIdList = ctx.helper._.map(changeLedgerData, 'id');
+                    const ledgerIdList = [];
+                    for (const cp of cplIdList) {
+                        if (ctx.helper._.indexOf(cLIdList, cp) === -1) {
+                            ledgerIdList.push(cp);
+                        }
+                    }
+                    if (ledgerIdList.length > 0) {
+                        for (const lid of ledgerIdList) {
+                            const data = ctx.helper._.find(ledgerData, { id: lid });
+                            data.cid = 1;
+                        }
+                    }
+                }
                 ctx.body = { err: 0, msg: '', data: { bills: ctx.helper._.concat(ledgerData, changeLedgerData), pos: ctx.helper._.concat(posData, changePosData), dealBills } };
             } catch (err) {
                 this.log(err);
@@ -1501,23 +1523,30 @@ module.exports = app => {
          */
         async reviseInfo(ctx) {
             try {
+                if (!ctx.session.sessionProject.page_show.openChangeRevise) {
+                    throw '该功能已关闭';
+                }
                 const change = ctx.change;
                 let edit = true;
-                if (change.status !== audit.flow.status.uncheck && change.status !== audit.flow.status.backnew) {
+                let changing = false;
+                if (change.status !== audit.flow.status.uncheck && change.status !== audit.flow.status.back && change.status !== audit.flow.status.revise) {
                     // throw '本条变更审批中或已完成,无法操作台账数据';
                     edit = false;
+                    changing = true;
                 }
+                let revising = false;
                 // 判断是否在修订中,是则无法操作本页
                 const lastRevise = await ctx.service.ledgerRevise.getLastestRevise(ctx.tender.id);
                 if (lastRevise && lastRevise.status !== audit.revise.status.checked) {
                     // throw '台账修订中,无法操作台账数据';
                     edit = false;
+                    revising = true;
                 }
-                const renderData = await this._getDefaultReviseInfoData(ctx, change);
-                // console.log(edit);
+                const renderData = await this._getDefaultReviseInfoData(ctx, change, edit);
                 // 台账只读、使用数据
-                // renderData.readOnly = edit;
-                renderData.readOnly = false;
+                renderData.readOnly = !edit;
+                renderData.changing = changing;
+                renderData.revising = revising;
                 await this.layout('change/revise.ejs', renderData, 'change/revise_modal.ejs');
             } catch (err) {
                 this.log(err);
@@ -1525,8 +1554,8 @@ module.exports = app => {
             }
         }
 
-        async _getDefaultReviseInfoData(ctx, change) {
-            const [ledgerSpread, posSpread] = this._getSpreadSetting(change);
+        async _getDefaultReviseInfoData(ctx, change, edit) {
+            const [ledgerSpread, posSpread] = this._getSpreadSetting(change, edit);
             const sjsRela = await this.ctx.service.project.getSjsRela(ctx.session.sessionProject.id);
             this.ctx.helper.refreshSpreadShow(sjsRela.ledgerCol, [ledgerSpread, posSpread]);
             const [stdBills, stdChapters] = await this.ctx.service.valuation.getValuationStdList(
@@ -1548,7 +1577,7 @@ module.exports = app => {
          * 获取SpreadSetting
          * @private
          */
-        _getSpreadSetting(change) {
+        _getSpreadSetting(change, edit) {
             const _ = this.app._;
             function removeFieldCols(setting, cols) {
                 _.remove(setting.cols, function(c) {
@@ -1562,7 +1591,11 @@ module.exports = app => {
             const ledger = JSON.parse(JSON.stringify(setting.ledger));
             const pos = setting.pos ? JSON.parse(JSON.stringify(setting.pos)) : spreadConst.blank;
 
-            if (change.status === audit.flow.status.checking || change.status === audit.flow.status.checked) {
+            // if (change.status === audit.flow.status.checking || change.status === audit.flow.status.checked) {
+            //     ledger.readOnly = true;
+            //     pos.readOnly = true;
+            // }
+            if (!edit) {
                 ledger.readOnly = true;
                 pos.readOnly = true;
             }
@@ -1578,10 +1611,21 @@ module.exports = app => {
         async updateRevise(ctx) {
             try {
                 if (!ctx.tender.data) throw '标段数据错误';
+                if (!ctx.session.sessionProject.page_show.openChangeRevise) {
+                    throw '该功能已关闭';
+                }
                 const data = JSON.parse(ctx.request.body.data);
                 if (!data.postType || !data.postData) throw '数据错误';
                 const responseData = { err: 0, msg: '', data: {} };
-                console.log(data);
+                const change = ctx.change;
+                if (change.status !== audit.flow.status.uncheck && change.status !== audit.flow.status.back && change.status !== audit.flow.status.revise) {
+                    throw '该变更令正在审批中或已完成,无法操作新增部位数据';
+                }
+                // 判断是否在修订中,是则无法操作本页
+                const lastRevise = await ctx.service.ledgerRevise.getLastestRevise(ctx.tender.id);
+                if (lastRevise && lastRevise.status !== audit.revise.status.checked) {
+                    throw '台账修订中,无法操作新增部位数据';
+                }
 
                 // const revise = await this.checkRevise(ctx);
                 //

+ 14 - 5
app/controller/material_controller.js

@@ -154,7 +154,7 @@ module.exports = app => {
                 const material_highOrder = await ctx.service.material.count({
                     tid: ctx.tender.id,
                 });
-                if (materialInfo === undefined || ctx.session.sessionUser.accountId !== materialInfo.user_id || material_highOrder !== materialInfo.order) {
+                if (materialInfo === undefined || !(ctx.session.sessionUser.is_admin || ((materialInfo.status === auditConst.status.uncheck || materialInfo.status === auditConst.status.checkNo) && materialInfo.user_id === ctx.session.sessionUser.accountId)) || material_highOrder !== materialInfo.order) {
                     throw '您无权删除材料调差期';
                 }
                 const result = await ctx.service.material.deleteMaterial(material_id);
@@ -447,9 +447,7 @@ module.exports = app => {
                 if (!ctx.material.ex_calc) {
                     const calcBase = await ctx.service.stage.getMaterialCalcBase(stage_list, ctx.tender.info);
                     for (const bq of renderData.ex_calc) {
-                        const calc = _.find(calcBase, function(item) {
-                            return bq.code === item.code;
-                        });
+                        const calc = _.find(calcBase, { code: bq.code }) || _.find(calcBase, { code: 'bqwc' });
                         bq.value = calc.value;
                         // if (calc.code === 'bqwc') {
                         //     ctx.material.ex_expr = calc.value.toString() + '*[0-1]';
@@ -459,7 +457,18 @@ module.exports = app => {
                     // 并更新至调差表中
                     await ctx.service.material.update({ ex_calc: JSON.stringify(renderData.ex_calc) }, { id: ctx.material.id });
                 } else {
-                    renderData.ex_calc = JSON.parse(ctx.material.ex_calc);
+                    const ex_calc = JSON.parse(ctx.material.ex_calc);
+                    if (ctx.helper._.findIndex(ex_calc, { code: 'zdy' }) === -1) {
+                        ex_calc.push({
+                            code: 'zdy',
+                            text: '自定义金额',
+                            value: ctx.helper._.find(ex_calc, { code: 'bqwc' }).value,
+                            select: false,
+                        });
+                        // 并更新至调差表中
+                        await ctx.service.material.update({ ex_calc: JSON.stringify(ex_calc) }, { id: ctx.material.id });
+                    }
+                    renderData.ex_calc = ex_calc;
                 }
 
                 renderData.materialExponentData = await this._getMaterialExponentData(ctx);

+ 37 - 5
app/controller/revise_controller.js

@@ -50,7 +50,37 @@ module.exports = app => {
                 (!revise || !revise.valid || revise.status === audit.revise.status.checked);
             const addValid = addVisible && (!lastStage || lastStage.status === audit.stage.status.uncheck ||
                 lastStage.status === audit.stage.status.checkNo || lastStage.status === audit.stage.status.checked);
-            return [addVisible, addValid];
+            let changeValid = true;
+            // 判断变更是否存在审批中,有则可能无法新增修订
+            const changeList = await ctx.service.change.getAllDataByCondition({
+                where: {
+                    tid: ctx.tender.id,
+                    status: [audit.flow.status.checking, audit.flow.status.backnew],
+                },
+            });
+            if (changeList.length > 0) {
+                // 判断是否已存在新增部位,有则可能无法新增修订
+                const changeLedgerList = await ctx.service.changeLedger.getAllDataByCondition({ where: { tender_id: ctx.tender.id, is_leaf: 1 } });
+                const changePosList = await ctx.service.changePos.getAllDataByCondition({ where: { tid: ctx.tender.id } });
+                // 判断变更是否已添加新增部位,有则可能无法新增修订
+                if (changeLedgerList.length > 0 || changePosList.length > 0) {
+                    // 判断审批中的变更令是否有调用了新增部位,有则无法新增修订,都没有则可新增修订
+                    for (const c of changeList) {
+                        const caList = await ctx.service.changeAuditList.getList(c.cid);
+                        const gclIdList = ctx.helper._.uniq(ctx.helper._.map(caList, 'gcl_id'));
+                        for (const gcl of gclIdList) {
+                            if (gcl && (ctx.helper._.findIndex(changeLedgerList, { id: gcl }) !== -1 || ctx.helper._.findIndex(changePosList, { lid: gcl }) !== -1)) {
+                                changeValid = false;
+                                break;
+                            }
+                        }
+                        if (!changeValid) {
+                            break;
+                        }
+                    }
+                }
+            }
+            return [addVisible, addValid, changeValid];
         }
 
         /**
@@ -82,8 +112,7 @@ module.exports = app => {
                         lr.curAuditor = await ctx.service.reviseAudit.getAuditorByStatus(lr.id, lr.status, lr.times);
                     }
                 }
-                const [addVisible, addValid] = await this._getAddReviseValid(ctx);
-                console.log(addVisible, addValid);
+                const [addVisible, addValid, changeValid] = await this._getAddReviseValid(ctx);
                 const [stdBills, stdChapters] = await this.ctx.service.valuation.getValuationStdList(
                     ctx.tender.data.valuation, ctx.tender.data.measure_type);
                 const renderData = {
@@ -98,6 +127,7 @@ module.exports = app => {
                     ledgerRevise,
                     addVisible,
                     addValid,
+                    changeValid,
                     auditConst: audit.revise,
                     auditConst2: JSON.stringify(audit.revise),
                     stdBills,
@@ -151,12 +181,14 @@ module.exports = app => {
          */
         async add(ctx) {
             try {
-                const addValid = await this._getAddReviseValid(ctx);
+                const [addValid, changeValid] = await this._getAddReviseValid(ctx);
                 if (!addValid) {
                     throw '无法新增修订';
                 }
+                if (!changeValid) {
+                    throw '台账修订会影响审批中的变更令(包含新增部位),请审批完成后再创建台账修订';
+                }
                 const revise = await ctx.service.ledgerRevise.add(ctx.tender.id, ctx.session.sessionUser.accountId);
-
                 ctx.redirect('/tender/' + ctx.tender.id + '/revise/info');
             } catch (err) {
                 this.log(err);

+ 1 - 0
app/controller/stage_rela_controller.js

@@ -14,6 +14,7 @@ const measureType = require('../const/tender').measureType;
 const tenderConst = require('../const/tender');
 const sendToWormhole = require('stream-wormhole');
 const path = require('path');
+const fs = require('fs');
 
 module.exports = app => {
 

+ 1 - 1
app/extend/helper.js

@@ -750,7 +750,7 @@ module.exports = {
      * @return {*}
      */
     round(value, decimal) {
-        return value ? new Decimal(value).toDecimalPlaces(decimal).toNumber() : null;
+        return value ? new Decimal(value).toDecimalPlaces(decimal).toNumber() : 0;
     },
     /**
      * 汇总

+ 101 - 57
app/lib/analysis_excel.js

@@ -62,8 +62,20 @@ class ImportBaseTree {
      * 构造函数
      * @param {Array} tempData - 清单模板数据
      */
-    constructor (tempData, ctx) {
+    constructor (tempData, ctx, setting) {
         this.ctx = ctx;
+        if (ctx.tender) {
+            this.mid = ctx.tender.id;
+            this.decimal = ctx.tender.info.decimal;
+            this.precision = ctx.tender.info.precision;
+        } else if (ctx.budget) {
+            this.mid = ctx.budget.id;
+            this.decimal = { up: 2, tp: 2};
+            this.precision = {
+                other: { value: 2 },
+            };
+        }
+        this.setting = setting;
         // 常量
         this.splitChar = '-';
         // 索引
@@ -91,10 +103,11 @@ class ImportBaseTree {
      * @private
      */
     _loadTemplateTree(data) {
+        const self = this;
         let loadCodeNodes = true;
         for (const node of data) {
-            node.ledger_id = node.template_id;
-            node.ledger_pid = node.pid;
+            node[this.setting.kid] = node.template_id;
+            node[this.setting.pid] = node.pid;
             node.id = this.ctx.app.uuid.v4();
             delete node.pid;
             if (node.code && loadCodeNodes) {
@@ -104,19 +117,19 @@ class ImportBaseTree {
                 loadCodeNodes = false;
             }
             this.items.push(node);
-            if (node.ledger_pid === -1) {
+            if (node[this.setting.pid] === -1) {
                 this.roots.push(node);
             }
-            if (node.ledger_id >= this.keyNodeId) {
-                this.keyNodeId = node.ledger_id + 1;
+            if (node[this.setting.kid] >= this.keyNodeId) {
+                this.keyNodeId = node[this.setting.kid] + 1;
             }
             this.tempData.push(node);
-            this.nodes[node.ledger_id] = node;
+            this.nodes[node[this.setting.kid]] = node;
         }
         for (const node of this.items) {
-            node.tender_id = this.ctx.tender.id;
+            node[this.setting.mid] = this.mid;
             node.children = this.items.filter(function (i) {
-                return i.ledger_pid === node.ledger_id;
+                return i[self.setting.pid] === node[self.setting.kid];
             });
         }
     }
@@ -175,12 +188,12 @@ class ImportBaseTree {
      */
     addNodeWithParent(node, parent) {
         node.id = this.ctx.app.uuid.v4();
-        node.ledger_id = this.keyNodeId;
+        node[this.setting.kid] = this.keyNodeId;
         this.keyNodeId += 1;
-        node.ledger_pid = parent ? parent.ledger_id : -1;
-        node.level = parent ? parent.level + 1 : 1;
-        node.order = parent ? parent.children.length + 1 : this.roots.length + 1;
-        node.full_path = parent ? parent.full_path + '-' + node.ledger_id : '' + node.ledger_id;
+        node[this.setting.pid] = parent ? parent[this.setting.kid] : -1;
+        node[this.setting.level] = parent ? parent[this.setting.level] + 1 : 1;
+        node[this.setting.order] = parent ? parent.children.length + 1 : this.roots.length + 1;
+        node[this.setting.fullPath] = parent ? parent[this.setting.fullPath] + '-' + node[this.setting.kid] : '' + node[this.setting.kid];
         if (parent) {
             parent.children.push(node);
         } else {
@@ -197,9 +210,9 @@ class ImportBaseTree {
      * @param {Object} node - 当前添加的节点
      */
     defineCacheData(node) {
-        this.nodes[node.ledger_id] = node;
+        this.nodes[node[this.setting.kid]] = node;
         this.finalNode = node;
-        this.finalPrecision = this.ctx.helper.findPrecision(this.ctx.tender.info.precision, node.unit);
+        this.finalPrecision = this.ctx.helper.findPrecision(this.precision, node.unit);
         if (node.code) {
             this.finalXmjNode = node;
         }
@@ -222,7 +235,7 @@ class ImportBaseTree {
      */
     addXmjNode(node) {
         node.id = this.ctx.app.uuid.v4();
-        node.tender_id = this.ctx.tender.id;
+        node[this.setting.mid] = this.mid;
         node.children = [];
         if (node.code.split(this.splitChar).length > 1) {
             const temp = this.findTempData(node);
@@ -259,7 +272,7 @@ class ImportBaseTree {
      */
     addGclNode(node) {
         node.id = this.ctx.app.uuid.v4();
-        node.tender_id = this.ctx.tender.id;
+        node[this.setting.mid] = this.mid;
         node.pos = [];
         node.children = [];
         if (this.finalXmjNode) {
@@ -316,7 +329,7 @@ class ImportBaseTree {
             }
         });
         for (const [i, c] of firstPart.children.entries()) {
-            c.order = i + 1;
+            c[this.setting.order] = i + 1;
         }
     }
 
@@ -330,7 +343,7 @@ class ImportBaseTree {
             if (!node.pos || node.pos.length === 0) { continue; }
             node.quantity = this.ctx.helper.sum(_.map(node.pos, 'quantity'));
             if (node.quantity && node.unit_price) {
-                node.total_price = this.ctx.helper.mul(node.quantity, node.unit_price, this.ctx.tender.info.decimal.tp);
+                node.total_price = this.ctx.helper.mul(node.quantity, node.unit_price, this.decimal.tp);
             } else {
                 node.total_price = null;
             }
@@ -371,7 +384,7 @@ class ImportStd18Tree extends ImportBaseTree {
         let parent = this.cacheMainXmjNode;
         while (parent) {
             if (this._checkParent(parent, code)) return parent;
-            parent = this.nodes[parent.ledger_pid];
+            parent = this.nodes[parent[this.setting.pid]];
         }
         return null;
     }
@@ -385,7 +398,7 @@ class ImportStd18Tree extends ImportBaseTree {
         let parent = this.cacheSubXmjNode;
         while (parent && parent.is_sub && parent.code.match(subReg)) {
             if (this._checkParent(parent, code)) return parent;
-            parent = this.nodes[parent.ledger_pid];
+            parent = this.nodes[parent[this.setting.pid]];
         }
         return this.cacheMainXmjNode;
     }
@@ -442,7 +455,6 @@ class ImportStd18Tree extends ImportBaseTree {
         if (!node.code || (!node.code.match(mainReg) && !node.code.match(subReg))) return null;
 
         node.id = this.ctx.app.uuid.v4();
-        node.tender_id = this.ctx.tender.id;
         node.children = [];
         if ((specCode106.code.indexOf(node.code) >= 0)) {
             if (this.cacheSpecMainXmj2 && this.cacheSpecMainXmj2.code.match(specCode106.reg))
@@ -470,9 +482,20 @@ class AnalysisExcelTree {
     /**
      * 构造函数
      */
-    constructor(ctx) {
+    constructor(ctx, setting, needCols) {
         this.ctx = ctx;
-        this.decimal = ctx.tender.info.decimal;
+        this.setting = setting;
+        if (ctx.tender) {
+            this.mid = ctx.tender.id;
+            this.decimal = ctx.tender.info.decimal;
+            this.precision = ctx.tender.info.precision;
+        } else if (ctx.budget) {
+            this.mid = ctx.budget.id;
+            this.decimal = ctx.budget.decimal;
+            this.precision = {
+                other: { value: ctx.budget.decimal.qty },
+            };
+        }
         this.colsDef = null;
         this.colHeaderMatch = {
             code: {value: ['项目节编号', '预算项目节'], type: colDefineType.match},
@@ -484,9 +507,11 @@ class AnalysisExcelTree {
             dgn_qty1: {value: ['设计数量1'], type: colDefineType.match},
             dgn_qty2: {value: ['设计数量2'], type: colDefineType.match},
             unit_price: {value: ['单价'], type: colDefineType.match},
-            drawing_code: {value: ['图号'], type: colDefineType.match},
+            total_price: {value: ['金额', '合价'], type: colDefineType.match},
+            drawing_code: {value: ['图号', '图册号'], type: colDefineType.match},
             memo: {value: ['备注'], type: colDefineType.match},
         };
+        this.needCols = needCols || ['code', 'b_code', 'pos'];
     }
 
     _isMatch11(tempData) {
@@ -504,11 +529,11 @@ class AnalysisExcelTree {
     _getNewCacheTree(tempData) {
         // 模板符合11编办规则,使用11编办树
         if (this._isMatch18(tempData)) {
-            return new ImportStd18Tree(tempData, this.ctx);
+            return new ImportStd18Tree(tempData, this.ctx, this.setting);
         // 反之使用11编办(未校验模板是否符合,替换注释部分即可实现)
         // } else if (this._isMatch11(tempData)){
         } else {
-            return new ImportBaseTree(tempData, this.ctx);
+            return new ImportBaseTree(tempData, this.ctx, this.setting);
         }
     }
 
@@ -524,18 +549,11 @@ class AnalysisExcelTree {
             node.code = this.ctx.helper.replaceReturn(row[this.colsDef.code]);
             node.name = this.ctx.helper.replaceReturn(row[this.colsDef.name]);
             node.unit = this.ctx.helper.replaceReturn(row[this.colsDef.unit]);
-            const precision = this.ctx.helper.findPrecision(this.ctx.tender.info.precision, node.unit);
-            node.quantity = this.ctx.helper.round(aeUtils.toNumber(row[this.colsDef.quantity]), precision.value);
             node.dgn_qty1 = aeUtils.toNumber(row[this.colsDef.dgn_qty1]);
             node.dgn_qty2 = aeUtils.toNumber(row[this.colsDef.dgn_qty2]);
-            node.unit_price = this.ctx.helper.round(aeUtils.toNumber(row[this.colsDef.unit_price]), this.decimal.up);
+            node.total_price = this.ctx.helper.round(aeUtils.toNumber(row[this.colsDef.total_price]), this.decimal.tp);
             node.drawing_code = this.ctx.helper.replaceReturn(row[this.colsDef.drawing_code]);
             node.memo = this.ctx.helper.replaceReturn(row[this.colsDef.memo]);
-            if (node.quantity && node.unit_price) {
-                node.total_price = this.ctx.helper.mul(node.quantity, node.unit_price, this.ctx.tender.info.decimal.tp);
-            } else {
-                node.total_price = null;
-            }
             return this.cacheTree.addXmjNode(node);
         } catch (error) {
             console.log(error);
@@ -559,24 +577,23 @@ class AnalysisExcelTree {
      * @private
      */
     _loadGclNode(row) {
+        if (this.filter.filterGcl) return true;
         const node = {};
         node.b_code = this.ctx.helper.replaceReturn(row[this.colsDef.b_code]);
         node.name = this.ctx.helper.replaceReturn(row[this.colsDef.name]);
         node.unit = this.ctx.helper.replaceReturn(row[this.colsDef.unit]);
-        const precision = this.ctx.helper.findPrecision(this.ctx.tender.info.precision, node.unit);
+        const precision = this.ctx.helper.findPrecision(this.precision, node.unit);
         node.quantity = this.ctx.helper.round(aeUtils.toNumber(row[this.colsDef.quantity]), precision.value);
         node.sgfh_qty = node.quantity;
         node.unit_price = this.ctx.helper.round(aeUtils.toNumber(row[this.colsDef.unit_price]), this.decimal.up);
         node.drawing_code = this.ctx.helper.replaceReturn(row[this.colsDef.drawing_code]);
         node.memo = this.ctx.helper.replaceReturn(row[this.colsDef.memo]);
         if (node.quantity && node.unit_price) {
-            node.total_price = this.ctx.helper.mul(node.quantity, node.unit_price, this.ctx.tender.info.decimal.tp);
+            node.total_price = this.ctx.helper.mul(node.quantity, node.unit_price, this.decimal.tp);
         } else {
             node.total_price = null;
         }
-        if (this.filter && !node.quantity && !node.total_price) {
-            return this.filter;
-        }
+        if (this.filter.filterZeroGcl && !node.quantity && !node.total_price) return true;
         return this.cacheTree.addGclNode(node);
     }
     /**
@@ -586,6 +603,7 @@ class AnalysisExcelTree {
      * @private
      */
     _loadPos(row) {
+        if (this.filter.filterPos) return true;
         const pos = {};
         pos.name = this.ctx.helper.replaceReturn(row[this.colsDef.name]);
         pos.quantity = aeUtils.toNumber(row[this.colsDef.quantity]);
@@ -629,9 +647,11 @@ class AnalysisExcelTree {
      */
     checkColHeader(row) {
         const colsDef = aeUtils.checkColHeader(row, this.colHeaderMatch);
-        if (colsDef.code && colsDef.b_code && colsDef.pos) {
-            this.colsDef = colsDef;
+        let check = true;
+        for (const col of this.needCols) {
+            if (!colsDef[col]) check = false;
         }
+        if (check) this.colsDef = colsDef;
     }
 
     /**
@@ -641,7 +661,7 @@ class AnalysisExcelTree {
      * @returns {ImportBaseTree}
      */
     analysisData(sheet, tempData, filter) {
-        this.filter = filter;
+        this.filter = filter ? filter : {};
         this.colsDef = null;
         this.cacheTree = this._getNewCacheTree(tempData);
         this.errorData = [];
@@ -656,7 +676,7 @@ class AnalysisExcelTree {
             }
         }
         this.cacheTree.resortFirstPartChildren();
-        this.cacheTree.calculateLeafWithPos();
+        if (!this.filter.filterCalc) this.cacheTree.calculateLeafWithPos();
         return this.cacheTree;
     }
 }
@@ -666,8 +686,20 @@ class ImportGclBaseTree {
      * 构造函数
      * @param {Array} tempData - 清单模板数据
      */
-    constructor (ctx, parent, maxId, defaultData) {
+    constructor (ctx, setting, parent, maxId, defaultData) {
         this.ctx = ctx;
+        this.setting = setting;
+        if (ctx.tender) {
+            this.mid = ctx.tender.id;
+            this.decimal = ctx.tender.info.decimal;
+            this.precision = ctx.tender.info.precision;
+        } else if (ctx.budget) {
+            this.mid = ctx.budget.id;
+            this.decimal = { up: 2, tp: 2};
+            this.precision = {
+                other: { value: 2 },
+            };
+        }
         this.parent = parent;
         this.defaultData = defaultData;
         // 常量
@@ -706,13 +738,13 @@ class ImportGclBaseTree {
         if (!parent.children) parent.children = [];
 
         node.id = this.ctx.app.uuid.v4();
-        node.tender_id = this.ctx.tender.id;
-        node.ledger_id = this.keyNodeId;
+        node[this.setting.mid] = this.mid;
+        node[this.setting.kid] = this.keyNodeId;
         this.keyNodeId += 1;
-        node.ledger_pid = parent.ledger_id;
-        node.level = parent.level + 1;
-        node.order = parent.children.length + 1;
-        node.full_path = parent.full_path + '-' + node.ledger_id;
+        node[this.setting.pid] = parent[this.setting.kid];
+        node[this.setting.level] = parent[this.setting.level] + 1;
+        node[this.setting.order] = parent.children.length + 1;
+        node[this.setting.fullPath] = parent[this.setting.fullPath] + '-' + node[this.setting.kid];
         parent.children.push(node);
         node.children = [];
         if (this.defaultData) _.assignIn(node, this.defaultData);
@@ -746,8 +778,20 @@ class AnalysisGclExcelTree {
     /**
      * 构造函数
      */
-    constructor(ctx) {
+    constructor(ctx, setting) {
         this.ctx = ctx;
+        this.setting = setting;
+        if (ctx.tender) {
+            this.mid = ctx.tender.id;
+            this.decimal = ctx.tender.info.decimal;
+            this.precision = ctx.tender.info.precision;
+        } else if (ctx.budget) {
+            this.mid = ctx.budget.id;
+            this.decimal = { up: 2, tp: 2};
+            this.precision = {
+                other: { value: 2 },
+            };
+        }
         this.colsDef = null;
         this.colHeaderMatch = {
             b_code: {value: ['编号', '清单编号', '子目号', '子目编号', '清单号'], type: colDefineType.match},
@@ -776,11 +820,11 @@ class AnalysisGclExcelTree {
         if ((_.isNil(node.b_code) || node.b_code === '') && (_.isNil(node.name) || node.name === '')) return node;
 
         node.unit = this.ctx.helper.replaceReturn(row[this.colsDef.unit]);
-        const precision = this.ctx.helper.findPrecision(this.ctx.tender.info.precision, node.unit);
+        const precision = this.ctx.helper.findPrecision(this.precision, node.unit);
         node.quantity = this.ctx.helper.round(aeUtils.toNumber(row[this.colsDef.quantity]), precision.value);
-        node.unit_price = this.ctx.helper.round(aeUtils.toNumber(row[this.colsDef.unit_price]), this.ctx.tender.info.decimal.up);
+        node.unit_price = this.ctx.helper.round(aeUtils.toNumber(row[this.colsDef.unit_price]), this.decimal.up);
         if (node.quantity && node.unit_price) {
-            node.total_price = this.ctx.helper.mul(node.quantity, node.unit_price, this.ctx.tender.info.decimal.tp);
+            node.total_price = this.ctx.helper.mul(node.quantity, node.unit_price, this.decimal.tp);
         } else {
             node.total_price = null;
         }
@@ -796,7 +840,7 @@ class AnalysisGclExcelTree {
     analysisData(sheet, parent, maxId, defaultData) {
         try {
             this.colsDef = null;
-            this.cacheTree = new ImportGclBaseTree(this.ctx, parent, maxId, defaultData);
+            this.cacheTree = new ImportGclBaseTree(this.ctx, this.setting, parent, maxId, defaultData);
             this.errorData = [];
             this.loadEnd = false;
             // 识别表头导入

+ 2 - 2
app/lib/bills_pos_convert.js

@@ -69,7 +69,7 @@ class BillsPosConvert {
 
     _convertXmj(data) {
         const xmj = this.resultTree.addData(data, ['ledger_id', 'ledger_pid', 'order', 'level', 'tender_id', 'full_path',
-            'code', 'name', 'unit', 'dgn_qty1', 'dgn_qty2', 'drawing_code', 'postil', 'memo', 'gxby_status', 'dagl_status', 'dagl_url',
+            'code', 'name', 'unit', 'dgn_qty1', 'dgn_qty2', 'drawing_code', 'postil', 'memo', 'gxby_status', 'dagl_status', 'gxby_url', 'dagl_url',
             'ex_memo1', 'ex_memo2', 'ex_memo3']);
         return xmj;
     }
@@ -126,7 +126,7 @@ class BillsPosConvert {
             for (const p of pos) {
                 const posUnit = xmj.unitTree.addNode({pos_name: p.name,
                     b_code: null, name: '', unit: null, unit_price: null,
-                    gxby_status: p.gxby_status, dagl_status: p.dagl_status, dagl_url: p.dagl_url});
+                    gxby_status: p.gxby_status, dagl_status: p.dagl_status, gxby_url: p.gxby_url, dagl_url: p.dagl_url});
                 if (p.drawing_code && posUnit.drawing_code.indexOf(p.drawing_code) < 0)
                     posUnit.drawing_code.push(p.drawing_code);
                 if (p.memo) posUnit.memo.push(p.memo);

+ 3 - 3
app/lib/stage_im.js

@@ -329,7 +329,7 @@ class StageIm {
                         (!im.code || im.code === d.code) &&
                         (!im.name || im.name === d.name) &&
                         (!im.unit || im.unit === d.unit) &&
-                        self.ctx.helper.checkZero(self.ctx.helper.sub(im[up_field], d[up_field])) &&
+                        self.ctx.helper.checkZero(self.ctx.helper.sub(im[self.up_field], d[self.up_field])) &&
                         (!im.pid || im.pid === d.pid) &&
                         (!im.pos_name || im.pos_name === d.pos_name);
                 });
@@ -340,7 +340,7 @@ class StageIm {
                         (!im.code || im.code === d.code) &&
                         (!im.name || im.name === d.name) &&
                         (!im.unit || im.unit === d.unit) &&
-                        self.ctx.helper.checkZero(self.ctx.helper.sub(im[up_field], d[up_field])) &&
+                        self.ctx.helper.checkZero(self.ctx.helper.sub(im[self.up_field], d[self.up_field])) &&
                         (!im.pid || im.pid === d.pid) &&
                         (!im.pos_name || im.pos_name === d.pos_name);
                 });
@@ -839,7 +839,7 @@ class StageIm {
             let im = nodeImData.find(function(d) {
                 return d.lid === node.id &&
                     d.code === p.b_code && p.name === d.name && p.unit === d.unit &&
-                    self.ctx.helper.checkZero(self.ctx.helper.sub(p[up_field], d[up_field]));
+                    self.ctx.helper.checkZero(self.ctx.helper.sub(p[self.up_field], d[self.up_field]));
             });
             if (!im) {
                 const peg = this._getPegNode(node);

+ 45 - 0
app/middleware/budget_check.js

@@ -0,0 +1,45 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date
+ * @version
+ */
+
+module.exports = options => {
+    /**
+     * 标段校验 中间件
+     * 1. 读取标段数据(包括属性)
+     * 2. 检验用户是否可见标段(不校验具体权限)
+     *
+     * @param {function} next - 中间件继续执行的方法
+     * @return {void}
+     */
+    return function* budgetCheck(next) {
+        try {
+            // 读取标段数据
+            const id = parseInt(this.params.id);
+            if (!id) throw '参数错误';
+            this.budget = yield this.service.budget.getCurBudget(id);
+            if (!this.budget) throw '项目不存在';
+            if (this.session.sessionUser.is_admin) {
+                this.budget.readOnly = false;
+            } else {
+                const bp = yield this.service.budgetPermission.getBudgetUserPermission(id);
+                if (!bp) throw '您无权查看该项目';
+                this.budget.readOnly = bp.permission.indexOf(this.service.budgetPermission.PermissionConst.edit.value) < 0;
+            }
+            yield next;
+        } catch (err) {
+            this.log(err);
+            if (this.helper.isAjax(this.request)) {
+                this.ajaxErrorBody(err, '概算投资项目未知错误');
+            } else {
+                this.postError(err, '概算投资项目未知错误');
+                this.redirect(this.request.headers.referer);
+            }
+        }
+    };
+};

+ 2 - 0
app/middleware/material_check.js

@@ -9,6 +9,7 @@
 
 const status = require('../const/audit').material.status;
 const shenpiConst = require('../const/shenpi');
+const materialConst = require('../const/material');
 const _ = require('lodash');
 
 module.exports = options => {
@@ -116,6 +117,7 @@ module.exports = options => {
             });
             // 调差的readOnly 指表格和页面只能看不能改,和审批无关
             material.readOnly = !((material.status === status.uncheck || material.status === status.checkNo) && accountId === material.user_id);
+            material.decimal = material.decimal ? JSON.parse(material.decimal) : materialConst.decimal;
             this.material = material;
             // 根据状态判断是否需要更新审批人列表
             if ((material.status === status.uncheck || material.status === status.checkNo) && this.tender.info.shenpi.material !== shenpiConst.sp_status.sqspr) {

+ 5 - 0
app/middleware/session_auth.js

@@ -55,6 +55,11 @@ module.exports = options => {
                 }
             }
             this.session.sessionProject.showDataCollect = showDataCollect;
+            if (sessionUser.is_admin) {
+                this.session.sessionProject.showBudget = true;
+            } else {
+                this.session.sessionProject.showBudget = yield this.service.budgetPermission.showBudget(sessionUser.accountId);
+            }
 
             // 同步消息
             yield this.service.notify.syncNotifyData();

二进制
app/public/css/bg/bg_01.png


二进制
app/public/css/bg/bg_02.png


二进制
app/public/css/bg/bg_03.png


二进制
app/public/css/bg/bg_04.png


二进制
app/public/css/bg/bg_05.png


二进制
app/public/css/bg/bg_06.png


二进制
app/public/css/image_nav_logo.png


+ 94 - 0
app/public/css/main.css

@@ -1593,4 +1593,98 @@ overflow-y: auto;
 }
 .left-big-chart-content,.right-big-chart-content{
   height: 83%;
+}
+.login-new-body{
+  background:#192948 url(bg/bg_06.png) no-repeat;
+  animation: change 60s steps(1) infinite;
+}
+@keyframes change {
+  0% {
+  background-image: url(bg/bg_06.png);
+  }
+  16% {
+  background-image: url(bg/bg_01.png);
+  }
+  32% {
+  background-image: url(bg/bg_02.png)
+  }
+  48% {
+  background-image: url(bg/bg_03.png)
+  }
+  64% {
+  background-image: url(bg/bg_04.png)
+  }
+  80% {
+  background-image: url(bg/bg_05.png)
+  }
+}
+.login-new-b{
+  border-right: 2px solid rgba(255, 255, 255, 0.6);
+}
+.logo-big-title{
+  font-size: 20px;
+}
+.logo-sm-title{
+  font-size: 14px;
+  color: rgba(255, 255, 255, 0.8);
+}
+.side-border{
+  width: 2px;
+  height: 24px;
+  background: rgba(255, 255, 255, 0.6);
+}
+.top-subtitle{
+  font-size: 20px;
+  font-weight: 500;
+  color: rgb(255, 255, 255);
+}
+.login-new-body .container{
+  width: 936px;
+  margin: 0 auto;
+}
+.login-new-body .content-center{
+  position: absolute;
+  top: 50%;
+  left: 50%;
+  transform: translate(-50%, -45%);
+}
+.left-login{
+  width: 476px;
+  height: 513px;
+  border-radius: 8px;
+  background: rgba(51, 119, 255, 0.9);
+  box-shadow: 6px 0px 6px rgba(0, 0, 0, 0.16);
+}
+.right-login{
+  width: 460px;
+  height: 465px;
+  border-radius: 0 8px 8px 0;
+  background: rgba(255, 255, 255, 1);
+}
+.right-login .position-absolute{
+  right: 12px;
+  top: 12px;
+}
+.left-login-title{
+  font-size: 24px;
+  line-height: 48px;
+}
+.login-border{
+  width: 48px;
+  height: 4px;
+  background: rgba(255, 255, 255, 1);
+}
+.erweima img{
+  width: 240px;
+  height: 240px;
+  padding: 12px;
+  background: #fff;
+  border: 1px solid rgba(0, 0, 0, 0.12);
+  border-radius: 4px;
+}
+.right-login .bottom-text{
+  padding-top: 5px;
+  font-size: 14px;
+  line-height: 18px;
+  color: rgba(0, 0, 0, 0.6);
 }

二进制
app/public/images/icon_login_folder.png


二进制
app/public/images/icon_login_lock.png


二进制
app/public/images/icon_login_pc.png


二进制
app/public/images/icon_login_qr.png


二进制
app/public/images/icon_login_user.png


二进制
app/public/images/image_nav_logo.png


+ 16 - 1
app/public/js/advance.js

@@ -32,7 +32,22 @@ $(document).ready(function () {
     //     })
     //     return false
     // })
-
+    $.subMenu({
+        menu: '#sub-menu', miniMenu: '#sub-mini-menu', miniMenuList: '#mini-menu-list',
+        toMenu: '#to-menu', toMiniMenu: '#to-mini-menu',
+        key: 'menu.1.0.0',
+        miniHint: '#sub-mini-hint', hintKey: 'menu.hint.1.0.1',
+        callback: function (info) {
+            if (info.mini) {
+                $('.panel-title').addClass('fluid');
+                $('#sub-menu').removeClass('panel-sidebar');
+            } else {
+                $('.panel-title').removeClass('fluid');
+                $('#sub-menu').addClass('panel-sidebar');
+            }
+            autoFlashHeight();
+        }
+    });
     $('.ml-auto').submit(function () {
         if (!advancePayTotal) {
             $('#erro').modal('show');

+ 18 - 1
app/public/js/advance_audit.js

@@ -9,7 +9,24 @@
  */
 
 $(document).ready(function () {
-    autoFlashHeight()
+    autoFlashHeight();
+
+    $.subMenu({
+        menu: '#sub-menu', miniMenu: '#sub-mini-menu', miniMenuList: '#mini-menu-list',
+        toMenu: '#to-menu', toMiniMenu: '#to-mini-menu',
+        key: 'menu.1.0.0',
+        miniHint: '#sub-mini-hint', hintKey: 'menu.hint.1.0.1',
+        callback: function (info) {
+            if (info.mini) {
+                $('.panel-title').addClass('fluid');
+                $('#sub-menu').removeClass('panel-sidebar');
+            } else {
+                $('.panel-title').removeClass('fluid');
+                $('#sub-menu').addClass('panel-sidebar');
+            }
+            autoFlashHeight();
+        }
+    });
 
     const decimal = 2
     let oldVal = null

+ 241 - 0
app/public/js/budget_compare.js

@@ -0,0 +1,241 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date
+ * @version
+ */
+
+$(document).ready(() => {
+    autoFlashHeight();
+    const compareSpread = SpreadJsObj.createNewSpread($('#cost-compare')[0]);
+    const compareSheet = compareSpread.getActiveSheet();
+
+    const spreadSetting = {
+        cols: [
+            {title: '项目节编号', colSpan: '1', rowSpan: '2', field: 'code', hAlign: 0, width: 150, formatter: '@', cellType: 'tree'},
+            {title: '名称', colSpan: '1', rowSpan: '2', field: 'name', hAlign: 0, width: 230, formatter: '@'},
+            {title: '单位', colSpan: '1', rowSpan: '2', field: 'unit', hAlign: 1, width: 50, formatter: '@', cellType: 'unit'},
+            {title: '投资估算|数量1/数量2', colSpan: '3|1', rowSpan: '1|1', field: 'gu_dgn_qty', hAlign: 2, width: 80},
+            {title: '|经济指标', colSpan: '|1', rowSpan: '|1', field: 'gu_dgn_price', hAlign: 2, width: 80, type: 'Number'},
+            {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'gu_tp', hAlign: 2, width: 80, type: 'Number'},
+            {title: '初步概算|数量1/数量2', colSpan: '3|1', rowSpan: '1|1', field: 'gai_dgn_qty', hAlign: 2, width: 80},
+            {title: '|经济指标', colSpan: '|1', rowSpan: '|1', field: 'gai_dgn_price', hAlign: 2, width: 80, type: 'Number'},
+            {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'gai_tp', hAlign: 2, width: 80, type: 'Number'},
+            {title: '施工图预算|数量1/数量2', colSpan: '3|1', rowSpan: '1|1', field: 'yu_dgn_qty', hAlign: 2, width: 80},
+            {title: '|经济指标', colSpan: '|1', rowSpan: '|1', field: 'yu_dgn_price', hAlign: 2, width: 80, type: 'Number'},
+            {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'yu_tp', hAlign: 2, width: 80, type: 'Number'},
+        ],
+        emptyRows: 0,
+        headRows: 2,
+        headRowHeight: [25, 25],
+        defaultRowHeight: 21,
+        headerFont: '12px 微软雅黑',
+        font: '12px 微软雅黑',
+        readOnly: true,
+    };
+    sjsSettingObj.setFxTreeStyle(spreadSetting, sjsSettingObj.FxTreeStyle.jz);
+    SpreadJsObj.initSheet(compareSheet, spreadSetting);
+
+    const compareTree = createNewPathTree('final', {
+        id: 'id',
+        pid: 'pid',
+        order: 'order',
+        level: 'level',
+        rootId: -1,
+    });
+
+
+    function compareCode(str1, str2, symbol = '-') {
+        if (!str1) {
+            return 1;
+        } else if (!str2) {
+            return -1;
+        }
+
+        function compareSubCode(code1, code2) {
+            if (numReg.test(code1)) {
+                if (numReg.test(code2)) {
+                    return parseInt(code1) - parseInt(code2);
+                } else {
+                    return -1
+                }
+            } else {
+                if (numReg.test(code2)) {
+                    return 1;
+                } else {
+                    return code1 === code2 ? 0 : (code1 < code2 ? -1 : 1); //code1.localeCompare(code2);
+                }
+            }
+        }
+        const numReg = /^[0-9]+$/;
+        const aCodes = str1.split(symbol), bCodes = str2.split(symbol);
+        for (let i = 0, iLength = Math.min(aCodes.length, bCodes.length); i < iLength; ++i) {
+            const iCompare = compareSubCode(aCodes[i], bCodes[i]);
+            if (iCompare !== 0) {
+                return iCompare;
+            }
+        }
+        return aCodes.length - bCodes.length;
+    }
+
+    postData(window.location.pathname + '/load', {}, function (result) {
+        const setting = { id: 'tree_id', pid: 'tree_pid', order: 'order', level: 'level', rootId: -1, calcFields: ['total_price'] };
+        const guTree = createNewPathTree('ledger', setting);
+        guTree.loadDatas(result.gu);
+        treeCalc.calculateAll(guTree);
+        compareTree.loadTree(guTree, function (cur, source) {
+            cur.base = true;
+            cur.gu_dgn_qty1 = ZhCalc.add(cur.gu_dgn_qty1, source.dgn_qty1);
+            cur.gu_dgn_qty2 = ZhCalc.add(cur.gu_dgn_qty2, source.dgn_qty2);
+            cur.gu_tp = ZhCalc.add(cur.gu_tp, source.total_price);
+        });
+        const gaiTree = createNewPathTree('ledger', setting);
+        gaiTree.loadDatas(result.gai);
+        treeCalc.calculateAll(gaiTree);
+        compareTree.loadTree(gaiTree, function (cur, source) {
+            cur.base = true;
+            cur.gai_dgn_qty1 = ZhCalc.add(cur.gai_dgn_qty1, source.dgn_qty1);
+            cur.gai_dgn_qty2 = ZhCalc.add(cur.gai_dgn_qty2, source.dgn_qty2);
+            cur.gai_tp = ZhCalc.add(cur.gai_tp, source.total_price);
+        });
+        const yuTree = createNewPathTree('ledger', setting);
+        yuTree.loadDatas(result.yu);
+        treeCalc.calculateAll(yuTree);
+        compareTree.loadTree(yuTree, function (cur, source) {
+            cur.base = true;
+            cur.yu_dgn_qty1 = ZhCalc.add(cur.yu_dgn_qty1, source.dgn_qty1);
+            cur.yu_dgn_qty2 = ZhCalc.add(cur.yu_dgn_qty2, source.dgn_qty2);
+            cur.yu_tp = ZhCalc.add(cur.yu_tp, source.total_price);
+        });
+        compareTree.afterLoad(node => {
+            if (node.code === '1')console.log(node);
+            node.gu_dgn_price = ZhCalc.div(node.gu_tp, node.gu_dgn_qty1, 2);
+            node.gu_dgn_qty = node.gu_dgn_qty1
+                ? (node.gu_dgn_qty2 ? node.gu_dgn_qty1 + '/' + node.gu_dgn_qty2 : node.gu_dgn_qty1)
+                : (node.gu_dgn_qty2 ? '/' + node.gu_dgn_qty2 : '');
+            node.gai_dgn_price = ZhCalc.div(node.gai_tp, node.gai_dgn_qty1, 2);
+            node.gai_dgn_qty = node.gai_dgn_qty1
+                ? (node.gai_dgn_qty2 ? node.gai_dgn_qty1 + '/' + node.gai_dgn_qty2 : node.gai_dgn_qty1)
+                : (node.gai_dgn_qty2 ? '/' + node.gai_dgn_qty2 : '');
+            node.yu_dgn_price = ZhCalc.div(node.yu_tp, node.yu_dgn_qty1, 2);
+            node.yu_dgn_qty = node.yu_dgn_qty1
+                ? (node.yu_dgn_qty2 ? node.yu_dgn_qty1 + '/' + node.yu_dgn_qty2 : node.yu_dgn_qty1)
+                : (node.yu_dgn_qty2 ? '/' + node.yu_dgn_qty2 : '');
+        });
+        compareTree.resortChildrenByCustom(function (x, y) {
+            const iCode = compareCode(x.code, y.code);
+            if (iCode) return iCode;
+            if (!x.name) return -1;
+            if (!y.name) return 1;
+            return x.name.localeCompare(y.name)
+        });
+        SpreadJsObj.loadSheetData(compareSheet, SpreadJsObj.DataType.Tree, compareTree);
+    });
+
+    $.subMenu({
+        menu: '#sub-menu', miniMenu: '#sub-mini-menu', miniMenuList: '#mini-menu-list',
+        toMenu: '#to-menu', toMiniMenu: '#to-mini-menu',
+        key: 'menu.1.0.0',
+        miniHint: '#sub-mini-hint', hintKey: 'menu.hint.1.0.1',
+        callback: function (info) {
+            if (info.mini) {
+                $('.panel-title').addClass('fluid');
+                $('#sub-menu').removeClass('panel-sidebar');
+            } else {
+                $('.panel-title').removeClass('fluid');
+                $('#sub-menu').addClass('panel-sidebar');
+            }
+            autoFlashHeight();
+            compareSpread.refresh();
+        }
+    });
+
+    $('#sf-select-all').click(function() {
+        $('[name=sf-tender]').prop("checked", this.checked);
+    });
+    $('#select-final-ok').click(() => {
+        const rela = [];
+        const select = $('[name=sf-tender]:checked');
+        for (const s of select) {
+            rela.push(parseInt(s.getAttribute('tid')));
+        }
+        if (rela.length === 0) return;
+        postData(window.location.pathname + '/final', {id: rela}, function(result) {
+            if (spreadSetting.cols.length < 13) {
+                spreadSetting.cols.push(...[
+                    {title: '台账|数量1/数量2', colSpan: '3|1', rowSpan: '1|1', field: 'dgn_qty', hAlign: 2, width: 80},
+                    {title: '|经济指标', colSpan: '|1', rowSpan: '|1', field: 'dgn_price', hAlign: 2, width: 80, type: 'Number'},
+                    {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'total_price', hAlign: 2, width: 80, type: 'Number'},
+                    {title: '决算|数量1/数量2', colSpan: '3|1', rowSpan: '1|1', field: 'final_dgn_qty', hAlign: 2, width: 80},
+                    {title: '|经济指标', colSpan: '|1', rowSpan: '|1', field: 'final_dgn_price', hAlign: 2, width: 80, type: 'Number'},
+                    {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'final_tp', hAlign: 2, width: 80, type: 'Number'},
+                    {title: '增幅%|数量1/数量2', colSpan: '2|1', rowSpan: '1|1', field: 'grow_dgn_qty', hAlign: 2, width: 80},
+                    {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'grow_tp', hAlign: 2, width: 80, type: 'Number'},
+                ]);
+            }
+            const setting = { id: 'ledger_id', pid: 'ledger_pid', order: 'order', level: 'level', rootId: -1, calcFields: ['total_price', 'end_gather_tp'] };
+            compareTree.clearFinal();
+            for (const r of result) {
+                const tree = createNewPathTree('ledger', setting);
+                tree.loadDatas(r);
+                treeCalc.calculateAll(tree);
+                compareTree.loadTree(tree, function (cur, source) {
+                    cur.total_price = ZhCalc.add(cur.total_price, source.total_price);
+                    cur.dgn_qty1 = ZhCalc.add(cur.dgn_qty1, source.dgn_qty1);
+                    cur.dgn_qty2 = ZhCalc.add(cur.dgn_qty2, source.dgn_qty2);
+                    cur.final_dgn_qty1 = ZhCalc.sum([cur.final_dgn_qty1, source.deal_dgn_qty1, source.c_dgn_qty1]);
+                    cur.final_dgn_qty2 = ZhCalc.sum([cur.final_dgn_qty2, source.deal_dgn_qty2, source.c_dgn_qty2]);
+                    cur.final_tp = ZhCalc.add(cur.final_tp, source.end_gather_tp);
+                });
+            }
+            compareTree.afterLoad(node => {
+                node.dgn_price = ZhCalc.div(node.total_price, node.dgn_qty1, 2);
+                node.dgn_qty = node.dgn_qty1
+                    ? (node.dgn_qty2 ? node.dgn_qty1 + '/' + node.dgn_qty2 : node.dgn_qty1)
+                    : (node.dgn_qty2 ? '/' + node.dgn_qty2 : '');
+                node.final_dgn_price = ZhCalc.div(node.final_tp, node.final_dgn_qty1, 2);
+                node.final_dgn_qty = node.final_dgn_qty1
+                    ? (node.final_dgn_qty2 ? node.final_dgn_qty1 + '/' + node.final_dgn_qty2 : node.final_dgn_qty1)
+                    : (node.final_dgn_qty2 ? '/' + node.final_dgn_qty2 : '');
+                node.grow_dgn_qty1 = ZhCalc.mul(ZhCalc.div(ZhCalc.sub(node.final_dgn_qty1, node.gai_dgn_qty1), node.gai_dgn_qty1, 4), 100);
+                node.grow_dgn_qty2 = ZhCalc.mul(ZhCalc.div(ZhCalc.sub(node.final_dgn_qty2, node.gai_dgn_qty2), node.gai_dgn_qty2, 4), 100);
+                node.grow_dgn_qty = node.grow_dgn_qty1
+                    ? (node.grow_dgn_qty2 ? node.grow_dgn_qty1 + '/' + node.grow_dgn_qty2 : node.grow_dgn_qty1)
+                    : (node.grow_dgn_qty2 ? '/' + node.grow_dgn_qty2 : '');
+                node.grow_tp = ZhCalc.mul(ZhCalc.div(ZhCalc.sub(node.final_tp, node.gai_tp), node.gai_tp, 4), 100);
+            });
+            SpreadJsObj.reLoadSheetHeader(compareSheet);
+            SpreadJsObj.reLoadSheetData(compareSheet);
+            $('#select-final').modal('hide');
+        });
+    });
+    // 显示层次
+    (function (select, sheet) {
+        $(select).click(function () {
+            if (!sheet.zh_tree) return;
+            const tag = $(this).attr('tag');
+            const tree = sheet.zh_tree;
+            setTimeout(() => {
+                showWaitingView();
+                switch (tag) {
+                    case "1":
+                    case "2":
+                    case "3":
+                    case "4":
+                    case "5":
+                        tree.expandByLevel(parseInt(tag));
+                        SpreadJsObj.refreshTreeRowVisible(sheet);
+                        break;
+                    case "last":
+                        tree.expandByCustom(() => { return true; });
+                        SpreadJsObj.refreshTreeRowVisible(sheet);
+                        break;
+                }
+                closeWaitingView();
+            }, 100);
+        });
+    })('a[name=showLevel]', compareSheet);
+});

+ 832 - 0
app/public/js/budget_detail.js

@@ -0,0 +1,832 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date
+ * @version
+ */
+
+// 根据Min Max限制Input输入
+function limitInputMinMax (obj) {
+    if (_.toNumber(obj.value) > _.toNumber(obj.max)) {
+        obj.value = obj.max;
+    }
+    if(_.toNumber(obj.value) < _.toNumber(obj.min)) {
+        obj.value = obj.min;
+    }
+}
+// 根据Maxlength限制input输入
+function limitMaxLength (obj) {
+    if (obj.value.length > obj.maxLength) {
+        obj.value = obj.value.substr(0, obj.maxLength);
+    }
+}
+// 根据正则限制输入
+function limitReg(obj, reg) {
+    obj.value = obj.value.replace(reg, '');
+}
+// 小数位数 input 输入限制
+function limitDecimal(obj) {
+    limitReg(obj, /[^\d]/g);
+    limitMaxLength(obj);
+    limitInputMinMax(obj);
+}
+
+const invalidFields = {
+    parent: ['quantity', 'total_price', 'unit_price'],
+    gcl: ['dgn_qty1', 'dgn_qty2', 'total_price'],
+    xmj: ['quantity', 'unit_price'],
+};
+$(document).ready(() => {
+    const copyBlockTag = 'zh.calc.copyBlock';
+    if (needGcl) invalidFields.parent.push('quantity');
+    let stdXmj, stdGcl, searchBudget;
+    autoFlashHeight();
+    const budgetSpread = SpreadJsObj.createNewSpread($('#budget-spread')[0]);
+    const budgetSheet = budgetSpread.getActiveSheet();
+    sjsSettingObj.setFxTreeStyle(spreadSetting, sjsSettingObj.FxTreeStyle.jz);
+    spreadSetting.readOnly = readOnly;
+    // if (thousandth) sjsSettingObj.setTpThousandthFormat(spreadSetting);
+    SpreadJsObj.initSheet(budgetSheet, spreadSetting);
+
+    $.subMenu({
+        menu: '#sub-menu', miniMenu: '#sub-mini-menu', miniMenuList: '#mini-menu-list',
+        toMenu: '#to-menu', toMiniMenu: '#to-mini-menu',
+        key: 'menu.1.0.0',
+        miniHint: '#sub-mini-hint', hintKey: 'menu.hint.1.0.1',
+        callback: function (info) {
+            if (info.mini) {
+                $('.panel-title').addClass('fluid');
+                $('#sub-menu').removeClass('panel-sidebar');
+            } else {
+                $('.panel-title').removeClass('fluid');
+                $('#sub-menu').addClass('panel-sidebar');
+            }
+            autoFlashHeight();
+            budgetSpread.refresh();
+        }
+    });
+    const budgetTree = createNewPathTree('ledger', {
+        id: 'tree_id',
+        pid: 'tree_pid',
+        order: 'order',
+        level: 'level',
+        rootId: -1,
+        keys: ['id', 'bid', 'tree_id'],
+        autoExpand: 3,
+        markExpandKey: 'bills-expand',
+        markExpandSubKey: window.location.pathname.split('/')[2],
+        calcFields: ['total_price'],
+        calcFun: function (node) {
+            node.dgn_price = ZhCalc.div(node.total_price, node.dgn_qty1, 2);
+        },
+    });
+
+    $.divResizer({
+        select: '#right-spr',
+        callback: function () {
+            budgetSpread.refresh();
+            if (stdXmj) stdXmj.spread.refresh();
+            if (stdGcl) stdGcl.spread.refresh();
+            if (searchBudget) searchBudget.spread.refresh();
+        }
+    });
+
+    const budgetTreeOpr = {
+        refreshTree: function (sheet, data) {
+            SpreadJsObj.massOperationSheet(sheet, function () {
+                const tree = sheet.zh_tree;
+                // 处理删除
+                if (data.delete) {
+                    data.delete.sort(function (a, b) {
+                        return b.deleteIndex - a.deleteIndex;
+                    });
+                    for (const d of data.delete) {
+                        sheet.deleteRows(d.deleteIndex, 1);
+                    }
+                }
+                // 处理新增
+                if (data.create) {
+                    const newNodes = data.create;
+                    if (newNodes) {
+                        newNodes.sort(function (a, b) {
+                            return a.index - b.index;
+                        });
+
+                        for (const node of newNodes) {
+                            sheet.addRows(node.index, 1);
+                            SpreadJsObj.reLoadRowData(sheet, tree.nodes.indexOf(node), 1);
+                        }
+                    }
+                }
+                // 处理更新
+                if (data.update) {
+                    const rows = [];
+                    for (const u of data.update) {
+                        rows.push(tree.nodes.indexOf(u));
+                    }
+                    SpreadJsObj.reLoadRowsData(sheet, rows);
+                }
+                // 处理展开
+                if (data.expand) {
+                    const expanded = [];
+                    for (const e of data.expand) {
+                        if (expanded.indexOf(e) === -1) {
+                            const posterity = tree.getPosterity(e);
+                            for (const p of posterity) {
+                                sheet.setRowVisible(tree.nodes.indexOf(p), p.visible);
+                                expanded.push(p);
+                            }
+                        }
+                    }
+                }
+            });
+        },
+        getDefaultSelectInfo: function (sheet) {
+            const tree = sheet.zh_tree;
+            if (!tree) return;
+            const sel = sheet.getSelections()[0];
+            const node = sheet.zh_tree.nodes[sel.row];
+            if (!node) return;
+            let count = 1;
+            if (sel.rowCount > 1) {
+                for (let r = 1; r < sel.rowCount; r++) {
+                    const rNode = sheet.zh_tree.nodes[sel.row + r];
+                    if (rNode.level > node.level) continue;
+                    if ((rNode.level < node.level) || (rNode.level === node.level && rNode.pid !== node.pid)) {
+                        toastr.warning('请选择同一节点下的节点,进行该操作');
+                        return;
+                    }
+                    count += 1;
+                }
+            }
+            return [tree, node, count];
+        },
+        /**
+         * 刷新顶部按钮是否可用
+         * @param sheet
+         * @param selections
+         */
+        refreshOperationValid: function (sheet, selection) {
+            const setObjEnable = function (obj, enable) {
+                if (enable) {
+                    obj.removeClass('disabled');
+                } else {
+                    obj.addClass('disabled');
+                }
+            };
+            const invalidAll = function () {
+                setObjEnable($('a[name=base-opr][type=add]'), false);
+                setObjEnable($('a[name=base-opr][type=delete]'), false);
+                setObjEnable($('a[name=base-opr][type=up-move]'), false);
+                setObjEnable($('a[name=base-opr][type=down-move]'), false);
+                setObjEnable($('a[name=base-opr][type=up-level]'), false);
+                setObjEnable($('a[name=base-opr][type=down-level]'), false);
+            };
+            const sel = selection ? selection[0] : sheet.getSelections()[0];
+            const row = sel ? sel.row : -1;
+            const tree = sheet.zh_tree;
+            if (!tree) {
+                invalidAll();
+                return;
+            }
+            const first = sheet.zh_tree.nodes[row];
+            if (!first) {
+                invalidAll();
+                return;
+            }
+            let last = first, sameParent = true;
+            if (sel.rowCount > 1 && first) {
+                for (let r = 1; r < sel.rowCount; r++) {
+                    const rNode = tree.nodes[sel.row + r];
+                    if (!rNode) {
+                        sameParent = false;
+                        break;
+                    }
+                    if (rNode.level > first.level) continue;
+                    if ((rNode.level < first.level) || (rNode.level === first.level && rNode.pid !== first.pid)) {
+                        sameParent = false;
+                        break;
+                    }
+                    last = rNode;
+                }
+            }
+            const preNode = tree.getPreSiblingNode(first);
+            const valid = !readOnly;
+
+            setObjEnable($('a[name=base-opr][type=add]'), valid && first && first.level > 1);
+            setObjEnable($('a[name=base-opr][type=delete]'), valid && first && sameParent && first.level > 1);
+            setObjEnable($('a[name=base-opr][type=up-move]'), valid && first && sameParent && first.level > 1 && preNode);
+            setObjEnable($('a[name=base-opr][type=down-move]'), valid && first && sameParent && first.level > 1 && !tree.isLastSibling(last));
+            setObjEnable($('a[name=base-opr][type=up-level]'), valid && first && sameParent && tree.getParent(first) && first.level > 2 && tree.isLastSibling(last));
+            setObjEnable($('a[name=base-opr][type=down-level]'), valid && first && sameParent && first.level > 1 && preNode);
+        },
+        selectionChanged: function (e, info) {
+            if (info.newSelections) {
+                if (!info.oldSelections || info.newSelections[0].row !== info.oldSelections[0].row) {
+                    budgetTreeOpr.refreshOperationValid(info.sheet);
+                }
+            }
+        },
+        /**
+         * 新增节点
+         * @param spread
+         */
+        baseOpr: function (sheet, type, addCount = 1) {
+            const self = this;
+            const [tree, node, count] = this.getDefaultSelectInfo(sheet);
+            if (!tree || !node || !count) return;
+
+            if (type === 'delete') {
+                deleteAfterHint(function () {
+                    postData(window.location.pathname + '/update', {
+                        postType: type,
+                        postData: { id: node.tree_id, count: type === 'add' ? addCount : count }
+                    }, function (result) {
+                        const refreshData = tree.loadPostData(result);
+                        self.refreshTree(sheet, refreshData);
+                        const sel = sheet.getSelections()[0];
+                        if (sel) {
+                            sheet.setSelection(sel.row, sel.col, 1, sel.colCount);
+                        }
+                        self.refreshOperationValid(sheet);
+                    });
+                });
+            } else {
+                postData(window.location.pathname + '/update', {
+                    postType: type,
+                    postData: { id: node.tree_id, count: type === 'add' ? addCount : count }
+                }, function (result) {
+                    const refreshData = tree.loadPostData(result);
+                    self.refreshTree(sheet, refreshData);
+                    if (['up-move', 'down-move'].indexOf(type) > -1) {
+                        const sel = sheet.getSelections()[0];
+                        if (sel) {
+                            sheet.setSelection(tree.nodes.indexOf(node), sel.col, sel.rowCount, sel.colCount);
+                            SpreadJsObj.reloadRowsBackColor(sheet, [sel.row, tree.nodes.indexOf(node)]);
+                        }
+                    } else if (type === 'add') {
+                        const sel = sheet.getSelections()[0];
+                        if (sel) {
+                            sheet.setSelection(tree.nodes.indexOf(refreshData.create[0]), sel.col, sel.rowCount, sel.colCount);
+                            SpreadJsObj.reloadRowsBackColor(sheet, [sel.row, tree.nodes.indexOf(refreshData.create[0])]);
+                        }
+                    }
+                    self.refreshOperationValid(sheet);
+                });
+            }
+        },
+        editStarting(e, info) {
+            if (!info.sheet.zh_setting || !info.sheet.zh_tree) return;
+            const col = info.sheet.zh_setting.cols[info.col];
+            const node = info.sheet.zh_tree.nodes[info.row];
+            if (!node) {
+                info.cancel = true;
+                return;
+            }
+            switch (col.field) {
+                case 'unit_price':
+                case 'quantity':
+                    info.cancel = (node.children && node.children.length > 0) || !node.b_code;
+                    break;
+                case 'total_price':
+                    info.cancel = (node.children && node.children.length > 0) || node.b_code;
+                    break;
+                case 'dgn_price':
+                    info.cance = false;
+                    break;
+                case 'dgn_qty1':
+                case 'dgn_qty2':
+                    info.cancel = !_.isEmpty(node.b_code);
+                    break;
+            }
+        },
+        /**
+         * 编辑单元格响应事件
+         * @param {Object} e
+         * @param {Object} info
+         */
+        editEnded: function (e, info) {
+            if (info.sheet.zh_setting) {
+                const col = info.sheet.zh_setting.cols[info.col];
+                const sortData = info.sheet.zh_tree.nodes;
+                const node = sortData[info.row];
+                const data = { id: node.id, bid: node.bid, tree_id: node.tree_id };
+                // 未改变值则不提交
+                const orgValue = node[col.field];
+                const newValue = trimInvalidChar(info.editingText);
+                if (orgValue == info.editingText || ((!orgValue || orgValue === '') && (newValue === ''))) return;
+
+                if (node.b_code && invalidFields.gcl.indexOf(col.field) >=0) {
+                    toastr.error('工程量清单请勿输入设计数量、金额');
+                    return;
+                }
+
+                // 获取更新数据
+                if (newValue) {
+                    if (col.type === 'Number') {
+                        const num = _.toNumber(newValue);
+                        if (_.isNaN(num)) {
+                            toastr.error('请输入数字');
+                            SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                            return;
+                        }
+                        data[col.field] = num;
+                    } else {
+                        data[col.field] = newValue;
+                    }
+                } else {
+                    data[col.field] = col.type === 'Number' ? 0 : '';
+                }
+                console.log(data);
+                // 更新至服务器
+                postData(window.location.pathname + '/update', {postType: 'update', postData: data}, function (result) {
+                    const refreshNode = budgetTree.loadPostData(result);
+                    budgetTreeOpr.refreshTree(info.sheet, refreshNode);
+                });
+            }
+        },
+        clipboardPasting: function (e, info) {
+            const tree = info.sheet.zh_tree, setting = info.sheet.zh_setting;
+            info.cancel = true;
+            if (!setting || !tree) return;
+
+            const pasteData = info.pasteData.html
+                ? SpreadJsObj.analysisPasteHtml(info.pasteData.html)
+                : (info.pasteData.text === ''
+                    ? SpreadJsObj.Clipboard.getAnalysisPasteText()
+                    : SpreadJsObj.analysisPasteText(info.pasteData.text));
+            const hint = {
+                invalidNum: {type: 'warning', msg: '粘贴的数字非法'},
+                parent: {type: 'warning', msg: `含有子项,不可粘贴${needGcl ? '数量、单价、' : ''}金额`},
+                gcl: {type: 'warning', msg: '工程量清单,不可粘贴项目节数量、金额'},
+                xmj: {type: 'warning', msg: '项目节,不可粘贴数量、单价'},
+            };
+            const datas = [], filterNodes = [];
+
+            let filterRow = 0;
+            for (let iRow = 0; iRow < info.cellRange.rowCount; iRow ++) {
+                const curRow = info.cellRange.row + iRow;
+                const node = tree.nodes[curRow];
+                if (!node) continue;
+
+                let bPaste = false;
+                const data = info.sheet.zh_tree.getNodeKeyData(node);
+                for (let iCol = 0; iCol < info.cellRange.colCount; iCol++) {
+                    const curCol = info.cellRange.col + iCol;
+                    const colSetting = info.sheet.zh_setting.cols[curCol];
+                    const value = trimInvalidChar(pasteData[iRow-filterRow][iCol]);
+                    if (node.children && node.children.length > 0 && invalidFields.parent.indexOf(colSetting.field) >= 0) {
+                        toastMessageUniq(hint.parent);
+                        continue;
+                    }
+                    if (!_.isEmpty(node.b_code) && invalidFields.gcl.indexOf(colSetting.field) >= 0) {
+                        toastMessageUniq(hint.gcl);
+                        continue;
+                    }
+                    if (_.isEmpty(node.b_code) && invalidFields.xmj.indexOf(colSetting.field) >= 0) {
+                        toastMessageUniq(hint.xmj);
+                        continue;
+                    }
+
+                    if (colSetting.type === 'Number') {
+                        if (colSetting.type === 'Number') {
+                            const num = _.toNumber(value);
+                            if (_.isNaN(num)) {
+                                toastMessageUniq(hint.invalidExpr);
+                                continue;
+                            }
+                            data[colSetting.field] = num;
+                        } else {
+                            data[colSetting.field] = value;
+                        }
+                    } else {
+                        data[colSetting.field] = value;
+                    }
+                    bPaste = true;
+                }
+                if (bPaste) {
+                    datas.push(data);
+                } else {
+                    filterNodes.push(node);
+                }
+            }
+            if (datas.length > 0) {
+                postData(window.location.pathname + '/update', {postType: 'update', postData: datas}, function (result) {
+                    const refreshNode = tree.loadPostData(result);
+                    if (refreshNode.update) refreshNode.update = refreshNode.update.concat(filterNodes);
+                    budgetTreeOpr.refreshTree(info.sheet, refreshNode);
+                }, function () {
+                    SpreadJsObj.reLoadRowData(info.sheet, info.cellRange.row, info.cellRange.rowCount);
+                });
+            } else {
+                SpreadJsObj.reLoadRowData(info.sheet, info.cellRange.row, info.cellRange.rowCount);
+            }
+        },
+        deletePress: function (sheet) {
+            const tree = sheet.zh_tree, setting = sheet.zh_setting;
+            if (!setting || !tree) return;
+
+            const sortData = sheet.zh_tree.nodes;
+            const datas = [];
+            const sel = sheet.getSelections()[0];
+            for (let iRow = sel.row; iRow < sel.row + sel.rowCount; iRow++) {
+                const node = sortData[iRow];
+                if (node) {
+                    let bDel = false;
+                    const data = tree.getNodeKeyData(node);
+                    for (let iCol = sel.col; iCol < sel.col + sel.colCount; iCol++) {
+                        const style = sheet.getStyle(iRow, iCol);
+                        if (!style.locked) {
+                            const colSetting = setting.cols[iCol];
+                            data[colSetting.field] = colSetting.type === 'Number' ? 0 : '';
+                            bDel = true;
+                        }
+                    }
+                    if (bDel) datas.push(data);
+                }
+            }
+            if (datas.length > 0) {
+                postData(window.location.pathname + '/update', {postType: 'update', postData: datas}, function (result) {
+                    const refreshNode = tree.loadPostData(result);
+                    budgetTreeOpr.refreshTree(sheet, refreshNode);
+                });
+            }
+        },
+        pasteBlock: function (sheet, copyInfo) {
+            const self = this;
+            const [tree, node] = this.getDefaultSelectInfo(sheet);
+
+            postData(window.location.pathname + '/update', {
+                postType: 'paste-block',
+                postData: {
+                    id: tree.getNodeKey(node),
+                    bid: copyInfo.bid,
+                    block: copyInfo.block,
+                }
+            }, function (data) {
+                const result = tree.loadPostData(data);
+                self.refreshTree(sheet, result);
+                const sel = sheet.getSelections()[0];
+                if (sel) {
+                    sheet.setSelection(tree.nodes.indexOf(result.create[0]), sel.col, sel.rowCount, sel.colCount);
+                    SpreadJsObj.reloadRowsBackColor(sheet, [sel.row, tree.nodes.indexOf(result.create[0])]);
+                }
+                self.refreshOperationValid(sheet);
+                removeLocalCache(copyBlockTag);
+            }, null, true);
+        }
+    };
+    budgetSpread.bind(spreadNS.Events.SelectionChanged, budgetTreeOpr.selectionChanged);
+    if (!readOnly) {
+        // 增删上下移升降级
+        $('a[name="base-opr"]').click(function () {
+            budgetTreeOpr.baseOpr(budgetSheet, this.getAttribute('type'));
+        });
+        budgetSpread.bind(spreadNS.Events.EditStarting, budgetTreeOpr.editStarting);
+        budgetSpread.bind(spreadNS.Events.EditEnded, budgetTreeOpr.editEnded);
+        budgetSpread.bind(spreadNS.Events.ClipboardPasting, budgetTreeOpr.clipboardPasting);
+        SpreadJsObj.addDeleteBind(budgetSpread, budgetTreeOpr.deletePress);
+        $.contextMenu({
+            selector: '#budget-spread',
+            build: function ($trigger, e) {
+                const target = SpreadJsObj.safeRightClickSelection($trigger, e, budgetSpread);
+                return target.hitTestType === spreadNS.SheetArea.viewport || target.hitTestType === spreadNS.SheetArea.rowHeader;
+            },
+            items: {
+                copyBlock: {
+                    name: '复制整块',
+                    icon: 'fa-files-o',
+                    callback: function (key, opt) {
+                        const copyBlockList = [];
+                        const sheet = budgetSheet;
+                        const sel = sheet.getSelections()[0];
+                        let iRow = sel.row;
+                        const pid = sheet.zh_tree.nodes[iRow].tree_pid;
+                        while (iRow < sel.row + sel.rowCount) {
+                            const node = sheet.zh_tree.nodes[iRow];
+                            if (node.tree_pid !== pid) {
+                                toastr.error('仅可同时选中同层节点');
+                                return;
+                            }
+                            const posterity = sheet.zh_tree.getPosterity(node);
+                            iRow += posterity.length + 1;
+                            posterity.unshift(node);
+                            copyBlockList.push(sheet.zh_tree.getDefaultData(posterity));
+                        }
+                        setLocalCache(copyBlockTag, JSON.stringify({ block: copyBlockList }));
+                    },
+                    visible: function (key, opt) {
+                        const select = SpreadJsObj.getSelectObject(budgetSheet);
+                        return !!select;
+                    },
+                    disabled: function (key, opt) {
+                        const select = SpreadJsObj.getSelectObject(budgetSheet);
+                        return !!select && select.level <= 1;
+                    }
+                },
+                copyBlockXmj: {
+                    name: '复制整块(只复制项目节)',
+                    icon: 'fa-files-o',
+                    callback: function (key, opt) {
+                        const copyBlockList = [];
+                        const sheet = budgetSheet;
+                        const sel = sheet.getSelections()[0];
+                        let iRow = sel.row;
+                        const pid = sheet.zh_tree.nodes[iRow].ledger_pid;
+                        while (iRow < sel.row + sel.rowCount) {
+                            const node = sheet.zh_tree.nodes[iRow];
+                            if (node.ledger_pid !== pid) {
+                                toastr.error('仅可同时选中同层节点');
+                                return;
+                            }
+                            const posterity = sheet.zh_tree.getPosterity(node);
+                            iRow += posterity.length + 1;
+                            const copyPosterity = posterity.filter(x => { return !x.b_code; });
+                            copyPosterity.unshift(node);
+                            const copyData = sheet.zh_tree.getDefaultData(copyPosterity);
+                            for (const p of copyData) {
+                                const children = copyData.filter(y => {return y.tree_pid === p.tree_id}) || [];
+                                p.is_leaf = children.length === 0;
+                            }
+                            copyBlockList.push(copyData);
+                        }
+                        setLocalCache(copyBlockTag, JSON.stringify({ block: copyBlockList }));
+                    },
+                    visible: function (key, opt) {
+                        const select = SpreadJsObj.getSelectObject(budgetSheet);
+                        return needGcl && !!select;
+                    },
+                    disabled: function (key, opt) {
+                        const select = SpreadJsObj.getSelectObject(budgetSheet);
+                        return !!select && select.level <= 1;
+                    }
+                },
+                pasteBlock: {
+                    name: '粘贴整块',
+                    icon: 'fa-clipboard',
+                    disabled: function (key, opt) {
+                        const copyInfo = JSON.parse(getLocalCache(copyBlockTag));
+                        return !(copyInfo && copyInfo.block && copyInfo.block.length > 0);
+                    },
+                    callback: function (key, opt) {
+                        const copyInfo = JSON.parse(getLocalCache(copyBlockTag));
+                        if (copyInfo.block.length > 0) {
+                            budgetTreeOpr.pasteBlock(budgetSheet, copyInfo);
+                        } else {
+                            document.execCommand('paste');
+                        }
+                    },
+                    visible: function (key, opt) {
+                        return !readOnly;
+                    }
+                }
+            }
+        });
+    }
+    postData(window.location.pathname + '/load', {}, function (result) {
+        budgetTree.loadDatas(result);
+        treeCalc.calculateAll(budgetTree);
+        SpreadJsObj.loadSheetData(budgetSheet, SpreadJsObj.DataType.Tree, budgetTree);
+        budgetTreeOpr.refreshOperationValid(budgetSheet);
+    });
+
+    const stdLibCellDoubleClick = function (e, info) {
+        const stdSheet = info.sheet;
+        if (!stdSheet.zh_setting || !stdSheet.zh_tree || !budgetSheet.zh_tree) return;
+
+        const stdTree = stdSheet.zh_tree;
+        const stdNode = stdTree.nodes[info.row];
+        if (!stdNode) return;
+
+        const budgetTree = budgetSheet.zh_tree;
+        const sel = budgetSheet.getSelections()[0];
+        const mainNode = budgetTree.nodes[sel.row];
+        if (info.sheet.zh_setting.stdType === 'gcl') {
+            if (mainNode.code && mainNode.code !== '' && !budgetTree.isLeafXmj(mainNode)) {
+                toastr.warning('非最底层项目下,不应添加清单');
+                return;
+            }
+        }
+
+        postData(window.location.pathname + '/update', {
+            postType: 'add-std',
+            postData: {
+                id: budgetTree.getNodeKey(mainNode),
+                tender_id: mainNode.tender_id,
+                stdType: info.sheet.zh_setting.stdType,
+                stdLibId: stdNode.list_id,
+                stdNode: stdTree.getNodeKey(stdNode)
+            }
+        }, function (result) {
+            const refreshNode = budgetTree.loadPostData(result);
+            budgetTreeOpr.refreshTree(budgetSheet, refreshNode);
+            if (refreshNode.create && refreshNode.create.length > 0) {
+                budgetSheet.setSelection(refreshNode.create[refreshNode.create.length - 1].index, sel.col, sel.rowCount, sel.colCount);
+                SpreadJsObj.reloadRowsBackColor(budgetSheet, [sel.row, refreshNode.create[refreshNode.create.length - 1].index]);
+            } else {
+                const node = _.find(budgetTree.nodes, {code: stdNode.code, name: stdNode.name});
+                if (node) {
+                    budgetSheet.setSelection(budgetTree.nodes.indexOf(node), sel.col, sel.rowCount, sel.colCount);
+                    SpreadJsObj.reloadRowsBackColor(budgetSheet, [sel.row, budgetTree.nodes.indexOf(node)]);
+                }
+            }
+            budgetTreeOpr.refreshOperationValid(budgetSheet);
+            budgetSpread.focus();
+        });
+    };
+    const stdXmjSetting = {
+        selector: '#std-xmj',
+        stdType: 'xmj',
+        treeSetting: {
+            id: 'chapter_id',
+            pid: 'pid',
+            order: 'order',
+            level: 'level',
+            rootId: -1,
+            keys: ['id', 'list_id', 'chapter_id'],
+        },
+        spreadSetting: {
+            cols: [
+                {title: '项目节编号', field: 'code', hAlign: 0, width: 120, formatter: '@', cellType: 'tree'},
+                {title: '名称', field: 'name', hAlign: 0, width: 150, formatter: '@'},
+                {title: '单位', field: 'unit', hAlign: 1, width: 50, formatter: '@'}
+            ],
+            treeCol: 0,
+            emptyRows: 0,
+            headRows: 1,
+            headRowHeight: [32],
+            defaultRowHeight: 21,
+            headerFont: '12px 微软雅黑',
+            font: '12px 微软雅黑',
+            headColWidth: [30],
+            selectedBackColor: '#fffacd',
+            readOnly: true,
+        },
+        cellDoubleClick: !readOnly ? stdLibCellDoubleClick : null,
+        page: 'budget',
+        tid: window.location.pathname.split('/')[2],
+    };
+    const stdGclSetting = {
+        selector: '#std-gcl',
+        stdType: 'gcl',
+        treeSetting: {
+            id: 'bill_id',
+            pid: 'pid',
+            order: 'order',
+            level: 'level',
+            rootId: -1,
+            keys: ['id', 'list_id', 'bill_id']
+        },
+        spreadSetting: {
+            cols: [
+                {title: '清单编号', field: 'b_code', hAlign: 0, width: 120, formatter: '@', cellType: 'tree'},
+                {title: '名称', field: 'name', hAlign: 0, width: 150, formatter: '@'},
+                {title: '单位', field: 'unit', hAlign: 1, width: 50, formatter: '@'}
+            ],
+            treeCol: 0,
+            emptyRows: 0,
+            headRows: 1,
+            headRowHeight: [32],
+            defaultRowHeight: 21,
+            headerFont: '12px 微软雅黑',
+            font: '12px 微软雅黑',
+            headColWidth: [30],
+            selectedBackColor: '#fffacd',
+            readOnly: true,
+        },
+        cellDoubleClick: !readOnly ? stdLibCellDoubleClick : null,
+        page: 'budget',
+        tid: window.location.pathname.split('/')[2],
+    };
+    // 展开收起标准清单
+    $('a', 'ul.right-nav').bind('click', function (e) {
+        e.preventDefault();
+        const tab = $(this), tabPanel = $(tab.attr('content'));
+        // 展开工具栏、切换标签
+        if (!tab.hasClass('active')) {
+            $('a', '.side-menu').removeClass('active');
+            $('.tab-content .tab-select-show.tab-pane.active').removeClass('active');
+            tab.addClass('active');
+            tabPanel.addClass('active');
+            showSideTools(tab.hasClass('active'));
+            if (tab.attr('content') === '#std-xmj') {
+                if (!stdXmj) stdXmj = new stdLib(stdXmjSetting);
+                stdXmj.spread.refresh();
+            } else if (tab.attr('content') === '#std-gcl') {
+                if (!stdGcl) stdGcl = new stdLib(stdGclSetting);
+                stdGcl.spread.refresh();
+            } else if (tab.attr('content') === '#search') {
+                if (!searchBudget) {
+                    const searchSetting = {
+                        selector: '#search',
+                        searchSpread: budgetSpread,
+                        keyId: 'tree_id',
+                        resultSpreadSetting: {
+                            cols: [
+                                {title: '项目节编号', field: 'code', hAlign: 0, width: 120, formatter: '@'},
+                                {title: '清单编号', field: 'b_code', hAlign: 0, width: 80, formatter: '@'},
+                                {title: '名称', field: 'name', width: 150, hAlign: 0, formatter: '@'},
+                                {title: '单位', field: 'unit', width: 50, hAlign: 1, formatter: '@'},
+                            ],
+                            emptyRows: 0,
+                            headRows: 1,
+                            headRowHeight: [32],
+                            headColWidth: [30],
+                            defaultRowHeight: 21,
+                            headerFont: '12px 微软雅黑',
+                            font: '12px 微软雅黑',
+                            selectedBackColor: '#fffacd',
+                            readOnly: true,
+                        },
+                    };
+                    if (!needGcl) {
+                        searchSetting.resultSpreadSetting.cols = searchSetting.resultSpreadSetting.cols.filter(x => {
+                            return ['b_code', 'quantity', 'unit_price'].indexOf(x.field) < 0;
+                        });
+                        searchSetting.searchRangeStr = '项目节编号/名称';
+                    } else {
+                        searchSetting.searchRangeStr = '项目节编号/清单编号/名称';
+                    }
+                    searchBudget = $.billsSearch(searchSetting);
+                }
+                searchBudget.spread.refresh();
+            }
+        } else { // 收起工具栏
+            tab.removeClass('active');
+            tabPanel.removeClass('active');
+            showSideTools(tab.hasClass('active'));
+        }
+        budgetSpread.refresh();
+    });
+    // 显示层次
+    (function (select, sheet) {
+        $(select).click(function () {
+            if (!sheet.zh_tree) return;
+            const tag = $(this).attr('tag');
+            const tree = sheet.zh_tree;
+            setTimeout(() => {
+                showWaitingView();
+                switch (tag) {
+                    case "1":
+                    case "2":
+                    case "3":
+                    case "4":
+                    case "5":
+                        tree.expandByLevel(parseInt(tag));
+                        SpreadJsObj.refreshTreeRowVisible(sheet);
+                        break;
+                    case "last":
+                        tree.expandByCustom(() => { return true; });
+                        SpreadJsObj.refreshTreeRowVisible(sheet);
+                        break;
+                    case "leafXmj":
+                        tree.expandToLeafXmj();
+                        SpreadJsObj.refreshTreeRowVisible(sheet);
+                        break;
+                }
+                closeWaitingView();
+            }, 100);
+        });
+    })('a[name=showLevel]', budgetSheet);
+    // 导入
+    $('#budget-import').click(() => {
+        importExcel.doImport({
+            template: {
+                hint: '导入Excel',
+                url: '/template/导入分项清单EXCEL格式.xlsx',
+            },
+            filter: needGcl,
+            callback: function (sheet, filter) {
+                postDataCompress(window.location.pathname + '/upload-excel/tz', {sheet, filter}, function (result) {
+                    budgetTree.loadDatas(result);
+                    treeCalc.calculateAll(budgetTree);
+                    SpreadJsObj.reLoadSheetData(budgetSheet);
+                    checkShowLast(result.length);
+                }, null);
+            },
+        });
+    });
+
+    $('#budget-set').on('show.bs.modal', () => {
+        $('#decimal-qty').val(decimal.qty);
+        $('#decimal-up').val(decimal.up);
+        $('#decimal-tp').val(decimal.tp);
+    });
+    // 设置小数位数
+    $('#budget-set-ok').click(() => {
+        const newDecimal = {
+            qty: parseInt($('#decimal-qty').val()),
+            up: parseInt($('#decimal-up').val()),
+            tp: parseInt($('#decimal-tp').val()),
+        };
+        postData('decimal', { decimal: newDecimal, page: window.location.pathname.split('/')[3] }, result => {
+            decimal = newDecimal;
+            const refreshNodes = budgetTree.loadPostData(result);
+            budgetTreeOpr.refreshTree(budgetSheet, refreshNodes);
+            $('#budget-set').modal('hide');
+        })
+    })
+});

+ 217 - 0
app/public/js/budget_list.js

@@ -0,0 +1,217 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date
+ * @version
+ */
+let curBudget = {};
+
+const budgetNameChange = function (obj) {
+    if (obj.value.length > 100) {
+        obj.classList.add('is-invalid');
+    } else {
+        obj.classList.remove('is-invalid');
+    }
+};
+
+const addBudget = function () {
+    const name = $('#add-budget-name').val();
+    if (!name || name.length > 100) return;
+    const std_id = parseInt($('[name=std_id]:checked').val());
+    postData('/budget/add', { name, std_id }, function () {
+        window.location.reload();
+    });
+};
+
+const showModal = function (obj) {
+    const tr = obj.parentNode.parentNode;
+    curBudget.id = tr.getAttribute('bid');
+    curBudget.name = tr.getAttribute('bname');
+    curBudget.rela_tender = tr.getAttribute('rela-tender');
+    $(obj.getAttribute('data-target')).modal('show');
+};
+
+const saveBudget = function () {
+    const name = $('#modify-budget-name').val();
+    if (!name || name.length > 100) return;
+    postData('/budget/save', { id: curBudget.id, name}, function () {
+        window.location.reload();
+    })
+};
+
+const delBudget = function () {
+    postData('/budget/del', { id: curBudget.id }, function () {
+        window.location.reload();
+    });
+};
+
+const relaTender = function () {
+    // todo 选择标段
+    const rela = [];
+    const select = $('[name=select-rela-check]:checked');
+    for (const s of select) {
+        rela.push(s.getAttribute('tid'));
+    }
+    postData('/budget/save', { id: curBudget.id, rela_tender: rela.join(',') }, function () {
+        $(`[bid=${curBudget.id}]`)[0].setAttribute('rela-tender', rela.join(','));
+        $('#select-rela').modal('hide');
+    });
+};
+
+
+$(document).ready(() => {
+    autoFlashHeight();
+    $('#modify-budget').on('show.bs.modal', () => {
+        $('#modify-budget-name').val(curBudget.name);
+    });
+    $('#del-budget').on('show.bs.modal', () => {
+        $('#del-budget-name').text(curBudget.name);
+    });
+    $('#sr-select-all').click(function () {
+        $('[name=select-rela-check]').prop("checked", this.checked);
+    });
+    $('#select-rela').on('show.bs.modal', () => {
+        $('#sr-select-all')[0].checked = false;
+        $('#valid-rela-tender').html('');
+        postData(`/budget/rela?id=${curBudget.id}`, {}, tenders => {
+            const rela = curBudget.rela_tender ? curBudget.rela_tender.split(',') : [];
+            const html = [];
+            for (const t of tenders) {
+                html.push(`<tr><td class="text-center"><input type="checkbox" name="select-rela-check" tid="${t.id}"></td><td>${t.name}</td><td>第${t.lastStageOrder}期</td><td>${t.lastStageStatus}</td></tr>`);
+            }
+            $('#valid-rela-tender').html(html.join(''));
+            for (const r of rela) {
+                $(`[tid=${r}]`).attr("checked", "checked");
+            }
+        });
+    });
+
+    let timer = null;
+    let oldSearchVal = null;
+    $('#member-search').bind('input propertychange', function(e) {
+        oldSearchVal = e.target.value;
+        timer && clearTimeout(timer);
+        timer = setTimeout(() => {
+            const newVal = $('#member-search').val();
+            let html = '';
+            if (newVal && newVal === oldSearchVal) {
+                accountList
+                    .filter(item => item && (item.name.indexOf(newVal) !== -1 || (item.mobile && item.mobile.indexOf(newVal) !== -1)))
+                    .forEach(item => {
+                        html += `<dd class="border-bottom p-2 mb-0 " data-id="${item.id}" >
+                        <p class="mb-0 d-flex"><span class="text-primary">${item.name}</span><span
+                                class="ml-auto">${item.mobile || ''}</span></p>
+                        <span class="text-muted">${item.role || ''}</span></dd>`
+                    });
+                $('.book-list').empty();
+                $('.book-list').append(html);
+            } else {
+                if (!$('.acc-btn').length) {
+                    accountGroup.forEach((group, idx) => {
+                        if (!group) return;
+                        html += `<dt><a href="javascript: void(0);" class="acc-btn" data-groupid="${idx}" data-type="hide"><i class="fa fa-plus-square"></i>
+                        </a> ${group.groupName}</dt>
+                        <div class="dd-content" data-toggleid="${idx}">`;
+                        group.groupList.forEach(item => {
+                            html += `<dd class="border-bottom p-2 mb-0 " data-id="${item.id}" >
+                                    <p class="mb-0 d-flex"><span class="text-primary">${item.name}</span><span
+                                            class="ml-auto">${item.mobile || ''}</span></p>
+                                    <span class="text-muted">${item.role || ''}</span>
+                                </dd>`
+                        });
+                        html += '</div>';
+                    });
+                    $('.book-list').empty();
+                    $('.book-list').append(html);
+                }
+            }
+        }, 400);
+    });
+    $('.book-list').on('click', 'dt', function () {
+        const idx = $(this).find('.acc-btn').attr('data-groupid')
+        const type = $(this).find('.acc-btn').attr('data-type')
+        if (type === 'hide') {
+            $(this).parent().find(`div[data-toggleid="${idx}"]`).show(() => {
+                $(this).children().find('i').removeClass('fa-plus-square').addClass('fa-minus-square-o')
+                $(this).find('.acc-btn').attr('data-type', 'show')
+
+            })
+        } else {
+            $(this).parent().find(`div[data-toggleid="${idx}"]`).hide(() => {
+                $(this).children().find('i').removeClass('fa-minus-square-o').addClass('fa-plus-square')
+                $(this).find('.acc-btn').attr('data-type', 'hide')
+            })
+        }
+        return false
+    });
+    let orgMembers, newMembers;
+    const generateMemberHtml = function () {
+        const html = [];
+        for (const mem of newMembers) {
+            html.push('<tr>');
+            html.push(`<td>${mem.name}</td>`);
+            html.push(`<td>${mem.role}</td>`);
+            // const view = mem.permission.indexOf(permissionConst.view.value) >= 0;
+            // html.push(`<td><div class="custom-control custom-checkbox mb-2">
+            //             <input type="checkbox" id="view${mem.uid}" ptype="view" uid="${mem.uid}" class="custom-control-input" ${(view ? 'checked' : '')}>
+            //             <label class="custom-control-label" for="view${mem.uid}"></label></div></td>`);
+            const edit = mem.permission.indexOf(permissionConst.edit.value) >= 0;
+            html.push(`<td><div class="custom-control custom-checkbox mb-2">
+                        <input type="checkbox" id="edit${mem.uid}" ptype="edit" uid="${mem.uid}" class="custom-control-input" ${(edit ? 'checked' : '')}>
+                        <label class="custom-control-label" for="edit${mem.uid}"></label></div></td>`);
+            html.push(`<td><a href="javascript: void(0);" class="btn btn-outline-danger btn-sm ml-1" name="del-member" uid="${mem.uid}">移除</a></td>`);
+            html.push('</tr>');
+        }
+        $('#member-list').html(html.join(''));
+    };
+    $('[data-target="#member"]').click(function(){
+        const tr = this.parentNode.parentNode;
+        curBudget.id = tr.getAttribute('bid');
+        curBudget.name = tr.getAttribute('bname');
+        curBudget.rela_tender = tr.getAttribute('rela-tender');
+        postData(window.location.pathname + '/member', curBudget, function (result) {
+            orgMembers = result;
+            newMembers = result;
+            generateMemberHtml();
+            $('#member').modal('show');
+        });
+    });
+    $('dl').on('click', 'dd', function () {
+        const auditorId = parseInt($(this).data('id'));
+        const user = accountList.find(x => { return x.id === auditorId; });
+        const check = $(`tr[uid=${auditorId}]`, '#member-list');
+        if (check.length > 0) {
+            toastr.error('请勿重复添加成员');
+            return;
+        }
+        newMembers.push({
+            uid: user.id,
+            name: user.name,
+            role: user.role,
+            permission: [1],
+        });
+        generateMemberHtml();
+    });
+    $('#member').on('click', 'a[name="del-member"]', function () {
+        const id = parseInt(this.getAttribute('uid'));
+        newMembers.splice(newMembers.findIndex(x => { return x.uid === id}), 1);
+        generateMemberHtml();
+    });
+    $('#member-ok').click(() => {
+        postData(window.location.pathname + '/member-save', {id: curBudget.id, member: newMembers}, function () {
+            $('#member').modal('hide');
+        })
+    });
+    $('#member-list').on('click', 'input', function () {
+        const id = parseInt(this.getAttribute('uid'));
+        const mem = newMembers.find(x => { return x.uid === id});
+        if (this.checked) {
+            mem.permission.push(parseInt(permissionConst[this.getAttribute('ptype')].value));
+        } else {
+            mem.permission.splice(mem.permission.indexOf(permissionConst[this.getAttribute('ptype')].value), 1);
+        }
+    });
+});

+ 3 - 0
app/public/js/change_information_approval.js

@@ -45,6 +45,9 @@ $(document).ready(() => {
                     indent: 16,
                     getColor: function (index, data) {
                         if (!data) return;
+                        if (_.findIndex(changeLedgerList, { id: data.gcl_id }) !== -1 || _.findIndex(changePosList, { id: data.mx_id }) !== -1) {
+                            return '#dc3545';
+                        }
                         if(data.lid != 0) return;
                         return '#007bff';
                     }

+ 103 - 30
app/public/js/change_information_set.js

@@ -441,6 +441,15 @@ $(document).ready(() => {
                 gclGatherData.splice(ggd, 1);
             }
             gclGatherData[ggd].code = gclGatherData[ggd].b_code;
+            let hadcid = 0;
+            for (const xmj of gclGatherData[ggd].leafXmjs) {
+                if (_.findIndex(changeLedgerList, { id: xmj.gcl_id }) !== -1 || _.findIndex(changePosList, { id: xmj.mx_id, lid: xmj.gcl_id }) !== -1) {
+                    xmj.cid = 1;
+                    hadcid = 1;
+                }
+            }
+
+            if (hadcid !== 0) gclGatherData[ggd].cid = 1;
         }
         // 数组去重
         const dealBillList = result.dealBills;
@@ -467,7 +476,7 @@ $(document).ready(() => {
             const lid = gcl.leafXmjs !== undefined && gcl.leafXmjs !== null ? (gcl.leafXmjs.length !== 0 ? gcl.leafXmjs[0].gcl_id : false) : gcl.id;
             if (lid) {
                 listHtml += '<tr data-lid="' + lid + '"' + gclhtml + ' data-index="' + list_index + '" data-bwmx="">' +
-                    '<td class="text-center">' + list_index + '</td>' +
+                    '<td class="text-center">' + list_index + (gcl.cid ? '<i class="text-danger" style="font-weight: 900">*</i>' : '') + '</td>' +
                     '<td>' + gcl.code + '</td>' +
                     '<td class="text-left">' + gcl.name + '</td>' +
                     '<td class="text-center">' + unit + '</td>' +
@@ -628,7 +637,7 @@ $(document).ready(() => {
         const isCheck = $(this).hasClass('table-success') ? true : false;
         const data_bwmx = $(this).attr('data-bwmx').split('$#$');
         const isDeal = $(this).data('gcl') !== undefined ? true : false;
-        let codeHtml = '<tr quantity="'+ $(this).children('td').eq(5).text() +'" gcl_id="" mx_id=""><td colspan="7" class="colspan_1">&nbsp;</td><td class="colspan_2"><input type="checkbox"></td></tr>';
+        let codeHtml = '<tr quantity="'+ $(this).children('td').eq(5).text() +'" gcl_id="" mx_id=""><td class="text-center">1</td><td colspan="7" class="colspan_1">&nbsp;</td><td class="colspan_2"><input type="checkbox"></td></tr>';
         if (isDeal) {
             const lid = $(this).data('lid');
             let gcl = _.find(gclGatherData, function (item) {
@@ -638,7 +647,7 @@ $(document).ready(() => {
                 gcl = gclGatherData[$(this).data('gcl')];
             }
             codeHtml = '';
-            for (const leaf of gcl.leafXmjs) {
+            for (const [index, leaf] of gcl.leafXmjs.entries()) {
                 const quantity = leaf.quantity !== undefined && leaf.quantity !== null ? leaf.quantity : 0;
                 const gcl_id = leaf.gcl_id ? leaf.gcl_id : '';
                 const mx_id = leaf.mx_id ? leaf.mx_id : '';
@@ -651,7 +660,9 @@ $(document).ready(() => {
                     'checked' : '';
                 const isUsed = _.find(changeUsedData, { gcl_id: leaf.gcl_id, bwmx: (bwmx ? bwmx : leaf.jldy ? leaf.jldy : ''), oamount: leaf.quantity });
                 const isDisabled = isUsed ? 'disabled ' : '';
-                codeHtml += '<tr quantity="' + quantity + '" gcl_id="' + gcl_id + '" mx_id="' + mx_id + '"><td>' + leaf.code + '</td>' +
+                codeHtml += '<tr quantity="' + quantity + '" gcl_id="' + gcl_id + '" mx_id="' + mx_id + '">' +
+                    '<td class="text-center">' + (index+1) + (leaf.cid ? '<i class="text-danger" style="font-weight: 900">*</i>' : '') + '</td>' +
+                    '<td>' + leaf.code + '</td>' +
                     '<td>' + (leaf.jldy ? leaf.jldy: '') + '</td>' +
                     '<td>' + (leaf.dwgc ? leaf.dwgc : '') + '</td>' +
                     '<td>' + (leaf.fbgc ? leaf.fbgc : '') + '</td>' +
@@ -662,9 +673,9 @@ $(document).ready(() => {
                     '></td></tr>';
             }
         } else if (!isDeal && isCheck) {
-            codeHtml = '<tr quantity="'+ $(this).children('td').eq(5).text() +'" gcl_id="" mx_id=""><td colspan="7" class="colspan_1">&nbsp;</td><td class="colspan_2"><input type="checkbox" checked></td></tr>';
+            codeHtml = '<tr quantity="'+ $(this).children('td').eq(5).text() +'" gcl_id="" mx_id=""><td class="text-center">1</td><td colspan="7" class="colspan_1">&nbsp;</td><td class="colspan_2"><input type="checkbox" checked></td></tr>';
         }
-        $('#code-list').attr('data-index', $(this).children('td').eq(0).text());
+        $('#code-list').attr('data-index', parseInt($(this).children('td').eq(0).text()));
         $('#code-input').val('');
         $('#code-input').siblings('a').hide();
         $('#code-list').html(codeHtml);
@@ -687,13 +698,13 @@ $(document).ready(() => {
                 const length = tr.children('td').length;
                 const gcl_id = tr.attr('gcl_id');
                 const mx_id = tr.attr('mx_id');
-                const bwmx = length === 8 ?
-                    tr.children('td').eq(0).text() + '!_!' +
+                const bwmx = length === 9 ?
                     tr.children('td').eq(1).text() + '!_!' +
                     tr.children('td').eq(2).text() + '!_!' +
                     tr.children('td').eq(3).text() + '!_!' +
-                    tr.children('td').eq(4).text() + '!_!' + gcl_id + '!_!' + mx_id + '!_!' +
-                    (tr.children('td').eq(5).text() !== '' ? tr.children('td').eq(5).text() : tr.children('td').eq(1).text()) : '0';
+                    tr.children('td').eq(4).text() + '!_!' +
+                    tr.children('td').eq(5).text() + '!_!' + gcl_id + '!_!' + mx_id + '!_!' +
+                    (tr.children('td').eq(6).text() !== '' ? tr.children('td').eq(6).text() : tr.children('td').eq(2).text()) : '0';
                 const quantity = tr.attr('quantity');
                 const de_qu = bwmx + '*;*' + quantity;
                 data_bwmx.push(de_qu);
@@ -710,13 +721,13 @@ $(document).ready(() => {
                     const length = tr.children('td').length;
                     const gcl_id = tr.attr('gcl_id');
                     const mx_id = tr.attr('mx_id');
-                    const bwmx = length === 8 ?
-                        tr.children('td').eq(0).text() + '!_!' +
+                    const bwmx = length === 9 ?
                         tr.children('td').eq(1).text() + '!_!' +
                         tr.children('td').eq(2).text() + '!_!' +
                         tr.children('td').eq(3).text() + '!_!' +
-                        tr.children('td').eq(4).text() + '!_!' + gcl_id + '!_!' + mx_id + '!_!' +
-                        (tr.children('td').eq(5).text() !== '' ? tr.children('td').eq(5).text() : tr.children('td').eq(1).text()) : '0';
+                        tr.children('td').eq(4).text() + '!_!' +
+                        tr.children('td').eq(5).text() + '!_!' + gcl_id + '!_!' + mx_id + '!_!' +
+                        (tr.children('td').eq(6).text() !== '' ? tr.children('td').eq(6).text() : tr.children('td').eq(2).text()) : '0';
                     const quantity = tr.attr('quantity');
                     const de_qu = bwmx + '*;*' + quantity;
                     data_bwmx.push(de_qu);
@@ -768,10 +779,60 @@ $(document).ready(() => {
         $('input[name="code"]').val(code);
     });
 
-    $('#list-input').on('valuechange', function (e, previous) {
+    $('#select-list').change(function () {
+        const select = parseInt($(this).val());
+        let showListData = changeListData;
+        const value = $('#list-input').val();
+        if (select === 1) {
+            if (value !== '') {
+                $('#list-input').siblings('a').show();
+                showListData = _.filter(changeListData, function (c) {
+                    return ((c.code && c.code.indexOf(value) !== -1) || (c.name && c.name.indexOf(value) !== -1)) && c.cid;
+                });
+            } else {
+                $('#list-input').siblings('a').hide();
+                showListData = _.filter(changeListData, function (c) {
+                    return c.cid;
+                });
+            }
+        } else {
+            if (value !== '') {
+                $('#list-input').siblings('a').show();
+                showListData = _.filter(changeListData, function (c) {
+                    return (c.code && c.code.indexOf(value) !== -1) || (c.name && c.name.indexOf(value) !== -1);
+                });
+            } else {
+                $('#list-input').siblings('a').hide();
+            }
+        }
+        makeListTable(changeListData, showListData);
+        $('#table-list-select tr').removeClass('table-warning');
+        $('#code-input').val('');
+        $('#code-input').siblings('a').hide();
+        $('#code-list').html('');
+        $('#code-select-all').prop('checked', false);
+    });
+    // 回车提交
+    $('#list-input').on('keypress', function () {
+        if(window.event.keyCode === 13) {
+            $(this).blur();
+        }
+    });
+    $('#list-input').on('blur', function () {
+        const select = parseInt($('#select-list').val());
         const value = $(this).val();
         let showListData = changeListData;
-        if (value !== '') {
+        if (select === 1 && value !== '') {
+            $(this).siblings('a').show();
+            showListData = _.filter(changeListData, function (c) {
+                return ((c.code && c.code.indexOf(value) !== -1) || (c.name && c.name.indexOf(value) !== -1)) && c.cid;
+            });
+        } else if (select === 1 && value === '') {
+            $(this).siblings('a').hide();
+            showListData = _.filter(changeListData, function (c) {
+                return c.cid;
+            });
+        } else if (value !== '') {
             $(this).siblings('a').show();
             showListData = _.filter(changeListData, function (c) {
                 return (c.code && c.code.indexOf(value) !== -1) || (c.name && c.name.indexOf(value) !== -1);
@@ -786,8 +847,13 @@ $(document).ready(() => {
         $('#code-list').html('');
         $('#code-select-all').prop('checked', false);
     });
-
-    $('#code-input').on('valuechange', function (e, previous) {
+    // 回车提交
+    $('#code-input').on('keypress', function () {
+        if(window.event.keyCode === 13) {
+            $(this).blur();
+        }
+    });
+    $('#code-input').on('blur', function () {
         const value = $(this).val();
         if (value !== '') {
             $(this).siblings('a').show();
@@ -802,7 +868,14 @@ $(document).ready(() => {
         $(this).hide();
         $(this).siblings('input').val('');
         if ($(this).data('btn') === 'list') {
-            makeListTable(changeListData);
+            const select = parseInt($('#select-list').val());
+            let showListData = changeListData;
+            if (select === 1) {
+                showListData = _.filter(changeListData, function (c) {
+                    return c.cid;
+                });
+            }
+            makeListTable(changeListData, showListData);
             $('#table-list-select tr').removeClass('table-warning');
             $('#code-list').html('');
         } else {
@@ -837,13 +910,13 @@ $(document).ready(() => {
                     const length = tr.children('td').length;
                     const gcl_id = tr.attr('gcl_id');
                     const mx_id = tr.attr('mx_id');
-                    const bwmx = length === 8 ?
-                        tr.children('td').eq(0).text() + '!_!' +
+                    const bwmx = length === 9 ?
                         tr.children('td').eq(1).text() + '!_!' +
                         tr.children('td').eq(2).text() + '!_!' +
                         tr.children('td').eq(3).text() + '!_!' +
-                        tr.children('td').eq(4).text() + '!_!' + gcl_id + '!_!' + mx_id + '!_!' +
-                        (tr.children('td').eq(5).text() !== '' ? tr.children('td').eq(5).text() : tr.children('td').eq(1).text()) : '0';
+                        tr.children('td').eq(4).text() + '!_!' +
+                        tr.children('td').eq(5).text() + '!_!' + gcl_id + '!_!' + mx_id + '!_!' +
+                        (tr.children('td').eq(6).text() !== '' ? tr.children('td').eq(6).text() : tr.children('td').eq(2).text()) : '0';
                     const quantity = tr.attr('quantity');
                     const de_qu = bwmx + '*;*' + quantity;
                     data_bwmx.push(de_qu);
@@ -1148,9 +1221,9 @@ function tableDataRemake(changeListData) {
         }
         if(removeList.length > 0) {
             _.pullAll(changeList, removeList);
-            // postData(window.location.pathname + '/save', { type:'remove_list', updateData: removeList }, function (result) {
-            // }, function () {
-            // });
+            postData(window.location.pathname + '/save', { type:'remove_list', updateData: removeList }, function (result) {
+            }, function () {
+            });
         }
     }
 }
@@ -1174,10 +1247,10 @@ function makeCodeTable(search = '') {
     }
     for(let i = 0; i < $('#code-list tr').length; i++) {
         const length = $('#code-list tr').eq(i).children('td').length;
-        if (length === 8) {
-            const code = $('#code-list tr').eq(i).children('td').eq(0).text();
-            const name = $('#code-list tr').eq(i).children('td').eq(1).text();
-            const jldy = $('#code-list tr').eq(i).children('td').eq(5).text();
+        if (length === 9) {
+            const code = $('#code-list tr').eq(i).children('td').eq(1).text();
+            const name = $('#code-list tr').eq(i).children('td').eq(2).text();
+            const jldy = $('#code-list tr').eq(i).children('td').eq(6).text();
             const isShow = code.indexOf(search) !== -1 || name.indexOf(search) !== -1 || jldy.indexOf(search) !== -1;
             $('#code-list tr').eq(i).css('display', (isShow ? 'table-row' : 'none'));
         } else {

+ 52 - 25
app/public/js/change_revise.js

@@ -8,7 +8,7 @@
  * @version
  */
 
-const ckBillsSpread = window.location.pathname + '-billsSelect';
+const ckBillsSpread = '/tender/' + window.location.pathname.split('/')[2] + '/change/revise-billsSelect';
 const invalidFields = {
     parent: ['sgfh_qty', 'sgfh_tp', 'sjcl_qty', 'sjcl_tp', 'qtcl_qty', 'qtcl_tp', 'deal_qty', 'deal_tp', 'unit_price'],
     gcl: ['dgn_qty1', 'dgn_qty2'],
@@ -36,6 +36,7 @@ const checkOption = {
         fields: ['sgfh_qty', 'qtcl_qty', 'sjcl_qty', 'quantity'],
     },
     zero: { enable: 1 },
+    zeroPos: { enable: 1 },
     tp: {
         enable: 1,
         fields: [
@@ -46,6 +47,7 @@ const checkOption = {
             {qty: 'deal_qty', tp: 'deal_tp'},
         ],
     },
+    same_code: { enable: 1 },
 };
 
 $(document).ready(() => {
@@ -76,7 +78,7 @@ $(document).ready(() => {
                 return !readOnly && !data.ccid;
             }
         }
-    }
+    };
     // 初始化spread
     const billsSpread = SpreadJsObj.createNewSpread($('#bills-spread')[0]);
     const billsSheet = billsSpread.getActiveSheet();
@@ -88,12 +90,12 @@ $(document).ready(() => {
                 indent: 16,
                 getColor: function (index, data) {
                     if (!data) return;
-                    if(!data.ccid) return;
-                    return '#007bff';
+                    if(!data.ccid && !data.cid) return;
+                    return '#dc3545';
                 }
             },
         },
-    ],
+    ];
     sjsSettingObj.setFxTreeStyle(billsSpreadSetting, sjsSettingObj.FxTreeStyle.jz);
     if (thousandth) sjsSettingObj.setTpThousandthFormat(billsSpreadSetting);
     SpreadJsObj.initSpreadSettingEvents(billsSpreadSetting, billsCol);
@@ -109,11 +111,11 @@ $(document).ready(() => {
                 getColor: function (index, data) {
                     if (!data) return;
                     if(!data.ccid) return;
-                    return '#007bff';
+                    return '#dc3545';
                 }
             },
         },
-    ],
+    ];
     sjsSettingObj.setGridSelectStyle(posSpreadSetting);
     if (thousandth) sjsSettingObj.setTpThousandthFormat(posSpreadSetting);
     SpreadJsObj.initSpreadSettingEvents(posSpreadSetting, posCol);
@@ -125,7 +127,7 @@ $(document).ready(() => {
         tabSelector: '#error-list-tab',
         selector: '#error-list',
         relaSpread: billsSpread,
-        storeKey: 'revise-error-' + window.location.pathname.split('/')[2] + '-' + window.location.pathname.split('/')[4],
+        storeKey: 'change-revise-error-' + window.location.pathname.split('/')[2],
         afterLocated:  function () {
             posSpreadObj.loadCurPosData();
         },
@@ -139,7 +141,7 @@ $(document).ready(() => {
         tabSelector: '#check-list-tab',
         selector: '#check-list',
         relaSpread: billsSpread,
-        storeKey: 'revise-check-' + window.location.pathname.split('/')[2] + '-' + window.location.pathname.split('/')[4],
+        storeKey: 'change-revise-check-' + window.location.pathname.split('/')[2],
         checkType: getCheckType(checkOption),
         afterLocated:  function () {
             posSpreadObj.loadCurPosData();
@@ -153,7 +155,7 @@ $(document).ready(() => {
         tabSelector: '#sum-load-miss-tab',
         selector: '#sum-load-miss',
         relaSpread: billsSpread,
-        storeKey: 'revise-slm-' + window.location.pathname.split('/')[2] + '-' + window.location.pathname.split('/')[4],
+        storeKey: 'change-revise-slm-' + window.location.pathname.split('/')[2],
         id: 'revise-slm',
         afterLocated:  function () {
             posSpreadObj.loadCurPosData();
@@ -174,6 +176,8 @@ $(document).ready(() => {
         keys: ['id', 'tender_id', 'ledger_id'],
         calcFields: ['sgfh_tp', 'sjcl_tp', 'qtcl_tp', 'total_price'],
         autoExpand: 3,
+        markExpandKey: 'change-bills-expand',
+        markExpandSubKey: window.location.pathname.split('/')[2],
     };
     if (!isTz) {
         treeSetting.calcFields.push('deal_tp');
@@ -183,16 +187,21 @@ $(document).ready(() => {
             const posData = pos.getLedgerPos(node.id) || [];
             if (posData.length > 0) {
                 let sgfh_qty = 0;
-                const sgfh_qty_arr = [];
+                let sjcl_qty = 0;
+                let qtcl_qty = 0;
                 for (const np of posData) {
                     sgfh_qty = ZhCalc.add(sgfh_qty, np.sgfh_qty);
-                    sgfh_qty_arr.push(np.sgfh_qty);
+                    sjcl_qty = ZhCalc.add(sjcl_qty, np.sjcl_qty);
+                    qtcl_qty = ZhCalc.add(qtcl_qty, np.qtcl_qty);
                 }
-                // 只针对设计量值判断
-                if (!_.isEqual(node.sgfh_qty, sgfh_qty)) {
+                if (!_.isEqual(node.sgfh_qty, sgfh_qty) || !_.isEqual(node.sjcl_qty, sjcl_qty) || !_.isEqual(node.qtcl_qty, qtcl_qty)) {
                     node.sgfh_qty = sgfh_qty;
-                    node.sgfh_tp = ZhCalc.mul(sgfh_qty, node.unit_price, decimal.tp) || 0;
+                    node.sjcl_qty = sjcl_qty;
+                    node.qtcl_qty = qtcl_qty;
                     node.quantity = ZhCalc.sum([node.sgfh_qty, node.sjcl_qty, node.qtcl_qty]);
+                    node.sgfh_tp = ZhCalc.mul(sgfh_qty, node.unit_price, decimal.tp) || 0;
+                    node.sjcl_tp = ZhCalc.mul(sjcl_qty, node.unit_price, decimal.tp) || 0;
+                    node.qtcl_tp = ZhCalc.mul(qtcl_qty, node.unit_price, decimal.tp) || 0;
                     node.total_price = ZhCalc.mul(node.quantity, node.unit_price, decimal.tp) || 0;
                 }
             }
@@ -336,7 +345,7 @@ $(document).ready(() => {
                 && first.level > 2 && ((!posRange || posRange.length === 0) || tree.isLastSibling(last)) && upPower && first.ccid);
             const preNodePosRange = preNode ? pos.getLedgerPos(preNode.id) : [];
             setObjEnable($('a[name=base-opr][type=down-level]'), valid && first && sameParent
-                && first.level > 1 && preNode && (!preNodePosRange || preNodePosRange.length === 0) && !preNode.used && first.ccid);
+                && first.level > 1 && preNode && (preNode.children.length > 0 || (preNode.children.length === 0 && preNode.ccid && (!preNodePosRange || preNodePosRange.length === 0))) && !preNode.used && first.ccid);
             setObjEnable($('#cut'), valid);
             setObjEnable($('#paste'), valid);
         },
@@ -398,14 +407,17 @@ $(document).ready(() => {
             if (info.newSelections) {
                 if (!info.oldSelections || info.newSelections[0].row !== info.oldSelections[0].row || info.newSelections[0].rowCount !== info.oldSelections[0].rowCount) {
                     billsTreeSpreadObj.refreshOperationValid(info.sheet);
-                    SpreadJsObj.resetTopAndSelect(posSheet);
-                    posSpreadObj.loadCurPosData();
-                    SpreadJsObj.saveTopAndSelect(billsSheet, ckBillsSpread);
-                    posSearch.search($('#pos-keyword').val());
+                    billsTreeSpreadObj.refreshPosData();
                 }
             }
             billsTreeSpreadObj.loadExprToInput(info.sheet);
         },
+        refreshPosData: function() {
+            SpreadJsObj.resetTopAndSelect(posSheet);
+            posSpreadObj.loadCurPosData();
+            SpreadJsObj.saveTopAndSelect(billsSheet, ckBillsSpread);
+            posSearch.search($('#pos-keyword').val());
+        },
         /**
          * 新增节点
          * @param spread
@@ -468,6 +480,7 @@ $(document).ready(() => {
                         const sel = sheet.getSelections()[0];
                         if (sel) {
                             sheet.setSelection(sel.row, sel.col, 1, sel.colCount);
+                            billsTreeSpreadObj.refreshPosData();
                         }
                         self.refreshOperationValid(sheet);
                     });
@@ -493,6 +506,7 @@ $(document).ready(() => {
                         if (sel) {
                             sheet.setSelection(tree.nodes.indexOf(refreshData.create[0]), sel.col, sel.rowCount, sel.colCount);
                             SpreadJsObj.reloadRowsBackColor(sheet, [sel.row, tree.nodes.indexOf(refreshData.create[0])]);
+                            billsTreeSpreadObj.refreshPosData();
                         }
                     }
                     self.refreshOperationValid(sheet);
@@ -1386,12 +1400,10 @@ $(document).ready(() => {
             if (col && col.type === 'Number') {
                 const data = SpreadJsObj.getSelectObject(posSheet);
                 if (data) {
-                    console.log(data);
                     const exprInfo = getExprInfo(col.field);
                     const value = exprInfo
                         ? (data[exprInfo.expr] ? data[exprInfo.expr] : data[col.field])
                         : data[col.field];
-                    console.log(cell.locked(), !data.ccid);
                     $('#pos-expr').val(value).attr('field', col.field).attr('org', data[col.field])
                         .attr('readOnly', readOnly || (!data.ccid && cell.locked())).attr('data-row', sel.row);
                 } else {
@@ -1877,7 +1889,7 @@ $(document).ready(() => {
 
     // 加载清单&计量单元数据
     const preUrl = window.location.pathname.split('/').slice(0, 4).join('/');
-    postData(preUrl + '/defaultBills', {}, function (result) {
+    postData(preUrl + '/defaultBills', { from: 'revise' }, function (result) {
         billsTree.loadDatas(result.bills);
         pos.loadDatas(result.pos);
         treeCalc.calculateAll(billsTree);
@@ -1888,7 +1900,8 @@ $(document).ready(() => {
 
         posSpreadObj.loadCurPosData();
         SpreadJsObj.resetTopAndSelect(posSheet);
-
+        billsTreeSpreadObj.refreshOperationValid(billsSheet);
+        billsTreeSpreadObj.loadExprToInput(billsSheet);
         checkList.loadHisCheckData();
     }, null);
     $.divResizer({
@@ -2454,6 +2467,8 @@ $(document).ready(() => {
                     searchLedger = $.billsSearch({
                         selector: '#search',
                         searchSpread: billsSpread,
+                        searchOver: true,
+                        searchEmpty: true,
                         resultSpreadSetting: {
                             cols: [
                                 {title: '项目节编号', field: 'code', hAlign: 0, width: 90, formatter: '@', readOnly: true},
@@ -2474,7 +2489,19 @@ $(document).ready(() => {
                         },
                         afterLocated: function () {
                             posSpreadObj.loadCurPosData();
-                        }
+                        },
+                        customSearch: [
+                            {
+                                key: 'revise', title: '新增部位', valid: true, parent: true,
+                                check: function (node) {
+                                    if (node.ccid || node.cid) {
+                                        return true;
+                                    } else {
+                                        return false;
+                                    }
+                                }
+                            }
+                        ],
                     });
                 }
                 searchLedger.spread.refresh();

+ 4 - 1
app/public/js/ledger.js

@@ -2053,6 +2053,7 @@ $(document).ready(function() {
                                     }
                                     bPaste = true;
                                 } catch (err) {
+
                                     delete posData[colSetting.field];
                                     toastMessageUniq(hint.expr);
                                 }
@@ -2061,6 +2062,8 @@ $(document).ready(function() {
                             if (!posData[colSetting.field]) {
                                 delete posData[colSetting.field];
                                 toastMessageUniq(hint.name);
+                                bPaste = false;
+                                break;
                             } else {
                                 bPaste = true;
                             }
@@ -2693,7 +2696,7 @@ $(document).ready(function() {
                     const file = $('#deal-bills-file')[0];
                     const formData = new FormData();
                     formData.append('file', file.files[0]);
-                    postDataWithFileProgress(self.url+'/upload-excel', formData, function (data) {
+                    postDataWithFile(self.url+'/upload-excel', formData, function (data) {
                         self.data = data;
                         SpreadJsObj.loadSheetData(self.spread.getActiveSheet(), 'data', data);
                         $('#upload-deal').modal('hide');

+ 21 - 0
app/public/js/material.js

@@ -1239,6 +1239,16 @@ $(document).ready(() => {
         }
     });
 
+    $.divResizer({
+        select: '#main-resize',
+        callback: function () {
+            materialSpread.refresh();
+            materialMonthSpread.refresh();
+            const height = $('#material-spread').height();
+            setLocalCache('material_bills_' + materialID, height);
+        }
+    });
+
     // 展开收起月信息价并浏览器记住本期展开收起
     $('a', '.right-nav').bind('click', function () {
         //const main = $('#main-view'), tool = $('#tools-view');
@@ -1275,4 +1285,15 @@ $(document).ready(() => {
         materialSpread.refresh();
         materialMonthSpread.refresh();
     }
+    if (getLocalCache('material_bills_' + materialID)) {
+        $('#material-spread').height(getLocalCache('material_bills_' + materialID));
+        const cHeader = getObjHeight($(".c-header"));
+        const sjs1 = getObjHeight($('.sjs-height-1'));
+        $(".bcontent-wrap").height($(window).height()-cHeader-sjs1-90+53);
+        materialSpread.refresh();
+        materialMonthSpread.refresh();
+    }
+    function getObjHeight(select) {
+        return select.length > 0 ? select.height() : 0;
+    }
 });

+ 49 - 1
app/public/js/material_exponent.js

@@ -480,7 +480,7 @@ $(document).ready(() => {
 
         // 调差基数选中
         $('.calc_select').on('click', function () {
-            // 如果是选中则清除其余2个的选中
+            // 如果是选中则清除其余的选中
             const code = $(this).val();
             for (const calc of ex_calc) {
                 calc.select = $(this).is(':checked') && code === calc.code ? true : false;
@@ -494,6 +494,35 @@ $(document).ready(() => {
                 resetExTpTable();
             });
         });
+        // 回车提交
+        $('#calc_zdy').on('keypress', function () {
+            if(window.event.keyCode === 13) {
+                $(this).blur();
+            }
+        });
+        // 自定义金额变更并提交
+        $('#calc_zdy').on('blur', function () {
+            let newValue = parseFloat($(this).val());
+            // 判断输入位数,并自动四舍五入
+            newValue = ZhCalc.round(newValue, decimal.tp);
+            $('#calc_zdy').val(newValue);
+            if (isNaN(newValue)) {
+                toastr.error('请输入正确的金额');
+                return false;
+            }
+            const zdy = _.find(ex_calc, { code: 'zdy' });
+            if (zdy.value === newValue) {
+                return;
+            } else {
+                zdy.value = newValue;
+            }
+            postData(window.location.pathname + '/save', { type:'ex_calc', updateData: ex_calc }, function (result) {
+                ex_tp = result.ex_tp;
+                ex_expr = result.ex_expr;
+                resetExTpTable();
+            });
+        });
+
 
         $('#changeRate').change(function () {
             const rate = parseInt($(this).val());
@@ -519,6 +548,15 @@ $(document).ready(() => {
         }
     });
 
+    $.divResizer({
+        select: '#main-resize',
+        callback: function () {
+            materialExponentSpread.refresh();
+            const height = $('#material-exponent-spread').height();
+            setLocalCache('material_exponent2_' + materialID, height);
+        }
+    });
+
     // 展开收起月信息价并浏览器记住本期展开收起
     $('a', '.right-nav').bind('click', function () {
         //const main = $('#main-view'), tool = $('#tools-view');
@@ -552,4 +590,14 @@ $(document).ready(() => {
         showSideTools(tab.hasClass('active'));
         materialExponentSpread.refresh();
     }
+    if (getLocalCache('material_exponent2_' + materialID)) {
+        $('#material-exponent-spread').height(getLocalCache('material_exponent2_' + materialID));
+        var cHeader = getObjHeight($(".c-header"));
+        var sjs1 = getObjHeight($('.sjs-height-1'));
+        $(".bcontent-wrap").height($(window).height()-cHeader-sjs1-90+53);
+        materialExponentSpread.refresh();
+    }
+    function getObjHeight(select) {
+        return select.length > 0 ? select.height() : 0;
+    }
 });

+ 26 - 0
app/public/js/material_list.js

@@ -1076,6 +1076,19 @@ $(document).ready(() => {
         }
     });
 
+    $.divResizer({
+        select: '#main-resize',
+        callback: function () {
+            materialSpread.refresh();
+            ledgerSpread.refresh();
+            let bcontent = $(".bcontent-wrap") ? $(".bcontent-wrap").height() : 0;
+            $(".sp-wrap").height(bcontent-30);
+            leafXmjSpread.refresh();
+            const height = $('.bcontent-wrap').height();
+            setLocalCache('material_list2_' + materialID, height);
+        }
+    });
+
     // 展开收起月信息价并浏览器记住本期展开收起
     $('a', '.right-nav').bind('click', function () {
         //const main = $('#main-view'), tool = $('#tools-view');
@@ -1113,4 +1126,17 @@ $(document).ready(() => {
         // leafXmjSpread.refresh();
         materialSpread.refresh();
     }
+    if (getLocalCache('material_list2_' + materialID)) {
+        $('.bcontent-wrap').height(getLocalCache('material_list2_' + materialID));
+        const cHeader = getObjHeight($(".c-header"));
+        const bcontent = $(".bcontent-wrap") ? $(".bcontent-wrap").height() : 0;
+        $(".sp-wrap").height(bcontent-30);
+        $('.sjs-height-1').height($(window).height()-cHeader-bcontent-90+53);
+        materialSpread.refresh();
+        ledgerSpread.refresh();
+        leafXmjSpread.refresh();
+    }
+    function getObjHeight(select) {
+        return select.length > 0 ? select.height() : 0;
+    }
 });

+ 100 - 1
app/public/js/path_tree.js

@@ -1699,7 +1699,7 @@ const createNewPathTree = function (type, setting) {
                 for (const c of node.children) {
                     addSortNode(c);
                 }
-            }
+            };
             this.nodes = [];
             for (const n of this.children) {
                 addSortNode(n);
@@ -1737,6 +1737,103 @@ const createNewPathTree = function (type, setting) {
         }
     }
 
+    class FinalTree extends BaseTree {
+        constructor(setting) {
+            super(setting);
+            this._newId = 1;
+        }
+        get newId() {
+            return this._newId++;
+        }
+
+        loadNode(node, parent, loadFun) {
+            if (node.b_code) return;
+            const siblings = parent ? parent.children : this.children;
+            let cur = siblings.find(function (x) {
+                return x.code === node.code && x.name === node.name;
+            });
+            if (!cur) {
+                const id = this.newId;
+                cur = {
+                    id: id,
+                    pid: parent ? parent.id : this.setting.rootId,
+                    full_path: parent ? parent.full_path + '-' + id : '' + id,
+                    level: parent ? parent.level + 1 : 1,
+                    order: siblings.length + 1,
+                    children: [],
+                    code: node.code, name: node.name, unit: node.unit,
+                };
+                siblings.push(cur);
+                this.datas.push(cur);
+            }
+            loadFun(cur, node);
+            for (const c of node.children) {
+                this.loadNode(c, cur, loadFun);
+            }
+        }
+        loadTree(tree, loadFun) {
+            for (const node of tree.children) {
+                this.loadNode(node, null, loadFun);
+            }
+        }
+
+        generateSortNodes() {
+            const self = this;
+            const addSortNode = function (node) {
+                self.nodes.push(node);
+                for (const c of node.children) {
+                    addSortNode(c);
+                }
+            };
+            this.nodes = [];
+            for (const n of this.children) {
+                addSortNode(n);
+            }
+        }
+        afterLoad(fun) {
+            for (const d of this.datas) {
+                fun && fun(d);
+                d.is_leaf = d.children.length === 0;
+                d.expanded = true;
+                d.visible = true;
+                this.items[itemsPre + d[this.setting.id]] = d;
+            }
+            this.generateSortNodes();
+        }
+        clearFinal() {
+            this.datas = this.datas.filter(x => {
+                return !!x.base;
+            });
+            this.datas.forEach(x => {
+                delete x.total_price;
+                delete x.dgn_qty1;
+                delete x.dgn_qty2;
+                delete x.dgn_qty;
+                delete x.dgn_price;
+
+                delete x.final_dgn_qty1;
+                delete x.final_dgn_qty2;
+                delete x.final_dgn_qty;
+                delete x.final_dgn_price;
+                delete x.final_tp;
+
+                delete x.grow_dgn_qty1;
+                delete x.grow_dgn_qty2;
+                delete x.grow_dgn_qty;
+                delete x.grow_tp;
+            });
+        }
+        resortChildrenByCustom(fun) {
+            for (const n of this.nodes) {
+                if (n.children && n.children.length > 1) {
+                    n.children.sort(fun);
+                    n.children.forEach((x, y) => { x.order = y + 1; });
+                }
+            }
+            this.generateSortNodes();
+        }
+    }
+
     if (type === 'base') {
         return new BaseTree(setting);
     } else if (type === 'fx') {
@@ -1759,6 +1856,8 @@ const createNewPathTree = function (type, setting) {
         return new CompareTree(setting);
     } else if (type === 'tree-gather') {
         return new TreeGatherTree(setting);
+    } else if (type === 'final') {
+        return new FinalTree(setting);
     }
 };
 

+ 5 - 3
app/public/js/revise.js

@@ -119,6 +119,8 @@ $(document).ready(() => {
         keys: ['id', 'tender_id', 'ledger_id'],
         calcFields: ['sgfh_tp', 'sjcl_tp', 'qtcl_tp', 'total_price'],
         autoExpand: 3,
+        markExpandKey: 'revise-bills-expand',
+        markExpandSubKey: window.location.pathname.split('/')[2],
     };
     if (!isTz) {
         treeSetting.calcFields.push('deal_tp');
@@ -126,7 +128,7 @@ $(document).ready(() => {
     treeSetting.calcFun = function (node) {
         node.dgn_price = ZhCalc.round(ZhCalc.div(node.total_price, node.dgn_qty1), 2);
     };
-    const billsTree = createNewPathTree('revise', treeSetting);    
+    const billsTree = createNewPathTree('revise', treeSetting);
     // 初始化 计量单元
     const pos = new PosData({ id: 'id', ledgerId: 'lid' });
 
@@ -1696,7 +1698,7 @@ $(document).ready(() => {
                     const curCol = info.cellRange.col + iCol;
                     const colSetting = info.sheet.zh_setting.cols[curCol];
                     if (!colSetting) continue;
-                    
+
                     posData[colSetting.field] = trimInvalidChar(info.sheet.getText(curRow, curCol));
                     if (colSetting.type === 'Number') {
                         const num = _.toNumber(posData[colSetting.field]);
@@ -1869,7 +1871,7 @@ $(document).ready(() => {
         SpreadJsObj.resetTopAndSelect(posSheet);
 
         checkList.loadHisCheckData();
-    }, null);    
+    }, null);
     $.divResizer({
         select: '#revise-resize',
         callback: function () {

+ 10 - 4
app/public/js/shares/cs_tools.js

@@ -483,11 +483,12 @@ const showSelectTab = function(select, spread, afterShow) {
     $.billsSearch = function (setting) {
         if (!setting.selector || !setting.searchSpread || !setting.resultSpreadSetting) return;
         if (!setting.searchRangeStr) setting.searchRangeStr = '项目节编号/清单编号/名称/台账数量';
+        if (!setting.keyId) setting.keyId = 'ledger_id';
         const resultId = setting.id + '-search-result';
         const obj = $(setting.selector);
         let filter = [];
         if (setting.searchOver || setting.searchEmpty) {
-            filter.push('<select class="form-control form-control-sm" id="search-filter">');
+            filter.push('<select class="input-group-text" id="search-filter">');
             filter.push('<option value="">台账</option>');
             if (setting.customSearch) {
                 for (const cs of setting.customSearch) {
@@ -546,19 +547,24 @@ const showSelectTab = function(select, spread, afterShow) {
             const cs = setting.customSearch.find(function (x) {return x.key === key});
             return cs ? cs.check : null;
         };
+        const getParantFun = function (key) {
+            const cs = setting.customSearch.find(function (x) {return x.key === key});
+            return cs && cs.parent !== undefined ? cs.parent : false;
+        };
         const searchCustom = function (key) {
             const keyword = $('#searchKeyword', obj).val();
             const keyNum = _.toNumber(keyword);
             const checkFun = getCheckFun(key);
             searchResult = [];
             const sortData = SpreadJsObj.getSortData(searchSheet);
+            const parantFun = getParantFun(key);
             for (const node of sortData) {
-                if (node.children && node.children.length > 0) continue;
+                if (node.children && node.children.length > 0 && !parantFun) continue;
                 if (checkFun && checkFun(node)) {
                     if (!keyword ||
                         (node.code && node.code.indexOf(keyword) > -1) ||
                         (node.b_code && node.b_code.indexOf(keyword) > -1) ||
-                        (node.name && node.name.indexOf(keyword) > -1)
+                        (node.name && node.name.indexOf(keyword) > -1) ||
                         (!_.isNaN(keyNum) && checkZero(ZhCalc.sub(keyNum, node.quantity)))
                     ) {
                         const data = JSON.parse(JSON.stringify(node));
@@ -590,7 +596,7 @@ const showSelectTab = function(select, spread, afterShow) {
             const curBills = data[info.row];
             if (!curBills) { return }
 
-            SpreadJsObj.locateTreeNode(searchSheet, curBills.ledger_id, true);
+            SpreadJsObj.locateTreeNode(searchSheet, curBills[setting.keyId], true);
             if (setting.afterLocated) {
                 setting.afterLocated();
             }

+ 14 - 1
app/public/js/shares/sjs_setting.js

@@ -67,5 +67,18 @@ const sjsSettingObj = (function () {
             if (col) col[prop] = value;
         }
     };
-    return {setFxTreeStyle, FxTreeStyle, setGridSelectStyle, setTpThousandthFormat, setThousandthFormat, setTpColsThousandthFormat, setPropValue};
+    const set3FCols = function (cols, rela) {
+        for (const r of rela) {
+            const col = _.find(cols, {field: r.field});
+            if (col) {
+                col.getValue = r.getValue;
+                col.cellType = 'activeImageBtn';
+                col.normalImg = '#rela-file-icon';
+                col.indent = 5;
+                col.imgAlign = 2;
+                col.showImage = function (data) { return data && data[r.url_field]; }
+            }
+        }
+    };
+    return {setFxTreeStyle, FxTreeStyle, setGridSelectStyle, setTpThousandthFormat, setThousandthFormat, setTpColsThousandthFormat, setPropValue, set3FCols};
 })();

+ 10 - 5
app/public/js/spreadjs_rela/spreadjs_zh.js

@@ -716,12 +716,12 @@ const SpreadJsObj = {
      * 整个sheet重新加载数据
      * @param {GC.Spread.Sheets.Worksheet} sheet
      */
-    reLoadSheetData: function (sheet) {
+    reLoadSheetData: function (sheet, force = false) {
         const self = this;
         const sortData = sheet.zh_dataType === 'tree' ? sheet.zh_tree.nodes : sheet.zh_data;
         this.beginMassOperation(sheet);
         try {
-            sheet.setRowCount(0, spreadNS.SheetArea.viewport);
+            force && sheet.setRowCount(0, spreadNS.SheetArea.viewport);
             sheet.clear(0, 0, sheet.getRowCount(), sheet.getColumnCount(), spreadNS.SheetArea.viewport, spreadNS.StorageType.data);
             sheet.getRange(0, 0, sheet.getRowCount(), sheet.getColumnCount()).backColor(sheet.getDefaultStyle().backColor);
             // 设置总行数
@@ -898,7 +898,7 @@ const SpreadJsObj = {
      * @param {String} dataType - 1.'zh_data' 2.'zh_tree'
      * @param {Array|PathTree} data - 对dataType对应
      */
-    loadSheetData: function (sheet, dataType, data){
+    loadSheetData: function (sheet, dataType, data, force = false){
         sheet.zh_dataType = dataType;
         if (dataType === 'tree') {
             sheet.zh_tree = data;
@@ -906,7 +906,7 @@ const SpreadJsObj = {
             sheet.zh_data = data;
         }
         this.protectedSheet(sheet);
-        this.reLoadSheetData(sheet);
+        this.reLoadSheetData(sheet, force);
     },
     /**
      * 获取复制数据HTML格式(过滤不可见单元格)
@@ -1288,7 +1288,12 @@ const SpreadJsObj = {
                     canvas.fillRect(x, y, w, h);
                     canvas.restore();
                 } else {
-                    canvas.clearRect(x, y, w, h);
+                    // 不知道为什么在 概算投资-造价对比 下,这一句代码无法生效。
+                    // canvas.clearRect(x, y, w, h);
+                    canvas.save();
+                    canvas.fillStyle = 'white';
+                    canvas.fillRect(x, y, w, h);
+                    canvas.restore();
                 }
 
 

+ 11 - 23
app/public/js/sr_detail.js

@@ -207,6 +207,7 @@ $(document).ready(() => {
     ledgerSpreadSetting.imageClick = function (data, hitinfo) {
         const col = hitinfo.sheet.zh_setting.cols[hitinfo.col];
         if (col.field === 'dagl') data.dagl_url && window.open(data.dagl_url);
+        if (col.field === 'gxby') data.gxby_url && window.open(data.gxby_url);
     };
     ledgerSpreadSetting.dgnUpFields = ['deal_dgn_qty1', 'deal_dgn_qty2', 'c_dgn_qty1', 'c_dgn_qty2'];
     ledgerSpreadSetting.getColor = function (sheet, data, row, col, defaultColor) {
@@ -229,17 +230,10 @@ $(document).ready(() => {
         }
     };
     sjsSettingObj.setFxTreeStyle(ledgerSpreadSetting, sjsSettingObj.FxTreeStyle.jz);
-    sjsSettingObj.setPropValue(ledgerSpreadSetting, ['gxby'], 'getValue', getGxbyText);
-    sjsSettingObj.setPropValue(ledgerSpreadSetting, ['dagl'], 'getValue', getDaglText);
-    const lDaglCol = _.find(ledgerSpreadSetting.cols, {field: 'dagl'});
-    if (lDaglCol) {
-        lDaglCol.getValue = getDaglText;
-        lDaglCol.cellType = 'activeImageBtn';
-        lDaglCol.normalImg = '#rela-file-icon';
-        lDaglCol.indent = 5;
-        lDaglCol.imgAlign = 2;
-        lDaglCol.showImage = function (data) { return data && data.dagl_url; }
-    }
+    sjsSettingObj.set3FCols(ledgerSpreadSetting.cols, [
+        {field: 'gxby', getValue: getGxbyText, url_field: 'gxby_url'},
+        {field: 'dagl', getValue: getDaglText, url_field: 'dagl_url'},
+    ]);
     if (thousandth) sjsSettingObj.setTpThousandthFormat(ledgerSpreadSetting);
     ledgerSpreadSetting.headColWidth = [50];
     ledgerSpreadSetting.rowHeader = [
@@ -290,6 +284,7 @@ $(document).ready(() => {
     posSpreadSetting.imageClick = function (data, hitinfo) {
         const col = hitinfo.sheet.zh_setting.cols[hitinfo.col];
         if (col.field === 'dagl') data.dagl_url && window.open(data.dagl_url);
+        if (col.field === 'gxby') data.gxby_url && window.open(data.gxby_url);
     };
     posSpreadSetting.getColor = function (sheet, data, row, col, defaultColor) {
         if (data) {
@@ -311,17 +306,10 @@ $(document).ready(() => {
     };
     sjsSettingObj.setGridSelectStyle(posSpreadSetting);
     if (thousandth) sjsSettingObj.setTpThousandthFormat(posSpreadSetting);
-    sjsSettingObj.setPropValue(posSpreadSetting, ['gxby'], 'getValue', getGxbyText);
-    sjsSettingObj.setPropValue(posSpreadSetting, ['dagl'], 'getValue', getDaglText);
-    const pDaglCol = _.find(posSpreadSetting.cols, {field: 'dagl'});
-    if (pDaglCol) {
-        pDaglCol.getValue = getDaglText;
-        pDaglCol.cellType = 'activeImageBtn';
-        pDaglCol.normalImg = '#rela-file-icon';
-        pDaglCol.indent = 5;
-        pDaglCol.imgAlign = 2;
-        pDaglCol.showImage = function (data) { return data && data.dagl_url; }
-    }
+    sjsSettingObj.set3FCols(posSpreadSetting.cols, [
+        {field: 'gxby', getValue: getGxbyText, url_field: 'gxby_url'},
+        {field: 'dagl', getValue: getDaglText, url_field: 'dagl_url'},
+    ]);
     SpreadJsObj.initSheet(spSpread.getActiveSheet(), posSpreadSetting);
 
     const billsTag = $.billsTag({
@@ -798,7 +786,7 @@ $(document).ready(() => {
             let html = [];
             if (data.attachment) {
                 for (const att of data.attachment) {
-                    const delHtml = (parseInt(att.uid) === userID && (att.renew || stage.status !== auditConst.status.checked))
+                    const delHtml = (parseInt(att.uid) === userID && att.renew)
                         ? '<a class="delete-att text-danger ml-1" href="javascript:void(0);" data-imid="'+ data.att_uuid +'" data-attid="'+ att.file_id +'" title="删除"><i class="fa fa-remove "></i></a>'
                         : '';
                     const viewHtml = att.viewpath ? `<a class="ml-1" href="${att.viewpath}" target="_blank" title="预览"><i class="fa fa-eye"></i></a>` : '';

+ 31 - 44
app/public/js/stage.js

@@ -650,15 +650,16 @@ $(document).ready(() => {
     ratioCol.field = tenderInfo.display.stage.correct ? 'end_correct_percent' : 'end_gather_percent';
     ledgerSpreadSetting.imageClick = function (data, hitinfo) {
         const col = hitinfo.sheet.zh_setting.cols[hitinfo.col];
-        if (col.field === 'dagl') {
-            data.dagl_url && window.open(data.dagl_url);
-        } else if (col.field === 'qc_qty') {
-            if (data.children && data.children.length > 0 || data.lock) return;
-
-            const nodePos = stagePos.getLedgerPos(data.id);
-            if (nodePos && nodePos.length > 0) return;
-
-            changesObj.loadChanges({bills: data});
+        switch (col.field) {
+            case 'dagl': data.dagl_url && window.open(data.dagl_url); break;
+            case 'gxby': data.gxby_url && window.open(data.gxby_url); break;
+            case 'qc_qty':
+                if (data.children && data.children.length > 0 || data.lock) return;
+                const nodePos = stagePos.getLedgerPos(data.id);
+                if (nodePos && nodePos.length > 0) return;
+                changesObj.loadChanges({bills: data});
+                break;
+            default: return;
         }
     };
     ledgerSpreadSetting.dgnUpFields = ['deal_dgn_qty1', 'deal_dgn_qty2', 'c_dgn_qty1', 'c_dgn_qty2'];
@@ -682,17 +683,10 @@ $(document).ready(() => {
         }
     };
     sjsSettingObj.setFxTreeStyle(ledgerSpreadSetting, sjsSettingObj.FxTreeStyle.jz);
-    sjsSettingObj.setPropValue(ledgerSpreadSetting, ['gxby'], 'getValue', getGxbyText);
-    sjsSettingObj.setPropValue(ledgerSpreadSetting, ['dagl'], 'getValue', getDaglText);
-    const lDaglCol = _.find(ledgerSpreadSetting.cols, {field: 'dagl'});
-    if (lDaglCol) {
-        lDaglCol.getValue = getDaglText;
-        lDaglCol.cellType = 'activeImageBtn';
-        lDaglCol.normalImg = '#rela-file-icon';
-        lDaglCol.indent = 5;
-        lDaglCol.imgAlign = 2;
-        lDaglCol.showImage = function (data) { return data && data.dagl_url; }
-    }
+    sjsSettingObj.set3FCols(ledgerSpreadSetting.cols, [
+        {field: 'gxby', getValue: getGxbyText, url_field: 'gxby_url'},
+        {field: 'dagl', getValue: getDaglText, url_field: 'dagl_url'},
+    ]);
     if (thousandth) sjsSettingObj.setTpThousandthFormat(ledgerSpreadSetting);
     ledgerSpreadSetting.headColWidth = [50];
     ledgerSpreadSetting.rowHeader = [
@@ -747,12 +741,15 @@ $(document).ready(() => {
     };
     posSpreadSetting.imageClick = function (data, hitinfo) {
         const col = hitinfo.sheet.zh_setting.cols[hitinfo.col];
-        if (col.field === 'dagl') {
-            data.dagl_url && window.open(data.dagl_url);
-        } else if (col.field === 'qc_qty') {
-            const node = SpreadJsObj.getSelectObject(slSpread.getActiveSheet());
-            if (node.lock) return;
-            changesObj.loadChanges({bills: node, pos: data});
+        switch (col.field) {
+            case 'gxby': data.gxby_url && window.open(data.gxby_url); break;
+            case 'dagl': data.dagl_url && window.open(data.dagl_url); break;
+            case 'qc_qty':
+                const node = SpreadJsObj.getSelectObject(slSpread.getActiveSheet());
+                if (node.lock) return;
+                changesObj.loadChanges({bills: node, pos: data});
+                break;
+            default: return;
         }
     };
     posSpreadSetting.getColor = function (sheet, data, row, col, defaultColor) {
@@ -775,17 +772,10 @@ $(document).ready(() => {
     };
     sjsSettingObj.setGridSelectStyle(posSpreadSetting);
     if (thousandth) sjsSettingObj.setTpThousandthFormat(posSpreadSetting);
-    sjsSettingObj.setPropValue(posSpreadSetting, ['gxby'], 'getValue', getGxbyText);
-    sjsSettingObj.setPropValue(posSpreadSetting, ['dagl'], 'getValue', getDaglText);
-    const pDaglCol = _.find(posSpreadSetting.cols, {field: 'dagl'});
-    if (pDaglCol) {
-        pDaglCol.getValue = getDaglText;
-        pDaglCol.cellType = 'activeImageBtn';
-        pDaglCol.normalImg = '#rela-file-icon';
-        pDaglCol.indent = 5;
-        pDaglCol.imgAlign = 2;
-        pDaglCol.showImage = function (data) { return data && data.dagl_url; }
-    }
+    sjsSettingObj.set3FCols(posSpreadSetting.cols, [
+        {field: 'gxby', getValue: getGxbyText, url_field: 'gxby_url'},
+        {field: 'dagl', getValue: getDaglText, url_field: 'dagl_url'},
+    ]);
     SpreadJsObj.initSheet(spSpread.getActiveSheet(), posSpreadSetting);
 
     const billsTag = $.billsTag({
@@ -1624,17 +1614,14 @@ $(document).ready(() => {
             const sheet = slSpread.getActiveSheet();
             const node = SpreadJsObj.getSelectObject(sheet);
             if (node) {
-                spSpread.getActiveSheet().zh_setting.readOnly = node.lock || (node.children && node.children.length > 0);
+                const posReadOnly = node.lock || (node.children && node.children.length > 0);
+                spSpread.getActiveSheet().zh_setting.readOnly = posReadOnly;
                 const posData = stagePos.ledgerPos[itemsPre + node.id] || [];
-                SpreadJsObj.loadSheetData(spSpread.getActiveSheet(), 'data', posData);
+                SpreadJsObj.loadSheetData(spSpread.getActiveSheet(), 'data', posData, posReadOnly);
                 getNodeList(node.id);
-                // 如果是附件是当前节点,隐藏
-                // if ($('#dqjiedian').hasClass('active')) {
-                //     $('#showAttachment').hide();
-                // }
             } else {
                 spSpread.getActiveSheet().zh_setting.readOnly = true;
-                SpreadJsObj.loadSheetData(spSpread.getActiveSheet(), 'data', []);
+                SpreadJsObj.loadSheetData(spSpread.getActiveSheet(), 'data', [], true);
             }
             stagePosSpreadObj.loadExprToInput(spSpread.getActiveSheet());
         },

+ 2 - 1
app/public/js/std_lib.js

@@ -17,6 +17,7 @@ class stdLib {
         const self = this;
         this.setting = setting;
         this.obj = $(setting.selector + '-spread')[0];
+        $('select', setting.selector).parent().parent().height(32);
         this.stdType = setting.stdType;
         this.treeSetting = setting.treeSetting;
         this.spreadSetting = setting.spreadSetting;
@@ -24,7 +25,7 @@ class stdLib {
         this.spread = SpreadJsObj.createNewSpread(this.obj);
         SpreadJsObj.initSheet(this.spread.getActiveSheet(), this.spreadSetting);
         SpreadJsObj.forbiddenSpreadContextMenu(setting.selector, this.spread);
-        this.spread.getActiveSheet().bind(spreadNS.Events.CellDoubleClick, setting.cellDoubleClick);
+        if (setting.cellDoubleClick) this.spread.getActiveSheet().bind(spreadNS.Events.CellDoubleClick, setting.cellDoubleClick);
         this.spread.getActiveSheet().bind(spreadNS.Events.SelectionChanged, function (e, info) {
             if (!info.oldSelections[0] || info.newSelections[0].row !== info.oldSelections[0].row) {
                 SpreadJsObj.saveTopAndSelect(info.sheet, self.cacheKey.node);

+ 18 - 0
app/router.js

@@ -29,6 +29,7 @@ module.exports = app => {
     const scheduleCheck = app.middlewares.scheduleCheck();
     // 修订
     const reviseCheck = app.middlewares.reviseCheck();
+    const budgetCheck = app.middlewares.budgetCheck();
     // 登入登出相关
     app.get('/login', 'loginController.index');
     app.get('/login/port', api2otherCheck, 'loginController.port');
@@ -573,4 +574,21 @@ module.exports = app => {
 
     // 决策大屏
     app.get('/datacollect', sessionAuth, 'datacollectController.index');
+
+    // 概算投资
+    app.get('/budget', sessionAuth, 'budgetController.list');
+    app.post('/budget/add', sessionAuth, projectManagerCheck, 'budgetController.add');
+    app.post('/budget/del', sessionAuth, projectManagerCheck, 'budgetController.del');
+    app.post('/budget/save', sessionAuth, 'budgetController.save');
+    app.post('/budget/rela', sessionAuth, 'budgetController.rela');
+    app.post('/budget/member', sessionAuth, projectManagerCheck, 'budgetController.member');
+    app.post('/budget/member-save', sessionAuth, projectManagerCheck, 'budgetController.memberSave');
+    app.get('/budget/:id/compare', sessionAuth, budgetCheck, 'budgetController.compare');
+    app.post('/budget/:id/compare/load', sessionAuth, budgetCheck, 'budgetController.compareLoad');
+    app.post('/budget/:id/compare/final', sessionAuth, budgetCheck, 'budgetController.compareFinal');
+    app.get('/budget/:id/:btype', sessionAuth, budgetCheck, 'budgetController.detail');
+    app.post('/budget/:id/:btype/load', sessionAuth, budgetCheck, 'budgetController.detailLoad');
+    app.post('/budget/:id/:btype/update', sessionAuth, budgetCheck, 'budgetController.detailUpdate');
+    app.post('/budget/:id/:btype/upload-excel/:ueType', sessionAuth, budgetCheck, 'budgetController.detailUploadExcel');
+    app.post('/budget/:id/decimal', sessionAuth, budgetCheck, 'budgetController.decimal');
 };

+ 12 - 18
app/service/advance_audit.js

@@ -22,8 +22,8 @@ module.exports = app => {
         async getAuditGroupByList(vid, times = 1) {
             const sql =
                 'SELECT la.`audit_id`, pa.`name`, pa.`company`, pa.`role`, la.`times`, la.`vid`, la.`order` ' +
-                'FROM ?? AS la, ?? AS pa ' +
-                'WHERE la.`vid` = ? and la.`times` = ? and la.`audit_id` = pa.`id` GROUP BY la.`audit_id` ORDER BY la.`order`';
+                '  FROM ?? AS la Left Join ?? AS pa On la.`audit_id` = pa.`id`' +
+                '  WHERE la.`vid` = ? and la.`times` = ? GROUP BY la.`audit_id` ORDER BY la.`order`';
             const sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, vid, times];
             return await this.db.query(sql, sqlParam);
         }
@@ -38,12 +38,8 @@ module.exports = app => {
             const result = await this.getAuditGroupByList(vid, times);
             const sql =
                 'SELECT pa.`id` As audit_id, pa.`name`, pa.`company`, pa.`role`, ? As times, ? As vid, 0 As `order`' +
-                '  FROM ' +
-                this.ctx.service.advance.tableName +
-                ' As s' +
-                '  LEFT JOIN ' +
-                this.ctx.service.projectAccount.tableName +
-                ' As pa' +
+                '  FROM ' + this.ctx.service.advance.tableName + ' As s' +
+                '  LEFT JOIN ' + this.ctx.service.projectAccount.tableName + ' As pa' +
                 '  ON s.uid = pa.id' +
                 '  WHERE s.id = ?';
             const sqlParam = [times, vid, vid];
@@ -164,9 +160,8 @@ module.exports = app => {
         async getCurAuditor(vid, times = 1) {
             const sql =
                 'SELECT la.`audit_id`, pa.`name`, pa.`company`, pa.`role`, pa.`mobile`, pa.`telephone`, la.`times`, la.`order`, la.`status`, la.`opinion`, la.`create_time`, la.`end_time` ' +
-                'FROM ?? AS la, ?? AS pa ' +
-                'WHERE la.`vid` = ? and la.`status` = ? and la.`times` = ?' +
-                '    and la.`audit_id` = pa.`id`';
+                '  FROM ?? AS la Left Join ?? AS pa On la.`audit_id` = pa.`id` ' +
+                '  WHERE la.`vid` = ? and la.`status` = ? and la.`times` = ?';
             const sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, vid, auditConst.status.checking, times];
             return await this.db.queryOne(sql, sqlParam);
         }
@@ -203,9 +198,8 @@ module.exports = app => {
         async getAuditor(vid, audit_id, times = 1) {
             const sql =
                 'SELECT la.`audit_id`, pa.`name`, pa.`company`, pa.`role`, pa.`mobile`, pa.`telephone`, la.`times`, la.`order`, la.`status`, la.`opinion`, la.`create_time`, la.`end_time` ' +
-                'FROM ?? AS la, ?? AS pa ' +
-                'WHERE la.`vid` = ? and la.`audit_id` = ? and la.`times` = ?' +
-                '    and la.`audit_id` = pa.`id`';
+                '  FROM ?? AS la Left Join ?? AS pa On la.`audit_id` = pa.`id` ' +
+                '  WHERE la.`vid` = ? and la.`audit_id` = ? and la.`times` = ?';
             const sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, vid, audit_id, times];
             return await this.db.queryOne(sql, sqlParam);
         }
@@ -537,16 +531,16 @@ module.exports = app => {
                 case auditConst.status.checkNoPre:
                     sql =
                         'SELECT la.`audit_id`, pa.`name`, pa.`company`, pa.`role`, la.`times`, la.`vid`, la.`order` ' +
-                        'FROM ?? AS la, ?? AS pa ' +
-                        'WHERE la.`vid` = ? and la.`status` = ? and la.`audit_id` = pa.`id` order by la.`times` desc, la.`order` desc';
+                        '  FROM ?? AS la Left Join ?? AS pa On la.`audit_id` = pa.`id` ' +
+                        '  WHERE la.`vid` = ? and la.`status` = ? order by la.`times` desc, la.`order` desc';
                     sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, vid, status];
                     auditor = await this.db.queryOne(sql, sqlParam);
                     break;
                 case auditConst.status.checkNo:
                     sql =
                         'SELECT la.`audit_id`, pa.`name`, pa.`company`, pa.`role`, la.`times`, la.`vid`, la.`order` ' +
-                        'FROM ?? AS la, ?? AS pa ' +
-                        'WHERE la.`vid` = ? and la.`status` = ? and la.`times` = ? and la.`audit_id` = pa.`id` order by la.`times` desc, la.`order` desc';
+                        '  FROM ?? AS la Left Join ?? AS pa On la.`audit_id` = pa.`id`' +
+                        '  WHERE la.`vid` = ? and la.`status` = ? and la.`times` = ? order by la.`times` desc, la.`order` desc';
                     sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, vid, auditConst.status.checkNo, parseInt(times) - 1];
                     auditor = await this.db.queryOne(sql, sqlParam);
                     break;

+ 250 - 0
app/service/budget.js

@@ -0,0 +1,250 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date 2021/11/9
+ * @version
+ */
+const defaultDecimal = {
+    qty: 3,
+    tp: 0,
+    up: 2,
+};
+
+module.exports = app => {
+
+    class Budget extends app.BaseService {
+
+        /**
+         * 构造函数
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        constructor(ctx) {
+            super(ctx);
+            this.tableName = 'budget';
+        }
+
+        /**
+         * 数据规则
+         *
+         * @param {String} scene - 场景
+         * @return {Object} - 返回数据规则
+         */
+        rule(scene) {
+            let rule = {};
+            switch (scene) {
+                case 'add':
+                    rule = {
+                        name: { type: 'string', required: true, min: 2 },
+                        std_id: { type: 'string', required: true, min: 1 },
+                    };
+                    break;
+                case 'save':
+                    rule = {
+                        name: { type: 'string', required: true, min: 2, max: 100, },
+                    };
+                default:
+                    break;
+            }
+
+            return rule;
+        }
+
+        async getBudget(admin) {
+            let result = await this.getAllDataByCondition({
+                where: { pid: this.ctx.session.sessionProject.id },
+                orders: [['name', 'asc']],
+            });
+            if (admin) return result;
+
+            const permissionConst = this.ctx.service.budgetPermission.PermissionConst;
+            const permissionBudget = await this.ctx.service.budgetPermission.getUserPermission();
+            result = result.filter(x => {
+                const pb = permissionBudget.find(y => { return x.id === y.bid});
+                if (pb) {
+                    x.canEdit = pb.permission.indexOf(permissionConst.edit.value) >= 0;
+                }
+                return !!pb;
+            });
+            return result;
+        }
+
+        async getCurBudget(id) {
+            const result = await this.getDataById(id);
+            result.decimal = result.decimal ? JSON.parse(result.decimal) : {};
+            this.ctx.helper._.defaults(result.decimal, defaultDecimal);
+            return result;
+        }
+
+        /**
+         * 新增标段
+         *
+         * @param {Object} data - 提交的数据
+         * @return {Boolean} - 返回新增结果
+         */
+        async add(data) {
+            const budgetStd = await this.ctx.service.budgetStd.getDataById(data.std_id);
+            if (!budgetStd) throw '选择的概算标准不存在,请刷新页面重试';
+
+            const conn = await this.db.beginTransaction();
+            try {
+                // 获取当前用户信息
+                const sessionUser = this.ctx.session.sessionUser;
+                // 获取当前项目信息
+                const sessionProject = this.ctx.session.sessionProject;
+
+                const insertData = {
+                    pid: sessionProject.id, user_id: sessionUser.accountId, in_time: new Date(),
+                    name: data.name, std_id: data.std_id,
+                };
+                const operate = await conn.insert(this.tableName, insertData);
+
+                if (operate.insertId === 0) throw '新增标段数据失败';
+
+                // 获取合同支付模板 并添加到标段
+                await this.ctx.service.budgetGu.initByTemplate(conn, operate.insertId, budgetStd.gu_template_id);
+                await this.ctx.service.budgetGai.initByTemplate(conn, operate.insertId, budgetStd.gai_template_id);
+                await this.ctx.service.budgetYu.initByTemplate(conn, operate.insertId, budgetStd.yu_template_id);
+                await conn.commit();
+                return await this.getDataById(operate.insertId);
+            } catch (error) {
+                await conn.rollback();
+                throw error;
+            }
+        }
+
+        /**
+         * 保存标段
+         *
+         * @param {Number} id
+         * @param {Object} postData - 表单post过来的数
+         * @return {Boolean} - 返回执行结果
+         */
+        async save(data) {
+            const result = await this.db.update(this.tableName, data);
+            return result.affectedRows > 0;
+        }
+
+        /**
+         * 假删除
+         *
+         * @param {Number} id - 删除的id
+         * @return {Boolean} - 删除结果
+         */
+        async deleteBudget(id) {
+            const updateData = { id, status: this.status.DISABLE };
+            const result = await this.db.update(this.tableName, updateData);
+
+            return result.affectedRows > 0;
+        }
+
+        /**
+         * 真删除
+         * @param {Number} id - 删除的标段id
+         * @return {Promise<boolean>} - 结果
+         */
+        async deleteBudgetNoBackup(id) {
+            const transaction = await this.db.beginTransaction();
+            try {
+                await transaction.delete(this.tableName, { id });
+                await transaction.delete(this.ctx.service.budgetGu.tableName, { bid: id });
+                await transaction.delete(this.ctx.service.budgetGai.tableName, { bid: id });
+                await transaction.delete(this.ctx.service.budgetYu.tableName, { bid: id });
+                await transaction.commit();
+                return true;
+            } catch (err) {
+                this.ctx.log(err);
+                await transaction.rollback();
+                return false;
+            }
+        }
+
+        async _getGuUpdateData(newDecimal, orgDecimal) {
+            if (newDecimal.qty >= orgDecimal.qty && newDecimal.tp >= orgDecimal.tp) return [];
+            const datas = await this.ctx.service.budgetGu.getData(this.ctx.budget.id);
+            const result = [];
+            for (const d of datas) {
+                const dgn_qty1 = this.ctx.helper.round(d.dgn_qty1, newDecimal.qty);
+                const dgn_qty2 = this.ctx.helper.round(d.dgn_qty2, newDecimal.qty);
+                const total_price = d.is_leaf ? this.ctx.helper.round(d.total_price, newDecimal.tp) : 0;
+                if (dgn_qty1 !== d.dgn_qty1 || dgn_qty2 !== d.dgn_qty2 || total_price !== d.total_price) {
+                    result.push({ id: d.id, tree_id: d.tree_id, dgn_qty1, dgn_qty2, total_price });
+                }
+            }
+            return result;
+        }
+
+        async _getGaiUpdateData(newDecimal, orgDecimal) {
+            if (newDecimal.qty >= orgDecimal.qty && newDecimal.tp >= orgDecimal.tp) return [];
+            const datas = await this.ctx.service.budgetGai.getData(this.ctx.budget.id);
+            const result = [];
+            for (const d of datas) {
+                const dgn_qty1 = this.ctx.helper.round(d.dgn_qty1, newDecimal.qty);
+                const dgn_qty2 = this.ctx.helper.round(d.dgn_qty2, newDecimal.qty);
+                const total_price = d.is_leaf ? this.ctx.helper.round(d.total_price, newDecimal.tp) : 0;
+                if (dgn_qty1 !== d.dgn_qty1 || dgn_qty2 !== d.dgn_qty2 || total_price !== d.total_price) {
+                    result.push({ id: d.id, tree_id: d.tree_id, dgn_qty1, dgn_qty2, total_price });
+                }
+            }
+            return result;
+        }
+
+        async _getYuUpdateData(newDecimal, orgDecimal) {
+            if (newDecimal.qty >= orgDecimal.qty && newDecimal.up >= orgDecimal.up && newDecimal.tp === orgDecimal.tp) return [];
+            const datas = await this.ctx.service.budgetYu.getData(this.ctx.budget.id);
+            const result = [];
+            for (const d of datas) {
+                if (d.b_code) {
+                    if (!d.is_leaf) continue;
+                    const quantity = this.ctx.helper.round(d.quantity, newDecimal.qty);
+                    const unit_price = this.ctx.helper.round(d.unit_price, newDecimal.up);
+                    const total_price = this.ctx.helper.mul(unit_price, quantity, newDecimal.tp);
+                    if (quantity !== d.quantity || unit_price !== d.unit_price || total_price !== d.total_price) {
+                        result.push({ id: d.id, tree_id: d.tree_id, quantity, unit_price, total_price });
+                    }
+                } else {
+                    const dgn_qty1 = this.ctx.helper.round(d.dgn_qty1, newDecimal.qty);
+                    const dgn_qty2 = this.ctx.helper.round(d.dgn_qty2, newDecimal.qty);
+                    const total_price = d.is_leaf ? this.ctx.helper.round(d.total_price, newDecimal.tp) : 0;
+                    if (dgn_qty1 !== d.dgn_qty1 || dgn_qty2 !== d.dgn_qty2 || total_price !== d.total_price) {
+                        result.push({ id: d.id, tree_id: d.tree_id, dgn_qty1, dgn_qty2, total_price });
+                    }
+                }
+            }
+            return result;
+        }
+
+        async saveDecimal(decimal, page) {
+            const newDecimal = JSON.parse(JSON.stringify(this.ctx.budget.decimal));
+            if (decimal.qty >= 0 && decimal.qty <= 6) newDecimal.qty = decimal.qty;
+            if (decimal.up >= 0 && decimal.up <= 6) newDecimal.up = decimal.up;
+            if (decimal.tp >= 0 && decimal.tp <= 6) newDecimal.tp = decimal.tp;
+
+            const guDatas = await this._getGuUpdateData(newDecimal, this.ctx.budget.decimal);
+            const gaiDatas = await this._getGaiUpdateData(newDecimal, this.ctx.budget.decimal);
+            const yuDatas = await this._getYuUpdateData(newDecimal, this.ctx.budget.decimal);
+            const conn = await this.db.beginTransaction();
+            try {
+                const result = await conn.update(this.tableName, { id: this.ctx.budget.id, decimal: JSON.stringify(newDecimal) });
+                if (guDatas.length > 0) await conn.updateRows(this.ctx.service.budgetGu.tableName, guDatas);
+                if (gaiDatas.length > 0) await conn.updateRows(this.ctx.service.budgetGai.tableName, gaiDatas);
+                if (yuDatas.length > 0) await conn.updateRows(this.ctx.service.budgetYu.tableName, yuDatas);
+                await conn.commit();
+                switch (page) {
+                    case 'gu': return { update: guDatas };
+                    case 'gai': return { update: gaiDatas };
+                    case 'yu': return { update: yuDatas };
+                }
+            } catch (err) {
+                await conn.rollback();
+                throw err;
+            }
+        }
+    }
+
+    return Budget;
+};

+ 30 - 0
app/service/budget_gai.js

@@ -0,0 +1,30 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date
+ * @version
+ */
+
+
+
+module.exports = app => {
+    class BudgetGai extends app.BaseBudgetService {
+
+        /**
+         * 构造函数
+         *
+         * @param {Object} ctx - egg全局变量
+         * @param {String} tableName - 表名
+         * @return {void}
+         */
+        constructor(ctx) {
+            super(ctx, { keyPre: 'budget_gai_maxLid:' });
+            this.tableName = 'budget_gai';
+        }
+    }
+
+    return BudgetGai;
+};

+ 30 - 0
app/service/budget_gu.js

@@ -0,0 +1,30 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date
+ * @version
+ */
+
+
+
+module.exports = app => {
+    class BudgetGu extends app.BaseBudgetService {
+
+        /**
+         * 构造函数
+         *
+         * @param {Object} ctx - egg全局变量
+         * @param {String} tableName - 表名
+         * @return {void}
+         */
+        constructor(ctx) {
+            super(ctx, { keyPre: 'budget_gu_maxLid:' });
+            this.tableName = 'budget_gu';
+        }
+    }
+
+    return BudgetGu;
+};

+ 100 - 0
app/service/budget_permission.js

@@ -0,0 +1,100 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date
+ * @version
+ */
+
+module.exports = app => {
+    class BudgetPermission extends app.BaseService {
+
+        /**
+         * 构造函数
+         *
+         * @param {Object} ctx - egg全局变量
+         * @param {String} tableName - 表名
+         * @return {void}
+         */
+        constructor(ctx) {
+            super(ctx);
+            this.tableName = 'budget_permission';
+            this.PermissionConst = {
+                view: { title: '查看', value: 1 },
+                edit: { title: '编辑', value: 2 },
+            };
+        }
+
+        async showBudget(uid) {
+            const count = await this.count({ pid: this.ctx.session.sessionProject.id, uid });
+            return count > 0;
+        }
+
+        async getBudgetPermission(bid) {
+            const _ = this.ctx.helper._;
+            const result = await this.db.query(`SELECT bp.*, p.name, p.role 
+                FROM ${this.tableName} bp LEFT JOIN ${this.ctx.service.projectAccount.tableName} p
+                On bp.uid = p.id WHERE bid = ?`, [bid]);
+            result.forEach(x => {
+                x.permission = x.permission ? _.map(x.permission.split(','), _.toInteger) : []
+            });
+            return result;
+        }
+
+        async getUserPermission() {
+            const _ = this.ctx.helper._;
+            const result = await this.getAllDataByCondition({
+                where: { uid: this.ctx.session.sessionUser.accountId, pid: this.ctx.session.sessionProject.id }
+            });
+            result.forEach(x => {
+                x.permission = x.permission ? _.map(x.permission.split(','), _.toInteger) : []
+            });
+            return result;
+        }
+
+        async getBudgetUserPermission(bid) {
+            const _ = this.ctx.helper._;
+            const result = await this.getDataByCondition({uid: this.ctx.session.sessionUser.accountId, bid});
+            if (result) result.permission = result.permission ? _.map(result.permission.split(','), _.toInteger) : [];
+            return result;
+        }
+
+        async saveBudgetPermission(bid, member) {
+            const orgMember = await this.getAllDataByCondition({ where: { bid } });
+            const dm = [], um = [], im = [], cur = new Date();
+            for (const om of orgMember) {
+                const nm = member.find(x => { return om.uid === x.uid });
+                if (!nm) {
+                    dm.push(om.id);
+                } else {
+                    um.push({
+                        id: om.id,
+                        permission: nm.permission.join(','),
+                        modify_time: cur,
+                    });
+                    member.splice(member.indexOf(nm), 1);
+                }
+            }
+            for (const m of member) {
+                im.push({
+                    pid: this.ctx.session.sessionProject.id, bid, uid: m.uid,
+                    permission: m.permission.join(','), in_time: cur, modify_time: cur,
+                })
+            }
+            const conn = await this.db.beginTransaction();
+            try {
+                if (dm.length > 0) await conn.delete(this.tableName, { id: dm });
+                if (um.length > 0) await conn.updateRows(this.tableName, um);
+                if (im.length > 0) await conn.insert(this.tableName, im);
+                await conn.commit();
+            } catch (err) {
+                await conn.rollback();
+                throw err;
+            }
+        }
+    }
+
+    return BudgetPermission;
+};

+ 62 - 0
app/service/budget_std.js

@@ -0,0 +1,62 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date
+ * @version
+ */
+
+module.exports = app => {
+
+    class BudgetStd extends app.BaseService {
+        /**
+         * 构造函数
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        constructor(ctx) {
+            super(ctx);
+            this.tableName = 'budget_std';
+        }
+
+        async getDataByProjectId(pid) {
+            const project = await this.ctx.service.project.getDataById(pid);
+            const sid =  this._.map(project.budget.split(','), this._.toInteger);
+            return await this.getAllDataByCondition({
+                where: { id: sid },
+                columns: ['id', 'name'],
+            });
+        }
+
+        async getStdList(id, type) {
+            if (!type) return [[], []];
+            const gclField = type + '_bills_id', xmjField = type + '_chapter_id';
+            const std = await this.service.budgetStd.getDataById(id);
+            const billsId = std[gclField] ? this._.map(std[gclField].split(','), this._.toInteger) : [];
+            const chaptersId = std[xmjField] ? this._.map(std[xmjField].split(','), this._.toInteger) : [];
+            const sql = 'SELECT `id`, `name`' +
+                '  From ?? ' +
+                '  WHERE `id` in ( ? ) ORDER BY FIELD(`id`, ?)';
+            const sqlParam = ['zh_std_gcl_list', billsId, billsId];
+            const billsList = billsId.length > 0 ? await this.db.query(sql, sqlParam) : [];
+            const sql2 = 'SELECT `id`, `name`' +
+                '  From ?? ' +
+                '  WHERE `id` in ( ? ) ORDER BY FIELD(`id`, ?)';
+            const sqlParam2 = ['zh_std_xmj_list', chaptersId, chaptersId];
+            const chapterList = chaptersId.length > 0 ? await this.db.query(sql2, sqlParam2) : [];
+            return [billsList, chapterList];
+        }
+
+        async getTemplateId(id, type) {
+            if (!id || !type) throw '参数错误';
+            const budgetStd = await this.getDataById(id);
+            if (!budgetStd) throw '模板不存在';
+            return budgetStd[type + '_template_id'];
+        }
+    }
+
+    return BudgetStd;
+};

+ 28 - 0
app/service/budget_yu.js

@@ -0,0 +1,28 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date
+ * @version
+ */
+
+module.exports = app => {
+    class BudgetYu extends app.BaseBudgetService {
+
+        /**
+         * 构造函数
+         *
+         * @param {Object} ctx - egg全局变量
+         * @param {String} tableName - 表名
+         * @return {void}
+         */
+        constructor(ctx) {
+            super(ctx, { keyPre: 'budget_yu_maxLid:' });
+            this.tableName = 'budget_yu';
+        }
+    }
+
+    return BudgetYu;
+};

+ 43 - 16
app/service/change_audit_list.js

@@ -429,12 +429,12 @@ module.exports = app => {
                         if (d.id) {
                             const changePosNum = await transaction.count(this.ctx.service.changePos.tableName, { lid: d.id });
                             const updateCol = {};
-                            if (column === 'gcl_id' && d.b_code !== undefined) updateCol.code = d.b_code;
-                            if (column === 'gcl_id' && d.sgfh_qty !== undefined && changePosNum === 0) updateCol.oamount = d.sgfh_qty ? d.sgfh_qty : 0;
+                            if (column === 'gcl_id' && d.b_code) updateCol.code = d.b_code;
+                            if (column === 'gcl_id' && d.quantity !== undefined && changePosNum === 0) updateCol.oamount = d.quantity ? d.quantity : 0;
                             if (column === 'gcl_id' && d.unit_price !== undefined) updateCol.unit_price = d.unit_price ? d.unit_price : 0;
                             if (column === 'gcl_id' && d.unit !== undefined) updateCol.unit = d.unit;
                             if (column === 'gcl_id' && d.name !== undefined) updateCol.name = d.name;
-                            if (d.code !== undefined && d.b_code === null) {
+                            if (d.b_code !== undefined && d.b_code === null) {
                                 // 清单升级成了项目节,故删除变更已有的此清单,并找出需要重新计算的变更令
                                 const sql = 'SELECT `cid` FROM ?? WHERE `tid` = ? AND ' + column + ' = ? GROUP BY `cid`';
                                 const params = [this.tableName, tid, d.id];
@@ -670,8 +670,9 @@ module.exports = app => {
                         if (d.id) {
                             const updateCol = {};
                             if (column === 'mx_id' && d.name !== undefined) updateCol.bwmx = d.name;
-                            if (column === 'mx_id' && d.sgfh_qty !== undefined) updateCol.oamount = d.sgfh_qty ? d.sgfh_qty : 0;
-                            if (column === 'mx_id' && d.sgfh_qty === undefined && d.sgfh_expr === '') updateCol.oamount = 0;
+                            if (column === 'mx_id' && d.quantity !== undefined) updateCol.oamount = d.quantity ? d.quantity : 0;
+                            if (column === 'mx_id' && d.quantity === undefined &&
+                                ((d.sgfh_expr && d.sgfh_expr === '') || (d.sjcl_expr && d.sjcl_expr === '') || (d.qtcl_expr && d.qtcl_expr === ''))) updateCol.oamount = 0;
                             const options = {
                                 row: {},
                                 where: {},
@@ -682,7 +683,6 @@ module.exports = app => {
                             if (!this._.isEmpty(options.row)) updateArr.push(options);
                         }
                     }
-                    console.log(updateArr);
                     if (updateArr.length > 0) await transaction.updateRows(this.tableName, updateArr);
                 }
             }
@@ -728,12 +728,25 @@ module.exports = app => {
 
         async updateToLedger(transaction, tid, cid) {
             // 找出本条变更属于新增部位的数据
-            const sql = 'SELECT a.* FROM ?? a LEFT JOIN ?? b ON a.id = b.gcl_id WHERE b.tid = ? AND b.cid = ? GROUP BY a.id';
-            const sqlParam = [this.ctx.service.changeLedger.tableName, this.tableName, tid, cid];
-            const result = await transaction.query(sql, sqlParam);
-            const sql2 = 'SELECT a.* FROM ?? a LEFT JOIN ?? b ON a.id = b.mx_id WHERE b.tid = ? AND b.cid = ?';
-            const sqlParam2 = [this.ctx.service.changePos.tableName, this.tableName, tid, cid];
-            const result2 = await transaction.query(sql2, sqlParam2);
+            const allList = await transaction.select(this.tableName, { where: { tid, cid } });
+            const result = [];
+            const result2 = [];
+            for (const l of allList) {
+                const changeLedgerInfo = await transaction.get(this.ctx.service.changeLedger.tableName, { id: l.gcl_id });
+                if (changeLedgerInfo && this._.findIndex(result, { id: l.gcl_id }) === -1) {
+                    result.push(changeLedgerInfo);
+                }
+                const changePosInfo = await transaction.get(this.ctx.service.changePos.tableName, { id: l.mx_id });
+                if (changePosInfo) {
+                    result2.push(changePosInfo);
+                }
+            }
+            // const sql = 'SELECT a.* FROM ?? a LEFT JOIN ?? b ON a.id = b.gcl_id WHERE b.tid = ? AND b.cid = ? GROUP BY a.id';
+            // const sqlParam = [this.ctx.service.changeLedger.tableName, this.tableName, tid, cid];
+            // const result = await transaction.query(sql, sqlParam);
+            // const sql2 = 'SELECT a.* FROM ?? a LEFT JOIN ?? b ON a.id = b.mx_id WHERE b.tid = ? AND b.cid = ?';
+            // const sqlParam2 = [this.ctx.service.changePos.tableName, this.tableName, tid, cid];
+            // const result2 = await transaction.query(sql2, sqlParam2);
             if (result.length > 0 || result2.length > 0) {
                 const changeLedgerGclIdList = this._.map(result, 'id');
                 const changeLedgerIdList = this._.uniq(this._.map(result, 'ledger_pid'));// 父节点集合
@@ -745,16 +758,22 @@ module.exports = app => {
                     if (this._.indexOf(changeLedgerGclIdList, data.lid) === -1) {
                         const info = this._.find(needUpdateLedgerList, { id: data.lid });
                         if (info) {
+                            info.sgfh_qty = this.ctx.helper.add(info.sgfh_qty, data.sgfh_qty);
+                            info.sjcl_qty = this.ctx.helper.add(info.sjcl_qty, data.sjcl_qty);
+                            info.qtcl_qty = this.ctx.helper.add(info.qtcl_qty, data.qtcl_qty);
                             info.quantity = this.ctx.helper.add(info.quantity, data.quantity);
                         } else {
-                            needUpdateLedgerList.push({ id: data.lid, quantity: data.quantity });
+                            needUpdateLedgerList.push({ id: data.lid, sgfh_qty: data.sgfh_qty, sjcl_qty: data.sjcl_qty, qtcl_qty: data.qtcl_qty, quantity: data.quantity });
                         }
                     } else {
                         const info = this._.find(needUpdateChangeLedgerList, { id: data.lid });
                         if (info) {
+                            info.sgfh_qty = this.ctx.helper.add(info.sgfh_qty, data.sgfh_qty);
+                            info.sjcl_qty = this.ctx.helper.add(info.sjcl_qty, data.sjcl_qty);
+                            info.qtcl_qty = this.ctx.helper.add(info.qtcl_qty, data.qtcl_qty);
                             info.quantity = this.ctx.helper.add(info.quantity, data.quantity);
                         } else {
-                            needUpdateChangeLedgerList.push({ id: data.lid, quantity: data.quantity });
+                            needUpdateChangeLedgerList.push({ id: data.lid, sgfh_qty: data.sgfh_qty, sjcl_qty: data.sjcl_qty, qtcl_qty: data.qtcl_qty, quantity: data.quantity });
                         }
                     }
                 }
@@ -762,9 +781,13 @@ module.exports = app => {
                 if (needUpdateChangeLedgerList.length > 0) {
                     for (const nucl of needUpdateChangeLedgerList) {
                         const now = this._.find(result, { id: nucl.id });
+                        now.sgfh_qty = nucl.sgfh_qty;
+                        now.sjcl_qty = nucl.sjcl_qty;
+                        now.qtcl_qty = nucl.qtcl_qty;
                         now.quantity = nucl.quantity;
-                        now.sgfh_qty = nucl.quantity;
                         now.sgfh_tp = this.ctx.helper.mul(now.sgfh_qty, now.unit_price, tpDecimal);
+                        now.sjcl_tp = this.ctx.helper.mul(now.sjcl_qty, now.unit_price, tpDecimal);
+                        now.qtcl_tp = this.ctx.helper.mul(now.qtcl_qty, now.unit_price, tpDecimal);
                         now.total_price = this.ctx.helper.mul(now.quantity, now.unit_price, tpDecimal);
                     }
                 }
@@ -772,9 +795,13 @@ module.exports = app => {
                 if (needUpdateLedgerList.length > 0) {
                     for (const nul of needUpdateLedgerList) {
                         const ledgerInfo = await this.ctx.service.ledger.getDataById(nul.id);
+                        ledgerInfo.sgfh_qty = this.ctx.helper.add(ledgerInfo.sgfh_qty, nul.sgfh_qty);
+                        ledgerInfo.sjcl_qty = this.ctx.helper.add(ledgerInfo.sjcl_qty, nul.sjcl_qty);
+                        ledgerInfo.qtcl_qty = this.ctx.helper.add(ledgerInfo.qtcl_qty, nul.qtcl_qty);
                         ledgerInfo.quantity = this.ctx.helper.add(ledgerInfo.quantity, nul.quantity);
-                        ledgerInfo.sgfh_qty = this.ctx.helper.add(ledgerInfo.sgfh_qty, nul.quantity);
                         ledgerInfo.sgfh_tp = this.ctx.helper.mul(ledgerInfo.sgfh_qty, ledgerInfo.unit_price, tpDecimal);
+                        ledgerInfo.sjcl_tp = this.ctx.helper.mul(ledgerInfo.sjcl_qty, ledgerInfo.unit_price, tpDecimal);
+                        ledgerInfo.qtcl_tp = this.ctx.helper.mul(ledgerInfo.qtcl_qty, ledgerInfo.unit_price, tpDecimal);
                         ledgerInfo.total_price = this.ctx.helper.mul(ledgerInfo.quantity, ledgerInfo.unit_price, tpDecimal);
                         await transaction.update(this.ctx.service.ledger.tableName, ledgerInfo);
                     }

+ 33 - 20
app/service/change_ledger.js

@@ -77,7 +77,7 @@ module.exports = app => {
             const cBills = await this.db.get(this.tableName, this.getCondition({mid: mid, kid: kid}));
             // 判断父节点是否是变更新建的
             const parentBills = await this.db.get(this.tableName, this.getCondition({
-                mid: cBills[this.setting.pid], kid: kid,
+                mid: mid, kid: cBills[this.setting.pid],
             }));
             this.newBills = parentBills !== null && parentBills !== undefined;
             return cBills;
@@ -460,7 +460,7 @@ module.exports = app => {
                     if (count === 1) {
                         const updateParent = {id: parent.id };
                         updateParent[this.setting.isLeaf] = true;
-                        this.newBills ? await this.transaction.update(this.tableName, updateParent) : await this.transaction.update(this.ctx.service.ledger.tableName, updateParent);
+                        await this.transaction.update(this.tableName, updateParent);
                     }
                 }
                 // 选中节点--全部后节点 order--
@@ -524,9 +524,9 @@ module.exports = app => {
                 }
                 // 选中节点--父节点 只有一个子节点时,应升级is_leaf
                 if (parent && childCount === count) {
-                    const updateParent = {id: parent.id };
+                    const updateParent = { id: parent.id };
                     updateParent[this.setting.isLeaf] = true;
-                    newBills ? await this.transaction.update(this.tableName, updateParent) : await this.transaction.update(this.ctx.service.ledger.tableName, updateParent);
+                    await this.transaction.update(this.tableName, updateParent);
                 }
                 // 选中节点--全部后节点 order--
                 await this._updateChildrenOrder(this.tableName, mid, first[this.setting.pid], first[this.setting.order] + count, -count);
@@ -1081,7 +1081,6 @@ module.exports = app => {
             if (!count) count = 1;
             this.newBills = false;
             const selects = await this.getDataByKidAndCount(mid, kid, count);
-            // const newBills = this.newBills;
             if (!selects) throw '降级节点数据错误';
             const first = selects[0], last = selects[count - 1];
             const pre = await this.getDataByParentAndOrder(mid, first[this.setting.pid], first[this.setting.order] - 1);
@@ -1112,11 +1111,15 @@ module.exports = app => {
                     await this._syncDownlevelChildren(s, pre);
                 }
                 // 选中节点--前兄弟节点 is_leaf应为false, 清空计算相关字段
-                const updateData2 = { id: pre.id };
-                updateData2[this.setting.isLeaf] = false;
-                this.clearParentingData(updateData2);
-                await this.transaction.update(this.tableName, updateData2);
-                await this._deleteChangeAuditListData(mid, [pre]);
+                if (pre.ccid) {
+                    const updateData2 = { id: pre.id };
+                    updateData2[this.setting.isLeaf] = false;
+                    this.clearParentingData(updateData2);
+                    await this.transaction.update(this.tableName, updateData2);
+                    await this._deleteChangeAuditListData(mid, [pre]);
+                } else if (!pre.ccid && pre.is_leaf === 1) {
+                    throw '原台账节点为子项时不能降级成为它的子项';
+                }
                 await this.transaction.commit();
                 this.transaction = null;
             } catch (err) {
@@ -1346,7 +1349,7 @@ module.exports = app => {
             this.transaction = await this.db.beginTransaction();
             try {
                 const result = await this.transaction.insert(this.tableName, data);
-                if (children.length === 0 && this.newBills) {
+                if (children.length === 0 && selectData.ccid) {
                     await this.transaction.update(this.tableName,
                         {
                             is_leaf: false,
@@ -1354,6 +1357,8 @@ module.exports = app => {
                             quantity: null, unit_price: null, total_price: null, deal_qty: null, deal_tp: null
                         },
                         { where: {tender_id: tenderId, ledger_id: selectData.ledger_id} });
+                } else if (!selectData.ccid && children.length === 0) {
+                    throw '原台账节点为子项时不能添加它的子项';
                 }
                 await this.transaction.commit();
             } catch (err) {
@@ -1467,6 +1472,14 @@ module.exports = app => {
                             name: stdNode.name,
                         }) || await this.ctx.service.ledger.getDataByCondition({tender_id: tenderId, ledger_pid: parent ? parent.ledger_id : rootId, code: stdNode.code, name: stdNode.name });
                         if (!node) {
+                            let children = await this.getChildrenByParentId(this.tableName, tenderId, parent.ledger_id);
+                            if (!this.newBills) {
+                                const ledgerChildren = await this.getChildrenByParentId(this.ctx.service.ledger.tableName, tenderId, parent.ledger_id);
+                                children = this._.orderBy(this._.concat(ledgerChildren, children), [this.setting.order], ['asc']);
+                            }
+                            if (children.length === 0 && !parent.ccid) {
+                                throw '原台账节点为子项时不能添加它的子项';
+                            }
                             isNew = true;
                             const newData = this._filterStdData(stdNode);
                             newData.is_leaf = (i === len - 1);
@@ -1496,7 +1509,6 @@ module.exports = app => {
                 updateData = await this.getNextsData(this.tableName, tenderId, firstNew.ledger_pid, firstNew.order);
                 if (!this.newBills) {
                     createData = createData.concat(await this.getDataByFullPath(this.ctx.service.ledger.tableName, tenderId, firstNew.full_path + '%'));
-                    updateData = await this.getNextsData(this.ctx.service.ledger.tableName, tenderId, firstNew.ledger_pid, firstNew.order);
                     const ledgerUpdateData = await this.getNextsData(this.ctx.service.ledger.tableName, tenderId, firstNew.ledger_pid, firstNew.order);
                     updateData = this._.orderBy(this._.concat(updateData, ledgerUpdateData), [this.setting.order], ['asc']);
                 }
@@ -1568,7 +1580,11 @@ module.exports = app => {
             };
 
             const pid = parentData ? parentData.ledger_id : rootId;
-            const children = await this.getChildrenByParentId(tenderId, pid);
+            let children = await this.getChildrenByParentId(this.tableName, tenderId, pid);
+            if (!this.newBills) {
+                const ledgerChildren = await this.getChildrenByParentId(this.ctx.service.ledger.tableName, tenderId, pid);
+                children = this._.orderBy(this._.concat(children, ledgerChildren), [this.setting.order], ['asc']);
+            }
             const preData = findPreData(children, data);
             if (!preData || children.indexOf(preData) < children.length - 1) {
                 await this._updateChildrenOrder(this.tableName, tenderId, pid, preData ? preData.order + 1 : 1);
@@ -1609,7 +1625,7 @@ module.exports = app => {
 
             this.transaction = await this.db.beginTransaction();
             try {
-                // const updateDatas = [];
+                const updateDatas = [];
                 for (const row of datas) {
                     const updateNode = await this.getDataById(row.id);
                     if (!updateNode || tenderId !== updateNode.tender_id || row.ledger_id !== updateNode.ledger_id) {
@@ -1634,7 +1650,7 @@ module.exports = app => {
                     if (row.code) row.b_code = null;
 
                     if (this._checkCalcField(row)) {
-                        let calcData = JSON.parse(JSON.stringify(row));
+                        const calcData = JSON.parse(JSON.stringify(row));
                         calcData.check_calc = 1;
                         const precision = helper.findPrecision(info.precision, row.unit ? row.unit : updateNode.unit);
                         // 数量保留小数位数
@@ -1680,14 +1696,11 @@ module.exports = app => {
                     }
                     await this.transaction.update(this.tableName, updateData);
                     // 如非子节点,需要更新底下所有已选清单的分部分项等数据
-                    // updateDatas.push(updateData);
+                    updateDatas.push(updateData);
                 }
                 // console.log(updateDatas);
                 // 如果是子节点 更新了单位或单价,则更新数据并重算变更令总金额
-                await this.ctx.service.changeAuditList.updateDataByReviseLedger(this.transaction, tenderId, datas);
-                // if (data) {
-                //     throw '阻断';
-                // }
+                await this.ctx.service.changeAuditList.updateDataByReviseLedger(this.transaction, tenderId, updateDatas);
                 await this.transaction.commit();
                 this.transaction = null;
             } catch (err) {

+ 16 - 2
app/service/change_pos.js

@@ -59,7 +59,15 @@ module.exports = app => {
                 await this.ctx.service.changeAuditList.deleteDataByRevise(transaction, tid, [data.lid], 'gcl_id', '');
                 await transaction.insert(this.tableName, data);
                 await transaction.commit();
-                return { pos: data };
+                const returnData = { pos: data };
+                const bills = await this.ctx.service.ledger.getDataById(data.lid);
+                if (bills) {
+                    bills.cid = 1;
+                    returnData.ledger = {
+                        update: [bills],
+                    };
+                }
+                return returnData;
             } catch (err) {
                 await transaction.rollback();
                 throw err;
@@ -231,11 +239,15 @@ module.exports = app => {
             let bills = await this.ctx.service.changeLedger.getDataById(pos[0].lid);
             let newBills = false;
             let billsPos = await this.getAllDataByCondition({ where: { tid: tid, lid: pos[0].lid } });
+            let cid = 1;
             if (bills) {
                 newBills = true;
             } else {
                 bills = await this.ctx.service.ledger.getDataById(pos[0].lid);
                 const posData = await this.ctx.service.pos.getAllDataByCondition({ where: { tid: tid, lid: pos[0].lid } });
+                if (billsPos.length === data.length) {
+                    cid = null;
+                }
                 billsPos = this._.concat(posData, billsPos);
             }
             // const bills = await this.ctx.service.reviseBills.getDataById(pos[0].lid);
@@ -261,6 +273,7 @@ module.exports = app => {
                 await this.ctx.service.changeAuditList.deleteDataByRevise(transaction, tid, data, 'mx_id');
                 await transaction.commit();
                 updateBills.ledger_id = bills.ledger_id;
+                updateBills.cid = cid;
                 return {
                     ledger: { update: [updateBills] },
                     pos: data,
@@ -340,6 +353,7 @@ module.exports = app => {
                     if (d.id) {
                         await transaction.update(this.tableName, d);
                     } else {
+                        updateBills.cid = 1;
                         await this._insertPosData(transaction, d, tid, cid);
                     }
                 }
@@ -359,7 +373,7 @@ module.exports = app => {
                 throw err;
             }
             result.pos = data;
-            result.ledger.update = needUpdateBills ? [updateBills] : [];
+            result.ledger.update = needUpdateBills ? [updateBills] : updateBills.cid && updateBills.cid === 1 ? [{ id: bills.id, ledger_id: bills.ledger_id, cid: 1 }] : [];
             return result;
         }
 

+ 17 - 20
app/service/deal_bills.js

@@ -159,24 +159,23 @@ module.exports = app => {
                     }
                 } else {
                     const code = this.ctx.helper.replaceReturn(this.ctx.helper._.trim(row[iCode]));
-                    //if (this.ctx.helper.validBillsCode(code)) {
-                    if (code) {
-                        const data = {
-                            id: this.uuid.v4(),
-                            order: bills.length + 1,
-                            tender_id: tenderId,
-                            code: code,
-                            name: this.ctx.helper.replaceReturn(row[iName]),
-                            unit: this.ctx.helper.replaceReturn(row[iUnit]),
-                            unit_price: (row[iUp] === undefined || row[iUp] === null) ? 0 : this._.toNumber(row[iUp]),
-                            quantity: (row[iQty] === undefined || row[iQty] === null) ? 0 : this._.toNumber(row[iQty]),
-                            total_price: (row[iTp] === undefined || row[iTp] === null) ? 0 : this._.toNumber(row[iTp]),
-                        };
-                        if (this._.isNaN(data.unit_price) || this._.isNaN(data.quantity) || this._.isNaN(data.total_price)) {
-                            throw '导入的Excel的数据类型有误,请检查第' + (iRow + 1) + '行';
-                        }
-                        bills.push(data);
+                    if (!code) continue;
+                    // if (!this.ctx.helper.validBillsCode(code)) continue;
+                    const data = {
+                        id: this.uuid.v4(),
+                        order: bills.length + 1,
+                        tender_id: tenderId,
+                        code: code,
+                        name: this.ctx.helper.replaceReturn(row[iName]),
+                        unit: this.ctx.helper.replaceReturn(row[iUnit]),
+                        unit_price: (row[iUp] === undefined || row[iUp] === null) ? 0 : this._.toNumber(row[iUp]),
+                        quantity: (row[iQty] === undefined || row[iQty] === null) ? 0 : this._.toNumber(row[iQty]),
+                        total_price: (row[iTp] === undefined || row[iTp] === null) ? 0 : this._.toNumber(row[iTp]),
+                    };
+                    if (this._.isNaN(data.unit_price) || this._.isNaN(data.quantity) || this._.isNaN(data.total_price)) {
+                        throw '导入的Excel的数据类型有误,请检查第' + (iRow + 1) + '行';
                     }
+                    bills.push(data);
                 }
             }
             if (!bCheckCol) {
@@ -188,9 +187,7 @@ module.exports = app => {
                 if (bills.length > 0) {
                     await transaction.delete(this.tableName, {tender_id: tenderId});
                     const billsResult = await transaction.insert(this.tableName, bills);
-                    if (billsResult.affectedRows !== bills.length) {
-                        throw '导入签约清单数据出错';
-                    }
+                    if (billsResult.affectedRows !== bills.length) throw '导入签约清单数据出错';
                 } else {
                     throw 'Excel文件中无签约清单数据';
                 }

+ 108 - 132
app/service/ledger.js

@@ -487,17 +487,55 @@ module.exports = app => {
          */
         async batchInsertChild(tenderId, selectId, data) {
             const result = { ledger: {}, pos: null };
-            if ((tenderId <= 0) || (selectId <= 0)) {
-                return result;
-            }
+            if ((tenderId <= 0) || (selectId <= 0)) return result;
             const selectData = await this.getDataByNodeId(tenderId, selectId);
-            if (!selectData) {
-                throw '位置数据错误';
-            }
+            if (!selectData) throw '位置数据错误';
 
+            const info = this.ctx.tender.info;
             this.transaction = await this.db.beginTransaction();
-            const newIds = [];
             const lastChild = await this.getLastChildData(tenderId, selectId);
+            // 计算id
+            const maxId = await this._getMaxLid(tenderId);
+            const insertBillsData = [], insertPosData = [], in_time = new Date();
+            const order = lastChild ? lastChild.order : 0;
+            for (let i = 0, iLen = data.length; i < iLen; i++) {
+                // 合并新增数据
+                const qd = {
+                    id: this.uuid.v4(),
+                    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: this.ctx.helper.round(data[i].price, info.decimal.up),
+                };
+                qd.full_path = selectData.full_path + '-' + qd.ledger_id;
+                insertBillsData.push(qd);
+                const precision = this.ctx.helper.findPrecision(info.precision, qd.unit);
+                if (data[i].pos.length > 0) {
+                    for (const [j, p] of data[i].pos.entries()) {
+                        const inD = {
+                            id: this.uuid.v4(), tid: tenderId, lid: qd.id,
+                            add_stage: 0, add_times: 0, add_user: this.ctx.session.sessionUser.accountId,
+                            in_time, porder: j + 1,
+                            name: p.name, drawing_code: p.drawing_code,
+                        };
+                        if (p.quantity) {
+                            inD.sgfh_qty = this.round(p.quantity, precision.value);
+                            inD.quantity = inD.sgfh_qty;
+                            qd.sgfh_qty = this.ctx.helper.add(qd.sgfh_qty, inD.sgfh_qty);
+                        }
+                        insertPosData.push(inD);
+                    }
+                    qd.sgfh_tp = this.ctx.helper.mul(qd.sgfh_qty, qd.unit_price, info.decimal.tp);
+                    qd.quantity = qd.sgfh_qty;
+                    qd.total_price = qd.sgfh_tp;
+                }
+            }
             try {
                 // 更新父项isLeaf
                 if (!lastChild) {
@@ -505,46 +543,15 @@ module.exports = app => {
                         id: selectData.id,
                         is_leaf: false,
                         unit_price: null,
-                        sgfh_qty: null,
-                        sgfh_tp: null,
-                        sjcl_qty: null,
-                        sjcl_tp: null,
-                        qtcl_qty: null,
-                        qtcl_tp: null,
-                        quantity: null,
-                        total_price: null,
-                        deal_qty: null,
-                        deal_tp: null,
+                        sgfh_qty: null, sgfh_tp: null,
+                        sjcl_qty: null, sjcl_tp: null,
+                        qtcl_qty: null, qtcl_tp: null,
+                        quantity: null, total_price: null,
+                        deal_qty: null, deal_tp: null,
                     });
                 }
-                const order = lastChild ? lastChild.order : 0;
-                // 计算id
-                const maxId = await this._getMaxLid(tenderId);
-
-                // 数据库创建新增节点数据
-                for (let i = 0, iLen = data.length; i < iLen; i++) {
-                    // 合并新增数据
-                    const qd = {
-                        id: this.uuid.v4(),
-                        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(qd.id);
-                    if (data[i].pos.length > 0) {
-                        await this.ctx.service.pos.insertLedgerPosData(this.transaction, tenderId, qd, data[i].pos);
-                        await this.calcNode(qd, this.transaction);
-                    }
-                }
+                await this.transaction.insert(this.tableName, insertBillsData);
+                if (insertPosData.length > 0) await this.transaction.insert(this.ctx.service.pos.tableName, insertPosData);
                 this._cacheMaxLid(tenderId, maxId + data.length);
                 await this.transaction.commit();
             } catch (err) {
@@ -553,11 +560,9 @@ module.exports = app => {
             }
 
             // 查询应返回的结果
-            result.ledger.create = await this.getDataByIds(newIds);
-            if (!lastChild) {
-                result.ledger.update = await this.getDataByIds([selectData.id]);
-            }
-            result.pos = await this.ctx.service.pos.getPosData({tid: tenderId, lid: newIds });
+            result.ledger.create = insertBillsData;
+            if (!lastChild) result.ledger.update = await this.getDataByIds([selectData.id]);
+            result.pos = insertPosData;
             return result;
         }
 
@@ -570,50 +575,62 @@ module.exports = app => {
          */
         async batchInsertNext(tenderId, selectId, data) {
             const result = { ledger: {}, pos: null };
-            if ((tenderId <= 0) || (selectId <= 0)) {
-                return result;
-            }
+            if ((tenderId <= 0) || (selectId <= 0)) return result;
             const selectData = await this.getDataByNodeId(tenderId, selectId);
-            if (!selectData) {
-                throw '位置数据错误';
-            }
+            if (!selectData) throw '位置数据错误';
             const parentData = await this.getDataByNodeId(tenderId, selectData.ledger_pid);
-            if (!parentData) {
-                throw '位置数据错误';
-            }
+            if (!parentData) throw '位置数据错误';
 
+            const info = this.ctx.tender.info;
             this.transaction = await this.db.beginTransaction();
-            const newIds = [];
+
+            const insertBillsData = [], insertPosData = [], in_time = new Date();
+            const maxId = await this._getMaxLid(tenderId);
+            const order = selectData.order;
+            for (let i = 0, iLen = data.length; i < iLen; i++) {
+                // 合并新增数据
+                const qd = {
+                    id: this.uuid.v4(),
+                    tender_id: tenderId,
+                    ledger_id: maxId + i + 1,
+                    ledger_pid: selectData.ledger_pid,
+                    is_leaf: true,
+                    order: order + i + 1,
+                    level: selectData.level,
+                    b_code: data[i].b_code,
+                    name: data[i].name,
+                    unit: data[i].unit,
+                    unit_price: this.ctx.helper.round(data[i].price, info.decimal.up),
+                };
+                qd.full_path = selectData.full_path + '-' + qd.ledger_id;
+                insertBillsData.push(qd);
+                const precision = this.ctx.helper.findPrecision(info.precision, qd.unit);
+                if (data[i].pos.length > 0) {
+                    for (const [j, p] of data[i].pos.entries()) {
+                        const inD = {
+                            id: this.uuid.v4(), tid: tenderId, lid: qd.id,
+                            add_stage: 0, add_times: 0, add_user: this.ctx.session.sessionUser.accountId,
+                            in_time, porder: j + 1,
+                            name: p.name, drawing_code: p.drawing_code,
+                        };
+                        if (p.quantity) {
+                            inD.sgfh_qty = this.round(p.quantity, precision.value);
+                            inD.quantity = inD.sgfh_qty;
+                            qd.sgfh_qty = this.ctx.helper.add(qd.sgfh_qty, inD.sgfh_qty);
+                        }
+                        insertPosData.push(inD);
+                    }
+                    qd.sgfh_tp = this.ctx.helper.mul(qd.sgfh_qty, qd.unit_price, info.decimal.tp);
+                    qd.quantity = qd.sgfh_qty;
+                    qd.total_price = qd.sgfh_tp;
+                }
+            }
+
             try {
                 // 选中节点的所有后兄弟节点,order+粘贴节点个数
                 await this._updateChildrenOrder(tenderId, selectData.ledger_pid, selectData.order + 1, data.length);
-                // 计算id和order
-                const maxId = await this._getMaxLid(tenderId);
-                const order = selectData.order;
-                // 数据库创建新增节点数据
-                for (let i = 0, iLen = data.length; i < iLen; i++) {
-                    // 合并新增数据
-                    const qd = {
-                        id: this.uuid.v4(),
-                        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(qd.id);
-                    if (data[i].pos.length > 0) {
-                        await this.ctx.service.pos.insertLedgerPosData(this.transaction, tenderId, qd, data[i].pos);
-                        await this.calcNode(qd, this.transaction);
-                    }
-                }
+                await this.transaction.insert(this.tableName, insertBillsData);
+                if (insertPosData.length > 0) await this.transaction.insert(this.ctx.service.pos.tableName, insertPosData);
                 this._cacheMaxLid(tenderId, maxId + data.length);
                 await this.transaction.commit();
             } catch (err) {
@@ -622,52 +639,12 @@ module.exports = app => {
             }
 
             // 查询应返回的结果
-            result.ledger.create = await this.getDataByIds(newIds);
+            result.ledger.create = insertBillsData;
             result.ledger.update = await this.getNextsData(selectData.tender_id, selectData.ledger_pid, selectData.order + data.length);
-            result.pos = await this.ctx.service.pos.getPosData({tid: tenderId, lid: newIds });
+            result.pos = insertPosData;
             return result;
         }
 
-        /**
-         *
-         * @param node
-         * @param transaction
-         * @return {Promise<void>}
-         * @private
-         */
-        async calcNode(node, transaction) {
-            const info = this.ctx.tender.info;
-            const precision = this.ctx.helper.findPrecision(info.precision, node.unit);
-
-            const calcQtySql = 'SELECT SUM(`sgfh_qty`) As `sgfh_qty`, SUM(`sjcl_qty`) As `sjcl_qty`, SUM(`qtcl_qty`) As `qtcl_qty`, SUM(`quantity`) As `quantity` FROM ?? WHERE `lid` = ?';
-            const data = await transaction.queryOne(calcQtySql, [this.ctx.service.pos.tableName, node.id]);
-            data.id = node.id;
-            data.sgfh_qty = this.round(data.sgfh_qty, precision.value);
-            data.sjcl_qty = this.round(data.sjcl_qty, precision.value);
-            data.qtcl_qty = this.round(data.qtcl_qty, precision.value);
-            data.quantity = this.round(data.quantity, precision.value);
-            data.sgfh_tp = this.ctx.helper.mul(data.sgfh_qty, node.unit_price, info.decimal.tp);
-            data.sjcl_tp = this.ctx.helper.mul(data.sjcl_qty, node.unit_price, info.decimal.tp);
-            data.qtcl_tp = this.ctx.helper.mul(data.qtcl_qty, node.unit_price, info.decimal.tp);
-            data.total_price = this.ctx.helper.mul(data.quantity, node.unit_price, info.decimal.tp);
-            const result = await transaction.update(this.tableName, data);
-        }
-
-        /**
-         *
-         * @param {Number} tid - 标段id
-         * @param {Number} id - 需要计算的节点的id
-         * @param {Object} transaction - 操作所属事务,没有则创建
-         * @return {Promise<void>}
-         */
-        async calc(tid, id, transaction) {
-            const node = await transaction.get(this.tableName, { id });
-            if (!node) {
-                throw '数据错误';
-            }
-            await this.calcNode(node, transaction);
-        }
-
         async _importCacheTreeNodes(transaction, nodes) {
             const datas = [];
             for (const node of nodes) {
@@ -706,9 +683,9 @@ module.exports = app => {
          */
         async importExcel(templateId, excelData, filter) {
             const AnalysisExcel = require('../lib/analysis_excel').AnalysisExcelTree;
-            const analysisExcel = new AnalysisExcel(this.ctx);
+            const analysisExcel = new AnalysisExcel(this.ctx, this.setting);
             const tempData = await this.ctx.service.tenderNodeTemplate.getData(templateId, true);
-            const cacheTree = analysisExcel.analysisData(excelData, tempData, filter);
+            const cacheTree = analysisExcel.analysisData(excelData, tempData, { filterZeroGcl: filter });
             const cacheKey = keyPre + this.ctx.tender.id;
             const orgMaxId = parseInt(await this.cache.get(cacheKey));
             const transaction = await this.db.beginTransaction();
@@ -781,7 +758,6 @@ module.exports = app => {
                 await conn.commit();
                 return result;
             } catch (err) {
-                console.log(err);
                 await conn.rollback();
                 throw (err.stack ? '导入工程量数据出错': err);
             }

+ 19 - 42
app/service/ledger_audit.js

@@ -38,21 +38,17 @@ module.exports = app => {
          * @return {Promise<*>}
          */
         async getAuditor(tenderId, auditorId, times = 1) {
-            const sql =
-                'SELECT la.`audit_id`, pa.`name`, pa.`company`, pa.`role`, pa.`mobile`, pa.`telephone`, la.`times`, la.`audit_order`, la.`status`, la.`opinion`, la.`begin_time`, la.`end_time` ' +
-                'FROM ?? AS la, ?? AS pa ' +
-                'WHERE la.`tender_id` = ? and la.`audit_id` = ? and la.`times` = ?' +
-                '    and la.`audit_id` = pa.`id`';
+            const sql = 'SELECT la.`audit_id`, pa.`name`, pa.`company`, pa.`role`, pa.`mobile`, pa.`telephone`, la.`times`, la.`audit_order`, la.`status`, la.`opinion`, la.`begin_time`, la.`end_time` ' +
+                '  FROM ?? AS la Left Join ?? AS pa ON la.`audit_id` = pa.`id`' +
+                '  WHERE la.`tender_id` = ? and la.`audit_id` = ? and la.`times` = ?';
             const sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, tenderId, auditorId, times];
             return await this.db.queryOne(sql, sqlParam);
         }
 
         async getAuditorByOrder(tenderId, order, times = 1) {
-            const sql =
-                'SELECT la.`audit_id`, pa.`name`, pa.`company`, pa.`role`, pa.`mobile`, pa.`telephone`, la.`times`, la.`audit_order`, la.`status`, la.`opinion`, la.`begin_time`, la.`end_time` ' +
-                'FROM ?? AS la, ?? AS pa ' +
-                'WHERE la.`tender_id` = ? and la.`audit_order` = ? and la.`times` = ?' +
-                '    and la.`audit_id` = pa.`id`';
+            const sql = 'SELECT la.`audit_id`, pa.`name`, pa.`company`, pa.`role`, pa.`mobile`, pa.`telephone`, la.`times`, la.`audit_order`, la.`status`, la.`opinion`, la.`begin_time`, la.`end_time` ' +
+                '  FROM ?? AS la Left Join ?? AS pa ON la.`audit_id` = pa.`id`' +
+                '  WHERE la.`tender_id` = ? and la.`audit_order` = ? and la.`times` = ?';
             const sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, tenderId, order, times];
             return await this.db.queryOne(sql, sqlParam);
         }
@@ -78,11 +74,8 @@ module.exports = app => {
          * @return {Promise<*>}
          */
         async getStatusName(tenderId, times) {
-            const sql =
-                'SELECT pa.`name` ' +
-                'FROM ?? AS la, ?? AS pa ' +
-                'WHERE la.`tender_id` = ?' +
-                '    and la.`audit_id` = pa.`id` and la.`status` != ? ORDER BY la.`times` DESC, la.`audit_order` DESC';
+            const sql = 'SELECT pa.`name` FROM ?? AS la Left Join ?? AS pa ON la.`audit_id` = pa.`id`' +
+                '  WHERE la.`tender_id` = ? and la.`status` != ? ORDER BY la.`times` DESC, la.`audit_order` DESC';
             const sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, tenderId, auditConst.status.uncheck];
             return await this.db.queryOne(sql, sqlParam);
         }
@@ -113,8 +106,8 @@ module.exports = app => {
         async getFinalAuditGroup(tenderId, times = 1) {
             const sql =
                 'SELECT la.`audit_id`, pa.`name`, pa.`company`, pa.`role`, pa.`mobile`, pa.`telephone`, pa.`sign_path`, la.`times`, la.`tender_id`, Max(la.`audit_order`) as max_order ' +
-                'FROM ?? AS la, ?? AS pa ' +
-                'WHERE la.`tender_id` = ? and la.`times` = ? and la.`audit_id` = pa.`id` GROUP BY la.`audit_id` ORDER BY la.`audit_order`';
+                '  FROM ?? AS la Left Join ?? AS pa On la.`audit_id` = pa.`id`' +
+                '  WHERE la.`tender_id` = ? and la.`times` = ? GROUP BY la.`audit_id` ORDER BY la.`audit_order`';
             const sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, tenderId, times];
             const result = await this.db.query(sql, sqlParam);
             for (const r of result) {
@@ -137,9 +130,8 @@ module.exports = app => {
         async getCurAuditor(tenderId, times = 1) {
             const sql =
                 'SELECT la.`audit_id`, pa.`name`, pa.`company`, pa.`role`, pa.`mobile`, pa.`telephone`, la.`times`, la.`audit_order`, la.`status`, la.`opinion`, la.`begin_time`, la.`end_time` ' +
-                'FROM ?? AS la, ?? AS pa ' +
-                'WHERE la.`tender_id` = ? and la.`status` = ? and la.`times` = ?' +
-                '    and la.`audit_id` = pa.`id`';
+                '  FROM ?? AS la Left Join ?? AS pa On la.`audit_id` = pa.`id`' +
+                '  WHERE la.`tender_id` = ? and la.`status` = ? and la.`times` = ?';
             const sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, tenderId, auditConst.status.checking, times];
             return await this.db.queryOne(sql, sqlParam);
         }
@@ -441,9 +433,9 @@ module.exports = app => {
         async getAuditTender(auditorId) {
             const sql =
                 'SELECT la.`audit_id`, la.`times`, la.`audit_order`, la.`begin_time`, la.`end_time`, t.`id`, t.`name`, t.`project_id`, t.`type`, t.`user_id`, t.`ledger_status` ' +
-                'FROM ?? AS la, ?? AS t ' +
-                'WHERE ((la.`audit_id` = ? and la.`status` = ?) OR (t.`user_id` = ? and t.`ledger_status` = ? and la.`status` = ? and la.`times` = (t.`ledger_times`-1)))' +
-                '    and la.`tender_id` = t.`id` ORDER BY la.`begin_time` DESC';
+                '  FROM ?? AS la Left Join ?? AS t ON la.`tender_id` = t.`id` ' +
+                '  WHERE ((la.`audit_id` = ? and la.`status` = ?) OR (t.`user_id` = ? and t.`ledger_status` = ? and la.`status` = ? and la.`times` = (t.`ledger_times`-1)))' +
+                '    ORDER BY la.`begin_time` DESC';
             const sqlParam = [
                 this.tableName,
                 this.ctx.service.tender.tableName,
@@ -464,17 +456,6 @@ module.exports = app => {
          * @return {Promise<*>}
          */
         async getNoticeTender(pid, uid, noticeTime) {
-            // const sql = 'SELECT * FROM (SELECT la.`audit_id`, la.`times`, la.`audit_order`, la.`end_time`, la.`status`, t.`id`, t.`name`, t.`project_id`, t.`type`, t.`user_id`, ' +
-            //             '    pa.name As `lu_name`, pa.role As `lu_role`, pa.company As `lu_company`' +
-            //             '  FROM (SELECT * FROM ?? WHERE `user_id` = ? OR `id` in (SELECT `tender_id` FROM ?? WHERE `audit_id` = ? GROUP BY `tender_id`)) As t ' +
-            //             '  LEFT JOIN ?? As la ON la.`tender_id` = t.`id`' +
-            //             '  LEFT JOIN ?? As pa ON la.`audit_id` = pa.`id`' +
-            //             '  WHERE la.`end_time` > ? and t.`project_id` = ?' +
-            //             '  ORDER By la.`end_time` DESC LIMIT 1000) as new_t GROUP BY new_t.`id` ORDER BY new_t.`end_time`';
-            // const sqlParam = [this.ctx.service.tender.tableName, auditorId, this.tableName, auditorId, this.tableName, this.ctx.service.projectAccount.tableName,
-            //     noticeTime, projectId];
-            // return await this.db.query(sql, sqlParam);
-
             let notice = await this.db.select('zh_notice', {
                 where: { pid, type: pushType.ledger, uid },
                 orders: [['create_time', 'desc']],
@@ -516,8 +497,8 @@ module.exports = app => {
         async getAuditGroupByList(tender_id, times = 1) {
             const sql =
                 'SELECT la.`audit_id`, pa.`name`, pa.`company`, pa.`role`, la.`times`, la.`tender_id`, la.`audit_order` ' +
-                'FROM ?? AS la, ?? AS pa ' +
-                'WHERE la.`tender_id` = ? and la.`times` = ? and la.`audit_id` = pa.`id` GROUP BY la.`audit_id` ORDER BY la.`audit_order`';
+                '  FROM ?? AS la Left Join ?? AS pa On la.`audit_id` = pa.`id`' +
+                '  WHERE la.`tender_id` = ? and la.`times` = ? GROUP BY la.`audit_id` ORDER BY la.`audit_order`';
             const sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, tender_id, times];
             return await this.db.query(sql, sqlParam);
         }
@@ -532,12 +513,8 @@ module.exports = app => {
             const result = await this.getAuditGroupByList(tender_id, times);
             const sql =
                 'SELECT pa.`id` As audit_id, pa.`name`, pa.`company`, pa.`role`, ? As times, ? As tender_id, 0 As `audit_order`' +
-                '  FROM ' +
-                this.ctx.service.tender.tableName +
-                ' As s' +
-                '  LEFT JOIN ' +
-                this.ctx.service.projectAccount.tableName +
-                ' As pa' +
+                '  FROM ' + this.ctx.service.tender.tableName + ' As s' +
+                '  LEFT JOIN ' + this.ctx.service.projectAccount.tableName + ' As pa' +
                 '  ON s.user_id = pa.id' +
                 '  WHERE s.id = ?';
             const sqlParam = [times, tender_id, tender_id];

+ 4 - 4
app/service/ledger_revise.js

@@ -100,12 +100,12 @@ module.exports = app => {
                 '     quantity, total_price, unit_price, drawing_code, memo, dgn_qty1, dgn_qty2, deal_qty, deal_tp,' +
                 '     sgfh_qty, sgfh_tp, sjcl_qty, sjcl_tp, qtcl_qty, qtcl_tp, node_type, crid, tender_id, is_tp,' +
                 '     sgfh_expr, sjcl_expr, qtcl_expr, check_calc,' +
-                '     gxby_status, dagl_status, dagl_url, gxby_limit, dagl_limit, ex_memo1, ex_memo2, ex_memo3)' +
+                '     gxby_status, dagl_status, gxby_url, dagl_url, gxby_limit, dagl_limit, ex_memo1, ex_memo2, ex_memo3)' +
                 '  Select id, code, b_code, name, unit, source, remark, ledger_id, ledger_pid, level, `order`, full_path, is_leaf,' +
                 '      quantity, total_price, unit_price, drawing_code, memo, dgn_qty1, dgn_qty2, deal_qty, deal_tp,' +
                 '      sgfh_qty, sgfh_tp, sjcl_qty, sjcl_tp, qtcl_qty, qtcl_tp, node_type, crid, tender_id, is_tp,' +
                 '      sgfh_expr, sjcl_expr, qtcl_expr, 0,' +
-                '      gxby_status, dagl_status, dagl_url, gxby_limit, dagl_limit, ex_memo1, ex_memo2, ex_memo3' +
+                '      gxby_status, dagl_status, gxby_url, dagl_url, gxby_limit, dagl_limit, ex_memo1, ex_memo2, ex_memo3' +
                 '  From ' + this.ctx.service.ledger.tableName +
                 '  Where `tender_id` = ?';
             const sqlParam = [tid];
@@ -117,11 +117,11 @@ module.exports = app => {
                 '  (id, tid, lid, name, drawing_code, quantity, add_stage, add_times, add_user,' +
                 '     sgfh_qty, sjcl_qty, qtcl_qty, crid, in_time, porder, position,' +
                 '     sgfh_expr, sjcl_expr, qtcl_expr, real_qty,' +
-                '     gxby_status, dagl_status, dagl_url, gxby_limit, dagl_limit, ex_memo1, ex_memo2, ex_memo3)' +
+                '     gxby_status, dagl_status, gxby_url, dagl_url, gxby_limit, dagl_limit, ex_memo1, ex_memo2, ex_memo3)' +
                 '  Select id, tid, lid, name, drawing_code, quantity, add_stage, add_times, add_user,' +
                 '     sgfh_qty, sjcl_qty, qtcl_qty, crid, in_time, porder, position,' +
                 '     sgfh_expr, sjcl_expr, qtcl_expr, real_qty,' +
-                '     gxby_status, dagl_status, dagl_url, gxby_limit, dagl_limit, ex_memo1, ex_memo2, ex_memo3' +
+                '     gxby_status, dagl_status, gxby_url, dagl_url, gxby_limit, dagl_limit, ex_memo1, ex_memo2, ex_memo3' +
                 '  From ' + this.ctx.service.pos.tableName +
                 '  Where `tid` = ?';
             const sqlParam = [tid];

+ 14 - 14
app/service/material_audit.js

@@ -37,9 +37,8 @@ module.exports = app => {
          */
         async getAuditor(materialId, auditorId, times = 1) {
             const sql = 'SELECT la.`aid`, pa.`name`, pa.`company`, pa.`role`, pa.`mobile`, pa.`telephone`, la.`times`, la.`order`, la.`status`, la.`opinion`, la.`begin_time`, la.`end_time` ' +
-                'FROM ?? AS la, ?? AS pa ' +
-                'WHERE la.`mid` = ? and la.`aid` = ? and la.`times` = ?' +
-                '    and la.`aid` = pa.`id`';
+                '  FROM ?? AS la Left Join ?? AS pa On la.`aid` = pa.`id`' +
+                '  WHERE la.`mid` = ? and la.`aid` = ? and la.`times` = ?';
             const sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, materialId, auditorId, times];
             return await this.db.queryOne(sql, sqlParam);
         }
@@ -69,8 +68,8 @@ module.exports = app => {
         async getFinalAuditGroup(materialId, times = 1) {
             const sql =
                 'SELECT la.`aid`, pa.`name`, pa.`company`, pa.`role`, pa.`mobile`, pa.`telephone`, pa.`sign_path`, la.`times`, la.`mid`, Max(la.`order`) as max_order ' +
-                'FROM ?? AS la, ?? AS pa ' +
-                'WHERE la.`mid` = ? and la.`times` = ? and la.`aid` = pa.`id` GROUP BY la.`aid` ORDER BY la.`order`';
+                '  FROM ?? AS la Left Join ?? AS pa On la.`aid` = pa.`id` ' +
+                '  WHERE la.`mid` = ? and la.`times` = ? GROUP BY la.`aid` ORDER BY la.`order`';
             const sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, materialId, times];
             const result = await this.db.query(sql, sqlParam);
             for (const r of result) {
@@ -92,9 +91,8 @@ module.exports = app => {
          */
         async getCurAuditor(materialId, times = 1) {
             const sql = 'SELECT la.`aid`, pa.`name`, pa.`company`, pa.`role`, pa.`mobile`, pa.`telephone`, la.`times`, la.`order`, la.`status`, la.`opinion`, la.`begin_time`, la.`end_time` ' +
-                'FROM ?? AS la, ?? AS pa ' +
-                'WHERE la.`mid` = ? and la.`status` = ? and la.`times` = ?' +
-                '    and la.`aid` = pa.`id`';
+                '  FROM ?? AS la Left Join ?? AS pa On la.`aid` = pa.`id` ' +
+                '  WHERE la.`mid` = ? and la.`status` = ? and la.`times` = ?';
             const sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, materialId, auditConst.status.checking, times];
             return await this.db.queryOne(sql, sqlParam);
         }
@@ -782,8 +780,8 @@ module.exports = app => {
          */
         async getAuditGroupByList(materialId, times) {
             const sql = 'SELECT la.`aid`, pa.`name`, pa.`company`, pa.`role`, la.`times`, la.`mid`, la.`aid`, la.`order` ' +
-                'FROM ?? AS la, ?? AS pa ' +
-                'WHERE la.`mid` = ? and la.`times` = ? and la.`aid` = pa.`id` GROUP BY la.`aid` ORDER BY la.`order`';
+                '  FROM ?? AS la Left Join ?? AS pa On la.`aid` = pa.`id`' +
+                '  WHERE la.`mid` = ? and la.`times` = ? GROUP BY la.`aid` ORDER BY la.`order`';
             const sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, materialId, times];
             return await this.db.query(sql, sqlParam);
         }
@@ -855,15 +853,17 @@ module.exports = app => {
                 case auditConst.status.checked :
                 case auditConst.status.checkNoPre :
                     sql = 'SELECT la.`aid`, pa.`name`, pa.`company`, pa.`role`, la.`times`, la.`mid`, la.`aid`, la.`order` ' +
-                        'FROM ?? AS la, ?? AS pa ' +
-                        'WHERE la.`mid` = ? and la.`status` = ? and la.`aid` = pa.`id` order by la.`times` desc, la.`order` desc';
+                        '  FROM ?? AS la Left Join ?? AS pa On la.`aid` = pa.`id` ' +
+                        '  WHERE la.`mid` = ? and la.`status` = ? ' +
+                        '  ORDER BY la.`times` desc, la.`order` desc';
                     sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, materialId, status];
                     auditor = await this.db.queryOne(sql, sqlParam);
                     break;
                 case auditConst.status.checkNo :
                     sql = 'SELECT la.`aid`, pa.`name`, pa.`company`, pa.`role`, la.`times`, la.`mid`, la.`aid`, la.`order` ' +
-                        'FROM ?? AS la, ?? AS pa ' +
-                        'WHERE la.`mid` = ? and la.`status` = ? and la.`times` = ? and la.`aid` = pa.`id` order by la.`times` desc, la.`order` desc';
+                        '  FROM ?? AS la Left Join ?? AS pa On la.`aid` = pa.`id`' +
+                        '  WHERE la.`mid` = ? and la.`status` = ? and la.`times` = ?' +
+                        '  ORDER BY la.`times` desc, la.`order` desc';
                     sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, materialId, auditConst.status.checkNo, parseInt(times) - 1];
                     auditor = await this.db.queryOne(sql, sqlParam);
                     break;

+ 2 - 6
app/service/material_exponent.js

@@ -236,12 +236,8 @@ module.exports = app => {
             const old_ex_calc = ex_calc ? JSON.parse(ex_calc) : null;
             const new_ex_calc = materialConst.ex_calc;
             for (const bq of new_ex_calc) {
-                const calc = this._.find(calcBase, function(item) {
-                    return bq.code === item.code;
-                });
-                const oldcalc = old_ex_calc ? this._.find(old_ex_calc, function(item) {
-                    return bq.code === item.code;
-                }) : null;
+                const calc = this._.find(calcBase, { code: bq.code }) || this._.find(calcBase, { code: 'bqwc' });
+                const oldcalc = old_ex_calc ? this._.find(old_ex_calc, { code: bq.code }) : null;
                 bq.value = calc.value;
                 bq.select = oldcalc ? oldcalc.select : false;
             }

+ 1 - 0
app/service/material_list.js

@@ -278,6 +278,7 @@ module.exports = app => {
                     mx_id: ml.mx_id,
                     gather_qty,
                     quantity: ml.quantity,
+                    expr: ml.expr,
                     is_join: ml.is_join,
                     in_time: new Date(),
                 };

+ 7 - 58
app/service/pos.js

@@ -29,7 +29,7 @@ module.exports = app => {
                 where: condition,
                 columns: ['id', 'tid', 'lid', 'name', 'quantity', 'position', 'drawing_code', 'sgfh_qty', 'sjcl_qty',
                     'qtcl_qty', 'in_time', 'porder', 'add_stage', 'sgfh_expr', 'sjcl_expr', 'qtcl_expr', 'real_qty',
-                    'dagl_status', 'dagl_url', 'gxby_status', 'gxby_limit', 'dagl_limit', 'dagl_status',
+                    'dagl_status', 'dagl_url', 'gxby_status', 'gxby_url', 'gxby_limit', 'dagl_limit',
                     'ex_memo1', 'ex_memo2', 'ex_memo3'],
                 order: [['porder', 'ASC']],
             });
@@ -39,7 +39,7 @@ module.exports = app => {
             if (!condition.tid) throw '查询计量单元缺少必要信息';
             const sql = 'SELECT p.id, p.tid, p.lid, p.name, p.quantity, p.position, p.drawing_code,' +
                 '    p.sgfh_qty, p.sjcl_qty, p.qtcl_qty, p.porder, p.add_stage, p.add_times, p.add_user, s.order As add_stage_order,' +
-                '    p.sgfh_expr, p.sjcl_expr, p.qtcl_expr, p.real_qty, p.gxby_status, p.dagl_status, p.dagl_url,' +
+                '    p.sgfh_expr, p.sjcl_expr, p.qtcl_expr, p.real_qty, p.gxby_status, p.gxby_url, p.dagl_status, p.dagl_url,' +
                 '    p.gxby_limit, p.dagl_limit, p.ex_memo1, p.ex_memo2, p.ex_memo3' +
                 '  FROM ' + this.departTableName(condition.tid) + ' p ' +
                 '  LEFT JOIN ' + this.ctx.service.stage.tableName + ' s' +
@@ -52,7 +52,7 @@ module.exports = app => {
             if (ids instanceof Array && ids.length > 0) {
                 const sql = 'SELECT id, tid, lid, name, quantity, position, drawing_code,' +
                     '    sgfh_qty, sjcl_qty, qtcl_qty, add_stage, add_times, add_user, sgfh_expr, sjcl_expr, qtcl_expr, real_qty,' +
-                    '    dagl_status, dagl_url, gxby_status, gxby_limit, dagl_limit, ex_memo1, ex_memo2, ex_memo3' +
+                    '    dagl_status, dagl_url, gxby_status, gxby_url, gxby_limit, dagl_limit, ex_memo1, ex_memo2, ex_memo3' +
                     '  FROM ' + this.departTableName(tid) +
                     '  WHERE id in (' + this.ctx.helper.getInArrStrSqlFilter(ids) + ')';
                 return await this.db.query(sql, []);
@@ -184,6 +184,7 @@ module.exports = app => {
             if (data.sgfh_qty !== undefined || data.sjcl_qty !== undefined || data.qtcl_qty !== undefined) {
                 const op = await this.getDataById(data.id);
                 const bills = await this.ctx.service.ledger.getDataById(op.lid);
+                if (!bills) throw '数据错误';
                 const billsPos = await this.getAllDataByCondition({where: {tid: tid, lid: op.lid} });
 
                 const precision = this.ctx.helper.findPrecision(this.ctx.tender.info.precision, bills.unit);
@@ -309,11 +310,11 @@ module.exports = app => {
         }
 
         async _deletePosData(tid, data) {
-            if (!data || data.length === 0) {
-                throw '提交数据错误';
-            }
+            if (!data || data.length === 0) throw '提交数据错误';
 
             const pos = await this.getPosData({tid: tid, id: data});
+            if (!pos || pos.length === 0) throw '删除的计量单元不存在';
+
             const bills = await this.ctx.service.ledger.getDataById(pos[0].lid);
             const billsPos = await this.getAllDataByCondition({ where: {tid: tid, lid: bills.id} });
             const updateBills = {id: bills.id, sgfh_qty: null, sjcl_qty: null, qtcl_qty: null, quantity: null};
@@ -454,58 +455,6 @@ module.exports = app => {
         async deletePosData(transaction, tid, lid) {
             await transaction.delete(this.tableName, {tid: tid, lid: lid});
         }
-
-        /**
-         * 复制整块 拷贝部位明细数据
-         * @param {Number} orgLid - 拷贝的部位明细所属台账id
-         * @param {Number} newLid - 新的台账id
-         * @param transaction - 复制整块事务
-         * @returns {Promise<void>}
-         */
-        async copyBillsPosData(lid, transaction) {
-            const lidArr = lid instanceof Array ? lid : [lid];
-            const pasteData = [];
-            for (const id of lidArr) {
-                const posData = await this.getAllDataByCondition({ where: { lid: id.org } });
-                if (posData.length > 0) {
-                    for (const pd of posData) {
-                        pd.id = this.uuid.v4();
-                        pd.lid = id.new;
-                        pd.tid = this.ctx.tender.id;
-                        pd.in_time = new Date();
-                        pasteData.push(pd);
-                    }
-                }
-            }
-            await transaction.insert(this.tableName, pasteData);
-        }
-
-        /**
-         * 批量插入部位数据 - 仅供批量插入清单部位调用
-         * @param transaction - 所属事务
-         * @param {Number} tid - 标段id
-         * @param {Number} lid - 台账id
-         * @param {Array} data - 新增数据
-         * @returns {Promise<void>}
-         */
-        async insertLedgerPosData(transaction, tid, bills, data) {
-            const precision = this.ctx.helper.findPrecision(this.ctx.tender.info.precision, bills.unit);
-            const insertDatas = [];
-            for (const d of data) {
-                const inD = {
-                    id: this.uuid.v4(), tid: tid, lid: bills.id,
-                    add_stage: 0, add_times: 0, add_user: this.ctx.session.sessionUser.accountId,
-                    in_time: new Date(), porder: data.indexOf(d) + 1,
-                    name: d.name, drawing_code: d.drawing_code,
-                };
-                if (d.quantity) {
-                    inD.sgfh_qty = this.round(d.quantity, precision.value);
-                    inD.quantity = inD.sgfh_qty;
-                }
-                insertDatas.push(inD);
-            }
-            await transaction.insert(this.tableName, insertDatas);
-        }
     }
 
     return Pos;

+ 2 - 1
app/service/project.js

@@ -12,6 +12,7 @@ const defaultFunRela = {
     banOver: true,
     hintOver: true,
     imType: imType.zl.value,
+    needGcl: false,
 };
 const sjsRelaConst = require('../const/setting').sjsRela;
 
@@ -161,7 +162,7 @@ module.exports = app => {
             const result = await this.db.update(this.tableName, {
                 id: id, fun_rela: JSON.stringify({
                     banOver: data.banOver, hintOver: data.hintOver,
-                    imType: data.imType
+                    imType: data.imType, needGcl: data.needGcl,
                 }),
             });
             return result.affectedRows === 1;

+ 18 - 27
app/service/revise_audit.js

@@ -39,9 +39,8 @@ module.exports = app => {
         async getAuditor(reviseId, auditorId, times = 1) {
             const sql =
                 'SELECT la.`audit_id`, pa.`name`, pa.`company`, pa.`role`, pa.`mobile`, pa.`telephone`, la.`times`, la.`audit_order`, la.`status`, la.`opinion`, la.`begin_time`, la.`end_time` ' +
-                'FROM ?? AS la, ?? AS pa ' +
-                'WHERE la.`rid` = ? and la.`audit_id` = ? and la.`times` = ?' +
-                '    and la.`audit_id` = pa.`id`';
+                '  FROM ?? AS la Left Join ?? AS pa On la.`audit_id` = pa.`id`' +
+                '  WHERE la.`rid` = ? and la.`audit_id` = ? and la.`times` = ?';
             const sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, reviseId, auditorId, times];
             return await this.db.queryOne(sql, sqlParam);
         }
@@ -57,11 +56,8 @@ module.exports = app => {
             const sql =
                 'SELECT la.`audit_id`, la.`times`, la.`audit_order`, la.`status`, la.`opinion`, la.`begin_time`, la.`end_time`,' +
                 '    pa.`name`, pa.`company`, pa.`role`, pa.`mobile`, pa.`telephone`' +
-                '  FROM ' +
-                this.tableName +
-                ' AS la ' +
-                '  INNER JOIN ' +
-                this.ctx.service.projectAccount.tableName +
+                '  FROM ' + this.tableName + ' AS la ' +
+                '  INNER JOIN ' + this.ctx.service.projectAccount.tableName +
                 ' AS pa ON la.`rid` = ? and la.`times` = ? and la.`audit_id` = pa.`id`' +
                 '  ORDER BY la.`audit_order`';
             const sqlParam = [reviseId, times];
@@ -101,9 +97,8 @@ module.exports = app => {
         async getCurAuditor(reviseId, times = 1) {
             const sql =
                 'SELECT la.`audit_id`, pa.`name`, pa.`company`, pa.`role`, pa.`mobile`, pa.`telephone`, la.`times`, la.`audit_order`, la.`status`, la.`opinion`, la.`begin_time`, la.`end_time` ' +
-                'FROM ?? AS la, ?? AS pa ' +
-                'WHERE la.`rid` = ? and la.`status` = ? and la.`times` = ?' +
-                '    and la.`audit_id` = pa.`id`';
+                '  FROM ?? AS la Left Join ?? AS pa On la.`audit_id` = pa.`id`' +
+                '  WHERE la.`rid` = ? and la.`status` = ? and la.`times` = ?';
             const sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, reviseId, auditConst.status.checking, times];
             return await this.db.queryOne(sql, sqlParam);
         }
@@ -311,13 +306,13 @@ module.exports = app => {
                 '     quantity, total_price, unit_price, drawing_code, memo, dgn_qty1, dgn_qty2, deal_qty, deal_tp,' +
                 '     sgfh_qty, sgfh_tp, sjcl_qty, sjcl_tp, qtcl_qty, qtcl_tp, node_type, crid, tender_id, is_tp,' +
                 '     sgfh_expr, sjcl_expr, qtcl_expr, check_calc,' +
-                '     gxby_status, dagl_status, dagl_url, gxby_limit,  dagl_limit,' +
+                '     gxby_status, dagl_status, gxby_url, dagl_url, gxby_limit,  dagl_limit,' +
                 '     ex_memo1, ex_memo2, ex_memo3)' +
                 '  Select id, code, b_code, name, unit, source, remark, ledger_id, ledger_pid, level, `order`, full_path, is_leaf,' +
                 '      quantity, total_price, unit_price, drawing_code, memo, dgn_qty1, dgn_qty2, deal_qty, deal_tp,' +
                 '      sgfh_qty, sgfh_tp, sjcl_qty, sjcl_tp, qtcl_qty, qtcl_tp, node_type, crid, tender_id, is_tp, ' +
                 '      sgfh_expr, sjcl_expr, qtcl_expr, check_calc,' +
-                '      gxby_status, dagl_status, dagl_url, gxby_limit,  dagl_limit,' +
+                '      gxby_status, dagl_status, gxby_url, dagl_url, gxby_limit,  dagl_limit,' +
                 '      ex_memo1, ex_memo2, ex_memo3' +
                 '  From ' +
                 this.ctx.service.reviseBills.tableName +
@@ -330,12 +325,12 @@ module.exports = app => {
                 '  (id, tid, lid, name, drawing_code, quantity, add_stage, add_times, add_user,' +
                 '     sgfh_qty, sjcl_qty, qtcl_qty, crid, porder, position, ' +
                 '     sgfh_expr, sjcl_expr, qtcl_expr, real_qty,' +
-                '     gxby_status, dagl_status, dagl_url, gxby_limit,  dagl_limit,' +
+                '     gxby_status, dagl_status, gxby_url, dagl_url, gxby_limit,  dagl_limit,' +
                 '     ex_memo1, ex_memo2, ex_memo3)' +
                 '  Select id, tid, lid, name, drawing_code, quantity, add_stage, add_times, add_user,' +
                 '     sgfh_qty, sjcl_qty, qtcl_qty, crid, porder, position,' +
                 '     sgfh_expr, sjcl_expr, qtcl_expr, real_qty,' +
-                '     gxby_status, dagl_status, dagl_url, gxby_limit,  dagl_limit,' +
+                '     gxby_status, dagl_status, gxby_url, dagl_url, gxby_limit,  dagl_limit,' +
                 '     ex_memo1, ex_memo2, ex_memo3' +
                 '  From ' +
                 this.ctx.service.revisePos.tableName +
@@ -686,16 +681,16 @@ module.exports = app => {
                 case auditConst.status.checkNoPre:
                     sql =
                         'SELECT la.`audit_id`, pa.`name`, pa.`company`, pa.`role`, la.`times`, la.`rid`, la.`audit_order` ' +
-                        'FROM ?? AS la, ?? AS pa ' +
-                        'WHERE la.`rid` = ? and la.`status` = ? and la.`audit_id` = pa.`id` order by la.`times` desc, la.`audit_order` desc';
+                        '  FROM ?? AS la Left Join ?? AS pa On la.`audit_id` = pa.`id` ' +
+                        '  WHERE la.`rid` = ? and la.`status` = ? order by la.`times` desc, la.`audit_order` desc';
                     sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, rid, status];
                     auditor = await this.db.queryOne(sql, sqlParam);
                     break;
                 case auditConst.status.checkNo:
                     sql =
                         'SELECT la.`audit_id`, pa.`name`, pa.`company`, pa.`role`, la.`times`, la.`rid`, la.`audit_order` ' +
-                        'FROM ?? AS la, ?? AS pa ' +
-                        'WHERE la.`rid` = ? and la.`status` = ? and la.`times` = ? and la.`audit_id` = pa.`id` order by la.`times` desc, la.`audit_order` desc';
+                        '  FROM ?? AS la Left Join ?? AS pa On la.`audit_id` = pa.`id`' +
+                        '  WHERE la.`rid` = ? and la.`status` = ? and la.`times` = ? order by la.`times` desc, la.`audit_order` desc';
                     sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, rid, auditConst.status.checkNo, parseInt(times) - 1];
                     auditor = await this.db.queryOne(sql, sqlParam);
                     break;
@@ -715,8 +710,8 @@ module.exports = app => {
         async getAuditGroupByList(rid, times) {
             const sql =
                 'SELECT la.`audit_id`, pa.`name`, pa.`company`, pa.`role`, la.`times`, la.`rid`, la.`audit_order` ' +
-                'FROM ?? AS la, ?? AS pa ' +
-                'WHERE la.`rid` = ? and la.`times` = ? and la.`audit_id` = pa.`id` GROUP BY la.`audit_id` ORDER BY la.`audit_order`';
+                '  FROM ?? AS la Left Join ?? AS pa On la.`audit_id` = pa.`id`' +
+                '  WHERE la.`rid` = ? and la.`times` = ? GROUP BY la.`audit_id` ORDER BY la.`audit_order`';
             const sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, rid, times];
             return await this.db.query(sql, sqlParam);
             // const sql = 'SELECT `tid`, `sid`, `aid`, `order` FROM ?? WHERE `sid` = ? and `times` = ? GROUP BY `aid`';
@@ -750,12 +745,8 @@ module.exports = app => {
 
         async getAllAuditors(tenderId) {
             const sql =
-                'SELECT ra.audit_id, ra.tender_id FROM ' +
-                this.tableName +
-                ' ra' +
-                '  LEFT JOIN ' +
-                this.ctx.service.tender.tableName +
-                ' t On ra.tender_id = t.id' +
+                'SELECT ra.audit_id, ra.tender_id FROM ' + this.tableName + ' ra' +
+                '  LEFT JOIN ' + this.ctx.service.tender.tableName + ' t On ra.tender_id = t.id' +
                 '  WHERE t.id = ?' +
                 '  GROUP BY ra.audit_id';
             const sqlParam = [tenderId];

+ 5 - 47
app/service/revise_bills.js

@@ -140,11 +140,9 @@ module.exports = app => {
             }
 
             // 查询应返回的结果
-            result.ledger.create = await this.getDataById(newIds);
-            if (!lastChild) {
-                result.ledger.update = await this.getDataById(select.id);
-            }
-            result.pos = await this.ctx.service.revisePos.getDataByLid(tid, newIds);
+            result.ledger.create = bills;
+            if (!lastChild) result.ledger.update = await this.getDataById(select.id);
+            result.pos = pos;
             return result;
         }
 
@@ -232,52 +230,12 @@ module.exports = app => {
             }
 
             // 查询应返回的结果
-            result.ledger.create = await this.getDataById(newIds);
+            result.ledger.create = bills;
             result.ledger.update = await this.getNextsData(select.tender_id, select.ledger_pid, select.order + data.length);
-            result.pos = await this.ctx.service.revisePos.getDataByLid(tid, newIds);
+            result.pos = pos;
             return result;
         }
 
-        /**
-         *
-         * @param node
-         * @param transaction
-         * @returns {Promise<void>}
-         * @private
-         */
-        async _calcNode(node, transaction) {
-            const info = this.ctx.tender.info;
-            const precision = this.ctx.helper.findPrecision(info.precision, node.unit);
-
-            const calcQtySql = 'SELECT SUM(`sgfh_qty`) As `sgfh_qty`, SUM(`sjcl_qty`) As `sjcl_qty`, SUM(`qtcl_qty`) As `qtcl_qty`, SUM(`quantity`) As `quantity` FROM ?? WHERE `lid` = ?';
-            const data = await transaction.queryOne(calcQtySql, [this.ctx.service.revisePos.tableName, node.id]);
-            data.id = node.id;
-            data.sgfh_qty = this.round(data.sgfh_qty, precision.value);
-            data.sjcl_qty = this.round(data.sjcl_qty, precision.value);
-            data.qtcl_qty = this.round(data.qtcl_qty, precision.value);
-            data.quantity = this.round(data.quantity, precision.value);
-            data.sgfh_tp = this.ctx.helper.mul(data.sgfh_qty, node.unit_price, info.decimal.tp);
-            data.sjcl_tp = this.ctx.helper.mul(data.sjcl_qty, node.unit_price, info.decimal.tp);
-            data.qtcl_tp = this.ctx.helper.mul(data.qtcl_qty, node.unit_price, info.decimal.tp);
-            data.total_price = this.ctx.helper.mul(data.quantity, node.unit_price, info.decimal.tp);
-            const result = await transaction.update(this.tableName, data);
-        }
-
-        /**
-         *
-         * @param {Number} tid - 标段id
-         * @param {Number} id - 需要计算的节点的id
-         * @param {Object} transaction - 操作所属事务,没有则创建
-         * @return {Promise<void>}
-         */
-        async calc(tid, id, transaction) {
-            const node = await transaction.get(this.tableName, {id: id});
-            if (!node) {
-                throw '数据错误';
-            }
-            await this._calcNode(node, transaction);
-        }
-
         async sumLoad(lid, rid, tenders) {
             const conn = await this.db.beginTransaction();
             try {

+ 0 - 18
app/service/revise_pos.js

@@ -63,24 +63,6 @@ module.exports = app => {
             return await this.db.query(sql, sqlParam);
         }
 
-        async insertLedgerPosData(transaction, tid, rid, bills, data) {
-            const precision = this.ctx.helper.findPrecision(this.ctx.tender.info.precision, bills.unit);
-            const insertDatas = [];
-            for (const d of data) {
-                const inD = {
-                    id: this.uuid.v4(), tid: tid, lid: bills.id, crid: rid,
-                    add_stage: 0, add_times: 0, add_user: this.ctx.session.sessionUser.accountId,
-                    name: d.name, drawing_code: d.drawing_code,
-                };
-                if (d.quantity) {
-                    inD.sgfh_qty = this.round(d.quantity, precision.value);
-                    inD.quantity = inD.sgfh_qty;
-                }
-                insertDatas.push(inD);
-            }
-            await transaction.insert(this.tableName, insertDatas);
-        }
-
         /**
          * 删除清单下部位明细数据(删除清单时调用)
          *

+ 27 - 51
app/service/stage_audit.js

@@ -42,9 +42,8 @@ module.exports = app => {
         async getAuditor(stageId, auditorId, times = 1) {
             const sql =
                 'SELECT la.`aid`, pa.`name`, pa.`company`, pa.`role`, pa.`mobile`, pa.`telephone`, la.`times`, la.`order`, la.`status`, la.`opinion`, la.`begin_time`, la.`end_time` ' +
-                'FROM ?? AS la, ?? AS pa ' +
-                'WHERE la.`sid` = ? and la.`aid` = ? and la.`times` = ?' +
-                '    and la.`aid` = pa.`id`';
+                'FROM ?? AS la Left Join ?? AS pa On la.`aid` = pa.`id`' +
+                'WHERE la.`sid` = ? and la.`aid` = ? and la.`times` = ?';
             const sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, stageId, auditorId, times];
             return await this.db.queryOne(sql, sqlParam);
         }
@@ -117,11 +116,8 @@ module.exports = app => {
          * @return {Promise<*>}
          */
         async getStatusName(stageId) {
-            const sql =
-                'SELECT pa.`name` ' +
-                'FROM ?? AS sa, ?? AS pa ' +
-                'WHERE sa.`sid` = ?' +
-                '    and sa.`aid` = pa.`id` and sa.`status` != ? ORDER BY sa.`times` DESC, sa.`order` DESC';
+            const sql = 'SELECT pa.`name` FROM ?? AS sa Left Join ?? AS pa On sa.`aid` = pa.`id`' +
+                '  WHERE sa.`sid` = ? and sa.`status` != ? ORDER BY sa.`times` DESC, sa.`order` DESC';
             const sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, stageId, auditConst.status.uncheck];
             return await this.db.queryOne(sql, sqlParam);
         }
@@ -136,9 +132,8 @@ module.exports = app => {
         async getCurAuditor(stageId, times = 1) {
             const sql =
                 'SELECT la.`aid`, pa.`name`, pa.`company`, pa.`role`, pa.`mobile`, pa.`telephone`, la.`times`, la.`order`, la.`status`, la.`opinion`, la.`begin_time`, la.`end_time` ' +
-                'FROM ?? AS la, ?? AS pa ' +
-                'WHERE la.`sid` = ? and la.`status` = ? and la.`times` = ?' +
-                '    and la.`aid` = pa.`id`';
+                '  FROM ?? AS la Left Join ?? AS pa On la.`aid` = pa.`id`' +
+                '  WHERE la.`sid` = ? and la.`status` = ? and la.`times` = ?';
             const sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, stageId, auditConst.status.checking, times];
             return await this.db.queryOne(sql, sqlParam);
         }
@@ -996,9 +991,11 @@ module.exports = app => {
                 'SELECT sa.`aid`, sa.`times`, sa.`order`, sa.`begin_time`, sa.`end_time`, sa.`tid`, sa.`sid`,' +
                 '    s.`order` As `sorder`, s.`status` As `sstatus`,' +
                 '    t.`name`, t.`project_id`, t.`type`, t.`user_id` ' +
-                '  FROM ?? AS sa, ?? AS s, ?? As t ' +
+                '  FROM ?? AS sa ' +
+                '    Left Join ?? AS s On sa.`sid` = s.`id` ' +
+                '    Left Join ?? As t ON sa.`tid` = t.`id`' +
                 '  WHERE ((sa.`aid` = ? and sa.`status` = ?) OR (s.`user_id` = ? and sa.`status` = ? and s.`status` = ? and sa.`times` = (s.`times`-1)))' +
-                '    and sa.`sid` = s.`id` and sa.`tid` = t.`id` ORDER BY sa.`begin_time` DESC';
+                '  ORDER BY sa.`begin_time` DESC';
             const sqlParam = [
                 this.tableName,
                 this.ctx.service.stage.tableName,
@@ -1020,20 +1017,6 @@ module.exports = app => {
          * @return {Promise<*>}
          */
         async getNoticeStage(pid, uid, time) {
-            // const sql = 'SELECT * FROM (SELECT t.`name`, t.`project_id`, t.`type`, t.`user_id`, ' +
-            //             '    s.`order` As `s_order`, s.`status` As `s_status`, ' +
-            //             '    sa.`aid`, sa.`times`, sa.`order`, sa.`end_time`, sa.`tid`, sa.`sid`, sa.`status`, ' +
-            //             '    pa.`name` As `su_name`, pa.role As `su_role`, pa.company As `su_company`' +
-            //             '  FROM (SELECT * FROM ?? WHERE `user_id` = ? OR `id` in (SELECT `tid` FROM ?? WHERE `aid` = ? GROUP BY `tid`)) As t' +
-            //             '  LEFT JOIN ?? As s On t.`id` = s.`tid`' +
-            //             '  LEFT JOIN ?? As sa ON s.`id` = sa.`sid`' +
-            //             '  LEFT JOIN ?? As pa ON sa.`aid` = pa.`id`' +
-            //             '  WHERE sa.`end_time` > ? and t.`project_id` = ?' +
-            //             '  ORDER By sa.`end_time` DESC LIMIT 1000) as new_t GROUP BY new_t.`tid`' +
-            //             '  ORDER By new_t.`end_time`';
-            // const sqlParam = [this.ctx.service.tender.tableName, uid, this.tableName, uid, this.ctx.service.stage.tableName, this.tableName,
-            //     this.ctx.service.projectAccount.tableName, time, pid];
-            // return await this.db.query(sql, sqlParam);
             let notice = await this.db.select('zh_notice', {
                 where: { pid, type: pushType.stage, uid },
                 orders: [['create_time', 'desc']],
@@ -1077,13 +1060,10 @@ module.exports = app => {
         async getAuditGroupByList(stageId, times) {
             const sql =
                 'SELECT la.`aid`, pa.`name`, pa.`company`, pa.`role`, la.`times`, la.`sid`, la.`aid`, la.`order` ' +
-                'FROM ?? AS la, ?? AS pa ' +
-                'WHERE la.`sid` = ? and la.`times` = ? and la.`aid` = pa.`id` GROUP BY la.`aid` ORDER BY la.`order`';
+                '  FROM ?? AS la Left Join ?? AS pa On la.`aid` = pa.`id` ' +
+                '  WHERE la.`sid` = ? and la.`times` = ? GROUP BY la.`aid` ORDER BY la.`order`';
             const sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, stageId, times];
             return await this.db.query(sql, sqlParam);
-            // const sql = 'SELECT `tid`, `sid`, `aid`, `order` FROM ?? WHERE `sid` = ? and `times` = ? GROUP BY `aid`';
-            // const sqlParam = [this.tableName, stageId, times];
-            // return await this.db.query(sql, sqlParam);
         }
 
         /**
@@ -1096,12 +1076,8 @@ module.exports = app => {
             const result = await this.getAuditGroupByList(stageId, times);
             const sql =
                 'SELECT pa.`id` As aid, pa.`name`, pa.`company`, pa.`role`, ? As times, ? As sid, 0 As `order`' +
-                '  FROM ' +
-                this.ctx.service.stage.tableName +
-                ' As s' +
-                '  LEFT JOIN ' +
-                this.ctx.service.projectAccount.tableName +
-                ' As pa' +
+                '  FROM ' + this.ctx.service.stage.tableName + ' As s' +
+                '  LEFT JOIN ' + this.ctx.service.projectAccount.tableName + ' As pa' +
                 '  ON s.user_id = pa.id' +
                 '  WHERE s.id = ?';
             const sqlParam = [times, stageId, stageId];
@@ -1152,18 +1128,16 @@ module.exports = app => {
                 case auditConst.status.checking:
                 case auditConst.status.checked:
                 case auditConst.status.checkNoPre:
-                    sql =
-                        'SELECT la.`aid`, pa.`name`, pa.`company`, pa.`role`, la.`times`, la.`sid`, la.`order` ' +
-                        'FROM ?? AS la, ?? AS pa ' +
-                        'WHERE la.`sid` = ? and la.`status` = ? and la.`aid` = pa.`id` order by la.`times` desc, la.`order` desc';
+                    sql = 'SELECT la.`aid`, pa.`name`, pa.`company`, pa.`role`, la.`times`, la.`sid`, la.`order` ' +
+                          '  FROM ?? AS la Left Join ?? AS pa On la.`aid` = pa.`id` ' +
+                          '  WHERE la.`sid` = ? and la.`status` = ? order by la.`times` desc, la.`order` desc';
                     sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, stageId, status];
                     auditor = await this.db.queryOne(sql, sqlParam);
                     break;
                 case auditConst.status.checkNo:
-                    sql =
-                        'SELECT la.`aid`, pa.`name`, pa.`company`, pa.`role`, la.`times`, la.`sid`, la.`order` ' +
-                        'FROM ?? AS la, ?? AS pa ' +
-                        'WHERE la.`sid` = ? and la.`status` = ? and la.`times` = ? and la.`aid` = pa.`id` order by la.`times` desc, la.`order` desc';
+                    sql = 'SELECT la.`aid`, pa.`name`, pa.`company`, pa.`role`, la.`times`, la.`sid`, la.`order` ' +
+                          '  FROM ?? AS la Left Join ?? AS pa On la.`aid` = pa.`id` ' +
+                          '  WHERE la.`sid` = ? and la.`status` = ? and la.`times` = ? order by la.`times` desc, la.`order` desc';
                     sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, stageId, auditConst.status.checkNo, parseInt(times) - 1];
                     auditor = await this.db.queryOne(sql, sqlParam);
                     break;
@@ -1201,9 +1175,11 @@ module.exports = app => {
                 '    s.*,' +
                 '    t.`name`, t.`project_id`, t.`type`, t.`user_id`,' +
                 '    ti.`deal_info` ' +
-                '  FROM ?? AS sa, ?? AS s, ?? As t, ?? AS ti ' +
-                '  WHERE sa.`aid` = ? and sa.`status` = ?' +
-                '    and sa.`sid` = s.`id` and sa.`tid` = t.`id` and ti.`tid` = t.`id`';
+                '  FROM ?? AS sa' +
+                '    Left Join ?? AS s On sa.`sid` = s.`id`' +
+                '    Left Join ?? As t On sa.`tid` = t.`id`' +
+                '    Left Join ?? AS ti ON ti.`tid` = t.`id`' +
+                '  WHERE sa.`aid` = ? and sa.`status` = ?';
             const sqlParam = [
                 this.tableName,
                 this.ctx.service.stage.tableName,
@@ -1379,8 +1355,8 @@ module.exports = app => {
         async getFinalAuditGroup(stageId, times) {
             const sql =
                 'SELECT la.`aid`, pa.`name`, pa.`company`, pa.`role`, pa.`mobile`, pa.`telephone`, pa.`sign_path`, la.`times`, la.`sid`, Max(la.`order`) as max_order ' +
-                'FROM ?? AS la, ?? AS pa ' +
-                'WHERE la.`sid` = ? and la.`times` = ? and la.`aid` = pa.`id` GROUP BY la.`aid` ORDER BY la.`order`';
+                '  FROM ?? AS la Left Join ?? AS pa On la.`aid` = pa.`id`' +
+                '  WHERE la.`sid` = ? and la.`times` = ? GROUP BY la.`aid` ORDER BY la.`order`';
             const sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, stageId, times];
             const result = await this.db.query(sql, sqlParam);
             for (const r of result) {

+ 1 - 3
app/service/stage_change.js

@@ -258,9 +258,7 @@ module.exports = app => {
             // 更新数据
             const transaction = await this.db.beginTransaction();
             try {
-                if (newChanges.length > 0) {
-                    await transaction.insert(this.tableName, newChanges);
-                }
+                if (newChanges.length > 0) await transaction.insert(this.tableName, newChanges);
                 for (const c of updateChanges) {
                     await transaction.update(this.tableName, c);
                 }

+ 4 - 4
app/service/stage_pay.js

@@ -229,6 +229,7 @@ module.exports = app => {
             const PayCalculator = require('../lib/pay_calc');
             const payCalculator = new PayCalculator(this.ctx, stage, this.ctx.tender.info);
             await payCalculator.calculateAll(stagePays);
+            console.log(stagePays);
             const srUpdate = [], update = [];
             for (const sp of stagePays) {
                 update.push({
@@ -291,10 +292,9 @@ module.exports = app => {
                         '    `pre_tp`, `end_tp`, `pre_used`, `pre_finish`, `start_stage_order`) ' +
                         '  SELECT SP.`tid`, SP.`sid`, SP.`pid`, ?, ?, SP.name, SP.`tp`, SP.`expr`, SP.`pause`,' +
                         '     SP.`pre_tp`, SP.`end_tp`, SP.`pre_used`, SP.`pre_finish`, SP.`start_stage_order` ' +
-                        '  FROM ?? As SP, ?? As P ' +
-                        '  WHERE SP.`sid` = ? AND SP.`stimes` = ? AND SP.`sorder` = ? And SP.`pid` = P.`id` And P.`valid`';
-            const sqlParam = [this.tableName, times, order, this.tableName, this.ctx.service.pay.tableName,
-                stage.id, stage.curTimes, stage.curOrder];
+                        '  FROM ?? As SP' +
+                        '  WHERE SP.`sid` = ? AND SP.`stimes` = ? AND SP.`sorder` = ?';
+            const sqlParam = [this.tableName, times, order, this.tableName, stage.id, stage.curTimes, stage.curOrder];
             return await transaction.query(sql, sqlParam);
         }
 

+ 9 - 27
app/service/stage_pos.js

@@ -360,21 +360,11 @@ module.exports = app => {
 
             const transaction = await this.db.beginTransaction();
             try {
-                if (updatePos.length > 0) {
-                    await transaction.updateRows(this.ctx.service.pos.tableName, updatePos);
-                }
-                if (updateBills) {
-                    await transaction.update(this.ctx.service.ledger.tableName, updateBills);
-                }
-                if (updatePosStage.length > 0) {
-                    await transaction.updateRows(this.tableName, updatePosStage);
-                }
-                if (insertPosStage.length > 0) {
-                    await transaction.insert(this.tableName, insertPosStage);
-                }
-                for (const lid of result.ledger) {
-                    await this.ctx.service.stageBills.calc(this.ctx.tender.id, this.ctx.stage.id, lid, transaction);
-                }
+                if (updatePos.length > 0) await transaction.updateRows(this.ctx.service.pos.tableName, updatePos);
+                if (updateBills) await transaction.update(this.ctx.service.ledger.tableName, updateBills);
+                if (updatePosStage.length > 0) await transaction.updateRows(this.tableName, updatePosStage);
+                if (insertPosStage.length > 0) await transaction.insert(this.tableName, insertPosStage);
+                if (bills) await this.ctx.service.stageBills.calc(this.ctx.tender.id, this.ctx.stage.id, bills.id, transaction);
                 await transaction.commit();
                 return result;
             } catch (err) {
@@ -477,18 +467,10 @@ module.exports = app => {
 
             const transaction = await this.db.beginTransaction();
             try {
-                if (updatePosStage.length > 0) {
-                    await transaction.updateRows(this.tableName, updatePosStage);
-                }
-                if (insertPosStage.length > 0) {
-                    await transaction.insert(this.tableName, insertPosStage);
-                }
-                if (updateBillsStage.length > 0) {
-                    await transaction.updateRows(this.ctx.service.stageBills.tableName, updateBillsStage);
-                }
-                if (insertBillsStage.length > 0) {
-                    await transaction.insert(this.ctx.service.stageBills.tableName, insertBillsStage);
-                }
+                if (updatePosStage.length > 0) await transaction.updateRows(this.tableName, updatePosStage);
+                if (insertPosStage.length > 0) await transaction.insert(this.tableName, insertPosStage);
+                if (updateBillsStage.length > 0) await transaction.updateRows(this.ctx.service.stageBills.tableName, updateBillsStage);
+                if (insertBillsStage.length > 0) await transaction.insert(this.ctx.service.stageBills.tableName, insertBillsStage);
                 await transaction.commit();
                 return result;
             } catch (err) {

+ 27 - 0
app/service/tender.js

@@ -160,6 +160,33 @@ module.exports = app => {
             return list;
         }
 
+        async getList4Select(selectType) {
+            const accountInfo = await this.ctx.service.projectAccount.getDataById(this.ctx.session.sessionUser.accountId);
+            const userPermission = accountInfo !== undefined && accountInfo.permission !== '' ? JSON.parse(accountInfo.permission) : null;
+            const tenderList = await this.ctx.service.tender.getList('', userPermission);
+            for (const t of tenderList) {
+                if (t.ledger_status === auditConst.ledger.status.checked) {
+                    t.lastStage = await this.ctx.service.stage.getLastestStage(t.id, false);
+                    t.completeStage = await this.ctx.service.stage.getLastestCompleteStage(t.id);
+                }
+            }
+            switch (selectType) {
+                case 'ledger': return tenderList.filter(x => {
+                    return x.ledger_status === auditConst.ledger.status.checked;
+                });
+                case 'revise': tenderList.filter(x => {
+                    return x.ledger_status === auditConst.ledger.status.checked;
+                });
+                case 'stage': return tenderList.filter(x => {
+                    return x.ledger_status === auditConst.ledger.status.checked && !!x.lastStage;
+                });
+                case 'stage-checked': return tenderList.filter(x => {
+                    return !!x.completeStage;
+                });
+                default: return tenderList;
+            }
+        }
+
         async getTender(id) {
             this.initSqlBuilder();
             this.sqlBuilder.setAndWhere('id', {

+ 1 - 0
app/view/advance/detail.ejs

@@ -2,6 +2,7 @@
 <div class="panel-content">
     <div class="panel-title">
         <div class="title-main d-flex justify-content-between">
+            <% include ../tender/tender_sub_mini_menu.ejs %>
             <div>
                 <div class="d-inline-block">
                     第<%- advance.order %>期

+ 1 - 0
app/view/advance/index.ejs

@@ -2,6 +2,7 @@
 <div class="panel-content">
     <div class="panel-title">
         <div class="title-main d-flex justify-content-between">
+            <% include ../tender/tender_sub_mini_menu.ejs %>
             <div>
                 <div class="d-inline-block">
                     <div class="btn-group group-tab">

+ 36 - 0
app/view/budget/compare.ejs

@@ -0,0 +1,36 @@
+<% include ./sub_menu.ejs %>
+<div class="panel-content">
+    <div class="panel-title">
+        <div class="title-main d-flex">
+            <% include ./sub_mini_menu.ejs %>
+            <div>
+                <div class="d-inline-block">
+                    <div class="dropdown">
+                        <button class="btn btn-sm btn-light dropdown-toggle text-primary" type="button" id="dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
+                            <i class="fa fa-list-ol"></i> 显示层级
+                        </button>
+                        <div class="dropdown-menu" aria-labelledby="dropdownMenuButton">
+                            <a class="dropdown-item" name="showLevel" tag="1" href="javascript: void(0);">第一层</a>
+                            <a class="dropdown-item" name="showLevel" tag="2" href="javascript: void(0);">第二层</a>
+                            <a class="dropdown-item" name="showLevel" tag="3" href="javascript: void(0);">第三层</a>
+                            <a class="dropdown-item" name="showLevel" tag="4" href="javascript: void(0);">第四层</a>
+                            <a class="dropdown-item" name="showLevel" tag="5" href="javascript: void(0);">第五层</a>
+                            <a class="dropdown-item" name="showLevel" tag="last" href="javascript: void(0);">最底层</a>
+                        </div>
+                    </div>
+                </div>
+                <div class="d-inline-block ml-3">
+                    <a class="btn btn-sm btn-primary" href="#select-final" data-toggle="modal" data-target="#select-final">决算对比</a>
+                </div>
+            </div>
+        </div>
+    </div>
+    <div class="content-wrap">
+        <div class="c-header p-0">
+        </div>
+        <div class="w-100 sub-content">
+            <div class="sjs-height-1" id="cost-compare">
+            </div>
+        </div>
+    </div>
+</div>

+ 39 - 0
app/view/budget/compare_modal.ejs

@@ -0,0 +1,39 @@
+<div class="modal fade show" id="select-final" 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" style="display: block; overflow: auto; height: 500px">
+                <h5>可选标段</h5>
+                <table class="table table-sm table-bordered">
+                    <thead>
+                    <tr class="text-center">
+                        <th>选择</th>
+                        <th>标段名称</th>
+                        <th>期数</th>
+                        <th>状态</th>
+                    </tr>
+                    </thead>
+                    <tbody>
+                    <% for (const t of tenderList) { %>
+                    <tr>
+                        <td class="text-center"><input type="checkbox" name="sf-tender" tid="<%- t.id %>"><td><%- t.name %></td><td>第<%- t.lastStage.order %>期</td><td><%- auditConst.stage.statusString[t.lastStage.status] %></td>
+                    </tr>
+                    <% } %>
+                    </tbody>
+                </table>
+            </div>
+            <div class="modal-footer">
+                <div class="form-check form-check-inline">
+                    <input class="form-check-input" type="checkbox" id="sf-select-all">
+                    <label class="form-check-label" for="sr-select-all">全选</label>
+                </div>
+                <div>
+                    <button type="button" class="btn btn-sm btn-secondary" data-dismiss="modal">取消</button>
+                    <button type="button" class="btn btn-sm btn-primary" id="select-final-ok">确定</button>
+                </div>
+            </div>
+        </div>
+    </div>
+</div>

+ 122 - 0
app/view/budget/detail.ejs

@@ -0,0 +1,122 @@
+<% include ./sub_menu.ejs %>
+<div class="panel-content">
+    <div class="panel-title">
+        <div class="title-main d-flex">
+            <% include ./sub_mini_menu.ejs %>
+            <div>
+                <div class="d-inline-block">
+                    <div class="dropdown">
+                        <button class="btn btn-sm btn-light dropdown-toggle text-primary" type="button" id="dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
+                            <i class="fa fa-list-ol"></i> 显示层级
+                        </button>
+                        <div class="dropdown-menu" aria-labelledby="dropdownMenuButton">
+                            <a class="dropdown-item" name="showLevel" tag="1" href="javascript: void(0);">第一层</a>
+                            <a class="dropdown-item" name="showLevel" tag="2" href="javascript: void(0);">第二层</a>
+                            <a class="dropdown-item" name="showLevel" tag="3" href="javascript: void(0);">第三层</a>
+                            <a class="dropdown-item" name="showLevel" tag="4" href="javascript: void(0);">第四层</a>
+                            <a class="dropdown-item" name="showLevel" tag="5" href="javascript: void(0);">第五层</a>
+                            <a class="dropdown-item" name="showLevel" tag="last" href="javascript: void(0);">最底层</a>
+                        </div>
+                    </div>
+                </div>
+                <div class="d-inline-block">
+                    <a href="javascript: void(0);" name="base-opr" type="add" class="btn btn-sm btn-light text-primary" data-toggle="tooltip" data-placement="bottom" title="" data-original-title="新增"><i class="fa fa-plus" aria-hidden="true"></i></a>
+                    <a href="javascript: void(0);" name="base-opr" type="delete" class="btn btn-sm btn-light text-primary" data-toggle="tooltip" data-placement="bottom" title="" data-original-title="删除"><i class="fa fa-remove" aria-hidden="true"></i></a>
+                    <a href="javascript: void(0);" name="base-opr" type="up-level" class="btn btn-sm btn-light text-primary" data-toggle="tooltip" data-placement="bottom" title="" data-original-title="升级"><i class="fa fa-arrow-left" aria-hidden="true"></i></a>
+                    <a href="javascript: void(0);" name="base-opr" type="down-level" class="btn btn-sm btn-light text-primary" data-toggle="tooltip" data-placement="bottom" title="" data-original-title="降级"><i class="fa fa-arrow-right" aria-hidden="true"></i></a>
+                    <a href="javascript: void(0);" name="base-opr" type="down-move" class="btn btn-sm btn-light text-primary" data-toggle="tooltip" data-placement="bottom" title="" data-original-title="下移"><i class="fa fa-arrow-down" aria-hidden="true"></i></a>
+                    <a href="javascript: void(0);" name="base-opr" type="up-move" class="btn btn-sm btn-light text-primary" data-toggle="tooltip" data-placement="bottom" title="" data-original-title="上移"><i class="fa fa-arrow-up" aria-hidden="true"></i></a>
+                </div>
+                <div class="d-inline-block ml-3">
+                    <% if (!ctx.budget.readOnly) { %>
+                    <a class="btn btn-sm btn-primary" href="javascript: void(0);" id="budget-import">导入</a>
+                    <% } %>
+                </div>
+            </div>
+        </div>
+    </div>
+    <div class="content-wrap pr-46">
+        <div class="c-header p-0">
+        </div>
+        <div class="row w-100 sub-content">
+            <div id="left-view" class="c-body" style="width: 100%">
+                <div class="sjs-height-1" id="budget-spread">
+                </div>
+            </div>
+            <div id="right-view" class="c-body" style="display: none; width: 33%;">
+                <div class="resize-x" id="right-spr" r-Type="width" div1="#left-view" div2="#right-view" title="调整大小" a-type="percent"></div>
+                <div class="tab-content">
+                    <div id="search" class="tab-pane tab-select-show">
+                        <div class="sjs-bar-1">
+                            <div class="input-group input-group-sm pb-1">
+                                <div class="input-group-prepend">
+                                    <div class="input-group-text">
+                                        <input type="radio" name="searchType" id="over"> 超计
+                                    </div>
+                                    <div class="input-group-text">
+                                        <input type="radio" name="searchType" id="empty"> 漏计
+                                    </div>
+                                </div>
+                                <input type="text" class="form-control form-control-sm" placeholder="可查找 项目节编号 / 清单编号 /名称" id="keyword">
+                                <div class="input-group-append">
+                                    <button class="btn btn-outline-secondary btn-sm" type="button" id="searchLedger">搜索</button>
+                                </div>
+                            </div>
+                        </div>
+                        <div id="search-result" class="sjs-sh-1">
+                        </div>
+                    </div>
+                    <div id="std-xmj" class="tab-pane tab-select-show">
+                        <div class="sjs-bar-2">
+                            <div class="pb-1">
+                                <select class="form-control form-control-sm">
+                                    <% for (const [i, c] of stdChapters.entries()) { %>
+                                    <option value="<%- c.id %>" <%- (i===0 ? ' selected' : '') %>><%- c.name %></option>
+                                    <% } %>
+                                </select>
+                            </div>
+                        </div>
+                        <div id="std-xmj-spread" class="sjs-sh-2">
+                        </div>
+                    </div>
+                    <% if (needGcl) { %>
+                    <div id="std-gcl" class="tab-pane tab-select-show">
+                        <div class="sjs-bar-3">
+                            <div class="pb-1">
+                                <select class="form-control form-control-sm">
+                                    <% for (const [i, b] of stdBills.entries()) { %>
+                                    <option value="<%- b.id %>" <%- (i===0 ? ' selected' : '') %>><%- b.name %></option>
+                                    <% } %>
+                                </select>
+                            </div>
+                        </div>
+                        <div id="std-gcl-spread" class="sjs-sh-3">
+                        </div>
+                    </div>
+                    <% } %>
+                </div>
+            </div>
+        </div>
+        <div class="side-menu">
+            <ul class="nav flex-column right-nav">
+                <li class="nav-item">
+                    <a class="nav-link" content="#search" href="javascript: void(0);">查找定位</a>
+                </li>
+                <li class="nav-item">
+                    <a class="nav-link" content="#std-xmj" href="javascript: void(0);">项目节</a>
+                </li>
+                <% if (needGcl) { %>
+                <li class="nav-item">
+                    <a class="nav-link" content="#std-gcl" href="javascript: void(0);">工程量清单</a>
+                </li>
+                <% } %>
+            </ul>
+        </div>
+    </div>
+</div>
+<script>
+    const readOnly = <%- ctx.budget.readOnly %>;
+    const needGcl = <%- needGcl %>;
+    const spreadSetting = JSON.parse('<%- JSON.stringify(spreadSetting) %>');
+    let decimal = JSON.parse('<%- JSON.stringify(ctx.budget.decimal) %>');
+</script>

+ 49 - 0
app/view/budget/detail_modal.ejs

@@ -0,0 +1,49 @@
+<% include ../shares/delete_hint_modal.ejs %>
+<% include ../shares/import_excel_modal.ejs %>
+<div class="modal fade" id="budget-set" 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">
+                <h6 id="del-node-hint">设置对投资估算、初步概算、施工图预算同时生效。</h6>
+                <div class="form-group">
+                    <div class="row">
+                        <div class="col-4">
+                            <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" id="decimal-qty"
+                                       min="0" max="4" maxlength="1" oninput="limitDecimal(this)">
+                            </div>
+                        </div>
+                        <div class="col-4">
+                            <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" id="decimal-up"
+                                       min="0" max="4" maxlength="1" oninput="limitDecimal(this)">
+                            </div>
+                        </div>
+                        <div class="col-4">
+                            <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" id="decimal-tp"
+                                       oninput="limitDecimal(this)" min="0" max="4" maxlength="1">
+                            </div>
+                        </div>
+                    </div>
+                </div>
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-sm btn-primary" id="budget-set-ok" data-dismiss="modal">确定</button>
+                <button type="button" class="btn btn-sm btn-secondary" data-dismiss="modal">取消</button>
+            </div>
+        </div>
+    </div>
+</div>

+ 47 - 0
app/view/budget/list.ejs

@@ -0,0 +1,47 @@
+<div class="panel-content">
+    <div class="panel-title fluid">
+        <div class="title-main  d-flex justify-content-between">
+            <div>概算投资</div>
+            <% if (ctx.session.sessionUser.is_admin) { %>
+            <div class="ml-auto">
+                <a href="#add-budget" name="add" data-toggle="modal" data-target="#add-budget" class="btn btn-sm btn-primary pull-right">新建项目</a>
+            </div>
+            <% } %>
+        </div>
+    </div>
+    <div class="content-wrap">
+        <div class="sjs-height-0" style="background-color: #fff">
+            <div class="c-body">
+                <% if (!budgetList || budgetList.length === 0) { %>
+                <div class="jumbotron">
+                    <h3 class="display-6">还没有项目数据</h3>
+                </div>
+                <% } else { %>
+                <table class="table table-hover table-bordered">
+                    <tr class="text-center"><th style="min-width: 200px">项目名称</th><th>概预算标准</th><th>创建时间</th><th>投资估算</th><th>初步概算</th><th>施工图预算</th><th>操作</th></tr>
+                    <% for (const bl of budgetList) { %>
+                    <tr bid="<%- bl.id %>" bname="<%- bl.name %>" rela-tender="<%- bl.rela_tender %>" >
+                        <td><a href="/budget/<%- bl.id %>/compare"><%- bl.name %></a></td>
+                        <td><%- bl.std_name %></td>
+                        <td><%- ctx.moment(bl.in_time).format('YYYY-MM-DD') %></td>
+                        <td class="text-right"><%- (bl.gu_tp ? bl.gu_tp : '')%></td>
+                        <td class="text-right"><%- (bl.gai_tp ? bl.gai_tp : '')%></td>
+                        <td class="text-right"><%- (bl.yu_tp ? bl.yu_tp : '')%></td>
+                        <td>
+                            <% if (ctx.session.sessionUser.is_admin || bl.canEdit) { %>
+                            <a href="javascript: void(0);" data-target="#modify-budget" class="btn btn-outline-primary btn-sm" onclick="showModal(this);">编辑</a>
+                            <a href="javascript: void(0);" data-target="#select-rela" class="btn btn-outline-primary btn-sm" onclick="showModal(this);">关联标段</a>
+                            <% } %>
+                            <% if (ctx.session.sessionUser.is_admin) { %>
+                            <a href="javascript: void(0);" data-target="#member" class="btn btn-outline-primary btn-sm ml-1">成员管理</a>
+                            <a href="javascript: void(0);" data-target="#del-budget" class="btn btn-outline-danger btn-sm ml-1" onclick="showModal(this);">删除</a>
+                            <% } %>
+                        </td>
+                    </tr>
+                    <% } %>
+                </table>
+                <% } %>
+            </div>
+        </div>
+    </div>
+</div>

+ 157 - 0
app/view/budget/list_modal.ejs

@@ -0,0 +1,157 @@
+<!--弹出添加标段-->
+<div class="modal fade" id="add-budget" 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>项目名称<b class="text-danger">*</b></label>
+                    <input class="form-control form-control-sm"  placeholder="输入项目名称" type="text" id="add-budget-name" onchange="budgetNameChange(this);">
+                    <div class="invalid-feedback">名称超过100个字,请缩减名称。</div>
+                </div>
+                <div class="form-group">
+                    <label>概预算标准<b class="text-danger">*</b></label>
+                    <div>
+                        <% for (const [i, bs] of budgetStd.entries()) { %>
+                        <div class="form-check form-check-inline mt-2">
+                            <input class="form-check-input" name="std_id" type="radio" id="std<%- bs.id %>" value="<%- bs.id %>" <% if (i === 0) { %>checked<% } %>>
+                            <label class="form-check-label" for="std<%- bs.id %>"><%- bs.name %></label>
+                        </div>
+                        <% } %>
+                    </div>
+                </div>
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-secondary btn-sm" data-dismiss="modal">关闭</button>
+                <button type="button" class="btn btn-primary btn-sm" id="add-budget-ok" onclick="addBudget();">确定添加</button>
+            </div>
+        </div>
+    </div>
+</div>
+<!--编辑标段-->
+<div class="modal fade" id="modify-budget" 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>项目名称<b class="text-danger">*</b></label>
+                    <input class="form-control form-control-sm"  placeholder="输入项目名称" type="text" id="modify-budget-name" value="" onchange="budgetNameChange(this);">
+                    <div class="invalid-feedback"> 名称超过100个字,请缩减名称。 </div>
+                </div>
+
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-secondary btn-sm" data-dismiss="modal">取消</button>
+                <button type="button" class="btn btn-primary btn-sm" onclick="saveBudget();">确定修改</button>
+            </div>
+        </div>
+    </div>
+</div>
+<!--删除标段-->
+<div class="modal fade" id="del-budget" 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">
+                <h6>确认删除「<span id="del-budget-name">XXX高速公路</span>」?</h6>
+                <h6>删除后,数据无法恢复,请谨慎操作。</h6>
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-sm btn-secondary" data-dismiss="modal">取消</button>
+                <button type="button" class="btn btn-sm btn-danger" onclick="delBudget();">确定删除</button>
+            </div>
+        </div>
+    </div>
+</div>
+<!--弹出关联标段-->
+<div class="modal fade" id="select-rela" 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" style="display: block; overflow: auto; height: 500px">
+                <h5>可选标段</h5>
+                <table class="table table-sm table-bordered">
+                    <tr class="text-center"><th>选择</th><th>标段名称</th><th>期数</th><th>状态</th></tr>
+                    <tbody id="valid-rela-tender">
+                    <% for (const t of tenderList) { %>
+                    <tr><td class="text-center"><input type="checkbox" name="select-rela-check" tid="<%- t.id %>"></td><td><%- t.name %></td><td>第<%- t.lastStage.order %>期</td><td><%- auditConst.stage.statusString[t.lastStage.status] %></td></tr>
+                    <% } %>
+                    </tbody>
+                </table>
+            </div>
+            <div class="modal-footer">
+                <div class="form-check form-check-inline">
+                    <input class="form-check-input" type="checkbox" id="sr-select-all">
+                    <label class="form-check-label" for="sr-select-all">全选</label>
+                </div>
+                <button type="button" class="btn btn-sm btn-secondary" data-dismiss="modal">取消</button>
+                <button type="button" class="btn btn-sm btn-primary" onclick="relaTender();">确定</button>
+            </div>
+        </div>
+    </div>
+</div>
+<!--成员管理-->
+<div class="modal fade" id="member" 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="dropdown">
+                    <button class="btn btn-outline-primary btn-sm dropdown-toggle" type="button" id="dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
+                        添加用户
+                    </button>
+                    <div class="dropdown-menu" aria-labelledby="dropdownMenuButton" style="width:220px">
+                        <div class="mb-2 p-2"><input class="form-control form-control-sm" placeholder="姓名/手机 检索" id="member-search" autocomplete="off"></div>
+                        <dl class="list-unstyled book-list">
+                            <% accountGroup.forEach((group, idx) => { %>
+                            <dt><a href="javascript: void(0);" class="acc-btn" data-groupid="<%- idx %>" data-type="hide"><i class="fa fa-plus-square"></i></a> <%- group.groupName %></dt>
+                            <div class="dd-content" data-toggleid="<%- idx %>">
+                                <% group.groupList.forEach(item => { %>
+                                <dd class="border-bottom p-2 mb-0 " data-id="<%- item.id %>" >
+                                    <p class="mb-0 d-flex"><span class="text-primary"><%- item.name %></span><span
+                                                class="ml-auto"><%- item.mobile %></span></p>
+                                    <span class="text-muted"><%- item.role %></span>
+                                </dd>
+                                <% });%>
+                            </div>
+                            <% }) %>
+                        </dl>
+                    </div>
+                </div>
+                <div class="mt-1">
+                    <table class="table table-bordered">
+                        <thead>
+                        <th>成员名称</th>
+                        <th>角色/职位</th>
+                        <th>编辑</th>
+                        <th>移除</th>
+                        </thead>
+                        <tbody id="member-list">
+
+                        </tbody>
+                    </table>
+                </div>
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-sm btn-secondary" data-dismiss="modal">取消</button>
+                <button type="button" class="btn btn-sm btn-primary" id="member-ok">确认修改</button>
+            </div>
+        </div>
+    </div>
+</div>
+<script>
+    const accountList = JSON.parse('<%- JSON.stringify(accountList) %>');
+    const accountGroup = JSON.parse('<%- JSON.stringify(accountGroup) %>');
+    const permissionConst = JSON.parse('<%- JSON.stringify(permissionConst) %>');
+</script>

+ 14 - 0
app/view/budget/sub_menu.ejs

@@ -0,0 +1,14 @@
+<div class="panel-sidebar" id="sub-menu">
+    <div class="sidebar-title" data-toggle="tooltip" data-placement="right" data-original-title="<%- ctx.budget.name %>">
+        <%- (ctx.budget.name.length > 15 ? ctx.budget.name.substring(0,15) + '...' : ctx.budget.name) %>
+    </div>
+    <div class="scrollbar-auto">
+        <% include ./sub_menu_list.ejs %>
+        <div class="side-fold"><a href="javascript: void(0)" data-toggle="tooltip" data-placement="top" data-original-title="折叠侧栏" id="to-mini-menu"><i class="fa fa-upload fa-rotate-270"></i></a></div>
+    </div>
+    <script>
+        new Vue({
+            el: '.scrollbar-auto',
+        });
+    </script>
+</div>

+ 8 - 0
app/view/budget/sub_menu_list.ejs

@@ -0,0 +1,8 @@
+<nav-menu title="返回" url="/budget" tclass="text-primary" ml="1" icon="fa-chevron-left"></nav-menu>
+<nav-menu title="造价对比" url="/budget/<%= ctx.budget.id %>/compare" ml="3" active="<%= ctx.url.indexOf('/compare') %>"></nav-menu>
+<nav-menu title="投资估算" url="/budget/<%= ctx.budget.id %>/gu" ml="3" active="<%= ctx.url.indexOf('/gu') %>"></nav-menu>
+<nav-menu title="初步概算" url="/budget/<%= ctx.budget.id %>/gai%>" ml="3" active="<%= ctx.url.indexOf('/gai') %>"></nav-menu>
+<nav-menu title="施工图预算" url="/budget/<%= ctx.budget.id %>/yu" ml="3" active="<%= ctx.url.indexOf('/yu') %>"></nav-menu>
+<% if (!ctx.budget.readOnly && ctx.url.indexOf('/compare') === -1 ) { %>
+<div class="contarl-box"><button class="btn btn-primary btn-sm btn-block" data-toggle="modal" data-target="#budget-set">设置</button></div>
+<% } %>

+ 16 - 0
app/view/budget/sub_mini_menu.ejs

@@ -0,0 +1,16 @@
+<!--折起的菜单-->
+<div class="min-side" id="sub-mini-menu" style="display: none;">
+    <div id="sub-mini-hint" class="side-switch" data-container="body" data-toggle="popover" data-placement="bottom" data-content="这里打开收起的菜单栏"></div>
+    <div class="side-switch">
+        <i class="fa fa-bars"></i>
+    </div>
+    <div class="side-menu" id="mini-menu-list" style="display: none">
+        <% include ./sub_menu_list.ejs %>
+        <div class="side-fold"><a href="javascript: void(0);" data-toggle="tooltip" data-placement="top" data-original-title="展开侧栏" id="to-menu"><i class="fa fa-upload fa-rotate-90"></i></a></div>
+    </div>
+</div>
+<script>
+    new Vue({
+        el: '.side-menu',
+    });
+</script>

+ 2 - 0
app/view/change/information.ejs

@@ -462,6 +462,8 @@
     const aidList = _.map(auditList2, 'uid');
     aidList.splice(0, 1);
     let changeUsedData = JSON.parse(unescape('<%- escape(JSON.stringify(changeUsedData)) %>'));
+    const changeLedgerList = JSON.parse(unescape('<%- escape(JSON.stringify(changeLedgerList)) %>'));
+    const changePosList = JSON.parse(unescape('<%- escape(JSON.stringify(changePosList)) %>'));
 </script>
 <script src="/public/js/change_information_approval.js"></script>
 <% } %>

+ 23 - 8
app/view/change/information_modal.ejs

@@ -116,8 +116,18 @@
                 <div class="row">
                     <div class="col-12">
                         <div class="mb-2 col-6 p-0 search-group">
-                            <input class="form-control form-control-sm" id="list-input" placeholder="输入 清单编号、名称 检索" value="">
-                            <a href="javascript:void(0);" style="display: none" data-btn="list" class="text-danger remove-btn" title="移除关键词"><i class="fa fa-times-circle "></i></a>
+                            <div class="input-group input-group-sm pb-1">
+                                <div class="input-group-prepend">
+                                    <% if (ctx.session.sessionProject.page_show.openChangeRevise) { %>
+                                    <select class="input-group-text" id="select-list">
+                                        <option value="0" selected>所有清单</option>
+                                        <option value="1">新增部位</option>
+                                    </select>
+                                    <% } %>
+                                </div>
+                                <input class="form-control form-control-sm" id="list-input" placeholder="输入 清单编号、名称 检索" value="">
+                                <a href="javascript:void(0);" style="display: none" data-btn="list" class="text-danger remove-btn" title="移除关键词"><i class="fa fa-times-circle "></i></a>
+                            </div>
                         </div>
                         <div style="overflow-y:auto" class="sjs-biangeng-height">
                             <table class="table table-striped table-bordered table-hover table-sm fixed_headers">
@@ -130,14 +140,19 @@
                     <div class="col-12">
                         <div class="row mb-2 mt-3 mx-0 p-0">
                             <div class="col-6 p-0 search-group">
-                                <input class="form-control form-control-sm" id="code-input" placeholder="输入 项目节编号、名称、计量单元 检索">
-                                <a href="javascript:void(0);" style="display: none" data-btn="code" class="text-danger remove-btn" title="移除关键词"><i class="fa fa-times-circle "></i></a>
+                                <div class="input-group input-group-sm pb-1">
+                                    <div class="input-group-prepend"></div>
+                                    <input class="form-control form-control-sm" id="code-input" placeholder="输入 项目节编号、名称、计量单元 检索">
+                                    <a href="javascript:void(0);" style="display: none" data-btn="code" class="text-danger remove-btn" title="移除关键词"><i class="fa fa-times-circle "></i></a>
+                                </div>
                             </div>
-                            <div class="col-3 pl-3 mt-1">
+                            <% if (ctx.session.sessionProject.page_show.openChangeRevise) { %>
+                            <div class="col-3 pl-3">
                                 <a href="/tender/<%- change.tid %>/change/<%- change.cid %>/information/revise" class="btn btn-primary btn-sm">新增部位</a>
                             </div>
-                            <div class="ml-auto mt-1">
-                                <div class="custom-control custom-checkbox mt-1">
+                            <% } %>
+                            <div class="ml-auto">
+                                <div class="custom-control custom-checkbox">
                                     <input type="checkbox" id="code-select-all" class="custom-control-input">
                                     <label class="custom-control-label" for="code-select-all">全选</label>
                                 </div>
@@ -146,7 +161,7 @@
                         <div style="overflow-y:auto" class="sjs-biangeng-height">
                             <table class="table table-striped table-bordered table-hover table-sm fixed_headers2">
                                 <thead>
-                                <tr class="text-center"><th width="100">项目节编号</th><th>细目</th><th>单位工程</th><th>分部工程</th><th>分项工程</th><th>计量单元</th><th width="70">数量</th><th width="40">选择</th></tr>
+                                <tr class="text-center"><th>序号</th><th width="100">项目节编号</th><th>细目</th><th>单位工程</th><th>分部工程</th><th>分项工程</th><th>计量单元</th><th width="70">数量</th><th width="40">选择</th></tr>
                                 </thead>
                                 <tbody id="code-list" data-index="">
                                 </tbody>

+ 37 - 0
app/view/change/revise_modal.ejs

@@ -35,6 +35,43 @@
         </div>
     </div>
 </div>
+<% if (revising) { %>
+    <!--正在修订提示-->
+    <div class="modal fade" id="unedit" 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">
+                    <h5>台账正在进行修订,新增部位页无法进行任何操作。</h5>
+                </div>
+                <div class="modal-footer">
+                    <button type="button" class="btn btn-secondary" data-dismiss="modal">好的</button>
+                </div>
+            </div>
+        </div>
+    </div>
+    <script type="text/javascript">$('#unedit').modal('show');</script>
+<% } else if (changing && !revising) { %>
+    <!--正在修订提示-->
+    <div class="modal fade" id="unedit2" 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">
+                    <h5>该变更令正在审批中或已完成,新增部位页无法进行任何操作。</h5>
+                </div>
+                <div class="modal-footer">
+                    <button type="button" class="btn btn-secondary" data-dismiss="modal">好的</button>
+                </div>
+            </div>
+        </div>
+    </div>
+    <script type="text/javascript">$('#unedit2').modal('show');</script>
+<% } %>
 <% include ../shares/delete_hint_modal.ejs %>
 <% include ../shares/check_data_modal.ejs %>
 <% include ../shares/check_modal2.ejs %>

+ 66 - 65
app/view/login/login.ejs

@@ -14,78 +14,79 @@
         html{height:100%;}
     </style>
 </head>
-<body class="login-body">
-<div class="login-bg img-<%- Math.floor(Math.random()*3 + 1); %>"></div>
+<body class="login-new-body">
+<div class="login-new-b d-flex flex-row align-items-center mt-5 ml-5">
+    <div>
+        <img src="/public/images/image_nav_logo.png">
+    </div>
+    <div class="ml-3">
+        <div class="logo-big-title text-white mt-1">纵横云平台</div>
+        <div class="logo-sm-title mt-1">计量支付&nbsp;&nbsp;&nbsp;结算决算</div>
+    </div>
+    <div class="side-border ml-4 mr-4"></div>
+    <div class="top-subtitle">施工舒心 / 监理省心 / 业主安心</div>
+</div>
 <div class="container">
-    <div class="row">
-        <div class="col-6">
-            <img src="/public/images/loginlogo.png">
+    <div class="d-flex justify-content-center align-items-center content-center">
+        <div class="left-login text-white pt-5 px-5">
+            <h2>Hi,您好!</h2>
+            <div class="login-border my-4"></div>
+            <div class="left-login-title" id="project_name">纵横捭阖  数“智”管家</div>
         </div>
-        <div class="col-6">
-            <% if (maintainData.status === maintainConst.status.ongoing) { %>
-                <form class="form-signin">
-                    <h4 class="text-center mb-3"><i class="fa fa-wrench"></i>系统正在维护</h4>
-                    <h4>预计恢复时间<%- (maintainData.duration !== maintainConst.duration.forever ? '为 ' + ctx.helper.dateTran(parseFloat(maintainData.maintain_time) + ctx.helper.timeAdd(maintainData.duration)) : ' 暂未确定') %></h4>
-                    <h4>造成不便敬请谅解。</h4>
-                </form>
-            <% } else { %>
-                <!--演示版-->
-                <form class="form-signin" method="post" action="/login" id="normal-form" style="min-width: 360px;position: relative">
-                    <!--<div class="change-form" style="position: absolute;right: 0;top: 0px;cursor: pointer;"><i style="padding:5px;font-size: 50px;color: #212529" class="fa fa-qrcode"></i></div>-->
-                    <div class="change-form" data-toggle="tooltip" data-placement="left" title="微信登录" style="position: absolute;right: 0;top: 0px;cursor: pointer;"><img alt="微信扫码登录" style="padding:5px;" width="50px" src="/public/images/icon-qrcode.png"></div>
-                    <!-- <h4 class="text-center mb-2">纵横云计量</h4> -->
-                    <h5 class="text-center mb-2" id="project_name"></h5>
-                    <h5 class="text-center mb-4 text-muted">用户登录</h5>
-                    <!--<nav class="nav nav-tabs nav-justified mb-3" role="tablist" id="login-tab">-->
-                    <!--<a class="nav-item nav-link" data-toggle="tab" data-type="1" href="#preview" role="tab">演示版登录</a>-->
-                    <!--<a class="nav-item nav-link active" data-toggle="tab" data-type="2" href="#paid" role="tab">项目版登录</a>-->
-                    <!--</nav>-->
-                    <div class="tab-content">
-                        <!--<div class="tab-pane active" id="preview" role="tabpanel">-->
-                        <!--<div class="form-group <% if (errorMessage !== undefined && errorMessage !== null) { %>has-danger<% } %>">-->
-                        <!--<input id="username" name="username" class="form-control form-control-sm" placeholder="通行账号 邮箱/手机" value="laiku123@qq.com" autofocus="">-->
-                        <!--</div>-->
-                        <!--<div class="form-group <% if (errorMessage !== undefined && errorMessage !== null) { %>has-danger<% } %>">-->
-                        <!--<input id="password" name="password" class="form-control form-control-sm" placeholder="输入密码" value="19930523" type="password">-->
-                        <!--</div>-->
-                        <!--</div>-->
-                        <div class="tab-pane active" id="paid" role="tabpanel">
-                            <div class="form-group <% if (errorMessage !== undefined && errorMessage !== null) { %>has-danger<% } %>">
-                                <input id="project" class="form-control" name="project" placeholder="项目编号" autofocus="" />
-                            </div>
-                            <div class="form-group <% if (errorMessage !== undefined && errorMessage !== null) { %>has-danger<% } %>">
-                                <input id="account" class="form-control" name="account" placeholder="输入账号" autofocus="" />
-                            </div>
-                            <div class="form-group <% if (errorMessage !== undefined && errorMessage !== null) { %>has-danger<% } %>">
-                                <input id="project-password" name="project_password" class="form-control" placeholder="输入密码" type="password" />
-                            </div>
-                        </div>
-                        <div class="form-group">
-                            <div class="alert alert-danger" <% if(errorMessage === undefined || errorMessage === null) { %>style="display: none"<% } %> role="alert" id="error-msg">
-                                <% if(errorMessage !== undefined && errorMessage !== null) { %><strong>登录失败</strong> <%= errorMessage %><% } %>
-                            </div>
-                        </div>
+        <% if (maintainData.status === maintainConst.status.ongoing) { %>
+        <form class="right-login position-relative text-center">
+            <h3 class="mt-5 mb-5"><i class="fa fa-wrench"></i>系统正在维护</h3>
+            <div>&nbsp;</div>
+            <h5 class="mt-5 mb-5">预计恢复时间<%- (maintainData.duration !== maintainConst.duration.forever ? '为 ' + ctx.helper.dateTran(parseFloat(maintainData.maintain_time) + ctx.helper.timeAdd(maintainData.duration)) : ' 暂未确定') %></h5>
+            <h5>造成不便敬请谅解。</h5>
+        </form>
+        <% } else { %>
+        <form method="post" action="/login" id="normal-form" class="right-login position-relative text-center" style="min-width: 360px;">
+            <h3 class="mt-5 mb-5">欢迎登录</h3>
+            <div class="position-absolute"><a href="javascript:void(0);" class="change-form" data-toggle="tooltip" data-placement="left" title="微信登录"><img alt="微信扫码登录" src="/public/images/icon_login_qr.png"></a></div>
+            <div class="mx-4">
+                <div class="input-group mb-4 <% if (errorMessage !== undefined && errorMessage !== null) { %>has-danger<% } %>">
+                    <div class="input-group-prepend">
+                        <div class="input-group-text"><img src="/public/images/icon_login_folder.png"></div>
                     </div>
-                    <div class="form-group">
-                        <button class="btn btn-primary btn-block" type="submit">登录</button>
-                        <input type="hidden" name="_csrf_j" value="<%= ctx.csrf %>" />
-                        <input type="hidden" name="type" value="2" />
+                    <input type="text" id="project" class="form-control" name="project" placeholder="项目编号" autofocus="">
+                </div>
+                <div class="input-group mb-4 <% if (errorMessage !== undefined && errorMessage !== null) { %>has-danger<% } %>">
+                    <div class="input-group-prepend">
+                        <div class="input-group-text"><img src="/public/images/icon_login_user.png"></div>
+                    </div>
+                    <input type="text" id="account" class="form-control" name="account" placeholder="帐号" autofocus="">
+                </div>
+                <div class="input-group mb-2 <% if (errorMessage !== undefined && errorMessage !== null) { %>has-danger<% } %>">
+                    <div class="input-group-prepend">
+                        <div class="input-group-text"><img src="/public/images/icon_login_lock.png"></div>
                     </div>
-                    <div class="pt-1 d-flex justify-content-end">
-                        <a href="#fg-password" data-toggle="modal" data-target="#fg-password"  class="mr-3">忘记密码?</a>
+                    <input type="password" id="project-password" name="project_password" class="form-control" placeholder="密码">
+                </div>
+                <div class="form-group mb-4">
+                    <div class="form-check d-flex justify-content-start pl-0">
+                        <label class="form-check-label" for="exampleCheck1"><a href="#fg-password" data-toggle="modal" data-target="#fg-password">忘记密码?</a></label>
                     </div>
-                </form>
-                <form class="form-signin" id="code-form" style="min-width: 360px;position: relative;display: none">
-                    <!--<div class="change-form" style="position: absolute;right: 0;top: 0px;cursor: pointer;"><i style="padding:5px;font-size: 40px;color: #212529" class="fa fa-laptop"></i></div>-->
-                    <div class="change-form" data-toggle="tooltip" data-placement="left" title="账号登录" style="position: absolute;right: 0;top: 0px;cursor: pointer;"><img alt="账号登录" style="padding:5px;" width="50px" src="/public/images/icon-pc.png"></div>
-                    <div id="wx-code" style="text-align: center">
+                </div>
+                <div class="form-group mb-4">
+                    <div class="alert alert-danger" <% if(errorMessage === undefined || errorMessage === null) { %>style="display: none"<% } %> role="alert" id="error-msg">
+                        <% if(errorMessage !== undefined && errorMessage !== null) { %><strong>登录失败</strong> <%= errorMessage %><% } %>
                     </div>
-                </form>
-            <% } %>
-            <!--项目版-->
-        </div>
+                </div>
+                <button type="submit" class="btn btn-primary btn-block">登&nbsp;&nbsp;录</button>
+                <input type="hidden" name="_csrf_j" value="<%= ctx.csrf %>" />
+                <input type="hidden" name="type" value="2" />
+            </div>
+        </form>
+        <form id="code-form" class="right-login position-relative text-center" style="min-width: 360px;display: none">
+            <h3 class="mt-3">&nbsp;</h3>
+            <div class="position-absolute"><a href="javascript:void(0);" class="change-form" data-toggle="tooltip" data-placement="left" title="账号登录"><img alt="账号登录" src="/public/images/icon_login_pc.png"></a></div>
+            <div id="wx-code" style="text-align: center">
+            </div>
+        </form>
+        <% } %>
     </div>
-    <div class="text-white fixed-bottom"><p class="text-center mb-1">Copyright © 2019 <a href="https://smartcost.com.cn" target="_blank" class="text-white">珠海纵横创新软件有限公司</a>.All Rights Reserved.<a class="text-white ml-2" href="http://www.miitbeian.gov.cn" target="_blank">粤ICP备14032472号</a></p></div>
+    <div class="text-white fixed-bottom"><p class="text-center mb-1">Copyright © 2019 <a href="https://smartcost.com.cn" target="_blank" class="text-white">珠海纵横创新软件有限公司</a>.All Rights Reserved.<a class="text-white ml-2" href="https://beian.miit.gov.cn" target="_blank">粤ICP备14032472号</a></p></div>
 </div>
 <!--忘记项目版密码-->
 <div class="modal fade" id="fg-password" data-backdrop="static">

+ 51 - 41
app/view/login/login_port.ejs

@@ -14,51 +14,61 @@
         html{height:100%;}
     </style>
 </head>
-<body class="login-body">
-<div class="login-bg img-<%- Math.floor(Math.random()*3 + 1); %>"></div>
+<body class="login-new-body">
+<div class="login-new-b d-flex flex-row align-items-center mt-5 ml-5">
+    <div>
+        <img src="/public/images/image_nav_logo.png">
+    </div>
+    <div class="ml-3">
+        <div class="logo-big-title text-white mt-1">纵横云平台</div>
+        <div class="logo-sm-title mt-1">计量支付&nbsp;&nbsp;&nbsp;结算决算</div>
+    </div>
+    <div class="side-border ml-4 mr-4"></div>
+    <div class="top-subtitle">施工舒心 / 监理省心 / 业主安心</div>
+</div>
 <div class="container">
-    <div class="row">
-        <div class="col-6">
-            <img src="/public/images/loginlogo.png">
+    <div class="d-flex justify-content-center align-items-center content-center">
+        <div class="left-login text-white pt-5 px-5">
+            <h2>Hi,您好!</h2>
+            <div class="login-border my-4"></div>
+            <div class="left-login-title" id="project_name"><%- projectData ? projectData.name : '纵横捭阖  数“智”管家' %></div>
         </div>
-        <div class="col-6">
-            <% if (maintainData.status === maintainConst.status.ongoing) { %>
-                <form class="form-signin">
-                    <h4 class="text-center mb-3"><i class="fa fa-wrench"></i>系统正在维护</h4>
-                    <h4>预计恢复时间<%- (maintainData.duration !== maintainConst.duration.forever ? '为 ' + ctx.helper.dateTran(parseFloat(maintainData.maintain_time) + ctx.helper.timeAdd(maintainData.duration)) : ' 暂未确定') %></h4>
-                    <h4>造成不便敬请谅解。</h4>
-                </form>
-            <% } else { %>
-                <!--演示版-->
-                <form class="form-signin" method="post" action="/login/port">
-                    <h4 class="text-center mb-2">纵横云计量</h4>
-                    <h5 class="text-center mb-4 text-muted"><%- projectData ? projectData.name : '' %></h5>
-                    <% if (accountData) { %>
-                        <h5>您好,<%- accountData.mobile %></h5>
-                        <% if (accountData.bind === 0) { %>
-                            <!--查询账号存在,进行绑定-->
-                            <h5>系统查询以下账号,请确认无误后进行绑定。</h5>
-                            <h5>姓名:<span class="text-danger"><%- accountData.name %></span></h5>
-                            <h5>账号:<span class="text-danger"><%- accountData.account %></span></h5>
-                            <h5>手机:<span class="text-danger"><%- accountData.mobile %></span></h5>
-                            <h5>单位:<span class="text-danger"><%- accountData.company %></span></h5>
-                            <h5>职称:<span class="text-danger"><%- accountData.role %></span></h5>
-                            <div class="form-group mt-4">
-                                <input type="hidden" value="<%= ctx.csrf %>" name="_csrf_j" >
-                                <input type="hidden" value="3" name="type" >
-                                <input type="hidden" value="<%= projectData.code %>" name="code" >
-                                <input type="hidden" value="<%= accountData.id %>" name="accountId" >
-                                <button type="submit" class="btn btn-primary btn-block">绑定并登录系统</button>
-                            </div>
-                        <% } %>
+        <% if (maintainData.status === maintainConst.status.ongoing) { %>
+            <form class="right-login position-relative text-center">
+                <h3 class="mt-5 mb-5"><i class="fa fa-wrench"></i>系统正在维护</h3>
+                <div>&nbsp;</div>
+                <h5 class="mt-5 mb-5">预计恢复时间<%- (maintainData.duration !== maintainConst.duration.forever ? '为 ' + ctx.helper.dateTran(parseFloat(maintainData.maintain_time) + ctx.helper.timeAdd(maintainData.duration)) : ' 暂未确定') %></h5>
+                <h5>造成不便敬请谅解。</h5>
+            </form>
+        <% } else { %>
+            <form method="post" action="/login/port" class="right-login position-relative" style="min-width: 360px;">
+                <h3 class="mt-5 mb-4 text-center">欢迎登录</h3>
+                <div class="mx-4">
+                <% if (accountData) { %>
+                    <h5>您好,<%- accountData.mobile %></h5>
+                    <% if (accountData.bind === 0) { %>
+                        <!--查询账号存在,进行绑定-->
+                        <h5 class="mb-3">系统查询到以下账号,请确认无误后进行绑定。</h5>
+                        <h5>姓名:<span class="text-danger"><%- accountData.name %></span></h5>
+                        <h5>账号:<span class="text-danger"><%- accountData.account %></span></h5>
+                        <h5>手机:<span class="text-danger"><%- accountData.mobile %></span></h5>
+                        <h5>单位:<span class="text-danger"><%- accountData.company %></span></h5>
+                        <h5>职称:<span class="text-danger"><%- accountData.role %></span></h5>
+                        <div class="form-group mt-4">
+                            <input type="hidden" value="<%= ctx.csrf %>" name="_csrf_j" >
+                            <input type="hidden" value="3" name="type" >
+                            <input type="hidden" value="<%= projectData.code %>" name="code" >
+                            <input type="hidden" value="<%= accountData.id %>" name="accountId" >
+                            <button type="submit" class="btn btn-primary btn-block">绑定并登录系统</button>
+                        </div>
                     <% } %>
-                    <h5 class="text-danger"><%- errorMessage %></h5>
-                </form>
-            <% } %>
-        </div>
-    </div>
+                <% } %>
+                <h5 class="text-danger text-center"><%- errorMessage %></h5>
+                </div>
+            </form>
+        <% } %>
     <!--项目版-->
-    <div class="text-white fixed-bottom"><p class="text-center mb-1">Copyright © 2019 <a href="https://smartcost.com.cn" target="_blank" class="text-white">珠海纵横创新软件有限公司</a>.All Rights Reserved.<a class="text-white ml-2" href="http://www.miitbeian.gov.cn" target="_blank">粤ICP备14032472号</a></p></div>
+    <div class="text-white fixed-bottom"><p class="text-center mb-1">Copyright © 2019 <a href="https://smartcost.com.cn" target="_blank" class="text-white">珠海纵横创新软件有限公司</a>.All Rights Reserved.<a class="text-white ml-2" href="https://beian.miit.gov.cn" target="_blank">粤ICP备14032472号</a></p></div>
 </div>
 <!-- JS. -->
 <div class="toast" style="text-align: center">

+ 39 - 0
app/view/material/audit_modal.ejs

@@ -737,3 +737,42 @@
         }
     });
 </script>
+<!--材料调差-小数位数-->
+<div class="modal fade" id="cc-digits" 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">
+
+                    <div class="row">
+                        <div class="col-4">
+                            <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="<%- material.decimal.qty %>" <% if (!((material.status === auditConst.status.uncheck || material.status === auditConst.status.checkNo) && ctx.session.sessionUser.accountId === material.user_id)) { %>disabled<% } %>>
+                            </div>
+                        </div>
+                        <div class="col-4">
+                            <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="<%- material.decimal.tp %>" <% if (!((material.status === auditConst.status.uncheck || material.status === auditConst.status.checkNo) && ctx.session.sessionUser.accountId === material.user_id)) { %>disabled<% } %>>
+                            </div>
+                        </div>
+                    </div>
+                </div>
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-sm btn-secondary" data-dismiss="modal">取消</button>
+                <% if ((material.status === auditConst.status.uncheck || material.status === auditConst.status.checkNo) && ctx.session.sessionUser.accountId === material.user_id) { %>
+                    <button type="button" class="btn btn-sm btn-primary">确认修改</button>
+                <% } %>
+            </div>
+        </div>
+    </div>
+</div>

+ 8 - 5
app/view/material/exponent.ejs

@@ -18,6 +18,7 @@
                 <!--</div>-->
             </div>
             <div class="ml-auto">
+                <!--<a href="#cc-digits" class="btn btn-sm btn-outline-primary" data-toggle="modal" data-target="#cc-digits" data-placement="bottom" title="" >小数位数</a>-->
             </div>
         </div>
     </div>
@@ -30,7 +31,8 @@
                 <div class="sjs-height-1" id="material-exponent-spread">
                 </div>
                 <!--下部分-->
-                <div class="bcontent-wrap">
+                <div class="bcontent-wrap" id="main-bottom">
+                    <div id="main-resize" class="resize-y" r-Type="height" div1=".sjs-height-1" div2=".bcontent-wrap" title="调整大小"><!--调整上下高度条--></div>
                     <div class="bc-bar mb-1">
                         <div class="input-group input-group-sm ">
                             <div class="input-group-prepend">
@@ -47,8 +49,8 @@
                             </select>
                         </div>
                     </div>
-                    <div class="sp-wrap">
-                        <div class="col-7 p-0">
+                    <div class="sp-wrap" style="max-width: 800px;min-width: 500px;">
+                        <div class="col-12 p-0">
                             <table class="table table-sm table-bordered">
                                 <tr><th rowspan="2"></th><th colspan="2" class="text-center">信息价</th><th colspan="2" class="text-center">价格指数</th></tr>
                                 <tr class="text-center"><th>本期金额</th><th>截止本期金额</th>
@@ -79,13 +81,13 @@
                         <div class="sjs-sh-1">
                             <table class="table table-bordered">
                                 <thead>
-                                <tr><th>需调整的价差</th><th>金额</th><th>选择</th></tr>
+                                <tr><th>选择</th><th>需调整的价差</th><th>金额</th></tr>
                                 </thead>
                                 <tbody id="calc_basic_select">
                                 <% for (const bq of ex_calc) { %>
                                     <tr>
-                                        <td><%- materialType.ex_basic_text[bq.code] %></td><td><%- bq.value %></td>
                                         <td><input type="checkbox" value="<%- bq.code %>" <% if (material.readOnly) { %>disabled<% } %> class="calc_select" <% if (bq.select) { %>checked<% } %>></td>
+                                        <td><%- materialType.ex_basic_text[bq.code] %></td><td><% if (bq.code === 'zdy' && !material.readOnly) { %><input type="number" id="calc_zdy" class="form-control form-control-sm" value="<%- bq.value %>"><% } else { %><%- bq.value %><% } %></td>
                                     </tr>
                                 <% } %>
                                 </tbody>
@@ -113,6 +115,7 @@
     const readOnly = <%- material.readOnly %>;
     const materialID = <%- material.id %>;
     const materialOrder = <%- material.order %>;
+    const decimal = JSON.parse('<%- JSON.stringify(ctx.tender.info.decimal) %>');
     let m_tp = <%= material.m_tp !== null ? material.m_tp : 0 %>;
     let ex_tp = <%= material.ex_tp !== null ? material.ex_tp : 0 %>;
     const pre_tp = <%= material.pre_tp !== null ? material.pre_tp : 0 %>;

+ 0 - 0
app/view/material/file.ejs


部分文件因为文件数量过多而无法显示