ソースを参照

合同增加项目节添加功能

ellisran 4 週間 前
コミット
b653ba55f3

+ 44 - 0
app/controller/contract_controller.js

@@ -1,4 +1,9 @@
 'use strict';
+const stdDataAddType = {
+    withParent: 1,
+    child: 2,
+    next: 3,
+};
 const auditConst = require('../const/audit');
 const contractConst = require('../const/contract');
 const moment = require('moment');
@@ -260,6 +265,8 @@ module.exports = app => {
                 const contractOptions = ctx.helper._.cloneDeep(ctx.contractOptions);
                 contractOptions.contract_type = ctx.contract_type;
                 const contractTreeAudits = await ctx.service.contractTreeAudit.getAllDataByCondition({ where: contractOptions });
+                const [stdBills, stdChapters] = ctx.contract.tender ? await this.ctx.service.valuation.getValuationStdList(
+                    ctx.contract.valuation, ctx.contract.measure_type) : await ctx.service.budgetStd.getStdList(ctx.subProject.std_id, 'yu');
                 const renderData = {
                     contractTreeAudits,
                     audit_permission: ctx.contract_audit_permission,
@@ -269,6 +276,7 @@ module.exports = app => {
                     contractConst,
                     whiteList,
                     thisUrl: `/sp/${ctx.subProject.id}` + (ctx.contract_tender ? `/contract/tender/${ctx.contract.id}/detail` : '/contract/detail'),
+                    stdChapters,
                 };
                 if (ctx.session.sessionUser.is_admin) {
                     const accountList = await ctx.service.projectAccount.getAllSubProjectAccount(ctx.subProject);
@@ -350,6 +358,9 @@ module.exports = app => {
                     case 'paste-block':
                         responseData.data = await this._pasteBlock(ctx, data.postData, options);
                         break;
+                    case 'add-std':
+                        responseData.data = await this._addStd(ctx, data.postData, options);
+                        break;
                     case 'add-contract':
                         responseData.data = await ctx.service.contract.add(options, data.postData.select, data.postData.contract);
                         break;
@@ -418,6 +429,39 @@ module.exports = app => {
                 (!data.block || data.block.length <= 0)) throw '参数错误';
             return await ctx.service.contractTree.pasteBlockData(options, data.id, data.block);
         }
+        /**
+         * 从标准项目表添加数据
+         * @param ctx
+         * @return {Promise<void>}
+         */
+        async _addStd(ctx, data, options) {
+            if ((isNaN(data.id) || data.id <= 0) || !data.stdType || !data.stdNode) throw '参数错误';
+            // todo 校验项目是否使用该库的权限
+            if (!(ctx.session.sessionUser.is_admin || ctx.audit_permission.permission_edit)) {
+                throw '没有操作权限';
+            }
+            let stdLib,
+                addType;
+            switch (data.stdType) {
+                case 'xmj':
+                    stdLib = ctx.service.stdXmj;
+                    addType = stdDataAddType.withParent;
+                    break;
+                default:
+                    throw '未知标准库';
+            }
+            const stdData = await stdLib.getDataByDataId(data.stdLibId, data.stdNode);
+            switch (addType) {
+                // case stdDataAddType.child:
+                //     return await ctx.service.contractTree.addStdNodeAsChild(options, data.id, stdData);
+                // case stdDataAddType.next:
+                //     return await ctx.service.contractTree.addStdNode(options, data.id, stdData);
+                case stdDataAddType.withParent:
+                    return await ctx.service.contractTree.addStdNodeWithParent(options, data.id, stdData, stdLib);
+                default:
+                    throw '未知添加方式';
+            }
+        }
 
         /**
          * 上传附件

+ 128 - 1
app/public/js/contract_detail.js

@@ -1,5 +1,6 @@
 const copyBlockTag = 'zh.calc.copyBlock';
 $(document).ready(function() {
+    let stdXmj;
     const ckBillsSpread = '/contract/' + window.location.pathname.split('/')[2] + '/' + contractConst.typeMap[contract_type] + '/detail/contract-billsSelect';
     autoFlashHeight();
     const contractSpreadSetting = {
@@ -2052,6 +2053,91 @@ $(document).ready(function() {
         });
     });
 
+    const stdLibCellDoubleClick = function (updateData, stdNode, stdTree) {
+        if (!permission_edit) return;
+        const mainSheet = contractSheet;
+        if (!stdTree || !mainSheet.zh_tree) { return; }
+
+        const mainTree = mainSheet.zh_tree;
+        const sel = mainSheet.getSelections()[0];
+        const mainNode = mainTree.nodes[sel.row];
+        if (!stdNode) return;
+
+        // if (updateData.postData.stdType === 'gcl') {
+        //     if (mainNode.code && mainNode.code !== '' && !mainTree.isLeafXmj(mainNode)) {
+        //         toastr.warning('非最底层项目下,不应添加节点');
+        //         return;
+        //     }
+        // } else {
+        //     const stdNodes = stdTree.getAllParents(stdNode);
+        //     for (const node of stdNodes) {
+        //         const parent = mainTree.datas.find(x => { return x.code === node.code && x.name === node.name; });
+        //     }
+        // }
+
+        console.log(mainTree.getNodeKey(mainNode), stdTree.getNodeKey(stdNode), stdNode.list_id, updateData.postData.stdType);
+
+        postData(window.location.pathname + '/update', {
+            postType: 'add-std',
+            postData: {
+                id: mainTree.getNodeKey(mainNode),
+                tender_id: mainNode.tender_id,
+                stdType: updateData.postData.stdType,
+                stdLibId: stdNode.list_id,
+                stdNode: stdTree.getNodeKey(stdNode)
+            }
+        }, function (result) {
+            const refreshNode = mainTree.loadPostData(result);
+            contractTreeSpreadObj.refreshTree(mainSheet, refreshNode);
+            if (sel) {
+                if (refreshNode.create && refreshNode.create.length > 0) {
+                    mainSheet.setSelection(refreshNode.create[refreshNode.create.length - 1].index, sel.col, sel.rowCount, sel.colCount);
+                    SpreadJsObj.reloadRowsBackColor(mainSheet, [sel.row, refreshNode.create[refreshNode.create.length - 1].index]);
+                } else {
+                    const node = _.find(mainTree.nodes, {code: stdNode.code, name: stdNode.name});
+                    if (node) {
+                        mainSheet.setSelection(mainTree.nodes.indexOf(node), sel.col, sel.rowCount, sel.colCount);
+                        SpreadJsObj.reloadRowsBackColor(mainSheet, [sel.row, mainTree.nodes.indexOf(node)]);
+                    }
+                }
+            }
+            contractTreeSpreadObj.refreshOperationValid(mainSheet);
+            contractSpread.focus();
+        });
+    };
+    const stdXmjSetting = {
+        selector: '#std-xmj',
+        stdType: 'xmj',
+        libs: stdChapters,
+        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: '@', readOnly: true, cellType: 'tree'},
+                {title: '名称', field: 'name', hAlign: 0, width: 150, formatter: '@', readOnly: true},
+            ],
+            treeCol: 0,
+            emptyRows: 0,
+            headRows: 1,
+            headRowHeight: [32],
+            defaultRowHeight: 21,
+            headerFont: '12px 微软雅黑',
+            font: '12px 微软雅黑',
+            headColWidth: [30],
+            selectedBackColor: '#fffacd',
+        },
+        spreadClassCss: 'sjs-contract',
+        cellDoubleClick: stdLibCellDoubleClick,
+        page: 'contract',
+        tid: window.location.pathname.split('/')[2],
+    };
+
     // 显示层次
     (function (select, sheet) {
         $(select).click(function () {
@@ -2082,15 +2168,55 @@ $(document).ready(function() {
             }, 100);
         });
     })('a[name=showLevel]', contractSheet);
+
     $.divResizer({
-        select: '#contract-resize',
+        select: '#right-spr',
         callback: function () {
             contractSpread.refresh();
+            if (stdXmj) stdXmj.spread.refresh();
+            const width = (($('#right-view').width()/$('#right-view').parent('div').width())*100).toFixed();
+        }
+    });
+    $.divResizer({
+        select: '#contract-resize',
+        callback: function () {
             let bcontent = $(".bcontent-wrap") ? $(".bcontent-wrap").height() : 0;
             $(".sp-wrap").height(bcontent-30);
+            updateSjsHeight();
+            contractSpread.refresh();
+            if (stdXmj) stdXmj.spread.refresh();
             // posSpread.refresh();
         }
     });
+    // 展开收起月信息价并浏览器记住本期展开收起
+    $('a', '.right-nav').bind('click', function () {
+        const tab = $(this), tabPanel = $(tab.attr('content'));
+        if (!tab.hasClass('active')) {
+            $('a', '.side-menu').removeClass('active');
+            $('#right-view .tab-content .tab-pane').removeClass('active');
+            tab.addClass('active');
+            tabPanel.addClass('active');
+            showSideTools(tab.hasClass('active'));
+            if (tab.attr('content') === '#std-xmj') {
+                if (!stdXmj) {
+                    stdXmj = $.stdLib(stdXmjSetting);
+                }
+                updateSjsHeight();
+                stdXmj.spread.refresh();
+            }
+        } else {
+            tab.removeClass('active');
+            tabPanel.removeClass('active');
+            showSideTools(tab.hasClass('active'));
+        }
+        contractSpread.refresh();
+    });
+    function updateSjsHeight() {
+        $('.sjs-contract').height($('.sjs-height-1').height() - getObjHeight($('.sjs-bar')));
+    }
+    function getObjHeight(select) {
+        return select.length > 0 ? select.outerHeight() : 0;
+    }
     $.subMenu({
         menu: '#sub-menu', miniMenu: '#sub-mini-menu', miniMenuList: '#mini-menu-list',
         toMenu: '#to-menu', toMiniMenu: '#to-mini-menu',
@@ -2106,6 +2232,7 @@ $(document).ready(function() {
             }
             autoFlashHeight();
             contractSpread.refresh();
+            if (stdXmj) stdXmj.spread.refresh();
         }
     });
 });

+ 1 - 1
app/public/js/shares/cs_tools.js

@@ -1729,7 +1729,7 @@ const showSelectTab = function(select, spread, afterShow) {
             '        </div>\n' +
             '    </div>' +
             '</div>\n' +
-            `<div id="std-${setting.stdType}-spread" class="sjs-sh"></div>\n`
+            `<div id="std-${setting.stdType}-spread" class="${setting.spreadClassCss ? setting.spreadClassCss : 'sjs-sh'}"></div>\n`
         );
         autoFlashHeight();
         const pathTree = createNewPathTree('base', setting.treeSetting);

+ 175 - 0
app/service/contract_tree.js

@@ -7,6 +7,7 @@
 const BaseService = require('../base/base_service');
 const contractConst = require('../const/contract');
 const rootId = -1;
+const billsUtils = require('../lib/bills_utils');
 
 module.exports = app => {
 
@@ -1036,6 +1037,180 @@ module.exports = app => {
 
             return data;
         }
+
+        /**
+         * 添加节点,并同步添加父节点
+         * @param {Number} tenderId - 标段id
+         * @param {Number} selectId - 选中节点id
+         * @param {Object} stdData - 节点数据
+         * @param {StandardLib} stdLib - 标准库
+         * @return {Promise<void>}
+         */
+        async addStdNodeWithParent(options, kid, stdData, stdLib) {
+            if (!options[this.setting.type]) throw '参数有误';
+            const select = kid ? await this.getDataByKid(options, kid) : null;
+            if (!select) throw '新增子节点数据错误';
+            if (select && select.c_code) throw '合同无法新增子节点';
+            // 查询完整标准清单,并按层次排序
+            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 = {
+                            name: stdNode.name,
+                            unit: stdNode.unit,
+                        };
+                        newData.code = stdNode.code ? stdNode.code : '';
+                        newData.is_leaf = (i === len - 1) ? 1 : 0;
+                        [addResult, node] = await this._addChildNodeData(options, node, newData);
+                    } else {
+                        const parent = node;
+                        const condition = this._.cloneDeep(options);
+                        condition.code = stdNode.code;
+                        condition.name = stdNode.name;
+                        node = await this.getDataByCondition(condition);
+                        if (!node) {
+                            // let children = await this.getChildrenByParentId(options, parent[this.setting.pid]);
+                            // if (children.length === 0) {
+                            //     throw '原台账节点为子项时不能添加它的子项';
+                            // }
+                            isNew = true;
+                            const newData = {
+                                name: stdNode.name,
+                                unit: stdNode.unit,
+                            };
+                            newData.code = stdNode.code ? stdNode.code : '';
+                            newData.is_leaf = (i === len - 1) ? 1 : 0;
+                            [addResult, node] = await this._addChildAutoOrder(options, parent, newData);
+                            if (parent && parent.is_leaf) {
+                                await this.transaction.update(this.tableName, { id: parent.id, is_leaf: 0 });
+                                updateParent = parent;
+                            }
+                            firstNew = node;
+                        } else {
+                            expandIds.push(node[this.setting.pid]);
+                        }
+                    }
+                }
+                await this.transaction.commit();
+            } catch (err) {
+                await this.transaction.rollback();
+                throw err;
+            }
+
+            // 查询应返回的结果
+            let createData = [],
+                updateData = [];
+            if (firstNew) {
+                createData = await this.getDataByFullPath(options, firstNew[this.setting.fullPath] + '%');
+                updateData = await this.getNextsData(options, firstNew[this.setting.pid], firstNew[this.setting.order]);
+                if (updateParent) {
+                    updateData.push(await this.getDataByCondition({ id: updateParent.id }));
+                }
+            }
+            return { create: createData, update: updateData };
+        }
+
+        /**
+         * 根据 父节点id 获取子节点
+         * @param tenderId
+         * @param nodeId
+         * @return {Promise<*>}
+         */
+        async getChildrenByParentId(options, pid) {
+            const sql = 'SELECT * FROM ?? WHERE ' + this.ctx.helper._getOptionsSql(options) + ' AND ' + this.setting.pid + ' = ? ORDER BY `order` ASC';
+            const sqlParam = [this.tableName, pid];
+            const data = await this.db.query(sql, sqlParam);
+
+            const sql1 = 'SELECT * FROM ?? WHERE ' + this.ctx.helper._getOptionsSql(options) + ' AND ' + this.setting.pid + ' = ? ORDER BY `order` ASC';
+            const sqlParam1 = [this.ctx.service.contract.tableName, pid];
+            const data1 = await this.db.query(sql1, sqlParam1);
+            // data和data1合并且按order排序
+            const resultData = data.concat(data1).sort((a, b) => a.order - b.order);
+            return resultData;
+        }
+
+        /**
+         * 根据parentData, data新增数据(新增为parentData的最后一个子项)
+         * @param {Number} tenderId - 标段id
+         * @param {Object} parentData - 父项数据
+         * @param {Object} data - 新增节点,初始数据
+         * @return {Promise<*>} - 新增结果
+         * @private
+         */
+        async _addChildNodeData(options, parentData, data) {
+            if (!data) {
+                data = {};
+            }
+            const pid = parentData ? parentData[this.setting.kid] : rootId;
+
+            const maxId = await this._getMaxLid(options);
+            data.id = this.uuid.v4();
+            data[this.setting.spid] = options.spid || null;
+            data[this.setting.pid] = pid;
+            data[this.setting.kid] = maxId + 1;
+            data[this.setting.type] = options[this.setting.type];
+            data[this.setting.mid] = options.tid || null;
+            data[this.setting.level] = parentData ? parentData[this.setting.level] + 1 : 1;
+            if (data[this.setting.order] === undefined) {
+                data[this.setting.order] = 1;
+            }
+            data.full_path = parentData ? parentData.full_path + '-' + data[this.setting.kid] : '' + data[this.setting.kid];
+            if (data[this.setting.isLeaf] === undefined) {
+                data[this.setting.isLeaf] = true;
+            }
+            const result = await this.transaction.insert(this.tableName, data);
+
+            this._cacheMaxLid(options, maxId + 1);
+
+            return [result, data];
+        }
+
+        /**
+         * 根据parentData, data新增数据(自动排序)
+         * @param tenderId
+         * @param parentData
+         * @param data
+         * @return {Promise<void>}
+         * @private
+         */
+        async _addChildAutoOrder(options, parentData, data) {
+            const self = this;
+            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[this.setting.kid] : rootId;
+            const children = await this.getChildrenByParentId(options, pid);
+            const preData = findPreData(children, data);
+            if (!preData || children.indexOf(preData) < children.length - 1) {
+                await this._updateChildrenOrder(options, pid, preData ? preData.order + 1 : 1);
+            }
+            data.order = preData ? preData.order + 1 : 1;
+            const [addResult, node] = await this._addChildNodeData(options, parentData, data);
+
+            return [addResult, node];
+        }
     }
     return ContractTree;
 };

