Selaa lähdekoodia

rollback some modules

TonyKang 8 vuotta sitten
vanhempi
commit
6ae2acc333
47 muutettua tiedostoa jossa 4669 lisäystä ja 0 poistoa
  1. 29 0
      modules/ration_repository/controllers/coe_controller.js
  2. 47 0
      modules/ration_repository/controllers/ration_controller.js
  3. 70 0
      modules/ration_repository/controllers/ration_repository_controller.js
  4. 45 0
      modules/ration_repository/controllers/ration_section_tree_controller.js
  5. 83 0
      modules/ration_repository/controllers/repository_glj_controller.js
  6. 30 0
      modules/ration_repository/controllers/search_controller.js
  7. 134 0
      modules/ration_repository/models/coe.js
  8. 220 0
      modules/ration_repository/models/glj_repository.js
  9. 45 0
      modules/ration_repository/models/rationAssist.js
  10. 35 0
      modules/ration_repository/models/rationCoe.js
  11. 168 0
      modules/ration_repository/models/ration_item.js
  12. 100 0
      modules/ration_repository/models/ration_section_tree.js
  13. 120 0
      modules/ration_repository/models/repository_map.js
  14. 32 0
      modules/ration_repository/routes/ration_fe_routes.js
  15. 46 0
      modules/ration_repository/routes/ration_rep_routes.js
  16. 99 0
      modules/reports/controllers/rpt_controller.js
  17. 46 0
      modules/reports/controllers/rpt_tpl_controller.js
  18. 72 0
      modules/reports/models/rpt_cfg.js
  19. 40 0
      modules/reports/models/rpt_template.js
  20. 34 0
      modules/reports/models/rpt_tpl_data.js
  21. 98 0
      modules/reports/models/tpl_tree_node.js
  22. 12 0
      modules/reports/routes/report_router.js
  23. 10 0
      modules/reports/routes/rpt_tpl_router.js
  24. 58 0
      modules/reports/rpt_component/helper/jpc_helper_area.js
  25. 74 0
      modules/reports/rpt_component/helper/jpc_helper_band.js
  26. 148 0
      modules/reports/rpt_component/helper/jpc_helper_common.js
  27. 60 0
      modules/reports/rpt_component/helper/jpc_helper_common_output.js
  28. 189 0
      modules/reports/rpt_component/helper/jpc_helper_cross_tab.js
  29. 58 0
      modules/reports/rpt_component/helper/jpc_helper_discrete.js
  30. 44 0
      modules/reports/rpt_component/helper/jpc_helper_field.js
  31. 43 0
      modules/reports/rpt_component/helper/jpc_helper_flow_tab.js
  32. 14 0
      modules/reports/rpt_component/helper/jpc_helper_text.js
  33. 52 0
      modules/reports/rpt_component/jpc_band.js
  34. 78 0
      modules/reports/rpt_component/jpc_bill_tab.js
  35. 554 0
      modules/reports/rpt_component/jpc_cross_tab.js
  36. 98 0
      modules/reports/rpt_component/jpc_data.js
  37. 202 0
      modules/reports/rpt_component/jpc_ex.js
  38. 47 0
      modules/reports/rpt_component/jpc_field.js
  39. 229 0
      modules/reports/rpt_component/jpc_flow_tab.js
  40. 18 0
      modules/reports/rpt_component/jpc_function.js
  41. 27 0
      modules/reports/rpt_component/jpc_param.js
  42. 47 0
      modules/reports/rpt_component/jpc_rte.js
  43. 15 0
      modules/reports/rpt_component/jpc_value_define.js
  44. 281 0
      modules/reports/util/excel_base_files/theme1.xml
  45. 687 0
      modules/reports/util/rpt_excel_util.js
  46. 27 0
      modules/reports/util/rpt_util.js
  47. 4 0
      server.js

+ 29 - 0
modules/ration_repository/controllers/coe_controller.js

@@ -0,0 +1,29 @@
+/**
+ * Created by CSL on 2017/5/19.
+ */
+var coeList = require("../models/coe");
+
+var callback = function(req,res,err,message, data){
+    res.json({error: err, message: message, data: data});
+}
+
+module.exports ={
+    getCoeList: function(req,res){
+        coeList.getCoesByLibID(req.body.libID, function(err,data){
+            callback(req, res, err, 'Get coes', data);
+        });
+    },
+
+    saveCoeList: function(req, res) {
+        coeList.saveToCoeList(JSON.parse(req.body.data), function(isErr, msg, data){
+            callback(req, res, isErr, msg, data);
+        });
+    },
+
+    getCoeItemsByIDs: function(req,res){
+        coeList.getCoeItemsByIDs(JSON.parse(req.body.data), function(err,data){
+            callback(req, res, err, 'Get coe', data);
+        });
+    }
+
+}

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

@@ -0,0 +1,47 @@
+/**
+ * Created by Tony on 2017/5/2.
+ */
+
+var rationItem = require('../models/ration_item');
+var callback = function(req, res, err, message, data){
+    res.json({error: err, message: message, data: data});
+};
+
+module.exports = {
+    getRationItemsBySection: function(req, res){
+        var sectionId = req.body.sectionID;
+        rationItem.getRationItemsBySection(sectionId, function(err, message, rst){
+            if (err) {
+                callback(req, res, err, message, null);
+            } else {
+                callback(req, res, err, message, rst);
+            }
+        });
+    },
+    mixUpdateRationItems: function(req, res){
+        var sectionId = req.body.sectionID,
+            rationLibId = req.body.rationLibId,
+            updateItems = JSON.parse(req.body.updateItems),
+            addItems = JSON.parse(req.body.addItems),
+            removeIds = JSON.parse(req.body.removeIds);
+        rationItem.mixUpdateRationItems(rationLibId, sectionId, updateItems, addItems, removeIds, function(err, message, rst){
+            if (err) {
+                callback(req, res, err, message, null);
+            } else {
+                callback(req, res, err, message, rst);
+            }
+        });
+    },
+    removeRationItems: function(req, res){
+        var rIds = JSON.parse(req.body.updateItems);
+        if (rIds && rIds.length > 0) {
+            rationItem.removeRationItems(rIds, function(err, message, rst){
+                if (err) {
+                    callback(req, res, err, message, null);
+                } else {
+                    callback(req, res, err, message, rst);
+                }
+            });
+        }
+    }
+}

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

@@ -0,0 +1,70 @@
+/**
+ * Created by Tony on 2017/4/20.
+ */
+var rationRepository = require("../models/repository_map");
+
+var callback = function(req, res, err, message, data){
+    res.json({error: err, message: message, data: data});
+};
+
+module.exports = {
+    addRationRepository:function(req,res){
+        var rationObj = JSON.parse(req.body.rationRepObj);
+        rationRepository.addRationRepository(rationObj,function(err,data){
+            if (data) {
+                callback(req, res, err, "has data", data);
+            } else {
+                callback(req, res, err, "no data", null);
+            }
+        })
+    },
+    getDisPlayRationLibs: function(req, res){
+        rationRepository.getDisplayRationLibs(function(err, data){
+            if (data) {
+                callback(req, res, err, "has data",data);
+            } else {
+                callback(req, res, err, "no data", null);
+            }
+        });
+    },
+    getRealLibName:function(req,res){
+        var libName = req.body.rationName;
+        rationRepository.getRealLibName(libName,function(err,data){
+            if (data) {
+                callback(req, res, err, "has data", data);
+            } else {
+                callback(req, res, err, "no data", null);
+            }
+        })
+    },
+    getLibIDByName:function(req,res){
+        rationRepository.getLibIDByName(req.body.libName, function(err,data){
+            if (data) {
+                callback(req, res, err, "has ID", data);
+            } else {
+                callback(req, res, err, "no ID", null);
+            }
+        })
+    },
+    deleteRationLib:function(req,res){
+        var rationName = req.body.rationName;
+        rationRepository.deleteRationLib(rationName,function(err,data){
+            if (data) {
+                callback(req, res, err, "has data", data);
+            } else {
+                callback(req, res, err, "no data", null);
+            }
+        })
+    },
+    updateRationRepositoryName: function(req, res) {
+        var orgName = req.body.rationName;
+        var newName = req.body.newName;
+        rationRepository.updateName(orgName, newName, function(err, data){
+            if (data) {
+                callback(req, res, err, "has data", data);
+            } else {
+                callback(req, res, err, "no data", null);
+            }
+        });
+    }
+}

+ 45 - 0
modules/ration_repository/controllers/ration_section_tree_controller.js

@@ -0,0 +1,45 @@
+/**
+ * Created by Tony on 2017/4/21.
+ */
+
+var rationChapterTreeData = require('../models/ration_section_tree');
+var callback = function(req,res,err,message, data){
+    res.json({error: err, message: message, data: data});
+}
+module.exports ={
+    getRationChapterTree: function(req,res){
+        var rationLibId = req.body.rationLibId;
+        var repId = req.body.rationRepositoryId;
+        if (rationLibId) {
+            rationChapterTreeData.getRationChapterTree(rationLibId,function(err,data){
+                callback(req,res,err, "", data);
+            })
+        } else if (repId) {
+            rationChapterTreeData.getRationChapterTreeByRepId(repId,function(err,data){
+                callback(req,res,err,"", data)
+            })
+        }
+    },
+    createNewNode: function(req, res){
+        var libId = req.body.rationLibId;
+        var lastNodeId = req.body.lastNodeId;
+        var nodeData = JSON.parse(req.body.rawNodeData);
+        rationChapterTreeData.createNewNode(libId, lastNodeId, nodeData, function(err,data){
+            callback(req,res,err,"", data)
+        });
+    },
+    updateNodes: function(req, res) {
+        var nodes = JSON.parse(req.body.nodes);
+        rationChapterTreeData.updateNodes(nodes, function(err,results){
+            callback(req,res, err, "", results)
+        });
+    },
+    deleteNodes: function(req, res) {
+        var nodes = JSON.parse(req.body.nodes);
+        var preNodeId = req.body.preNodeId;
+        var preNodeNextId = req.body.preNodeNextId;
+        rationChapterTreeData.removeNodes(nodes, preNodeId, preNodeNextId, function(err,results){
+            callback(req,res, err, "", results)
+        });
+    }
+}

+ 83 - 0
modules/ration_repository/controllers/repository_glj_controller.js

@@ -0,0 +1,83 @@
+/**
+ * Created by Tony on 2017/5/5.
+ */
+var gljRepository = require("../models/glj_repository");
+
+var callback = function(req,res,err,message, data){
+    res.json({error: err, message: message, data: data});
+}
+module.exports ={
+    getGljTree: function(req,res){
+        var rationLibId = req.body.rationLibId;
+        gljRepository.getGljTypes(rationLibId,function(err,data){
+            callback(req,res,err, 'Get Tree', data)
+        });
+    },
+    createNewGljTypeNode: function(req, res) {
+        var repId = req.body.repositoryId;
+        var lastNodeId = req.body.lastNodeId;
+        var nodeData = JSON.parse(req.body.rawNodeData);
+        gljRepository.createNewNode(repId, lastNodeId, nodeData, function(err, msg, data){
+            callback(req,res,err,msg, data)
+        });
+    },
+    updateGljNodes: function(req, res) {
+        var nodes = JSON.parse(req.body.nodes);
+        gljRepository.updateNodes(nodes, function(err,results){
+            callback(req,res, err, results)
+        });
+    },
+    deleteGljNodes: function(req, res) {
+        var nodes = JSON.parse(req.body.nodes);
+        var preNodeId = req.body.preNodeId;
+        var preNodeNextId = req.body.preNodeNextId;
+        gljRepository.removeNodes(nodes, preNodeId, preNodeNextId, function(err,results){
+            callback(req,res, err, results)
+        });
+    },
+    getGljItems: function(req, res) {
+        var repId = req.body.repositoryId,
+            gljType = req.body.type,
+            gljCode = req.body.code;
+        if (gljCode) {
+            gljRepository.getGljItem(repId, gljCode, function(err, data){
+                callback(req,res,err,'Get Items', data)
+            });
+        } else if (gljType) {
+            gljRepository.getGljItemByType(repId, gljType, function(err, data){
+                callback(req,res,err,'Get Types', data)
+            });
+        } else {
+            gljRepository.getGljItemsByRep(repId, function(err, data){
+                callback(req,res,err,'Get Items',data)
+            });
+        }
+    },
+    getGljItemsByIds: function(req, res) {
+        var gljIds = JSON.parse(req.body.gljIds);
+        gljRepository.getGljItems(gljIds, function(err, data){
+            callback(req,res,err,'Get Items',data)
+        });
+    },
+    getGljItemsByCodes: function(req, res) {
+        var gljCodes = JSON.parse(req.body.gljCodes),
+            repId = req.body.repId;
+        gljRepository.getGljItemsByCode(repId, gljCodes, function(err, data){
+            callback(req,res,err,'Get Items',data)
+        });
+    },
+    mixUpdateGljItems: function(req, res){
+        var repId = req.body.repositoryId,
+            updateItems = JSON.parse(req.body.updateItems),
+            addItems = JSON.parse(req.body.addItems),
+            removeIds = JSON.parse(req.body.removeIds);
+        gljRepository.mixUpdateGljItems(repId, updateItems, addItems, removeIds, function(err, message, rst){
+            if (err) {
+                callback(req, res, err, message, null);
+            } else {
+                callback(req, res, err, message, rst);
+            }
+        });
+    }
+
+}

+ 30 - 0
modules/ration_repository/controllers/search_controller.js

@@ -0,0 +1,30 @@
+/**
+ * Created by Mai on 2017/6/5.
+ */
+var rationItem = require('../models/ration_item');
+var callback = function(req, res, err, message, data){
+    res.json({error: err, message: message, data: data});
+};
+
+module.exports = {
+    getRationItem: function (req, res) {
+        var rId = req.body.rationLibId, code = req.body.code;
+        var rationData = {}
+        rationItem.getRationItem(rId, code).then(function (result) {
+            rationData = result._doc;
+            callback(req, res, null, '', rationData);
+        }).catch(function (err, message) {
+            callback(req, res, err, message, null);
+        })
+    },
+    findRation: function (req, res) {
+        var rId = req.body.rationLibId, keyword = req.body.keyword;
+        rationItem.findRation(rId, keyword, function (err, message, rst) {
+            if (err) {
+                callback(req, res, err, message, null);
+            } else {
+                callback(req, res, 0, '', rst);
+            }
+        });
+    }
+};

+ 134 - 0
modules/ration_repository/models/coe.js

@@ -0,0 +1,134 @@
+/**
+ * Created by CSL on 2017/5/3.
+ * 系数表。
+ */
+
+var mongoose = require("mongoose");
+var dbm = require("../../../config/db/db_manager");
+var db = dbm.getCfgConnection("rationRepository");
+var counter = require('../../../public/counter/counter');
+
+var coeSchema = mongoose.Schema({
+    coeType: String,                // 系数类型,指作用范围:
+                                    // 单个(如:111量0.001)、人工类、材料类、机械类、全部(如:定额×0.925)。
+    gljID: Number,                  // 要调整的工料机ID(当coeType=0时有效)
+    operator: String,               // 运算符(*、+、-、=)
+    amount: String,                 // 调整的量
+    _id: false
+});
+
+var coeListSchema = mongoose.Schema({
+    libID: Number,                      // 所属定额定ID
+    ID: Number,                         // 系数ID(流水号ID)
+    name: String,                       // 名称
+    content: String,                    // 说明
+    coes: [coeSchema]
+}, {versionKey: false});
+
+var coeListModel = db.model("coeLists",coeListSchema, "coeLists")
+
+var coeListDAO = function(){};
+
+coeListDAO.prototype.getCoe = function (data, callback) {
+    coeListModel.findOne({
+            "libID": data.libID,
+            "ID": data.ID,
+            "$or": [{"isDeleted": null}, {"isDeleted": false}]
+        },
+        function (err, doc) {
+            if (err) callback("获取系数明细错误!", null)
+            else callback(null, doc);
+        })
+};
+
+coeListDAO.prototype.getCoeItemsByIDs = function (data, callback) {
+    coeListModel.find({
+            "libID": data.libID,
+            "ID": {"$in":data.coeIDs}
+        }, ["libID","ID","name","content","-_id"],
+        function (err, doc) {
+            if (err) callback("批量获取系数明细错误!", null)
+            else callback(null, doc);
+        })
+};
+
+coeListDAO.prototype.getCoesByLibID = function (libID, callback) {
+    coeListModel.find({ "libID": libID },
+        function (err, doc) {
+            if (err) callback("获取定额库系数表错误", null)
+            else callback(null, doc);
+        })
+};
+
+coeListDAO.prototype.saveToCoeList = function(data, callback) {
+    var me = this;
+    if (data.addArr.length > 0) {
+        me.addItems(data.addArr, callback);
+    };
+
+    if (data.deleteArr.length > 0) {
+        me.deleteItems(data.deleteArr, callback);
+    };
+
+    if (data.updateArr.length > 0) {
+        me.updateItems(data.updateArr, callback);
+    };
+};
+
+coeListDAO.prototype.addItems = function(addArr, callback) {
+    if (addArr && addArr.length > 0) {
+        counter.counterDAO.getIDAfterCount(counter.moduleName.coeList, addArr.length, function(err, result){
+            var maxId = result.value.sequence_value;
+            for (var i = 0; i < addArr.length; i++) {
+                var obj = new coeListModel(addArr[i]);
+                obj.ID = (maxId - (addArr.length - 1) + i);
+                coeListModel.create(obj, function(err) {
+                    if (err) {
+                        callback(true, "add fail", null);
+                    } else {
+                        callback(false, "add success", obj.ID);
+                    };
+                });
+            };
+
+        });
+    } else {
+        callback(true, "no source", null);
+    };
+};
+
+coeListDAO.prototype.updateItems = function(updateArr, callback) {
+    if (updateArr && updateArr.length > 0) {
+            for (var i = 0; i < updateArr.length; i++) {
+                var obj = updateArr[i];
+                coeListModel.update({"libID": obj.libID, "ID": obj.ID}, updateArr[i], function(err) {
+                    if (err) {
+                        callback(true, "update fail", null);
+                    } else {
+                        callback(false, "update success", obj.ID);
+                    };
+                });
+            };
+    } else {
+        callback(true, "no source", null);
+    };
+};
+
+coeListDAO.prototype.deleteItems = function(deleteArr, callback) {
+    if (deleteArr && deleteArr.length > 0) {
+        for (var i = 0; i < deleteArr.length; i++) {
+            var obj = deleteArr[i];
+            coeListModel.remove({"libID": obj.libID, "ID": obj.ID}, function(err) {
+                if (err) {
+                    callback(true, "delete fail", null);
+                } else {
+                    callback(false, "delete success", obj.ID);
+                };
+            });
+        };
+    } else {
+        callback(true, "no source", null);
+    };
+};
+
+module.exports = new coeListDAO();

+ 220 - 0
modules/ration_repository/models/glj_repository.js

@@ -0,0 +1,220 @@
+/**
+ * Created by Tony on 2017/5/4.
+ * 工料机的总库,根据不同定额库分类,参考原gljList表
+ */
+
+var mongoose = require("mongoose");
+var dbm = require("../../../config/db/db_manager");
+var db = dbm.getCfgConnection("rationRepository")
+var async = require("async");
+var Schema = mongoose.Schema;
+
+var gljTypeSchema = mongoose.Schema({
+    repositoryId: Number,
+    ID: Number,
+    ParentID: Number,
+    NextSiblingID: Number,
+    Name: String,
+    isDeleted: Boolean
+});
+
+var gljSchema = mongoose.Schema({
+    repositoryId: Number,
+    ID:Number,
+    //以下是基于已有access库
+    code: String,
+    name: String,
+    specs: String,
+    unit: String,
+    basePrice: Number,
+    gljType: Number, //这个是UI显示上的详细分类,对应gljTypeSchema
+    gljDistType: String  //人工,材料,机械
+});
+var gljTypeModel = db.model("gljType",gljTypeSchema, "gljType");
+var gljItemModel = db.model("gljRepository",gljSchema, "gljRepository");
+var repositoryMap = require('./repository_map');
+var counter = require('../../../public/counter/counter');
+
+var gljItemDAO = function(){};
+gljItemDAO.prototype.getGljTypes = function(rationLibId, callback){
+    gljTypeModel.find({"repositoryId": rationLibId, "$or": [{"isDeleted": null}, {"isDeleted": false} ]},function(err,data){
+        if(data.length) callback(false,data);
+        else  if(err) callback("获取工料机类型错误!",false)
+        else callback(false,false);
+    })
+};
+
+gljItemDAO.prototype.getGljItemsByRep = function(repositoryId,callback){
+    gljItemModel.find({"repositoryId": repositoryId},function(err,data){
+        if(err) callback(true, "")
+        else callback(false,data);
+    })
+};
+
+gljItemDAO.prototype.getGljItemByType = function(repositoryId, type, callback){
+    gljItemModel.find({"repositoryId": repositoryId, "gljType": type},function(err,data){
+        if(err) callback(true, "")
+        else callback(false, data);
+    })
+};
+
+gljItemDAO.prototype.getGljItem = function(repositoryId, code, callback){
+    gljItemModel.find({"repositoryId": repositoryId, "code": code},function(err,data){
+        if(err) callback(true, "")
+        else callback(false, data);
+    })
+};
+
+gljItemDAO.prototype.getGljItems = function(gljIds, callback){
+    gljItemModel.find({"ID": {"$in": gljIds}},function(err,data){
+        if(err) callback(true, "")
+        else callback(false, data);
+    })
+};
+
+gljItemDAO.prototype.getGljItemsByCode = function(repositoryId, codes, callback){
+    gljItemModel.find({"repositoryId": repositoryId,"code": {"$in": codes}},function(err,data){
+        if(err) callback(true, "")
+        else callback(false, data);
+    })
+};
+
+gljItemDAO.prototype.mixUpdateGljItems = function(repId, updateItems, addItems, rIds, callback) {
+    var me = this;
+    if (updateItems.length == 0 && rIds.length == 0) {
+        me.addGljItems(repId, addItems, callback);
+    } else {
+        me.removeGljItems(rIds, function(err, message, docs) {
+            me.updateGljItems(repId, updateItems, function(err, results){
+                if (err) {
+                    callback(true, "Fail to update", false);
+                } else {
+                    if (addItems && addItems.length > 0) {
+                        me.addGljItems(repId, addItems, callback);
+                    } else {
+                        callback(false, "Save successfully", results);
+                    }
+                }
+            });
+        });
+    }
+};
+
+gljItemDAO.prototype.removeGljItems = function(rIds, callback) {
+    if (rIds && rIds.length > 0) {
+        gljItemModel.collection.remove({ID: {$in: rIds}}, null, function(err, docs){
+            if (err) {
+                callback(true, "Fail to remove", false);
+            } else {
+                callback(false, "Remove successfully", docs);
+            }
+        })
+    } else {
+        callback(false, "No records were deleted!", null);
+    }
+};
+
+gljItemDAO.prototype.addGljItems = function(repId, items, callback) {
+    if (items && items.length > 0) {
+        counter.counterDAO.getIDAfterCount(counter.moduleName.GLJ, items.length, function(err, result){
+            var maxId = result.value.sequence_value;
+            var arr = [];
+            for (var i = 0; i < items.length; i++) {
+                var obj = new gljItemModel(items[i]);
+                obj.ID = (maxId - (items.length - 1) + i);
+                obj.repositoryId = repId;
+                arr.push(obj);
+            }
+            gljItemModel.collection.insert(arr, null, function(err, docs){
+                if (err) {
+                    callback(true, "Fail to add", false);
+                } else {
+                    callback(false, "Add successfully", docs);
+                }
+            })
+        });
+    } else {
+        callback(true, "No source", false);
+    }
+};
+
+gljItemDAO.prototype.updateGljItems = function(repId, items, callback) {
+    var functions = [];
+    for (var i=0; i < items.length; i++) {
+        functions.push((function(doc) {
+            return function(cb) {
+                var filter = {};
+                if (doc.ID) {
+                    filter.ID = doc.ID;
+                } else {
+                    filter.repositoryId = repId;
+                    filter.code = doc.code;
+                }
+                gljItemModel.update(filter, doc, cb);
+            };
+        })(items[i]));
+    }
+    async.parallel(functions, function(err, results) {
+        callback(err, results);
+    });
+};
+
+gljItemDAO.prototype.updateNodes = function(nodes, callback) {
+    var functions = [];
+    for (var i=0; i < nodes.length; i++) {
+        functions.push((function(doc) {
+            return function(cb) {
+                gljTypeModel.update({ID: doc.ID}, doc, cb);
+            };
+        })(nodes[i]));
+    }
+    async.parallel(functions, function(err, results) {
+        callback(err, results);
+    });
+};
+gljItemDAO.prototype.removeNodes =  function(nodeIds, preNodeId, preNodeNextId, callback){
+    var functions = [];
+    if (preNodeId != -1) {
+        functions.push((function(nodeId, nextId) {
+            return function(cb) {
+                gljTypeModel.update({ID: nodeId}, {"NextSiblingID": nextId}, cb);
+            };
+        })(preNodeId, preNodeNextId));
+    }
+    for (var i=0; i < nodeIds.length; i++) {
+        functions.push((function(nodeId) {
+            return function(cb) {
+                gljTypeModel.update({ID: nodeId}, {"isDeleted": true}, cb);
+            };
+        })(nodeIds[i]));
+    }
+    async.parallel(functions, function(err, results) {
+        callback(err, results);
+    });
+};
+
+gljItemDAO.prototype.createNewNode = function(repId, lastNodeId, nodeData, callback) {
+    return counter.counterDAO.getIDAfterCount(counter.moduleName.GLJ, 1, function(err, result){
+        nodeData.repositoryId = repId;
+        nodeData.ID = result.value.sequence_value;
+        var node = new gljTypeModel(nodeData);
+        node.save(function (err, result) {
+            if (err) {
+                callback(true, "章节树ID错误!", false);
+            } else {
+                if (lastNodeId > 0) {
+                    gljTypeModel.update({ID: lastNodeId}, {"NextSiblingID": nodeData.ID}, function(err, rst){
+                        if (err) {
+                            callback(true, "章节树ID错误!", false);
+                        } else {
+                            callback(false, '', result);
+                        }
+                    });
+                } else callback(false, '', result);
+            }
+        });
+    });
+};
+
+module.exports = new gljItemDAO();
+

+ 45 - 0
modules/ration_repository/models/rationAssist.js

@@ -0,0 +1,45 @@
+/**
+ * Created by CSL on 2017/5/5.
+ * 辅助定额调整。
+ */
+
+var mongoose = require("mongoose");
+var dbm = require("../../../config/db/db_manager");
+var db = dbm.getCfgConnection("rationRepository")
+
+// eg:重庆CQJZDE-2008,P28,AA0116机械装运土方全程运距100米内(主定额)20米内(会根据用户实际录入值变化),AA0117每增加10米(辅助定额)。
+// 建筑中的主定额只有一条辅助定额。(公路的主定额会对应多条辅助定额)
+var assistSchema = mongoose.Schema({
+    libID: Number,                      // 所属定额定ID
+    mainRationID: Number,               // 主定额ID
+    assistRationID: Number,             // 辅助定额ID
+    assistDisplayName: String,          // 辅助定额显示名称 (eg:每增加10米)
+    minValue: String,                   // 下限值(eg:20)
+    maxValue: String,                   // 上限值(eg:100,也可能没有)
+    stepValue: String                   // 步距值 (eg:10)
+});
+
+var assistModel = db.model("rationAssists",assistSchema, "rationAssists")
+
+var assistDAO = function(){};
+
+assistDAO.prototype.getAssist = function (data, callback) {
+    assistModel.findOne({
+            "libID": data.libID,
+            "mainRationID": data.mainRationID,
+            "$or": [{"isDeleted": null}, {"isDeleted": false}]
+        },
+        function (err, doc) {
+            if (err) callback(true, "获取辅助定额错误!", "")
+            else callback(false, "获取辅助定额成功", doc);
+        })
+};
+
+// test datas.
+//function callbackExec(err) {if (err) {console.log(err);} else {console.log('saved.')};};
+//assistModel.create({"libID": 1, "mainRationID":1, assistRationID: 2, assistDisplayName: "每增加10米", minValue:"20", maxValue: "100", stepValue:"10"}, callbackExec);
+//assistModel.create({"libID": 1, "mainRationID":3, assistRationID: 4, assistDisplayName: "每增加100米", minValue:"200", maxValue: "500", stepValue:"100"}, callbackExec);
+//assistModel.create({"libID": 1, "mainRationID":5, assistRationID: 6, assistDisplayName: "每增加100米", minValue:"1000", maxValue: null, stepValue:"1000"}, callbackExec);
+
+
+module.exports = new assistDAO();

