Browse Source

Merge branch 'master' of http://192.168.1.41:3000/SmartCost/ConstructionOperation

zhangweicheng 3 years ago
parent
commit
60856e8c11
30 changed files with 2167 additions and 57 deletions
  1. 19 0
      modules/all_models/bill_class.js
  2. 4 0
      modules/all_models/std_billsGuidance_items.js
  3. 21 0
      modules/all_models/std_billsGuidance_material.js
  4. 9 3
      modules/price_info_lib/facade/index.js
  5. 9 0
      modules/ration_repository/controllers/ration_controller.js
  6. 4 0
      modules/ration_repository/controllers/ration_repository_controller.js
  7. 2 2
      modules/ration_repository/models/ration_item.js
  8. 321 0
      modules/reports/controllers/adhoc_task_controller.js
  9. 22 1
      modules/reports/routes/report_router_operation.js
  10. 63 0
      modules/std_billsGuidance_lib/controllers/libController.js
  11. 258 0
      modules/std_billsGuidance_lib/facade/facades.js
  12. 4 0
      modules/std_billsGuidance_lib/routes/routes.js
  13. 5 0
      modules/users/models/manager_model.js
  14. 12 0
      public/id_tree.js
  15. 4 2
      public/web/common_ajax.js
  16. 40 3
      public/web/tree_sheet/tree_sheet_helper.js
  17. 42 0
      test/unit/exportData/exportGLJItem.js
  18. 168 0
      test/unit/exportData/exportRationCoe.js
  19. 62 0
      test/unit/exportData/exportRationGLJ.js
  20. 80 0
      test/unit/exportData/exportRationItems.js
  21. 212 0
      test/unit/exportData/exportRationSectionTree.js
  22. 22 0
      web/maintain/billsGuidance_lib/html/main.html
  23. 64 2
      web/maintain/billsGuidance_lib/html/zhiyin.html
  24. 500 42
      web/maintain/billsGuidance_lib/js/billsGuidance.js
  25. 38 1
      web/maintain/billsGuidance_lib/js/main.js
  26. 1 0
      web/maintain/price_info_lib/js/index.js
  27. 1 0
      web/maintain/ration_repository/js/coe.js
  28. 124 0
      web/maintain/report/html/adhoc_task.html
  29. 54 0
      web/maintain/report/js/adhoc_task.js
  30. 2 1
      web/maintain/report/js/rpt_tpl_pre_handle.js

+ 19 - 0
modules/all_models/bill_class.js

@@ -0,0 +1,19 @@
+// 清单分类
+const mongoose = require('mongoose');
+const Schema = mongoose.Schema;
+
+
+const billClass = new Schema({
+    compilationID: { type: String, index: true},
+    name: String,
+    code: String,
+    itemCharacter: String,
+    class: { type: Number, index: true }, // 分类
+    classCode: { type: String, index: true }, // 类别别名
+    requiredRationIDs: { type: [Number], default: [] }, // 必套定额ID
+    optionalRationIDs: { type: [Number], default: [] }, // 选套定额ID
+    errorRationIDs: { type: [Number], default: [] }, // 错套定额ID
+}, {versionKey: false});
+
+
+mongoose.model('billClass', billClass, 'billClass');

+ 4 - 0
modules/all_models/std_billsGuidance_items.js

@@ -24,6 +24,10 @@ const stdBillsGuidanceItems = new Schema({
     deleted: {type: Boolean, default: false},
     outputItemCharacter: {type: Boolean, default: false},
     required: {type: Boolean, default: false},
+    unit: String, // 单位,辅助运距功能
+    interval: String, // 区间,辅助运距功能
+    isMaterial: {type: Boolean, default: false}, // 材料,辅助替换材料规格
+    isDefaultOption: {type: Boolean, default: false}, // 是否是默认选项
 }, {versionKey: false});
 
 mongoose.model('std_billsGuidance_items', stdBillsGuidanceItems, 'std_billsGuidance_items');

+ 21 - 0
modules/all_models/std_billsGuidance_material.js

@@ -0,0 +1,21 @@
+//清单指引材料数据
+const mongoose = require('mongoose');
+const Schema = mongoose.Schema;
+
+const materialSchema = {
+  gljID: { type: Number, required: true }
+};
+
+const stdBillsGuidanceMaterial = new Schema({
+    libID: String,
+    ID: { type: String, index: true },
+    billID: String, //关联清单的ID
+    materials: {
+      type: [materialSchema],
+      default: [],
+    },
+}, {versionKey: false});
+
+stdBillsGuidanceMaterial.index({ libID: 1, billID: 1 });
+
+mongoose.model('std_billsGuidance_materials', stdBillsGuidanceMaterial, 'std_billsGuidance_materials');

+ 9 - 3
modules/price_info_lib/facade/index.js