+ 28 - 8
app/view/contract/detail.ejs

@@ -45,16 +45,27 @@
             </div>
         </div>
     </div>
-    <div class="content-wrap row">
-        <div class="c-header p-0 col-12"></div>
-        <!--核心内容(两栏)-->
+    <div class="content-wrap pr-46">
+        <div class="c-header p-0">
+        </div>
         <div class="row w-100 sub-content">
-            <!--左栏-->
-            <div class="c-body" id="left-view" style="width: 100%">
-                <!--0号台账模式-->
-                <div class="sjs-height-1" style="overflow: hidden" id="contract-spread">
+            <div class="col-12 c-body">
+                <!--上部分-->
+                <div class="sjs-height-1 row w-100 sub-content">
+                    <!--左栏-->
+                    <div class="c-body" id="left-view" style="width: 100%">
+                        <div class="sjs-height-1" style="overflow: hidden" id="contract-spread"></div>
+                    </div>
+                    <div class="c-body" id="right-view" style="display: none; width: 30%">
+                        <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="std-xmj" class="tab-pane">
+                            </div>
+                        </div>
+                    </div>
                 </div>
-                <div class="bcontent-wrap">
+                <!--下部分-->
+                <div class="bcontent-wrap mt-1">
                     <div id="contract-resize" class="resize-y" id="top-spr" r-Type="height" div1=".sjs-height-1" div2=".bcontent-wrap" title="调整大小"><!--调整上下高度条--></div>
                     <div class="bc-bar mb-1">
                         <ul class="nav nav-tabs">
@@ -186,6 +197,14 @@
                     </div>
                 </div>
             </div>
+            <div class="side-menu">
+                <!--右侧菜单-->
+                <ul class="nav flex-column right-nav">
+                    <li class="nav-item">
+                        <a class="nav-link" content="#std-xmj" href="javascript: void(0);">项目节</a>
+                    </li>
+                </ul>
+            </div>
         </div>
     </div>
 </div>
@@ -200,5 +219,6 @@
     const contractConst = JSON.parse(unescape('<%- escape(JSON.stringify(contractConst)) %>'));
     let contractTreeAudits = JSON.parse(unescape('<%- escape(JSON.stringify(contractTreeAudits)) %>'));
     const thisUrl = JSON.parse(unescape('<%- escape(JSON.stringify(thisUrl)) %>'));
+    const stdChapters = JSON.parse(unescape('<%- escape(JSON.stringify(stdChapters)) %>'));
     let contractPays = [];
 </script>