+ 35 - 0
modules/ration_repository/models/rationCoe.js

@@ -0,0 +1,35 @@
+/**
+ * Created by CSL on 2017/5/3.
+ * 定额系数关系表。(即附注条件。系数会被定额公用,如同一个分枝下的兄弟定额。)
+ * 公路上,定额章节点上也会挂系数(关系数据库可减少数据冗余),该系数作用于该章节下的所有定额。每条定额还有自己特有的系数。
+ * 建筑上,简化逻辑设计,把章节点上的系数移到具体的定额上。
+ */
+var mongoose = require("mongoose");
+var dbm = require("../../../config/db/db_manager");
+var db = dbm.getCfgConnection("rationRepository")
+
+var rationCoeSchema = mongoose.Schema({
+    ID:Number,
+    libID: Number,
+    rationID: Number,
+    coeIDs: Array
+});
+
+var rationCoeModel = db.model("rationCoes",rationCoeSchema, "rationCoes")
+
+var rationCoeDAO = function(){};
+
+rationCoeDAO.prototype.getRationCoes = function (data, callback) {
+    rationCoeModel.findOne({
+            "libID": data.libID,
+            "rationID": data.rationID,
+            "$or": [{"isDeleted": null}, {"isDeleted": false}]
+        },
+        function (err, doc) {
+            if (err) callback(true, "获取定额调整系数错误!", "")
+            else callback(false, "获取定额调整系数成功", doc);
+        })
+};
+
+module.exports = new rationCoeDAO();
+

+ 168 - 0
modules/ration_repository/models/ration_item.js

@@ -0,0 +1,168 @@
+/**
+ * Created by Tony on 2017/4/28.
+ */
+var mongoose = require("mongoose");
+var dbm = require("../../../config/db/db_manager");
+var db = dbm.getCfgConnection("rationRepository")
+var async = require("async");
+var Schema = mongoose.Schema;
+
+var rationGljItemSchema = mongoose.Schema({
+    gljId: Number,
+    consumeAmt: Number,
+    proportion: Number //配合比,暂时无需使用,默认0
+}, { _id: false });
+
+var rationItemSchema = mongoose.Schema({
+    ID:Number,
+    code: String,
+    name: String,
+    unit: String,
+    basePrice: Number,
+    sectionId: Number,
+    rationRepId: Number,
+    caption: String,
+    feeType: Number,
+    rationGljList: [rationGljItemSchema],
+    rationCoeList: Array
+});
+var rationItemModel = db.model("rationItems",rationItemSchema, "rationItems")
+var counter = require('../../../public/counter/counter');
+
+var rationItemDAO = function(){};
+
+rationItemDAO.prototype.getRationItemsBySection = function(sectionId,callback){
+    rationItemModel.find({"sectionId": sectionId, "$or": [{"isDeleted": null}, {"isDeleted": false} ]},function(err,data){
+        if(err) callback(true, "Fail to get items", "")
+        else callback(false,"Get items successfully", data);
+    })
+};
+rationItemDAO.prototype.mixUpdateRationItems = function(rationLibId, sectionId, updateItems, addItems, rIds, callback){
+    var me = this;
+    if (updateItems.length == 0 && rIds.length == 0) {
+        me.addRationItems(rationLibId, sectionId, addItems, callback);
+    } else {
+        me.removeRationItems(rIds, function(err, message, docs) {
+            if (err) {
+                callback(true, "Fail to remove", false);
+            } else {
+                me.updateRationItems(rationLibId, sectionId, updateItems, function(err, results){
+                    if (err) {
+                        callback(true, "Fail to save", false);
+                    } else {
+                        if (addItems && addItems.length > 0) {
+                            me.addRationItems(rationLibId, sectionId, addItems, callback);
+                        } else {
+                            callback(false, "Save successfully", results);
+                        }
+                    }
+                });
+            }
+        })
+    }
+};
+
+rationItemDAO.prototype.removeRationItems = function(rIds,callback){
+    if (rIds.length > 0) {
+        rationItemModel.collection.remove({ID: {$in: rIds}}, null, function(err, docs){
+            if (err) {
+                callback(true, "Fail to remove", false);
+            } else {
+                callback(false, "Remove successfully", docs);
+            }
+        })
+    } else {
+        callback(false, "No records were deleted!", null);
+    }
+};
+
+rationItemDAO.prototype.getRationItemsByCode = function(repId, code,callback){
+    rationItemModel.find({"rationRepId": repId, "code": {'$regex': code, $options: '$i'}, "$or": [{"isDeleted": null}, {"isDeleted": false}]},function(err,data){
+        if(err) callback(true, "Fail to get items", "")
+        else callback(false,"Get items successfully", data);
+    })
+};
+
+rationItemDAO.prototype.findRation = function (repId, keyword, callback) {
+    var filter = {
+        'rationRepId': repId,
+        '$and': [{
+            '$or': [{'code': {'$regex': keyword, $options: '$i'}}, {'name': {'$regex': keyword, $options: '$i'}}]
+        }, {
+            '$or': [{'isDeleted': {"$exists":false}}, {'isDeleted': null}, {'isDeleted': false}]
+        }]
+    };
+    rationItemModel.find(filter, function (err, data) {
+        if (err) {
+            callback(true, 'Fail to find ration', null);
+        } else {
+            callback(false, '', data);
+        }
+    })
+}
+
+rationItemDAO.prototype.getRationItem = function (repId, code, callback) {
+    if (callback) {
+        rationItemModel.findOne({rationRepId: repId, code: code, "$or": [{"isDeleted": null}, {"isDeleted": false}]}, '-_id').exec()
+            .then(function (result, err) {
+                if (err) {
+                    callback(1, '找不到定额“' + code +'”' , null);
+                } else {
+                    callback(0, '', result);
+                }
+            });
+        return null;
+    } else {
+        return rationItemModel.findOne({rationRepId: repId, code: code, "$or": [{"isDeleted": null}, {"isDeleted": false}]}, '-_id').exec();
+    }
+};
+
+rationItemDAO.prototype.addRationItems = function(rationLibId, sectionId, items,callback){
+    if (items && items.length > 0) {
+        counter.counterDAO.getIDAfterCount(counter.moduleName.rations, items.length, function(err, result){
+            var maxId = result.value.sequence_value;
+            var arr = [];
+            for (var i = 0; i < items.length; i++) {
+                var obj = new rationItemModel(items[i]);
+                obj.ID = (maxId - (items.length - 1) + i);
+                obj.sectionId = sectionId;
+                obj.rationRepId = rationLibId;
+                arr.push(obj);
+            }
+            rationItemModel.collection.insert(arr, null, function(err, docs){
+                if (err) {
+                    callback(true, "Fail to save", false);
+                } else {
+                    callback(false, "Save successfully", docs);
+                }
+            })
+        });
+    } else {
+        callback(true, "Source error!", false);
+    }
+};
+
+rationItemDAO.prototype.updateRationItems = function(rationLibId, sectionId, items,callback){
+    var functions = [];
+    for (var i=0; i < items.length; i++) {
+        functions.push((function(doc) {
+            return function(cb) {
+                var filter = {};
+                if (doc.ID) {
+                    filter.ID = doc.ID;
+                } else {
+                    filter.sectionId = sectionId;
+                    if (rationLibId) filter.rationRepId = rationLibId;
+                    filter.code = doc.code;
+                }
+                rationItemModel.update(filter, doc, cb);
+            };
+        })(items[i]));
+    }
+    async.parallel(functions, function(err, results) {
+        callback(err, results);
+    });
+};
+
+module.exports = new rationItemDAO()
+

+ 100 - 0
modules/ration_repository/models/ration_section_tree.js

@@ -0,0 +1,100 @@
+/**
+ * Created by Tony on 2017/4/21.
+ */
+
+var mongoose = require("mongoose");
+var dbm = require("../../../config/db/db_manager");
+var chapterTreeDb = dbm.getCfgConnection("rationRepository")
+var async = require("async");
+var Schema = mongoose.Schema;
+
+var rationChapterTreeSchema = new Schema({//章节树  //生成唯一id改为sectionID  改成string
+    rationRepId: Number,
+    ID:Number,
+    ParentID:Number,
+    NextSiblingID:Number,
+    name: String,
+    isDeleted: Boolean
+});
+var rationChapterTreeModel = chapterTreeDb.model("rationChapterTrees",rationChapterTreeSchema, "rationChapterTrees")
+var counter = require('../../../public/counter/counter');
+
+var rationChapterTreeDAO = function(){};
+
+rationChapterTreeDAO.prototype.getRationChapterTree = function(rationLibId,callback){
+    rationChapterTreeModel.find({"rationRepId": rationLibId, "$or": [{"isDeleted": null}, {"isDeleted": false} ]},function(err,data){
+        if(data.length) callback(false,data);
+        else  if(err) callback("获取定额树错误!",false)
+        else callback(false,false);
+    })
+}
+
+rationChapterTreeDAO.prototype.getRationChapterTreeByRepId = function(repId,callback){
+    rationChapterTreeModel.find({"rationRepId": repId, "$or": [{"isDeleted": null}, {"isDeleted": false} ]},function(err,data){
+        if(data.length) callback(false,data);
+        else  if(err) callback("获取定额树错误!",false)
+        else callback(false,false);
+    })
+}
+
+rationChapterTreeDAO.prototype.createNewNode = function(libId, lastNodeId, nodeData,callback){
+    counter.counterDAO.getIDAfterCount(counter.moduleName.rationTree, 1, function(err, result){
+        nodeData.rationRepId = libId;
+        nodeData.ID = result.value.sequence_value;
+        var node = new rationChapterTreeModel(nodeData);
+        node.save(function (err, result) {
+            if (err) {
+                callback("章节树ID错误!", false);
+            } else {
+                if (lastNodeId > 0) {
+                    rationChapterTreeModel.update({ID: lastNodeId}, {"NextSiblingID": nodeData.ID}, function(err, rst){
+                        if (err) {
+                            callback("章节树ID错误!", false);
+                        } else {
+                            callback(false, result);
+                        }
+                    });
+                } else callback(false, result);
+            }
+        });
+    });
+},
+
+rationChapterTreeDAO.prototype.removeNodes = function(nodeIds, preNodeId, preNodeNextId, callback){
+    var functions = [];
+    if (preNodeId != -1) {
+        functions.push((function(nodeId, nextId) {
+            return function(cb) {
+                rationChapterTreeModel.update({ID: nodeId}, {"NextSiblingID": nextId}, cb);
+            };
+        })(preNodeId, preNodeNextId));
+    }
+    for (var i=0; i < nodeIds.length; i++) {
+        functions.push((function(nodeId) {
+            return function(cb) {
+                rationChapterTreeModel.update({ID: nodeId}, {"isDeleted": true}, cb);
+            };
+        })(nodeIds[i]));
+    }
+    async.parallel(functions, function(err, results) {
+        callback(err, results);
+    });
+}
+
+rationChapterTreeDAO.prototype.updateNodes = function(nodes,callback){
+    var functions = [];
+    for (var i=0; i < nodes.length; i++) {
+        //var md = new rationChapterTreeModel(nodes[i]);
+        //md.isNew = false;
+        functions.push((function(doc) {
+            return function(cb) {
+                rationChapterTreeModel.update({ID: doc.ID}, doc, cb);
+            };
+        })(nodes[i]));
+    }
+    async.parallel(functions, function(err, results) {
+        callback(err, results);
+    });
+};
+
+module.exports = new rationChapterTreeDAO()

+ 120 - 0
modules/ration_repository/models/repository_map.js

@@ -0,0 +1,120 @@
+/**
+ * Created by Tony on 2017/4/24.
+ * 重新构造,不适宜生成多个定额库db,还是得统一在一个表
+ */
+var mongoose = require('mongoose');
+var dbm = require("../../../config/db/db_manager");
+//var stringUtil = require('../../../public/stringUtil');
+var rationLibdb = dbm.getCfgConnection("rationRepository");
+var Schema = mongoose.Schema;
+var RepositoryMapSchema = new Schema({
+    "ID": Number,
+    "dispName" : String,
+    "appType" : String, //如:"建筑" / "公路"
+    "localeType": String, //如 各个省份 / 部颁
+    "descr" : String,
+    "deleted": Boolean
+});
+var counter = require('../../../public/counter/counter');
+
+var rationRepository = rationLibdb.model("repositoryMap", RepositoryMapSchema, "repositoryMap");
+
+function createNewLibModel(rationLibObj){
+    var rst = {};
+    rst.dispName = rationLibObj.dispName;
+    rst.appType = rationLibObj.appType?rationLibObj.appType:'construct';
+    rst.localeType = rationLibObj.localeType?rationLibObj.localeType:'';
+    rst.descr = rationLibObj.descr;
+    rst.deleted = false;
+    return rst;
+}
+
+rationRepositoryDao = function(){};
+
+rationRepositoryDao.prototype.getRealLibName = function(dispName,callback){
+    if (callback) {
+        rationRepository.find({"dispName": dispName}, function(err,data){
+            if (err) {
+                callback('Error', null);
+            } else {
+                callback(false, data);
+            }
+        });
+    } else {
+        var rst = rationRepository.find({"dispName": dispName}).exec();
+        return rst;
+    }
+};
+
+rationRepositoryDao.prototype.getLibIDByName = function(dispName, callback){
+    rationRepository.findOne({"dispName": dispName}, function(err,data){
+        if (err) {
+            callback('Error', null);
+        } else {
+            callback(false, data.ID);
+        }
+    });
+};
+
+rationRepositoryDao.prototype.getRepositoryById = function(repId,callback){
+    if (callback) {
+        rationRepository.find({"ID": repId}, function(err,data){
+            if (err) {
+                callback('Error', null);
+            } else {
+                callback(false, data);
+            }
+        });
+    } else {
+        var rst = rationRepository.find({"ID": repId}).exec();
+        return rst;
+    }
+};
+
+rationRepositoryDao.prototype.addRationRepository = function( rationLibObj,callback){
+    counter.counterDAO.getIDAfterCount(counter.moduleName.rationMap, 1, function(err, result){
+        var rMap = createNewLibModel(rationLibObj);
+        rMap.ID = result.value.sequence_value;
+        new rationRepository(rMap).save(function(err, result) {
+            if (err) callback("Error", null)
+            else
+                callback(false, result);
+        });
+    });
+};
+
+rationRepositoryDao.prototype.getDisplayRationLibs = function(callback) {
+    rationRepository.find({"deleted": false}, function(err, data){
+        if (err) {
+            callback( 'Error', null);
+        } else {
+            callback( false, data);
+        }
+    });
+};
+
+rationRepositoryDao.prototype.updateName = function(orgName,newName,callback){
+    rationRepository.find({"dispName":newName, "deleted": false}, function(err, data){
+        if (data.length == 0) {
+            rationRepository.update({dispName:orgName},{$set:{dispName:newName}},function(err){
+                if(err) callback("err",false);
+                else callback(false,"ok")
+            })
+        } else
+            callback("不可重名!",false);
+    });
+}
+
+rationRepositoryDao.prototype.deleteRationLib = function(rationName,callback){
+    rationRepository.find({"dispName":rationName, "deleted": false}, function(err, data){
+        if (data.length == 1) {
+            rationRepository.update({dispName:rationName},{$set:{deleted: true}},function(err){
+                if(err) callback("err",false);
+                else callback(false,"ok")
+            })
+        } else
+            callback("删除失败!",false);
+    });
+}
+
+module.exports = new rationRepositoryDao();

+ 32 - 0
modules/ration_repository/routes/ration_fe_routes.js

@@ -0,0 +1,32 @@
+/**
+ * Created by Tony on 2017/6/15.
+ */
+var express = require("express");
+var apiRouter =express.Router();
+//var _rootDir = __dirname;
+
+var rationRepositoryController = require("../controllers/ration_repository_controller");
+var rationChapterTreeController = require("../controllers/ration_section_tree_controller");
+var rationController = require("../controllers/ration_controller");
+var repositoryGljController = require("../controllers/repository_glj_controller");
+var coeListController = require("../controllers/coe_controller");
+var searchController = require('../controllers/search_controller');
+
+apiRouter.post("/getRationDisplayNames",rationRepositoryController.getDisPlayRationLibs);
+apiRouter.post("/getRealLibName",rationRepositoryController.getRealLibName);
+apiRouter.post("/getLibIDByName",rationRepositoryController.getLibIDByName);
+
+apiRouter.post("/getRationTree",rationChapterTreeController.getRationChapterTree);
+
+apiRouter.post("/getRationItems",rationController.getRationItemsBySection);
+
+apiRouter.post("/getGljTree",repositoryGljController.getGljTree);
+apiRouter.post("/getGljItems",repositoryGljController.getGljItems);
+apiRouter.post("/getGljItemsByIds",repositoryGljController.getGljItemsByIds);
+apiRouter.post("/getGljItemsByCodes",repositoryGljController.getGljItemsByCodes);
+
+apiRouter.post("/getCoeList",coeListController.getCoeList);
+
+apiRouter.post('/findRation', searchController.findRation);
+
+module.exports = apiRouter;

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

@@ -0,0 +1,46 @@
+/**
+ * Created by Tony on 2017/4/20.
+ */
+var express = require("express");
+var apiRouter =express.Router();
+//var _rootDir = __dirname;
+
+var rationRepositoryController = require("../controllers/ration_repository_controller");
+var rationChapterTreeController = require("../controllers/ration_section_tree_controller");
+var rationController = require("../controllers/ration_controller");
+var repositoryGljController = require("../controllers/repository_glj_controller");
+var coeListController = require("../controllers/coe_controller");
+var searchController = require('../controllers/search_controller');
+
+apiRouter.post("/getRationDisplayNames",rationRepositoryController.getDisPlayRationLibs);
+apiRouter.post("/editRationLibs",rationRepositoryController.updateRationRepositoryName);
+apiRouter.post("/addRationRepository",rationRepositoryController.addRationRepository);
+apiRouter.post("/deleteRationLibs",rationRepositoryController.deleteRationLib);
+apiRouter.post("/getRealLibName",rationRepositoryController.getRealLibName);
+apiRouter.post("/getLibIDByName",rationRepositoryController.getLibIDByName);
+
+apiRouter.post("/getRationTree",rationChapterTreeController.getRationChapterTree);
+apiRouter.post("/createNewNode",rationChapterTreeController.createNewNode);
+apiRouter.post("/updateNodes",rationChapterTreeController.updateNodes);
+apiRouter.post("/deleteNodes",rationChapterTreeController.deleteNodes);
+
+apiRouter.post("/getRationItems",rationController.getRationItemsBySection);
+apiRouter.post("/mixUpdateRationItems",rationController.mixUpdateRationItems);
+
+apiRouter.post("/createNewGljTypeNode",repositoryGljController.createNewGljTypeNode);
+apiRouter.post("/updateGljNodes",repositoryGljController.updateGljNodes);
+apiRouter.post("/deleteGljNodes",repositoryGljController.deleteGljNodes);
+apiRouter.post("/getGljTree",repositoryGljController.getGljTree);
+apiRouter.post("/getGljItems",repositoryGljController.getGljItems);
+apiRouter.post("/mixUpdateGljItems",repositoryGljController.mixUpdateGljItems);
+apiRouter.post("/getGljItemsByIds",repositoryGljController.getGljItemsByIds);
+apiRouter.post("/getGljItemsByCodes",repositoryGljController.getGljItemsByCodes);
+
+apiRouter.post("/getCoeList",coeListController.getCoeList);
+apiRouter.post("/saveCoeList",coeListController.saveCoeList);
+apiRouter.post("/getCoeItemsByIDs",coeListController.getCoeItemsByIDs);
+
+apiRouter.post('/getRationItem', searchController.getRationItem);
+apiRouter.post('/findRation', searchController.findRation);
+
+module.exports = apiRouter;

+ 99 - 0
modules/reports/controllers/rpt_controller.js

@@ -0,0 +1,99 @@
+/**
+ * Created by Tony on 2017/3/13.
+ */
+
+let JV = require('../rpt_component/jpc_value_define');
+let Template = require('../models/rpt_template');
+let TemplateData = require('../models/rpt_tpl_data');
+let JpcEx = require('../rpt_component/jpc_ex');
+//let cache = require('../../../public/cache/cacheUtil');
+let rptUtil = require("../util/rpt_util");
+let rpt_xl_util = require('../util/rpt_excel_util');
+let fs = require('fs');
+let strUtil = require('../../../public/stringUtil');
+
+//统一回调函数
+let callback = function(req, res, err, data){
+    if(err){
+        res.json({success: false, error: err});
+    }
+    else{
+        //res.send({success: true, data: data});
+        res.json({success:true, data: data});
+    }
+};
+
+module.exports = {
+    getReportAllPages: function(req, res){
+        let grp_id = req.body.grp_id;
+        let tpl_id = req.body.tpl_id;
+        let pageSize = req.body.pageSize;
+        let rptTpl = null;
+        Template.getPromise(grp_id, tpl_id).then(function(rst) {
+            rptTpl = rst;
+            if (rptTpl) {
+                return TemplateData.getPromise(tpl_id);
+            } else {
+                callback(req, res, 'No report template was found!', null);
+            }
+        }).then(function(tplData){
+                if (tplData) {
+                    let printCom = JpcEx.createNew();
+                    rptTpl[JV.NODE_MAIN_INFO][JV.NODE_PAGE_INFO][JV.PROP_PAGE_SIZE] = pageSize;
+                    let defProperties = rptUtil.getReportDefaultCache();
+                    printCom.initialize(rptTpl);
+                    printCom.analyzeData(rptTpl, tplData, defProperties);
+                    let maxPages = printCom.totalPages;
+                    let pageRst = printCom.outputAsSimpleJSONPageArray(rptTpl, tplData, 1, maxPages, defProperties);
+                    if (pageRst) {
+                        callback(req, res, null, pageRst);
+                    } else {
+                        callback(req, res, "Have errors while on going...", null);
+                    }
+                } else {
+                    callback(req, res, 'No report data were found!', null);
+                }
+            }
+        );
+    },
+    getExcel: function(req, res) {
+        let grp_id = req.params.id, tpl_id = req.params.pm, pageSize = req.params.size, rptName = req.params.rptName;
+        let rptTpl = null;
+        Template.getPromise(grp_id, tpl_id).then(function(rst) {
+            rptTpl = rst;
+            if (rptTpl) {
+                return TemplateData.getPromise(tpl_id);
+            } else {
+                callback(req, res, 'No report template was found!', null);
+            }
+        }).then(function(tplData){
+                if (tplData) {
+                    let printCom = JpcEx.createNew();
+                    rptTpl[JV.NODE_MAIN_INFO][JV.NODE_PAGE_INFO][JV.PROP_PAGE_SIZE] = pageSize;
+                    let defProperties = rptUtil.getReportDefaultCache();
+                    printCom.initialize(rptTpl);
+                    printCom.analyzeData(rptTpl, tplData, defProperties);
+                    let maxPages = printCom.totalPages;
+                    let pageRst = printCom.outputAsSimpleJSONPageArray(rptTpl, tplData, 1, maxPages, defProperties);
+                    if (pageRst) {
+                        rpt_xl_util.exportExcel(pageRst, rptName, null, function(newName){
+                            res.setHeader('Content-Type', 'application/vnd.openxmlformats');
+                            res.setHeader("Content-Disposition", "attachment; filename=" + strUtil.getPinYinCamelChars(rptName) + ".xlsx");
+                            let filestream = fs.createReadStream(__dirname.slice(0, __dirname.length - 28) + '/tmp/' + newName + '.xlsx');
+                            filestream.on('data', function(chunk) {
+                                res.write(chunk);
+                            });
+                            filestream.on('end', function() {
+                                res.end();
+                            });
+                        });
+                    } else {
+                        callback(req, res, "Have errors while on going...", null);
+                    }
+                } else {
+                    callback(req, res, 'No report data were found!', null);
+                }
+            }
+        );
+    }
+};

+ 46 - 0
modules/reports/controllers/rpt_tpl_controller.js

@@ -0,0 +1,46 @@
+/**
+ * Created by Tony on 2017/6/1.
+ */
+
+let TplNode = require('../models/tpl_tree_node');
+
+//统一回调函数
+let callback = function(req, res, err, message, data){
+    res.json({error: err, message: message, data: data});
+}
+
+module.exports = {
+    getRptTplTree: function(req, res) {
+        let params = JSON.parse(req.body.params),
+            grpType = params.grpType,
+            userId = params.userId,
+            tplType = params.tplType;
+        TplNode.getTplTreeNodes(grpType, userId, tplType, function(err, data){
+            callback(req,res,err,"", data);
+        })
+    },
+    updateTreeNodes: function(req, res) {
+        let params = JSON.parse(req.body.params),
+            nodes = params.nodes;
+        TplNode.updateTreeNodes(nodes, function(err,results){
+            callback(req,res, err, "", results)
+        });
+    },
+    deleteTptTplNodes: function(req, res){
+        let params = JSON.parse(req.body.params),
+            nodeIds = params.nodeIds,
+            preNodeId = params.preNodeId,
+            preNodeNextId = params.preNodeNextId;
+        TplNode.removeNodes(nodeIds, preNodeId, preNodeNextId, function(err,results){
+            callback(req,res, err, "", results)
+        });
+    },
+    createTplTreeNode: function(req, res){
+        let params = JSON.parse(req.body.params),
+            lastNodeId = params.lastNodeId,
+            nodeData = params.rawNodeData;
+        TplNode.createTplTreeNode(nodeData, lastNodeId, function(err, data){
+            callback(req,res,err,"", data);
+        })
+    }
+}

+ 72 - 0
modules/reports/models/rpt_cfg.js

@@ -0,0 +1,72 @@
+/**
+ * Created by Tony on 2017/6/14.
+ */
+let mongoose = require('mongoose');
+let dbm = require("../../../config/db/db_manager");
+let smartcostdb = dbm.getCfgConnection("Reports");
+let Schema = mongoose.Schema;
+let FormatSchema = new Schema({
+    "ID" : String,
+    "CfgDispName": String,
+    "Shrink" : String,
+    "ShowZero" : String,
+    "Horizon" : String,
+    "Vertical" : String,
+    "Wrap" : String
+});
+let FontSchema = new Schema({
+    "ID" : String,
+    "Name" : String,
+    "CfgDispName": String,
+    "FontHeight" : String,
+    "FontColor" : String,
+    "FontBold" : String,
+    "FontItalic" : String,
+    "FontUnderline" : String,
+    "FontStrikeOut" : String,
+    "FontAngle" : String
+});
+let LineSchema = new Schema({
+    "Position": String,
+    "LineWeight": String,
+    "DashStyle": String,
+    "Color": String
+});
+let StyleSchema = new Schema({
+    "ID" : String,
+    "CfgDispName": String,
+    "border_style" : [LineSchema]
+});
+let RptCfgSchema = new Schema({
+    "userId" : String,
+    "fonts": [FontSchema],
+    "borders" : [StyleSchema],
+    "formats" : [FormatSchema]
+});
+
+let Rpt_Cfg_Mdl = smartcostdb.model("rpt_cfg", RptCfgSchema, "rpt_cfg");
+
+class RptCfgDAO {
+    getByUserId(userId, callback){
+        Rpt_Cfg_Mdl.find({userId: userId}, '-_id', function(err, templates){
+            if(templates.length){
+                callback(false, templates[0]);
+            }
+            else{
+                callback('no record was found!');
+            }
+        })
+    };
+    getAll(id, callback){
+        Rpt_Cfg_Mdl.find({}, '-_id', function(err, templates){
+            if(templates.length){
+                callback(false, templates);
+            }
+            else{
+                callback('no record was found!');
+            }
+        })
+    };
+};
+
+module.exports = new RptCfgDAO();

+ 40 - 0
modules/reports/models/rpt_template.js

@@ -0,0 +1,40 @@
+/**
+ * Created by Tony on 2016/12/23.
+ */
+let mongoose = require('mongoose');
+let dbm = require("../../../config/db/db_manager");
+let smartcostdb = dbm.getCfgConnection("Reports");
+let Schema = mongoose.Schema;
+let RptTemplateSchema = new Schema({
+    "GROUP_KEY": String,
+    "ID_KEY": String,
+    "主信息": Schema.Types.Mixed,
+    "指标_数据_映射": Schema.Types.Mixed,
+    "布局框_集合": Array,
+    "流水式表_信息": Schema.Types.Mixed,
+    "交叉表_信息": Schema.Types.Mixed,
+    "无映射离散指标_集合": Schema.Types.Mixed,
+    "离散参数_集合": Schema.Types.Mixed,
+    "计算式_集合": Array
+});
+
+let Template = smartcostdb.model("rpt_templates", RptTemplateSchema, "rpt_templates");
+
+class RplTplDAO{
+    get(grpId, id, callback){
+        Template.find({GROUP_KEY: grpId, ID_KEY: id}, '-_id', function(err, templates){
+            if(templates.length){
+                callback(false, templates[0]);
+            }
+            else{
+                callback('查找不到报表模板!');
+            }
+        })
+    };
+    getPromise(grpId, id){
+        let rst = Template.findOne({GROUP_KEY: grpId, ID_KEY: id}, '-_id').exec() ;
+        return rst;
+    }
+}
+
+module.exports = new RplTplDAO();