@@ -270,21 +270,27 @@ async function importKeyData(libID, mainData, subData) {
     for (let row = 1; row < subData.length; row++) {
         const rowData = subData[row];
         const keywordItem = {
-            code: rowData[0] || '',
+            code: rowData[0] ? String(rowData[0]) : '',
             keyword: rowData[1] || '',
             unit: rowData[2] || '',
             coe: rowData[3] || '',
             group: rowData[4] || '',
             optionCode: rowData[5] || '',
         };
+        if (!keywordItem.code) {
+            continue;
+        }
         (keywordMap[keywordItem.code] || (keywordMap[keywordItem.code] = [])).push(keywordItem);
     }
 
     const priceItems = [];
     for (let row = 1; row < mainData.length; row++) {
         const rowData = mainData[row];
-        const code = rowData[0] || '';
-        const matchCode = code.substr(0, 4);
+        const code = rowData[0] ? String(rowData[0]) : '';
+        if (!code) {
+            continue;
+        }
+        const matchCode = code.substring(0, 4);
         const classID = classMap[matchCode] || otherClassID;
         const priceItem = {
             code,

+ 9 - 0
modules/ration_repository/controllers/ration_controller.js

@@ -3,6 +3,7 @@
  */
 
 var rationItem = require('../models/ration_item');
+const fs = require("fs");
 import BaseController from "../../common/base/base_controller";
 var callback = function(req, res, err, message, data){
     res.json({error: err, message: message, data: data});
@@ -13,6 +14,14 @@ class RationController extends BaseController{
         try{
             let data = JSON.parse(req.body.data);
             let rationItems = await rationItem.getRationItemsByLib(data.rationLibId, data.showHint, data.returnFields);
+
+            /*
+            let testRationItems = await rationItem.getRationItemsByLib(211);
+            // console.log('testRationItems:');
+            fs.writeFile(`D:/GitHome/ConstructionOperation/tmp/testRationData_${(new Date()).getTime()}.js`, JSON.stringify(testRationItems), { 'flag': 'a' }, function(err){
+                if(err) throw err;
+            });
+            //*/
             callback(req, res, 0, '', rationItems);
         }
         catch(err){

+ 4 - 0
modules/ration_repository/controllers/ration_repository_controller.js

@@ -29,6 +29,10 @@ class RationRepositoryController extends baseController {
         try {
             const data = JSON.parse(req.body.data);
             const rst = await rationItem.prepareInitData(data.rationRepId);
+            // console.log(rst);
+            // fs.writeFile(`D:/GitHome/ConstructionOperation/tmp/testDataResult_${(new Date()).getTime()}.js`, JSON.stringify(rst.sectionTree), { 'flag': 'a' }, function(err){
+            //     if(err) throw err;
+            // });
             res.json({error: 0, message: 'success', data: rst});
         } catch (err) {
             console.log(err);

+ 2 - 2
modules/ration_repository/models/ration_item.js

@@ -1111,9 +1111,9 @@ rationItemDAO.prototype.batchAddFromExcel = async function(rationRepId, data) {
             continue;
         }
         // 如果第一个字段为null则是工料机数据,放入上一个数据的工料机字段
-        if (tmp[0] === undefined && Object.keys(lastData).length > 0) {
+        if (!tmp[0] && Object.keys(lastData).length > 0) {
             // 如果不存在对应的工料机库数据则跳过
-            if (stdGLJList[tmp[1]] === undefined) {
+            if (!stdGLJList[tmp[1]]) {
                 if (lastFailCount === 0) {
                     failGLJList.push('定额' + lastData.code + '下的');
                     lastFailCount++;

+ 321 - 0
modules/reports/controllers/adhoc_task_controller.js

@@ -0,0 +1,321 @@
+'use strict';
+
+/**
+ * Created by Tony on 2021/11/3.
+ */
+// const odbc = require('odbc');
+// const odbcdb = new odbc.Database();
+
+const ADODB = require('node-adodb');
+const connection = ADODB.open('Provider=Microsoft.Jet.OLEDB.4.0;Data Source=D:/GitHome/公路养护支持文件/空白.mdb;');
+const rationItemDAO = require("../../ration_repository/models/ration_item");
+
+module.exports = {
+    createDesktopMdb: async function(req, res) {
+        // console.log('request createDesktopMdb!');
+        // console.log(req);
+        let params = JSON.parse(req.body.params);
+        let compilationId = params.compilationId,
+            rationLibId = params.rationLibId,
+            hasRationTree = params.hasRationTree,
+            hasRation = params.hasRation,
+            hasAssistRation = params.hasAssistRation,
+            hasGLJ = params.hasGLJ,
+            hasCOE = params.hasCOE
+        ;
+        // console.log(params);
+        // let rst = {};
+        if (hasRationTree || hasRation || hasGLJ || hasCOE) {
+            //const rst = await rationItem.prepareInitData(data.rationRepId);
+            let mixData = await rationItemDAO.prepareInitData(rationLibId);
+            let gljCache = {};
+            const prefix = '_'
+
+            if (hasRationTree) {
+                //mixData.sectionTree //定额树
+                // await _addTreeData(mixData.sectionTree);
+            }
+            if (hasRation || hasGLJ) {
+                for (let gljItem of mixData.gljList) {
+                    gljCache[prefix + gljItem.ID] = gljItem;
+                }
+                //mixData.gljList //总工料机
+                // await _addGLJData(mixData.gljList);
+            }
+            if (hasRation || hasCOE) {
+                let rationItems = await rationItemDAO.getRationItemsByLib(rationLibId); //定额
+                await _addRationData(rationItems);
+                // await _addRationGljData(rationItems, gljCache, prefix);
+            }
+        }
+        if (hasAssistRation) {
+            //
+        }
+        res.json({error: 0, message: 'success', data: ''});
+    }
+};
+
+const NODE_ID = "ID", P_ID = "ParentID", NEXT_ID = "NextSiblingID", ADHOC_PRE_ID="Previous_ID", CHILDREN_NODE = "items", SUB_ID = "sub_ids",
+    EMPTY_ID_VAL = -1, TREE_LEVEL = 'treeLevel', TOP_BILL_ID = "topBillID";
+
+let tree_Data_Helper = {
+    buildTreeNodeDirectly: function(data, addLevel) {
+        let topArr = [], rst = [], tmpNodes = {}, prefix = "id_";
+        let private_getStartNode = function (idArr) {
+            let tmpNodeRst = null;
+            for (let i = 0; i < idArr.length; i++) {
+                if (parseInt(tmpNodes[prefix + idArr[i]][ADHOC_PRE_ID]) === EMPTY_ID_VAL) {
+                    tmpNodeRst = tmpNodes[prefix + idArr[i]];
+                    break;
+                }
+            }
+            return tmpNodeRst;
+        };
+        let private_buildNodeData = function(parentItem, idArr, treeLevel, tbID) {
+            let iter = [], nextNode = private_getStartNode(idArr), pushedIds = [];
+            while (nextNode !== null && nextNode !== undefined ) {
+                if (parentItem) {
+                    parentItem[CHILDREN_NODE].push(nextNode);
+                } else {
+                    rst.push(nextNode);
+                }
+                iter.push(nextNode);
+                pushedIds.push(nextNode[NODE_ID]);
+                nextNode[TOP_BILL_ID] = tbID;
+                if (parentItem === null) {
+                    nextNode[TOP_BILL_ID] = nextNode[NODE_ID];
+                    if (nextNode.flags && nextNode.flags.length > 0) {
+                        for (let flag of nextNode.flags) {
+                            if (flag.fieldName === "fixed") {
+                                nextNode[TOP_BILL_ID] = flag.flag;
+                                break;
+                            }
+                        }
+                    }
+                }
+                if (addLevel) nextNode[TREE_LEVEL] = treeLevel;
+                nextNode = tmpNodes[prefix + nextNode[NEXT_ID]];
+                if (nextNode === null || nextNode === undefined) {
+                    //备注: 考虑到实际数据的健壮性,有些节点会掉链子,需要用 parentItem[SUB_ID] 比对已经加上的节点,如发现加上的节点数量不够,那就得在这里补充上去
+                    if (parentItem) {
+                        if (parentItem[SUB_ID].length > iter.length) {
+                            for (let subId of parentItem[SUB_ID]) {
+                                if (pushedIds.indexOf(subId) < 0) {
+                                    let restNode = tmpNodes[prefix + subId];
+                                    if (addLevel) restNode[TREE_LEVEL] = treeLevel;
+                                    restNode[TOP_BILL_ID] = tbID;
+                                    parentItem[CHILDREN_NODE].push(restNode);
+                                    iter.push(restNode);
+                                }
+                            }
+                        }
+                    } else {
+                        if (idArr.length > iter.length) {
+                            for (let topId of idArr) {
+                                if (pushedIds.indexOf(topId) < 0) {
+                                    let restNode = tmpNodes[prefix + topId];
+                                    if (addLevel) restNode[TREE_LEVEL] = treeLevel;
+                                    restNode[TOP_BILL_ID] = restNode[NODE_ID];
+                                    if (restNode.flags && restNode.flags.length > 0) {
+                                        for (let flag of restNode.flags) {
+                                            if (flag.fieldName === "fixed") {
+                                                restNode[TOP_BILL_ID] = flag.flag;
+                                                break;
+                                            }
+                                        }
+                                    }
+                                    rst.push(restNode);
+                                    iter.push(restNode);
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+            pushedIds = [];
+            for (let i = 0; i < iter.length; i++) {
+                let rtbID = tbID;
+                if (parentItem === null) {
+                    rtbID = iter[i][TOP_BILL_ID];
+                }
+                private_buildNodeData(iter[i], iter[i][SUB_ID], (treeLevel + 1), rtbID);
+            }
+        };
+        //1. 给每个节点设置key, 顺便找Top Node
+        for (let i = 0; i < data.length; i++) {
+            tmpNodes[prefix + data[i][NODE_ID]] = data[i];
+            data[i][ADHOC_PRE_ID] = EMPTY_ID_VAL;
+            data[i][SUB_ID] = [];
+            data[i][CHILDREN_NODE] = [];
+            if (parseInt(data[i][P_ID]) === EMPTY_ID_VAL) {
+                topArr.push(data[i][NODE_ID]);
+            }
+        }
+        //2. 通过key,设置好兄弟/父子关系
+        for (let i = 0; i < data.length; i++) {
+            if (parseInt(data[i][NEXT_ID]) !== EMPTY_ID_VAL) {
+                if (tmpNodes[prefix + data[i][NEXT_ID]] !== undefined){
+                    if (tmpNodes[prefix + data[i][NEXT_ID]][P_ID] === data[i][P_ID]) {
+                        tmpNodes[prefix + data[i][NEXT_ID]][ADHOC_PRE_ID] = data[i][NODE_ID];
+                    } else {
+                        tmpNodes[prefix + data[i][NEXT_ID]][ADHOC_PRE_ID] = EMPTY_ID_VAL;
+                        data[i][NEXT_ID] = EMPTY_ID_VAL;
+                    }
+                }
+            }
+            if (parseInt(data[i][P_ID]) !== EMPTY_ID_VAL) {
+                tmpNodes[prefix + data[i][P_ID]][SUB_ID].push(data[i][NODE_ID]);
+            }
+        }
+        //3. 开build
+        private_buildNodeData(null, topArr, 0, -1);
+        //try to release and return
+        tmpNodes = null;
+        topArr.length = 0;
+        return rst;
+    },
+
+    getFlatArray: function (srcArr, destArr) {
+        let private_put = function (parentItem) {
+            destArr.push(parentItem);
+            if (parentItem.items) {
+                for (let subItem of parentItem.items) {
+                    private_put(subItem);
+                }
+            }
+        }
+        for (let node of srcArr) {
+            private_put(node);
+        }
+        for (let item of destArr) {
+            delete item[CHILDREN_NODE];
+            delete item[SUB_ID];
+            delete item[ADHOC_PRE_ID];
+        }
+    }
+};
+
+async function _addRationGljData(rationItemArr, gljCache, prefix) {
+    console.log('start Ration GLJ!');
+    // console.log(`Ration GLJ total amount: ${rationGljArr.length}`);
+    for (let idx = 0; idx < rationItemArr.length; idx++) {
+        let rationItem = rationItemArr[idx];
+        for (let rgljItem of rationItem.rationGljList) {
+            if (parseFloat(rgljItem.consumeAmt) > 0) {
+                let fieldStr = 'RationID, GLJID, Amount, Proportion, Type';
+                let valueStr = `${rationItem.ID}, ${rgljItem.gljId}, ${rgljItem.consumeAmt}, ${(rgljItem.proportion !== undefined)?rgljItem.proportion:0}, ${gljCache[prefix + rgljItem.gljId].gljType}`;
+                let sqlStr = `INSERT INTO Ration_GLJ(${fieldStr}) VALUES (${valueStr})`;
+                try {
+                    // console.log(`executing SQL: ${sqlStr}`);
+                    await connection.execute(sqlStr);
+                } catch(ex) {
+                    console.log(`execute SQL with error: ${sqlStr}`);
+                }
+            }
+        }
+    }
+    console.log('end Ration GLJ!');
+}
+
+async function _addRationData(rationItemArr) {
+    console.log('start Ration!');
+    console.log(`Ration total amount: ${rationItemArr.length}`);
+    for (let idx = 0; idx < rationItemArr.length; idx++) {
+        let item = rationItemArr[idx];
+        let fieldStr = 'ID, IDCode, Code, Name, Unit, BasePrice, SectionID, Caption, FeeType, Name2, Unit2, UUID';
+        let valueStr = `${item.ID}, ${_getIDCode(item.code)}, '${item.code}', '${item.name}', '${item.unit}', ${item.basePrice}, ${item.sectionId}, '${item.caption}', ${item.feeType}, '', '', '' `;
+        let sqlStr = `INSERT INTO RationItems(${fieldStr}) VALUES (${valueStr})`;
+        try {
+            // console.log(`executing SQL: ${sqlStr}`);
+            await connection.execute(sqlStr);
+        } catch(ex) {
+            console.log(`execute SQL with error: ${sqlStr}`);
+        }
+    }
+    console.log('end Ration!');
+}
+
+async function _addGLJData(gljItemArr) {
+    console.log('start GLJ!');
+    console.log(`GLJ total amount: ${gljItemArr.length}`);
+    for (let idx = 0; idx < gljItemArr.length; idx++) {
+        let item = gljItemArr[idx];
+        let fieldStr = 'ID, Code, Name, Specs, Unit, BasePrice, Main, New, Type, DetailType, ShortName, Name2, Unit2, SortParam, CalculateType, UUID, 字段1, 字段2';
+        let valueStr = `${item.ID}, '${item.code}', '${item.name}', '${item.specs}', '${item.unit}', ${(item.basePrice !== undefined)?item.basePrice:0}, 0, 0, ${item.gljType}, ${item.gljClass}, '${item.shortName}', '', '', '', '', '', '', ''`;
+        let sqlStr = `INSERT INTO GLJList(${fieldStr}) VALUES (${valueStr})`;
+        try {
+            // console.log(`executing SQL: ${sqlStr}`);
+            await connection.execute(sqlStr);
+        } catch(ex) {
+            console.log(`execute SQL with error: ${sqlStr}`);
+        }
+    }
+    console.log('end GLJ!');
+}
+
+async function _addTreeData(treeDataArr) {
+    console.log('start tree!');
+    console.log(`GLJ total amount: ${treeDataArr.length}`);
+    let tmpTreeArr = tree_Data_Helper.buildTreeNodeDirectly(treeDataArr, true);
+    for (let idx = 0; idx < tmpTreeArr[0].items.length; idx++) {
+        _setChapterLevel(tmpTreeArr[0].items[idx], idx + 1);
+    }
+    let sectionTreeArr = [];
+    tree_Data_Helper.getFlatArray(tmpTreeArr, sectionTreeArr);
+    for (let sectionNode of sectionTreeArr) {
+        let fieldStr = 'ID, ParentID, NextSiblingID, Name, Code, FullCode, Name2';
+        let valueStr = `${sectionNode.ID},${sectionNode.ParentID},${sectionNode.NextSiblingID},'${sectionNode.name}',${sectionNode.chapterLv === undefined?-1:sectionNode.chapterLv},'',''`;
+        let sqlStr = `INSERT INTO RationTree(${fieldStr}) VALUES (${valueStr})`;
+        try {
+            // console.log(`executing SQL: ${sqlStr}`);
+            await connection.execute(sqlStr);
+        } catch(ex) {
+            console.log(`execute SQL with error: ${sqlStr}`);
+        }
+    }
+    console.log('end tree!');
+}
+
+function _setChapterLevel(chapterNode, cLv) {
+    chapterNode.chapterLv = cLv;
+    if (chapterNode.items && chapterNode.items.length > 0) {
+        for (let subNode of chapterNode.items) {
+            _setChapterLevel(subNode, cLv);
+        }
+    }
+}
+
+function _getIDCode(code) {
+    let rst = '';
+    if (code !== null && code !== undefined) {
+        let codes = code.split('-');
+        if (codes.length === 3) {
+            let suppls = ['', '', ''];
+
+            let regExp = new RegExp('D', "gm");
+            codes[0] = codes[0].replace(regExp, '');
+
+            if (codes[0].length < 2) {
+                for (let idx = codes[0].length; idx < 2; idx++) {
+                    suppls[0] = suppls[0] + '0';
+                }
+            }
+            if (codes[1].length < 2) {
+                for (let idx = codes[1].length; idx < 2; idx++) {
+                    suppls[1] = suppls[1] + '0';
+                }
+            }
+            if (codes[2].length < 3) {
+                for (let idx = codes[2].length; idx < 3; idx++) {
+                    suppls[2] = suppls[2] + '0';
+                }
+            }
+            for (let si = 0; si < codes.length; si++) {
+                rst += (suppls[si] + codes[si]);
+            }
+        } else {
+            //
+        }
+    }
+    return rst;
+}

+ 22 - 1
modules/reports/routes/report_router_operation.js

@@ -4,8 +4,11 @@
 
 import express from "express";
 let rptRouter = express.Router();
-import reportController from "../controllers/rpt_controller_operation";
+// import reportController from "../controllers/rpt_controller_operation";
+let adHocController = require("../controllers/adhoc_task_controller");
 
+// 完成历史任务,屏蔽
+/*
 module.exports =function (app) {
     app.get('/report',  function(req, res) {
         if (!req.session.managerData.username) {
@@ -32,3 +35,21 @@ module.exports =function (app) {
 
     app.use("/report_api", rptRouter);
 };
+//*/
+
+module.exports = function (app) {
+    app.get('/outputDataForDesktopVersion',  function(req, res) {
+        if (!req.session.managerData.username) {
+            res.redirect('/login');
+        }
+        else {
+            res.render('maintain/report/html/adhoc_task.html',
+                {userAccount: req.session.userAccount,
+                    userID: req.session.managerData.userID});
+        }
+    });
+
+    rptRouter.post('/createDesktopMdb', adHocController.createDesktopMdb);
+
+    app.use("/adHoc_report_api", rptRouter);
+};

+ 63 - 0
modules/std_billsGuidance_lib/controllers/libController.js

@@ -11,6 +11,9 @@ import BaseController from '../../common/base/base_controller';
 import moment from 'moment';
 const billsGuidanceFacade = require('../facade/facades');
 let logger = require('../../../logs/log_helper').logger;
+const fs = require("fs");
+// excel解析
+const excel = require("node-xlsx");
 let callback = function (req, res, err, msg, data) {
     res.json({error: err, message: msg, data: data});
 };
@@ -103,6 +106,66 @@ class BillsGuideLibController extends BaseController{
         }
     }
 
+    async getBillMaterials(req, res){
+        try{
+            const data = JSON.parse(req.body.data);
+            const materials = await billsGuidanceFacade.getBillMaterials(data.libID, data.billID);
+            callback(req, res, 0, '', materials);
+        }
+        catch(err){
+            callback(req, res, 1, err, null);
+        }
+    }
+
+    async editBillMaterials(req, res){
+        try{
+            const data = JSON.parse(req.body.data);
+            const materials = await billsGuidanceFacade.editBillMaterials(data.libID, data.billID, data.gljCodes, data.compilationID);
+            callback(req, res, 0, '', materials);
+        }
+        catch(err){
+            console.log(err);
+            callback(req, res, 1, err.message, []);
+        }
+    }
+    
+    async generateClassData(req, res) {
+        try{
+            res.setTimeout(1000 * 60 * 10); // 不设置的话,处理时间过长,会触发默认的响应超时,报错(前端报错,后台还继续在处理)
+            const data = JSON.parse(req.body.data);
+            await billsGuidanceFacade.generateClassData(data.libID);
+            callback(req, res, 0, '', []);
+        }
+        catch(err){
+            console.log(err);
+            callback(req, res, 1, err.message, []);
+        }
+    }
+
+    async exportClassExcel(req, res) {
+        try{
+            const excelData = await billsGuidanceFacade.getClassExcelData(req.query.libID);
+            console.log('start-build');
+            const buffer = excel.build([{name: "清单分类库", data: excelData}], {'!cols': [{wch:6}, {wch:12}, {wch:14}, {wch:24}, {wch:45}, {wch:20}, {wch:30}, {wch:30}]});
+            console.log('end-build');
+            const filePath = './public/export.xlsx';
+            fs.writeFileSync(filePath, buffer, 'binary');
+            const stats = fs.statSync(filePath);
+            // 下载相关header
+            res.set({
+                'Content-Type': 'application/octet-stream',
+                'Content-Disposition': 'attachment; filename=billClass.xlsx',
+                'Content-Length': stats.size
+            });
+            fs.createReadStream(filePath).pipe(res);
+            fs.unlink(filePath);
+        }
+        catch(err){
+            console.log(err);
+            response.end(error);
+        }
+    }
+
     async testItems(req, res){
         try{
             let data = JSON.parse(req.body.data);

+ 258 - 0
modules/std_billsGuidance_lib/facade/facades.js

@@ -20,6 +20,11 @@ const stdBillsJobsModel = mongoose.model('std_bills_lib_jobContent');
 const stdRationModel = mongoose.model('std_ration_lib_ration_items');
 const engLibModel = mongoose.model('engineering_lib');
 const compilationModel = mongoose.model('compilation');
+const billMaterialModel = mongoose.model('std_billsGuidance_materials');
+const gljModel = mongoose.model('std_glj_lib_gljList');
+const gljLibModel = mongoose.model('std_glj_lib_map');
+const billClassModel = mongoose.model('billClass');
+const idTree = require('../../../public/id_tree').getTree();
 const _ = require('lodash');
 const zhLibID = 'cf851660-3534-11ec-9641-2da8021b8e4e';
 const cqLibID = '90c51220-a740-11e8-a354-ab5db7d42428';
@@ -32,6 +37,10 @@ module.exports = {
     getLibWithBills,
     getItemsBybills,
     updateItems,
+    getBillMaterials,
+    editBillMaterials,
+    generateClassData,
+    getClassExcelData,
     testItems
 };
 
@@ -404,6 +413,255 @@ async function updateItems(updateDatas) {
     }
 }
 
+// 获取清单材料数据
+async function getBillMaterials(libID, billID) {
+    // 指引下已有定额人材机(材料大类)
+    let allGljList = [];
+    const rationItems = await billsGuideItemsModel.find({ libID, billsID: billID, rationID: { $ne: null } }, '-_id rationID').lean();
+    if (rationItems.length) {
+        const rationIDs = rationItems.map(item => item.rationID);
+        const rations = await stdRationModel.find({ ID: { $in: rationIDs } }, '-_id rationGljList');
+        const gljIDs = [];
+        rations.forEach(ration => {
+            if (ration.rationGljList && ration.rationGljList.length) {
+                gljIDs.push(...ration.rationGljList.map(rGlj => rGlj.gljId));
+            }
+        });
+        if (gljIDs.length) {
+            allGljList = await gljModel.find({ ID: { $in: [...new Set(gljIDs)] } }, '-_id ID code name specs gljType').lean();
+            allGljList = allGljList.filter(glj => /^2/.test(glj.gljType) || [4, 5].includes(glj.gljType));
+        }
+    }
+    // 清单材料
+    let billMaterials = [];
+    const billMaterial = await billMaterialModel.findOne({ libID, billID }).lean();
+    if (!billMaterial || !billMaterial.materials) {
+        return {
+            billMaterials, 
+            allGljList
+        };
+    }
+    const gljIDs = billMaterial.materials.map(m => m.gljID);
+    const gljList = await gljModel.find({ ID: { $in: gljIDs } }, '-_id ID code name specs').lean();
+    billMaterials = gljList.map(glj => ({ gljID: glj.ID, code: glj.code, name: glj.name, specs: glj.specs }));
+    return {
+        billMaterials, 
+        allGljList
+    }
+}
+
+// 编辑清单材料数据,返回清单材料数据
+async function editBillMaterials(libID, billID, gljCodes, compilationID) {
+    const gljLib = await gljLibModel.findOne({ compilationId: compilationID }, '-_id ID').lean();
+    const gljList = await gljModel.find({ repositoryId: gljLib.ID, code: { $in: gljCodes }, }, '-_id ID code name specs').lean();
+    const gljMap = {};
+    const materials = [];
+    gljList.forEach(glj => {
+        materials.push({ gljID: glj.ID });
+        gljMap[glj.code] = 1;
+    });
+    const missCodes = gljCodes.filter(code => !gljMap[code]);
+    if (missCodes.length) {
+        throw new Error(`没有找到人材机:<br/>${missCodes.join('<br/>')}`);
+    }
+    const billMaterial = await billMaterialModel.findOne({ libID, billID }, '-_id ID').lean();
+    if (billMaterial) {
+        await billMaterialModel.update({ ID: billMaterial.ID }, { $set: { materials } });
+    } else {
+        await billMaterialModel.create({ libID, billID, ID: uuidV1(), materials });
+    }
+    return gljList.map(glj => ({ gljID: glj.ID, code: glj.code, name: glj.name, specs: glj.specs }));
+}
+
+ // 是否为工序行
+ function isProcessNode(node) {
+    return node && node.depth() % 2 === 0 && node.data.type === 0
+}
+
+// 是否是选项行
+function isOptionNode(node) {
+    return node && node.depth() % 2 === 1 && node.data.type === 0
+}
+
+// 从指引节点,获取分类特征、必套定额数据
+function getItemClassData(nodes, prefix) {
+    const processNodes = nodes.filter(node => isProcessNode(node));
+    const classGroups = []; // 同层必填选项的数组(二维数组)
+    processNodes.forEach(processNode => {
+        const classItems = [];
+        const optionNodes = processNode.children.filter(node => isOptionNode(node));
+        optionNodes.forEach(optionNode => {
+            if (optionNode.parent && optionNode.parent.data.required && (!optionNode.children 
+                || !optionNode.children.length 
+                || (optionNode.children[0].data && optionNode.children[0].data.rationID) 
+                || !optionNode.children.some(node => isProcessNode(node)))) {
+                // 必套定额
+                const requiredRationIDs = optionNode.children && optionNode.children.length ?
+                    optionNode.children.filter(node => !!node.data.rationID).map(node => node.data.rationID) : [];
+                classItems.push({ name: optionNode.data.name, requiredRationIDs });
+            } else {
+                classItems.push(...getItemClassData(optionNode.children, optionNode.parent && optionNode.parent.data.required ? optionNode.data.name : ''));
+            }
+        });
+        if (classItems.length) {
+            classGroups.push(classItems);
+        }
+    });
+    // 拼接上一文本
+    if (classGroups[0] && classGroups[0].length) {
+        classGroups[0] = classGroups[0].map(item => {
+            item.name = prefix ? `${prefix}@${item.name}` : item.name
+            return item;
+        });
+    }
+    // 二维数组内容排列组合
+    while (classGroups.length > 1) {
+        const prevClassItems = classGroups[0];
+        const nextClassItems = classGroups[1];
+        const mergedClassItems = [];
+        for (let i = 0; i < prevClassItems.length; i++) {
+            for (let j = 0; j < nextClassItems.length; j++) {
+                // 拼接文本
+                const mergedName = `${prevClassItems[i].name}@${nextClassItems[j].name}`;
+                // 拼接必套定额
+                const mergedRationIDs = [...prevClassItems[i].requiredRationIDs, ...nextClassItems[j].requiredRationIDs];
+                mergedClassItems.push({name: mergedName, requiredRationIDs: mergedRationIDs});
+            }
+        }
+        classGroups.splice(0, 2, mergedClassItems);
+    }
+    // 去重(类别别名要唯一)
+    const items = classGroups[0] || [];
+    const nameMap = {};
+    const rst = [];
+    items.forEach(item => {
+        if (!nameMap[item.name]) {
+            rst.push(item);
+        }
+        nameMap[item.name] = true;
+    });
+    return rst;
+}
+
+// 获取选套定额:把所有分类数据的必套定额确定好了先。选套定额就是清单下所有定额除了必套的
+function getOptionalRationIDs(itemClassData, allRationIDs) {
+    // 所有必套定额
+    let requiredRationIDs = [];
+    itemClassData.forEach(item => {
+        if (item.requiredRationIDs) {
+            requiredRationIDs.push(...item.requiredRationIDs);
+        }
+    });
+    requiredRationIDs = [...new Set(requiredRationIDs)];
+    // 选套定额就是清单下所有定额除了必套的
+    const optionalRationIDs = [];
+    allRationIDs.forEach(rationID => {
+        if (!requiredRationIDs.includes(rationID)) {
+            optionalRationIDs.push(rationID);
+        }
+    });
+    return [...new Set(optionalRationIDs)];
+}
+
+// 获取错套定额:清单下所有定额,除了分类对应的必套、选套定额
+function getErrorRationIDs(requiredRationIDs, optionalRationIDs, allRationIDs) {
+    const errorRationIDs = [];
+    allRationIDs.forEach(rationID => {
+        if (!requiredRationIDs.includes(rationID) && !optionalRationIDs.includes(rationID)) {
+            errorRationIDs.push(rationID);
+        }
+    });
+    return [...new Set(errorRationIDs)];
+}
+
+// 生成清单分类
+async function generateClassData(libID) {
+    const lib = await billsGuideLibModel.findOne({ ID: libID }).lean();
+    if (!lib) {
+        throw new Error('无有效精灵库');
+    }
+    const guidanceItems = await billsGuideItemsModel.find({ libID }, '-_id').lean();
+    // 清单ID - 指引数据映射
+    const guidanceMap = {};
+    guidanceItems.forEach(item => {
+        (guidanceMap[item.billsID] || (guidanceMap[item.billsID] = [])).push(item);
+    });
+    const bills = await stdBillsModel.find({ billsLibId: lib.billsLibId }, '-_id ID ParentID NextSiblingID name code').lean();
+    const billTree = idTree.createNew({id: 'ID', pid: 'ParentID', nid: 'NextSiblingID', rootId: -1, autoUpdate: true});
+    billTree.loadDatas(bills);
+    // 叶子清单
+    const leaves = billTree.items.filter(node => !node.children || !node.children.length);
+    // 获取分类数据
+    let classNum = 1;
+    const billClassData = [];
+    leaves.forEach(billNode => {
+        const guidanceItems = guidanceMap[billNode.data.ID];
+        if (!guidanceItems || !guidanceItems.length) {
+            return;
+        }
+        const guidanceTree = idTree.createNew({id: 'ID', pid: 'ParentID', nid: 'NextSiblingID', rootId: -1, autoUpdate: true});
+        guidanceTree.loadDatas(guidanceItems);
+        const itemClassData = getItemClassData(guidanceTree.roots); // 必套定额在这个方法内就获取了,避免重复执行递归方法
+        const allRationIDs = guidanceTree.items.filter(node => !!node.data.rationID).map(node => node.data.rationID);
+        // 选套定额ID
+        const optionalRationIDs = getOptionalRationIDs(itemClassData, allRationIDs);
+        itemClassData.forEach(item => {
+            // 错套定额
+            const errorRationIDs = getErrorRationIDs(item.requiredRationIDs, optionalRationIDs, allRationIDs);
+            billClassData.push({
+                itemCharacter: item.name,
+                class: classNum++,
+                classCode: `${billNode.data.code}@${item.name}`,
+                compilationID: lib.compilationId,
+                name: billNode.data.name,
+                code: billNode.data.code,
+                requiredRationIDs: item.requiredRationIDs || [],
+                optionalRationIDs,
+                errorRationIDs,
+            });
+        });
+    });
+    console.log(`billClassData.length`);
+    console.log(billClassData.length);
+    // 清空旧的分类数据
+    await billClassModel.deleteMany({ compilationID: lib.compilationId });
+    // 之前遇到一次性插入40w条数据的情况,会卡死,循环插入速度快很多
+    while (billClassData.length > 10000){
+        const list = billClassData.splice(0,10000);
+        await billClassModel.insertMany(list);
+    } 
+    await billClassModel.insertMany(billClassData);
+
+}
+
+// 获取分类excel数据
+async function getClassExcelData(libID) {
+    const lib = await billsGuideLibModel.findOne({ ID: libID }, '-_id compilationId').lean();
+    if (!lib) {
+        throw new Error('无有效精灵库');
+    }
+    console.log('start');
+    const date = Date.now();
+    const classData = await billClassModel.find({ compilationID: lib.compilationId }).lean();
+    console.log('end');
+    console.log(Date.now() -date);
+    const excelData = [['类别', '编码', '清单名称', '必填特征排列组合', '类别别名', '必套定额', '选套定额', '错套定额']];
+    classData.forEach(item => {
+        const excelItem = [
+            item.class,
+            item.code,
+            item.name,
+            item.itemCharacter,
+            item.classCode,
+            (item.requiredRationIDs || []).join('@'),
+            (item.optionalRationIDs || []).join('@'),
+            (item.errorRationIDs || []).join('@'),
+        ];
+        excelData.push(excelItem);
+    });
+    return excelData;
+}
+
 async function testItems(libID) {
     let items = await billsGuideItemsModel.find({libID: libID});
     //删除垃圾数据

+ 4 - 0
modules/std_billsGuidance_lib/routes/routes.js

@@ -23,6 +23,10 @@ module.exports = function (app) {
     router.post('/getLibWithBills', billsGuideLibController.auth, billsGuideLibController.init, billsGuideLibController.getLibWithBills);
     router.post('/getItemsByBills', billsGuideLibController.auth, billsGuideLibController.init, billsGuideLibController.getItemsByBills);
     router.post('/updateItems', billsGuideLibController.auth, billsGuideLibController.init, billsGuideLibController.updateItems);
+    router.post('/getBillMaterials', billsGuideLibController.auth, billsGuideLibController.init, billsGuideLibController.getBillMaterials);
+    router.post('/editBillMaterials', billsGuideLibController.auth, billsGuideLibController.init, billsGuideLibController.editBillMaterials);
+    router.post('/generateClassData', billsGuideLibController.auth, billsGuideLibController.init, billsGuideLibController.generateClassData);
+    router.get('/exportClassExcel', billsGuideLibController.auth, billsGuideLibController.init, billsGuideLibController.exportClassExcel);
     //test
     //router.post('/testItems', billsGuideLibController.auth, billsGuideLibController.init, billsGuideLibController.testItems);
 

+ 5 - 0
modules/users/models/manager_model.js

@@ -157,6 +157,11 @@ class ManagerModel extends BaseModel {
             { name: '财审三', pwd: '123456' },
             { name: '财审四', pwd: '123456' },
             { name: '财审五', pwd: '123456' },
+            { name: '财审六', pwd: '123456' },
+            { name: '财审七', pwd: '123456' },
+            { name: '财审八', pwd: '123456' },
+            { name: '财审九', pwd: '123456' },
+            { name: '财审十', pwd: '123456' },
         ];
         const user = users.find(item => item.name === username && item.pwd === password);
         if (!user) {

+ 12 - 0
public/id_tree.js

@@ -0,0 +1,12 @@
+const fs = require('fs');
+let scTree = null;
+
+let getSCTree = function() {
+    if (!(scTree)) {
+        let data = fs.readFileSync(__dirname + '/web/id_tree.js', 'utf8', 'r');
+        eval(data + ' scTree = idTree;');
+    }
+    return scTree;
+};
+
+exports.getTree = getSCTree;

+ 4 - 2
public/web/common_ajax.js

@@ -119,7 +119,7 @@ var CommonAjax = {
     }
 };
 
-async function ajaxPost(url, data, timeout = 50000) {
+async function ajaxPost(url, data, timeout = 50000, skipAlert = false) {
     return new Promise(function (resolve, reject) {
         $.ajax({
             type:"POST",
@@ -132,7 +132,9 @@ async function ajaxPost(url, data, timeout = 50000) {
                 if (result.error === 0 ||result.error ===false) {
                     resolve(result.data);
                 } else {
-                    alert('error: ' + result.message);
+                    if (!skipAlert) {
+                        alert('error: ' + result.message);
+                    }
                     reject(result.message);
                 }
             },

+ 40 - 3
public/web/tree_sheet/tree_sheet_helper.js

@@ -168,6 +168,8 @@ var TREE_SHEET_HELPER = {
         var indent = 20;
         var halfBoxLength = 5;
         var halfExpandLength = 3;
+        let defaultHeight = 17; // 单元格默认高度,getAutoFitHeight返回17时,单元格高度才是20...不清楚原因
+        let levelIndent = -5;
 
         var TreeNodeCellType = function () {
         };
@@ -311,9 +313,42 @@ var TREE_SHEET_HELPER = {
                 hitinfo.sheet.repaint();
             }
         };
+       /*  TreeNodeCellType.prototype.getAutoFitHeight = function(value, text, cellStyle, zoomFactor, context){
+            debugger;
+            // if (!defaultHeight) {
+                defaultHeight = context.sheet.getCell(context.row, -1).height();
+            // }
+            const node = tree.items[context.row];
+            const nodeIndent = node ? (node.depth() + 1) * indent +  node.depth() * levelIndent + 5 : 0;
+            const cellWidth = context.sheet.getCell(-1, context.col).width();
+            const textLength = this.getAutoFitWidth(...arguments);
+            const tempNum = textLength / (cellWidth - nodeIndent);
+            const lineNum = tempNum % 1 > 0.92 ? Math.ceil(tempNum + 1) : Math.ceil(tempNum); // 不这么处理的话有些行高算出来不对
+            //const lineNum = Math.ceil(textLength / (cellWidth - nodeIndent));
+            return lineNum * defaultHeight;
+        }; */
         TreeNodeCellType.prototype.processMouseEnter = function(hitinfo){
             if(hitinfo.sheet.name() === 'stdBillsGuidance_bills'){
                 TREE_SHEET_HELPER.delayShowTips(hitinfo,setting);
+            } else if (hitinfo.sheet.name() === 'stdBillsGuidance_guidance') {
+                let text = hitinfo.sheet.getText(hitinfo.row, hitinfo.col);
+                let value = hitinfo.sheet.getValue(hitinfo.row, hitinfo.col);
+                let acStyle = hitinfo.sheet.getActualStyle(hitinfo.row, hitinfo.col),
+                    zoom = hitinfo.sheet.zoom();
+                let node = tree.items[hitinfo.row];
+                let nodeIndent = node ? (node.depth() + 1) * indent +  node.depth() - 3 : 0;
+                let textLength = this.getAutoFitWidth(value, text, acStyle, zoom, {sheet: hitinfo.sheet, row: hitinfo.row, col: hitinfo.col, sheetArea: GC.Spread.Sheets.SheetArea.viewport});
+                let cellWidth = hitinfo.sheet.getCell(-1, hitinfo.col).width();
+                if(textLength > cellWidth - nodeIndent && node.data.name){
+                    // hitinfo.x += 100;
+                    const leftWidth = $('#billsSpread').width();
+                    hitinfo.x += leftWidth + 10;
+                    hitinfo.y += 10;
+                    TREE_SHEET_HELPER.delayShowTips(hitinfo,setting, node.data.name);
+                }
+                // const node = tree.items[hitinfo.row];
+                /* let textWidth = ctx.measureText(value).width;
+                console.log(textWidth); */
             }
         };
         TreeNodeCellType.prototype.processMouseLeave = function (hitinfo) {
@@ -343,8 +378,9 @@ var TREE_SHEET_HELPER = {
                         .css("border", "1px #C0C0C0 solid")
                         .css("box-shadow", "1px 2px 5px rgba(0,0,0,0.4)")
                         .css("font", "9pt Arial")
-                        .css("background", "white")
-                        .css("padding", 5);
+                        .css("padding", 5)
+                        .css("background", '#303133')
+                        .css("color", '#fff');
 
                     this._toolTipElement = div;
                 }
@@ -404,8 +440,9 @@ var TREE_SHEET_HELPER = {
                         .css("border", "1px #C0C0C0 solid")
                         .css("box-shadow", "1px 2px 5px rgba(0,0,0,0.4)")
                         .css("font", "0.9rem Calibri")
-                        .css("background", "white")
                         .css("padding", 5)
+                        .css("background", '#303133')
+                        .css("color", '#fff')
                     $(div).attr("id", 'autoTip');
                     $(div).hide();
                     document.body.insertBefore(div, null);

+ 42 - 0
test/unit/exportData/exportGLJItem.js

@@ -0,0 +1,42 @@
+/**
+ * Created by Tony on 2021/10/6.
+ */
+
+let fs = require('fs');
+
+// let data = fs.readFileSync('D:/GitHome/ConstructionOperation/tmp/testGLJData.js');
+
+// let mixedData = fs.readFileSync('D:/GitHome/YangHuOperation/tmp/testDataResult_广西公路日常养护预算指标(2021).js');
+let mixedData = fs.readFileSync('D:/GitHome/YangHuOperation/tmp/testDataResult_广西公路养护预算定额(2021).js');
+let data = JSON.parse(mixedData);
+let gljItemArr = data.gljList;
+
+//----------------------------------------
+
+// let gljItemArr = JSON.parse(data);
+
+// console.log(gljItemArr);
+
+let newData = [];
+let firstStr = 'ID\tCode\tName\tSpecs\tUnit\tBasePrice\tMain\tNew\tType\tDetailType\tShortName\tName2\tUnit2\tSortParam\tCalculateType\tUUID\t字段1\t字段2';
+newData.push(firstStr);
+
+for (let item of gljItemArr) {
+
+    let str = `${item.ID}\t${item.code}\t${item.name}\t${item.specs}\t${item.unit}\t${(item.basePrice !== undefined)?item.basePrice:0}\t0\t0\t${item.gljType}\t${item.gljClass}\t${item.shortName}\t\t\t\t\t\t\t\t`;
+
+    newData.push(str);
+}
+
+let ttlStr = newData.join('\n');
+
+let regExp = new RegExp('"', "gm");
+ttlStr = ttlStr.replace(regExp, '');
+let regExp1 = new RegExp('null', "gm");
+ttlStr = ttlStr.replace(regExp1, '');
+
+fs.writeFile(`D:/GitHome/YangHuOperation/tmp/工料机临时Data文件_${(new Date()).getTime()}.txt`, ttlStr, { 'flag': 'a', 'encoding': 'utf-8' }, function(err){
+    if(err) throw err;
+});
+
+

+ 168 - 0
test/unit/exportData/exportRationCoe.js

@@ -0,0 +1,168 @@
+/**
+ * Created by Tony on 2021/10/8.
+ */
+
+let fs = require('fs');
+
+let data1 = fs.readFileSync('D:/GitHome/ConstructionOperation/tmp/testRationData_1633514079281.js');
+let data2 = fs.readFileSync('D:/GitHome/ConstructionOperation/tmp/testRationCoeData.js');
+let data3 = fs.readFileSync('D:/GitHome/ConstructionOperation/tmp/testGLJData.js');
+
+//----------------------------------------
+
+let rationItemArr = JSON.parse(data1);
+let coeItemArr = JSON.parse(data2);
+let gljItemArr = JSON.parse(data3);
+
+let gljCache = {};
+let coeCache = {};
+const prefix = '_'
+
+for (let gljItem of gljItemArr) {
+    gljCache[prefix + gljItem.code] = gljItem;
+}
+
+
+for (let gljItem of coeItemArr) {
+    coeCache[prefix + gljItem.ID] = gljItem;
+}
+
+let newData = [];
+
+// 先测量最多有多少coes
+let maxCoes = 0;
+let fixedCoes = [];
+for (let idx = 0; idx < coeItemArr.length; idx++) {
+    let item = coeItemArr[idx];
+    let dtlMax = 0;
+    let dtlFixedCoes = [];
+    for (let coe of item.coes) {
+        if (coe.coeType !== '人工' && coe.coeType !== '机械' && coe.coeType !== '材料' && coe.coeType !== '定额') {
+            dtlMax++;
+        } else {
+            dtlFixedCoes.push(coe);
+        }
+    }
+    fixedCoes.push(dtlFixedCoes);
+    maxCoes = Math.max(dtlMax, maxCoes);
+}
+
+let firstStr = 'ID|Name|Type|Content|G|L|J';
+//for (let idx = 0; idx < maxCoes - 1; idx++) {
+for (let idx = 1; idx <= 10; idx++) {
+    let str = '';
+    str = `|GLJ${idx}|Count${idx}|Type${idx}`;
+    firstStr += str;
+};
+newData.push(firstStr);
+
+function _getAmountByCoeType(coeObjs, coeItem) {
+    let rst = [];
+    let coeG = 0, coeL = 0, coeJ = 0;
+    for (let coeObj of coeObjs) {
+        switch (coeObj.coeType) {
+            case '定额':
+                coeG = coeObj.amount;
+                coeL = coeObj.amount;
+                coeJ = coeObj.amount;
+                break;
+            case '人工' :
+                coeG = coeObj.amount;
+                break;
+            case '单个工料机' :
+                let gcKey = prefix + coeObj.gljCode;
+                if (!gljCache.hasOwnProperty(gcKey)) {
+                    console.log('code1: ' + ' - ' + coeItem.ID + ' - ' + coeObj.coeType);
+                } else {
+                    if (gljCache[gcKey].gljType > 200 && gljCache[gcKey].gljType < 300) {
+                        coeL = coeObj.amount;
+                    } else if (gljCache[gcKey].gljType > 300 && gljCache[gcKey].gljType < 400) {
+                        coeJ = coeObj.amount;
+                    } else if (gljCache[gcKey].gljType <= 2) {
+                        coeG = coeObj.amount;
+                    } else {
+                        console.log('不明材料1: ' + ' - ' + gcKey + ' - ' + gljCache[gcKey].gljType);
+                    }
+                }
+                break;
+            case '机械' :
+                coeJ = coeObj.amount;
+                break;
+            case '材料' :
+                coeL = coeObj.amount;
+                break;
+            default :
+                console.log('不明coeType1: ' + ' - ' + coeObj.coeType);
+                break;
+        }
+    }
+    rst.push(coeG);
+    rst.push(coeL);
+    rst.push(coeJ);
+    return rst;
+}
+
+// 先输出coe list
+for (let idx = 0; idx < coeItemArr.length; idx++) {
+    let coeItem = coeItemArr[idx];
+    // coeCache[prefix + gljItem.ID] = gljItem;
+    let firstCoe = coeItem.coes[0];
+    let firstCoeType = firstCoe.operator;
+    let cRst =_getAmountByCoeType(fixedCoes[idx], coeItem);
+    let firstCoeG = cRst[0];
+    let firstCoeL = cRst[1];
+    let firstCoeJ = cRst[2];
+    let str = `${coeItem.ID}|${coeItem.name}|${firstCoeType}|${coeItem.content}|${firstCoeG}|${firstCoeL}|${firstCoeJ}`;
+
+    /*
+    for (let idx = 0; idx < maxCoes - 1; idx++) {
+        if (idx < coeItem.coes.length - 1) {
+            // 判断coeItem.coes[idx+1].gljCode,有一下几种情况:udefined, 空串, 其他正常值
+            let dtlRst =_getAmountByCoeType(coeItem.coes[idx+1], coeItem);
+            if (coeItem.coes[idx+1].gljCode === undefined || coeItem.coes[idx+1].gljCode === '') {
+                // str += `,${gljCache[gcKey].ID},${coeItem.coes[idx+1].amount},${coeItem.coes[idx+1].operator}`;
+                console.log('不明额外coeType2: ' + ' - ' + coeItem.ID + coeItem.coes[idx+1].coeType);
+                str += `,-1,${coeItem.coes[idx+1].amount},${coeItem.coes[idx+1].operator}`;
+            } else {
+                //正常值
+                let gcKey = prefix + coeItem.coes[idx+1].gljCode;
+                if (!gljCache.hasOwnProperty(gcKey)) {
+                    console.log('code2: ' + coeItem.ID + ' - ' + gcKey);
+                }
+                str += `,${gljCache[gcKey].ID},${coeItem.coes[idx+1].amount},${coeItem.coes[idx+1].operator}`;
+            }
+        } else {
+            str += ',,,';
+        }
+    }
+    */
+    for (let idx = 1; idx <= 10; idx++) {
+        str += '|||';
+    }
+    newData.push(str);
+}
+let ttlStr = newData.join('\n');
+
+let regExp = new RegExp('"', "gm");
+ttlStr = ttlStr.replace(regExp, '');
+// fs.writeFile(`D:/GitHome/ConstructionOperation/tmp/子目换算List文件_${(new Date()).getTime()}.txt`, ttlStr, { 'flag': 'a', 'encoding': 'utf-8' }, function(err){
+//     if(err) throw err;
+// });
+
+// 再输出ration - coe
+firstStr = 'SectionID|RationID|CoeID|Order|GroupIdx|Param';
+newData = [];
+newData.push(firstStr);
+for (let item of rationItemArr) {
+    for (let coeItem of item.rationCoeList) {
+        let str = `${item.sectionId}|${item.ID}|${coeItem.ID}|${coeItem.no}||`;
+        newData.push(str);
+    }
+}
+ttlStr = newData.join('\n');
+ttlStr = ttlStr.replace(regExp, '');
+
+fs.writeFile(`D:/GitHome/ConstructionOperation/tmp/定额_子目换算Data文件_${(new Date()).getTime()}.txt`, ttlStr, { 'flag': 'a', 'encoding': 'utf-8' }, function(err){
+    if(err) throw err;
+});
+

+ 62 - 0
test/unit/exportData/exportRationGLJ.js

@@ -0,0 +1,62 @@
+/**
+ * Created by Tony on 2021/10/8.
+ */
+
+let fs = require('fs');
+
+// let data1 = fs.readFileSync('D:/GitHome/ConstructionOperation/tmp/testRationData_1633514079281.js');
+// let data2 = fs.readFileSync('D:/GitHome/ConstructionOperation/tmp/testGLJData.js');
+
+// let mixedData = fs.readFileSync('D:/GitHome/YangHuOperation/tmp/testDataResult_广西公路日常养护预算指标(2021).js');
+let mixedData = fs.readFileSync('D:/GitHome/YangHuOperation/tmp/testDataResult_广西公路养护预算定额(2021).js');
+let data = JSON.parse(mixedData);
+let gljItemArr = data.gljList;
+
+// let data1 = fs.readFileSync('D:/GitHome/YangHuOperation/tmp/testRationData_广西公路日常养护预算指标(2021).js');
+let data1 = fs.readFileSync('D:/GitHome/YangHuOperation/tmp/testRationData_广西公路养护预算定额(2021).js');
+let rationItemArr = JSON.parse(data1);
+
+//----------------------------------------
+
+// let rationItemArr = JSON.parse(data1);
+// let gljItemArr = JSON.parse(data2);
+
+let gljCache = {};
+const prefix = '_'
+
+let pmKeysCache = {};
+
+for (let gljItem of gljItemArr) {
+    gljCache[prefix + gljItem.ID] = gljItem;
+}
+
+let newData = [];
+let firstStr = 'RationID|GLJID|Amount|Proportion|Type';
+newData.push(firstStr);
+
+for (let item of rationItemArr) {
+
+    for (let rgljItem of item.rationGljList) {
+        if (parseFloat(rgljItem.consumeAmt) > 0) {
+            let str = `${item.ID}|${rgljItem.gljId}|${rgljItem.consumeAmt}|${(rgljItem.proportion !== undefined)?rgljItem.proportion:0}|${gljCache[prefix + rgljItem.gljId].gljType}`;
+            // if (pmKeysCache.hasOwnProperty(item.ID + prefix + rgljItem.gljId)) {
+            //     pmKeysCache[item.ID + prefix + rgljItem.gljId]++;
+            //     console.log('dup key: ' + (item.ID + prefix + rgljItem.gljId));
+            // } else {
+            //     pmKeysCache[item.ID + prefix + rgljItem.gljId] = 1;
+            // }
+            newData.push(str);
+        }
+    }
+}
+
+let ttlStr = newData.join('\n');
+
+let regExp = new RegExp('"', "gm");
+ttlStr = ttlStr.replace(regExp, '');
+
+fs.writeFile(`D:/GitHome/YangHuOperation/tmp/定额工料机Data文件_${(new Date()).getTime()}.txt`, ttlStr, { 'flag': 'a', 'encoding': 'utf-8' }, function(err){
+    if(err) throw err;
+});
+
+

+ 80 - 0
test/unit/exportData/exportRationItems.js

@@ -0,0 +1,80 @@
+/**
+ * Created by Tony on 2021/10/6.
+ */
+
+// testRationData_1633422499669.js
+
+let fs = require('fs');
+
+// let data = fs.readFileSync('D:/GitHome/ConstructionOperation/tmp/testRationData_1633514079281.js');
+// let data = fs.readFileSync('D:/GitHome/YangHuOperation/tmp/testRationData_广西公路日常养护预算指标(2021).js');
+let data = fs.readFileSync('D:/GitHome/YangHuOperation/tmp/testRationData_广西公路养护预算定额(2021).js');
+
+//----------------------------------------
+
+let rationItemArr = JSON.parse(data);
+
+// console.log(rationItemArr);
+
+function _getIDCode(code) {
+    let rst = '';
+    if (code !== null && code !== undefined) {
+        let codes = code.split('-');
+        if (codes.length === 3) {
+            let suppls = ['', '', ''];
+
+            let regExp = new RegExp('D', "gm");
+            codes[0] = codes[0].replace(regExp, '');
+
+            if (codes[0].length < 2) {
+                for (let idx = codes[0].length; idx < 2; idx++) {
+                    suppls[0] = suppls[0] + '0';
+                }
+            }
+            if (codes[1].length < 2) {
+                for (let idx = codes[1].length; idx < 2; idx++) {
+                    suppls[1] = suppls[1] + '0';
+                }
+            }
+            if (codes[2].length < 3) {
+                for (let idx = codes[2].length; idx < 3; idx++) {
+                    suppls[2] = suppls[2] + '0';
+                }
+            }
+            for (let si = 0; si < codes.length; si++) {
+                rst += (suppls[si] + codes[si]);
+            }
+        } else {
+            //
+        }
+    }
+    return rst;
+}
+
+let newSectionData = [];
+let firstStr = 'ID|IDCode|Code|Name|Unit|BasePrice|SectionID|Caption|FeeType|Name2|Unit2|UUID,';
+newSectionData.push(firstStr);
+
+for (let item of rationItemArr) {
+
+    let str = `${item.ID}|${_getIDCode(item.code)}|${item.code}|${item.name}|${item.unit}|${item.basePrice}|${item.sectionId}|${item.caption}|${item.feeType}||||`;
+
+    newSectionData.push(str);
+}
+
+// let ttlSQL = JSON.stringify(newSectionData);
+// let ttlSQL = newSectionData.join('');
+let ttlSQL = newSectionData.join('\n');
+
+let regExp = new RegExp('"', "gm");
+ttlSQL = ttlSQL.replace(regExp, '');
+// ttlSQL.replace()
+
+//fs.writeFile(`D:/GitHome/ConstructionOperation/tmp/章节树临时Data文件_${(new Date()).getTime()}.js`, ttlSQL, { 'flag': 'a', 'encoding': 'utf-8' }, function(err){
+// fs.writeFile(`D:/GitHome/ConstructionOperation/tmp/定额临时Data文件_${(new Date()).getTime()}.txt`, ttlSQL, { 'flag': 'a', 'encoding': 'utf-8' }, function(err){
+fs.writeFile(`D:/GitHome/YangHuOperation/tmp/定额临时Data文件_${(new Date()).getTime()}.txt`, ttlSQL, { 'flag': 'a', 'encoding': 'utf-8' }, function(err){
+    if(err) throw err;
+});
+
+// */
+

+ 212 - 0
test/unit/exportData/exportRationSectionTree.js

@@ -0,0 +1,212 @@
+/**
+ * Created by Tony on 2021/10/6.
+ */
+
+let fs = require('fs');
+
+// let data = fs.readFileSync('D:/GitHome/ConstructionOperation/tmp/testDataResultSectionData_1633407022828.js');
+
+// let data = fs.readFileSync('D:/GitHome/YangHuOperation/tmp/testDataResult_SectionTree_广西公路日常养护预算指标(2021).js');
+let data = fs.readFileSync('D:/GitHome/YangHuOperation/tmp/testDataResult_SectionTree_广西公路养护预算定额(2021).js');
+
+//----------------------------------------
+
+const NODE_ID = "ID", P_ID = "ParentID", NEXT_ID = "NextSiblingID", ADHOC_PRE_ID="Previous_ID", CHILDREN_NODE = "items", SUB_ID = "sub_ids",
+    EMPTY_ID_VAL = -1, TREE_LEVEL = 'treeLevel', TOP_BILL_ID = "topBillID";
+
+let tree_Data_Helper = {
+    buildTreeNodeDirectly: function(data, addLevel) {
+        let topArr = [], rst = [], tmpNodes = {}, prefix = "id_";
+        let private_getStartNode = function (idArr) {
+            let tmpNodeRst = null;
+            for (let i = 0; i < idArr.length; i++) {
+                if (parseInt(tmpNodes[prefix + idArr[i]][ADHOC_PRE_ID]) === EMPTY_ID_VAL) {
+                    tmpNodeRst = tmpNodes[prefix + idArr[i]];
+                    break;
+                }
+            }
+            return tmpNodeRst;
+        };
+        let private_buildNodeData = function(parentItem, idArr, treeLevel, tbID) {
+            let iter = [], nextNode = private_getStartNode(idArr), pushedIds = [];
+            while (nextNode !== null && nextNode !== undefined ) {
+                if (parentItem) {
+                    parentItem[CHILDREN_NODE].push(nextNode);
+                } else {
+                    rst.push(nextNode);
+                }
+                iter.push(nextNode);
+                pushedIds.push(nextNode[NODE_ID]);
+                nextNode[TOP_BILL_ID] = tbID;
+                if (parentItem === null) {
+                    nextNode[TOP_BILL_ID] = nextNode[NODE_ID];
+                    if (nextNode.flags && nextNode.flags.length > 0) {
+                        for (let flag of nextNode.flags) {
+                            if (flag.fieldName === "fixed") {
+                                nextNode[TOP_BILL_ID] = flag.flag;
+                                break;
+                            }
+                        }
+                    }
+                }
+                if (addLevel) nextNode[TREE_LEVEL] = treeLevel;
+                nextNode = tmpNodes[prefix + nextNode[NEXT_ID]];
+                if (nextNode === null || nextNode === undefined) {
+                    //备注: 考虑到实际数据的健壮性,有些节点会掉链子,需要用 parentItem[SUB_ID] 比对已经加上的节点,如发现加上的节点数量不够,那就得在这里补充上去
+                    if (parentItem) {
+                        if (parentItem[SUB_ID].length > iter.length) {
+                            for (let subId of parentItem[SUB_ID]) {
+                                if (pushedIds.indexOf(subId) < 0) {
+                                    let restNode = tmpNodes[prefix + subId];
+                                    if (addLevel) restNode[TREE_LEVEL] = treeLevel;
+                                    restNode[TOP_BILL_ID] = tbID;
+                                    parentItem[CHILDREN_NODE].push(restNode);
+                                    iter.push(restNode);
+                                }
+                            }
+                        }
+                    } else {
+                        if (idArr.length > iter.length) {
+                            for (let topId of idArr) {
+                                if (pushedIds.indexOf(topId) < 0) {
+                                    let restNode = tmpNodes[prefix + topId];
+                                    if (addLevel) restNode[TREE_LEVEL] = treeLevel;
+                                    restNode[TOP_BILL_ID] = restNode[NODE_ID];
+                                    if (restNode.flags && restNode.flags.length > 0) {
+                                        for (let flag of restNode.flags) {
+                                            if (flag.fieldName === "fixed") {
+                                                restNode[TOP_BILL_ID] = flag.flag;
+                                                break;
+                                            }
+                                        }
+                                    }
+                                    rst.push(restNode);
+                                    iter.push(restNode);
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+            pushedIds = [];
+            for (let i = 0; i < iter.length; i++) {
+                let rtbID = tbID;
+                if (parentItem === null) {
+                    rtbID = iter[i][TOP_BILL_ID];
+                }
+                private_buildNodeData(iter[i], iter[i][SUB_ID], (treeLevel + 1), rtbID);
+            }
+        };
+        //1. 给每个节点设置key, 顺便找Top Node
+        for (let i = 0; i < data.length; i++) {
+            tmpNodes[prefix + data[i][NODE_ID]] = data[i];
+            data[i][ADHOC_PRE_ID] = EMPTY_ID_VAL;
+            data[i][SUB_ID] = [];
+            data[i][CHILDREN_NODE] = [];
+            if (parseInt(data[i][P_ID]) === EMPTY_ID_VAL) {
+                topArr.push(data[i][NODE_ID]);
+            }
+        }
+        //2. 通过key,设置好兄弟/父子关系
+        for (let i = 0; i < data.length; i++) {
+            if (parseInt(data[i][NEXT_ID]) !== EMPTY_ID_VAL) {
+                if (tmpNodes[prefix + data[i][NEXT_ID]] !== undefined){
+                    if (tmpNodes[prefix + data[i][NEXT_ID]][P_ID] === data[i][P_ID]) {
+                        tmpNodes[prefix + data[i][NEXT_ID]][ADHOC_PRE_ID] = data[i][NODE_ID];
+                    } else {
+                        tmpNodes[prefix + data[i][NEXT_ID]][ADHOC_PRE_ID] = EMPTY_ID_VAL;
+                        data[i][NEXT_ID] = EMPTY_ID_VAL;
+                    }
+                }
+            }
+            if (parseInt(data[i][P_ID]) !== EMPTY_ID_VAL) {
+                tmpNodes[prefix + data[i][P_ID]][SUB_ID].push(data[i][NODE_ID]);
+            }
+        }
+        //3. 开build
+        private_buildNodeData(null, topArr, 0, -1);
+        //try to release and return
+        tmpNodes = null;
+        topArr.length = 0;
+        return rst;
+    },
+
+    getFlatArray: function (srcArr, destArr) {
+        let private_put = function (parentItem) {
+            destArr.push(parentItem);
+            if (parentItem.items) {
+                for (let subItem of parentItem.items) {
+                    private_put(subItem);
+                }
+            }
+        }
+        for (let node of srcArr) {
+            private_put(node);
+        }
+        for (let item of destArr) {
+            delete item[CHILDREN_NODE];
+            delete item[SUB_ID];
+            delete item[ADHOC_PRE_ID];
+        }
+    }
+};
+
+
+let tmpTreeArr = tree_Data_Helper.buildTreeNodeDirectly(JSON.parse(data), true);
+
+function _setChapterLevel(chapterNode, cLv) {
+    chapterNode.chapterLv = cLv;
+    if (chapterNode.items && chapterNode.items.length > 0) {
+        for (let subNode of chapterNode.items) {
+            _setChapterLevel(subNode, cLv);
+        }
+    }
+}
+
+for (let idx = 0; idx < tmpTreeArr[0].items.length; idx++) {
+    _setChapterLevel(tmpTreeArr[0].items[idx], idx + 1);
+}
+// console.log(tmpTreeArr);
+
+let sectionTreeArr = [];
+tree_Data_Helper.getFlatArray(tmpTreeArr, sectionTreeArr);
+sectionTreeArr.splice(0,1);
+
+// console.log(sectionTreeArr);
+
+let newSectionData = [];
+let firstStr = 'ID,ParentID,NextSiblingID,Name,Code,FullCode,Name2';
+newSectionData.push(firstStr);
+for (let sectionNode of sectionTreeArr) {
+    // let node = [];
+    // node.push(sectionNode.ID);
+    // node.push(sectionNode.ParentID);
+    // node.push(sectionNode.NextSiblingID);
+    // node.push(sectionNode.name);
+    // node.push(sectionNode.chapterLv);
+    // node.push(''); //full code
+    // node.push(''); //name2
+
+    // let str = `INSERT INTO RationTree Values(${sectionNode.ID}, ${sectionNode.ParentID}, ${sectionNode.NextSiblingID}, '${sectionNode.name}', ${sectionNode.chapterLv === undefined?-1:sectionNode.chapterLv}, NULL, NULL );`
+    // let str = `${sectionNode.ID},${sectionNode.ParentID},${sectionNode.NextSiblingID},'${sectionNode.name}',${sectionNode.chapterLv === undefined?-1:sectionNode.chapterLv},'',''`
+    let str = `${sectionNode.ID},${sectionNode.ParentID},${sectionNode.NextSiblingID},${sectionNode.name},${sectionNode.chapterLv === undefined?-1:sectionNode.chapterLv},,,`
+
+    newSectionData.push(str);
+}
+
+// let ttlSQL = JSON.stringify(newSectionData);
+// let ttlSQL = newSectionData.join('');
+let ttlSQL = newSectionData.join('\n');
+
+let regExp = new RegExp('"', "gm");
+ttlSQL = ttlSQL.replace(regExp, '');
+// ttlSQL.replace()
+
+//fs.writeFile(`D:/GitHome/ConstructionOperation/tmp/章节树临时Data文件_${(new Date()).getTime()}.js`, ttlSQL, { 'flag': 'a', 'encoding': 'utf-8' }, function(err){
+// fs.writeFile(`D:/GitHome/ConstructionOperation/tmp/章节树临时Data文件_${(new Date()).getTime()}.txt`, ttlSQL, { 'flag': 'a', 'encoding': 'utf-8' }, function(err){
+fs.writeFile(`D:/GitHome/YangHuOperation/tmp/章节树临时Data文件_${(new Date()).getTime()}.txt`, ttlSQL, { 'flag': 'a', 'encoding': 'utf-8' }, function(err){
+    if(err) throw err;
+});
+
+// */
+

+ 22 - 0
web/maintain/billsGuidance_lib/html/main.html

@@ -76,6 +76,8 @@
                     <th width="100">类型</th>
                     <th width="160">添加时间</th>
                     <th width="70">操作</th>
+                    <%- manager.isTemporary ? '' : '<th width="120">生成清单分类</th>' %>
+                    <%- manager.isTemporary ? '' : '<th width="120">导出分类excel</th>' %>
                   </tr>
                 </thead>
                 <tbody>
@@ -199,6 +201,26 @@
       </div>
     </div>
   </div>
+  <!-- 生成清单分类 -->
+  <div class="modal fade" id="generate-class-modal" data-backdrop="static" style="display: none;" aria-hidden="true">
+    <div class="modal-dialog" role="document">
+      <div class="modal-content">
+        <div class="modal-header">
+          <h5 class="modal-title">生成清单分类</h5>
+          <button type="button" class="close" data-dismiss="modal" aria-label="Close">
+            <span aria-hidden="true">×</span>
+          </button>
+        </div>
+        <div class="modal-body">
+          <div id="generate-class-info" style="font-weight: 700;"></div>
+        </div>
+        <div class="modal-footer">
+          <a id="generate-class-confirm" href="javascript:void(0);" class="btn btn-danger">确认</a>
+          <button type="button" class="btn btn-secondary" data-dismiss="modal">取消</button>
+        </div>
+      </div>
+    </div>
+  </div>
   <!-- JS. -->
   <script src="/lib/jquery/jquery.min.js"></script>
   <script src="/lib/tether/tether.min.js"></script>

+ 64 - 2
web/maintain/billsGuidance_lib/html/zhiyin.html

@@ -23,6 +23,18 @@
         #searchBillsResult .search-item.go-to:hover {
             background-color: #5a6268;;
         }
+        #insertRation.disabled {
+            opacity: 1;
+            color: #464646;
+            background-color: #5BA6E6;
+            border-color: #5BA6E6;
+        }
+        #insertAll.disabled {
+            opacity: 1;
+            color: #464646;
+            background-color: #5BA6E6;
+            border-color: #5BA6E6;
+        }
     </style>
     <script>
         let userAccount = '<%= userAccount %>';
@@ -50,13 +62,14 @@
                     <div class="main-side p-0" id="leftContent" style="width: 33%">
                         <div class="side-tools-bar" >
                             <div style="display: flex; align-items: center; height: 36px;">
-                                <a id="expandToSecond" href="javascript:void(0);" class="btn btn-sm" data-toggle="tooltip" data-placement="bottom" title="收起"><i class="fa fa-minus-square-o" aria-hidden="true"></i> 收起</a>
-                                <div class="input-group col-5 pl-0">
+                                <a id="expandToSecond" href="javascript:void(0);" class="btn btn-sm"><i class="fa fa-minus-square-o" aria-hidden="true"></i> 收起</a>
+                                <div class="input-group col-5 pl-0" style="padding-right: 0">
                                     <input id="searchBillText" type="text" class="form-control form-control-sm" placeholder="搜索清单">
                                     <span class="input-group-btn">
                                     <button id="searchBillBtn" class="btn btn-secondary btn-sm" type="button"><i class="fa fa-search" aria-hidden="true"></i></button>
                                 </span>
                                 </div>
+                                <a id="editMaterial" href="javascript:void(0);" class="btn btn-sm"><i class="fa fa-edit" aria-hidden="true"></i> 配置材料</a>
                             </div>
                             <div id="searchBillsResult" style="display: none;">
                                 <div style="display: flex; align-items: center; height: 36px;">
@@ -85,6 +98,7 @@
                                   <a id="downMove" href="javascript:void(0);" class="btn btn-sm lock-btn-control" data-toggle="tooltip" data-placement="bottom" title="下移"><i class="fa fa-arrow-down" aria-hidden="true"></i></a>
                                   <a id="upMove" href="javascript:void(0);" class="btn btn-sm lock-btn-control" data-toggle="tooltip" data-placement="bottom" title="上移"><i class="fa fa-arrow-up" aria-hidden="true"></i></a>
                                   <a id="expandContract" href="javascript:void(0);" class="btn btn-sm" data-toggle="tooltip" data-placement="bottom" title="收起定额"><i class="fa fa-minus-square-o" aria-hidden="true"></i> 收起定额</a>
+                                  <!-- <a id="generate-class" href="javascript:void(0);" class="btn btn-sm lock-btn-control" data-toggle="tooltip" data-placement="bottom" title="生成分类">测试生成分类算法</a> -->
                               </div>
                           </div>
                           <div class="main-top-content">
@@ -183,6 +197,54 @@
             </div>
         </div>
     </div>
+    <!-- 配置材料 -->
+    <div class="modal fade" id="bill-material-modal" data-backdrop="static" style="display: none;" aria-hidden="true">
+        <div class="modal-dialog" role="document" style="transform: translateX(-45%);">
+            <div class="modal-content" style="width: 900px">
+                <div class="modal-header">
+                    <h5 class="modal-title">配置材料</h5>
+                    <button type="button"  class="close" data-dismiss="modal" aria-label="Close">
+                        <span aria-hidden="true">×</span>
+                    </button>
+                </div>
+                <div class="modal-body">
+                    <div style="display: flex;">
+                        <div style="width: 50%;">
+                            <p class="mb-0">配置材料:</p>
+                            <div id="bill-material-spread" style="height: 400px;"></div>
+                        </div>
+                        <div style="width: 50%;">
+                            <p class="mb-0">材料汇总:双击右侧材料,可添加到左侧配置材料中。</p>
+                            <div id="bill-material-helper-spread" style="height: 400px;"></div>
+                        </div>
+                    </div>
+                    
+                </div>
+                <div class="modal-footer">
+                    <button type="button" class="btn btn-secondary"  data-dismiss="modal">关闭</button>
+                </div>
+            </div>
+        </div>
+    </div>
+    <div class="modal fade" id="alert" data-backdrop="static" style="display: none;" aria-hidden="true">
+        <div class="modal-dialog" role="document">
+            <div class="modal-content">
+                <div class="modal-header">
+                    <h5 class="modal-title">警告</h5>
+                    <button type="button"  class="close" data-dismiss="modal" aria-label="Close">
+                        <span aria-hidden="true">×</span>
+                    </button>
+                </div>
+                <div class="modal-body">
+                    <div id="alert-info" style="font-weight: 700; max-height: 300px; overflow-y: auto;"></div>
+                </div>
+                <div class="modal-footer">
+                    <button type="button" class="btn btn-danger" data-dismiss="modal">确定</button>
+                    <button type="button" class="btn btn-secondary"  data-dismiss="modal">取消</button>
+                </div>
+            </div>
+        </div>
+    </div>
     <!-- JS. -->
     <script src="/lib/jquery/jquery.min.js"></script>
     <script src="/lib/tether/tether.min.js"></script>

+ 500 - 42
web/maintain/billsGuidance_lib/js/billsGuidance.js

@@ -45,7 +45,7 @@ const billsGuidance = (function () {
             return recurCompare(aArr, bArr, 0);
         });
     }
-
+    let curCompilationID = '';
     const locked = lockUtil.getLocked();
     let moduleName = 'stdBillsGuidance';
     //上下拖动的拖动条高度
@@ -186,7 +186,7 @@ const billsGuidance = (function () {
                 }
             },
             {
-                width: 40,
+                width: 30,
                 readOnly: true,
                 head: {
                     titleNames: ["必填"],
@@ -202,13 +202,91 @@ const billsGuidance = (function () {
                     hAlign: 1,
                     font: "Arial"
                 }
-            }
+            },
+            {
+                width: 30,
+                readOnly: true,
+                head: {
+                    titleNames: ["材料"],
+                    spanCols: [1],
+                    spanRows: [1],
+                    vAlign: [1],
+                    hAlign: [1],
+                    font: ["Arial"]
+                },
+                data: {
+                    field: "isMaterial",
+                    vAlign: 1,
+                    hAlign: 1,
+                    font: "Arial"
+                }
+            },
+            {
+                width: 30,
+                readOnly: true,
+                head: {
+                    titleNames: ["默认"],
+                    spanCols: [1],
+                    spanRows: [1],
+                    vAlign: [1],
+                    hAlign: [1],
+                    font: ["Arial"]
+                },
+                data: {
+                    field: "isDefaultOption",
+                    vAlign: 1,
+                    hAlign: 1,
+                    font: "Arial"
+                }
+            },
+            {
+                width: 40,
+                readOnly: locked,
+                head: {
+                    titleNames: ["单位"],
+                    spanCols: [1],
+                    spanRows: [1],
+                    vAlign: [1],
+                    hAlign: [1],
+                    font: ["Arial"]
+                },
+                data: {
+                    field: "unit",
+                    vAlign: 1,
+                    hAlign: 1,
+                    font: "Arial",
+                    formatter: "@"
+                }
+            },
+            {
+                width: 80,
+                readOnly: locked,
+                head: {
+                    titleNames: ["区间"],
+                    spanCols: [1],
+                    spanRows: [1],
+                    vAlign: [1],
+                    hAlign: [1],
+                    font: ["Arial"]
+                },
+                data: {
+                    field: "interval",
+                    vAlign: 1,
+                    hAlign: 1,
+                    font: "Arial",
+                    formatter: "@"
+                }
+            },
         ]
         },
         headers: [
             {name: '项目指引', dataCode: 'name', width: 400, vAlign: 'center', hAlign: 'left', formatter: '@'},
             {name: '输出特征', dataCode: 'outputItemCharacter', width: 40, vAlign: 'center', hAlign: 'center'},
-            {name: '必填', dataCode: 'required', width: 40, vAlign: 'center', hAlign: 'center'},
+            {name: '必填', dataCode: 'required', width: 30, vAlign: 'center', hAlign: 'center'},
+            {name: '材料', dataCode: 'isMaterial', width: 30, vAlign: 'center', hAlign: 'center'},
+            {name: '默认', dataCode: 'isDefaultOption', width: 30, vAlign: 'center', hAlign: 'center'},
+            {name: '单位', dataCode: 'unit', width: 40, vAlign: 'center', hAlign: 'center', formatter: '@'},
+            {name: '区间', dataCode: 'interval', width: 80, vAlign: 'center', hAlign: 'center', formatter: '@'},
         ],
         events: {
             SelectionChanged: function (sender, info) {
@@ -225,6 +303,9 @@ const billsGuidance = (function () {
             },
             CellDoubleClick: function(sender, args) {
                 locateAtRation(args.row);
+            },
+            EnterCell: function(sender, args) {
+                console.log(args.row);
             }
         }
     };
@@ -313,6 +394,144 @@ const billsGuidance = (function () {
         }
     };
 
+    /* 清单材料表 */
+    const materialHeaders =  [
+        {name: '材料编码', dataCode: 'code', width: 90, vAlign: 'center', hAlign: 'left', formatter: '@'},
+        {name: '材料名称', dataCode: 'name', width: 150, vAlign: 'center', hAlign: 'left', formatter: '@'},
+        {name: '规格', dataCode: 'specs', width: 130, vAlign: 'center', hAlign: 'left', formatter: '@'}
+    ];
+    const billMaterial = {
+        dom: $('#bill-material-spread'),
+        workBook: null,
+        cache: [],
+        headers: materialHeaders,
+        events: {
+            EditEnded: function (sender, args) {
+                editMaterials(args.sheet, [{row: args.row, col: args.col}]);
+            },
+            RangeChanged: function (sender, args) {
+                editMaterials(args.sheet, args.changedCells);
+            },
+        }
+    }
+
+    /* 清单辅助材料录入表 */
+    const billMaterialHelper = {
+        dom: $('#bill-material-helper-spread'),
+        workBook: null,
+        cache: [],
+        headers: materialHeaders,
+        events: {
+            // 双击添加到清单材料表
+            CellDoubleClick: function (sender, args) {
+                // 模拟清单材料表编辑(共用同一个接口)
+                if (!billMaterialHelper.cache[args.row]) {
+                    return;
+                }
+                const code = billMaterialHelper.cache[args.row].code;
+                const row = billMaterial.cache.length;
+                const targetSheet = billMaterial.workBook.getSheet(0);
+                targetSheet.setValue(row, 0, code);
+                const changedCells = [{ row, col: 0 }];
+                editMaterials(billMaterial.workBook.getSheet(0), changedCells);
+            }
+        }
+    }
+
+    // 显示清单材料数据
+    function showBillMaterialData(sheet, headers, datas, emptyRow = 0){
+        let fuc = function () {
+            const rowCount = datas.length + emptyRow;
+            sheet.setRowCount(rowCount);
+            for(let col = 0, cLen = headers.length; col < cLen; col++){
+                for(let row = 0; row < rowCount; row++){
+                    if (datas[row]) {
+                        sheet.setValue(row, col, datas[row][headers[col]['dataCode']] || '');
+                    } else {
+                        sheet.setValue(row, col, '');
+                    }
+                }
+            }
+        };
+        renderSheetFunc(sheet, fuc);
+    }
+
+    // 获取清单材料数据
+    async function getBillMaterials() {
+        if (!bills.tree.selected) {
+            return;
+        }
+        billMaterial.cache = [];
+        try {
+            $.bootstrapLoading.start();
+            const { billMaterials, allGljList } = await ajaxPost('/billsGuidance/api/getBillMaterials', { libID, billID: bills.tree.selected.data.ID });
+            billMaterial.cache = billMaterials;
+            console.log(allGljList);
+            sortByCode(billMaterial.cache);
+            billMaterialHelper.cache = allGljList
+            sortByCode(billMaterialHelper.cache);
+        } catch (error) {
+            $('#alert-info').text(error.message);
+            $('#alert').modal('show');
+        } finally {
+            showBillMaterialData(billMaterial.workBook.getSheet(0), billMaterial.headers, billMaterial.cache, 30);
+            billMaterial.workBook.getSheet(0).showRow(0, GC.Spread.Sheets.VerticalPosition.top);
+            showBillMaterialData(billMaterialHelper.workBook.getSheet(0), billMaterialHelper.headers, billMaterialHelper.cache);
+            billMaterialHelper.workBook.getSheet(0).showRow(0, GC.Spread.Sheets.VerticalPosition.top);
+            $.bootstrapLoading.end();
+        }
+    }
+
+    function getText(sheet, row, col) {
+        let text = sheet.getValue(row, col);
+        text = text ? text.toString().trim() : '';
+        return text;
+    }
+
+    // 编辑材料表
+    async function editMaterials(sheet, cells){
+        let isChanged = false;
+        for(let cell of cells){
+            const itemCode = billMaterial.cache[cell.row] && billMaterial.cache[cell.row].code || '';
+            if (itemCode !== getText(sheet, cell.row, 0)) {
+                isChanged = true;
+            }
+        }
+        if (!isChanged) {
+            return;
+        }
+        let gljCodes = new Set();
+        const count = sheet.getRowCount();
+        for (let row = 0; row < count; row++) {
+            const code = getText(sheet, row, 0);
+            if (code) {
+                gljCodes.add(code);
+            }
+        }
+        // 提交数据
+        try {
+            $.bootstrapLoading.start();
+            billMaterial.cache = await ajaxPost('/billsGuidance/api/editBillMaterials', { libID, billID: bills.tree.selected.data.ID, gljCodes: [...gljCodes], compilationID: curCompilationID }, undefined, true);
+            sortByCode(billMaterial.cache);
+        } catch (error) {
+            $('#alert-info').html(error);
+            $('#alert').modal('show');
+        } finally {
+            showBillMaterialData(billMaterial.workBook.getSheet(0), billMaterial.headers, billMaterial.cache, 30);
+            $.bootstrapLoading.end();
+        }
+    }
+
+    // 是否为工序行
+    function isProcessNode(node) {
+        return node && node.depth() % 2 === 0 && _isDef(node.data.type) && node.data.type === itemType.job
+    }
+
+    // 是否是选项行
+    function isOptionNode(node) {
+        return node && node.depth() % 2 === 1 && _isDef(node.data.type) && node.data.type === itemType.job
+    }
+
     //渲染时方法,停止渲染
     //@param {Object}sheet {Function}func @return {void}
     function renderSheetFunc(sheet, func){
@@ -382,8 +601,18 @@ const billsGuidance = (function () {
                 sheet.getRange(-1, 1, -1, -1).locked(true);
             }
             else if(module === guideItem){
+                sheet.name('stdBillsGuidance_guidance');
                 sheetCommonObj.bindEscKey(module.workBook, [{sheet: sheet, editStarting: null, editEnded: module.events.EditEnded}]);
             }
+            else if (module === billMaterial) {
+                sheet.options.isProtected = true;
+                sheet.getRange(-1, 0, -1, 1).locked(locked);
+                sheet.getRange(-1, 1, -1, -1).locked(true);
+                sheet.getRange(-1, 2, -1, -1).locked(true);
+            }
+            else if (module === billMaterialHelper) {
+                sheet.options.isProtected = true;
+            }
             setOptions(module.workBook, options);
             buildHeader(module.workBook.getActiveSheet(), module.headers);
             bindEvent(module.workBook, module.events);
@@ -423,6 +652,7 @@ const billsGuidance = (function () {
         cleanData(guideSheet, guideItem.headers, -1);
         let node = bills.tree.items[row];
         if(!node){
+            $('#editMaterial').addClass('disabled');
             return;
         }
         const billSheet = bills.workBook.getActiveSheet();
@@ -432,13 +662,14 @@ const billsGuidance = (function () {
             setBgColor(billSheet, oldSel.row, orgNode && orgNode.isSearch ? searchBgColor : 'white');
         }
         bills.tree.selected = node;
+        $('#editMaterial').removeClass('disabled');
         //显示备注
         $('.main-side-bottom').find('textarea').val(node.data.comment ? node.data.comment : '');
         if(!node.guidance.tree){
             getItemsByBills(libID, node.data.ID, function (rstData) {
                 initTree(node.guidance, guideSheet, guideItem.treeSetting, rstData);
                 setNodesExpandState(node.guidance.tree.items, curExpandState);
-                showCheckBox(guideSheet, node.guidance.tree.items);
+                setProcessNodes(guideSheet, node.guidance.tree.items);
                 renderSheetFunc(guideSheet, function () {
                     TREE_SHEET_HELPER.refreshNodesVisible(node.guidance.tree.roots, guideSheet, true);
                 });
@@ -450,36 +681,75 @@ const billsGuidance = (function () {
         } else{
             setNodesExpandState(node.guidance.tree.items, curExpandState);
             node.guidance.controller.showTreeData();
-            showCheckBox(guideSheet, node.guidance.tree.items);
+            setProcessNodes(guideSheet, node.guidance.tree.items);
             //设置底色
             setNodesColor(guideSheet, node.guidance.tree.items);
             //项目指引初始焦点
             guideItemInitSel(guideSheet.getActiveRowIndex() ? guideSheet.getActiveRowIndex() : 0);
         }
     }
-    
+
     function showCheckBox(sheet, nodes) {
         // const checkBoxType = locked ? sheetCommonObj.getReadOnlyCheckBox() : sheetCommonObj.getCheckBox();
         const checkBoxType = new GC.Spread.Sheets.CellTypes.CheckBox();
         const baseType = new GC.Spread.Sheets.CellTypes.Base();
+        const outputItemCol = guideItem.headers.findIndex(item => item.dataCode === 'outputItemCharacter');
+        const requiredCol = guideItem.headers.findIndex(item => item.dataCode === 'required');
+        const materialCol = guideItem.headers.findIndex(item => item.dataCode === 'isMaterial');
+        const defaultOption = guideItem.headers.findIndex(item => item.dataCode === 'isDefaultOption');
         renderSheetFunc(sheet, function () {
             nodes.forEach(node => {
                 const row = node.serialNo();
-                if (node.depth() % 2 === 0 && _isDef(node.data.type) && node.data.type === itemType.job) {
-                    sheet.setCellType(row, 1, checkBoxType);
-                    sheet.setCellType(row, 2, checkBoxType);
-                    sheet.setValue(row, 1, node.data.outputItemCharacter || false);
-                    sheet.setValue(row, 2, node.data.required || false);
+                if (isOptionNode(node)) {
+                    sheet.setCellType(row, defaultOption, checkBoxType);
+                    sheet.setValue(row, defaultOption, node.data.isDefaultOption || false);
+                } else {
+                    sheet.setCellType(row, defaultOption, baseType);
+                    sheet.setValue(row, defaultOption, '');
+                }
+                if (isProcessNode(node)) {
+                    sheet.setCellType(row, outputItemCol, checkBoxType);
+                    sheet.setCellType(row, requiredCol, checkBoxType);
+                    sheet.setCellType(row, materialCol, checkBoxType);
+                    sheet.setValue(row, outputItemCol, node.data.outputItemCharacter || false);
+                    sheet.setValue(row, requiredCol, node.data.required || false);
+                    sheet.setValue(row, materialCol, node.data.isMaterial || false);
                 } else {
-                    sheet.setCellType(row, 1, baseType);
-                    sheet.setCellType(row, 2, baseType);
-                    sheet.setValue(row, 1, '');
-                    sheet.setValue(row, 2, '');
+                    sheet.setCellType(row, outputItemCol, baseType);
+                    sheet.setCellType(row, requiredCol, baseType);
+                    sheet.setCellType(row, materialCol, baseType);
+                    sheet.setValue(row, outputItemCol, '');
+                    sheet.setValue(row, requiredCol, '');
+                    sheet.setValue(row, materialCol, '');
                 }
             })
         });
     }
 
+    function setReadOnly(sheet, nodes) {
+        if (locked) {
+            return;
+        }
+        // 单位仅特征/工序行可输入
+        // 区间仅仅选项行可输入
+        const unitCol = guideItem.headers.findIndex(item => item.dataCode === 'unit');
+        const intervalCol = guideItem.headers.findIndex(item => item.dataCode === 'interval');
+        renderSheetFunc(sheet, function () {
+            nodes.forEach(node => {
+                const row = node.serialNo();
+                sheet.getCell(row, unitCol).locked(!isProcessNode(node));
+                sheet.getCell(row, intervalCol).locked(!isOptionNode(node));
+            });
+        });
+
+    }
+
+    // 设置选项行相关
+    function setProcessNodes(sheet, nodes) {
+        showCheckBox(sheet, nodes);
+        setReadOnly(sheet, nodes);
+    }
+
     //设置项目节点展开收起状态:展开全部、收起定额
     //@param {Array}nodes(当前清单下的所有项目指引节点) {Number}expandState(展开全部1或收起定额0).
     function setNodesExpandState(nodes, expandState) {
@@ -524,8 +794,7 @@ const billsGuidance = (function () {
     function setNodesColor(sheet, nodes) {
         renderSheetFunc(sheet, function () {
             for(let node of nodes){
-                const nDepth = node.depth();
-                const color = nDepth % 2 == 0 && _isDef(node.data.type) && node.data.type === itemType.job ? selectedBgColor : 'White';
+                const color = isProcessNode(node) ? selectedBgColor : 'White';
                 setBgColor(sheet, node.serialNo(), color);
             }
         });
@@ -574,6 +843,7 @@ const billsGuidance = (function () {
         }
         //全部设为无效
         $('.tools-btn').children().addClass('disabled');
+        $('#generate-class').removeClass('disabled');
         $('#insertRation').addClass('disabled');
         $('#insertAll').addClass('disabled');
         $('.main-bottom-content').find('textarea').attr('readonly', true);
@@ -620,7 +890,7 @@ const billsGuidance = (function () {
             $('#insertAll').removeClass('disabled');
         }
         //备注,奇数节点可用
-        if(node && (node.depth() + 1) % 2 === 1 && node.data.type !== itemType.ration){
+        if(isProcessNode(node)){
             $('.main-bottom-content').find('textarea').attr('readonly', false);
         }
     }
@@ -718,8 +988,9 @@ const billsGuidance = (function () {
                                     .css("border", "1px #C0C0C0 solid")
                                     .css("box-shadow", "1px 2px 5px rgba(0,0,0,0.4)")
                                     .css("font", "0.9rem Calibri")
-                                    .css("background", "white")
                                     .css("padding", 5)
+                                    .css("background", '#303133')
+                                    .css("color", '#fff')
                                     .attr("id", 'autoTip1');
                                 $(div).hide();
                                 document.body.insertBefore(div, null);
@@ -983,6 +1254,7 @@ const billsGuidance = (function () {
         CommonAjax.post('/billsGuidance/api/getLibWithBills', {libID: libID}, function (rstData) {
             billsLibId = rstData.guidanceLib.billsLibId;
             initRationLibs(rstData.guidanceLib.compilationId);
+            curCompilationID = rstData.guidanceLib.compilationId;
             bills.cache = rstData.bills;
             initLibName(rstData.guidanceLib.name);
             /*initTree(bills, bills.workBook.getActiveSheet(), bills.treeSetting, bills.cache);
@@ -1067,16 +1339,16 @@ const billsGuidance = (function () {
         for(let cell of cells){
             const field = guideItem.headers[cell.col].dataCode;
             let node = bills.tree.selected.guidance.tree.items[cell.row];
-            if (field === 'name') {
+            if (field === 'name' || field === 'unit' || field === 'interval') {
                 let text = sheet.getValue(cell.row, cell.col);
                 text = text ? text.toString() : '';
                 text = text.replace(deESC, '');
                 sheet.setValue(cell.row, cell.col, text);
-                if(node.data.name != text){
+                if(node.data[field] != text){
                     syncDatas.push({node: node, text: text, field, cell});
-                    updateDatas.push({updateType: updateType.update, findData: {ID: node.getID()}, updateData: {name: text}});
+                    updateDatas.push({updateType: updateType.update, findData: {ID: node.getID()}, updateData: {[field]: text}});
                 }
-            } else if (field === 'outputItemCharacter' || field === 'required') {
+            } else if (field === 'outputItemCharacter' || field === 'required' || field === 'isMaterial' || field === 'isDefaultOption') {
                 const val = !sheet.getValue(cell.row, cell.col);
                 sheet.setValue(cell.row, cell.col, val);
                 syncDatas.push({node: node, text: val, field, cell });
@@ -1111,7 +1383,8 @@ const billsGuidance = (function () {
         for(let i = 0; i < datas.length; i++){
             let newNodeData = {
                 libID: libID, ID: uuid.v1(), ParentID: selected ? selected.getParentID() : -1, NextSiblingID: selected ? selected.getNextSiblingID() : -1,
-                billsID: bills.tree.selected.getID()
+                billsID: bills.tree.selected.getID(),
+                outputItemCharacter: true,
             };
             //定额类型插入当前工作内容焦点行,
             if(selected && ((selected.data.type === itemType.job && datas[i].type === itemType.ration) || tobeChild)){
@@ -1136,6 +1409,7 @@ const billsGuidance = (function () {
             updateDatas.push({updateType: updateType.create, updateData: newDataIndex[i]});
         }
         updateGuideItems(updateDatas, function () {
+            const outputItemCol = guideItem.headers.findIndex(item => item.dataCode === 'outputItemCharacter');
             for(let updateData of updateDatas){
                 if(updateData.updateType === updateType.create){
                     let newNode = controller.insertByIDS(updateData.updateData.ID, updateData.updateData.ParentID, updateData.updateData.NextSiblingID);
@@ -1144,9 +1418,9 @@ const billsGuidance = (function () {
                     const row = newNode.serialNo();
                     sheet.setValue(row, 0, newNode.data.name);
                     if (newNode.data.outputItemCharacter !== undefined) {
-                        sheet.setValue(row, 1, newNode.data.outputItemCharacter);
+                        sheet.setValue(row, outputItemCol, newNode.data.outputItemCharacter);
                     }
-                    showCheckBox(sheet, [newNode]);
+                    setProcessNodes(sheet, [newNode]);
                     refreshBtn(newNode);
                 }
             }
@@ -1245,7 +1519,7 @@ const billsGuidance = (function () {
             guideItemInitSel(sheet.getActiveRowIndex());
             refreshBtn(bills.tree.selected.guidance.tree.selected);
             setNodesColor(guideItem.workBook.getActiveSheet(), bills.tree.selected.guidance.tree.items);
-            showCheckBox(guideItem.workBook.getActiveSheet(), bills.tree.selected.guidance.tree.items);
+            setProcessNodes(guideItem.workBook.getActiveSheet(), bills.tree.selected.guidance.tree.items);
             if (bills.tree.selected.data.hasGuide && !bills.tree.selected.guidance.tree.items.length) {
                 bills.tree.selected.data.hasGuide = false;
                 setBillsForeColor([bills.tree.selected]);
@@ -1265,7 +1539,7 @@ const billsGuidance = (function () {
         updateDatas.push({updateType: updateType.update, findData: {ID: selected.getParentID()}, updateData: {NextSiblingID: selected.getID()}});
         //更新选中节点
         updateDatas.push({updateType: updateType.update, findData: {ID: selected.getID()},
-            updateData: {ParentID: selected.parent.getParentID(), NextSiblingID: selected.parent.getNextSiblingID()}});
+            updateData: {ParentID: selected.parent.getParentID(), NextSiblingID: selected.parent.getNextSiblingID(), unit: '', interval: ''}});
         if(selected.nextSibling && selected.children.length > 0){
             //更新选中节点最末子节点
             let lastChild = selected.children[selected.children.length - 1];
@@ -1283,9 +1557,14 @@ const billsGuidance = (function () {
         }
         updateGuideItems(updateDatas, function () {
             controller.upLevel();
+            const sheet = guideItem.workBook.getActiveSheet();
             refreshBtn(bills.tree.selected.guidance.tree.selected);
-            setNodesColor(guideItem.workBook.getActiveSheet(), bills.tree.selected.guidance.tree.items);
-            showCheckBox(guideItem.workBook.getActiveSheet(), bills.tree.selected.guidance.tree.items);
+            setNodesColor(sheet, bills.tree.selected.guidance.tree.items);
+            setProcessNodes(sheet, bills.tree.selected.guidance.tree.items);
+            const unitCol = guideItem.headers.findIndex(item => item.dataCode === 'unit');
+            const intervalCol = guideItem.headers.findIndex(item => item.dataCode === 'interval');
+            sheet.setValue(selected.serialNo(), unitCol, '');
+            sheet.setValue(selected.serialNo(), intervalCol, '');
             $.bootstrapLoading.end();
             guideItem.workBook.focus(true)//31574
         });
@@ -1305,12 +1584,17 @@ const billsGuidance = (function () {
             updateDatas.push({updateType: updateType.update, findData: {ID: lastChild.getID()}, updateData: {NextSiblingID: selected.getID()}});
         }
         //更新选中节点
-        updateDatas.push({updateType: updateType.update, findData: {ID: selected.getID()}, updateData: {ParentID: selected.preSibling.getID(), NextSiblingID: -1}});
+        updateDatas.push({updateType: updateType.update, findData: {ID: selected.getID()}, updateData: {ParentID: selected.preSibling.getID(), NextSiblingID: -1, unit: '', interval: ''}});
         updateGuideItems(updateDatas, function () {
             controller.downLevel();
+            const sheet = guideItem.workBook.getActiveSheet();
             refreshBtn(bills.tree.selected.guidance.tree.selected);
-            setNodesColor(guideItem.workBook.getActiveSheet(), bills.tree.selected.guidance.tree.items);
-            showCheckBox(guideItem.workBook.getActiveSheet(), bills.tree.selected.guidance.tree.items);
+            setNodesColor(sheet, bills.tree.selected.guidance.tree.items);
+            setProcessNodes(sheet, bills.tree.selected.guidance.tree.items);
+            const unitCol = guideItem.headers.findIndex(item => item.dataCode === 'unit');
+            const intervalCol = guideItem.headers.findIndex(item => item.dataCode === 'interval');
+            sheet.setValue(selected.serialNo(), unitCol, '');
+            sheet.setValue(selected.serialNo(), intervalCol, '');
             $.bootstrapLoading.end();
             guideItem.workBook.focus(true)
         });
@@ -1334,7 +1618,7 @@ const billsGuidance = (function () {
             controller.upMove();
             refreshBtn(bills.tree.selected.guidance.tree.selected);
             setNodesColor(guideItem.workBook.getActiveSheet(), bills.tree.selected.guidance.tree.items);
-            showCheckBox(guideItem.workBook.getActiveSheet(), bills.tree.selected.guidance.tree.items);
+            setProcessNodes(guideItem.workBook.getActiveSheet(), bills.tree.selected.guidance.tree.items);
             $.bootstrapLoading.end();
             guideItem.workBook.focus(true)
         });
@@ -1358,7 +1642,7 @@ const billsGuidance = (function () {
             controller.downMove();
             refreshBtn(bills.tree.selected.guidance.tree.selected);
             setNodesColor(guideItem.workBook.getActiveSheet(), bills.tree.selected.guidance.tree.items);
-            showCheckBox(guideItem.workBook.getActiveSheet(), bills.tree.selected.guidance.tree.items);
+            setProcessNodes(guideItem.workBook.getActiveSheet(), bills.tree.selected.guidance.tree.items);
             $.bootstrapLoading.end();
             guideItem.workBook.focus(true)
         });
@@ -1572,7 +1856,7 @@ const billsGuidance = (function () {
             cleanData(guideItem.workBook.getActiveSheet(), guideItem.headers, -1);
             itemObj.controller.showTreeData();
             setNodesColor(guideItem.workBook.getActiveSheet(), bills.tree.selected.guidance.tree.items);
-            showCheckBox(guideItem.workBook.getActiveSheet(), bills.tree.selected.guidance.tree.items);
+            setProcessNodes(guideItem.workBook.getActiveSheet(), bills.tree.selected.guidance.tree.items);
         }, function () {
             $.bootstrapLoading.end();
         });
@@ -1752,7 +2036,7 @@ const billsGuidance = (function () {
         });
     }
 
-    // 初始化定额右键菜单
+    // 初始化清单右键菜单
     function initRationContextMenu() {
         $.contextMenu({
             selector: '#rationSpread',
@@ -1803,6 +2087,53 @@ const billsGuidance = (function () {
         });
     }
 
+    // 初始化定额右键菜单
+    function initBillsContextMenu() {
+        $.contextMenu({
+            selector: '#billsSpread',
+            build: function($triggerElement, e){
+                //控制允许右键菜单在哪个位置出现
+                let sheet = bills.workBook.getActiveSheet();;
+                let offset = $("#billsSpread").offset(),
+                    x = e.pageX - offset.left,
+                    y = e.pageY - offset.top;
+                let target = sheet.hitTest(x, y);
+                if(target.hitTestType === 3 && typeof target.row !== 'undefined' && typeof target.col !== 'undefined'){//在表格内
+                    let sel = sheet.getSelections()[0];
+                    if(sel && sel.rowCount === 1){
+                        sheet.setActiveCell(target.row, target.col);
+                    }
+                    sel = sheet.getSelections()[0];
+                    if(sel){
+                        sel.row =  sel.row === -1 ? 0 : sel.row;
+                    }
+                    //右键在多选内则不重设焦点
+                    if(!sel || sel.rowCount === 1 || !(target.row >= sel.row && target.row <= sel.row + sel.rowCount - 1)){
+                        sheet.setActiveCell(target.row, target.col);
+                    }
+                    billsInitSel(target.row, { row: bills.tree.selected.serialNo() });
+                    return {
+                        callback: function(){},
+                        items: {
+                            "replace": {
+                                name: "配置材料",
+                                disabled: function () {
+                                    return !bills.tree.selected;
+                                },
+                                icon: "fa-edit",
+                                callback: function (key, opt) {
+                                    $('#bill-material-modal').modal('show');
+                                }},
+                        }
+                    };
+                }
+                else{
+                    return false;
+                }
+            }
+        });
+    }
+
     //展开至搜索出来点的节点
     //@param {Array}nodes @return {void}
     function expandSearchNodes(sheet, nodes, roots){
@@ -1933,9 +2264,22 @@ const billsGuidance = (function () {
 
     }
 
-    //初始化个按钮点击
+    //初始化dom时间
     //@return {void}
-    function initBtn(){
+    function initDomEvents(){
+        // 清单材料窗口
+        $("#bill-material-modal").on('hidden.bs.modal', function () {
+            billMaterial.cache = [];
+            showBillMaterialData(billMaterial.workBook.getSheet(0), billMaterial.headers, billMaterial.cache, 30);
+        });
+        $("#bill-material-modal").on('shown.bs.modal', function () {
+            if (billMaterial.workBook && billMaterialHelper.workBook) {
+                billMaterialHelper.workBook.refresh();
+                billMaterial.workBook.refresh();
+                getBillMaterials();
+            }
+        });
+
         $('#insert').click(function () {
             insert([{type: itemType.job, name: '', outputItemCharacter: true }], false);
         });
@@ -1996,6 +2340,12 @@ const billsGuidance = (function () {
                 TREE_SHEET_HELPER.refreshNodesVisible(tree.roots, itemSheet, true);
             });
         });
+        // 配置材料
+        $('#editMaterial').click(function () {
+            if (bills && bills.tree && bills.tree.selected) {
+                $('#bill-material-modal').modal('show');
+            }
+        });
         // 插入选中定额
         $('#insertRation').click(function () {
             let checkedRows = getCheckedRationRows();
@@ -2227,17 +2577,125 @@ const billsGuidance = (function () {
     //初始化视图
     //@param {void} @return {void}
     function initViews(){
-        let modules = [bills, guideItem, section, ration];
+        let modules = [bills, guideItem, section, ration, billMaterial, billMaterialHelper];
         initWorkBooks(modules);
         lockUtil.lockTools($(document.body), locked);
         getLibWithBills(libID);
-        initBtn();
+        initDomEvents();
         initContextMenu();
         initRationContextMenu();
+        //initBillsContextMenu();
         initSlideSize();
     }
 
 
+    /* 生成特征分类: 前端测试使用,想要前端测试,需要在zhiyin.html中,将id=generate-classa的按钮放开 */
+    function getItemCharacterData(nodes, prefix) {
+        const processNodes = nodes.filter(node => isProcessNode(node));
+        const classGroups = []; // 同层必填选项的数组(二维数组)
+        processNodes.forEach(processNode => {
+            const classItems = [];
+            const optionNodes = processNode.children.filter(node => isOptionNode(node));
+            optionNodes.forEach(optionNode => {
+                // const name = prefix ? `${prefix}@${optionNode.data.name}` : optionNode.data.name;
+                if (optionNode.parent && optionNode.parent.data.required && (!optionNode.children 
+                    || !optionNode.children.length 
+                    || (optionNode.children[0].data && optionNode.children[0].data.rationID) 
+                    || !optionNode.children.some(node => isProcessNode(node)))) {
+                    // 必套定额
+                    const requiredRationIDs = optionNode.children && optionNode.children.length ?
+                        optionNode.children.filter(node => !!node.data.rationID).map(node => node.data.rationID) : [];
+                    classItems.push({ name: optionNode.data.name, requiredRationIDs });
+                } else {
+                    classItems.push(...getItemCharacterData(optionNode.children, optionNode.parent && optionNode.parent.data.required ? optionNode.data.name : ''));
+                }
+            });
+            if (classItems.length) {
+                classGroups.push(classItems);
+            }
+        });
+        // 拼接上一文本
+        if (classGroups[0] && classGroups[0].length) {
+            // classGroups[0] = classGroups[0].map(name => prefix ? `${prefix}@${name}` : name);
+            classGroups[0] = classGroups[0].map(item => {
+                item.name = prefix ? `${prefix}@${item.name}` : item.name
+                return item;
+            });
+        }
+        // 二维数组内容排列组合
+        while (classGroups.length > 1) {
+            const prevClassItems = classGroups[0];
+            const nextClassItems = classGroups[1];
+            const mergedClassItems = [];
+            for (let i = 0; i < prevClassItems.length; i++) {
+                for (let j = 0; j < nextClassItems.length; j++) {
+                    // 拼接文本
+                    const mergedName = `${prevClassItems[i].name}@${nextClassItems[j].name}`;
+                    // 拼接必套定额
+                    const mergedRationIDs = [...prevClassItems[i].requiredRationIDs, ...nextClassItems[j].requiredRationIDs];
+                    mergedClassItems.push({name: mergedName, requiredRationIDs: mergedRationIDs});
+                }
+            }
+            classGroups.splice(0, 2, mergedClassItems);
+        }
+        // 去重(类别别名要唯一)
+        const items = classGroups[0] || [];
+        const nameMap = {};
+        const rst = [];
+        items.forEach(item => {
+            if (!nameMap[item.name]) {
+                rst.push(item);
+            }
+            nameMap[item.name] = true;
+        });
+        return rst;
+        
+    }
+
+    // 获取选套定额:把所有分类数据的必套定额确定好了先。选套定额就是清单下所有定额除了必套的
+    function getOptionalRationIDs(itemClassData, guideNodes) {
+        // 所有必套定额
+        let requiredRationIDs = [];
+        itemClassData.forEach(item => {
+            if (item.requiredRationIDs) {
+                requiredRationIDs.push(...item.requiredRationIDs);
+            }
+        });
+        requiredRationIDs = [...new Set(requiredRationIDs)];
+        // 选套定额就是清单下所有定额除了必套的
+        const optionalRationIDs = [];
+        guideNodes.forEach(node => {
+            if (node.data.rationID && !requiredRationIDs.includes(node.data.rationID)) {
+                optionalRationIDs.push(node.data.rationID);
+            }
+        });
+        return [...new Set(optionalRationIDs)];
+    }
+
+    // 获取错套定额:清单下所有定额,除了分类对应的必套、选套定额
+    function getErrorRationIDs(requiredRationIDs, optionalRationIDs, guideNodes) {
+        const errorRationIDs = [];
+        guideNodes.forEach(node => {
+            if (node.data.rationID && !requiredRationIDs.includes(node.data.rationID) && !optionalRationIDs.includes(node.data.rationID)) {
+                errorRationIDs.push(node.data.rationID);
+            }
+        });
+        return [...new Set(errorRationIDs)];
+    }
+
+    $('#generate-class').click(() => {
+        if (bills.tree.selected && bills.tree.selected.guidance.tree) {
+            const classData = getItemCharacterData(bills.tree.selected.guidance.tree.roots);
+            const optionalRationIDs = getOptionalRationIDs(classData, bills.tree.selected.guidance.tree.items);
+            classData.forEach(item => {
+                item.errorRationIDs = getErrorRationIDs(item.requiredRationIDs, optionalRationIDs, bills.tree.selected.guidance.tree.items);
+            })
+            console.log(optionalRationIDs);
+            console.log(classData);
+        }
+    });
+
+
     return {initViews, initSlideSize};
 })();
 

+ 38 - 1
web/maintain/billsGuidance_lib/js/main.js

@@ -56,7 +56,18 @@ const billsGuidanceMain = (function () {
             <a class="lock-btn-control disabled text-danger" href="javascript:void(0);" data-toggle="modal" data-target="#del" title="删除"><i class="fa fa-remove"></i></a>
             ` : '' }
             <a class="lock" data-locked="true" href="javascript:void(0);" title="解锁"><i class="fa fa-unlock-alt"></i></a>
-            </td></tr>`;
+            </td>
+            ${ isTemporary !== 'true' ? `
+            <td style="text-align: center;">
+            <a class="btn btn-secondary btn-sm generate-class lock-btn-control disabled" href="javascript:void(0);" data-id="${lib.ID}" title="生成清单分类"><i class="fa fa-sign-in fa-rotate-90"></i>生成</a>
+            </td>
+            ` : '' }
+            ${ isTemporary !== 'true' ? `
+            <td style="text-align: center;">
+            <a class="btn btn-success btn-sm export lock-btn-control disabled" href="/billsGuidance/api/exportClassExcel?libID=${lib.ID}" download="清单类别库.xlsx" title="导出清单分类excel"><i class="fa fa-sign-out fa-rotate-270"></i>导出</a>
+            </td>
+            ` : '' }
+            </tr>`;
         tbody.append(tr);
     }
     //获取清单指引库
@@ -145,6 +156,32 @@ const billsGuidanceMain = (function () {
         $('#add').on('shown.bs.modal', function () {
             $('#createName').focus();
         });
+        // 生成清单分类
+        $('.main').find('tbody').on('click', '.generate-class', function () {
+            let tr = $(this).parent().parent();
+            let selLib = existLib({k: 'ID', v: tr.attr('id')}, guidanceLibs);
+            curLib = selLib;
+            $('#generate-class-modal').modal('show');
+            $('#generate-class-info').html(`
+            <p>确认根据库“${curLib.name}”生成清单分类?</p>
+            <p style="color: #d9534f">注意,原有分类数据将被清空</p>
+            `)
+            console.log(curLib);
+        });
+        $('#generate-class-confirm').click(async () => {
+            if (!curLib || !curLib.ID) {
+                return;
+            }
+            $.bootstrapLoading.start();
+            try {
+                await ajaxPost('/billsGuidance/api/generateClassData', { libID: curLib.ID }, 1000 * 60 * 10);
+                $('#generate-class-modal').modal('hide');
+            } catch (error) {
+                console.log(error);
+            } finally {
+                $.bootstrapLoading.end();
+            }
+        });
         //所有编辑按钮
         $('.main').find('tbody').on('click', '[data-target="#edit"]', function () {
             let tr = $(this).parent().parent();

+ 1 - 0
web/maintain/price_info_lib/js/index.js

@@ -653,6 +653,7 @@ const KEYWORD_BOOK = (() => {
     const setting = {
         header: [
             { headerName: '关键字', headerWidth: 200, dataCode: 'keyword', dataType: 'String', hAlign: 'left', vAlign: 'center' },
+            { headerName: '单位', headerWidth: 70, dataCode: 'unit', dataType: 'String', hAlign: 'center', vAlign: 'center' },
             { headerName: '关键字效果', headerWidth: 100, dataCode: 'coe', dataType: 'String', hAlign: 'center', vAlign: 'center' },
             { headerName: '组别', headerWidth: 50, dataCode: 'group', dataType: 'String', hAlign: 'center', vAlign: 'center' },
             { headerName: '选项号', headerWidth: 70, dataCode: 'optionCode', dataType: 'String', hAlign: 'center', vAlign: 'center' },

+ 1 - 0
web/maintain/ration_repository/js/coe.js

@@ -542,6 +542,7 @@ let coeOprObj = {
                 if(!result.error){
                     me.currentCoeList = result.data;
                     me.sortCoeList(me.currentCoeList);
+                    // console.log(me.currentCoeList);
                     me.currentMaxNo =  me.currentCoeList.length > 0 ? me.currentCoeList[me.currentCoeList.length - 1].serialNo : 0;
                     pageObj.showData(me.workSheet, me.setting, me.currentCoeList);
                     me.workSheet.clearSelection();

+ 124 - 0
web/maintain/report/html/adhoc_task.html

@@ -0,0 +1,124 @@
+<!DOCTYPE html>
+<html lang="zh-CN">
+
+<head>
+    <meta charset="utf-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
+    <meta http-equiv="x-ua-compatible" content="ie=edge">
+    <title>WEB导本地ACCESS库</title>
+    <link rel="stylesheet" href="/lib/bootstrap/css/bootstrap.min.css">
+    <link rel="stylesheet" href="/web/maintain/report/css/main.css">
+    <link rel="stylesheet" href="/lib/font-awesome/font-awesome.min.css">
+    <link rel="stylesheet" href="/lib/font-zy/iconfont.css">
+    <script>
+        // 这里的变量供页面调用
+        let userAccount = '<%- userAccount %>';
+        let userID = '<%- userID %>';
+    </script>
+</head>
+
+<body>
+<div class="header">
+    <nav class="navbar navbar-toggleable-lg navbar-light bg-faded p-0 ">
+        <span class="header-logo px-2">Smartcost</span>
+        <div class="navbar-text"><a>额外导出</a> > <a id="rpt_tpl_display_label">...</a></div>
+    </nav>
+    <nav class="navbar navbar-toggleable-lg justify-content-between navbar-light p-0">
+        <div>
+            <ul class="nav nav-tabs" role="tablist">
+                <li class="nav-item">
+                    <a class="nav-link px-3" ><select class="form-control form-control-sm" id="compilations" onchange="adHocTaskObj.changeCompilation(this)"></select></a>
+                </li>
+                <li class="nav-item">
+                    <a class="nav-link px-3" ><select class="form-control form-control-sm" id="rations" onchange="adHocTaskObj.changeRationRepository(this)"></select></a>
+                </li>
+            </ul>
+        </div>
+        <!--
+        -->
+    </nav>
+</div>
+<div class="main">
+    <div class="content">
+        <div class="container-fluid">
+            <div class="row">
+                <div class="form-group col-md-12">
+                    <div class="form-check">
+                        <label class="form-check-label">
+                            <input type="checkbox" class="form-check-input" id="eleRationTree" checked>
+                            导出定额章节树
+                        </label>
+                    </div>
+                </div>
+                <div class="form-group col-md-12">
+                    <div class="form-check">
+                        <label class="form-check-label">
+                            <input type="checkbox" class="form-check-input" id="eleRationItems" checked>
+                            导出定额
+                        </label>
+                    </div>
+                </div>
+                <div class="form-group col-md-12">
+                    <div class="form-check">
+                        <label class="form-check-label">
+                            <input type="checkbox" class="form-check-input" id="eleAssistRationItems" checked>
+                            导出辅助定额
+                        </label>
+                    </div>
+                </div>
+                <div class="form-group col-md-12">
+                    <div class="form-check">
+                        <label class="form-check-label">
+                            <input type="checkbox" class="form-check-input" id="eleAllGLJ" checked>
+                            导出工料机
+                        </label>
+                    </div>
+                </div>
+                <div class="form-group col-md-12">
+                    <div class="form-check">
+                        <label class="form-check-label">
+                            <input type="checkbox" class="form-check-input" id="eleCOE" disabled>
+                            导出COE
+                        </label>
+                    </div>
+                </div>
+                <div class="tab-bar">
+                    <a onclick="adHocTaskObj.createDesktopMdb()" class="btn btn-secondary fa fa-plus-square">数据转换</a>
+                </div>
+            </div>
+        </div>
+    </div>
+</div>
+
+<script src="/lib/jquery/jquery.min.js"></script>
+<script src="/lib/tether/tether.min.js"></script>
+<script src="/lib/bootstrap/bootstrap.min.js"></script>
+
+<script src="/web/maintain/report/js/global.js"></script>
+<script type="text/javascript" src="/public/web/common_ajax.js"></script>
+
+<script src="/web/maintain/report/js/adhoc_task.js"></script>
+
+
+<!-- JS.
+-->
+<!-- zTree
+<script type="text/javascript" src="/public/web/date_util.js"></script>
+<script type="text/javascript" src="/lib/ztree/jquery.ztree.core.js"></script>
+<script type="text/javascript" src="/lib/ztree/jquery.ztree.excheck.js"></script>
+<script type="text/javascript" src="/lib/ztree/jquery.ztree.exedit.js"></script>
+<script type="text/javascript" src="/public/web/storageUtil.js"></script>
+<script type="text/javascript" src="/public/web/rpt_tpl_def.js"></script>
+<script type="text/javascript" src="/public/web/treeDataHelper.js"></script>
+<script type="text/javascript" src="/public/web/ztree_common.js"></script>
+<script type="text/javascript" src="/public/web/rpt_value_define.js"></script>
+<script type="text/javascript" src="/public/web/string_util_light.js"></script>
+-->
+
+</body>
+<script type="text/javascript">
+    autoFlashHeight();
+    adHocTaskObj.iniPage();
+</script>
+
+</html>

+ 54 - 0
web/maintain/report/js/adhoc_task.js

@@ -0,0 +1,54 @@
+'use strict'
+
+/**
+ * Created by Tony on 2021/11/3.
+ */
+
+let adHocTaskObj = {
+    currentRationRepId: '',
+    currentRationLibId: '',
+    iniPage: function() {
+        this._getCompilationList();
+    },
+    _getCompilationList: function(){
+        let me = adHocTaskObj, params = {};
+        CommonAjax.postEx("report_tpl_api/getCompilationList", params, 20000, true, function(result){
+                for (let item of result) {
+                    $("#compilations").append("<option value='" + item._id + "'>" + item.name + "</option>");
+                }
+                me.changeCompilation($("#compilations").get(0));
+                me.currentRationRepId = $("#compilations").get(0).value;
+            }, null, null
+        );
+    },
+    changeCompilation: function(dom) {
+        $("#rations").empty();
+        ///rationRepository/api
+        let compilationId = dom.value;
+        CommonAjax.post('/rationRepository/api/getRationLibsByCompilation', {compilationId: compilationId}, function (rstData) {
+            $('#rations').empty();
+            for(let rationLib of rstData){
+                let opt = `<option value="${rationLib.ID}">${rationLib.dispName}</option>`;
+                $('#rations').append(opt);
+            }
+            adHocTaskObj.currentRationLibId = $("#rations").get(0).value;
+        });
+    },
+    changeRationRepository: function(dom) {
+        adHocTaskObj.currentRationLibId = dom.value;
+    },
+    createDesktopMdb: function() {
+        let params = {};
+        params.compilationId = adHocTaskObj.currentRationRepId;
+        params.rationLibId = adHocTaskObj.currentRationLibId;
+        params.hasRationTree = $("#eleRationTree").get(0).checked;
+        params.hasRation = $("#eleRationItems").get(0).checked;
+        params.hasAssistRation = $("#eleAssistRationItems").get(0).checked;
+        params.hasGLJ = $("#eleAllGLJ").get(0).checked;
+        params.hasCOE = $("#eleCOE").get(0).checked;
+        CommonAjax.postEx('/adHoc_report_api/createDesktopMdb', params, 10000, false, function (result) {
+            console.log(`result: `);
+            console.log(result);
+        });
+    }
+};

+ 2 - 1
web/maintain/report/js/rpt_tpl_pre_handle.js

@@ -119,6 +119,7 @@ const exposed_ration_properties = [
 ];
 const exposed_ration_glj_properties = [
     {Name: "定额人材机_ID", Title: "", Key: "ID"}
+    ,{Name: "定额人材机_项目人材机映射ID", Title: "", Key: "projectGLJID", Order: "ascend"}
     ,{Name: "定额人材机_所属定额ID", Title: "", Key: "rationID", Order: "ascend"}
     ,{Name: "定额人材机_所属清单ID", Title: "", Key: "billsItemID", Order: "ascend"}
     ,{Name: "定额人材机_所属工程ID", Title: "", Key: "projectID", Order: "ascend"}
@@ -130,7 +131,7 @@ const exposed_ration_glj_properties = [
     ,{Name: "定额人材机_三材类别", Title: "", Key: "ref_join(projectGLJID,projectGLJ,id).materialType", Order: "ascend", individualType: fixed_material_types}
 ];
 const exposed_prj_glj_properties = [
-    {Name: "项目人材机_ID", Title: "", Key: "ID"}
+    {Name: "项目人材机_ID", Title: "", Key: "id"}
     ,{Name: "项目人材机_所属工程ID", Title: "", Key: "projectID", Order: "ascend"}
     ,{Name: "项目人材机_代码", Title: "", Key: "code", Order: "ascend"}
     ,{Name: "项目人材机_类型", Title: "", Key: "type", Order: "ascend", individualType: fixed_glj_types}