浏览代码

Merge branch 'master' of http://smartcost.f3322.net:3000/SmartCost/ConstructionOperation

Conflicts:
	modules/ration_repository/models/ration_item.js
zhongzewei 7 年之前
父节点
当前提交
1324459f48
共有 32 个文件被更改,包括 1189 次插入167 次删除
  1. 23 0
      modules/common/const/bills_type.js
  2. 165 57
      modules/ration_repository/controllers/ration_repository_controller.js
  3. 189 2
      modules/ration_repository/models/ration_item.js
  4. 1 1
      modules/ration_repository/models/repository_map.js
  5. 4 0
      modules/ration_repository/routes/ration_rep_routes.js
  6. 12 8
      modules/std_glj_lib/models/gljModel.js
  7. 2 0
      modules/users/controllers/compilation_controller.js
  8. 3 3
      modules/users/models/bills_template_model.js
  9. 2 0
      modules/users/models/schemas/bills_template.js
  10. 2 0
      package.json
  11. 6 2
      public/counter/counter.js
  12. 97 2
      web/maintain/ration_repository/js/main.js
  13. 60 1
      web/maintain/ration_repository/main.html
  14. 110 0
      web/maintain/report/js/cfg_const.js
  15. 1 1
      web/maintain/report/js/rpt_tpl_data_map.js
  16. 4 1
      web/maintain/report/js/rpt_tpl_helper.js
  17. 1 0
      web/maintain/report/js/rpt_tpl_main.js
  18. 381 0
      web/maintain/report/js/rpt_tpl_pre_handle.js
  19. 4 0
      web/maintain/report/rpt_tpl_detail.html
  20. 0 10
      web/maintain/report/rpt_tpl_detail_field_location_layout_detail.html
  21. 0 22
      web/maintain/report/rpt_tpl_detail_field_location_selectableFields.html
  22. 0 35
      web/maintain/report/rpt_tpl_detail_field_location_tab.html
  23. 38 10
      web/maintain/report/rpt_tpl_detail_pre_handle.html
  24. 2 0
      web/maintain/report/rpt_tpl_detail_pre_handle_adddummy.html
  25. 2 0
      web/maintain/report/rpt_tpl_detail_pre_handle_adjust.html
  26. 2 0
      web/maintain/report/rpt_tpl_detail_pre_handle_filter.html
  27. 37 0
      web/maintain/report/rpt_tpl_detail_pre_handle_sort.html
  28. 2 0
      web/maintain/report/rpt_tpl_detail_pre_handle_summary.html
  29. 1 0
      web/maintain/report/rpt_tpl_main.html
  30. 32 6
      web/users/js/template.js
  31. 1 1
      web/users/views/compilation/engineering.html
  32. 5 5
      web/users/views/compilation/template.html

+ 23 - 0
modules/common/const/bills_type.js

@@ -0,0 +1,23 @@
+/**
+ * Created by Mai on 2017/8/29.
+ * 清单类别
+ */
+
+const typeFlag = {
+    //大项费用
+    DXFY: 1,
+    //分部
+    FB: 2,
+    //分项
+    FX: 3,
+    //清单    
+    BILLS: 4
+}
+const typeFlagList = [
+    {name: '大项费用', value: typeFlag.DXFY},
+    {name: '分部', value: typeFlag.FB},
+    {name: '分项', value: typeFlag.FX},
+    {name: '清单', value: typeFlag.BILLS}
+];
+
+export {typeFlag as default, typeFlagList as List};

+ 165 - 57
modules/ration_repository/controllers/ration_repository_controller.js

@@ -9,59 +9,66 @@ import async from "async";
 var callback = function(req, res, err, message, data){
     res.json({error: err, message: message, data: data});
 };
+// 上传控件
+const multiparty = require("multiparty");
+const fs = require("fs");
+// excel解析
+const excel = require("node-xlsx");
+const rationItem = require("../models/ration_item");
 