+ 34 - 0
modules/reports/models/rpt_tpl_data.js

@@ -0,0 +1,34 @@
+/**
+ * Created by Tony on 2016/12/28.
+ */
+let mongoose = require('mongoose');
+let dbm = require("../../../config/db/db_manager");
+let smartcostdb = dbm.getCfgConnection("Reports");
+let Schema = mongoose.Schema;
+let RptTemplateDataSchema = new Schema({
+    "Data_Key": String,
+    "discrete_data": Array,
+    "master_data": Array,
+    "detail_data": Array
+});
+
+let TemplateData = smartcostdb.model("temp_tpl_data", RptTemplateDataSchema, "temp_tpl_data");
+
+class RplTplDataDAO{
+    //根据id获取临时数据
+    get(tpl_id, callback){
+        TemplateData.find({"Data_Key": tpl_id}, function(err, templates){
+            if(templates.length){
+                callback(false, templates[0]);
+            }
+            else{
+                callback('查找不到模板临时数据!');
+            }
+        })
+    };
+    getPromise(tpl_id, callback){
+        return TemplateData.findOne({"Data_Key": tpl_id}).exec();
+    }
+};
+
+module.exports = new RplTplDataDAO();

+ 98 - 0
modules/reports/models/tpl_tree_node.js

@@ -0,0 +1,98 @@
+/**
+ * Created by Tony on 2017/5/31.
+ */
+var mongoose = require('mongoose');
+var dbm = require("../../../config/db/db_manager");
+var db = dbm.getCfgConnection("Reports");
+var async = require("async");
+var rptTplDef = require("../../../public/rpt_tpl_def").getUtil();
+var Schema = mongoose.Schema;
+var TreeNodeSchema = new Schema({
+    ID:Number,
+    ParentID:Number,
+    NextSiblingID:Number,
+    grpType: Number, //建筑(const : 1)/公路(const 2)/其他 etc...
+    nodeType: Number,//节点类型:树节点(枝) 或 模板节点(叶)
+    tplType: Number, //概算、预算、招投标 etc...
+    userId: String,   //用户自定义模板用
+    refId: Number,   //
+    name: String,
+    isDeleted: Boolean
+});
+
+var TreeNodeModel = db.model("rpt_tpl_tree", TreeNodeSchema, "rpt_tpl_tree");
+var counter = require('../../../public/counter/counter');
+
+//var RplTplTreeDAO = function(){};
+class RplTplTreeDAO{
+    getTplTreeNodes(grpType, userId, tplType, callback) {
+        var filter = {"grpType": grpType, "$or": [{"isDeleted": null}, {"isDeleted": false} ]};
+        if (userId) {
+            filter.userId = userId;
+        }
+        if ((tplType && tplType !== rptTplDef.TplType.ALL)) {
+            filter.tplType = tplType;
+        }
+        TreeNodeModel.find(filter, '-_id', function(err, data){
+            if (err) {
+                callback(true, null);
+            } else callback(false,data);
+        });
+    };
+    updateTreeNodes(nodes, callback) {
+        var functions = [];
+        for (let node of nodes) {
+            functions.push((function(doc) {
+                return function(cb) {
+                    TreeNodeModel.update({ID: doc.ID}, doc, cb);
+                };
+            })(node));
+        }
+        async.parallel(functions, function(err, results) {
+            callback(err, results);
+        });
+    };
+    removeNodes(nodeIds, preNodeId, preNodeNextId, callback) {
+        var functions = [];
+        if (preNodeId != -1) {
+            functions.push((function(nodeId, nextId) {
+                return function(cb) {
+                    TreeNodeModel.update({ID: nodeId}, {"NextSiblingID": nextId}, cb);
+                };
+            })(preNodeId, preNodeNextId));
+        }
+        for (let nId of nodeIds) {
+            functions.push((function(nodeId) {
+                return function(cb) {
+                    TreeNodeModel.update({ID: nodeId}, {"isDeleted": true}, cb);
+                };
+            })(nId));
+        }
+        async.parallel(functions, function(err, results) {
+            callback(err, results);
+        });
+    };
+    createTplTreeNode(nodeData, lastNodeId, callback) {
+        counter.counterDAO.getIDAfterCount(counter.moduleName.report, 1, function(err, result){
+            nodeData.ID = result.value.sequence_value;
+            var node = new TreeNodeModel(nodeData);
+            node.save(function (err, result) {
+                if (err) {
+                    callback("树节点错误!", false);
+                } else {
+                    if (lastNodeId > 0) {
+                        TreeNodeModel.update({ID: lastNodeId}, {"NextSiblingID": nodeData.ID}, function(err, rst){
+                            if (err) {
+                                callback("树节点错误!", false);
+                            } else {
+                                callback(false, result);
+                            }
+                        });
+                    } else callback(false, result);
+                }
+            });
+        });
+    };
+}
+
+module.exports = new RplTplTreeDAO();

+ 12 - 0
modules/reports/routes/report_router.js

@@ -0,0 +1,12 @@
+/**
+ * Created by Tony on 2017/3/13.
+ */
+
+let express = require('express');
+let rptRouter = express.Router();
+let reportController = require('./../controllers/rpt_controller');
+
+rptRouter.post('/getReport', reportController.getReportAllPages);
+rptRouter.get('/getExcel/:id/:pm/:size/:rptName', reportController.getExcel);
+
+module.exports = rptRouter;

+ 10 - 0
modules/reports/routes/rpt_tpl_router.js

@@ -0,0 +1,10 @@
+let express = require("express");
+let rptTplRouter = express.Router();
+let reportTplController = require('./../controllers/rpt_tpl_controller');
+
+rptTplRouter.post('/createTplTreeNode', reportTplController.createTplTreeNode);
+rptTplRouter.post('/getRptTplTree', reportTplController.getRptTplTree);
+rptTplRouter.post('/updateTptTplNodes', reportTplController.updateTreeNodes);
+rptTplRouter.post('/deleteTptTplNodes', reportTplController.deleteTptTplNodes);
+
+module.exports = rptTplRouter;

+ 58 - 0
modules/reports/rpt_component/helper/jpc_helper_area.js

@@ -0,0 +1,58 @@
+let JV = require('../jpc_value_define');
+
+let JpcAreaHelper = {
+    outputArea: function(areaNode, band, unitFactor, rowAmount, rowIdx, colAmount, colIdx, multipleDispCol, multipleColIdx,syncHeight, syncWidth) {
+        let rst = {}, maxMultiColumns = 3;
+        if (multipleDispCol > 0 && multipleDispCol <= maxMultiColumns) {
+            //1. calculate left/right
+            let areaWidth = 1.0 * (band[JV.PROP_RIGHT] - band[JV.PROP_LEFT]) / multipleDispCol;
+            areaWidth = areaWidth / colAmount;
+            let innerLeft = 0.0, innerRight = areaWidth;
+            switch (areaNode[JV.PROP_H_CALCULATION]) {
+                case JV.CAL_TYPE[JV.CAL_TYPE_PERCENTAGE] :
+                    innerLeft = (1.0 * areaNode[JV.PROP_LEFT] * areaWidth / JV.HUNDRED_PERCENT);
+                    innerRight = (1.0 * areaNode[JV.PROP_RIGHT] * areaWidth / JV.HUNDRED_PERCENT) ;
+                    break;
+                case JV.CAL_TYPE[JV.CAL_TYPE_ABSTRACT] :
+                    innerLeft = 1.0 * areaNode[JV.PROP_LEFT] * unitFactor;
+                    innerRight = 1.0 * areaNode[JV.PROP_RIGHT] * unitFactor ;
+                    break;
+            }
+            //2. calculate top/bottom
+            let  areaHeight = 1.0 * (band[JV.PROP_BOTTOM] - band[JV.PROP_TOP]);
+            areaHeight = areaHeight / rowAmount;
+            let innerTop = 0.0, innerBottom = areaHeight;
+            switch (areaNode[JV.PROP_V_CALCULATION]) {
+                case JV.CAL_TYPE[JV.CAL_TYPE_PERCENTAGE] :
+                    innerTop = (1.0 * areaNode[JV.PROP_TOP] * areaHeight / JV.HUNDRED_PERCENT);
+                    innerBottom = (1.0 * areaNode[JV.PROP_BOTTOM] * areaHeight / JV.HUNDRED_PERCENT) ;
+                    break;
+                case JV.CAL_TYPE[JV.CAL_TYPE_ABSTRACT] :
+                    innerTop = 1.0 * areaNode[JV.PROP_TOP] * unitFactor;
+                    innerBottom = 1.0 * areaNode[JV.PROP_BOTTOM] * unitFactor ;
+                    break;
+            }
+            //
+            let rstLeft = 0.0, rstRight = 0.0, rstTop = 0.0, rstBottom = 0.0;
+            if (syncHeight) {
+                rstBottom = Math.round(1.0 * band[JV.PROP_TOP] + areaHeight * (rowIdx + 1) + innerTop);
+            } else {
+                rstBottom = Math.round(1.0 * band[JV.PROP_TOP] + areaHeight * rowIdx + innerBottom);
+            }
+            if (syncWidth) {
+                rstRight = Math.round(1.0 * band[JV.PROP_LEFT] + areaWidth * (colIdx + 1) + innerLeft + multipleColIdx * areaWidth);
+            } else {
+                rstRight = Math.round(1.0 * band[JV.PROP_LEFT] + areaWidth * colIdx + innerRight + multipleColIdx * areaWidth);
+            }
+            rstLeft = Math.round(1.0 * band[JV.PROP_LEFT] + areaWidth * colIdx + innerLeft + multipleColIdx * areaWidth);
+            rstTop = Math.round(1.0 * band[JV.PROP_TOP] + areaHeight * rowIdx + innerTop);
+            rst[JV.PROP_LEFT] = rstLeft;
+            rst[JV.PROP_RIGHT] = rstRight;
+            rst[JV.PROP_TOP] = rstTop;
+            rst[JV.PROP_BOTTOM] = rstBottom;
+        }
+        return rst;
+    }
+};
+
+module.exports = JpcAreaHelper;

+ 74 - 0
modules/reports/rpt_component/helper/jpc_helper_band.js

@@ -0,0 +1,74 @@
+let JV = require('../jpc_value_define');
+let JpcCommonHelper = require('./jpc_helper_common');
+
+let JpcBandHelper = {
+    getBandTypeValByString: function(bandType) {
+        let rst = JV.PAGE_STATUS.indexOf(bandType);
+        if (rst < 0) rst = JV.STATUS_NORMAL;
+        return rst;
+    },
+    setBandArea: function(bands, rptTpl, pageStatus) {
+        let me = this;
+        if (rptTpl[JV.NODE_BAND_COLLECTION]) {
+            let unitFactor = JpcCommonHelper.getUnitFactor(rptTpl);
+            let orgArea = JpcCommonHelper.getReportArea(rptTpl, unitFactor);
+            for (let i = 0; i < rptTpl[JV.NODE_BAND_COLLECTION].length; i++) {
+                me.setBandPos(bands, rptTpl[JV.NODE_BAND_COLLECTION][i], orgArea, unitFactor, pageStatus);
+            }
+        }
+    },
+    setBandPos: function(bands, bandNode, orgArea, unitFactor, pageStatus) {
+        let me = this, band = bands[bandNode[JV.BAND_PROP_NAME]];
+        //1. initialize
+        band[JV.PROP_LEFT] = orgArea[JV.IDX_LEFT];
+        band[JV.PROP_TOP] = orgArea[JV.IDX_TOP];
+        band[JV.PROP_RIGHT] = orgArea[JV.IDX_RIGHT];
+        band[JV.PROP_BOTTOM] = orgArea[JV.IDX_BOTTOM];
+        //2. set this band
+        if (pageStatus[band[JV.BAND_PROP_DISPLAY_TYPE]] == true) {
+            switch (band[JV.BAND_PROP_ALIGNMENT]) {
+                case JV.LAYOUT_TOP:
+                    if (band[JV.PROP_CALCULATION] == JV.CAL_TYPE_ABSTRACT) {
+                        band.Bottom = band.Top + unitFactor * band[JV.BAND_PROP_HEIGHT];
+                    } else {
+                        band.Bottom = band.Top + (band.Bottom - band.Top) * band[JV.BAND_PROP_HEIGHT] / 100;
+                    }
+                    orgArea[JV.IDX_TOP] = band.Bottom;
+                    break;
+                case JV.LAYOUT_BOTTOM:
+                    if (band[JV.PROP_CALCULATION] == JV.CAL_TYPE_ABSTRACT) {
+                        band.Top = band.Bottom - unitFactor * band[JV.BAND_PROP_HEIGHT];
+                    } else {
+                        band.Top = band.Bottom - (band.Bottom - band.Top) * band[JV.BAND_PROP_HEIGHT] / 100;
+                    }
+                    orgArea[JV.IDX_BOTTOM] = band.Top;
+                    break;
+                case JV.LAYOUT_LEFT:
+                    if (band[JV.PROP_CALCULATION] == JV.CAL_TYPE_ABSTRACT) {
+                        band.Right = band.Left + unitFactor * band[JV.BAND_PROP_WIDTH];
+                    } else {
+                        band.Right = band.Left + (band.Right - band.Left) * band[JV.BAND_PROP_WIDTH] / 100;
+                    }
+                    orgArea[JV.IDX_LEFT] = band.Right;
+                    break;
+                case JV.LAYOUT_RIGHT:
+                    if (band[JV.PROP_CALCULATION] == JV.CAL_TYPE_ABSTRACT) {
+                        band.Left = band.Right - unitFactor * band[JV.BAND_PROP_WIDTH];
+                    } else {
+                        band.Left = band.Right - (band.Right - band.Left) * band[JV.BAND_PROP_WIDTH] / 100;
+                    }
+                    orgArea[JV.IDX_RIGHT] = band.Left;
+                    break;
+            }
+            //3. set sub-bands
+            if (bandNode[JV.BAND_PROP_SUB_BANDS]) {
+                let bandArea = [band.Left, band.Top, band.Right, band.Bottom];
+                for (let i = 0; i < bandNode[JV.BAND_PROP_SUB_BANDS].length; i++) {
+                    me.setBandPos(bands, bandNode[JV.BAND_PROP_SUB_BANDS][i], bandArea, unitFactor, pageStatus);
+                }
+            }
+        }
+    }
+};
+
+module.exports = JpcBandHelper;

+ 148 - 0
modules/reports/rpt_component/helper/jpc_helper_common.js

@@ -0,0 +1,148 @@
+let JV = require('../jpc_value_define');
+
+let JpcCommonHelper = {
+    commonConstant: {},
+    getResultByID: function (KeyID, collectionList) {
+        let rst = null;
+        if (KeyID) {
+            for (let i = 0; i < collectionList.length; i++) {
+                let collection = collectionList[i];
+                if (collection && collection instanceof Array) {
+                    for (let j = 0; j < collection.length; j++) {
+                        if (collection[j][JV.PROP_ID] === KeyID) {
+                            rst = collection[j];
+                            break;
+                        }
+                    }
+                    if (rst) break;
+                }
+            }
+        }
+        return rst;
+    },
+    getFont: function(fontName, dftFonts, rptTpl) {
+        let me = this, rst = null, list = [];
+        if (rptTpl) list.push(rptTpl[JV.NODE_FONT_COLLECTION]);
+        list.push(dftFonts);
+        rst = me.getResultByID(fontName, list);
+        return rst;
+    },
+    getStyle: function(styleName, dftStyles, rptTpl) {
+        let me = this, rst = null, list = [];
+        if (rptTpl) list.push(rptTpl[JV.NODE_STYLE_COLLECTION]);
+        list.push(dftStyles);
+        rst = me.getResultByID(styleName, list);
+        return rst;
+    },
+    getControl: function(controlName, dftControls, rptTpl) {
+        let me = this, rst = null, list = [];
+        if (rptTpl) list.push(rptTpl[JV.NODE_CONTROL_COLLECTION]);
+        list.push(dftControls);
+        rst = me.getResultByID(controlName, list);
+        return rst;
+    },
+    getLayoutAlignment: function(alignStr) {
+        let rst = JV.LAYOUT.indexOf(alignStr);
+        if (rst < 0) rst = JV.LAYOUT_FULFILL;
+        return rst;
+    },
+    getPosCalculationType: function (typeStr) {
+        let rst = JV.CAL_TYPE.indexOf(typeStr);
+        if (rst < 0) rst = JV.CAL_TYPE_ABSTRACT;
+        return rst;
+    },
+    getScreenDPI: function() {
+        let me = this, arrDPI = [];
+        if (!me.commonConstant.resolution) {
+            arrDPI = [96,96];
+            me.commonConstant.resolution = arrDPI;
+        } else {
+            arrDPI = me.commonConstant.resolution;
+        }
+        return arrDPI;
+    },
+    getScreenDPI_bk: function() {
+        let me = this, arrDPI = [];
+        if (!me.commonConstant.resolution) {
+            if (window) {
+                if (window.screen.deviceXDPI != undefined) {
+                    arrDPI.push(window.screen.deviceXDPI);
+                    arrDPI.push(window.screen.deviceYDPI);
+                } else {
+                    let tmpNode = document.createElement("DIV");
+                    tmpNode.style.cssText = "width:1in;height:1in;position:absolute;left:0px;top:0px;z-index:99;visibility:hidden";
+                    document.body.appendChild(tmpNode);
+                    arrDPI.push(parseInt(tmpNode.offsetWidth));
+                    arrDPI.push(parseInt(tmpNode.offsetHeight));
+                    tmpNode.parentNode.removeChild(tmpNode);
+                }
+            } else {
+                arrDPI = [96,96];
+            }
+            me.commonConstant.resolution = arrDPI;
+        } else {
+            arrDPI = me.commonConstant.resolution;
+        }
+        return arrDPI;
+    },
+    getUnitFactor: function(rptTpl) {
+        let me = this;
+        return me.translateUnit(rptTpl[JV.NODE_MAIN_INFO][JV.PROP_UNITS]);
+    },
+    translateUnit: function(unitStr) {
+        let me = this, rst = 1.0;
+        if (unitStr) {
+            let resolution = me.getScreenDPI();
+            if (JV.MEASUREMENT.PIXEL.indexOf(unitStr) >= 0) {
+                rst = 1.0;
+            } else if (JV.MEASUREMENT.CM.indexOf(unitStr) >= 0) {
+                rst = 1.0 * resolution[0] / 2.54;
+            } else if (JV.MEASUREMENT.INCH.indexOf(unitStr) >= 0) {
+                rst = 1.0 * resolution[0];
+            }
+        }
+        return rst;
+    },
+    getPageSize: function (rptTpl) {
+        let me = this, size = null;
+        let sizeStr = rptTpl[JV.NODE_MAIN_INFO][JV.NODE_PAGE_INFO][JV.PROP_PAGE_SIZE];
+        let sizeIdx = JV.PAGES_SIZE_STR.indexOf(sizeStr);
+        if (sizeIdx >= 0) {
+            size = JV.PAGES_SIZE[sizeIdx].slice(0);
+        } else if (sizeStr === JV.PAGE_SELF_DEFINE) {
+            //
+        } else {
+            size = JV.SIZE_A4.slice(0);
+        }
+        let page_orientation = rptTpl[JV.NODE_MAIN_INFO][JV.NODE_PAGE_INFO][JV.PROP_ORIENTATION];
+        if (page_orientation === JV.ORIENTATION_LANDSCAPE || page_orientation === JV.ORIENTATION_LANDSCAPE_CHN) {
+            //swap x,y
+            let tmp = size[0];
+            size[0] = size[1];
+            size[1] = tmp;
+        }
+        return size;
+    },
+    getReportArea: function(rptTpl, unitFactor) {
+        let me = this, resolution = me.getScreenDPI(), rst = [], size = me.getPageSize(rptTpl);
+        size[0] = resolution[0] * size[0];
+        size[1] = resolution[0] * size[1];
+        rst.push(unitFactor * rptTpl[JV.NODE_MAIN_INFO][JV.NODE_MARGINS][JV.PROP_LEFT]);
+        rst.push(unitFactor * rptTpl[JV.NODE_MAIN_INFO][JV.NODE_MARGINS][JV.PROP_TOP]);
+        rst.push(size[0] - unitFactor * rptTpl[JV.NODE_MAIN_INFO][JV.NODE_MARGINS][JV.PROP_RIGHT]);
+        rst.push(size[1] - unitFactor * rptTpl[JV.NODE_MAIN_INFO][JV.NODE_MARGINS][JV.PROP_BOTTOM]);
+        return rst;
+    },
+    getSegIdxByPageIdx: function(page, page_seg_map) {
+        let rst = -1;
+        for (let pIdx = 0; pIdx < page_seg_map.length; pIdx++) {
+            if (page_seg_map[pIdx][0] == page) {
+                rst = page_seg_map[pIdx][1];
+                break;
+            }
+        }
+        return rst;
+    }
+};
+
+module.exports = JpcCommonHelper;

+ 60 - 0
modules/reports/rpt_component/helper/jpc_helper_common_output.js

@@ -0,0 +1,60 @@
+let JV = require('../jpc_value_define');
+let JpcFieldHelper = require('./jpc_helper_field');
+
+let JpcCommonOutputHelper = {
+    createCommonOutputWithoutDecorate: function (node, value, controls) {
+        let rst = {};
+        //1. font/style/control
+        rst[JV.PROP_FONT] = node[[JV.PROP_FONT]];
+        rst[JV.PROP_CONTROL] = node[[JV.PROP_CONTROL]];
+        rst[JV.PROP_STYLE] = node[[JV.PROP_STYLE]];
+        //2. value
+        rst[JV.PROP_VALUE] = value;
+        if (node[JV.PROP_FORMAT]) {
+            if (!(isNaN(parseFloat(rst[JV.PROP_VALUE])))) {
+                let dotIdx = node[JV.PROP_FORMAT].indexOf(".");
+                if (dotIdx >= 0) {
+                    rst[JV.PROP_VALUE] = parseFloat(rst[JV.PROP_VALUE]).toFixed(node[JV.PROP_FORMAT].length - dotIdx - 1);
+                } else {
+                    rst[JV.PROP_VALUE] = parseFloat(rst[JV.PROP_VALUE]).toFixed(0);
+                }
+            }
+        }
+        if (node[JV.PROP_PREFIX] && rst[JV.PROP_VALUE] != null) {
+            rst[JV.PROP_VALUE] = node[JV.PROP_PREFIX] + rst[JV.PROP_VALUE];
+        }
+        if (node[JV.PROP_SUFFIX] && rst[JV.PROP_VALUE] != null) {
+            rst[JV.PROP_VALUE] = rst[JV.PROP_VALUE] + node[JV.PROP_SUFFIX];
+        }
+        return rst;
+    },
+    createCommonOutput: function (node, value, controls) {
+        let rst = {};
+        //1. font/style/control
+        rst[JV.PROP_FONT] = node[[JV.PROP_FONT]];
+        rst[JV.PROP_CONTROL] = node[[JV.PROP_CONTROL]];
+        rst[JV.PROP_STYLE] = node[[JV.PROP_STYLE]];
+        //2. value
+        rst[JV.PROP_VALUE] = value;
+        JpcFieldHelper.decorateValue(rst, controls);
+        if (node[JV.PROP_FORMAT]) {
+            if (!(isNaN(parseFloat(rst[JV.PROP_VALUE])))) {
+                let dotIdx = node[JV.PROP_FORMAT].indexOf(".");
+                if (dotIdx >= 0) {
+                    rst[JV.PROP_VALUE] = parseFloat(rst[JV.PROP_VALUE]).toFixed(node[JV.PROP_FORMAT].length - dotIdx - 1);
+                } else {
+                    rst[JV.PROP_VALUE] = parseFloat(rst[JV.PROP_VALUE]).toFixed(0);
+                }
+            }
+        }
+        if (node[JV.PROP_PREFIX] && rst[JV.PROP_VALUE] != null && rst[JV.PROP_VALUE] != "") {
+            rst[JV.PROP_VALUE] = node[JV.PROP_PREFIX] + rst[JV.PROP_VALUE];
+        }
+        if (node[JV.PROP_SUFFIX] && rst[JV.PROP_VALUE] != null && rst[JV.PROP_VALUE] != "") {
+            rst[JV.PROP_VALUE] = rst[JV.PROP_VALUE] + node[JV.PROP_SUFFIX];
+        }
+        return rst;
+    }
+}
+
+module.exports = JpcCommonOutputHelper;

+ 189 - 0
modules/reports/rpt_component/helper/jpc_helper_cross_tab.js

