Przeglądaj źródła

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

MaiXinRong 4 tygodni temu
rodzic
commit
4e7d5a6419

+ 2 - 2
app/const/source_type.js

@@ -11,8 +11,8 @@ const sourceTypeData = [
     { id: 100, name: '支付审批', key: 'payment' },
     { id: 101, name: '安全生产费', key: 'payment_safe' },
     { id: 200, name: '动态投资', key: 'budget' },
-    { id: 300, name: '项目合同', key: 'project_contract' },
-    { id: 301, name: '标段合同', key: 'tender_contract' },
+    { id: 300, name: '合同管理', key: 'contract_management' },
+    // { id: 301, name: '标段合同', key: 'tender_contract' },
 ];
 
 // sourceType = { tender: 1, advance: 10, ... };

+ 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 '未知添加方式';
+            }
+        }
 
         /**
          * 上传附件

+ 143 - 66
app/controller/report_controller.js

@@ -148,6 +148,22 @@ module.exports = app => {
             return { accountList, newAccountGroup, advance };
         }
 
+        async _getPrjAccList(ctx, pageShow) {
+            let prjAccList = await ctx.service.projectAccount.getAllSubProjectAccount(ctx.subProject, ['id', 'name', 'company', 'role', 'enable', 'is_admin', 'account_group', 'mobile', 'company_id', 'account', 'telephone', 'permission', 'sign_path', 'stamp_path']);
+            // 根据需求,如果开启了‘开启个人「签字」’功能,则只能是本身用户签名,管理员例外
+            if (!ctx.session.sessionUser.is_admin && pageShow.individualSign === 1) {
+                // 削减其他account
+                const newAccList = [];
+                for (const acc of prjAccList) {
+                    if (acc.id === this.ctx.session.sessionUser.accountId) {
+                        newAccList.push(acc);
+                    }
+                }
+                prjAccList = newAccList;
+            }
+            return prjAccList;
+        }
+
         /**
          * 报表显示页面
          *
@@ -235,25 +251,9 @@ module.exports = app => {
                         archiveList = JSON.parse(archives[0].content);
                     }
                 }
-                // let prjAccList = await ctx.service.projectAccount.getAllAccountByProjectId(tender.data.project_id);
-                // prjAccList = prjAccList.filter(pa => pa.enable !== 0);
-                let prjAccList = await ctx.service.projectAccount.getAllSubProjectAccount(ctx.subProject, ['id', 'name', 'company', 'role', 'enable', 'is_admin', 'account_group', 'mobile', 'company_id', 'account', 'telephone', 'permission', 'sign_path', 'stamp_path']);
                 const roleList = await ctx.service.signatureRole.getSignatureRolesByTenderId(tender.id);
                 const usedList = await ctx.service.signatureUsed.getSignatureUsedByTenderId(tender.id);
-
-                // ctx.session.sessionUser.is_admin
-                // const pageShow = ctx.session.sessionProject.page_show;
-                // 根据需求,如果开启了‘开启个人「签字」’功能,则只能是本身用户签名,管理员例外
-                if (!ctx.session.sessionUser.is_admin && pageShow.individualSign === 1) {
-                    // 削减其他account
-                    const newAccList = [];
-                    for (const acc of prjAccList) {
-                        if (acc.id === this.ctx.session.sessionUser.accountId) {
-                            newAccList.push(acc);
-                        }
-                    }
-                    prjAccList = newAccList;
-                }
+                const prjAccList = await this._getPrjAccList(ctx, pageShow);
 
                 // 分类列表
                 const categoryData = await this.ctx.service.category.getAllCategory(ctx.subProject);
@@ -433,22 +433,9 @@ module.exports = app => {
                 const isAdmin = ctx.session.sessionUser.is_admin;
                 const lastAuditor = null;
                 const archiveList = [];
-                // let prjAccList = await ctx.service.projectAccount.getAllAccountByProjectId(tender.pid);
-                // prjAccList = prjAccList.filter(pa => pa.enable !== 0);
-                let prjAccList = await ctx.service.projectAccount.getAllSubProjectAccount(ctx.subProject, ['id', 'name', 'company', 'role', 'enable', 'is_admin', 'account_group', 'mobile', 'company_id', 'account', 'telephone', 'permission', 'sign_path', 'stamp_path']);
                 const roleList = await ctx.service.signatureRole.getSignatureRolesByTenderId(tender.id);
                 const usedList = await ctx.service.signatureUsed.getSignatureUsedByTenderId(tender.id);
-                // 根据需求,如果开启了‘开启个人「签字」’功能,则只能是本身用户签名,管理员例外
-                if (!ctx.session.sessionUser.is_admin && pageShow.individualSign === 1) {
-                    // 削减其他account
-                    const newAccList = [];
-                    for (const acc of prjAccList) {
-                        if (acc.id === this.ctx.session.sessionUser.accountId) {
-                            newAccList.push(acc);
-                        }
-                    }
-                    prjAccList = newAccList;
-                }
+                const prjAccList = await this._getPrjAccList(ctx, pageShow);
                 // 分类列表 todo 支付审批 目前没有所属子项目,自定义分类暂无法按所属子项目取值
                 const categoryData = await this.ctx.service.category.getAllCategory(this.ctx.subProject.id);
                 // 获取用户权限
@@ -588,22 +575,9 @@ module.exports = app => {
                 const isAdmin = ctx.session.sessionUser.is_admin;
                 const lastAuditor = null;
                 const archiveList = [];
-                // let prjAccList = await ctx.service.projectAccount.getAllAccountByProjectId(pid);
-                // prjAccList = prjAccList.filter(pa => pa.enable !== 0);
-                let prjAccList = await ctx.service.projectAccount.getAllSubProjectAccount(ctx.subProject, ['id', 'name', 'company', 'role', 'enable', 'is_admin', 'account_group', 'mobile', 'company_id', 'account', 'telephone', 'permission', 'sign_path', 'stamp_path']);
                 const roleList = [];
                 const usedList = [];
-                // 根据需求,如果开启了‘开启个人「签字」’功能,则只能是本身用户签名,管理员例外
-                if (!ctx.session.sessionUser.is_admin && pageShow.individualSign === 1) {
-                    // 削减其他account
-                    const newAccList = [];
-                    for (const acc of prjAccList) {
-                        if (acc.id === this.ctx.session.sessionUser.accountId) {
-                            newAccList.push(acc);
-                        }
-                    }
-                    prjAccList = newAccList;
-                }
+                const prjAccList = await this._getPrjAccList(ctx, pageShow);
                 // 分类列表
                 const categoryData = await this.ctx.service.category.getAllCategory(ctx.subProject);
                 // 获取用户权限
@@ -806,22 +780,9 @@ module.exports = app => {
                     archiveList = JSON.parse(archives[0].content);
                 }
 
-                // let prjAccList = await ctx.service.projectAccount.getAllAccountByProjectId(pid);
-                // prjAccList = prjAccList.filter(pa => pa.enable !== 0);
-                let prjAccList = await ctx.service.projectAccount.getAllSubProjectAccount(ctx.subProject, ['id', 'name', 'company', 'role', 'enable', 'is_admin', 'account_group', 'mobile', 'company_id', 'account', 'telephone', 'permission', 'sign_path', 'stamp_path']);
                 const roleList = [];
                 const usedList = await ctx.service.signatureUsed.getSignatureUsedByTenderId(tenderId);
-                // 根据需求,如果开启了‘开启个人「签字」’功能,则只能是本身用户签名,管理员例外
-                if (!ctx.session.sessionUser.is_admin && pageShow.individualSign === 1) {
-                    // 削减其他account
-                    const newAccList = [];
-                    for (const acc of prjAccList) {
-                        if (acc.id === this.ctx.session.sessionUser.accountId) {
-                            newAccList.push(acc);
-                        }
-                    }
-                    prjAccList = newAccList;
-                }
+                const prjAccList = await this._getPrjAccList(ctx, pageShow);
                 // 分类列表
                 const categoryData = await this.ctx.service.category.getAllCategory(ctx.subProject);
                 // 获取用户权限
@@ -952,7 +913,7 @@ module.exports = app => {
             await this._indexForBGL(ctx, sourceTypeConst.sourceType.change_apply, -303);
         }
 
-        async _indexForContract(ctx, source_type, stage_id) {
+        async _indexForContract(ctx, source_type, stage_id, preUrl) {
             // 合同管理报表入口(项目、标段级别)
             try {
                 await this._getStageAuditViewData(ctx);
@@ -960,24 +921,140 @@ module.exports = app => {
                 pageShow.closeWatermark = 1;
                 pageShow.showArchive = 1;
                 pageShow.closeShowAllCustomized = 0;
-                const tenderId = ctx.params.tid || -1; // 标段级别有
+                // const tenderId = ctx.params.tid || -1; // 标段级别有
+                const tenderId = ctx.contractOptions.tid || -1;
                 const bglObj = {};
+                // const tender = ctx.tender;
+                const stage = ctx.stage;
+                const stage_order = -1;
+                const sorder = -1;
+                const stage_times = -1;
+                const stage_status = -1;
+                const project_id = ctx.params.id;
+                const { treeNodes, custCfg, commonArrs } = await this._createNodes(ctx, sourceTypeConst.sourceType.contract_management, project_id);
                 if (stage_id === -400) {
-                    // 项目级别
-                } else {
-                    // 标段级别 -401
+                    // 统一为合同管理
+                }
+                const cust_select_keys = JSON.stringify(['common', 'customize']); // 因其他地方也有可能保存用户报表的显示选择项,因当初设计问题,不好改数据库结构了,但可以调节内部json来满足需求
+                const rpt_tpl_items = { customize: [], common: [] };
+                commonArrs.forEach(item => {
+                    rpt_tpl_items.common.push(item.name);
+                });
+                if (treeNodes && treeNodes.length > 0) {
+                    for (let tIdx = treeNodes.length - 1; tIdx >= 0; tIdx--) {
+                        if (treeNodes[tIdx].name !== '通用报表') {
+                            rpt_tpl_items.customize.push(treeNodes[tIdx].name);
+                        }
+                    }
                 }
+
+                // const custTreeFolders = await ctx.service.rptTreeNodeCust.getCustFoldersByUserId(this.ctx.session.sessionUser.accountId);
+                // if (custTreeFolders.length > 0) {
+                //     const cust_select_item = JSON.parse(custTreeFolders[0].rpt_tpl_items);
+                //     if (cust_select_item.common) rpt_tpl_items.common = cust_select_item.common;
+                //     if (cust_select_item.customize) rpt_tpl_items.customize = cust_select_item.customize;
+                // }
+                // 获取所有项目参与者
+                const { accountList, newAccountGroup, advance } = await this._getInvolveAcc(ctx, tenderId);
+                const unitList = await ctx.service.constructionUnit.getAllDataByCondition({ where: { pid: ctx.session.sessionProject.id } }); // 找公司章用的
+                const roleList = await ctx.service.signatureRole.getSignatureRolesByTenderId(tenderId);
+                const usedList = await ctx.service.signatureUsed.getSignatureUsedByTenderId(tenderId);
+                const isAdmin = ctx.session.sessionUser.is_admin;
+                const prjAccList = await this._getPrjAccList(ctx, pageShow);
+                for (const prjAcc of prjAccList) {
+                    prjAcc.account_group = prjAcc.company;
+                }
+                // 分类列表
+                const categoryData = await this.ctx.service.category.getAllCategory(ctx.subProject);
+                // // 获取用户权限
+                const accountInfo = await this.ctx.service.projectAccount.getDataById(this.ctx.session.sessionUser.accountId);
+                const tenderList = [];
+                const cid = this.ctx.helper._.map(treeNodes, 'id');
+                const customSelects = null;
+                const dataSelects = {};
+                dataSelects.material_select = await ctx.service.rptCustomDefine.getDataSelectByRpt(cid,
+                    reportConst.rptDataType[JV.NODE_CUS_MATERIAL_SELECT]);
+
+                // const materialList = await ctx.service.material.getValidMaterials(ctx.tenderId);
+                // const materialAdjAuditList = await ctx.service.materialAudit.getAuditorsByTender(tenderId);
+
+                const renderData = {
+                    accountGroup: newAccountGroup,
+                    accountList,
+                    unitList: JSON.stringify(unitList),
+                    tender: null,
+                    tenderInfo: null,
+                    rpt_tpl_data: JSON.stringify(treeNodes),
+                    cust_tpl_data: JSON.stringify(rpt_tpl_items),
+                    all_common_tpl_data: JSON.stringify([]),
+                    all_indivi_tpl_data: JSON.stringify([]),
+                    cust_select_keys,
+                    cust_cfg: JSON.stringify(custCfg),
+                    project_id,
+                    tender_id: tenderId,
+                    budget_id: -1,
+                    sp_id: -1,
+                    tender_name: '',
+                    detail_id: -1,
+                    stg_id: stage_id,
+                    stg_order: stage_order,
+                    cur_order: sorder,
+                    stg_times: stage_times,
+                    stg_status: stage_status,
+                    stage_list: '[]',
+                    prj_account_list: JSON.stringify(prjAccList),
+                    role_list: JSON.stringify(roleList),
+                    used_list: JSON.stringify(usedList),
+                    tenderMenu,
+                    preUrl,
+                    thisUrl: `/sp/${ctx.params.id}/contract/detail`, // 合同专有
+                    measureType,
+                    categoryData,
+                    tenderList,
+                    auditConst: auditConst.stage,
+                    ledgerAuditConst: auditConst.ledger,
+                    jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.report.main),
+                    customSelects,
+                    rptCustomType: reportConst.rptCustomType,
+                    advanceList: JSON.stringify([]),
+                    advanceAuditList: JSON.stringify([]),
+                    materialAdjList: JSON.stringify([]),
+                    materialAdjAuditList: JSON.stringify([]),
+                    materialList: [],
+                    stages: [],
+                    dataSelects,
+                    pageShow,
+                    authMobile: accountInfo.auth_mobile,
+                    shenpiConst,
+                    archiveList: [],
+                    lastAuditor: null,
+                    rpt_id: ctx.query.rpt_id,
+                    isAdmin,
+                    prePay: JSON.stringify(advance),
+                    OSS_PATH: ctx.app.config.fujianOssPath,
+                    viewPmData: PermissionCheck.viewPmData(this.ctx.session.sessionUser.permission),
+                    source_type,
+                    bglObj: JSON.stringify(bglObj),
+                    changes: 'null',
+                    otherChangeList: 'null',
+                    otherHintName: '',
+                    bizId: bglObj.BUSINESS_ID,
+                };
+                await this.layout('report/index.ejs', renderData, 'report/rpt_all_popup.ejs');
             } catch (err) {
                 this.log(err);
             }
         }
 
         async indexForProjectContract(ctx) {
-            await this._indexForContract(ctx, sourceTypeConst.sourceType.project_contract, -400);
+            const preUrl = `/sp/${ctx.params.prjid}/contract/panel`;
+            await this._indexForContract(ctx, sourceTypeConst.sourceType.contract_management, -400, preUrl);
         }
 
         async indexForTenderContract(ctx) {
-            await this._indexForContract(ctx, sourceTypeConst.sourceType.tender_contract, -401);
+            // const preUrl = `/sp/${ctx.params.prjid}/contract/panel`;
+            const preUrl = '';
+            await this._indexForContract(ctx, sourceTypeConst.sourceType.contract_management, -400, preUrl);
         }
 
         /**

+ 4 - 4
app/public/js/change_revise.js

@@ -726,7 +726,7 @@ $(document).ready(() => {
                             unit_price: gclInfo.unit_price,
                             oamount: xmjInfo.quantity,
                             oamount2: oldCInfo ? oldCInfo.oamount2 : xmjInfo.quantity,
-                            bwmx: xmjInfo.bwmx || xmjInfo.jldy,
+                            bwmx: xmjInfo.bwmx || xmjInfo.jldy || '',
                             xmj_code: xmjInfo.code || '',
                             xmj_jldy: xmjInfo.jldy || '',
                             xmj_dwgc: xmjInfo.dwgc || '',
@@ -867,7 +867,7 @@ $(document).ready(() => {
                             unit_price: gclInfo.unit_price,
                             oamount: xmjInfo.quantity,
                             oamount2: oldCInfo ? oldCInfo.oamount2 : xmjInfo.quantity,
-                            bwmx: xmjInfo.bwmx || xmjInfo.jldy,
+                            bwmx: xmjInfo.bwmx || xmjInfo.jldy || '',
                             xmj_code: xmjInfo.code || '',
                             xmj_jldy: xmjInfo.jldy || '',
                             xmj_dwgc: xmjInfo.dwgc || '',
@@ -1211,7 +1211,7 @@ $(document).ready(() => {
                                 unit_price: gclInfo.unit_price,
                                 oamount: xmjInfo.quantity,
                                 oamount2: oldCInfo ? oldCInfo.oamount2 : xmjInfo.quantity,
-                                bwmx: xmjInfo.bwmx || xmjInfo.jldy,
+                                bwmx: xmjInfo.bwmx || xmjInfo.jldy || '',
                                 xmj_code: xmjInfo.code || '',
                                 xmj_jldy: xmjInfo.jldy || '',
                                 xmj_dwgc: xmjInfo.dwgc || '',
@@ -1967,7 +1967,7 @@ $(document).ready(() => {
                         unit_price: gclInfo.unit_price,
                         oamount: xmjInfo.quantity,
                         oamount2: oldCInfo ? oldCInfo.oamount2 : xmjInfo.quantity,
-                        bwmx: xmjInfo.bwmx || xmjInfo.jldy,
+                        bwmx: xmjInfo.bwmx || xmjInfo.jldy || '',
                         xmj_code: xmjInfo.code || '',
                         xmj_jldy: xmjInfo.jldy || '',
                         xmj_dwgc: xmjInfo.dwgc || '',

+ 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);

+ 3 - 1
app/router.js

@@ -813,7 +813,9 @@ module.exports = app => {
     app.get('/tender/:id/change/project/:cprjid/report', sessionAuth, tenderCheck, subProjectCheck, uncheckTenderCheck, 'reportController.indexForChangeProject');
     app.get('/tender/:id/change/apply/:caid/report', sessionAuth, tenderCheck, subProjectCheck, uncheckTenderCheck, 'reportController.indexForChangeApply');
     app.get('/tender/:id/measure/stage/:order/report', sessionAuth, tenderCheck, subProjectCheck, uncheckTenderCheck, stageCheck, 'reportController.index');
-    // app.get('/sp/:prjid/contract/:contracid/report', sessionAuth, tenderCheck, subProjectCheck, uncheckTenderCheck, 'reportController.indexForProjectContract');
+    // app.get('/sp/:prjid/contract/report', sessionAuth, subProjectCheck, 'reportController.indexForProjectContract');
+    // app.get('/sp/:id/contract/panel', sessionAuth, subProjectCheck, contractCheck, 'contractController.panel');
+    app.get('/sp/:id/contract/report', sessionAuth, subProjectCheck, contractCheck, 'reportController.indexForProjectContract');
     // app.get('/sp?/contract/report', sessionAuth, tenderCheck, subProjectCheck, uncheckTenderCheck, 'reportController.indexForTenderContract');
     app.get('/sp/:id/payment/:pid/safe/:did/report', sessionAuth, subProjectCheck, paymentTenderCheck, paymentDetailCheck, 'reportController.indexForPaymentSafe');
     app.get('/budget/:id/report', sessionAuth, budgetCheck, 'reportController.indexForDynamicGrandTotal');

+ 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>

+ 1 - 1
app/view/contract/sub_menu_list.ejs

@@ -23,7 +23,7 @@
 <div class="nav-box">
     <ul class="nav-list list-unstyled">
         <li class="">
-            <a href="/sp/<%- `${ctx.subProject.id}` %>/contract/<%- `${ctx.contract.id}` %>/report"><span class="ml-3">输出报表</span></a>
+            <a href="/sp/<%- `${ctx.subProject.id}` %>/contract/report"><span class="ml-3">输出报表</span></a>
         </li>
     </ul>
 </div>

+ 11 - 5
app/view/report/index.ejs

@@ -6,6 +6,8 @@
     <% include ../budget/sub_menu.ejs %>
 <% } else if ([-300, -301, -302, -303].includes(stg_id)) { %>
     <% include ../tender/tender_sub_menu.ejs %>
+<% } else if ([-400, -401].includes(stg_id)) { %>
+    <% include ../contract/sub_menu.ejs %>
 <% } else { %>
     <% include ../stage/stage_sub_menu.ejs %>
 <% } %>
@@ -14,7 +16,7 @@
         <div class="title-main d-flex">
             <% if(stg_id === -1) { %>
                 <% include ../tender/tender_sub_mini_menu.ejs %>
-            <% } else if ([-100, -200, -300, -301, -302, -303].includes(stg_id)) { %>
+            <% } else if ([-100, -200, -300, -301, -302, -303, -400, -401].includes(stg_id)) { %>
             <% } else { %>
                 <% include ../stage/stage_sub_mini_menu.ejs %>
             <% } %>
@@ -46,7 +48,7 @@
                     </div>
                 </div>
                 <% } %>
-                <% if (false || ![-100, -200, -300, -301, -302, -303].includes(stg_id)) { %>
+                <% if (false || ![-100, -200, -300, -301, -302, -303, -400].includes(stg_id)) { %>
                 <div class="d-inline-block" id="divSelectableBizs_up">
                     <div class="dropdown" id="divSelectableStages">
                         <button class="btn btn-sm btn-light dropdown-toggle text-primary" type="button" id="btnCurrentStage" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></button>
@@ -62,7 +64,7 @@
                     </div>
                 </div>
                 <% } %>
-                <% if (stg_id === -200 || ![-100, -300, -301, -302, -303].includes(stg_id)) { %>
+                <% if (stg_id === -200 || ![-100, -300, -301, -302, -303, -400, -401].includes(stg_id)) { %>
                 <div class="d-inline-block" id="report_selects_div">
                     <ul class="nav nav-pills m-0" id="report_selects_ul">
                         <% if (ctx.session.sessionUser.is_admin || ctx.session.sessionProject.page_show.isCommonSetup) { %>
@@ -616,7 +618,11 @@
         CUST_CFG.continuousOutput = false;
     }
     //
-    const PROJECT_ID = <%- project_id %>;
+    <% if (stg_id === -400) {%>
+        const PROJECT_ID = '<%- project_id %>';
+    <% } else { %>
+        const PROJECT_ID = <%- project_id %>;
+    <% } %>
     const TENDER_ID = <%- tender_id %>;
     const TENDER_NAME = '<%- tender_name %>';
     const STAGE_ID = <%- stg_id %>;
@@ -671,7 +677,7 @@
         current_stage_status = STAGE_LIST[STAGE_LIST.length - 1].status;
     }
 
-    <% if (false || ![-100, -200, -300, -301, -302, -303].includes(stg_id)) { %>
+    <% if (false || ![-100, -200, -300, -301, -302, -303, -400, -401].includes(stg_id)) { %>
     buildStageSelection();
     <% } %>
     setupSignature();

+ 2 - 2
app/view/report/rpt_all_popup.ejs

@@ -793,7 +793,7 @@
         </div>
     </div>
 </div>
-<% if (![-100, -200, -300, -301, -302, -303].includes(stg_id)) { %>
+<% if (![-100, -200, -300, -301, -302, -303, -400, -401].includes(stg_id)) { %>
 <% include ../stage/audit_modal.ejs %>
 <% } %>
 
@@ -801,7 +801,7 @@
     zTreeOprObj.getCustomerCfg();
     zTreeOprObj.iniFontCfgDom(CUST_CFG);
     
-    <% if (![-100, -300, -301, -302, -303].includes(stg_id)) { %>
+    <% if (![-100, -300, -301, -302, -303, -400, -401].includes(stg_id)) { %>
     buildCustRptCommon('report_cust_group_common', ORG_TOP_TREE_NODES[1], CUST_TREE_NODES.common, 'true');
     buildCustRptCommon('report_cust_group_individual', ORG_TOP_TREE_NODES[0], CUST_TREE_NODES.customize, 'false');
     <% } %>