-class RationRepositoryController extends baseController{
-   async getCompilationList(req, res){
-       try{
-           let compilationModel = new CompilationModel(), rst = [];
-           let compilationList = await compilationModel.getCompilationList();
-           if(compilationList.length <= 0){
-               throw '没有数据';
-           }
-           else{
-
-               compilationList.forEach(function (compilation) {
-                   rst.push({_id: compilation._id, name: compilation.name});
-               });
-               //获得相关编办下的工料机库
-               let parallelFucs = [];
-               for(let i = 0; i < rst.length; i++){
-                   parallelFucs.push((function (obj) {
-                       return function (cb) {
-                           gljMapModel.find({compilationId: obj._id, deleted: false}, function (err, result) {
-                               if(err){
-                                   cb(err);
-                               }
-                               else{
-                                   cb(null, result);
-                               }
-                           })
-                       }
-                   })(rst[i]));
-               }
-               async.parallel(parallelFucs, function (err, result) {
-                   if(err){
-                       callback(req, res, err, '没有数据', null);
-                   }
-                   else{
-                       let gljLibsRst = [];
-                       for(let i = 0; i < result.length; i++){
-                           for(let j = 0; j < result[i].length; j++){
-                               gljLibsRst.push(result[i][j]);
-                           }
-                       }
-                       callback(req, res, false, '', {compilation: rst, gljLibs: gljLibsRst});
-                   }
-               })
-           }
-       }
-        catch(err) {
+class RationRepositoryController extends baseController {
+    async getCompilationList(req, res) {
+        try {
+            let compilationModel = new CompilationModel(), rst = [];
+            let compilationList = await compilationModel.getCompilationList();
+            if (compilationList.length <= 0) {
+                throw '没有数据';
+            }
+            else {
+
+                compilationList.forEach(function (compilation) {
+                    rst.push({_id: compilation._id, name: compilation.name});
+                });
+                //获得相关编办下的工料机库
+                let parallelFucs = [];
+                for (let i = 0; i < rst.length; i++) {
+                    parallelFucs.push((function (obj) {
+                        return function (cb) {
+                            gljMapModel.find({compilationId: obj._id, deleted: false}, function (err, result) {
+                                if (err) {
+                                    cb(err);
+                                }
+                                else {
+                                    cb(null, result);
+                                }
+                            })
+                        }
+                    })(rst[i]));
+                }
+                async.parallel(parallelFucs, function (err, result) {
+                    if (err) {
+                        callback(req, res, err, '没有数据', null);
+                    }
+                    else {
+                        let gljLibsRst = [];
+                        for (let i = 0; i < result.length; i++) {
+                            for (let j = 0; j < result[i].length; j++) {
+                                gljLibsRst.push(result[i][j]);
+                            }
+                        }
+                        callback(req, res, false, '', {compilation: rst, gljLibs: gljLibsRst});
+                    }
+                })
+            }
+        }
+        catch (err) {
             callback(req, res, err, '没有数据', null);
         }
     }
-    addRationRepository(req,res){
+
+    addRationRepository(req, res) {
         var rationObj = JSON.parse(req.body.rationRepObj);
-        rationRepository.addRationRepository(rationObj,function(err,data){
+        rationRepository.addRationRepository(rationObj, function (err, data) {
             if (data) {
                 callback(req, res, err, "has data", data);
             } else {
@@ -74,24 +81,26 @@ class RationRepositoryController extends baseController{
         let libId = data.libId;
         rationRepository.getRationLib(libId, function(err, data){
             if (data) {
-                callback(req, res, err, "has data",data);
+                callback(req, res, err, "has data", data);
             } else {
                 callback(req, res, err, "no data", null);
             }
         });
     }
-    getDisPlayRationLibs(req, res){
-        rationRepository.getDisplayRationLibs(function(err, data){
+
+    getDisPlayRationLibs(req, res) {
+        rationRepository.getDisplayRationLibs(function (err, data) {
             if (data) {
-                callback(req, res, err, "has data",data);
+                callback(req, res, err, "has data", data);
             } else {
                 callback(req, res, err, "no data", null);
             }
         });
     }
-    getRealLibName(req,res){
+
+    getRealLibName(req, res) {
         var libName = req.body.rationName;
-        rationRepository.getRealLibName(libName,function(err,data){
+        rationRepository.getRealLibName(libName, function (err, data) {
             if (data) {
                 callback(req, res, err, "has data", data);
             } else {
@@ -99,8 +108,9 @@ class RationRepositoryController extends baseController{
             }
         })
     }
-    getLibIDByName(req,res){
-        rationRepository.getLibIDByName(req.body.libName, function(err,data){
+
+    getLibIDByName(req, res) {
+        rationRepository.getLibIDByName(req.body.libName, function (err, data) {
             if (data) {
                 callback(req, res, err, "has ID", data);
             } else {
@@ -109,7 +119,7 @@ class RationRepositoryController extends baseController{
         })
     }
 
-    deleteRationLib(req,res){
+    deleteRationLib(req, res) {
         let oprtor = req.body.oprtor,
             libId = req.body.libId;
         rationRepository.deleteRationLib(oprtor, libId, function (err, message) {
@@ -124,6 +134,104 @@ class RationRepositoryController extends baseController{
             callback(req, res, err, message, null);
         })
     }
+
+    /**
+     * 上传定额库原始数据
+     *
+     * @param {Object} request
+     * @param {Object} response
+     * @return {void}
+     */
+    async uploadSourceData(request, response) {
+        let responseData = {
+            err: 0,
+            msg: ''
+        };
+        const allowHeader = ['application/vnd.ms-excel', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'];
+        try {
+            const uploadOption = {
+                uploadDir: './public/tmp'
+            };
+            const form = new multiparty.Form(uploadOption);
+            form.parse(request, async function(err, fields, files) {
+                const rationRepId = fields.rationRepId !== undefined && fields.rationRepId.length > 0 ?
+                    fields.rationRepId[0] : 0;
+                const type = fields.type !== undefined && fields.type.length > 0 ?
+                    fields.type[0] : '';
+                if (rationRepId <= 0 || type === '') {
+                    throw '参数错误';
+                }
+                const file = files.file !== undefined ? files.file[0] : null;
+                if (err || file === null) {
+                    throw '上传失败';
+                }
+                // 判断类型
+                if (file.headers['content-type'] === undefined || allowHeader.indexOf(file.headers['content-type']) < 0) {
+                    throw '不支持该类型';
+                }
+                // 重命名文件名
+                const uploadFullName = uploadOption.uploadDir + '/' + file.originalFilename;
+                fs.renameSync(file.path, uploadFullName);
+
+                const sheet = excel.parse(uploadFullName);
+                if (sheet[0] === undefined || sheet[0].data === undefined) {
+                    throw 'excel没有对应数据';
+                }
+                const result = type === 'source_file' ?
+                    await rationItem.batchAddFromExcel(rationRepId, sheet[0].data) :
+                    await rationItem.batchUpdateSectionIdFromExcel(sheet[0].data);
+
+                if (rationItem.failGLJList.length > 0) {
+                    responseData.msg = rationItem.failGLJList.join("\r\n");
+                }
+                // 删除文件
+                if (result) {
+                    fs.unlink(uploadFullName);
+                }
+                response.json(responseData);
+            });
+        } catch (error) {
+            responseData.err = 1;
+            responseData.msg = error;
+            response.json(responseData);
+        }
+
+        return;
+    }
+
+    /**
+     * 导出内容数据
+     *
+     * @param {Object} request
+     * @param {Object} response
+     * @return {void}
+     */
+    async exportRationData(request, response) {
+        let rationRepId = request.query.rationRepId;
+        rationRepId = parseInt(rationRepId);
+        try {
+            if (isNaN(rationRepId) || rationRepId <= 0) {
+                throw '参数错误';
+            }
+            const excelData = await rationItem.exportExcelData(rationRepId);
+            const buffer = excel.build([{name: "sheet", data: excelData}]);
+            const filePath = './public/tmp/export.xlsx';
+            fs.writeFileSync(filePath, buffer, 'binary');
+            const stats = fs.statSync(filePath);
+            // 下载相关header
+            response.set({
+                'Content-Type': 'application/octet-stream',
+                'Content-Disposition': 'attachment; filename=ration.xlsx',
+                'Content-Length': stats.size
+            });
+            fs.createReadStream(filePath).pipe(response);
+            fs.unlink(filePath);
+        } catch (error) {
+            response.end(error);
+        }
+
+    }
+
 }
 
 export default RationRepositoryController;

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

@@ -9,6 +9,7 @@ let gljDao = require('./glj_repository');
 let rationRepositoryDao = require('./repository_map');
 const scMathUtil = require('../../../public/scMathUtil').getUtil();
 import {rationItemModel, compleRationModel} from './schemas';
+import STDGLJListModel from '../../std_glj_lib/models/gljModel';
 
 var rationItemDAO = function(){};
 
@@ -210,6 +211,7 @@ function round(v,e){
 
 
 rationItemDAO.prototype.updateRationBasePrc = function (basePrcArr, callback) {
+
     async.each(basePrcArr, function (basePrcObj, finalCb) {
         let adjGljId = basePrcObj.gljId, adjBasePrice = basePrcObj.basePrice, adjGljType = basePrcObj.gljType;
         async.waterfall([
@@ -484,7 +486,192 @@ rationItemDAO.prototype.updateAnnotation = function (lastOpr, repId, updateArr,
             }
         });
     });
-}
+};
+
+/**
+ * 根据条件获取定额数据
+ *
+ * @param {Object} condition
+ * @param {Object} fields
+ * @param {String} indexBy
+ * @return {Promise|Array}
+ */
+rationItemDAO.prototype.getRationItemByCondition = async function (condition, fields = null, indexBy = null) {
+    let result = [];
+    if (Object.keys(condition).length <= 0) {
+        return result;
+    }
+    result = await rationItemModel.find(condition, fields);
+    if (indexBy !== null && result.length > 0) {
+        let tmpResult = {};
+        for(let tmp of result) {
+            tmpResult[tmp[indexBy]] = tmp;
+        }
+        result = tmpResult;
+    }
+    return result;
+};
+
+/**
+ * 从excel中批量新增数据
+ *
+ * @param {Number} rationRepId
+ * @param {Array} data
+ * @return {bool}
+ */
+rationItemDAO.prototype.batchAddFromExcel = async function(rationRepId, data) {
+    if (data.length <= 0) {
+        return false;
+    }
+    // 获取定额库相关数据
+    const rationRepository = await rationRepositoryDao.getRepositoryById(rationRepId);
+    if (rationRepository.length !== 1 || rationRepository[0].gljLib === undefined) {
+        return false;
+    }
+    // 获取标准工料机库数据
+    const stdBillsLibListsModel = new STDGLJListModel();
+    const stdGLJData = await stdBillsLibListsModel.getGljItemsByRep(rationRepository[0].gljLib);
+    // 整理标准工料机库数据
+    let stdGLJList = {};
+    for (const tmp of stdGLJData) {
+        stdGLJList[tmp.code.toString()] = tmp.ID;
+    }
+    let lastData = {};
+    const rationData = [];
+    // 编码列表,用于查找库中是否有对应数据
+    let rationCodeList = [];
+    let gljCodeList = [];
+    // 插入失败的工料机列表(用于提示)
+    this.failGLJList = [];
+    for (const tmp of data) {
+        if (tmp.length <= 0) {
+            continue;
+        }
+        // 如果第一个字段为null则是工料机数据,放入上一个数据的工料机字段
+        if (tmp[0] === undefined && Object.keys(lastData).length > 0) {
+            // 如果不存在对应的工料机库数据则跳过
+            if (stdGLJList[tmp[1]] === undefined) {
+                const failString = '定额' + lastData.code + '下的' + tmp[1];
+                this.failGLJList.push(failString);
+                continue;
+            }
+            const tmpRationGlj = {
+                gljId: stdGLJList[tmp[1]],
+                consumeAmt: tmp[4],
+                proportion: 0,
+            };
+            lastData.rationGljList.push(tmpRationGlj);
+            if (gljCodeList.indexOf(tmp[1]) < 0) {
+                gljCodeList.push(tmp[1]);
+            }
+            continue;
+        }
+
+        if (tmp[0] === '定额' && Object.keys(lastData).length > 0) {
+            rationData.push(lastData);
+        }
+
+        // 组装数据
+        lastData = {
+            code: tmp[1],
+            name: tmp[2],
+            unit: tmp[3],
+            caption: tmp[2],
+            rationRepId: rationRepId,
+            sectionId: 0,
+            rationGljList: []
+        };
+        // 防止重复加入
+        if (rationCodeList.indexOf(tmp[1]) < 0) {
+            rationCodeList.push(tmp[1]);
+        }
+    }
+    // 最后一个入数组
+    rationData.push(lastData);
+
+    // 查找数据库中是否已存在待插入数据
+    const condition = {
+        rationRepId: rationRepId,
+        code: { $in: rationCodeList }
+    };
+    const existCodeList = await this.getRationItemByCondition(condition, ['code'], 'code');
+    // 过滤插入数据
+    let insertData = [];
+    for (const ration of rationData) {
+        if (existCodeList[ration.code] !== undefined) {
+            continue;
+        }
+        insertData.push(ration);
+    }
+    // 如果都已经存在,直接返回
+    if (insertData.length <= 0) {
+        return true;
+    }
+    // 组织id
+    const counterInfo = await counter.counterDAO.getIDAfterCount(counter.moduleName.rations, insertData.length);
+    let maxId = counterInfo.value.sequence_value;
+    maxId = parseInt(maxId);
+
+    let count = 0;
+    for (const index in insertData) {
+        insertData[index].ID = maxId - (insertData.length - 1) + count;
+        count++;
+    }
+    // 插入数据库
+    const result = await rationItemModel.create(insertData);
+    return result.length > 0;
+};
+
+/**
+ * 导出到excel的数据
+ *
+ * @param {Number} rationRepId
+ * @return {Array}
+ */
+rationItemDAO.prototype.exportExcelData = async function(rationRepId) {
+    const condition = {
+        rationRepId: rationRepId
+    };
+    // @todo 限制导出的数量以防内存溢出
+    const rationDataList = await this.getRationItemByCondition(condition, ['name', 'code', 'ID']);
+    // 整理数据
+    let rationData = [];
+    for (const tmp of rationDataList) {
+        const ration = [null, tmp.ID, tmp.code, tmp.name];
+        rationData.push(ration);
+    }
+    const excelData = [['树ID', '定额ID', '定额编码', '定额名']];
+    excelData.push.apply(excelData, rationData);
+
+    return excelData;
+};
+
+/**
+ * 批量更改章节id
+ *
+ * @param {Object} data
+ * @return {bool}
+ */
+rationItemDAO.prototype.batchUpdateSectionIdFromExcel = async function(data) {
+    if (data.length <= 0) {
+        return false;
+    }
+    // 批量执行update
+    const bulk = rationItemModel.collection.initializeOrderedBulkOp();
+    for (const tmp of data) {
+        let rationId = parseInt(tmp[1]);
+        rationId = isNaN(rationId) || rationId <= 0 ? 0 : rationId;
+        let sectionId = parseInt(tmp[0]);
+        sectionId = isNaN(sectionId) || sectionId <= 0 ? 0 : sectionId;
+        if (sectionId <= 0 || rationId <= 0) {
+            continue;
+        }
+        bulk.find({"ID": rationId}).update({$set: { sectionId: sectionId }});
+    }
+
+    const result = await bulk.execute();
+    return result.isOk();
+};
 
-module.exports = new rationItemDAO()
+module.exports = new rationItemDAO();
 

+ 1 - 1
modules/ration_repository/models/repository_map.js

@@ -108,7 +108,7 @@ rationRepositoryDao.prototype.getLibIDByName = function(dispName, callback){
     });
 };
 
-rationRepositoryDao.prototype.getRepositoryById = function(repId,callback){
+rationRepositoryDao.prototype.getRepositoryById = function(repId,callback = null){
     if (callback) {
         rationRepository.find({"ID": repId}, function(err,data){
             if (err) {

+ 4 - 0
modules/ration_repository/routes/ration_rep_routes.js

@@ -73,5 +73,9 @@ module.exports =  function (app) {
     apiRouter.post('/getRationItem',searchController.auth, searchController.init, searchController.getRationItem);
     apiRouter.post('/findRation', searchController.auth, searchController.init, searchController.findRation);
 
+    // 导入导出定额库相关
+    apiRouter.post('/upload', rationRepositoryController.auth, rationRepositoryController.init, rationRepositoryController.uploadSourceData);
+    apiRouter.get('/export', rationRepositoryController.auth, rationRepositoryController.init, rationRepositoryController.exportRationData);
+
     app.use("/rationRepository/api",apiRouter);
 }

+ 12 - 8
modules/std_glj_lib/models/gljModel.js

@@ -67,15 +67,19 @@ class GljDao  extends OprDao{
         }
     }
 
-    getGljItemsByRep(repositoryId,callback){
+    getGljItemsByRep(repositoryId,callback = null){
         let me = this;
-        gljModel.find({"repositoryId": repositoryId},function(err,data){
-            if(err) callback(true, "")
-            else {
-                me.sortToNumber(data);
-                callback(false,data);
-            }
-        })
+        if (callback === null) {
+            return gljModel.find({"repositoryId": repositoryId});
+        } else {
+            gljModel.find({"repositoryId": repositoryId},function(err,data){
+                if(err) callback(true, "")
+                else {
+                    me.sortToNumber(data);
+                    callback(false,data);
+                }
+            })
+        }
     }
 
     getGljItemByType (repositoryId, type, callback){

+ 2 - 0
modules/users/controllers/compilation_controller.js

@@ -14,6 +14,7 @@ import STDFeeRateLibsModel from "../../common/std/std_fee_rate_libs_model";
 import {default as EngineeringConst, List as EngineeringList} from "../../common/const/engineering";
 import BillsTemplateModel from "../models/bills_template_model";
 import {default as BillsFixedFlagConst, List as BillsFixedFlagList} from "../../common/const/bills_fixed.js";
+import {default as BillsTypeFlagConst, List as BillsTypeFlagList} from "../../common/const/bills_type.js";
 import EngineeringLibModel from "../models/engineering_lib_model";
 import STDLabourCoesModel from "../../common/std/std_labour_coes_model";
 import STDCalcProgramModel from "../../common/std/std_calc_program_model";
@@ -482,6 +483,7 @@ class CompilationController extends BaseController {
             billList: JSON.stringify(billList),
             billsTemplateData: JSON.stringify(billsTemplateData),
             billsFixedFlagList: JSON.stringify(BillsFixedFlagList),
+            billsTypeFlagList: JSON.stringify(BillsTypeFlagList),
             valuationData: valuationData,
             valuationList: valuationList,
             selectedCompilation: selectedCompilation,

+ 3 - 3
modules/users/models/bills_template_model.js

@@ -25,9 +25,9 @@ class BillsTemplateModel extends BaseModel {
      */
     async getTemplateData (valuationId, engineering) {
         // 筛选字段
-        let field = {_id: 1, valuationId: 1, ID: 1, ParentID: 1, NextSiblingID: 1, code: 1, name: 1, unit: 1, flags: 1};
+        let field = {_id: 1, valuationId: 1, ID: 1, ParentID: 1, NextSiblingID: 1, code: 1, name: 1, unit: 1, flags: 1, type: 1};
         let data = await this.findDataByCondition({valuationId: valuationId, engineering: engineering}, field, false);
-
+        console.log(data);
         return data === null ? [] : data;
     }
 
@@ -39,7 +39,7 @@ class BillsTemplateModel extends BaseModel {
      */
     async getTemplateDataForNewProj (valuationId, engineering) {
         // 筛选字段
-        let field = {_id: 0, ID: 1, ParentID: 1, NextSiblingID: 1, code: 1, name: 1, unit: 1, flags: 1};
+        let field = {_id: 0, ID: 1, ParentID: 1, NextSiblingID: 1, code: 1, name: 1, unit: 1, flags: 1, type: 1};
         let data = await this.findDataByCondition({valuationId: valuationId, engineering: engineering}, field, false);
 
         return data === null ? [] : data;

+ 2 - 0
modules/users/models/schemas/bills_template.js

@@ -22,6 +22,8 @@ let BillsTemplateSchema = {
     name: String,
     // 单位
     unit: String,
+    // 类别
+    type: Number,
     // 标记
     flags:{
         type: [flagsSchema],

+ 2 - 0
package.json

@@ -25,6 +25,8 @@
     "bluebird": "^3.5.0",
     "jszip": "^3.1.3",
     "log4js": "~2.3.3",
+    "multiparty": "^4.1.3",
+    "node-xlsx": "^0.11.2",
     "pdfkit": "^0.8.2",
     "ueditor": "^1.2.3"
   },

+ 6 - 2
public/counter/counter.js

@@ -45,14 +45,18 @@ var counterDAO = function(){};
  *     result.value.sequence_value Ϊ�޸ĺ��id
  * }
  */
-counterDAO.prototype.getIDAfterCount = function(moduleName, stepCount, callback) {
+counterDAO.prototype.getIDAfterCount = async function(moduleName, stepCount, callback = null) {
     var sc = stepCount;
     if (isNaN(stepCount) || (stepCount < 0)) {
         sc = 1;
     } else if (!(/^-?\d+$/.test(stepCount))) {
         sc = Math.round(stepCount + 0.5);
     }
-    counterModel.findAndModify({_id: moduleName}, [], { $inc: { sequence_value: sc } }, {'new':true}, callback);
+    if (callback === null) {
+        return await counterModel.findAndModify({_id: moduleName}, [], { $inc: { sequence_value: sc } }, {'new':true});
+    } else {
+        counterModel.findAndModify({_id: moduleName}, [], { $inc: { sequence_value: sc } }, {'new':true}, callback);
+    }
 }
 
 counterDAO.prototype.getCurrentID = function(moduleName, callback) {

+ 97 - 2
web/maintain/ration_repository/js/main.js

@@ -76,6 +76,93 @@ $(function () {
 
     });
     getCompilationList();
+
+    // 导入原始数据按钮
+    let rationRepId = 0;
+    $("#showArea").on("click", ".import-source", function () {
+        let id = $(this).data("id");
+        id = parseInt(id);
+        if (isNaN(id) || id <= 0) {
+            return false;
+        }
+        rationRepId = id;
+        $("#import").modal("show");
+    });
+    // 导入内部数据
+    $("#showArea").on("click", ".import-data", function () {
+        let id = $(this).data("id");
+        id = parseInt(id);
+        if (isNaN(id) || id <= 0) {
+            return false;
+        }
+        rationRepId = id;
+        $("#import2").modal("show");
+    });
+
+    // 导入原始数据确认
+    $("#source-import,#data-import").click(function() {
+        const self = $(this);
+        const type = self.is("#source-import") ? 'source_file' : 'import_data';
+        const dialog = type === 'source_file' ? $("#import") : $("#import2");
+        try {
+            let formData = new FormData();
+            let file = $("input[name='"+ type +"']")[0];
+            if (file.files.length <= 0) {
+                throw '请选择文件!';
+            }
+            formData.append('file', file.files[0]);
+            // 获取定额库id
+            if (rationRepId <= 0) {
+                return false;
+            }
+            formData.append('rationRepId', rationRepId);
+            formData.append('type', type);
+            $.ajax({
+                url: '/rationRepository/api/upload',
+                type: 'POST',
+                data: formData,
+                cache: false,
+                contentType: false,
+                processData: false,
+                beforeSend: function() {
+                    self.attr('disabled', 'disabled');
+                    self.text('上传中...');
+                },
+                success: function(response){
+                    self.removeAttr('disabled');
+                    self.text('确定导入');
+                    if (response.err === 0) {
+                        const message = response.msg !== undefined ? response.msg : '';
+                        if (message !== '') {
+                            alert(message);
+                        }
+                        // 成功则关闭窗体
+                        dialog.modal("hide");
+                    } else {
+                        const message = response.msg !== undefined ? response.msg : '上传失败!';
+                        alert(message);
+                    }
+                },
+                error: function(){
+                    alert("与服务器通信发生错误");
+                    self.removeAttr('disabled');
+                    self.text('确定导入');
+                }
+            });
+        } catch(error) {
+            alert(error);
+        }
+    });
+
+    // 导出数据
+    $("#showArea").on("click", ".export", function () {
+        let id = $(this).data("id");
+        id = parseInt(id);
+        if (isNaN(id) || id <= 0) {
+            return false;
+        }
+        window.location.href = '/rationRepository/api/export?rationRepId=' + id;
+    });
 });
 
 function getAllRationLib(callback){
@@ -103,7 +190,11 @@ function getAllRationLib(callback){
                         "<td>"+createDate+" </td>" +
                         "<td><a href='javascript:void(0);' data-toggle='modal' data-target='#edit' title='编辑'>" +
                         "<i class='fa fa-pencil-square-o'></i></a> <a href='javascript:void(0);' data-toggle='modal' data-target='#del' class='text-danger' title='删除'>" +
-                        "<i class='fa fa-remove'></i></a></td></tr>");
+                        "<i class='fa fa-remove'></i></a></td>" +
+                        "<td><a class='btn btn-secondary btn-sm import-source' href='javacript:void(0);' data-id='"+ id +"' title='导入原始数据'><i class='fa fa-sign-in fa-rotate-90'></i>导入</a></td>" +
+                        "<td><a class='btn btn-success btn-sm export' href='javacript:void(0);' data-toggle='modal' data-id='"+ id +"' data-target='#emport' title='导出内部数据'><i class='fa fa-sign-out fa-rotate-270'></i>导出</a> " +
+                        "<a class='btn btn-secondary btn-sm import-data' href='javacript:void(0);' data-id='"+ id +"' title='导入内部数据'><i class='fa fa-sign-in fa-rotate-90'></i>导入</a></td>" +
+                        "</tr>");
                     var newHref = "/rationRepository/ration?repository="+id;
                     $("#tempId td:first a").attr("href", newHref);
                     $("#tempId").attr("id", id);
@@ -188,7 +279,11 @@ function createRationLib(rationObj, dispNamesArr){
                     "<td>"+createDate+" </td>" +
                     "<td><a href='javascript:void(0);' data-toggle='modal' data-target='#edit' title='编辑'>" +
                     "<i class='fa fa-pencil-square-o'></i></a> <a href='javascript:void(0);' data-toggle='modal' data-target='#del' class='text-danger' title='删除'>" +
-                    "<i class='fa fa-remove'></i></a></td></tr>");
+                    "<i class='fa fa-remove'></i></a>" +
+                    "<td><a class='btn btn-secondary btn-sm import-source' href='javacript:void(0);' data-id='"+ id +"' title='导入原始数据'><i class='fa fa-sign-in fa-rotate-90'></i>导入</a></td>" +
+                    "<td><a class='btn btn-success btn-sm export' href='javacript:void(0);' data-toggle='modal' data-id='"+ id +"' data-target='#emport' title='导出内部数据'><i class='fa fa-sign-out fa-rotate-270'></i>导出</a> " +
+                    "<a class='btn btn-secondary btn-sm import-data' href='javacript:void(0);' data-id='"+ id +"' title='导入内部数据'><i class='fa fa-sign-in fa-rotate-90'></i>导入</a></td>" +
+                    "</tr>");
                 var newHref = "/rationRepository/ration?repository="+id;
                 $("#tempId td:first a").attr("href", newHref);
                 $("#tempId").attr("id", id);

+ 60 - 1
web/maintain/ration_repository/main.html

@@ -35,7 +35,7 @@
                   <div class="col-md-8">
                     <div class="warp-p2 mt-3">
                       <table class="table table-hover table-bordered">
-                        <thead><tr><th>定额库名称</th><th width="160">编办</th><th width="160">添加时间</th><th width="90">操作</th></tr></thead>
+                        <thead><tr><th>定额库名称</th><th width="160">编办</th><th width="160">添加时间</th><th width="90">操作</th><th width="90">原始数据</th><th width="150">内部数据</th></tr></thead>
                         <tbody id="showArea">
                         </tbody>
                       </table>
@@ -153,6 +153,65 @@
             </div>
         </div>
     </div>
+    <!--弹出导入原始数据-->
+    <div class="modal fade" id="import" data-backdrop="static" style="display: none;" aria-hidden="true">
+        <form action="/rationRepository/api/upload" enctype="multipart/form-data" method="post">
+            <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 class="alert alert-warning" role="alert">
+                            导入操作会覆盖数据,请谨慎操作!!
+                        </div>
+                        <form>
+                            <div class="form-group">
+                                <label>请选择Excel格式文件</label>
+                                <input class="form-control-file" type="file" name="source_file">
+                            </div>
+                        </form>
+                    </div>
+                    <div class="modal-footer">
+                        <button type="button" class="btn btn-secondary" data-dismiss="modal">取消</button>
+                        <button type="button" class="btn btn-primary" id="source-import">确定导入</button>
+                    </div>
+                </div>
+            </div>
+        </form>
+    </div>
+
+    <!--弹出导入内部数据-->
+    <div class="modal fade" id="import2" 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 class="alert alert-warning" role="alert">
+                        导入操作会覆盖数据,请谨慎操作!!
+                    </div>
+                    <form>
+                        <div class="form-group">
+                            <label>请选择Excel格式文件</label>
+                            <input class="form-control-file" type="file" name="import_data"/>
+                        </div>
+                    </form>
+                </div>
+                <div class="modal-footer">
+                    <button type="button" class="btn btn-secondary" data-dismiss="modal">取消</button>
+                    <button type="button" class="btn btn-primary" id="data-import">确定导入</button>
+                </div>
+            </div>
+        </div>
+    </div>
     <!-- JS. -->
     <script src="/lib/jquery/jquery.min.js"></script>
     <script src="/lib/tether/tether.min.js"></script>

+ 110 - 0
web/maintain/report/js/cfg_const.js

@@ -403,6 +403,116 @@ let discreteFieldParamSetting = {
     }
 };
 
+let preHandleSetting = {
+    view: {
+        showIcon: true,
+        expandSpeed: "",
+        selectedMulti: false
+    },
+    edit: {
+        enable: true,
+        editNameSelectAll: false,
+        showRemoveBtn: true,
+        showRenameBtn: false,
+        removeTitle: "删除",
+        drag: {
+            isCopy: false,
+            isMove: true
+        }
+    },
+    data: {
+        keep: {
+            parent:true,
+            leaf:true
+        },
+        key: {
+            children: 'items',
+            name: "Name",
+            title: "Title",
+        },
+        simpleData: {
+            enable: true
+        }
+    },
+    callback:{
+        onClick: preHandleObj.onPreHandleClick,
+        beforeRemove: preHandleObj.onBeforeRemove,
+        beforeDrop: preHandleObj.onBeforeDrop
+    }
+};
+
+let preHandleTypeSetting = {
+    view: {
+        showIcon: true,
+        expandSpeed: "",
+        selectedMulti: false
+    },
+    edit: {
+        enable: true,
+        editNameSelectAll: false,
+        showRemoveBtn: false,
+        showRenameBtn: false
+    },
+    check: {
+        enable: true,
+        chkStyle: "radio",
+        radioType: "all"
+    },
+    data: {
+        keep: {
+            parent:true,
+            leaf:true
+        },
+        key: {
+            children: 'items',
+            name: "Name",
+            title: "Title",
+        },
+        simpleData: {
+            enable: true
+        }
+    },
+    callback:{
+        onClick: preHandleObj.onTypeClick,
+        beforeDrag: function() {return false;}
+    }
+};
+
+let sortingKeysSetting = {
+    view: {
+        showIcon: true,
+        expandSpeed: "",
+        selectedMulti: false
+    },
+    edit: {
+        enable: true,
+        editNameSelectAll: false,
+        showRemoveBtn: false,
+        showRenameBtn: false
+    },
+    check: {
+        enable: true
+    },
+    data: {
+        keep: {
+            parent:true,
+            leaf:true
+        },
+        key: {
+            children: 'items',
+            name: "Name",
+            title: "Title",
+        },
+        simpleData: {
+            enable: true
+        }
+    },
+    callback:{
+        onCheck: preHandleSortObj.onCheck,
+        beforeDrag: function(){return false;}
+    }
+};
+
 const engineering = {
     // 建筑工程
     ARCHITECTURE: 1,

+ 1 - 1
web/maintain/report/js/rpt_tpl_data_map.js

@@ -100,7 +100,7 @@ let dataInfoMapTreeOprObj = {
         rst.push(rootFieldNode);
         rst.push(discreteNode);
         me.private_build_content_field_data(rootFieldNode, rptTpl[JV.NODE_BILL_INFO][JV.NODE_BILL_CONTENT], JV.PROP_BILL_FIELDS, rptTpl);
-        me.private_build_discrete_data(discreteNode, rptTpl[JV.NODE_BILL_INFO][JV.NODE_DISCRETE_INFO]);
+        me.private_build_discrete_data(discreteNode, rptTpl[JV.NODE_BILL_INFO][JV.NODE_DISCRETE_INFO], rptTpl);
 
         return rst;
     },

+ 4 - 1
web/maintain/report/js/rpt_tpl_helper.js

@@ -68,7 +68,10 @@ let tplHelper = {
             dataInfoMapTreeOprObj.iniTree(rptTpl);
             discreteFieldParamTreeOprObj.iniTree(rptTpl);
             dataInfoMapTreeOprObj.iniDataMap();
-            //5. 计算式
+            //5. 数据预处理
+            preHandleObj.iniTree(rptTpl);
+            preHandleObj.hide_all_dom();
+            //6. 计算式
             calculationTreeOprObj.iniTree(rptTpl);
         }
     },

+ 1 - 0
web/maintain/report/js/rpt_tpl_main.js

@@ -8,6 +8,7 @@ let rptTplObj = {
         selectableFiledTreeOprObj.iniTree();
         preview_util.drawBorder($("#tplCanvas")[0]);
         calculationTreeOprObj.buildRunType();
+        preHandleObj.buildTypeData();
     }
 }
 

+ 381 - 0
web/maintain/report/js/rpt_tpl_pre_handle.js

@@ -0,0 +1,381 @@
+/**
+ * Created by Tony on 2017/12/26.
+ */
+
+const sort_types = ["normal", "tree", "accord_to_parent", "self_define"];
+const pre_handle_data_objects = ["bills", "ration", "ration_glj"];
+const pre_handle_data_objects_name = ["清单", "定额", "定额工料机"];
+const exposed_bills_properties = [
+    {Name: "清单_ID", Title: "", Key: "ID"},
+    {Name: "清单_所属项目ID", Title: "", Key: "projectID"},
+    {Name: "清单_项目编码", Title: "", Key: "code"},
+    {Name: "清单_项目名称", Title: "", Key: "name"},
+    {Name: "清单_项目特征", Title: "", Key: "itemCharacterText"},
+    {Name: "清单_计量单位", Title: "", Key: "unit"},
+    {Name: "清单_工程量", Title: "", Key: "quantity"},
+    {Name: "清单_综合单价", Title: "", Key: "unitFee"},
+    {Name: "清单_综合合价", Title: "", Key: "totalFee"},
+    {Name: "清单_暂估价", Title: "", Key: "tenderUnitFee"}
+];
+const exposed_ration_properties = [
+    {Name: "定额_工程内部ID", Title: "", Key: "ID"},
+    {Name: "定额_所属清单ID", Title: "", Key: "billsItemID"},
+    {Name: "定额_所属项目ID", Title: "", Key: "projectID"},
+    {Name: "定额_编号", Title: "", Key: "code"},
+    {Name: "定额_项目名称", Title: "", Key: "caption"},
+    {Name: "定额_单位", Title: "", Key: "unit"},
+    {Name: "定额_数量", Title: "", Key: "quantity"},
+    {Name: "定额_人工费", Title: "", Key: "labour"},
+    {Name: "定额_材料费", Title: "", Key: "material"},
+    {Name: "定额_机械费", Title: "", Key: "machine"},
+    {Name: "定额_机上人工费", Title: "", Key: "machineLabour"},
+    {Name: "定额_人工费调整", Title: "", Key: "adjustLabour"},
+    {Name: "定额_材料费调整", Title: "", Key: "'adjustMaterial'"},
+    {Name: "定额_机械费调整", Title: "", Key: "adjustMachine"},
+    {Name: "定额_机上人工费调整", Title: "", Key: "adjustMachineLabour"},
+    {Name: "定额_人工价差", Title: "", Key: "labourDiff"},
+    {Name: "定额_材料价差", Title: "", Key: "materialDiff"},
+    {Name: "定额_机械价差", Title: "", Key: "machineDiff"},
+    {Name: "定额_风险费用", Title: "", Key: "risk"}
+    // {Name: "定额_管理费率", Title: "", Key: ""}
+];
+const exposed_prj_glj_properties = [
+    {Name: "工料机_ID", Title: "", Key: "ID"},
+    {Name: "工料机_所属定额ID", Title: "", Key: "rationID"},
+    {Name: "工料机_所属清单ID", Title: "", Key: "billsItemID"},
+    {Name: "工料机_所属工程ID", Title: "", Key: "projectID"},
+    {Name: "工料机_代码", Title: "", Key: "code"},
+    {Name: "工料机_名称", Title: "", Key: "name"},
+    {Name: "工料机_单位", Title: "", Key: "unit"},
+    {Name: "工料机_数量", Title: "", Key: "quantity"}
+];
+const exposed_properties_arr = [exposed_bills_properties, exposed_ration_properties, exposed_prj_glj_properties];
+
+let preHandleObj = {
+    treeObj: null,
+    typeTreeObj: null,
+    currentNode: null,
+    iniTree: function(rptTpl) {
+        let me = this;
+        me.buildData(rptTpl);
+    },
+    buildData: function(rptTpl) {
+        let me = this, handles = [];
+        if (rptTpl[JV.NODE_MAP_DATA_HANDLE_INFO]) {
+            for (let item of rptTpl[JV.NODE_MAP_DATA_HANDLE_INFO]) {
+                let hd = {Name: "预处理环节"};
+                me.private_copy_node(item, hd);
+                me.private_set_title(hd);
+                hd.Operations = []; //考虑到用户可能会换来换去,这样会有冗余的属性出现
+                handles.push(hd);
+            }
+        }
+        me.treeObj = $.fn.zTree.init($("#pre_handle_data_info_reversed"), preHandleSetting, handles);
+    },
+    build_handle_data_selection: function() {
+        let sel = $("#select_mapping_data"), sel2 = $("#select_mapping_data_for_sort_type");
+        for (let i = 0; i < pre_handle_data_objects.length; i++) {
+            sel2.append("<option value='" + pre_handle_data_objects[i] + "'>" + pre_handle_data_objects_name[i] + "</option>");
+            sel.append("<option value='" + pre_handle_data_objects[i] + "'>" + pre_handle_data_objects_name[i] + "</option>");
+        }
+    },
+    buildTypeData: function () {
+        let me = this, types = [];
+        types.push({Name: JV.PROP_HANDLE_TYPE_SORT, Title: ""});
+        types.push({Name: JV.PROP_HANDLE_TYPE_FILTER, Title: ""});
+        types.push({Name: JV.PROP_HANDLE_TYPE_SUM, Title: ""});
+        types.push({Name: JV.PROP_HANDLE_TYPE_ADJUST, Title: ""});
+        types.push({Name: JV.PROP_HANDLE_TYPE_ADD_DUMMY, Title: ""});
+        // types.push({Name: "纯手工填写", Title: ""});
+        me.typeTreeObj = $.fn.zTree.init($("#pre_handle_type_reversed"), preHandleTypeSetting, types);
+        me.setDisabledBandSelect(true);
+        me.build_handle_data_selection();
+    },
+    setDisabledBandSelect: function(disabled) {
+        let me = this;
+        if (me.typeTreeObj) {
+            let nodes = me.typeTreeObj.getNodes();
+            for (let node of nodes) {
+                me.typeTreeObj.setChkDisabled(node, disabled, true, true);
+            }
+        }
+    },
+    addNewNode: function() {
+        let me = this, item = {Name: "预处理环节", Title: "", "映射数据对象": "bills", "预处理类型": "排序", "排序方式": "normal", Operations : []};
+        me.private_set_title(item);
+        me.treeObj.addNodes(null, -1, [item], true);
+    },
+    private_copy_node: function(src, dest) {
+        let me = this;
+        dest[JV.PROP_DATA_KEY] = src[JV.PROP_DATA_KEY];
+        dest[JV.PROP_HANDLE_TYPE] = src[JV.PROP_HANDLE_TYPE];
+
+        if (src[JV.PROP_HANDLE_TYPE] === JV.PROP_HANDLE_TYPE_SORT) {
+            preHandleSortObj.copyNode(src, dest);
+        } else if (src[JV.PROP_HANDLE_TYPE] === JV.PROP_HANDLE_TYPE_FILTER) {
+            preHandleFilterObj.copyNode(src, dest);
+        } else if (src[JV.PROP_HANDLE_TYPE] === JV.PROP_HANDLE_TYPE_SUM) {
+            preHandleSummaryObj.copyNode(src, dest);
+        } else if (src[JV.PROP_HANDLE_TYPE] === JV.PROP_HANDLE_TYPE_ADJUST) {
+            preHandleAdjustObj.copyNode(src, dest);
+        } else if (src[JV.PROP_HANDLE_TYPE] === JV.PROP_HANDLE_TYPE_ADD_DUMMY) {
+            preHandleAddDummyObj.copyNode(src, dest);
+        }
+
+        // if (src[JV.PROP_SORT_TYPE]) dest[JV.PROP_SORT_TYPE] = src[JV.PROP_SORT_TYPE];
+        // if (src[JV.PROP_PARENT_DATA_KEY]) dest[JV.PROP_PARENT_DATA_KEY] = src[JV.PROP_PARENT_DATA_KEY];
+        // if (src[JV.PROP_PARENT_CHILD_SORT_KEY]) dest[JV.PROP_PARENT_CHILD_SORT_KEY] = src[JV.PROP_PARENT_CHILD_SORT_KEY];
+        // if (src[JV.PROP_PARENT_SORT_KEYS]) dest[JV.PROP_PARENT_SORT_KEYS] = src[JV.PROP_PARENT_SORT_KEYS];
+        // if (src[JV.PROP_CHILD_SORT_KEYS]) dest[JV.PROP_CHILD_SORT_KEYS] = src[JV.PROP_CHILD_SORT_KEYS];
+        // if (src[JV.PROP_OTHER_SUB_SORT]) dest[JV.PROP_OTHER_SUB_SORT] = src[JV.PROP_OTHER_SUB_SORT];
+        // if (src[JV.PROP_FILTER_KEY]) dest[JV.PROP_FILTER_KEY] = src[JV.PROP_FILTER_KEY];
+        // if (src[JV.PROP_FILTER_COMPARE_OBJ]) dest[JV.PROP_FILTER_COMPARE_OBJ] = src[JV.PROP_FILTER_COMPARE_OBJ];
+        // if (src[JV.PROP_FILTER_COMPARE_OBJ_KEY]) dest[JV.PROP_FILTER_COMPARE_OBJ_KEY] = src[JV.PROP_FILTER_COMPARE_OBJ_KEY];
+        // if (src[JV.PROP_FILTER_COMPARE_VAL]) dest[JV.PROP_FILTER_COMPARE_VAL] = src[JV.PROP_FILTER_COMPARE_VAL];
+        // if (src[JV.PROP_FILTER_CONDITION]) dest[JV.PROP_FILTER_CONDITION] = src[JV.PROP_FILTER_CONDITION];
+
+        // if (src[JV.PROP_HANDLE_TYPE_SUM]) dest[JV.PROP_HANDLE_TYPE_SUM] = src[JV.PROP_HANDLE_TYPE_SUM];
+        // if (src[JV.PROP_HANDLE_TYPE_SORT]) dest[JV.PROP_HANDLE_TYPE_SORT] = src[JV.PROP_HANDLE_TYPE_SORT];
+        // if (src[JV.PROP_HANDLE_TYPE_ADD_DUMMY]) dest[JV.PROP_HANDLE_TYPE_ADD_DUMMY] = src[JV.PROP_HANDLE_TYPE_ADD_DUMMY];
+        // if (src[JV.PROP_HANDLE_TYPE_ADJUST]) dest[JV.PROP_HANDLE_TYPE_ADJUST] = src[JV.PROP_HANDLE_TYPE_ADJUST];
+
+        // if (src[JV.PROP_PARENT_DATA_KEY]) dest[JV.PROP_PARENT_DATA_KEY] = src[JV.PROP_PARENT_DATA_KEY];
+
+    },
+    private_set_title: function(node) {
+        let nameStr = node[JV.PROP_NAME], idx = nameStr.indexOf("(");
+        if (idx >= 0) {
+            nameStr = nameStr.slice(0, idx);
+        }
+        node[JV.PROP_NAME] = nameStr + "(" + node["预处理类型"] + ")";
+        node.Title = "映射数据对象:" + node["映射数据对象"] + " | " + node["预处理类型"];
+    },
+    refreshByNode: function(treeNode) {
+        let me = preHandleObj;
+        if (me.typeTreeObj) {
+            let nodes = me.typeTreeObj.getNodes();
+            let typeNode = null;
+            for (let node of nodes) {
+                if (treeNode[JV.PROP_NAME].indexOf(node[JV.PROP_NAME]) > 0) {
+                    me.typeTreeObj.checkNode(node, true, false, false);
+                    typeNode = node;
+                    break;
+                }
+            }
+            let md = $("#select_mapping_data")[0];
+            for (let i = 0; i < md.children.length; i++) {
+                if (treeNode[JV.PROP_DATA_KEY] === md.children[i].value) {
+                    md.selectedIndex = i;
+                    break;
+                }
+            }
+            if (typeNode) {
+                me.hide_all_dom();
+                $("#div_pre_handle_data")[0].style.display = "";
+                if (typeNode[JV.PROP_NAME] === "排序") {
+                    preHandleSortObj.refresh_node();
+                } else if (typeNode[JV.PROP_NAME] === "过滤") {
+                    preHandleFilterObj.refresh_node();
+                } else if (typeNode[JV.PROP_NAME] === "合计") {
+                    preHandleSummaryObj.refresh_node();
+                } else if (typeNode[JV.PROP_NAME] === "数据调整") {
+                    preHandleAdjustObj.refresh_node();
+                } else if (typeNode[JV.PROP_NAME] === "增加Dummy数据") {
+                    preHandleAddDummyObj.refresh_node();
+                }
+            }
+        }
+    },
+    hide_all_dom: function() {
+        $("#div_pre_handle_data")[0].style.display = "none";
+        $("#div_sort_type")[0].style.display = "none";
+        $("#div_sort_type_normal")[0].style.display = "none";
+        $("#div_filter_type")[0].style.display = "none";
+        $("#div_summary_type")[0].style.display = "none";
+        $("#div_adjust_type")[0].style.display = "none";
+        $("#div_add_dummy_type")[0].style.display = "none";
+        $("#div_sort_type_according_to_parent")[0].style.display = "none";
+        $("#div_sort_type_parent_data")[0].style.display = "none";
+    },
+    onPreHandleClick: function(event,treeId,treeNode) {
+        let me = preHandleObj;
+        me.currentNode = treeNode;
+        me.setDisabledBandSelect(false);
+        me.refreshByNode(treeNode);
+    },
+    onTypeClick: function(event,treeId,treeNode) {
+        //
+    },
+    onChangeDataMappingObj: function(dom, oprType) {
+        if (oprType === "top") {
+            $.fn.zTree.init($("#child_sorting_keys"), sortingKeysSetting, exposed_properties_arr[dom.selectedIndex]);
+        } else if (oprType === "accord_to_parent") {
+            $.fn.zTree.init($("#parent_sorting_keys"), sortingKeysSetting, exposed_properties_arr[dom.selectedIndex]);
+        }
+    },
+    onSortTypeChange: function(dom) {
+        if (dom.selectedIndex === 2) {
+            $("#div_sort_type_according_to_parent")[0].style.display = "";
+            $("#div_sort_type_parent_data")[0].style.display = "";
+        } else {
+            $("#div_sort_type_according_to_parent")[0].style.display = "none";
+            $("#div_sort_type_parent_data")[0].style.display = "none";
+        }
+    },
+    onBeforeDrop: function(treeId, treeNodes, targetNode, moveType){
+        //
+    },
+    onBeforeRemove: function(treeId, treeNode){
+        let rst = true;
+        if (treeNode.isParent) {
+            rst = false;
+        } else {
+            //
+        }
+        return rst;
+    },
+    extractTabFields: function (rptTpl) {
+        //
+    }
+};
+
+let preHandleSortObj = {
+    copyNode: function (src, dest) {
+        dest[JV.PROP_SORT_TYPE] = src[JV.PROP_SORT_TYPE];
+        switch (sort_types.indexOf(src[JV.PROP_SORT_TYPE])) {
+            case -1:
+                //unknown!
+                break;
+            case 0 :
+                //normal
+                break;
+            case 1 :
+                //tree
+                break;
+            case 2 :
+                //according to parent
+                dest[JV.PROP_PARENT_CHILD_SORT_KEY] = {};
+                dest[JV.PROP_OTHER_SUB_SORT] = [];
+                dest[JV.PROP_PARENT_CHILD_SORT_KEY][JV.PROP_PARENT_DATA_KEY] = src[JV.PROP_PARENT_CHILD_SORT_KEY][JV.PROP_PARENT_DATA_KEY];
+                dest[JV.PROP_PARENT_CHILD_SORT_KEY][JV.PROP_PARENT_SORT_KEYS] = [];
+                for (let item of src[JV.PROP_PARENT_CHILD_SORT_KEY][JV.PROP_PARENT_SORT_KEYS]) {
+                    dest[JV.PROP_PARENT_CHILD_SORT_KEY][JV.PROP_PARENT_SORT_KEYS].push(item);
+                }
+                dest[JV.PROP_PARENT_CHILD_SORT_KEY][JV.PROP_CHILD_SORT_KEYS] = [];
+                for (let item of src[JV.PROP_PARENT_CHILD_SORT_KEY][JV.PROP_CHILD_SORT_KEYS]) {
+                    dest[JV.PROP_PARENT_CHILD_SORT_KEY][JV.PROP_CHILD_SORT_KEYS].push(item);
+                }
+                for (let subSort of src[JV.PROP_OTHER_SUB_SORT]) {
+                    let st = {};
+                    st[JV.PROP_SORT_TYPE] = subSort[JV.PROP_SORT_TYPE];
+                    if (subSort[JV.PROP_SORT_TYPE] === "normal") {
+                        st[JV.PROP_SORT_KEYS] = [];
+                        for (let item of subSort[JV.PROP_SORT_KEYS]) {
+                            let di = {key: item.key, order: item.order};
+                            st[JV.PROP_SORT_KEYS].push(di);
+                        }
+                    } else {
+                        //其他暂时不实现,目前没那么复杂
+                    }
+                }
+                break;
+            case 3 :
+                //self define
+                break;
+            default:
+                break;
+        }
+    },
+    refresh_node: function () {
+        $("#div_sort_type")[0].style.display = "";
+        if (preHandleObj.currentNode) {
+            let sel = $("#select_sort_types")[0];
+            let idx = -1;
+            let sortTypeIdx = sort_types.indexOf(preHandleObj.currentNode[JV.PROP_SORT_TYPE]);
+            sel.selectedIndex = sortTypeIdx;
+            switch (sortTypeIdx) {
+                case -1:
+                    //unknown!
+                    break;
+                case 0 :
+                    //normal
+                    $("#div_sort_type_normal")[0].style.display = "";
+                    idx = pre_handle_data_objects.indexOf(preHandleObj.currentNode[JV.PROP_DATA_KEY]);
+                    $.fn.zTree.init($("#pre_handle_sort_keys_reversed"), sortingKeysSetting, exposed_properties_arr[idx]);
+                    let sortKeyRoot = {Name: JV.PROP_SORT_KEYS, items: [], isParent: true};
+                    let sortKeys = [];
+                    // me.treeObj = $.fn.zTree.init($("#pre_handle_sort_keys_reversed"), preHandleSetting, handles);
+                    break;
+                case 1 :
+                    //tree
+                    //tree排序是我们特有的数据结构方式,与数据结构有关,非common用
+                    break;
+                case 2 :
+                    //according to parent
+                    $("#div_sort_type_according_to_parent")[0].style.display = "";
+                    $("#div_sort_type_parent_data")[0].style.display = "";
+                    sel = $("#select_mapping_data_for_sort_type")[0];
+                    idx = pre_handle_data_objects.indexOf(preHandleObj.currentNode[JV.PROP_PARENT_CHILD_SORT_KEY][JV.PROP_PARENT_DATA_KEY]);
+                    sel.selectedIndex = idx;
+                    //parent_sorting_keys
+                    $.fn.zTree.init($("#parent_sorting_keys"), sortingKeysSetting, exposed_properties_arr[idx]);
+                    //child_sorting_keys
+                    idx = pre_handle_data_objects.indexOf(preHandleObj.currentNode[JV.PROP_DATA_KEY]);
+                    $.fn.zTree.init($("#child_sorting_keys"), sortingKeysSetting, exposed_properties_arr[idx]);
+                    break;
+                case 3 :
+                    //self define
+                    break;
+                default:
+                    break;
+            }
+        }
+    },
+    onCheck: function(event,treeId,treeNode) {
+        //
+    }
+};
+
+let preHandleFilterObj = {
+    copyNode: function (src, dest) {
+        //
+    },
+    refresh_node: function () {
+        $("#div_filter_type")[0].style.display = "";
+        if (preHandleObj.currentNode) {
+        }
+    }
+};
+
+let preHandleSummaryObj = {
+    copyNode: function (src, dest) {
+        //
+    },
+    refresh_node: function () {
+        $("#div_summary_type")[0].style.display = "";
+        if (preHandleObj.currentNode) {
+        }
+    }
+};
+
+let preHandleAdjustObj = {
+    copyNode: function (src, dest) {
+        //
+    },
+    refresh_node: function () {
+        $("#div_adjust_type")[0].style.display = "";
+        if (preHandleObj.currentNode) {
+        }
+    }
+};
+
+let preHandleAddDummyObj = {
+    copyNode: function (src, dest) {
+        //
+    },
+    refresh_node: function () {
+        $("#div_add_dummy_type")[0].style.display = "";
+        if (preHandleObj.currentNode) {
+        }
+    }
+};

+ 4 - 0
web/maintain/report/rpt_tpl_detail.html

@@ -14,6 +14,9 @@
                 <a class="nav-link p-1" data-toggle="tab" href="#rpttplfieldlocation" role="tab" onclick="dataInfoMapTreeOprObj.iniDataMap();">指标摆放</a>
             </li>
             <li class="nav-item">
+                <a class="nav-link p-1" data-toggle="tab" href="#rpt_tpl_pre_handle_tab" role="tab">数据预处理</a>
+            </li>
+            <li class="nav-item">
                 <a class="nav-link p-1" data-toggle="tab" href="#rpttplformula" role="tab">计算式</a>
             </li>
         </ul>
@@ -27,6 +30,7 @@
             <!--指标摆放-->
             <%include ./rpt_tpl_detail_field_location.html %>
             <!--报表数据预处理-->
+            <%include ./rpt_tpl_detail_pre_handle.html %>
             <!--计算式-->
             <%include ./rpt_tpl_detail_calculation.html %>
         </div>

+ 0 - 10
web/maintain/report/rpt_tpl_detail_field_location_layout_detail.html

@@ -1,10 +0,0 @@
-<!DOCTYPE html>
-<html lang="en">
-<head>
-    <meta charset="UTF-8">
-    <title>Title</title>
-</head>
-<body>
-
-</body>
-</html>

+ 0 - 22
web/maintain/report/rpt_tpl_detail_field_location_selectableFields.html

@@ -1,22 +0,0 @@
-<div class="tab-pane" id="rpttplselectablefields" role="tabpanel">
-    <div class="main-data">
-        <div class="p-3">
-            <div class="row">
-                <div class="form-group col-md-7">
-                    <div class="form-group">
-                        <label>已选映射指标</label>
-                        <div class="ztree-warp">
-                            <ul id="tpl_data_selected_field_map_reversed" class="ztree"></ul>
-                        </div>
-                    </div>
-                </div>
-                <div class="form-group col-md-5">
-                    <div class="ztree-warp">
-                        <label>离散独立指标/参数</label>
-                        <ul id="tpl_discrete_fields_params_reversed" class="ztree"></ul>
-                    </div>
-                </div>
-            </div>
-        </div>
-    </div>
-</div>

+ 0 - 35
web/maintain/report/rpt_tpl_detail_field_location_tab.html

@@ -1,35 +0,0 @@
-<div class="tab-pane" id="rpttplfieldlocation" role="tabpanel">
-    <div class="main-data">
-        <div class="p-3">
-            <div class="row">
-                <div class="form-group col-md-8">
-                    <div class="ztree-warp">
-                        <ul id="tpl_data_info_reversed" class="ztree"></ul>
-                    </div>
-                </div>
-                <div class="form-group col-md-4" id="band_tree2_div">
-                    <div class="ztree-warp">
-                        <ul id="band_tree2_reversed" class="ztree"></ul>
-                    </div>
-                </div>
-            </div>
-            <div class="main-data">
-                <div class="main-data-top">
-                    <ul class="nav nav-tabs tools-bar" role="tablist">
-                        <li class="nav-item">
-                            <a class="nav-link p-1 active" data-toggle="tab" href="#rpttplselectablefields" role="tab">可映射指标</a>
-                        </li>
-                        <li class="nav-item">
-                            <a class="nav-link p-1" data-toggle="tab" href="#rpttpllayoutdetail" role="tab">控制明细</a>
-                        </li>
-                    </ul>
-                    <div class="tab-content">
-                        <!--模板信息-->
-                        <%include ./rpt_tpl_detail_field_location_selectableFields.html %>
-                        <!--模板布局-->
-                    </div>
-                </div>
-            </div>
-        </div>
-    </div>
-</div>

+ 38 - 10
web/maintain/report/rpt_tpl_detail_pre_handle.html

@@ -1,10 +1,38 @@
-<!DOCTYPE html>
-<html lang="en">
-<head>
-    <meta charset="UTF-8">
-    <title>Title</title>
-</head>
-<body>
-
-</body>
-</html>
+<div class="tab-pane" id="rpt_tpl_pre_handle_tab" role="tabpanel">
+    <div class="main-data">
+        <div class="p-3">
+            <div class="tab-bar">
+                <a onclick="preHandleObj.addNewNode()" class="btn btn-secondary btn-sm fa fa-plus-square">预处理环节</a>
+            </div>
+            <div class="form-group row">
+                <div class="form-group col-md-7">
+                    <div class="ztree-warp">
+                        <ul id="pre_handle_data_info_reversed" class="ztree"></ul>
+                    </div>
+                </div>
+                <div class="form-group col-md-5">
+                    <div class="ztree-warp">
+                        <ul id="pre_handle_type_reversed" class="ztree"></ul>
+                    </div>
+                </div>
+            </div>
+            <div class="form-group row" id="div_pre_handle_data">
+                <div class="input-group col-12">
+                    <div class="input-group-addon">预处理数据对象</div>
+                    <select class="form-control input-sm" id="select_mapping_data" onchange="preHandleObj.onChangeDataMappingObj(this, 'top')">
+                    </select>
+                </div>
+            </div>
+            <!--排序处理-->
+            <%include ./rpt_tpl_detail_pre_handle_sort.html %>
+            <!--过滤处理-->
+            <%include ./rpt_tpl_detail_pre_handle_filter.html %>
+            <!--合计处理-->
+            <%include ./rpt_tpl_detail_pre_handle_summary.html %>
+            <!--调整处理-->
+            <%include ./rpt_tpl_detail_pre_handle_adjust.html %>
+            <!--dummy数据处理-->
+            <%include ./rpt_tpl_detail_pre_handle_adddummy.html %>
+        </div>
+    </div>
+</div>

+ 2 - 0
web/maintain/report/rpt_tpl_detail_pre_handle_adddummy.html

@@ -0,0 +1,2 @@
+<div class="form-group row" id="div_add_dummy_type">
+</div>

+ 2 - 0
web/maintain/report/rpt_tpl_detail_pre_handle_adjust.html

@@ -0,0 +1,2 @@
+<div class="form-group row" id="div_adjust_type">
+</div>

+ 2 - 0
web/maintain/report/rpt_tpl_detail_pre_handle_filter.html

@@ -0,0 +1,2 @@
+<div class="form-group row" id="div_filter_type">
+</div>

+ 37 - 0
web/maintain/report/rpt_tpl_detail_pre_handle_sort.html

@@ -0,0 +1,37 @@
+<div class="form-group row" id="div_sort_type">
+    <div class="input-group col-5">
+        <div class="input-group-addon">排序类型</div>
+        <select class="form-control input-sm" id="select_sort_types" onchange="preHandleObj.onSortTypeChange(this)">
+            <option value="normal">普通</option>
+            <option value="tree">树结构</option>
+            <option value="accord_to_parent">依据上级</option>
+            <option value="self_define">自定义排序</option>
+        </select>
+    </div>
+    <div class="input-group col-md-5" id="div_sort_type_parent_data">
+        <div class="input-group-addon">父数据对象</div>
+        <select class="form-control input-sm" id="select_mapping_data_for_sort_type" onchange="preHandleObj.onChangeDataMappingObj(this, 'accord_to_parent')">
+        </select>
+    </div>
+</div>
+<div class="form-group row" id="div_sort_type_normal">
+    <div class="form-group col-md-7">
+        <div class="ztree-warp">
+            <ul id="pre_handle_sort_keys_reversed" class="ztree"></ul>
+        </div>
+    </div>
+</div>
+<div class="form-group row" id="div_sort_type_according_to_parent">
+    <div class="form-group col-md-6">
+        <label>父排序键值集</label>
+        <div class="tab-content">
+            <ul id="parent_sorting_keys" class="ztree"></ul>
+        </div>
+    </div>
+    <div class="form-group col-md-6">
+        <label>子排序键值集</label>
+        <div class="tab-content">
+            <ul id="child_sorting_keys" class="ztree"></ul>
+        </div>
+    </div>
+</div>

+ 2 - 0
web/maintain/report/rpt_tpl_detail_pre_handle_summary.html

@@ -0,0 +1,2 @@
+<div class="form-group row" id="div_summary_type">
+</div>

+ 1 - 0
web/maintain/report/rpt_tpl_main.html

@@ -80,6 +80,7 @@
     <script src="/web/maintain/report/js/rpt_tpl_calculation.js"></script>
     <script src="/web/maintain/report/js/rpt_tpl_helper.js"></script>
     <script src="/web/maintain/report/js/rpt_tpl_data_map.js"></script>
+    <script src="/web/maintain/report/js/rpt_tpl_pre_handle.js"></script>
     <script src="/web/maintain/report/js/cfg_const.js"></script>
     <script src="/web/maintain/report/js/rpt_tpl_preview_util.js"></script>
     <!-- zTree -->

+ 32 - 6
web/users/js/template.js

@@ -6,8 +6,25 @@ let TEMPLATE_BILLS_SETTING = {
     "emptyRows":1,
     "headRows":1,
     "headRowHeight":[35],
-    "treeCol": 0,
+    "treeCol": 1,
     "cols":[{
+        "width":80,
+        "readOnly":false,
+        "head":{
+            "titleNames":["类别"],
+            "spanCols":[1],
+            "spanRows":[1],
+            "vAlign":[1],
+            "hAlign":[1],
+            "font":["Arial"]
+        },
+        "data":{
+            "field":"type",
+            "vAlign":0,
+            "hAlign":1,
+            "font":"Arail",
+        }
+    }, {
         "width":200,
         "readOnly":false,
         "head":{
@@ -152,16 +169,23 @@ $(document).ready(function () {
             }
         });
     };
-    let getFixedFlagCellType = function () {
-        let billsFixedFlagData = JSON.parse(billsFixedFlagList);
+    let getNameValueComboCellType = function (datas) {
         let comboItems = [];
-        for (let data of billsFixedFlagData) {
+        for (let data of datas) {
             comboItems.push({text: data.name, value: data.value});
         }
         let combo = new GC.Spread.Sheets.CellTypes.ComboBox();
         combo.editorValueType(GC.Spread.Sheets.CellTypes.EditorValueType.value)
             .items(comboItems);
         return combo;
+    }
+    let getFixedFlagCellType = function () {
+        let billsFixedFlagData = JSON.parse(billsFixedFlagList);
+        return getNameValueComboCellType(billsFixedFlagData);
+    };
+    let getTypeFlagCellType = function () {
+        let billsTypeFlagData = JSON.parse(billsTypeFlagList);
+        return getNameValueComboCellType(billsTypeFlagData);
     };
     let setFee = function (data, fullField, value) {
         let fields = fullField.split('.'), valueField = data;
@@ -195,7 +219,9 @@ $(document).ready(function () {
     }
 
     for (col of TEMPLATE_BILLS_SETTING.cols) {
-        if (col.data.field === 'flagsIndex.fixed.flag' && TEMPLATE_BILLS_SETTING.cols.indexOf(col) !== TEMPLATE_BILLS_SETTING.treeCol) {
+        if (col.data.field === 'type' && TEMPLATE_BILLS_SETTING.cols.indexOf(col) !== TEMPLATE_BILLS_SETTING.treeCol) {
+            col.data.cellType = getTypeFlagCellType();
+        } else if (col.data.field === 'flagsIndex.fixed.flag' && TEMPLATE_BILLS_SETTING.cols.indexOf(col) !== TEMPLATE_BILLS_SETTING.treeCol) {
             col.data.cellType = getFixedFlagCellType();
         }
     }
@@ -210,7 +236,7 @@ $(document).ready(function () {
         var node = controller.tree.items[info.row];
         var fieldName = controller.setting.cols[info.col].data.field;
         var data = {type: 'update', data: {ID: node.getID()}};
-        if (/flagsIndex/.test(fieldName)) {
+       if (/flagsIndex/.test(fieldName)) {
             data.data.flags = [];
             let flagField = fieldName.split('.');
             data.data.flags.push({fieldName: flagField[1], flag: info.editingText});

+ 1 - 1
web/users/views/compilation/engineering.html

@@ -132,7 +132,7 @@
                                data-toggle="modal" data-target="" class="btn btn-primary btn-sm pull-right"
                                style="margin-right:5px">模板设置</a>
                         <input type="hidden" name="main_tree_col" value="<%= mainTreeCol %>">
-                        <div id="main-tree-col"></div>
+                        <div id="main-tree-col" style="height: 400px;"></div>
                     </div>
                 </div>
                 <input type="hidden" name="engineering" value="<%= engineeringInfo.id %>" id="engineering">

+ 5 - 5
web/users/views/compilation/template.html

@@ -25,7 +25,7 @@
         <div class="c-body">
             <legend>清单模板设置<a href="/compilation/valuation/<%= section %>/<%= valuationId %>" class="btn btn-default btn-sm pull-right">返回</a><a href="#" class="btn btn-primary btn-sm pull-right" style="margin-right:5px">保存</a></legend>
             <div class="row">
-                <div class="col-md-8">
+                <div class="col-md-9">
                     <div class="tools-btn btn-group align-top">
                         <a href="" class="btn btn-sm"><i class="fa fa-files-o" aria-hidden="true"></i> 复制</a>
                         <a href="" class="btn btn-sm"><i class="fa fa-scissors" aria-hidden="true"></i> 剪切</a>
@@ -37,13 +37,12 @@
                         <a href="javascript:void(0)" class="btn btn-sm" id="downMove"><i class="fa fa-arrow-down" aria-hidden="true"></i> 下移</a>
                         <a href="javascript:void(0)" class="btn btn-sm" id="upMove"><i class="fa fa-arrow-up" aria-hidden="true"></i> 上移</a>
                     </div>
-                    <div class="mb-qd-height" id="billsSpread">
+                    <div class="mb-qd-height" id="billsSpread" style="height: 500px;">
                     </div>
                 </div>
-                <div class="col-md-4">
-                    <select class="form-control"><option>定额</option></select>
+                <div class="col-md-3">
+                    <select class="form-control" style="display: none"><option>定额</option></select>
                     <div class="mb-de-height">
-
                     </div>
                 </div>
             </div>
@@ -53,6 +52,7 @@
 <script>
     let billsTemplateData = '<%- billsTemplateData %>';
     let billsFixedFlagList = '<%- billsFixedFlagList %>';
+    let billsTypeFlagList = '<%- billsTypeFlagList %>';
     let updateUrl = '/compilation/template/<%= section %>/<%= valuationId %>/<%= engineering %>/update';
 </script>
 <script type="text/javascript" src="/public/web/id_tree.js"></script>