@@ -0,0 +1,189 @@
+let JV = require('../jpc_value_define');
+let JpcCommonHelper = require('./jpc_helper_common');
+
+let JpcCrossTabHelper = {
+    getColIDX: function(cl, val) {
+        let rst = -1;
+        for (let i = 0; i < cl.length; i++) {
+            let ca = cl[i];
+            for (let j = 0; j < ca.length; j++) {
+                if (ca[j] == val) {
+                    rst = i;
+                    break;
+                }
+            }
+            if (rst != -1) {
+                break;
+            }
+        }
+        return rst;
+    },
+    pushToSeg: function(segArr, dataSeq, segIdx, sIdx, eIdx) {
+        let arrIdx = [];
+        for (let k = sIdx; k < eIdx; k++) {
+            arrIdx.push(dataSeq[segIdx][k]);
+        }
+        segArr.push(arrIdx);
+    },
+    sortFieldValue: function(sIDX, eIDX, sortOrder, dataField, dataValSeq) {
+        let tmpSeq = [];
+        if ((sortOrder) && (sortOrder != JV.TAB_FIELD_PROP_SORT_VAL_NOSORT)) {
+            if (sIDX >= 0 && eIDX >= sIDX && dataValSeq.length > eIDX) {
+                let reversed = 1;
+                if (sortOrder === JV.TAB_FIELD_PROP_SORT_VAL_DESC) {
+                    reversed = -1;
+                }
+                for (let i = sIDX; i <= eIDX; i++) {
+                    tmpSeq.push(dataValSeq[i]);
+                }
+                tmpSeq.sort(function(idx1, idx2) {
+                    let rst = 0;
+                    if (isNaN(parseFloat(dataField[idx1])) || isNaN(parseFloat(dataField[idx1]))) {
+                        if (dataField[idx1] > dataField[idx2]) {
+                            rst = reversed;
+                        } else if (dataField[idx1] < dataField[idx2]) {
+                            rst = -reversed;
+                        }
+                    } else {
+                        if ((1.0 * dataField[idx1]) > (1.0 * dataField[idx2])) {
+                            rst = reversed;
+                        } else if ((1.0 * dataField[idx1]) < (1.0 * dataField[idx2])) {
+                            rst = -reversed;
+                        }
+                    }
+                    return rst;
+                });
+            }
+        }
+        if (tmpSeq.length > 0) {
+            for (let i = sIDX; i <= eIDX; i++) {
+                dataValSeq[i] = tmpSeq[i - sIDX];
+            }
+        }
+        return tmpSeq;
+    },
+    checkIfEqual: function(dataFields, seq1, seq2) {
+        let rst = true;
+        for (let i = 0; i < dataFields.length; i++) {
+            if ((dataFields[i][seq1] !== dataFields[i][seq2])) {
+                rst = false;
+                break;
+            }
+        }
+        return rst;
+    },
+    sortTabFields: function(tabFields, fieldSeqs, data_details, dataSeq) {
+        let me = this;
+        let sIDX = 0, eIDX = -1, isFirstSort = true;
+        for (let i = 0; i < tabFields.length; i++) {
+            let tabField = tabFields[i];
+            if (tabField[JV.TAB_FIELD_PROP_SORT] != JV.TAB_FIELD_PROP_SORT_VAL_NOSORT) {
+                if (isFirstSort) {
+                    isFirstSort = false;
+                    //first field, should sort all data items
+                    for (let j = 0; j < dataSeq.length; j++) {
+                        sIDX = 0;
+                        eIDX = dataSeq[j].length - 1;
+                        //sort the field value here
+                        me.sortFieldValue(sIDX, eIDX, tabField[JV.TAB_FIELD_PROP_SORT],data_details[fieldSeqs[i]], dataSeq[j]);
+                    }
+                } else {
+                    //then sort the rest fields one by one
+                    for (let j = 0; j < dataSeq.length; j++) {
+                        let chkFields = [];
+                        for (let k = 0; k < i; k++) {
+                            chkFields.push(data_details[fieldSeqs[k]]);
+                        }
+                        sIDX = 0, eIDX = -1;
+                        for (let m = 1; m < dataSeq[j].length; m++) {
+                            if (!(me.checkIfEqual(chkFields, dataSeq[j][m - 1], dataSeq[j][m]))) {
+                                eIDX = m - 1;
+                            } else if (m == dataSeq[j].length - 1) {
+                                eIDX = m;
+                            };
+                            if (eIDX >= sIDX) {
+                                if (eIDX != sIDX) {
+                                    me.sortFieldValue(sIDX, eIDX, tabField[JV.TAB_FIELD_PROP_SORT],data_details[fieldSeqs[i]], dataSeq[j]);
+                                }
+                                sIDX = m;
+                                eIDX = m - 1; //for protection purpose
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    },
+    getMaxTabCntPerPage: function(bands, rptTpl, tabNodeName, tabMeasurePropName, measureForCal) {
+        let rst = 1;
+        if (rptTpl[JV.NODE_CROSS_INFO][tabNodeName]) {
+            let tab = rptTpl[JV.NODE_CROSS_INFO][tabNodeName];
+            let maxFieldMeasure = 1.0;
+            if (JV.CAL_TYPE_ABSTRACT == JpcCommonHelper.getPosCalculationType(tab[JV.PROP_CALCULATION])) {
+                let unitFactor = JpcCommonHelper.getUnitFactor(rptTpl);
+                maxFieldMeasure = 1.0 * rptTpl[JV.NODE_CROSS_INFO][tabNodeName][tabMeasurePropName] * unitFactor;
+            } else {
+                maxFieldMeasure = measureForCal * rptTpl[JV.NODE_CROSS_INFO][tabNodeName][tabMeasurePropName] / JV.HUNDRED_PERCENT;
+            }
+            rst = Math.floor(measureForCal / maxFieldMeasure);
+        }
+        return rst;
+    },
+    getMaxRowsPerPage: function(bands, rptTpl) {
+        let me = this, rst = 1;
+        let band = bands[rptTpl[JV.NODE_CROSS_INFO][JV.NODE_CROSS_ROW][JV.PROP_BAND_NAME]];
+        if (band) {
+            rst =  me.getMaxTabCntPerPage(bands, rptTpl, JV.NODE_CROSS_ROW, JV.PROP_CMN_HEIGHT, band.Bottom - band.Top);
+        }
+        return rst;
+    },
+    getMaxColsPerPage: function(bands, rptTpl) {
+        let me = this, rst = 1;
+        let band = bands[rptTpl[JV.NODE_CROSS_INFO][JV.NODE_CROSS_COL][JV.PROP_BAND_NAME]];
+        if (band) {
+            rst =  me.getMaxTabCntPerPage(bands, rptTpl, JV.NODE_CROSS_COL, JV.PROP_CMN_WIDTH, band.Right - band.Left);
+        }
+        return rst;
+    },
+    chkTabEnd: function(tabType, rptTpl, bands, sortedSequence, segIdx, preRec, nextRec) {
+        let me = this, rst = true;
+        let remainAmt = preRec + nextRec - sortedSequence[segIdx].length;
+        rst = me.hasEnoughSpace(tabType, rptTpl, bands, remainAmt);
+        return rst;
+    },
+    hasEnoughSpace: function (tabType, rptTpl, bands, remainAmt) {
+        if (remainAmt < 0) return false;
+        let rst = true, measurement = 1.0, douDiffForCompare = 0.00001;
+        let unitFactor = JpcCommonHelper.getUnitFactor(rptTpl);
+        let band = null;
+        if (rptTpl[JV.NODE_CROSS_INFO][tabType]) {
+            band = bands[rptTpl[JV.NODE_CROSS_INFO][tabType][JV.PROP_BAND_NAME]];
+        }
+        if (band != null && band != undefined) {
+            if (tabType === JV.NODE_CROSS_ROW_SUM || tabType === JV.NODE_CROSS_ROW_EXT) {
+                measurement = 1.0 * rptTpl[JV.NODE_CROSS_INFO][JV.NODE_CROSS_ROW][JV.PROP_CMN_HEIGHT] * unitFactor;
+                let spareHeight = measurement * remainAmt;
+                let douH = 1.0 * (band.Bottom - band.Top);
+                rst = (spareHeight >= douH) || (spareHeight - douH <= douDiffForCompare);
+            } else if (tabType === JV.NODE_CROSS_COL_SUM) {
+                measurement = 1.0 * rptTpl[JV.NODE_CROSS_INFO][JV.NODE_CROSS_COL][JV.PROP_CMN_WIDTH] * unitFactor;
+                let spareWidth = measurement * remainAmt;
+                let douW = 1.0 * (band.Right - band.Left);
+                rst = (spareWidth >= douW) || (spareWidth - douW <= douDiffForCompare);
+            }
+        }
+        return rst;
+    },
+    initialPageStatus: function (pageStatus) {
+        pageStatus[JV.STATUS_NORMAL] = true;
+        pageStatus[JV.STATUS_REPORT_START] = false;
+        pageStatus[JV.STATUS_REPORT_END] = false;
+        pageStatus[JV.STATUS_SEGMENT_START] = false;
+        pageStatus[JV.STATUS_SEGMENT_END] = false;
+        pageStatus[JV.STATUS_GROUP] = false;
+        pageStatus[JV.STATUS_CROSS_ROW_END] = false;
+        pageStatus[JV.STATUS_CROSS_COL_END] = false;
+    }
+};
+
+module.exports = JpcCrossTabHelper;

+ 58 - 0
modules/reports/rpt_component/helper/jpc_helper_discrete.js

@@ -0,0 +1,58 @@
+let JV = require('../jpc_value_define');
+let JE = require('../jpc_rte');
+let JpcTextHelper = require('./jpc_helper_text');
+let JpcCommonOutputHelper = require('./jpc_helper_common_output');
+let JpcAreaHelper = require('./jpc_helper_area');
+
+let JpcDiscreteHelper = {
+    outputDiscreteInfo: function (discreteArray, bands, dataObj, unitFactor, pageStatus, segIdx, multiCols, multiColIdx, $CURRENT_RPT) {
+        let rst = [];
+        if (discreteArray && dataObj) {
+            for (let i = 0; i < discreteArray.length; i++) {
+                let band = bands[discreteArray[i][JV.PROP_BAND_NAME]];
+                if (band && pageStatus[band[JV.BAND_PROP_DISPLAY_TYPE]] == true) {
+                    if (discreteArray[i][JV.PROP_TEXT]) {
+                        rst.push(JpcTextHelper.outputText(discreteArray[i][JV.PROP_TEXT], band, unitFactor, 1, 0, 1, 0, multiCols, multiColIdx));
+                    }
+                    if (discreteArray[i][JV.PROP_TEXTS]) {
+                        for (let j = 0; j < discreteArray[i][JV.PROP_TEXTS].length; j++) {
+                            rst.push(JpcTextHelper.outputText(discreteArray[i][JV.PROP_TEXTS][j], band, unitFactor, 1, 0, 1, 0, multiCols, multiColIdx));
+                        }
+                    }
+                    if (discreteArray[i][JV.PROP_DISCRETE_FIELDS]) {
+                        for (let j = 0; j < discreteArray[i][JV.PROP_DISCRETE_FIELDS].length; j++) {
+                            let df = discreteArray[i][JV.PROP_DISCRETE_FIELDS][j];
+                            let value = "";
+                            if (df[JV.PROP_FIELD_ID]) {
+                                let field = JE.F(df[JV.PROP_FIELD_ID], $CURRENT_RPT);
+                                if (field.DataSeq != JV.BLANK_FIELD_INDEX) {
+                                    let data = dataObj[field.DataNodeName][field.DataSeq];
+                                    if (data && data.length > 0) {
+                                        if (data.length > segIdx) {
+                                            value = data[segIdx];
+                                        } else {
+                                            value = data[0];
+                                        }
+                                    }
+                                } else {
+                                    if (field[JV.PROP_AD_HOC_DATA] && field[JV.PROP_AD_HOC_DATA].length > 0) value = field[JV.PROP_AD_HOC_DATA][0]
+                                    else value = "";
+                                }
+                            } else if (df[JV.PROP_PARAM_ID]) {
+                                let param = JE.P(df[JV.PROP_PARAM_ID], $CURRENT_RPT);
+                                value = param[JV.PROP_DFT_VALUE];
+                            }
+                            let item = JpcCommonOutputHelper.createCommonOutputWithoutDecorate(df, value, null);
+                            //position
+                            item[JV.PROP_AREA] = JpcAreaHelper.outputArea(df[JV.PROP_AREA], band, unitFactor, 1, 0, 1, 0, multiCols, multiColIdx, false, false);
+                            rst.push(item);
+                        }
+                    }
+                }
+            }
+        }
+        return rst;
+    }
+}
+
+module.exports = JpcDiscreteHelper;

+ 44 - 0
modules/reports/rpt_component/helper/jpc_helper_field.js

@@ -0,0 +1,44 @@
+let JV = require('../jpc_value_define');
+
+let JpcFieldHelper = {
+    getValue: function(dataField, valueIdx) {
+        let rst = "";
+        if (dataField && (dataField.length > valueIdx) && (valueIdx >= 0)) {
+            rst = dataField[valueIdx];
+        }
+        return rst;
+    },
+    decorateValue: function (cell, controls) {
+        if (controls) {
+            let val = cell[JV.PROP_VALUE];
+            let showZero = controls[cell[JV.PROP_CONTROL]][JV.PROP_SHOW_ZERO];
+            if (showZero && showZero == 'F' ) {
+                if (0.0 == 1.0 * (0 + val)) {
+                    cell[JV.PROP_VALUE] = "";
+                }
+            }
+        }
+    },
+    findAndPutDataFieldIdx: function (rptTpl, tab_fields, rstFields, rstFieldsIdx) {
+        if (tab_fields) {
+            let detail_fields = rptTpl[JV.NODE_FIELD_MAP][JV.NODE_DETAIL_FIELDS];
+            for (let i = 0; i < tab_fields.length; i++) {
+                let isFounded = false;
+                for (let j = 0; j < detail_fields.length; j++) {
+                    if (tab_fields[i]["FieldID"] == detail_fields[j]["ID"]) {
+                        isFounded = true;
+                        if (rstFields) rstFields.push(tab_fields[i]);
+                        if (rstFieldsIdx) rstFieldsIdx.push(j);
+                        break;
+                    }
+                }
+                if (!isFounded) {
+                    if (rstFields) rstFields.push(tab_fields[i]);
+                    if (rstFieldsIdx) rstFieldsIdx.push(JV.BLANK_FIELD_INDEX);
+                }
+            }
+        }
+    }
+};
+
+module.exports = JpcFieldHelper;

+ 43 - 0
modules/reports/rpt_component/helper/jpc_helper_flow_tab.js

@@ -0,0 +1,43 @@
+let JV = require('../jpc_value_define');
+let JpcCommonHelper = require('./jpc_helper_common');
+
+let JpcFlowTabHelper = {
+    getMaxRowsPerPage: function(bands, rptTpl) {
+        let me = this, rst = 1;
+        let tab = rptTpl[JV.NODE_FLOW_INFO][JV.NODE_FLOW_CONTENT];
+        let band = bands[tab[JV.PROP_BAND_NAME]];
+        if (band) {
+            let maxFieldMeasure = 1.0;
+            if (JV.CAL_TYPE_ABSTRACT == JpcCommonHelper.getPosCalculationType(tab[JV.PROP_CALCULATION])) {
+                let unitFactor = JpcCommonHelper.getUnitFactor(rptTpl);
+                maxFieldMeasure = 1.0 * rptTpl[JV.NODE_FLOW_INFO][JV.NODE_FLOW_CONTENT][JV.PROP_CMN_HEIGHT] * unitFactor;
+            } else {
+                maxFieldMeasure = (band.Bottom - band.Top) * rptTpl[JV.NODE_FLOW_INFO][JV.NODE_FLOW_CONTENT][JV.PROP_CMN_HEIGHT] / JV.HUNDRED_PERCENT;
+            }
+            rst = Math.floor((band.Bottom - band.Top) / maxFieldMeasure);
+        }
+        return rst;
+    },
+    chkSegEnd: function (bands, rptTpl, sortedSequence, segIdx, preRec, nextRec) {
+        let me = this, rst = true;
+        let remainAmt = preRec + nextRec - sortedSequence[segIdx].length;
+        rst = me.hasEnoughSpace(rptTpl, bands, remainAmt);
+        return rst;
+    },
+    hasEnoughSpace: function (rptTpl, bands, remainAmt) {
+        if (remainAmt < 0) return false;
+        let rst = true, measurement = 1.0, douDiffForCompare = 0.00001;
+        let unitFactor = JpcCommonHelper.getUnitFactor(rptTpl);
+        let tab = rptTpl[JV.NODE_FLOW_INFO][JV.NODE_FLOW_CONTENT];
+        let band = bands[tab[JV.PROP_BAND_NAME]];
+        if (band != null && band != undefined) {
+            measurement = 1.0 * tab[JV.PROP_CMN_HEIGHT] * unitFactor;
+            let spareHeight = measurement * remainAmt;
+            let douH = 1.0 * (band.Bottom - band.Top);
+            rst = (spareHeight >= douH) || (spareHeight - douH <= douDiffForCompare);
+        }
+        return rst;
+    }
+};
+
+module.exports = JpcFlowTabHelper;

+ 14 - 0
modules/reports/rpt_component/helper/jpc_helper_text.js

@@ -0,0 +1,14 @@
+let JV = require('../jpc_value_define');
+let JpcCommonOutputHelper = require('./jpc_helper_common_output');
+let JpcAreaHelper = require('./jpc_helper_area');
+
+let JpcTextHelper = {
+    outputText: function (textNode, band, unitFactor, rows, rowIdx, cols, colIdx, multiCols, multiColIdx) {
+        let rst = JpcCommonOutputHelper.createCommonOutput(textNode, textNode[JV.PROP_LABEL], null);
+        //position
+        rst[JV.PROP_AREA] = JpcAreaHelper.outputArea(textNode[JV.PROP_AREA], band, unitFactor, rows, rowIdx, cols, colIdx, multiCols, multiColIdx, false, false);
+        return rst;
+    }
+};
+
+module.exports = JpcTextHelper;

+ 52 - 0
modules/reports/rpt_component/jpc_band.js

@@ -0,0 +1,52 @@
+let JV = require('./jpc_value_define');
+let JpcCommonHelper = require('./helper/jpc_helper_common');
+let JpcBandHelper = require('./helper/jpc_helper_band');
+
+let JpcBand = {
+    createNew: function(rptTpl, defProperties) {
+        let me = this;
+        let JpcBandResult = {};
+        if (rptTpl[JV.NODE_BAND_COLLECTION]) {
+            for (let i = 0; i < rptTpl[JV.NODE_BAND_COLLECTION].length; i++) {
+                me.createSingle(rptTpl[JV.NODE_BAND_COLLECTION][i], JpcBandResult, rptTpl, defProperties);
+            }
+        }
+        return JpcBandResult;
+    },
+    createSingle: function(bandNode, parentObj, rptTpl, defProperties) {
+        let me = this;
+        if (bandNode && bandNode[JV.BAND_PROP_NAME]) {
+            let item = {Left:0, Right:0, Top:0, Bottom:0};
+            item[JV.BAND_PROP_STYLE] = JpcCommonHelper.getStyle(bandNode[JV.BAND_PROP_STYLE], defProperties.styles, null);
+            item[JV.BAND_PROP_CONTROL] = JpcCommonHelper.getControl(bandNode[JV.BAND_PROP_CONTROL], defProperties.ctrls, null);
+            if (bandNode[JV.BAND_PROP_HEIGHT]) {
+                item[JV.BAND_PROP_HEIGHT] = 1.0 * bandNode[JV.BAND_PROP_HEIGHT];
+            } else {
+                item[JV.BAND_PROP_HEIGHT] = 0.0;
+            }
+            if (bandNode[JV.BAND_PROP_WIDTH]) {
+                item[JV.BAND_PROP_WIDTH] = 1.0 * bandNode[JV.BAND_PROP_WIDTH];
+            } else {
+                item[JV.BAND_PROP_WIDTH] = 0.0;
+            }
+            item[JV.BAND_PROP_DISPLAY_TYPE] = JpcBandHelper.getBandTypeValByString(bandNode[JV.BAND_PROP_DISPLAY_TYPE]);
+            item[JV.BAND_PROP_ALIGNMENT] = JpcCommonHelper.getLayoutAlignment(bandNode[JV.BAND_PROP_ALIGNMENT]);
+            item[JV.PROP_CALCULATION] = JpcCommonHelper.getPosCalculationType(bandNode[JV.PROP_CALCULATION]);
+
+            if (bandNode[JV.BAND_PROP_MERGE_BORDER]) {
+                item[JV.BAND_PROP_MERGE_BORDER] = bandNode[JV.BAND_PROP_MERGE_BORDER];
+            }
+            if (bandNode[JV.BAND_PROP_SUB_BANDS]) {
+                for (let i = 0; i < bandNode[JV.BAND_PROP_SUB_BANDS].length; i++) {
+                    me.createSingle(bandNode[JV.BAND_PROP_SUB_BANDS][i], parentObj, rptTpl, defProperties);
+                }
+            }
+            parentObj[bandNode[JV.BAND_PROP_NAME]] = item;
+            if (item[JV.BAND_PROP_MERGE_BORDER] != null && item[JV.BAND_PROP_MERGE_BORDER] != undefined && item[JV.BAND_PROP_MERGE_BORDER] == 'T') {
+                parentObj[JV.BAND_PROP_MERGE_BAND] = item;
+            }
+        }
+    }
+}
+
+module.exports = JpcBand;

+ 78 - 0
modules/reports/rpt_component/jpc_bill_tab.js

@@ -0,0 +1,78 @@
+let JV = require('./jpc_value_define');
+let JpcFieldHelper = require('./helper/jpc_helper_field');
+let JpcBandHelper = require('./helper/jpc_helper_band');
+let JpcBand = require('./jpc_band');
+let JpcFlowTabHelper = require('./helper/jpc_helper_flow_tab');
+let JpcCommonHelper = require('./helper/jpc_helper_common');
+let JpcDiscreteHelper = require('./helper/jpc_helper_discrete');
+let JpcTextHelper = require('./helper/jpc_helper_text');
+let JpcCommonOutputHelper = require('./helper/jpc_helper_common_output');
+let JpcAreaHelper = require('./helper/jpc_helper_area');
+
+let JpcBillTabSrv = function(){};
+JpcBillTabSrv.prototype.createNew = function(){
+    let JpcBillTabResult = {};
+    JpcBillTabResult.initialize = function() {
+        let me = this;
+        me.disp_fields_idx = [];
+    };
+    JpcBillTabResult.sorting = function(rptTpl) {
+        let me = this;
+        JpcFieldHelper.findAndPutDataFieldIdx(rptTpl, rptTpl[JV.NODE_BILL_INFO][JV.NODE_BILL_CONTENT][JV.PROP_BILL_FIELDS], null, me.disp_fields_idx);
+    };
+    JpcBillTabResult.outputAsSimpleJSONPage = function (rptTpl, dataObj, page, bands, controls, $CURRENT_RPT) {
+        let me = this, rst = [], tabRstLst = [];
+        //1 calculate the band position
+        let pageStatus = [true, false, false, false, false, false, false, false];
+        JpcBandHelper.setBandArea(bands, rptTpl, pageStatus);
+        //2. start to output detail-part
+        let unitFactor = JpcCommonHelper.getUnitFactor(rptTpl);
+        //2.1 output content
+        tabRstLst.push(me.outputContent(rptTpl, dataObj, page, bands, unitFactor, controls, pageStatus));
+        //2.2 output discrete
+        tabRstLst.push(JpcDiscreteHelper.outputDiscreteInfo(rptTpl[JV.NODE_BILL_INFO][JV.NODE_DISCRETE_INFO], bands, dataObj, unitFactor, pageStatus, page - 1, 1, 0, $CURRENT_RPT));
+    }
+    JpcBillTabResult.outputContent = function(rptTpl, dataObj, page, bands, unitFactor, controls, pageStatus) {
+        let me = this, rst = [];
+        let tab = rptTpl[JV.NODE_BILL_INFO][JV.NODE_BILL_CONTENT];
+        let band = bands[tab[JV.PROP_BAND_NAME]];
+        if (band) {
+            if (pageStatus[band[JV.BAND_PROP_DISPLAY_TYPE]] == true) {
+                let tab_fields = tab[JV.PROP_BILL_FIELDS];
+                let data_details = dataObj[JV.DATA_MASTER_DATA];
+                for (let i = 0; i < tab_fields.length; i++) {
+                    let tab_field = tab_fields[i];
+                    let data_field = null;
+                    if (me.disp_fields_idx[i] != JV.BLANK_FIELD_INDEX) {
+                        data_field = data_details[me.disp_fields_idx[i]];
+                    } else {
+                        data_field = JE.F(tab_field[JV.PROP_FIELD_ID]);
+                        if (data_field) {
+                            data_field = data_field[JV.PROP_AD_HOC_DATA];
+                        }
+                    }
+                    if (!(tab_field[JV.PROP_HIDDEN])) {
+                        let cellItem = JpcCommonOutputHelper.createCommonOutput(tab_field, JpcFieldHelper.getValue(data_field, page - 1), controls);
+                        cellItem[JV.PROP_AREA] = JpcAreaHelper.outputArea(tab_field[JV.PROP_AREA], band, unitFactor, 1, 0, 1, 0, 1, 0, true, false);
+                        rst.push(cellItem);
+                    }
+                }
+                if (tab[JV.PROP_TEXT]) {
+                    rst.push(JpcTextHelper.outputText(tab[JV.PROP_TEXT], band, unitFactor, 1, 0, 1, 0, 1, 0));
+                }
+                if (tab[JV.PROP_TEXTS]) {
+                    for (let j = 0; j < tab[JV.PROP_TEXTS].length; j++) {
+                        rst.push(JpcTextHelper.outputText(tab[JV.PROP_TEXTS][j], band, unitFactor, 1, 0, 1, 0, 1, 0));
+                    }
+                }
+                if (tab[JV.NODE_DISCRETE_INFO]) {
+                    rst = rst.concat(JpcDiscreteHelper.outputDiscreteInfo(tab[JV.NODE_DISCRETE_INFO], bands, dataObj, unitFactor, pageStatus, page - 1, 1, 0));
+                }
+            }
+        }
+        return rst;
+    }
+    return JpcBillTabResult;
+}
+
+module.exports = new JpcBillTabSrv();

+ 554 - 0
modules/reports/rpt_component/jpc_cross_tab.js

@@ -0,0 +1,554 @@
+let JV = require('./jpc_value_define');
+let JpcFieldHelper = require('./helper/jpc_helper_field');
+let JpcBandHelper = require('./helper/jpc_helper_band');
+let JpcBand = require('./jpc_band');
+let JpcFlowTabHelper = require('./helper/jpc_helper_flow_tab');
+let JpcCrossTabHelper = require('./helper/jpc_helper_cross_tab');
+let JpcCommonHelper = require('./helper/jpc_helper_common');
+let JpcDiscreteHelper = require('./helper/jpc_helper_discrete');
+let JpcTextHelper = require('./helper/jpc_helper_text');
+let JpcCommonOutputHelper = require('./helper/jpc_helper_common_output');
+let JpcAreaHelper = require('./helper/jpc_helper_area');
+
+let JpcCrossTabSrv = function(){};
+JpcCrossTabSrv.prototype.createNew = function(){
+    function private_addTabValue(tabValuedIdxLst, sortedSequence, segIdx, preRec, nextRec, dispSerialIdxLst, sorted_sum_value_Lst, rst_sum_value_Lst) {
+        if (tabValuedIdxLst) {
+            let serial1stTier = null;
+            if (dispSerialIdxLst) serial1stTier = [];
+            let pgseg1stTier = [];
+            let sumVal = [];
+            let sumValL = 1;
+            if (sortedSequence) {
+                let arrDupVals = sortedSequence[segIdx];
+                let arrDupSumVals = null;
+                if (sorted_sum_value_Lst != null) {
+                    arrDupSumVals = sorted_sum_value_Lst[segIdx];
+                    sumValL = arrDupSumVals[0].length;
+                }
+
+                for (let i = 0; i < nextRec; i++) {
+                    if (arrDupVals.length <= preRec + i) {
+                        pgseg1stTier[i] = JV.BLANK_VALUE_INDEX;
+                        sumVal[i] = [];
+                        for (let ei = 0; ei < sumValL; ei++) {
+                            sumVal[i][ei] = null;
+                        }
+                        if (serial1stTier != null) {
+                            serial1stTier[i] = JV.BLANK_VALUE_INDEX;
+                        }
+                        continue;
+                    }
+                    let duplicateValueArr = arrDupVals[preRec + i];
+                    pgseg1stTier[i] = duplicateValueArr[0];
+                    if (arrDupSumVals != null) sumVal[i] = arrDupSumVals[preRec + i];
+
+                    if (serial1stTier != null) {
+                        serial1stTier[i] = preRec + i;
+                    }
+                }
+                tabValuedIdxLst.push(pgseg1stTier);
+                if (dispSerialIdxLst != null) {
+                    dispSerialIdxLst.push(serial1stTier);
+                }
+                if (sorted_sum_value_Lst != null && rst_sum_value_Lst != null) {
+                    rst_sum_value_Lst.push(sumVal);
+                }
+            } else {
+                //should push blank value index rather than null
+                for (let i = 0; i < nextRec; i++) {
+                    pgseg1stTier[i] = JV.BLANK_VALUE_INDEX;
+                    sumVal[i] = null;
+                    if (serial1stTier != null) {
+                        serial1stTier[i] = JV.BLANK_VALUE_INDEX;
+                    }
+                }
+                tabValuedIdxLst.push(pgseg1stTier);
+                if (dispSerialIdxLst != null) {
+                    dispSerialIdxLst.push(serial1stTier);
+                }
+                if (sorted_sum_value_Lst != null && rst_sum_value_Lst != null) {
+                    rst_sum_value_Lst.push(sumVal);
+                }
+            }
+        }
+    }
+    function private_addContentValue(dispValueIdxLst_Content, sortedContentSequence, segIdx, counterRowRec, maxRowRec, counterColRec, maxColRec, page_seg_map, pageIdx) {
+        if (dispValueIdxLst_Content != null) {
+            page_seg_map.push([pageIdx,segIdx]);
+            let arrContents = [];
+            if (sortedContentSequence != null) {
+                let arrAllContent = sortedContentSequence[segIdx];
+                for (let i = 0; i < maxRowRec; i++) {
+                    arrContents.push([]);
+                    for (let j = 0; j < maxColRec; j++) {
+                        if (arrAllContent.length <= counterRowRec + i || arrAllContent[counterRowRec + i].length <= counterColRec + j) {
+                            arrContents[i][j] = JV.BLANK_VALUE_INDEX;
+                        } else {
+                            arrContents[i][j] = arrAllContent[counterRowRec + i][counterColRec + j];
+                        }
+                    }
+                }
+                dispValueIdxLst_Content.push(arrContents);
+            } else {
+                //should push blank value index rather than null
+                for (let i = 0; i < maxRowRec; i++) {
+                    arrContents.push([]);
+                    for (let j = 0; j < maxColRec; j++) {
+                        arrContents[i][j] = JV.BLANK_VALUE_INDEX;
+                    }
+                }
+                dispValueIdxLst_Content.push(arrContents);
+            }
+        }
+    }
+    function private_SortAndOptimize(rptTpl, dataObj, dataSeq, sortTab, rstFieldsIdx) {
+        let result = [];
+        let tab = rptTpl[JV.NODE_CROSS_INFO][sortTab];
+        if (tab) {
+            let sIDX = 0;
+            //1. prepare and sort by tab-field
+            let fields = [];
+            JpcFieldHelper.findAndPutDataFieldIdx(rptTpl, tab[JV.TAB_CROSS_FIELDS], fields, rstFieldsIdx);
+            let data_details = dataObj[JV.DATA_DETAIL_DATA];
+            JpcCrossTabHelper.sortTabFields(fields, rstFieldsIdx, data_details, dataSeq);
+            //2. distinguish sort tab fields value
+            let b1 = false;
+            for (let i = 0; i < dataSeq.length; i++) {
+                sIDX = 0;
+                let segArr = [];
+                if (dataSeq[i].length == 1) {
+                    JpcCrossTabHelper.pushToSeg(segArr, dataSeq, i, 0, 1);
+                } else {
+                    for (let j = 1; j < dataSeq[i].length; j++) {
+                        b1 = false;
+                        for (let k = 0; k < rstFieldsIdx.length; k++) {
+                            if (data_details[rstFieldsIdx[k]][dataSeq[i][j - 1]] != data_details[rstFieldsIdx[k]][dataSeq[i][j]]) {
+                                b1 = true;
+                                break;
+                            }
+                        }
+                        if (b1) {
+                            JpcCrossTabHelper.pushToSeg(segArr, dataSeq, i, sIDX, j);
+                            sIDX = j;
+                            if (j == dataSeq[i].length - 1) {
+                                JpcCrossTabHelper.pushToSeg(segArr, dataSeq, i, j, dataSeq[i].length);
+                            }
+                        } else if (j == dataSeq[i].length - 1) {
+                            JpcCrossTabHelper.pushToSeg(segArr, dataSeq, i, sIDX, dataSeq[i].length);
+                        }
+                    }
+                }
+                if (segArr.length > 0) result.push(segArr);
+            }
+        }
+        return result;
+    }
+    function private_SortForDisplayContent(rptTpl, rowSeq, colSeq, rstFieldsIdx){
+        let result = [];
+        let tab = rptTpl[JV.NODE_CROSS_INFO][JV.NODE_CROSS_CONTENT];
+        if (tab) {
+            JpcFieldHelper.findAndPutDataFieldIdx(rptTpl, tab[JV.TAB_CROSS_FIELDS], null, rstFieldsIdx);
+        }
+        for (let i = 0; i < rowSeq.length; i++) {
+            let rl = rowSeq[i], cl = colSeq[i];
+            let ds = [];
+            //1. initialize to blank value index
+            for (let j = 0; j < rl.length; j++) {
+                ds.push([]);
+                for (let k = 0; k < cl.length; k++) {
+                    ds[j].push(JV.BLANK_VALUE_INDEX);
+                }
+            }
+            //2. then fill up the right index
+            for (let j = 0; j < rl.length; j++) {
+                let ra = rl[j];
+                for (let k = 0; k < ra.length; k++) {
+                    let colIdx = JpcCrossTabHelper.getColIDX(cl, ra[k]);
+                    if (colIdx >= 0) {
+                        ds[j][colIdx] = ra[k];
+                    }
+                }
+            }
+            result.push(ds);
+        }
+        return result;
+    }
+
+    let JpcCrossTabResult = {};
+    JpcCrossTabResult.initialize = function() {
+        let me = this;
+        me.dispValueIdxLst_Row = [];
+        me.dispValueIdxLst_Col = [];
+        me.dispValueIdxLst_Content = [];
+        me.dispSerialIdxLst_Row = [];
+        me.col_sum_fields_idx = [];
+        me.col_sum_fields_value_total = [];
+        me.dispSumValueLst_Col = [];
+        me.page_seg_map = [];
+        me.row_fields_idx = [];
+        me.col_fields_idx = [];
+        me.content_fields_idx = [];
+        me.row_extension_fields_idx = [];
+        me.row_sum_extension_fields_idx = [];
+        me.crsOrient = JV.PAGE_ORIENTATION_V_FIRST;
+        me.pageStatusLst = [];
+    };
+    JpcCrossTabResult.sorting = function(rptTpl, dataObj, dataSeq) {
+        let me = this;
+        //IMPORTANT: the data should be sorted in SQL/NoSQL level!
+        me.sortedRowSequence = private_SortAndOptimize(rptTpl, dataObj, dataSeq, JV.NODE_CROSS_ROW, me.row_fields_idx);
+        me.sortedColSequence = private_SortAndOptimize(rptTpl, dataObj, dataSeq, JV.NODE_CROSS_COL, me.col_fields_idx);
+        me.sortedContentSequence = private_SortForDisplayContent(rptTpl, me.sortedRowSequence, me.sortedColSequence, me.content_fields_idx);
+        JpcFieldHelper.findAndPutDataFieldIdx(rptTpl, rptTpl[JV.NODE_CROSS_INFO][JV.NODE_CROSS_COL_SUM][JV.TAB_CROSS_FIELDS], null, me.col_sum_fields_idx);
+        //pre-sum the data(for col sum display)
+        let data_details = dataObj[JV.DATA_DETAIL_DATA],
+            data_fields = [];
+        for (let i = 0; i < me.col_sum_fields_idx.length; i++) {
+            let data_field = data_details[me.col_sum_fields_idx[i]];
+            data_fields.push(data_field);
+        }
+        for (let i = 0; i < me.sortedRowSequence.length; i++) { //seg level
+            if (me.sortedRowSequence[i].length > 0) {
+                me.col_sum_fields_value_total.push([]);
+                for (let j = 0; j < me.sortedRowSequence[i].length; j++) {
+                    let rowGrandTotal = [];
+                    for (let di = 0; di < data_fields.length; di++) {
+                        rowGrandTotal.push(0.0);
+                        for (let k = 0; k < me.sortedRowSequence[i][j].length; k++) {
+                            //3. start to sum
+                            rowGrandTotal[di] = rowGrandTotal[di] + 1.0 * JpcFieldHelper.getValue(data_fields[di], me.sortedRowSequence[i][j][k]);
+                        }
+                    }
+                    me.col_sum_fields_value_total[i].push(rowGrandTotal);
+                }
+            }
+
+        }
+    };
+    JpcCrossTabResult.preSetupPages = function(rptTpl, defProperties) {
+        let rst = 0, me = this;
+        //1. original initialize
+        let maxRowRec = 1, maxColRec = 1, counterRowRec = 0, counterColRec = 0, pageIdx = 0, segCnt = me.sortedContentSequence.length;
+        let pageStatus = [true, true, false, true, false, false, false, false];
+        //2. calculate the page info one by one
+        let bands = JpcBand.createNew(rptTpl, defProperties);
+        function private_resetBandArea() {
+            JpcBandHelper.setBandArea(bands, rptTpl, pageStatus);
+            maxRowRec = JpcCrossTabHelper.getMaxRowsPerPage(bands, rptTpl);
+            maxColRec = JpcCrossTabHelper.getMaxColsPerPage(bands, rptTpl);
+        }
+        JpcFieldHelper.findAndPutDataFieldIdx(rptTpl, rptTpl[JV.NODE_CROSS_INFO][JV.NODE_CROSS_ROW_EXT][JV.TAB_CROSS_FIELDS], null, me.row_extension_fields_idx);
+        JpcFieldHelper.findAndPutDataFieldIdx(rptTpl, rptTpl[JV.NODE_CROSS_INFO][JV.NODE_CROSS_ROW_SUM_EXT][JV.TAB_CROSS_FIELDS], null, me.row_sum_extension_fields_idx);
+        for (let segIdx = 0; segIdx < segCnt; segIdx++) {
+            //2.1. seg level initialize
+            private_resetBandArea();
+            let orgMaxRowRec = maxRowRec, orgMaxColRec = maxColRec;
+            let rowSplitCnt = Math.ceil(1.0 * me.sortedRowSequence[segIdx].length / maxRowRec);
+            let colSplitCnt = Math.ceil(1.0 * me.sortedColSequence[segIdx].length / maxColRec);
+            pageStatus[JV.STATUS_CROSS_ROW_END] = true;
+            private_resetBandArea();
+            let hasAdHocRow = !JpcCrossTabHelper.chkTabEnd(JV.NODE_CROSS_ROW_SUM, rptTpl, bands, me.sortedRowSequence, segIdx, (rowSplitCnt - 1) * orgMaxRowRec, maxRowRec);
+            if (hasAdHocRow) {
+                hasAdHocRow = !JpcCrossTabHelper.chkTabEnd(JV.NODE_CROSS_ROW_EXT, rptTpl, bands, me.sortedRowSequence, segIdx, (rowSplitCnt - 1) * orgMaxRowRec, maxRowRec);
+            }
+            pageStatus[JV.STATUS_CROSS_ROW_END] = false;
+            pageStatus[JV.STATUS_CROSS_COL_END] = true;
+            private_resetBandArea();
+            let hasAdHocCol = !JpcCrossTabHelper.chkTabEnd(JV.NODE_CROSS_COL_SUM, rptTpl, bands, me.sortedColSequence, segIdx, (colSplitCnt - 1) * orgMaxColRec, maxColRec);
+            pageStatus[JV.STATUS_CROSS_COL_END] = false;
+            private_resetBandArea();
+            if (hasAdHocRow) rowSplitCnt++;
+            if (hasAdHocCol) colSplitCnt++;
+            //2.2
+            for (let colIdx = 0; colIdx < colSplitCnt; colIdx++) {
+                pageStatus[JV.STATUS_CROSS_COL_END] = colIdx == (colSplitCnt - 1)?true:false;
+                private_resetBandArea();
+                counterColRec = orgMaxColRec * colIdx;
+                let currentSortedContentSequence = me.sortedContentSequence;
+                let currentSortedColSequence = me.sortedColSequence;
+                if (hasAdHocCol && colIdx == (colSplitCnt - 1)) {
+                    currentSortedColSequence = null;
+                    currentSortedContentSequence = null;
+                    counterColRec = 0;
+                }
+                for (let rowIdx = 0; rowIdx < rowSplitCnt; rowIdx++) {
+                    pageStatus[JV.STATUS_CROSS_ROW_END] = rowIdx == (rowSplitCnt - 1)?true:false;
+                    private_resetBandArea();
+                    me.pageStatusLst.push(pageStatus.slice(0));
+                    pageIdx++;
+                    counterRowRec = orgMaxRowRec * rowIdx;
+                    let currentSortedRowSequence = me.sortedRowSequence;
+                    if (hasAdHocRow && rowIdx == (rowSplitCnt - 1)) {
+                        currentSortedRowSequence = null;
+                        currentSortedContentSequence = null;
+                        counterRowRec = 0;
+                    }
+                    private_addTabValue(me.dispValueIdxLst_Row, currentSortedRowSequence, segIdx, counterRowRec, maxRowRec, me.dispSerialIdxLst_Row, me.col_sum_fields_value_total, me.dispSumValueLst_Col);
+                    private_addTabValue(me.dispValueIdxLst_Col, currentSortedColSequence, segIdx, counterColRec, maxColRec, null, null, null);
+                    private_addContentValue(me.dispValueIdxLst_Content, currentSortedContentSequence, segIdx, counterRowRec, maxRowRec, counterColRec, maxColRec, me.page_seg_map, pageIdx);
+                }
+            }
+            JpcCrossTabHelper.initialPageStatus(pageStatus);
+        }
+        bands = null;
+        //3. set pageSeq and return the result
+        rst = pageIdx;
+        return rst;
+    };
+    JpcCrossTabResult.outputAsSimpleJSONPage = function(rptTpl, dataObj, page, bands, controls, $CURRENT_RPT) {
+        let me = this, rst = [], tabRstLst = [];
+        let segIdx = JpcCommonHelper.getSegIdxByPageIdx(page, me.page_seg_map);
+        //1 calculate the band position
+        JpcBandHelper.setBandArea(bands, rptTpl, me.pageStatusLst[page - 1]);
+        //2. start to output detail-part
+        let unitFactor = JpcCommonHelper.getUnitFactor(rptTpl);
+        //2.1 Row-Tab
+        tabRstLst.push(me.outputRowTab(rptTpl, dataObj, page, bands, unitFactor, controls));
+        //2.2 Col-Tab
+        tabRstLst.push(me.outputColTab(rptTpl, dataObj, page, bands, unitFactor, controls));
+        //2.3 Content-Tab
+        tabRstLst.push(me.outputContent(rptTpl, dataObj, page, bands, unitFactor, controls));
+        //2.4 Sum-Tab Row
+        //2.4 Sum-tab Col
+        tabRstLst.push(me.outputTabSum(rptTpl, dataObj, page, bands, unitFactor, JV.NODE_CROSS_COL_SUM, controls));
+        //2.x row tab ext
+        tabRstLst.push(me.outputTabExt(rptTpl, dataObj, page, bands, unitFactor, controls));
+        tabRstLst.push(me.outputSumTabExt(rptTpl, dataObj, page, bands, unitFactor, segIdx, controls));
+        //2.5 Discrete
+        tabRstLst.push(JpcDiscreteHelper.outputDiscreteInfo(rptTpl[JV.NODE_CROSS_INFO][JV.NODE_DISCRETE_INFO], bands, dataObj, unitFactor, me.pageStatusLst[page - 1], segIdx, 1, 0, $CURRENT_RPT));
+        for (let i = 0; i < tabRstLst.length; i++) {
+            rst = rst.concat(tabRstLst[i]);
+            tabRstLst[i] = null;
+        }
+        return rst;
+    };
+    JpcCrossTabResult.outputRowTab = function(rptTpl, dataObj, page, bands, unitFactor, controls) {
+        let me = this, rst = [];
+        let tab = rptTpl[JV.NODE_CROSS_INFO][JV.NODE_CROSS_ROW];
+        let band = bands[tab[JV.PROP_BAND_NAME]];
+        if (band) {
+            let pageStatus = me.pageStatusLst[page - 1];
+            if (pageStatus[band[JV.BAND_PROP_DISPLAY_TYPE]] == true) {
+                let tab_fields = tab[JV.TAB_CROSS_FIELDS];
+                let data_details = dataObj[JV.DATA_DETAIL_DATA];
+                let valuesIdx = me.dispValueIdxLst_Row[page - 1];
+                let serialsIdx = me.dispSerialIdxLst_Row[page - 1];
+                for (let i = 0; i < me.row_fields_idx.length; i++) {
+                    let tab_field = tab_fields[i];
+                    let data_field = data_details[me.row_fields_idx[i]];
+                    if (!(tab_field[JV.PROP_HIDDEN])) {
+                        let rows = valuesIdx.length;
+                        for (let rowIdx = 0; rowIdx < rows; rowIdx++) {
+                            rst.push(me.outputTabField(band, tab_field, data_field, valuesIdx[rowIdx], serialsIdx[rowIdx], rows, rowIdx, 1, 0, unitFactor, true, controls));
+                        }
+                    }
+                }
+            }
+        }
+        return rst;
+    };
+    JpcCrossTabResult.outputColTab = function(rptTpl, dataObj, page, bands, unitFactor, controls) {
+        let me = this, rst = [], firstTextOutput = true;
+        let tab = rptTpl[JV.NODE_CROSS_INFO][JV.NODE_CROSS_COL];
+        let band = bands[tab[JV.PROP_BAND_NAME]];
+        if (band) {
+            let pageStatus = me.pageStatusLst[page - 1];
+            if (pageStatus[band[JV.BAND_PROP_DISPLAY_TYPE]] == true) {
+                let tab_fields = tab[JV.TAB_CROSS_FIELDS];
+                let data_details = dataObj[JV.DATA_DETAIL_DATA];
+                let valuesIdx = me.dispValueIdxLst_Col[page - 1];
+                for (let i = 0; i < me.col_fields_idx.length; i++) {
+                    let tab_field = tab_fields[i];
+                    let data_field = data_details[me.col_fields_idx[i]];
+                    if (!(tab_field[JV.PROP_HIDDEN])) {
+                        let cols = valuesIdx.length;
+                        for (let colIdx = 0; colIdx < cols; colIdx++) {
+                            rst.push(me.outputTabField(band, tab_field, data_field, valuesIdx[colIdx], -1, 1, 0, cols, colIdx, unitFactor, false, controls));
+                            //2. output texts
+                            if (firstTextOutput) {
+                                if (tab[JV.PROP_TEXT]) {
+                                    rst.push(JpcTextHelper.outputText(tab[JV.PROP_TEXT], band, unitFactor, 1, 0, cols, colIdx, 1, 0));
+                                }
+                                if (tab[JV.PROP_TEXTS]) {
+                                    for (let j = 0; j < tab[JV.PROP_TEXTS].length; j++) {
+                                        rst.push(JpcTextHelper.outputText(tab[JV.PROP_TEXTS][j], band, unitFactor, 1, 0, cols, colIdx, 1, 0));
+                                    }
+                                }
+                            }
+                        }
+                        firstTextOutput = false;
+                    }
+                }
+            }
+        }
+        return rst;
+    };
+    JpcCrossTabResult.outputContent = function (rptTpl, dataObj, page, bands, unitFactor, controls) {
+        let me = this, rst = [];
+        let tab = rptTpl[JV.NODE_CROSS_INFO][JV.NODE_CROSS_CONTENT];
+        let band = bands[tab[JV.PROP_BAND_NAME]];
+        if (band) {
+            let pageStatus = me.pageStatusLst[page - 1];
+            if (pageStatus[band[JV.BAND_PROP_DISPLAY_TYPE]] == true) {
+                let tab_fields = tab[JV.TAB_CROSS_FIELDS];
+                let data_details = dataObj[JV.DATA_DETAIL_DATA];
+                let contentValuesIdx = me.dispValueIdxLst_Content[page - 1];
+                for (let i = 0; i < tab_fields.length; i++) {
+                    let tab_field = tab_fields[i];
+                    let data_field = data_details[me.content_fields_idx[i]];
+                    if (!(tab_field[JV.PROP_HIDDEN])) {
+                        let rows = contentValuesIdx.length;
+                        for (let rowIdx = 0; rowIdx < rows; rowIdx++) {
+                            let cols = contentValuesIdx[rowIdx].length;
+                            for (let colIdx = 0; colIdx < cols; colIdx++) {
+                                rst.push(me.outputTabField(band, tab_field, data_field, contentValuesIdx[rowIdx][colIdx], -1, rows, rowIdx, cols, colIdx, unitFactor, true, controls));
+                            }
+                        }
+                    }
+                }
+            }
+        }
+        return rst;
+    };
+    JpcCrossTabResult.outputTabSum = function (rptTpl, dataObj, page, bands, unitFactor, tabNodeName, controls) {
+        let me = this, rst = [],
+            tab = rptTpl[JV.NODE_CROSS_INFO][tabNodeName],
+            band = bands[tab[JV.PROP_BAND_NAME]];
+        if (band) {
+            let pageStatus = me.pageStatusLst[page - 1];
+            if (pageStatus[band[JV.BAND_PROP_DISPLAY_TYPE]] == true) {
+                let tab_fields = tab[JV.TAB_CROSS_FIELDS];
+                for (let i = 0; i < me.dispSumValueLst_Col[page - 1].length; i++) {
+                    if (me.dispSumValueLst_Col[page - 1][i] != null) {
+                        for (let j = 0; j < me.dispSumValueLst_Col[page - 1][i].length; j++) {
+                            let tab_field = tab_fields[j];
+                            let val = me.dispSumValueLst_Col[page - 1][i][j];
+                            let cellItem = JpcCommonOutputHelper.createCommonOutput(tab_field, val, controls);
+                            cellItem[JV.PROP_AREA] = JpcAreaHelper.outputArea(tab_field[JV.PROP_AREA], band, unitFactor, me.dispSumValueLst_Col[page - 1].length, i, 1, 0, 1, 0, true, false);
+                            rst.push(cellItem);
+                        }
+                    } else {
+                        let sumL = 1;
+                        for (let si = 0; si < me.dispSumValueLst_Col.length; si++) {
+                            if (me.dispSumValueLst_Col[si][0] != null) {
+                                sumL = me.dispSumValueLst_Col[si][0].length;
+                                break;
+                            }
+                        }
+                        for (let j = 0; j < sumL; j++) {
+                            let tab_field = tab_fields[j];
+                            let val = null;
+                            let cellItem = JpcCommonOutputHelper.createCommonOutput(tab_field, val, controls);
+                            cellItem[JV.PROP_AREA] = JpcAreaHelper.outputArea(tab_field[JV.PROP_AREA], band, unitFactor, me.dispSumValueLst_Col[page - 1].length, i, 1, 0, 1, 0, true, false);
+                            rst.push(cellItem);
+                        }
+                    }
+                }
+            }
+        }
+        return rst;
+    };
+    JpcCrossTabResult.outputTabExt = function (rptTpl, dataObj, page, bands, unitFactor, controls) {
+        let me = this, rst = [], firstTextOutput = true,
+            tab = rptTpl[JV.NODE_CROSS_INFO][JV.NODE_CROSS_ROW_EXT];
+        let band = bands[tab[JV.PROP_BAND_NAME]];
+        if (band) {
+            let pageStatus = me.pageStatusLst[page - 1];
+            if (pageStatus[band[JV.BAND_PROP_DISPLAY_TYPE]] == true) {
+                let tab_fields = tab[JV.TAB_CROSS_FIELDS],
+                    data_details = dataObj[JV.DATA_DETAIL_DATA],
+                    valuesIdx = me.dispValueIdxLst_Col[page - 1];
+                for (let i = 0; i < me.row_extension_fields_idx.length; i++) {
+                    let tab_field = tab_fields[i];
+                    let data_field = data_details[me.row_extension_fields_idx[i]];
+                    if (!(tab_field[JV.PROP_HIDDEN])) {
+                        let cols = valuesIdx.length;
+                        for (let colIdx = 0; colIdx < cols; colIdx++) {
+                            rst.push(me.outputTabField(band, tab_field, data_field, valuesIdx[colIdx], -1, 1, 0, cols, colIdx, unitFactor, false, controls));
+                            //2. output texts if has
+                            if (firstTextOutput) {
+                                if (tab[JV.PROP_TEXT]) {
+                                    rst.push(JpcTextHelper.outputText(tab[JV.PROP_TEXT], band, unitFactor, 1, 0, cols, colIdx, 1, 0));
+                                }
+                                if (tab[JV.PROP_TEXTS]) {
+                                    for (let j = 0; j < tab[JV.PROP_TEXTS].length; j++) {
+                                        rst.push(JpcTextHelper.outputText(tab[JV.PROP_TEXTS][j], band, unitFactor, 1, 0, cols, colIdx, 1, 0));
+                                    }
+                                }
+                            }
+                        }
+                        firstTextOutput = false;
+                    }
+                }
+            }
+        }
+        return rst;
+    };
+    JpcCrossTabResult.outputSumTabExt = function (rptTpl, dataObj, page, bands, unitFactor, segIdx, controls) {
+        let me = this, rst = [],
+            tab = rptTpl[JV.NODE_CROSS_INFO][JV.NODE_CROSS_ROW_SUM_EXT];
+        let band = bands[tab[JV.PROP_BAND_NAME]];
+        if (band) {
+            let pageStatus = me.pageStatusLst[page - 1];
+            if (pageStatus[band[JV.BAND_PROP_DISPLAY_TYPE]] == true && pageStatus[JV.STATUS_CROSS_ROW_END] === true) {
+                let tab_fields = tab[JV.TAB_CROSS_FIELDS],
+                    data_details = dataObj[JV.DATA_DETAIL_DATA],
+                    data_fields = [];
+                for (let i = 0; i < me.row_sum_extension_fields_idx.length; i++) {
+                    let data_field = data_details[me.row_sum_extension_fields_idx[i]];
+                    data_fields.push(data_field);
+                }
+                //2. initialize grand total value
+                let rowGrandTotal = [];
+                for (let di = 0; di < data_fields.length; di++) {
+                    rowGrandTotal[di] = 0.0;
+                    //3. start to sum
+                    for (let i = 0; i < me.sortedColSequence[segIdx].length; i++) {
+                        //me.sortedColSequence[segIdx][i][0] //this is the data field value index!
+                        rowGrandTotal[di] = rowGrandTotal[di] + 1.0 * JpcFieldHelper.getValue(data_fields[di], me.sortedColSequence[segIdx][i][0]);
+                    }
+                }
+                //4. output
+                for (let di = 0; di < tab_fields.length; di++) {
+                    let tab_field = tab_fields[di];
+                    if (!tab_field[JV.PROP_HIDDEN]) {
+                        let val = rowGrandTotal[di];
+                        let cellItem = JpcCommonOutputHelper.createCommonOutput(tab_field, val, controls);
+                        cellItem[JV.PROP_AREA] = JpcAreaHelper.outputArea(tab_field[JV.PROP_AREA], band, unitFactor, 1, 0, 1, 0, 1, 0, false, false);
+                        rst.push(cellItem);
+                    }
+                }
+                //output texts if has
+                if (tab[JV.PROP_TEXT]) {
+                    rst.push(JpcTextHelper.outputText(tab[JV.PROP_TEXT], band, unitFactor, 1, 0, 1, 0, 1, 0));
+                }
+                if (tab[JV.PROP_TEXTS]) {
+                    for (let j = 0; j < tab[JV.PROP_TEXTS].length; j++) {
+                        rst.push(JpcTextHelper.outputText(tab[JV.PROP_TEXTS][j], band, unitFactor, 1, 0, 1, 0, 1, 0));
+                    }
+                }
+            }
+        }
+        return rst;
+    };
+    JpcCrossTabResult.outputTabField = function (band, tab_field, data_field, valueIdx, serialIdx, rows, rowIdx, cols, colIdx, unitFactor, isRow, controls) {
+        let me = this, rst = null;
+        if (isRow == true && tab_field[JV.PROP_IS_SERIAL] && tab_field[JV.PROP_IS_SERIAL] == true) {
+            if (serialIdx >= 0) rst = JpcCommonOutputHelper.createCommonOutput(tab_field, serialIdx + 1)
+            else rst = JpcCommonOutputHelper.createCommonOutput(tab_field, "", controls);
+        } else {
+            rst = JpcCommonOutputHelper.createCommonOutput(tab_field, JpcFieldHelper.getValue(data_field, valueIdx), controls);
+        }
+        //position
+        if (isRow == true) {
+            rst[JV.PROP_AREA] = JpcAreaHelper.outputArea(tab_field[JV.PROP_AREA], band, unitFactor, rows, rowIdx, cols, colIdx, 1, 0, true, false);
+        } else {
+            rst[JV.PROP_AREA] = JpcAreaHelper.outputArea(tab_field[JV.PROP_AREA], band, unitFactor, rows, rowIdx, cols, colIdx, 1, 0, false, false);
+        }
+        return rst;
+    }
+    return JpcCrossTabResult;
+}
+
+module.exports = new JpcCrossTabSrv();

+ 98 - 0
modules/reports/rpt_component/jpc_data.js

@@ -0,0 +1,98 @@
+let JV = require('./jpc_value_define');
+let JpcData = {
+    createNew: function() {
+        let JpcDataRst = {};
+        JpcDataRst.dataSeq = [];
+        JpcDataRst.analyzeData = function(rptTpl, dataObj) {
+            let me = this;
+            if ((rptTpl) && (dataObj)) {
+                //1. get ID fields
+                let masterIDs = [];
+                for (let i = 0; i < rptTpl[JV.NODE_FIELD_MAP][JV.NODE_MASTER_FIELDS].length; i++) {
+                    let mstFieldObj = rptTpl[JV.NODE_FIELD_MAP][JV.NODE_MASTER_FIELDS][i];
+                    if ((mstFieldObj[JV.PROP_IS_ID]) && (mstFieldObj[JV.PROP_IS_ID] === 'T')) {
+                        masterIDs.push({"idx": i, "seq": mstFieldObj[JV.PROP_ID_SEQ]});
+                    }
+                }
+                let detailIDs = [];
+                for (let i = 0; i < rptTpl[JV.NODE_FIELD_MAP][JV.NODE_DETAIL_FIELDS].length; i++) {
+                    let dtlFieldObj = rptTpl[JV.NODE_FIELD_MAP][JV.NODE_DETAIL_FIELDS][i];
+                    if ((dtlFieldObj[JV.PROP_IS_ID]) && (dtlFieldObj[JV.PROP_IS_ID] === 'T')) {
+                        detailIDs.push({"idx": i, "seq": dtlFieldObj[JV.PROP_ID_SEQ]});
+                    }
+                }
+                //2. sort the ID fields
+                if (masterIDs.length > 1) {
+                    masterIDs.sort(function(a, b) {
+                        return 1*a["seq"] - 1*b["seq"];
+                    })
+                }
+                if (detailIDs.length > 1) {
+                    detailIDs.sort(function(a, b) {
+                        return 1*a["seq"] - 1*b["seq"];
+                    })
+                }
+                //3. prepare data sequence
+                if (masterIDs.length > 0) {
+                    let mst_dt_len = 0, dtl_dt_len = 0, mst_fields = [];
+                    for (let i = 0; i < masterIDs.length; i++) {
+                        mst_fields.push(dataObj[JV.DATA_MASTER_DATA][masterIDs[i]["idx"]]);
+                        mst_dt_len = dataObj[JV.DATA_MASTER_DATA][masterIDs[i]["idx"]].length;
+                    }
+                    let dtl_fields = [];
+                    for (let i = 0; i < detailIDs.length; i++) {
+                        dtl_fields.push(dataObj[JV.DATA_DETAIL_DATA][detailIDs[i]["idx"]]);
+                        dtl_dt_len = dataObj[JV.DATA_DETAIL_DATA][detailIDs[i]["idx"]].length;
+                    }
+                    let sIdx = 0;
+                    let isEqual = true;
+                    for (let i = 0; i < mst_dt_len; i++) {
+                        me.dataSeq.push([]);
+                        //then compare the master/detail ID-field value
+                        for (let j = sIdx; j < dtl_dt_len; j++) {
+                            isEqual = true;
+                            for (let k = 0; k < mst_fields.length; k++) {
+                                if (!(mst_fields[k][i] === dtl_fields[k][j])) {
+                                    isEqual = false;
+                                    break;
+                                }
+                            }
+                            if (isEqual) {
+                                me.dataSeq[i].push(j);
+                            } else {
+                                sIdx = j;
+                                //below logic is for the data robustness purpose, to avoid those strange record(detail level) which could not match even one of the master record!
+                                if (i < mst_dt_len - 1 && j < dtl_dt_len - 1) {
+                                    for (let j1 = j; j1 < dtl_dt_len; j1++) {
+                                        isEqual = true;
+                                        for (let k = 0; k < mst_fields.length; k++) {
+                                            if (!(mst_fields[k][i + 1] === dtl_fields[k][j1])) {
+                                                isEqual = false;
+                                                break;
+                                            }
+                                        }
+                                        if (isEqual) {
+                                            sIdx = j1;
+                                            break;
+                                        }
+                                    }
+                                }
+                                break;
+                            }
+                        }
+                    }
+                } else { //if no master data
+                    let field = dataObj[JV.DATA_DETAIL_DATA][0];
+                    me.dataSeq = [[]];
+                    for (let i = 0; i < field.length; i++) {
+                        me.dataSeq[0].push(i);
+                    }
+                }
+            }
+            //alert(3);
+        };
+        return JpcDataRst;
+    }
+}
+
+module.exports = JpcData;

+ 202 - 0
modules/reports/rpt_component/jpc_ex.js

@@ -0,0 +1,202 @@
+let JV = require('./jpc_value_define');
+let JpcBand = require('./jpc_band');
+let JpcFlowTab = require('./jpc_flow_tab');
+let JpcBillTab = require('./jpc_bill_tab');
+let JpcCrossTab = require('./jpc_cross_tab');
+let JpcField = require('./jpc_field');
+let JpcParam = require('./jpc_param');
+let JpcFunc = require('./jpc_function');
+let JpcData = require('./jpc_data');
+let JpcCommonHelper = require('./helper/jpc_helper_common');
+let JE = require('./jpc_rte'); //Important: for self-define function execution purpose
+
+let JpcExSrv = function(){};
+JpcExSrv.prototype.createNew = function(){
+    function private_buildDftItems(rptTpl, dftCollection, propArray, nodeName) {
+        let rst = {};
+        if (dftCollection) {
+            for (let i = 0; i < dftCollection.length; i++) {
+                let item = {};
+                for (let j = 0; j < propArray.length; j++) {
+                    item[propArray[j]] = dftCollection[i][propArray[j]];
+                }
+                rst[dftCollection[i][JV.PROP_ID]] = item;
+            }
+            if (rptTpl && rptTpl[nodeName] && rptTpl[nodeName].length > 0) {
+                for (let i = 0; i < rptTpl[nodeName].length; i++) {
+                    let rptDftItem = rptTpl[nodeName][i];
+                    if (rst[rptDftItem[JV.PROP_ID]] == undefined) {
+                        let item = {};
+                        for (let j = 0; j < propArray.length; j++) {
+                            item[propArray[j]] = rptDftItem[propArray[j]];
+                        }
+                        rst[rptDftItem[JV.PROP_ID]] = item;
+                    }
+                }
+            }
+        }
+        return rst;
+    }
+    function private_buildDftControls(rptTpl, dftControlCollection) {
+        let rst = private_buildDftItems(rptTpl,dftControlCollection, JV.CONTROL_PROPS, JV.NODE_CONTROL_COLLECTION);
+        return rst;
+    }
+    function private_buildDftFonts(rptTpl, dftFontCollection) {
+        let rst = private_buildDftItems(rptTpl,dftFontCollection, JV.FONT_PROPS, JV.NODE_FONT_COLLECTION);
+        return rst;
+    }
+    function private_buildDftStyles(rptTpl, dftStyleCollection) {
+        let rst = {};
+        if (dftStyleCollection) {
+            for (let i = 0; i < dftStyleCollection.length; i++) {
+                let item = {};
+                if (dftStyleCollection[i][JV.PROP_BORDER_STYLE] && dftStyleCollection[i][JV.PROP_BORDER_STYLE].length > 0) {
+                    for (let j = 0; j < dftStyleCollection[i][JV.PROP_BORDER_STYLE].length; j++) {
+                        let borderItem = {};
+                        for (let k = 0; k < JV.BORDER_STYLE_PROPS.length; k++) {
+                            borderItem[JV.BORDER_STYLE_PROPS[k]] = dftStyleCollection[i][JV.PROP_BORDER_STYLE][j][JV.BORDER_STYLE_PROPS[k]];
+                        }
+                        item[dftStyleCollection[i][JV.PROP_BORDER_STYLE][j][JV.PROP_POSITION]] = borderItem;
+                    }
+                }
+                rst[dftStyleCollection[i][JV.PROP_ID]] = item;
+            }
+            if (rptTpl && rptTpl[JV.NODE_STYLE_COLLECTION] && rptTpl[JV.NODE_STYLE_COLLECTION].length > 0) {
+                for (let i = 0; i < rptTpl[JV.NODE_STYLE_COLLECTION].length; i++) {
+                    let rptDftItem = rptTpl[JV.NODE_STYLE_COLLECTION][i];
+                    if (rst[rptDftItem[JV.PROP_ID]] == undefined) {
+                        let item = {};
+                        for (let j = 0; j < rptDftItem[JV.PROP_BORDER_STYLE].length; j++) {
+                            let borderItem = {};
+                            for (let k = 0; k < JV.BORDER_STYLE_PROPS.length; k++) {
+                                borderItem[JV.BORDER_STYLE_PROPS[k]] = rptDftItem[JV.PROP_BORDER_STYLE][j][JV.BORDER_STYLE_PROPS[k]];
+                            }
+                            item[rptDftItem[JV.PROP_BORDER_STYLE][j][JV.PROP_POSITION]] = borderItem;
+                        }
+                        rst[rptDftItem[JV.PROP_ID]] = item;
+                    }
+                }
+            }
+        }
+        return rst;
+    }
+    let JpcResult = {};
+    //JpcResult.report_title
+    JpcResult.initialize = function(rptTpl) {
+        let me = this;
+        if (rptTpl[JV.NODE_FLOW_INFO]) {
+            me.flowTab = JpcFlowTab.createNew();
+            me.flowTab.initialize();
+        }
+        if (rptTpl[JV.NODE_BILL_INFO]) {
+            me.billTab = JpcBillTab.createNew();
+            me.billTab.initialize();
+        }
+        //let dt1 = new Date();
+        if (rptTpl[JV.NODE_CROSS_INFO]) {
+            me.crossTab = JpcCrossTab.createNew();
+            me.crossTab.initialize();
+        }
+        me.totalPages = 0;
+        me.runTimePageData = {};
+        me.fields = JpcField.createNew(rptTpl);
+        me.params = JpcParam.createNew(rptTpl);
+        me.formulas = JpcFunc.createNew(rptTpl);
+    };
+
+    JpcResult.analyzeData = function(rptTpl, dataObj, defProperties) {
+        let me = this;
+        //1. data object
+        let dataHelper = JpcData.createNew();
+        dataHelper.analyzeData(rptTpl, dataObj);
+        //2. tab object
+        //pre-condition: the data should be sorted in SQL/NoSQL level!
+        //let dt1 = new Date();
+        if (me.flowTab) {
+            me.flowTab.sorting(rptTpl, dataObj, dataHelper.dataSeq.slice(0));
+        }
+        if (me.crossTab) {
+            me.crossTab.sorting(rptTpl, dataObj, dataHelper.dataSeq.slice(0));
+        }
+        //let dt2 = new Date();
+        //alert(dt2 - dt1);
+        //3. formulas
+        me.executeFormulas(JV.RUN_TYPE_BEFORE_PAGING, rptTpl, dataObj, me);
+        //4. paging
+        me.paging(rptTpl, dataObj, defProperties);
+        //alert('analyzeData was completed!');
+        //for garbage collection:
+        dataHelper = null;
+    };
+    JpcResult.paging = function(rptTpl, dataObj, defProperties) {
+        let me = this;
+        if (me.flowTab) {
+            me.totalPages = me.flowTab.preSetupPages(rptTpl, dataObj, defProperties);
+        } else if (me.crossTab) {
+            me.totalPages = me.crossTab.preSetupPages(rptTpl, defProperties);
+        } else if (me.billTab) {
+            //me.totalPages = billTab.paging();
+        }
+    };
+    JpcResult.executeFormulas = function(runType, $CURRENT_TEMPLATE, $CURRENT_DATA, $CURRENT_RPT) {
+        let me = this;
+        for (let i = 0; i < me.formulas.length; i++) {
+            if (me.formulas[i][JV.PROP_RUN_TYPE] === runType) {
+                let expression = me.formulas[i][JV.PROP_EXPRESSION];
+                if (expression) {
+                    eval(expression);
+                }
+            }
+        }
+    };
+    JpcResult.outputAsSimpleJSONPageArray = function(rptTpl, dataObj, startPage, endPage, defProperties) {
+        let me = this, rst = {};
+        if ((startPage > 0) && (startPage <= endPage) && (endPage <= me.totalPages)) {
+            rst[JV.NODE_CONTROL_COLLECTION] = private_buildDftControls(rptTpl, (defProperties == null)?null:defProperties.ctrls);
+            rst[JV.NODE_STYLE_COLLECTION] = private_buildDftStyles(rptTpl, (defProperties == null)?null:defProperties.styles);
+            rst[JV.NODE_FONT_COLLECTION] = private_buildDftFonts(rptTpl, (defProperties == null)?null:defProperties.fonts);
+            rst[JV.NODE_PAGE_INFO] = JpcCommonHelper.getPageSize(rptTpl);
+            rst.items = [];
+            let bands = JpcBand.createNew(rptTpl, defProperties);
+            try {
+                for (let page = startPage; page <= endPage; page++) {
+                    me.runTimePageData.currentPage = page;
+                    me.executeFormulas(JV.RUN_TYPE_BEFORE_OUTPUT, rptTpl, dataObj, me);
+                    rst.items.push(me.outputAsSimpleJSONPage(rptTpl, dataObj, bands, page, rst[JV.NODE_CONTROL_COLLECTION]));
+                }
+                if (bands[JV.BAND_PROP_MERGE_BAND]) {
+                    let mergedBand = {}, band = bands[JV.BAND_PROP_MERGE_BAND];
+                    mergedBand[JV.PROP_LEFT] = band[JV.PROP_LEFT].toFixed(0);
+                    mergedBand[JV.PROP_RIGHT] = band[JV.PROP_RIGHT].toFixed(0);
+                    mergedBand[JV.PROP_TOP] = band[JV.PROP_TOP].toFixed(0);
+                    mergedBand[JV.PROP_BOTTOM] = band[JV.PROP_BOTTOM].toFixed(0);
+                    mergedBand[JV.BAND_PROP_STYLE] = band[JV.BAND_PROP_STYLE];
+                    rst[JV.BAND_PROP_MERGE_BAND] = mergedBand;
+                }
+            } finally {
+                bands = null;
+            }
+        }
+        return rst;
+    };
+    JpcResult.outputAsSimpleJSONPage = function(rptTpl, dataObj, bands, page, controls) {
+        let me = this, rst = null;
+        if (me.totalPages >= page) {
+            rst = {};
+            rst[JV.PROP_PAGE_SEQ] = page;
+            //rst.cells = [];
+            if (me.flowTab) {
+                rst.cells = me.flowTab.outputAsSimpleJSONPage(rptTpl, dataObj, page, bands, controls, me);
+            } else if (me.crossTab) {
+                rst.cells = me.crossTab.outputAsSimpleJSONPage(rptTpl, dataObj, page, bands, controls, me);
+            } else if (me.billTab) {
+                //
+            }
+        }
+        return rst;
+    };
+    //JpcEx.rte.currentRptObj = JpcResult;
+    return JpcResult;
+}
+
+module.exports = new JpcExSrv();

+ 47 - 0
modules/reports/rpt_component/jpc_field.js

@@ -0,0 +1,47 @@
+let JV = require('./jpc_value_define');
+let JpcField = {
+    createNew: function(rptTpl) {
+        let JpcFieldResult = {};
+        let me = this;
+        JpcFieldResult[JV.NODE_DISCRETE_FIELDS] = {};
+        if (rptTpl[JV.NODE_FIELD_MAP] && rptTpl[JV.NODE_FIELD_MAP][JV.NODE_DISCRETE_FIELDS]) {
+            for (let i = 0; i < rptTpl[JV.NODE_FIELD_MAP][JV.NODE_DISCRETE_FIELDS].length; i++) {
+                me.createSingle(rptTpl[JV.NODE_FIELD_MAP][JV.NODE_DISCRETE_FIELDS][i], JpcFieldResult[JV.NODE_DISCRETE_FIELDS], rptTpl, JV.DATA_DISCRETE_DATA, i);
+            }
+        }
+        JpcFieldResult[JV.NODE_MASTER_FIELDS] = {};
+        if (rptTpl[JV.NODE_FIELD_MAP] && rptTpl[JV.NODE_FIELD_MAP][JV.NODE_MASTER_FIELDS]) {
+            for (let i = 0; i < rptTpl[JV.NODE_FIELD_MAP][JV.NODE_MASTER_FIELDS].length; i++) {
+                me.createSingle(rptTpl[JV.NODE_FIELD_MAP][JV.NODE_MASTER_FIELDS][i], JpcFieldResult[JV.NODE_MASTER_FIELDS], rptTpl, JV.DATA_MASTER_DATA, i);
+            }
+        }
+        JpcFieldResult[JV.NODE_DETAIL_FIELDS] = {};
+        if (rptTpl[JV.NODE_FIELD_MAP] && rptTpl[JV.NODE_FIELD_MAP][JV.NODE_DETAIL_FIELDS]) {
+            for (let i = 0; i < rptTpl[JV.NODE_FIELD_MAP][JV.NODE_DETAIL_FIELDS].length; i++) {
+                me.createSingle(rptTpl[JV.NODE_FIELD_MAP][JV.NODE_DETAIL_FIELDS][i], JpcFieldResult[JV.NODE_DETAIL_FIELDS], rptTpl, JV.DATA_DETAIL_DATA, i);
+            }
+        }
+        JpcFieldResult[JV.NODE_NO_MAPPING_FIELDS] = {};
+        if (rptTpl[JV.NODE_NO_MAPPING_FIELDS]) {
+            for (let i = 0; i < rptTpl[JV.NODE_NO_MAPPING_FIELDS].length; i++) {
+                me.createSingle(rptTpl[JV.NODE_NO_MAPPING_FIELDS][i], JpcFieldResult[JV.NODE_NO_MAPPING_FIELDS], rptTpl, "NA", JV.BLANK_FIELD_INDEX);
+            }
+        }
+        //
+        return JpcFieldResult;
+    },
+    createSingle: function(fieldNode, parentObj, rptTpl, dataNodeName, sequence) {
+        let me = this;
+        if (fieldNode && fieldNode[JV.PROP_ID]) {
+            let item = {};
+            item[JV.PROP_ID] = fieldNode[JV.PROP_ID];
+            item[JV.PROP_NAME] = fieldNode[JV.PROP_NAME];
+            item[JV.PROP_DATA_TYPE] = fieldNode[JV.PROP_DATA_TYPE];
+            item.DataNodeName = dataNodeName;
+            item.DataSeq = sequence;
+            parentObj[JV.PROP_ID + "_" + fieldNode[JV.PROP_ID]] = item;
+        }
+    }
+}
+
+module.exports = JpcField;

+ 229 - 0
modules/reports/rpt_component/jpc_flow_tab.js

@@ -0,0 +1,229 @@
+let JV = require('./jpc_value_define');
+let JE = require('./jpc_rte');
+let JpcFieldHelper = require('./helper/jpc_helper_field');
+let JpcBandHelper = require('./helper/jpc_helper_band');
+let JpcBand = require('./jpc_band');
+let JpcFlowTabHelper = require('./helper/jpc_helper_flow_tab');
+let JpcCommonHelper = require('./helper/jpc_helper_common');
+let JpcDiscreteHelper = require('./helper/jpc_helper_discrete');
+let JpcTextHelper = require('./helper/jpc_helper_text');
+let JpcCommonOutputHelper = require('./helper/jpc_helper_common_output');
+let JpcAreaHelper = require('./helper/jpc_helper_area');
+
+let JpcFlowTabSrv = function(){};
+JpcFlowTabSrv.prototype.createNew = function(){
+    function private_addPageValue(ValuedIdxLst, sortedSequence, preRec, nextRec,page_seg_map, segIdx, pageIdx) {
+        let vIdx = [];
+        for (let vi = 0; vi < nextRec; vi++) {
+            if (sortedSequence.length > preRec + vi) {
+                vIdx.push(sortedSequence[preRec + vi]);
+            } else {
+                vIdx.push(JV.BLANK_VALUE_INDEX);
+            }
+        }
+        page_seg_map.push([pageIdx, segIdx]);
+        ValuedIdxLst.push(vIdx);
+    }
+    let JpcFlowTabResult = {};
+    JpcFlowTabResult.initialize = function() {
+        let me = this;
+        me.segments = [];
+        me.dispValueIdxLst = [];
+        me.page_seg_map = [];
+        me.disp_fields_idx = [];
+        me.seg_sum_fields_idx = [];
+        me.seg_sum_tab_fields = [];
+        me.page_sum_fields_idx = [];
+        me.group_fields_idx = [];
+        me.pageStatusLst = [];
+        me.groupSumValLst = [];
+        me.segSumValLst = [];
+        me.multiCols = 1;
+    };
+    JpcFlowTabResult.sorting = function(rptTpl, dataObj, dataSeq) {
+        let me = this;
+        JpcFieldHelper.findAndPutDataFieldIdx(rptTpl, rptTpl[JV.NODE_FLOW_INFO][JV.NODE_FLOW_SEG_SUM][JV.PROP_SUM_FIELDS], me.seg_sum_tab_fields, me.seg_sum_fields_idx);
+        JpcFieldHelper.findAndPutDataFieldIdx(rptTpl, rptTpl[JV.NODE_FLOW_INFO][JV.NODE_FLOW_PAGE_SUM][JV.PROP_SUM_FIELDS], null, me.page_sum_fields_idx);
+        JpcFieldHelper.findAndPutDataFieldIdx(rptTpl, rptTpl[JV.NODE_FLOW_INFO][JV.NODE_FLOW_GROUP][JV.PROP_GROUP_FIELDS], null, me.group_fields_idx);
+        for (let si = 0; si < dataSeq.length; si++) {
+            me.segments.push(dataSeq[si].slice(0));
+        }
+        //pre-sum the data(for seg sum display)
+        let data_details = dataObj[JV.DATA_DETAIL_DATA],
+            data_fields = [];
+        for (let i = 0; i < me.seg_sum_fields_idx.length; i++) {
+            let data_field = data_details[me.seg_sum_fields_idx[i]];
+            data_fields.push(data_field);
+        }
+        for (let i = 0; i < me.segments.length; i++) { //seg level
+            if (me.segments[i].length > 0) {
+                let rowGrandTotal = [];
+                for (let di = 0; di < data_fields.length; di++) {
+                    rowGrandTotal.push(0.0);
+                    for (let j = 0; j < me.segments[i].length; j++) {
+                        //3. start to sum
+                        rowGrandTotal[di] = rowGrandTotal[di] + 1.0 * JpcFieldHelper.getValue(data_fields[di], me.segments[i][j]);
+                    }
+                }
+                me.segSumValLst.push(rowGrandTotal);
+            }
+
+        }
+    };
+    JpcFlowTabResult.preSetupPages = function (rptTpl, dataOjb, defProperties) {
+        let rst = 0, me = this, counterRowRec = 0, maxRowRec = 1, pageIdx = 0;
+        JpcFieldHelper.findAndPutDataFieldIdx(rptTpl, rptTpl[JV.NODE_FLOW_INFO][JV.NODE_FLOW_CONTENT][JV.PROP_FLOW_FIELDS], null, me.disp_fields_idx);
+        let bands = JpcBand.createNew(rptTpl, defProperties);
+        let pageStatus = [true, true, false, false, false, false, false, false];
+        if (rptTpl[JV.NODE_FLOW_INFO][JV.PROP_MULTI_COLUMN]) {
+            me.multiCols = 1 * rptTpl[JV.NODE_FLOW_INFO][JV.PROP_MULTI_COLUMN];
+        }
+        function private_resetBandArea() {
+            JpcBandHelper.setBandArea(bands, rptTpl, pageStatus);
+            maxRowRec = JpcFlowTabHelper.getMaxRowsPerPage(bands, rptTpl);
+        }
+        for (let segIdx = 0; segIdx < me.segments.length; segIdx++) {
+            private_resetBandArea();
+            let orgMaxRowRec = maxRowRec;
+            let rowSplitCnt = Math.ceil(1.0 * me.segments[segIdx].length / orgMaxRowRec);
+            pageStatus[JV.STATUS_SEGMENT_END] = true;
+            private_resetBandArea();
+            let hasAdHocRow = !JpcFlowTabHelper.chkSegEnd(bands, rptTpl, me.segments, segIdx, (rowSplitCnt - 1) * orgMaxRowRec, maxRowRec);
+            if (hasAdHocRow) rowSplitCnt++;
+            if (rowSplitCnt % me.multiCols > 0) {
+                rowSplitCnt++
+            }
+            for (let rowIdx = 0; rowIdx < rowSplitCnt; rowIdx++) {
+                pageStatus[JV.STATUS_SEGMENT_END] = rowIdx == (rowSplitCnt - 1)?true:false;
+                if (pageIdx > 0) pageStatus[JV.STATUS_REPORT_START] = false;
+                private_resetBandArea();
+                me.pageStatusLst.push(pageStatus.slice(0));
+                pageIdx++;
+                counterRowRec = orgMaxRowRec * rowIdx;
+                private_addPageValue(me.dispValueIdxLst, me.segments[segIdx], counterRowRec, maxRowRec,me.page_seg_map, segIdx, pageIdx);
+            }
+            pageStatus[JV.STATUS_SEGMENT_END] = false;
+            pageStatus[JV.STATUS_REPORT_START] = false;
+        }
+        rst = Math.ceil(1.0 * pageIdx / me.multiCols);
+        return rst;
+    };
+    JpcFlowTabResult.outputAsSimpleJSONPage = function (rptTpl, dataObj, page, bands, controls, $CURRENT_RPT) {
+        let me = this, rst = [], tabRstLst = [];
+        let segIdx = JpcCommonHelper.getSegIdxByPageIdx(page, me.page_seg_map);
+        //1 calculate the band position
+        JpcBandHelper.setBandArea(bands, rptTpl, me.pageStatusLst[page - 1]);
+        //2. start to output detail-part
+        let unitFactor = JpcCommonHelper.getUnitFactor(rptTpl);
+        for (let pi = 0; pi < me.multiCols; pi++) {
+            let actualPage = (page - 1) * me.multiCols + pi + 1;
+            //2.1 Content-Tab
+            tabRstLst.push(me.outputContent(rptTpl, dataObj, actualPage, bands, unitFactor, controls, pi));
+            //2.2 Column tab
+            tabRstLst.push(me.outputColumn(rptTpl, dataObj, actualPage, segIdx, bands, unitFactor, controls, pi));
+            //2.3 Sum Seg
+            tabRstLst.push(me.outputSegSum(rptTpl, dataObj, actualPage, segIdx, bands, unitFactor, controls));
+            //2.4 Sum Page
+            //2.5 Discrete
+            if (pi == 0) {
+                tabRstLst.push(JpcDiscreteHelper.outputDiscreteInfo(rptTpl[JV.NODE_FLOW_INFO][JV.NODE_DISCRETE_INFO], bands, dataObj, unitFactor, me.pageStatusLst[actualPage - 1], segIdx, 1, pi, $CURRENT_RPT));
+            }
+        }
+        for (let i = 0; i < tabRstLst.length; i++) {
+            rst = rst.concat(tabRstLst[i]);
+            tabRstLst[i] = null;
+        }
+        return rst;
+    };
+    JpcFlowTabResult.outputContent = function(rptTpl, dataObj, page, bands, unitFactor, controls, multiColIdx, $CURRENT_RPT) {
+        let me = this, rst = [];
+        let tab = rptTpl[JV.NODE_FLOW_INFO][JV.NODE_FLOW_CONTENT];
+        let band = bands[tab[JV.PROP_BAND_NAME]];
+        if (band) {
+            let pageStatus = me.pageStatusLst[page - 1];
+            if (pageStatus[band[JV.BAND_PROP_DISPLAY_TYPE]] == true) {
+                let tab_fields = tab[JV.PROP_FLOW_FIELDS];
+                let data_details = dataObj[JV.DATA_DETAIL_DATA];
+                let contentValuesIdx = me.dispValueIdxLst[page - 1];
+                for (let i = 0; i < tab_fields.length; i++) {
+                    let tab_field = tab_fields[i];
+                    let data_field = null;
+                    if (me.disp_fields_idx[i] != JV.BLANK_FIELD_INDEX) {
+                        data_field = data_details[me.disp_fields_idx[i]];
+                    } else {
+                        data_field = JE.F(tab_field[JV.PROP_FIELD_ID], $CURRENT_RPT);
+                        if (data_field) {
+                            data_field = data_field[JV.PROP_AD_HOC_DATA];
+                        }
+                    }
+                    if (!(tab_field[JV.PROP_HIDDEN])) {
+                        for (let rowIdx = 0; rowIdx < contentValuesIdx.length; rowIdx++) {
+                            rst.push(me.outputTabField(band, tab_field, data_field, contentValuesIdx[rowIdx], -1, contentValuesIdx.length, rowIdx, 1, 0, unitFactor, true, controls, multiColIdx));
+                        }
+                    }
+                }
+            }
+        }
+        return rst;
+    };
+    JpcFlowTabResult.outputColumn = function (rptTpl, dataObj, page, segIdx, bands, unitFactor, controls, multiColIdx) {
+        let me = this, rst = [];
+        let tab = rptTpl[JV.NODE_FLOW_INFO][JV.NODE_FLOW_COLUMN];
+        let band = bands[tab[JV.PROP_BAND_NAME]];
+        if (band) {
+            let pageStatus = me.pageStatusLst[page - 1];
+            if (pageStatus[band[JV.BAND_PROP_DISPLAY_TYPE]] == true) {
+                if (tab[JV.PROP_TEXT]) {
+                    rst.push(JpcTextHelper.outputText(tab[JV.PROP_TEXT], band, unitFactor, 1, 0, 1, 0, me.multiCols, multiColIdx));
+                }
+                if (tab[JV.PROP_TEXTS]) {
+                    for (let j = 0; j < tab[JV.PROP_TEXTS].length; j++) {
+                        rst.push(JpcTextHelper.outputText(tab[JV.PROP_TEXTS][j], band, unitFactor, 1, 0, 1, 0, me.multiCols, multiColIdx));
+                    }
+                }
+                if (tab[JV.NODE_DISCRETE_INFO]) {
+                    rst = rst.concat(JpcDiscreteHelper.outputDiscreteInfo(tab[JV.NODE_DISCRETE_INFO], bands, dataObj, unitFactor, me.pageStatusLst[page - 1], segIdx, me.multiCols, multiColIdx));
+                }
+            }
+        }
+        return rst;
+    };
+    JpcFlowTabResult.outputSegSum = function (rptTpl, dataObj, page, segIdx, bands, unitFactor, controls) {
+        let me = this, rst = [];
+        let tab = rptTpl[JV.NODE_FLOW_INFO][JV.NODE_FLOW_SEG_SUM];
+        let band = bands[tab[JV.PROP_BAND_NAME]];
+        if (band) {
+            let pageStatus = me.pageStatusLst[page - 1];
+            if (pageStatus[band[JV.BAND_PROP_DISPLAY_TYPE]] == true) {
+                let tab_fields = me.seg_sum_tab_fields;
+                for (let i = 0; i < tab_fields.length; i++) {
+                    let cellItem = JpcCommonOutputHelper.createCommonOutput(tab_fields[i], me.segSumValLst[segIdx][i], controls);
+                    cellItem[JV.PROP_AREA] = JpcAreaHelper.outputArea(tab_fields[i][JV.PROP_AREA], band, unitFactor, 1, 0, 1, 0, me.multiCols, 0, true, false);
+                    rst.push(cellItem);
+                }
+                if (tab[JV.PROP_TEXT]) {
+                    rst.push(JpcTextHelper.outputText(tab[JV.PROP_TEXT], band, unitFactor, 1, 0, 1, 0, me.multiCols, 0));
+                }
+                if (tab[JV.PROP_TEXTS]) {
+                    for (let j = 0; j < tab[JV.PROP_TEXTS].length; j++) {
+                        rst.push(JpcTextHelper.outputText(tab[JV.PROP_TEXTS][j], band, unitFactor, 1, 0, 1, 0, me.multiCols, 0));
+                    }
+                }
+                if (tab[JV.NODE_DISCRETE_INFO]) {
+                    rst = rst.concat(JpcDiscreteHelper.outputDiscreteInfo(tab[JV.NODE_DISCRETE_INFO], bands, dataObj, unitFactor, me.pageStatusLst[page - 1], segIdx, me.multiCols, 0));
+                }
+            }
+        }
+        return rst;
+    };
+    JpcFlowTabResult.outputTabField = function (band, tab_field, data_field, valueIdx, serialIdx, rows, rowIdx, cols, colIdx, unitFactor, isRow, controls, multiColIdx) {
+        let me = this, rst = null;
+        rst = JpcCommonOutputHelper.createCommonOutput(tab_field, JpcFieldHelper.getValue(data_field, valueIdx), controls);
+        rst[JV.PROP_AREA] = JpcAreaHelper.outputArea(tab_field[JV.PROP_AREA], band, unitFactor, rows, rowIdx, cols, colIdx, me.multiCols, multiColIdx, true, false);
+        return rst;
+    }
+
+    return JpcFlowTabResult;
+}
+
+module.exports = new JpcFlowTabSrv();

+ 18 - 0
modules/reports/rpt_component/jpc_function.js

@@ -0,0 +1,18 @@
+let JV = require('./jpc_value_define');
+let JpcFunc = {
+    createNew: function (rptTpl) {
+        let me = this;
+        let rst = [];
+        if (rptTpl[JV.NODE_FORMULAS]) {
+            for (let i = 0; i < rptTpl[JV.NODE_FORMULAS].length; i++) {
+                let item = {};
+                item[JV.PROP_RUN_TYPE] = rptTpl[JV.NODE_FORMULAS][i][JV.PROP_RUN_TYPE];
+                item[JV.PROP_EXPRESSION] = rptTpl[JV.NODE_FORMULAS][i][JV.PROP_EXPRESSION];
+                rst.push(item);
+            }
+        }
+        return rst;
+    }
+};
+
+module.exports = JpcFunc;

+ 27 - 0
modules/reports/rpt_component/jpc_param.js

@@ -0,0 +1,27 @@
+let JV = require('./jpc_value_define');
+let JpcParam = {
+    createNew: function(rptTpl) {
+        let JpcParamResult = {};
+        let me = this;
+        if (rptTpl[JV.NODE_DISCRETE_PARAMS]) {
+            for (let i = 0; i < rptTpl[JV.NODE_DISCRETE_PARAMS].length; i++) {
+                me.createSingle(rptTpl[JV.NODE_DISCRETE_PARAMS][i], JpcParamResult, rptTpl, i);
+            }
+        }
+        return JpcParamResult;
+    },
+    createSingle: function(paramNode, parentObj, rptTpl, sequence) {
+        let me = this;
+        if (paramNode && paramNode[JV.PROP_ID]) {
+            let item = {};
+            item[JV.PROP_ID] = paramNode[JV.PROP_ID];
+            item[JV.PROP_NAME] = paramNode[JV.PROP_NAME];
+            item[JV.PROP_DATA_TYPE] = paramNode[JV.PROP_DATA_TYPE];
+            if (paramNode[JV.PROP_DFT_VALUE]) item[JV.PROP_DFT_VALUE] = paramNode[JV.PROP_DFT_VALUE];
+            item.DataSeq = sequence;
+            parentObj[JV.PROP_ID + "_" + paramNode[JV.PROP_ID]] = item;
+        }
+    }
+}
+
+module.exports = JpcParam;

+ 47 - 0
modules/reports/rpt_component/jpc_rte.js

@@ -0,0 +1,47 @@
+/**
+ * Created by Tony on 2016/12/28.
+ */
+
+let JV = require('./jpc_value_define');
+let JE = {
+    F: function(fID, $CURRENT_RPT) {
+        let rst = null;
+        if ($CURRENT_RPT && ($CURRENT_RPT.fields[JV.NODE_DETAIL_FIELDS][JV.PROP_ID + "_" + fID])) {
+            rst = $CURRENT_RPT.fields[JV.NODE_DETAIL_FIELDS][JV.PROP_ID + "_" + fID];
+        } else if ($CURRENT_RPT && ($CURRENT_RPT.fields[JV.NODE_MASTER_FIELDS][JV.PROP_ID + "_" + fID])) {
+            rst = $CURRENT_RPT.fields[JV.NODE_MASTER_FIELDS][JV.PROP_ID + "_" + fID];
+        } else if ($CURRENT_RPT && ($CURRENT_RPT.fields[JV.NODE_DISCRETE_FIELDS][JV.PROP_ID + "_" + fID])) {
+            rst = $CURRENT_RPT.fields[JV.NODE_DISCRETE_FIELDS][JV.PROP_ID + "_" + fID];
+        } else if ($CURRENT_RPT && ($CURRENT_RPT.fields[JV.NODE_NO_MAPPING_FIELDS][JV.PROP_ID + "_" + fID])) {
+            rst = $CURRENT_RPT.fields[JV.NODE_NO_MAPPING_FIELDS][JV.PROP_ID + "_" + fID];
+        } else {
+            rst = {msg: "the Field-ID is not valid, no result could be found!"};
+        }
+        return rst;
+    },
+    P: function(pID, $CURRENT_RPT) {
+        let rst = null;
+        if ($CURRENT_RPT && ($CURRENT_RPT.params[JV.PROP_ID + "_" + pID])) {
+            rst = $CURRENT_RPT.params[JV.PROP_ID + "_" + pID];
+        } else {
+            rst = {msg: "the Param-ID is not valid, no result was found!"};
+        }
+        return rst;
+    },
+    getCurrentPage: function ($CURRENT_RPT) {
+        let rst = 0;
+        if ($CURRENT_RPT) {
+            rst = $CURRENT_RPT.runTimePageData.currentPage;
+        }
+        return rst;
+    },
+    getTotalPage: function ($CURRENT_RPT) {
+        let rst = 0;
+        if ($CURRENT_RPT) {
+            rst = $CURRENT_RPT.totalPages;
+        }
+        return rst;
+    }
+}
+
+module.exports = JE;

+ 15 - 0
modules/reports/rpt_component/jpc_value_define.js

@@ -0,0 +1,15 @@
+const fs = require('fs');
+let VAL_DEF = null;
+
+getValDefine = function() {
+    if (!(VAL_DEF)) {
+        let data = fs.readFileSync(__dirname.slice(0, __dirname.length - 30) + '/public/web/rpt_value_define.js', 'utf8', 'r');
+        eval(data);
+        VAL_DEF = JV;
+        JV = null;
+    }
+    return VAL_DEF;
+}
+
+
+module.exports = getValDefine();

+ 281 - 0
modules/reports/util/excel_base_files/theme1.xml

@@ -0,0 +1,281 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<a:theme xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" name="Office 主题">
+	<a:themeElements>
+		<a:clrScheme name="Office">
+			<a:dk1>
+				<a:sysClr val="windowText" lastClr="000000"/>
+			</a:dk1>
+			<a:lt1>
+				<a:sysClr val="window" lastClr="FFFFFF"/>
+			</a:lt1>
+			<a:dk2>
+				<a:srgbClr val="1F497D"/>
+			</a:dk2>
+			<a:lt2>
+				<a:srgbClr val="EEECE1"/>
+			</a:lt2>
+			<a:accent1>
+				<a:srgbClr val="4F81BD"/>
+			</a:accent1>
+			<a:accent2>
+				<a:srgbClr val="C0504D"/>
+			</a:accent2>
+			<a:accent3>
+				<a:srgbClr val="9BBB59"/>
+			</a:accent3>
+			<a:accent4>
+				<a:srgbClr val="8064A2"/>
+			</a:accent4>
+			<a:accent5>
+				<a:srgbClr val="4BACC6"/>
+			</a:accent5>
+			<a:accent6>
+				<a:srgbClr val="F79646"/>
+			</a:accent6>
+			<a:hlink>
+				<a:srgbClr val="0000FF"/>
+			</a:hlink>
+			<a:folHlink>
+				<a:srgbClr val="800080"/>
+			</a:folHlink>
+		</a:clrScheme>
+		<a:fontScheme name="Office">
+			<a:majorFont>
+				<a:latin typeface="Cambria"/>
+				<a:ea typeface=""/>
+				<a:cs typeface=""/>
+				<a:font script="Jpan" typeface="MS Pゴシック"/>
+				<a:font script="Hang" typeface="맑은 고딕"/>
+				<a:font script="Hans" typeface="宋体"/>
+				<a:font script="Hant" typeface="新細明體"/>
+				<a:font script="Arab" typeface="Times New Roman"/>
+				<a:font script="Hebr" typeface="Times New Roman"/>
+				<a:font script="Thai" typeface="Tahoma"/>
+				<a:font script="Ethi" typeface="Nyala"/>
+				<a:font script="Beng" typeface="Vrinda"/>
+				<a:font script="Gujr" typeface="Shruti"/>
+				<a:font script="Khmr" typeface="MoolBoran"/>
+				<a:font script="Knda" typeface="Tunga"/>
+				<a:font script="Guru" typeface="Raavi"/>
+				<a:font script="Cans" typeface="Euphemia"/>
+				<a:font script="Cher" typeface="Plantagenet Cherokee"/>
+				<a:font script="Yiii" typeface="Microsoft Yi Baiti"/>
+				<a:font script="Tibt" typeface="Microsoft Himalaya"/>
+				<a:font script="Thaa" typeface="MV Boli"/>
+				<a:font script="Deva" typeface="Mangal"/>
+				<a:font script="Telu" typeface="Gautami"/>
+				<a:font script="Taml" typeface="Latha"/>
+				<a:font script="Syrc" typeface="Estrangelo Edessa"/>
+				<a:font script="Orya" typeface="Kalinga"/>
+				<a:font script="Mlym" typeface="Kartika"/>
+				<a:font script="Laoo" typeface="DokChampa"/>
+				<a:font script="Sinh" typeface="Iskoola Pota"/>
+				<a:font script="Mong" typeface="Mongolian Baiti"/>
+				<a:font script="Viet" typeface="Times New Roman"/>
+				<a:font script="Uigh" typeface="Microsoft Uighur"/>
+			</a:majorFont>
+			<a:minorFont>
+				<a:latin typeface="Calibri"/>
+				<a:ea typeface=""/>
+				<a:cs typeface=""/>
+				<a:font script="Jpan" typeface="MS Pゴシック"/>
+				<a:font script="Hang" typeface="맑은 고딕"/>
+				<a:font script="Hans" typeface="宋体"/>
+				<a:font script="Hant" typeface="新細明體"/>
+				<a:font script="Arab" typeface="Arial"/>
+				<a:font script="Hebr" typeface="Arial"/>
+				<a:font script="Thai" typeface="Tahoma"/>
+				<a:font script="Ethi" typeface="Nyala"/>
+				<a:font script="Beng" typeface="Vrinda"/>
+				<a:font script="Gujr" typeface="Shruti"/>
+				<a:font script="Khmr" typeface="DaunPenh"/>
+				<a:font script="Knda" typeface="Tunga"/>
+				<a:font script="Guru" typeface="Raavi"/>
+				<a:font script="Cans" typeface="Euphemia"/>
+				<a:font script="Cher" typeface="Plantagenet Cherokee"/>
+				<a:font script="Yiii" typeface="Microsoft Yi Baiti"/>
+				<a:font script="Tibt" typeface="Microsoft Himalaya"/>
+				<a:font script="Thaa" typeface="MV Boli"/>
+				<a:font script="Deva" typeface="Mangal"/>
+				<a:font script="Telu" typeface="Gautami"/>
+				<a:font script="Taml" typeface="Latha"/>
+				<a:font script="Syrc" typeface="Estrangelo Edessa"/>
+				<a:font script="Orya" typeface="Kalinga"/>
+				<a:font script="Mlym" typeface="Kartika"/>
+				<a:font script="Laoo" typeface="DokChampa"/>
+				<a:font script="Sinh" typeface="Iskoola Pota"/>
+				<a:font script="Mong" typeface="Mongolian Baiti"/>
+				<a:font script="Viet" typeface="Arial"/>
+				<a:font script="Uigh" typeface="Microsoft Uighur"/>
+			</a:minorFont>
+		</a:fontScheme>
+		<a:fmtScheme name="Office">
+			<a:fillStyleLst>
+				<a:solidFill>
+					<a:schemeClr val="phClr"/>
+				</a:solidFill>
+				<a:gradFill rotWithShape="1">
+					<a:gsLst>
+						<a:gs pos="0">
+							<a:schemeClr val="phClr">
+								<a:tint val="50000"/>
+								<a:satMod val="300000"/>
+							</a:schemeClr>
+						</a:gs>
+						<a:gs pos="35000">
+							<a:schemeClr val="phClr">
+								<a:tint val="37000"/>
+								<a:satMod val="300000"/>
+							</a:schemeClr>
+						</a:gs>
+						<a:gs pos="100000">
+							<a:schemeClr val="phClr">
+								<a:tint val="15000"/>
+								<a:satMod val="350000"/>
+							</a:schemeClr>
+						</a:gs>
+					</a:gsLst>
+					<a:lin ang="16200000" scaled="1"/>
+				</a:gradFill>
+				<a:gradFill rotWithShape="1">
+					<a:gsLst>
+						<a:gs pos="0">
+							<a:schemeClr val="phClr">
+								<a:shade val="51000"/>
+								<a:satMod val="130000"/>
+							</a:schemeClr>
+						</a:gs>
+						<a:gs pos="80000">
+							<a:schemeClr val="phClr">
+								<a:shade val="93000"/>
+								<a:satMod val="130000"/>
+							</a:schemeClr>
+						</a:gs>
+						<a:gs pos="100000">
+							<a:schemeClr val="phClr">
+								<a:shade val="94000"/>
+								<a:satMod val="135000"/>
+							</a:schemeClr>
+						</a:gs>
+					</a:gsLst>
+					<a:lin ang="16200000" scaled="0"/>
+				</a:gradFill>
+			</a:fillStyleLst>
+			<a:lnStyleLst>
+				<a:ln w="9525" cap="flat" cmpd="sng" algn="ctr">
+					<a:solidFill>
+						<a:schemeClr val="phClr">
+							<a:shade val="95000"/>
+							<a:satMod val="105000"/>
+						</a:schemeClr>
+					</a:solidFill>
+					<a:prstDash val="solid"/>
+				</a:ln>
+				<a:ln w="25400" cap="flat" cmpd="sng" algn="ctr">
+					<a:solidFill>
+						<a:schemeClr val="phClr"/>
+					</a:solidFill>
+					<a:prstDash val="solid"/>
+				</a:ln>
+				<a:ln w="38100" cap="flat" cmpd="sng" algn="ctr">
+					<a:solidFill>
+						<a:schemeClr val="phClr"/>
+					</a:solidFill>
+					<a:prstDash val="solid"/>
+				</a:ln>
+			</a:lnStyleLst>
+			<a:effectStyleLst>
+				<a:effectStyle>
+					<a:effectLst>
+						<a:outerShdw blurRad="40000" dist="20000" dir="5400000" rotWithShape="0">
+							<a:srgbClr val="000000">
+								<a:alpha val="38000"/>
+							</a:srgbClr>
+						</a:outerShdw>
+					</a:effectLst>
+				</a:effectStyle>
+				<a:effectStyle>
+					<a:effectLst>
+						<a:outerShdw blurRad="40000" dist="23000" dir="5400000" rotWithShape="0">
+							<a:srgbClr val="000000">
+								<a:alpha val="35000"/>
+							</a:srgbClr>
+						</a:outerShdw>
+					</a:effectLst>
+				</a:effectStyle>
+				<a:effectStyle>
+					<a:effectLst>
+						<a:outerShdw blurRad="40000" dist="23000" dir="5400000" rotWithShape="0">
+							<a:srgbClr val="000000">
+								<a:alpha val="35000"/>
+							</a:srgbClr>
+						</a:outerShdw>
+					</a:effectLst>
+					<a:scene3d>
+						<a:camera prst="orthographicFront">
+							<a:rot lat="0" lon="0" rev="0"/>
+						</a:camera>
+						<a:lightRig rig="threePt" dir="t">
+							<a:rot lat="0" lon="0" rev="1200000"/>
+						</a:lightRig>
+					</a:scene3d>
+					<a:sp3d>
+						<a:bevelT w="63500" h="25400"/>
+					</a:sp3d>
+				</a:effectStyle>
+			</a:effectStyleLst>
+			<a:bgFillStyleLst>
+				<a:solidFill>
+					<a:schemeClr val="phClr"/>
+				</a:solidFill>
+				<a:gradFill rotWithShape="1">
+					<a:gsLst>
+						<a:gs pos="0">
+							<a:schemeClr val="phClr">
+								<a:tint val="40000"/>
+								<a:satMod val="350000"/>
+							</a:schemeClr>
+						</a:gs>
+						<a:gs pos="40000">
+							<a:schemeClr val="phClr">
+								<a:tint val="45000"/>
+								<a:shade val="99000"/>
+								<a:satMod val="350000"/>
+							</a:schemeClr>
+						</a:gs>
+						<a:gs pos="100000">
+							<a:schemeClr val="phClr">
+								<a:shade val="20000"/>
+								<a:satMod val="255000"/>
+							</a:schemeClr>
+						</a:gs>
+					</a:gsLst>
+					<a:path path="circle">
+						<a:fillToRect l="50000" t="-80000" r="50000" b="180000"/>
+					</a:path>
+				</a:gradFill>
+				<a:gradFill rotWithShape="1">
+					<a:gsLst>
+						<a:gs pos="0">
+							<a:schemeClr val="phClr">
+								<a:tint val="80000"/>
+								<a:satMod val="300000"/>
+							</a:schemeClr>
+						</a:gs>
+						<a:gs pos="100000">
+							<a:schemeClr val="phClr">
+								<a:shade val="30000"/>
+								<a:satMod val="200000"/>
+							</a:schemeClr>
+						</a:gs>
+					</a:gsLst>
+					<a:path path="circle">
+						<a:fillToRect l="50000" t="50000" r="50000" b="50000"/>
+					</a:path>
+				</a:gradFill>
+			</a:bgFillStyleLst>
+		</a:fmtScheme>
+	</a:themeElements>
+	<a:objectDefaults/>
+	<a:extraClrSchemeLst/>
+</a:theme>

+ 687 - 0
modules/reports/util/rpt_excel_util.js

@@ -0,0 +1,687 @@
+/**
+ * Created by Tony on 2017/4/1.
+ */
+let JV = require('../rpt_component/jpc_value_define');
+let fs = require('fs');
+let JSZip = require("jszip");
+let strUtil = require('../../../public/stringUtil');
+let jpcCmnHelper = require('../rpt_component/helper/jpc_helper_common');
+let DPI = jpcCmnHelper.getScreenDPI()[0];
+const dftHeadXml = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>';
+
+function writeContentTypes(sheets) {
+    let rst = [];
+    rst.push(dftHeadXml + '\r\n');
+    rst.push('<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">');
+    //...
+    rst.push('<Override PartName="/xl/theme/theme1.xml" ContentType="application/vnd.openxmlformats-officedocument.theme+xml"/>');
+    rst.push('<Override PartName="/xl/styles.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml"/>');
+    rst.push('<Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>');
+    rst.push('<Default Extension="xml" ContentType="application/xml"/>');
+    rst.push('<Override PartName="/xl/workbook.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"/>');
+    rst.push('<Override PartName="/docProps/app.xml" ContentType="application/vnd.openxmlformats-officedocument.extended-properties+xml"/>');
+    rst.push('<Override PartName="/xl/sharedStrings.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml"/>');
+    for (let i = 0; i < sheets.length; i++) {
+        rst.push('<Override PartName="/xl/worksheets/sheet' + (i + 1) + '.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"/>')
+    }
+    rst.push('<Override PartName="/docProps/core.xml" ContentType="application/vnd.openxmlformats-package.core-properties+xml"/>');
+    rst.push('</Types>');
+    return rst;
+}
+function writeRootRels(){
+    let rst = [];
+    rst.push(dftHeadXml + '\r\n');
+    rst.push('<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">');
+    rst.push('<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="xl/workbook.xml"/>');
+    rst.push('<Relationship Id="rId2" Type="http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties" Target="docProps/core.xml"/>');
+    rst.push('<Relationship Id="rId3" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties" Target="docProps/app.xml"/>');
+    rst.push('</Relationships>');
+    return rst;
+}
+function writeApp(sheets) {
+    let rst = [];
+    rst.push(dftHeadXml + '\r\n');
+    rst.push('<Properties xmlns="http://schemas.openxmlformats.org/officeDocument/2006/extended-properties" xmlns:vt="http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes">');
+    rst.push('<Application>Microsoft Excel</Application>');
+    rst.push('<DocSecurity>0</DocSecurity>');
+    rst.push('<ScaleCrop>false</ScaleCrop>');
+    rst.push('<HeadingPairs>');
+    rst.push('<vt:vector size="2" baseType="variant">');
+    rst.push('<vt:variant><vt:lpstr>工作表</vt:lpstr></vt:variant>');
+    rst.push('<vt:variant><vt:i4>' + sheets.length + '</vt:i4></vt:variant>');
+    rst.push('</vt:vector>');
+    rst.push('</HeadingPairs>');
+    rst.push('<TitlesOfParts>');
+    rst.push('<vt:vector size="' + sheets.length + '" baseType="lpstr">');
+    for (let i = 0; i < sheets.length; i++) {
+        rst.push('<vt:lpstr>' + sheets[i].sheetName + '</vt:lpstr>')
+    }
+    rst.push('</vt:vector>');
+    rst.push('</TitlesOfParts>');
+    rst.push('<Company>SmartCost</Company>');
+    rst.push('<LinksUpToDate>false</LinksUpToDate>');
+    rst.push('<SharedDoc>false</SharedDoc>');
+    rst.push('<HyperlinksChanged>false</HyperlinksChanged>');
+    rst.push('<AppVersion>12.0000</AppVersion>');
+    //rst.push('');
+    rst.push('</Properties>');
+    return rst;
+}
+function writeCore() {
+    let rst = [];
+    p_fillZero = function(val){
+        let rst = val;
+        if (val < 10) {
+            rst = '0' + val;
+        }
+        return rst;
+    };
+    rst.push(dftHeadXml + '\r\n');
+    rst.push('<cp:coreProperties xmlns:cp="http://schemas.openxmlformats.org/package/2006/metadata/core-properties" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:dcterms="http://purl.org/dc/terms/" xmlns:dcmitype="http://purl.org/dc/dcmitype/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">');
+    rst.push('<dc:creator>SmartCost</dc:creator>');
+    rst.push('<cp:lastModifiedBy>SmartCost</cp:lastModifiedBy>');
+    let dt = new Date(), dtStr = dt.getFullYear() + '-' + p_fillZero(dt.getMonth()+1) + '-' + p_fillZero(dt.getDate()) + 'T' +
+        p_fillZero(dt.getHours()) + ':' + p_fillZero(dt.getMinutes()) + ':' + p_fillZero(dt.getSeconds()) + 'Z';
+    rst.push('<dcterms:created xsi:type="dcterms:W3CDTF">' + dtStr + '</dcterms:created>');
+    rst.push('<dcterms:modified xsi:type="dcterms:W3CDTF">' + dtStr + '</dcterms:modified>');
+    //rst.push('');
+    rst.push('</cp:coreProperties>');
+    return rst;
+}
+function writeXlWorkBook(sheets){
+    let rst = [];
+    rst.push(dftHeadXml + '\r\n');
+    rst.push('<workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">');
+    rst.push('<fileVersion appName="xl" lastEdited="4" lowestEdited="4" rupBuild="4505"/>');
+    rst.push('<workbookPr defaultThemeVersion="124226"/>');
+    rst.push('<bookViews><workbookView xWindow="360" yWindow="345" windowWidth="14655" windowHeight="4305"/></bookViews>');
+    rst.push('<sheets>');
+    for (let i = 0; i < sheets.length; i++) {
+        rst.push('<sheet name="' + sheets[i].sheetName + '" sheetId="' + (i + 1) + '" r:id="rId' + (i + 1) + '"/>')
+    }
+    rst.push('</sheets>');
+    rst.push('<calcPr calcId="124519"/>');
+    //rst.push('');
+    rst.push('</workbook>');
+    return rst;
+}
+function writeXlRels(sheets){
+    let rst = [], idx = 1;
+    rst.push(dftHeadXml + '\r\n');
+    rst.push('<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">');
+    for (let i = 0; i < sheets.length; i++) {
+        rst.push('<Relationship Id="rId' + idx + '" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet" Target="worksheets/sheet' + (i + 1) + '.xml"/>')
+        idx++;
+    }
+    rst.push('<Relationship Id="rId' + idx + '" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme" Target="theme/theme1.xml"/>');
+    idx++;
+    rst.push('<Relationship Id="rId' + idx + '" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles" Target="styles.xml"/>');
+    idx++;
+    rst.push('<Relationship Id="rId' + idx + '" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings" Target="sharedStrings.xml"/>');
+    //rst.push('');
+    rst.push('</Relationships>');
+    return rst;
+}
+function writeTheme(){
+    let rst = fs.readFileSync(__dirname + '/excel_base_files/theme1.xml', 'utf8', 'r');
+    return rst;
+}
+function writeStyles(stylesObj){
+    let rst = [];
+    rst.push(dftHeadXml + '\r\n');
+    rst.push('<styleSheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">');
+    //1. push fonts
+    rst.push('<fonts count="' + stylesObj.fonts.length + '">')
+    for (let i = 0; i < stylesObj.fonts.length; i++) {
+        let font = stylesObj.fonts[i];
+        rst.push('<font>');
+        if (strUtil.convertStrToBoolean(font[JV.FONT_PROPS[3]])) {
+            rst.push('<b/>');
+        }
+        rst.push('<sz val="' + font.size + '"/>');
+        rst.push('<color indexed="' + font.colorIdx + '"/>');
+        rst.push('<name val="' + font[JV.FONT_PROPS[0]] + '"/>');
+        rst.push('<charset val="' + font.charset + '"/>');
+        rst.push('</font>');
+    }
+    rst.push('</fonts>');
+    //2. push default fills
+    rst.push('<fills count="2"><fill><patternFill patternType="none" /></fill><fill><patternFill patternType="gray125" /></fill></fills>');
+    //3. push borders
+    rst.push('<borders count="' + stylesObj.borders.length + '">')
+    private_setBorder = function(border, borderDirection) {
+        if (border[borderDirection][JV.PROP_LINE_WEIGHT] == 0) {
+            rst.push('<' + borderDirection.toLowerCase() + '/>');
+        } else {
+            let bW = 'thin';
+            if (border[borderDirection][JV.PROP_LINE_WEIGHT] == 2) bW = 'medium';
+            if (border[borderDirection][JV.PROP_LINE_WEIGHT] > 2) bW = 'thick';
+            rst.push('<' + borderDirection.toLowerCase() + ' style="' + bW + '">' + '<color indexed="64"/>' + '</' + borderDirection.toLowerCase() + '>');
+        }
+    };
+    for (let i = 0; i < stylesObj.borders.length; i++) {
+        let border = stylesObj.borders[i];
+        rst.push('<border>');
+        private_setBorder(border, JV.PROP_LEFT);
+        private_setBorder(border, JV.PROP_RIGHT);
+        private_setBorder(border, JV.PROP_TOP);
+        private_setBorder(border, JV.PROP_BOTTOM);
+        rst.push('<diagonal />');
+        rst.push('</border>');
+    }
+    rst.push('</borders>');
+    //4. push cellStyleXfs
+    rst.push('<cellStyleXfs count="1"><xf numFmtId="0" fontId="0" fillId="0" borderId="0"><alignment vertical="center"/></xf></cellStyleXfs>');
+    //5. push cellXfs
+    rst.push('<cellXfs count="' + stylesObj.cellXfs.length + '">');
+    for (let i = 0; i < stylesObj.cellXfs.length; i++) {
+        let excelStyle = stylesObj.cellXfs[i];
+        rst.push('<xf numFmtId="0" fontId="' + excelStyle.fontId + '" fillId="0" borderId="' + excelStyle.borderId + '" xfId="0">');
+        let alignStr = '<alignment horizontal="' + excelStyle[JV.CONTROL_PROPS[2]] + '" vertical="' + excelStyle[JV.CONTROL_PROPS[3]] + '"';
+        if (strUtil.convertStrToBoolean(excelStyle[JV.CONTROL_PROPS[1]])) {
+            alignStr = alignStr + ' shrinkToFit="1"';
+        }
+        if (strUtil.convertStrToBoolean(excelStyle[JV.CONTROL_PROPS[4]])) {
+            alignStr = alignStr + ' wrapText="1"';
+        }
+        alignStr = alignStr + '/>';
+        rst.push(alignStr);
+        rst.push('</xf>');
+    }
+    rst.push('</cellXfs>');
+    //6. others (xfl style / dxfs / tableStyles)
+    rst.push('<cellStyles count="1"><cellStyle name="常规" xfId="0" builtinId="0"/></cellStyles>');
+    rst.push('<dxfs count="0"/>');
+    rst.push('<tableStyles count="0" defaultTableStyle="TableStyleMedium9" defaultPivotStyle="PivotStyleLight16"/>');
+    rst.push('</styleSheet>');
+    return rst;
+}
+function writeSharedString(sharedStrList){
+    let rst = [];
+    if (sharedStrList && sharedStrList.length > 0) {
+        rst.push(dftHeadXml + '\r\n');
+        rst.push('<sst xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" count="' + sharedStrList.length + '" uniqueCount="' + sharedStrList.length + '">');
+        for (let i = 0; i < sharedStrList.length; i++) {
+            //rst.push('<si><t>' + sharedStrList[i] + '</t></si>');
+            if (typeof sharedStrList[i] === 'string') {
+                rst.push('<si><t>' + sharedStrList[i].replace('|','\r\n') + '</t></si>');
+            } else {
+                rst.push('<si><t>' + sharedStrList[i] + '</t></si>');
+            }
+        }
+        rst.push('</sst>');
+    }
+    return rst;
+}
+function writeSheets(pageData, sharedStrList, stylesObj){
+    let rst = [];
+    private_pushDftFont = function(){
+        let font = {};
+        if (!(stylesObj.fonts)) {
+            stylesObj.fonts = [];
+        }
+        font[JV.FONT_PROPS[0]] = "宋体"; //font name
+        font.size = 11;
+        font.charset = 134;
+        font.colorIdx = "8";
+        stylesObj.fonts.push(font);
+    };
+    private_pushDftFont();
+    for (let i = 0; i < pageData.items.length; i++) {
+        rst.push(writeSheet(pageData, pageData.items[i], sharedStrList, stylesObj));
+    }
+    return rst;
+}
+function writeSheet(pageData, sheetData, sharedStrList, stylesObj){
+    let rst = [], xPos = [], yPos = [], headerStr = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
+    let cacheBorderCell = {};
+    xPos.push(0);
+    yPos.push(0);
+    private_pre_analyze_pos = function(){
+        let cell, pos;
+        sheetData.cells.sort(function(cell1, cell2) {
+            let rst = 0;
+            if (cell1[JV.PROP_AREA][JV.PROP_TOP] > cell2[JV.PROP_AREA][JV.PROP_TOP]) {
+                rst = 1;
+            } else if (cell1[JV.PROP_AREA][JV.PROP_TOP] < cell2[JV.PROP_AREA][JV.PROP_TOP]) {
+                rst = -1;
+            } else {
+                if (cell1[JV.PROP_AREA][JV.PROP_LEFT] > cell2[JV.PROP_AREA][JV.PROP_LEFT]) {
+                    rst = 1;
+                } else if (cell1[JV.PROP_AREA][JV.PROP_LEFT] < cell2[JV.PROP_AREA][JV.PROP_LEFT]) {
+                    rst = -1;
+                }
+            }
+            return rst;
+        });
+        for (let i = 0; i < sheetData.cells.length; i++) {
+            cell = sheetData.cells[i];
+            pos = cell[JV.PROP_AREA][JV.PROP_LEFT];
+            if (xPos.indexOf(pos) < 0) xPos.push(pos);
+            pos = cell[JV.PROP_AREA][JV.PROP_RIGHT];
+            if (xPos.indexOf(pos) < 0) xPos.push(pos);
+            pos = cell[JV.PROP_AREA][JV.PROP_TOP];
+            if (yPos.indexOf(pos) < 0) yPos.push(pos);
+            pos = cell[JV.PROP_AREA][JV.PROP_BOTTOM];
+            if (yPos.indexOf(pos) < 0) yPos.push(pos);
+        }
+        xPos.sort(private_array_sort);
+        yPos.sort(private_array_sort);
+    };
+    private_array_sort = function(i1, i2){
+        let rst = 0;
+        if (i1 > i2) {rst = 1} else
+        if (i1 < i2) rst = -1;
+        return rst;
+    };
+    private_getCellIdxStr = function(idx){
+        let rst = 'A';
+        if (idx < 26) {
+            rst = headerStr[idx];
+        } else if (idx < 26*26+26) {
+            let ti = Math.floor(idx / 26), tj = idx % 26;
+            rst = headerStr[ti - 1] + headerStr[tj];
+        } else if (idx < 26*26*26+26) {
+            let ti = Math.floor(idx / (26*26)), tj = Math.floor((idx - ti * 26*26) / 26), tk = idx % 26;
+            rst = headerStr[ti - 1] + headerStr[tj-1] + headerStr[tk];
+        }
+        return rst;
+    };
+    private_getSharedStrIdx = function(val) {
+        let rst = sharedStrList.indexOf(val);
+        if (rst < 0) {
+            sharedStrList.push(val);
+            rst = sharedStrList.length - 1;
+        }
+        return rst;
+    };
+    private_getFontId = function(cell) {
+        let rst = 0, hasFont = false;
+        if (!(stylesObj.fonts)) {
+            stylesObj.fonts = [];
+            //for (let i = 0; i < sheetData.font_collection)
+        }
+        let sheetFont = pageData.font_collection[cell.font];
+        for (let i = 0; i < stylesObj.fonts.length; i++) {
+            let font = stylesObj.fonts[i];
+            if (sheetFont) {
+                if (font[JV.FONT_PROPS[0]] === sheetFont[JV.FONT_PROPS[0]] && font.size === Math.round(sheetFont[JV.FONT_PROPS[1]] * 3 / 4) && font[JV.FONT_PROPS[3]] == sheetFont[JV.FONT_PROPS[3]]) {
+                    hasFont = true;
+                    rst = i;
+                    break;
+                }
+            } else {
+                break;
+            }
+        }
+        if (!hasFont) {
+            let font = {};
+            font[JV.FONT_PROPS[0]] = sheetFont[JV.FONT_PROPS[0]]; //font name
+            font.size = Math.round(sheetFont[JV.FONT_PROPS[1]] * 3 / 4);
+            font.charset = 134;
+            font.colorIdx = "8";
+            font[JV.FONT_PROPS[3]] = sheetFont[JV.FONT_PROPS[3]]; //font bold
+            stylesObj.fonts.push(font);
+            rst = stylesObj.fonts.length - 1;
+        }
+        return rst;
+    };
+    private_checkBorder = function(cell, border, sheetBorder) {
+        let rst = true, borderLineWidths = [], sheetBorderLineWidths = [];
+        borderLineWidths.push(border[JV.PROP_LEFT][JV.PROP_LINE_WEIGHT]);
+        borderLineWidths.push(border[JV.PROP_RIGHT][JV.PROP_LINE_WEIGHT]);
+        borderLineWidths.push(border[JV.PROP_TOP][JV.PROP_LINE_WEIGHT]);
+        borderLineWidths.push(border[JV.PROP_BOTTOM][JV.PROP_LINE_WEIGHT]);
+        if (sheetBorder[JV.PROP_LEFT] && sheetBorder[JV.PROP_LEFT][JV.PROP_LINE_WEIGHT]) {
+            sheetBorderLineWidths.push(private_chkAndGetMergeLine(cell, sheetBorder, JV.PROP_LEFT));
+        } else {
+            sheetBorderLineWidths.push(0);
+        }
+        if (sheetBorder[JV.PROP_RIGHT] && sheetBorder[JV.PROP_RIGHT][JV.PROP_LINE_WEIGHT]) {
+            sheetBorderLineWidths.push(private_chkAndGetMergeLine(cell, sheetBorder, JV.PROP_RIGHT));
+        } else {
+            sheetBorderLineWidths.push(0);
+        }
+        if (sheetBorder[JV.PROP_TOP] && sheetBorder[JV.PROP_TOP][JV.PROP_LINE_WEIGHT]) {
+            sheetBorderLineWidths.push(private_chkAndGetMergeLine(cell, sheetBorder, JV.PROP_TOP));
+        } else {
+            sheetBorderLineWidths.push(0);
+        }
+        if (sheetBorder[JV.PROP_BOTTOM] && sheetBorder[JV.PROP_BOTTOM][JV.PROP_LINE_WEIGHT]) {
+            sheetBorderLineWidths.push(private_chkAndGetMergeLine(cell, sheetBorder, JV.PROP_BOTTOM));
+        } else {
+            sheetBorderLineWidths.push(0);
+        }
+        for (let i = 0; i < 4; i++) {
+            if (borderLineWidths[i] != sheetBorderLineWidths[i]) {
+                rst = false;
+                break;
+            }
+        }
+        return rst;
+    };
+    private_chkAndGetMergeLine = function(cell, sheetBorder, borderStr) {
+        let rst = sheetBorder[borderStr][JV.PROP_LINE_WEIGHT], mergeBorder = pageData[JV.BAND_PROP_MERGE_BAND];
+        if (cell[JV.PROP_AREA][borderStr] == mergeBorder[borderStr]) {
+            let destStyle = pageData[JV.NODE_STYLE_COLLECTION][mergeBorder[JV.PROP_STYLE][JV.PROP_ID]];
+            rst = destStyle[borderStr][JV.PROP_LINE_WEIGHT];
+        }
+        return parseInt(rst);
+    };
+    private_getIniBorder = function() {
+        let rst = {};
+        rst[JV.PROP_LEFT] = {};
+        rst[JV.PROP_LEFT][JV.PROP_LINE_WEIGHT] = 0;
+        rst[JV.PROP_RIGHT] = {};
+        rst[JV.PROP_RIGHT][JV.PROP_LINE_WEIGHT] = 0;
+        rst[JV.PROP_TOP] = {};
+        rst[JV.PROP_TOP][JV.PROP_LINE_WEIGHT] = 0;
+        rst[JV.PROP_BOTTOM] = {};
+        rst[JV.PROP_BOTTOM][JV.PROP_LINE_WEIGHT] = 0;
+        return rst;
+    };
+    private_getBorderId = function(cell) {
+        let rst = 0, hasBorder = false;
+        if (!(stylesObj.borders)) {
+            stylesObj.borders = [];
+        }
+        let sheetBorder = pageData[JV.NODE_STYLE_COLLECTION][cell.style];
+        for (let i = 0; i < stylesObj.borders.length; i++) {
+            let border = stylesObj.borders[i];
+            if (private_checkBorder(cell, border, sheetBorder)) {
+                hasBorder = true;
+                rst = i;
+                break;
+            }
+        }
+        if (!hasBorder) {
+            let border = private_getIniBorder();
+            if (sheetBorder && sheetBorder[JV.PROP_LEFT]) {
+                border[JV.PROP_LEFT][JV.PROP_LINE_WEIGHT] = private_chkAndGetMergeLine(cell, sheetBorder, JV.PROP_LEFT);
+            }
+            if (sheetBorder && sheetBorder[JV.PROP_RIGHT]) {
+                border[JV.PROP_RIGHT][JV.PROP_LINE_WEIGHT] = private_chkAndGetMergeLine(cell, sheetBorder, JV.PROP_RIGHT);
+            }
+            if (sheetBorder && sheetBorder[JV.PROP_TOP]) {
+                border[JV.PROP_TOP][JV.PROP_LINE_WEIGHT] = private_chkAndGetMergeLine(cell, sheetBorder, JV.PROP_TOP);
+            }
+            if (sheetBorder && sheetBorder[JV.PROP_BOTTOM]) {
+                border[JV.PROP_BOTTOM][JV.PROP_LINE_WEIGHT] = private_chkAndGetMergeLine(cell, sheetBorder, JV.PROP_BOTTOM);
+            }
+            stylesObj.borders.push(border);
+            rst = stylesObj.borders.length - 1;
+        }
+        return rst;
+    };
+    private_checkControl = function(cellControl, sheetControl) {
+        let rst = true;
+        for (let i = 0; i < JV.CONTROL_PROPS.length; i++) {
+            if (cellControl[JV.CONTROL_PROPS[i]] != sheetControl[JV.CONTROL_PROPS[i]]) {
+                rst = false;
+                break;
+            }
+        }
+        return rst;
+    };
+    private_getStyleId = function(cell) {
+        let rst = 1, hasStyle = false;
+        if (!(stylesObj.cellXfs)) stylesObj.cellXfs = [];
+        let fontId = private_getFontId(cell);
+        let borderId = private_getBorderId(cell);
+        let cellControl = pageData[JV.NODE_CONTROL_COLLECTION][cell[JV.PROP_CONTROL]];
+        for (let i = 0; i < stylesObj.cellXfs.length; i++) {
+            let sheetControl = stylesObj.cellXfs[i];
+            if (sheetControl.fontId == fontId && sheetControl.borderId == borderId) {
+                if (private_checkControl(cellControl, sheetControl)) {
+                    rst = i;
+                    hasStyle = true;
+                    break;
+                }
+            }
+        }
+        if (!hasStyle) {
+            let sheetControl = {};
+            sheetControl.fontId = fontId;
+            sheetControl.borderId = borderId;
+            for (let i = 0; i < JV.CONTROL_PROPS.length; i++) {
+                sheetControl[JV.CONTROL_PROPS[i]] = cellControl[JV.CONTROL_PROPS[i]];
+            }
+            stylesObj.cellXfs.push(sheetControl);
+            rst = stylesObj.cellXfs.length - 1;
+        }
+        return rst;
+    };
+    private_setCols = function(){
+        //remark: 1 excel width = 2.117 mm
+        rst.push('<cols>');
+        let w = 0;
+        for (let i = 1; i < xPos.length - 1; i++) {
+            w = 1.0 * (xPos[i + 1] - xPos[i]) / DPI * 25.4 / 2.117;
+            w = Math.round(w * 1000) / 1000;
+            rst.push('<col min="' + i +'" max="' + i +'" width="' + w + '" customWidth="1"/>');
+        }
+        rst.push('</cols>');
+    };
+    private_setMergedCells = function() {
+        let cell, idxR, idxL, idxT, idxB, cnt = 0;
+        rst.push('<mergeCells count="?">');
+        let startIdx = rst.length - 1;
+        for (let i = 0; i < sheetData.cells.length; i++) {
+            cell = sheetData.cells[i];
+            idxR = xPos.indexOf(cell[JV.PROP_AREA][JV.PROP_RIGHT]);
+            idxL = xPos.indexOf(cell[JV.PROP_AREA][JV.PROP_LEFT]);
+            idxB = yPos.indexOf(cell[JV.PROP_AREA][JV.PROP_BOTTOM]);
+            idxT = yPos.indexOf(cell[JV.PROP_AREA][JV.PROP_TOP]);
+            if (idxR - idxL > 1 || idxB - idxT > 1) {
+                rst.push('<mergeCell ref="' + private_getCellIdxStr(idxL - 1) + idxT + ':' + private_getCellIdxStr(idxR - 2) + (idxB - 1) + '"/>');
+                cnt++;
+            }
+        }
+        rst[startIdx] = '<mergeCells count="' + cnt + '">';
+        rst.push('</mergeCells>');
+    };
+    private_chkIfNeedCacheCell = function(cell){
+        let rst = false;
+        if (cell[JV.PROP_AREA][JV.PROP_LEFT] == pageData[JV.BAND_PROP_MERGE_BAND][JV.PROP_LEFT] ||
+            cell[JV.PROP_AREA][JV.PROP_RIGHT] == pageData[JV.BAND_PROP_MERGE_BAND][JV.PROP_RIGHT] ||
+            cell[JV.PROP_AREA][JV.PROP_TOP] == pageData[JV.BAND_PROP_MERGE_BAND][JV.PROP_TOP] ||
+            cell[JV.PROP_AREA][JV.PROP_BOTTOM] == pageData[JV.BAND_PROP_MERGE_BAND][JV.PROP_BOTTOM]){
+            if (cell[JV.PROP_AREA][JV.PROP_LEFT] >= pageData[JV.BAND_PROP_MERGE_BAND][JV.PROP_LEFT] &&
+                cell[JV.PROP_AREA][JV.PROP_RIGHT] <= pageData[JV.BAND_PROP_MERGE_BAND][JV.PROP_RIGHT] &&
+                cell[JV.PROP_AREA][JV.PROP_TOP] >= pageData[JV.BAND_PROP_MERGE_BAND][JV.PROP_TOP] &&
+                cell[JV.PROP_AREA][JV.PROP_BOTTOM] <= pageData[JV.BAND_PROP_MERGE_BAND][JV.PROP_BOTTOM]) {
+                rst = true;
+            }
+        }
+        return rst;
+    };
+    private_cacheMergeBandBorderIdxs = function() {
+        let cell, idxR, idxL, idxT, idxB;
+        for (let i = 0; i < sheetData.cells.length; i++) {
+            cell = sheetData.cells[i];
+            idxR = xPos.indexOf(cell[JV.PROP_AREA][JV.PROP_RIGHT]);
+            idxL = xPos.indexOf(cell[JV.PROP_AREA][JV.PROP_LEFT]);
+            idxB = yPos.indexOf(cell[JV.PROP_AREA][JV.PROP_BOTTOM]);
+            idxT = yPos.indexOf(cell[JV.PROP_AREA][JV.PROP_TOP]);
+            if (idxR - idxL > 1 || idxB - idxT > 1) {
+                if (private_chkIfNeedCacheCell(cell)) {
+                    for (let xi = idxL; xi < idxR; xi++) {
+                        for (let yj = idxT; yj < idxB; yj++) {
+                            cacheBorderCell[private_getCellIdxStr(xi - 1) + yj] = cell;
+                        }
+                    }
+                }
+            }
+        }
+    };
+    private_getMergedCellStyleId = function(preStyleId, colIdxStr) {
+        let rst = preStyleId;
+        if (cacheBorderCell[colIdxStr]) {
+            rst = private_getStyleId(cacheBorderCell[colIdxStr]);
+        }
+        return rst;
+    };
+    private_setSheetData = function(){
+        //remark: 1 excel height = 0.3612 mm
+        rst.push('<sheetData>');
+        let spanX = xPos.length - 2, cellIdx = 0, h = 0,
+            hasMoreCols = true, nextColIdx = -1,
+            nextRowIdx = yPos.indexOf(sheetData.cells[cellIdx][JV.PROP_AREA][JV.PROP_TOP])
+            ;
+        private_cacheMergeBandBorderIdxs();
+        for (let i = 1; i < yPos.length - 1; i++) {
+            h = 1.0 * (yPos[i+1] - yPos[i]) / DPI * 25.4 / 0.3612;
+            h = Math.round(h * 1000) / 1000;
+            rst.push('<row r="' + i + '" spans="1:' + spanX + '" ht="' + h + '" customHeight="1">');
+            //then put the cells of this row
+            let colIdxStr = '';
+            hasMoreCols = true;
+            while (nextRowIdx < i) {
+                if (cellIdx >= sheetData.cells.length || nextRowIdx > i) {
+                    break;
+                } else {
+                    cellIdx++;
+                    nextRowIdx = yPos.indexOf(sheetData.cells[cellIdx][JV.PROP_AREA][JV.PROP_TOP]);
+                }
+            }
+            if (nextRowIdx > i) {
+                hasMoreCols = false;
+            }
+            nextColIdx = xPos.indexOf(sheetData.cells[cellIdx][JV.PROP_AREA][JV.PROP_LEFT]);
+            let preStyleIdx = 1;
+            for (let j = 1; j < xPos.length - 1; j++) {
+                colIdxStr = private_getCellIdxStr(j - 1);
+                if (hasMoreCols) {
+                    if (nextColIdx == j) {
+                        let styleIdx = private_getStyleId(sheetData.cells[cellIdx]);
+                        preStyleIdx = styleIdx;
+                        if (strUtil.isEmptyString(sheetData.cells[cellIdx][JV.PROP_VALUE])) {
+                            rst.push('<c r="' + colIdxStr + i + '" s="' + styleIdx + '"/>');
+                            //should setup the right style instead!
+                        } else {
+                            let valIdx = private_getSharedStrIdx(sheetData.cells[cellIdx][JV.PROP_VALUE]);
+                            rst.push('<c r="' + colIdxStr + i + '" s="' + styleIdx + '" t="s">');
+                            rst.push('<v>' + valIdx + '</v>');
+                            rst.push('</c>');
+                        }
+                        cellIdx++;
+                        if (cellIdx < sheetData.cells.length) {
+                            nextRowIdx = yPos.indexOf(sheetData.cells[cellIdx][JV.PROP_AREA][JV.PROP_TOP]);
+                            if (nextRowIdx > i) {
+                                hasMoreCols = false;
+                            } else {
+                                nextColIdx = xPos.indexOf(sheetData.cells[cellIdx][JV.PROP_AREA][JV.PROP_LEFT]);
+                            }
+                        } else {
+                            hasMoreCols = false;
+                        }
+                    } else if (nextColIdx < 0) {
+                        //impossible!
+                        console.log('has abnormal case!');
+                        hasMoreCols = false;
+                    } else {
+                        //rst.push('<c r="' + colIdxStr + i + '" s="' + preStyleIdx + '"/>');
+                        rst.push('<c r="' + colIdxStr + i + '" s="' + private_getMergedCellStyleId(preStyleIdx, colIdxStr + i) + '"/>');
+                    }
+                } else {
+                    //rst.push('<c r="' + colIdxStr + i + '" s="' + preStyleIdx + '"/>');
+                    rst.push('<c r="' + colIdxStr + i + '" s="' + private_getMergedCellStyleId(preStyleIdx, colIdxStr + i) + '"/>');
+                }
+            }
+            rst.push('</row>');
+        }
+        //sheetData.cells.length
+        rst.push('</sheetData>');
+    };
+    private_pre_analyze_pos();
+    rst.push(dftHeadXml + '\r\n');
+    rst.push('<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">');
+    let colStr = private_getCellIdxStr(xPos.length - 3);
+    rst.push('<dimension ref="A1:' + colStr + '' + yPos.length + '"/>');
+    rst.push('<sheetViews><sheetView tabSelected="1" workbookViewId="0">');
+    rst.push('<selection sqref="A1:' + colStr + '1"/>');
+    rst.push('</sheetView></sheetViews>');
+    rst.push('<sheetFormatPr defaultRowHeight="13.5"/>');
+    private_setCols();
+    private_setSheetData();
+    private_setMergedCells();
+    rst.push('<phoneticPr fontId="1" type="noConversion"/>');
+    rst.push('<pageMargins left="0.315" right="0.215" top="0.315" bottom="0.315" header="0" footer="0"/>');
+    //rst.push('<pageSetup paperSize="9" fitToWidth="0" fitToHeight="0" orientation="landscape" horizontalDpi="300" verticalDpi="300"/>');
+    rst.push('<headerFooter alignWithMargins="0"/>');
+    rst.push('</worksheet>');
+    //rst.push('');
+    return rst;
+}
+
+module.exports = {
+    exportExcel: function (pageData, fName, options, callback) {
+        let rptOptions = (options || {singlePage: false, fileName: 'report'});
+        let sheets = [];
+        for (let i = 0; i < pageData.items.length; i++) {
+            sheets.push({sheetName: '第' + (i + 1) + '页'});
+        }
+        //1.
+        let file = '[Content_Types].xml';
+        let data = writeContentTypes(sheets);
+        let zip = new JSZip();
+        zip.file(file, data.join(''), {compression: 'DEFLATE'});
+        //2.
+        let zip_rels = zip.folder('_rels');
+        file = '.rels';
+        data = writeRootRels();
+        zip_rels.file(file, data.join(''), {compression: 'DEFLATE'});
+        //3.
+        let zip_docProps = zip.folder('docProps');
+        file = 'app.xml';
+        data = writeApp(sheets);
+        zip_docProps.file(file, data.join(''), {compression: 'DEFLATE'});
+        file = 'core.xml';
+        data = writeCore();
+        zip_docProps.file(file, data.join(''), {compression: 'DEFLATE'});
+        //4.
+        let zip_xl = zip.folder('xl');
+        file = 'workbook.xml';
+        data = writeXlWorkBook(sheets);
+        zip_xl.file(file, data.join(''), {compression: 'DEFLATE'});
+        let zip_rels2 = zip_xl.folder('_rels');
+        file = 'workbook.xml.rels';
+        data = writeXlRels(sheets);
+        zip_rels2.file(file, data.join(''), {compression: 'DEFLATE'});
+        //5.
+        let zip_theme = zip_xl.folder('theme');
+        file = 'theme1.xml';
+        data = writeTheme();
+        zip_theme.file(file, data, {compression: 'DEFLATE'});
+        //6.
+        let zip_worksheets = zip_xl.folder('worksheets');
+        let sharedStrList = [], stylesObj = {};
+        data = writeSheets(pageData, sharedStrList, stylesObj);
+        for (let i = 0; i < data.length; i++) {
+            file = 'sheet' + (i + 1) + '.xml';
+            zip_worksheets.file(file, data[i].join(''), {compression: 'DEFLATE'});
+        }
+        file = 'sharedStrings.xml';
+        data = writeSharedString(sharedStrList);
+        zip_xl.file(file, data.join(''), {compression: 'DEFLATE'});
+        file = 'styles.xml';
+        data = writeStyles(stylesObj);
+        zip_xl.file(file, data.join(''), {compression: 'DEFLATE'});
+
+        if (fName) {
+            let newName = '' + (new Date()).valueOf();
+            zip.generateNodeStream({type:'nodebuffer',streamFiles:true})
+                .pipe(fs.createWriteStream(__dirname.slice(0, __dirname.length - 21) + '/tmp/' + newName + '.xlsx'))
+                .on('finish', function () {
+                    // JSZip generates a readable stream with a "end" event,
+                    // but is piped here in a writable stream which emits a "finish" event.
+                    console.log(newName + ".xlsx was written.");
+                    if (callback) callback(newName);
+                }
+            );
+        } else {
+            //return zip.generateNodeStream({type:'nodebuffer',streamFiles:true});
+            return zip;
+        }
+    }
+}

+ 27 - 0
modules/reports/util/rpt_util.js

@@ -0,0 +1,27 @@
+/**
+ * Created by Tony on 2017/3/24.
+ */
+let cache = require('../../../public/cache/cacheUtil');
+let rpt_cfg = require('../models/rpt_cfg');
+
+const RPT_CFG_GRP = 'rpt_cfg';
+
+module.exports = {
+    setReportDefaultCache: function () {
+        rpt_cfg.getByUserId("Administrator", function (err, cfgs) {
+            if (cfgs) {
+                cache.setCache(RPT_CFG_GRP,'admin_cfg',cfgs);
+            }
+        })
+    },
+    getReportDefaultCache: function () {
+        let rst = {ctrls: null, fonts: null, styles: null},
+            admin_cfg = cache.getCache(RPT_CFG_GRP,'admin_cfg');
+            ;
+        rst.ctrls = admin_cfg.formats;
+        rst.fonts = admin_cfg.fonts;
+        rst.styles = admin_cfg.borders;
+        admin_cfg = null;
+        return rst;
+    }
+}

+ 4 - 0
server.js

@@ -138,6 +138,10 @@ app.get('/stdItems', function(req, res){
 });
 let billsLibRouter = require("./modules/bills_lib/routes/bills_lib_routes");
 app.use("/stdBillsEditor", billsLibRouter);
+
+let rationRouter = require("./modules/ration_repository/routes/ration_fe_routes");
+app.use("/rationRepository/api", rationRouter);
+
 //-----------------
 
 app.use(function(req, res, next) {