Browse Source

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

TonyKang 3 years ago
parent
commit
d487887114
41 changed files with 2152 additions and 214 deletions
  1. 16 0
      config/config.js
  2. 16 0
      modules/all_models/bill_class.js
  3. 8 2
      modules/all_models/std_billsGuidance_items.js
  4. 21 0
      modules/all_models/std_billsGuidance_material.js
  5. 44 0
      modules/all_models/std_price_info_items.js
  6. 4 0
      modules/all_models/user.js
  7. 8 0
      modules/bills_lib/controllers/bills_lib_controllers.js
  8. 6 5
      modules/common/base/base_controller.js
  9. 4 2
      modules/price_info_lib/controllers/index.js
  10. 87 0
      modules/price_info_lib/facade/index.js
  11. 9 0
      modules/ration_repository/controllers/search_controller.js
  12. 9 4
      modules/ration_repository/models/ration_item.js
  13. 1 0
      modules/ration_repository/routes/ration_rep_routes.js
  14. 73 2
      modules/std_billsGuidance_lib/controllers/libController.js
  15. 5 2
      modules/std_billsGuidance_lib/controllers/viewController.js
  16. 224 3
      modules/std_billsGuidance_lib/facade/facades.js
  17. 4 0
      modules/std_billsGuidance_lib/routes/routes.js
  18. 1 1
      modules/users/controllers/cld_controller.js
  19. 5 1
      modules/users/controllers/dashboard_controller.js
  20. 13 3
      modules/users/controllers/login_controller.js
  21. 1 1
      modules/users/facade/online_facade.js
  22. 42 0
      modules/users/models/manager_model.js
  23. 101 83
      package-lock.json
  24. 12 0
      public/id_tree.js
  25. 4 2
      public/web/common_ajax.js
  26. 24 0
      public/web/id_tree.js
  27. 14 0
      public/web/sheet/sheet_common.js
  28. 40 3
      public/web/tree_sheet/tree_sheet_helper.js
  29. 70 13
      web/maintain/billsGuidance_lib/html/main.html
  30. 117 4
      web/maintain/billsGuidance_lib/html/zhiyin.html
  31. 1026 53
      web/maintain/billsGuidance_lib/js/billsGuidance.js
  32. 4 3
      web/maintain/billsGuidance_lib/js/global.js
  33. 41 2
      web/maintain/billsGuidance_lib/js/main.js
  34. 8 0
      web/maintain/price_info_lib/css/index.css
  35. 2 1
      web/maintain/price_info_lib/html/edit.html
  36. 7 1
      web/maintain/price_info_lib/html/main.html
  37. 41 0
      web/maintain/price_info_lib/js/index.js
  38. 5 1
      web/maintain/price_info_lib/js/main.js
  39. 11 11
      web/maintain/ration_repository/js/ration_coe.js
  40. 18 9
      web/over_write/crawler/guangdong_2018_price_crawler.js
  41. 6 2
      web/users/js/login.js

+ 16 - 0
config/config.js

@@ -89,6 +89,22 @@ module.exports = {
             useMongoClient: true
         }
     },
+    uat_wc: {
+        title:"大司空V2.0",
+        startPort:1002,
+        server: "172.18.111.231",//数据库ID
+        port: "27017",//数据库端口
+        dbName:'stdBuilding',
+        options:{
+            user:'wisecost',
+            pass:'Smartcost3850888',
+            auth: {
+                "authSource": "admin"
+            },
+            connectTimeoutMS: 60000,
+            useMongoClient: true
+        }
+    },
     setupDb:function (env="local") {
         let me = this;
         me.current.server = me[env].server;

+ 16 - 0
modules/all_models/bill_class.js

@@ -0,0 +1,16 @@
+// 清单分类
+const mongoose = require('mongoose');
+const Schema = mongoose.Schema;
+
+
+const billClass = new Schema({
+    compilationID: String,
+    name: String,
+    code: String,
+    itemCharacter: String,
+    class: { type: Number, index: true }, // 分类
+    classCode: { type: String, index: true }, // 类别别名
+}, {versionKey: false});
+
+
+mongoose.model('billClass', billClass, 'billClass');

+ 8 - 2
modules/all_models/std_billsGuidance_items.js

@@ -13,7 +13,7 @@ const Schema = mongoose.Schema;
 
 const stdBillsGuidanceItems = new Schema({
     libID: String,
-    ID: String, //uuid
+    ID: { type: String, index: true }, //uuid
     ParentID: String,
     NextSiblingID: String,
     billsID: String, //关联清单的ID
@@ -21,7 +21,13 @@ const stdBillsGuidanceItems = new Schema({
     comment: String, //备注
     type: Number, //0:工作内容 1:定额
     rationID: {type: Number, default: null}, //定额类型时
-    deleted: {type: Boolean, default: false}
+    deleted: {type: Boolean, default: false},
+    outputItemCharacter: {type: Boolean, default: false},
+    required: {type: Boolean, default: false},
+    unit: String, // 单位,辅助运距功能
+    interval: String, // 区间,辅助运距功能
+    isMaterial: {type: Boolean, default: false}, // 材料,辅助替换材料规格
+    isDefaultOption: {type: Boolean, default: false}, // 是否是默认选项
 }, {versionKey: false});
 
 mongoose.model('std_billsGuidance_items', stdBillsGuidanceItems, 'std_billsGuidance_items');

+ 21 - 0
modules/all_models/std_billsGuidance_material.js

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

+ 44 - 0
modules/all_models/std_price_info_items.js

@@ -2,6 +2,30 @@
 const mongoose = require('mongoose');
 
 const Schema = mongoose.Schema;
+
+const keywordSchema = new Schema({
+    keyword: {
+        type: String,
+        default: ''
+    }, // 关键字
+    coe: {
+        type: String,
+        default: ''
+    }, // 系数(关键字效果)
+    unit: {
+        type: String,
+        default: ''
+    }, // 单位
+    group: {
+        type: String,
+        default: ''
+    }, // 组别
+    optionCode: {
+        type: String,
+        default: ''
+    }, // 选项号
+}, { _id: false });
+
 const priceInfoItems = new Schema({
     ID: String,
     libID: String,
@@ -37,6 +61,26 @@ const priceInfoItems = new Schema({
     remark: {
         type: String,
         default: ''
+    },
+    // 别名编码
+    classCode: {
+        type: String,
+        default: ''
+    },
+    // 计算式
+    expString: {
+        type: String,
+        default: ''
+    },
+    // 月份、价格备注
+    dateRemark: {
+        type: String,
+        default: ''
+    },
+    // 关键字
+    keywordList: {
+        type: [keywordSchema],
+        default: []
     }
 }, { versionKey: false });
 mongoose.model('std_price_info_items', priceInfoItems, 'std_price_info_items');

+ 4 - 0
modules/all_models/user.js

@@ -34,6 +34,10 @@ let upgrade = mongoose.Schema({
         type:String,
         default: '',
     },
+    lock: { // 锁信息 1:借出;2:销售;
+        type: Number,
+        default: 0
+    },
 }, { _id: false })
 
 const userdList = mongoose.Schema({

+ 8 - 0
modules/bills_lib/controllers/bills_lib_controllers.js

@@ -117,6 +117,14 @@ module.exports = {
         });
     },
     updateBills: function(req, res){
+        const zhLibID = 'cf851660-3534-11ec-9641-2da8021b8e4e';
+        if (req.session.managerData.isTemporary) {
+            const match = req.headers.referer.match(/libID=([\d,a-z,A-Z,-]{36})/);
+            if (match && match[1] && match[1] !== zhLibID) {
+                callback(req, res, '无此清单精灵库权限', '无此清单精灵库权限', null);
+                return;
+            }
+        }
         let data = JSON.parse(req.body.data);
         billsLibDao.updateBills(data, function(err, message){
             callback(req, res, err, message, null);

+ 6 - 5
modules/common/base/base_controller.js

@@ -42,6 +42,7 @@ class BaseController {
      * @return {void}
      */
     init(request, response, next) {
+        const referer = request.headers.referer;
         // 获取当前控制器和动作名称
         let urlInfo = Url.parse(request.originalUrl, true);
         let url = urlInfo.pathname.substr(1);
@@ -61,10 +62,9 @@ class BaseController {
                 break;
         }
 
+        let sessionManager = request.session.managerData;
         try {
-            console.log('enterINit');
             // 如果不适超级管理员则判断权限
-            let sessionManager = request.session.managerData;
             let MenuPermission = sessionManager.menuData;
             if (sessionManager.superAdmin !== 1) {
                 let currentPermission = sessionManager.toolPermission;
@@ -126,10 +126,11 @@ class BaseController {
             // moment工具
             response.locals.moment = Moment;
         } catch (error) {
-            console.log('enterAURE');
             console.log(error);
-            response.redirect('/dashboard');
-            return;
+            if (!(sessionManager.isTemporary && /\/billsGuidance\/guidance\//.test(referer))) {
+                response.redirect('/dashboard');
+                return;
+            }
         }
 
         next();

+ 4 - 2
modules/price_info_lib/controllers/index.js

@@ -132,7 +132,9 @@ class PriceInfoController extends BaseController {
             try {
                 const libID = fields.libID !== undefined && fields.libID.length > 0 ?
                     fields.libID[0] : null;
-                if (!libID) {
+                const importType = fields.importType !== undefined && fields.importType.length > 0 ?
+                    fields.importType[0] : null;
+                if (!libID || !importType) {
                     throw '参数错误。';
                 }
                 const file = files.file !== undefined ? files.file[0] : null;
@@ -152,7 +154,7 @@ class PriceInfoController extends BaseController {
                     throw 'excel没有对应数据。';
                 }
                 // 提取excel数据并入库
-                await facade.importExcelData(libID, sheet[0].data);
+                importType === 'originalData' ? await facade.importExcelData(libID, sheet[0].data) : await facade.importKeyData(libID, sheet[0].data, sheet[1].data);
                 // 删除文件
                 if (uploadFullName && fs.existsSync(uploadFullName)) {
                     fs.unlink(uploadFullName);

+ 87 - 0
modules/price_info_lib/facade/index.js

@@ -231,6 +231,92 @@ async function importExcelData(libID, sheetData) {
         throw 'excel没有有效数据。'
     }
 }
+
+// 导入excel关键字数据(主表+副表),目前只针对珠海,根据列号导入
+/* 
+主表:主从对应码	别名编码	材料名称	规格	单位	含税价(元)	除税价(元)	月份备注	计算式
+副表:主从对应码	关键字	单位	关键字效果	组别	选项号
+ */
+async function importKeyData(libID, mainData, subData) {
+    const lib = await priceInfoLibModel.findOne({ ID: libID }).lean();
+    if (!lib) {
+        throw new Error('库不存在');
+    }
+    const zh = await priceInfoAreaModel.findOne({ name: { $regex: '珠海' } }).lean();
+    if (!zh) {
+        throw new Error('该库不存在珠海地区');
+    }
+    // 删除珠海地区所有材料
+    await priceInfoItemModel.deleteMany({ libID, areaID: zh.ID });
+
+    const classItems = await priceInfoClassModel.find({ libID, areaID: zh.ID }).lean();
+    // 分类树前四位编码 - 分类节点ID映射表
+    let otherClassID = '';
+    const classMap = {};
+    classItems.forEach(item => {
+        if (item.name) {
+            if (!otherClassID && /其他/.test(item.name)) {
+                otherClassID = item.ID;
+            }
+            const code = item.name.substr(0, 4);
+            if (/\d{4}/.test(code)) {
+                classMap[code] = item.ID;
+            }
+        }
+    });
+
+    // 主从对应码 - 关键字数组映射
+    const keywordMap = {};
+    for (let row = 1; row < subData.length; row++) {
+        const rowData = subData[row];
+        const keywordItem = {
+            code: rowData[0] ? String(rowData[0]) : '',
+            keyword: rowData[1] || '',
+            unit: rowData[2] || '',
+            coe: rowData[3] || '',
+            group: rowData[4] || '',
+            optionCode: rowData[5] || '',
+        };
+        if (!keywordItem.code) {
+            continue;
+        }
+        (keywordMap[keywordItem.code] || (keywordMap[keywordItem.code] = [])).push(keywordItem);
+    }
+
+    const priceItems = [];
+    for (let row = 1; row < mainData.length; row++) {
+        const rowData = mainData[row];
+        const code = rowData[0] ? String(rowData[0]) : '';
+        if (!code) {
+            continue;
+        }
+        const matchCode = code.substring(0, 4);
+        const classID = classMap[matchCode] || otherClassID;
+        const priceItem = {
+            code,
+            libID,
+            classID,
+            ID: uuidV1(),
+            compilationID: lib.compilationID,
+            areaID: zh.ID,
+            period: lib.period,
+            classCode: rowData[1] || '',
+            name: rowData[2] || '',
+            specs: rowData[3] || '',
+            unit: rowData[4] || '',
+            taxPrice: rowData[5] || '',
+            noTaxPrice: rowData[6] || '',
+            dateRemark: rowData[7] || '',
+            expString: rowData[8] ||  '',
+            keywordList: keywordMap[code] || [],
+        }
+        priceItems.push(priceItem);
+    }
+    if (priceItems.length) {
+        await priceInfoItemModel.insertMany(priceItems);
+    }
+}
+
 /* async function importExcelData(libID, sheetData) {
     const libs = await getLibs({ ID: libID });
     const compilationID = libs[0].compilationID;
@@ -454,6 +540,7 @@ module.exports = {
     processChecking,
     crawlDataByCompilation,
     importExcelData,
+    importKeyData,
     getAreas,
     updateAres,
     insertAreas,

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

@@ -18,6 +18,15 @@ class SearchController extends BaseController{
             callback(req, res, 1, err, null);
         }
     }
+    async getRationByID (req, res) {
+        try {
+            let data = JSON.parse(req.body.data);
+            let ration = await rationItem.getRationByID(data.ID);
+            callback(req, res, 0, '', ration);
+        } catch (err) {
+            callback(req, res, 1, err, null);
+        }
+    }
     findRation (req, res) {
         var rId = req.body.rationLibId, keyword = req.body.keyword;
         rationItem.findRation(rId, keyword, function (err, message, rst) {

+ 9 - 4
modules/ration_repository/models/ration_item.js

@@ -298,7 +298,7 @@ rationItemDAO.prototype.getRationItemsByLib = async function (rationRepId, showH
         return [];
     }
     let startDate = new Date();
-    let rations = await rationItemModel.find({rationRepId: rationRepId}, returnFields);
+    let rations = await rationItemModel.find({rationRepId: rationRepId}, returnFields).lean();
     console.log(`Date: ${new Date() - startDate}====================================`);
     if(!showHint){
         return rations;
@@ -343,7 +343,7 @@ rationItemDAO.prototype.getRationItemsByLib = async function (rationRepId, showH
                 hintsArr.push(`附注:`);
                 hintsArr = hintsArr.concat(ration.annotation.split('\n'));
             }
-            ration._doc.hint = hintsArr.join('<br>');
+            ration.hint = hintsArr.join('<br>');
         }
         return rations;
     }
@@ -505,6 +505,11 @@ rationItemDAO.prototype.getRationItem = async function (repId, code) {
     return ration;
 };
 
+rationItemDAO.prototype.getRationByID = async function (ID) {
+    let ration = await rationItemModel.findOne({ ID: +ID }).lean();
+    return ration;
+};
+
 rationItemDAO.prototype.addRationItems = function(rationLibId, lastOpr, sectionId, items,callback){
     if (items && items.length > 0) {
         counter.counterDAO.getIDAfterCount(counter.moduleName.rations, items.length, function(err, result){
@@ -1106,9 +1111,9 @@ rationItemDAO.prototype.batchAddFromExcel = async function(rationRepId, data) {
             continue;
         }
         // 如果第一个字段为null则是工料机数据,放入上一个数据的工料机字段
-        if (tmp[0] === undefined && Object.keys(lastData).length > 0) {
+        if (!tmp[0] && Object.keys(lastData).length > 0) {
             // 如果不存在对应的工料机库数据则跳过
-            if (stdGLJList[tmp[1]] === undefined) {
+            if (!stdGLJList[tmp[1]]) {
                 if (lastFailCount === 0) {
                     failGLJList.push('定额' + lastData.code + '下的');
                     lastFailCount++;

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

@@ -87,6 +87,7 @@ module.exports =  function (app) {
     apiRouter.post('/updateSection', installationController.auth, installationController.init, installationController.updateSection);
     apiRouter.post('/batchUpdateInst', installationController.auth, installationController.init, installationController.batchUpdateInst);
 
+    apiRouter.post('/getRationByID', searchController.auth, searchController.init, searchController.getRationByID);
     apiRouter.post('/getRationItem',searchController.auth, searchController.init, searchController.getRationItem);
     apiRouter.post('/findRation', searchController.auth, searchController.init, searchController.findRation);
 

+ 73 - 2
modules/std_billsGuidance_lib/controllers/libController.js

@@ -11,11 +11,17 @@ import BaseController from '../../common/base/base_controller';
 import moment from 'moment';
 const billsGuidanceFacade = require('../facade/facades');
 let logger = require('../../../logs/log_helper').logger;
+const fs = require("fs");
+// excel解析
+const excel = require("node-xlsx");
 let callback = function (req, res, err, msg, data) {
     res.json({error: err, message: msg, data: data});
 };
 
+const zhLibID = 'cf851660-3534-11ec-9641-2da8021b8e4e';
+
 class BillsGuideLibController extends BaseController{
+
     //获取编办及编办清单库信息
     async getComBillsLibInfo(req, res){
         try{
@@ -29,7 +35,7 @@ class BillsGuideLibController extends BaseController{
 
     async getBillsGuideLibs(req, res){
         try{
-            let libs = await billsGuidanceFacade.getBillsGuideLibs({deleted: false});
+            let libs = await billsGuidanceFacade.getBillsGuideLibs({deleted: false}, req.session.managerData.isTemporary);
             callback(req, res, 0, '', libs);
         }
         catch(err){
@@ -82,9 +88,17 @@ class BillsGuideLibController extends BaseController{
 
     async updateItems(req, res){
         try{
+
+            if (req.session.managerData.isTemporary) {
+                const match = req.headers.referer.match(/libID=([\d,a-z,A-Z,-]{36})/);
+                if (match && match[1] && match[1] !== zhLibID) {
+                    throw '无此清单精灵库权限';
+                }
+            }
+
             let data = JSON.parse(req.body.data);
             let updateDatas = data.updateDatas;
-            await billsGuidanceFacade.updateItems(updateDatas);
+            await billsGuidanceFacade.updateItems(updateDatas, req.session.managerData.isTemporary);
             callback(req, res, 0, '', null);
         }
         catch(err){
@@ -92,6 +106,63 @@ class BillsGuideLibController extends BaseController{
         }
     }
 
+    async getBillMaterials(req, res){
+        try{
+            const data = JSON.parse(req.body.data);
+            const materials = await billsGuidanceFacade.getBillMaterials(data.libID, data.billID);
+            callback(req, res, 0, '', materials);
+        }
+        catch(err){
+            callback(req, res, 1, err, null);
+        }
+    }
+
+    async editBillMaterials(req, res){
+        try{
+            const data = JSON.parse(req.body.data);
+            const materials = await billsGuidanceFacade.editBillMaterials(data.libID, data.billID, data.gljCodes, data.compilationID);
+            callback(req, res, 0, '', materials);
+        }
+        catch(err){
+            console.log(err);
+            callback(req, res, 1, err.message, []);
+        }
+    }
+    
+    async generateClassData(req, res) {
+        try{
+            const data = JSON.parse(req.body.data);
+            await billsGuidanceFacade.generateClassData(data.libID);
+            callback(req, res, 0, '', []);
+        }
+        catch(err){
+            console.log(err);
+            callback(req, res, 1, err.message, []);
+        }
+    }
+
+    async exportClassExcel(req, res) {
+        try{
+            const excelData = await billsGuidanceFacade.getClassExcelData(req.query.libID);
+            const buffer = excel.build([{name: "清单分类库", data: excelData}], {'!cols': [{wch:6}, {wch:12},{wch:14},{wch:24}, {wch:45}]});
+            const filePath = './public/export.xlsx';
+            fs.writeFileSync(filePath, buffer, 'binary');
+            const stats = fs.statSync(filePath);
+            // 下载相关header
+            res.set({
+                'Content-Type': 'application/octet-stream',
+                'Content-Disposition': 'attachment; filename=billClass.xlsx',
+                'Content-Length': stats.size
+            });
+            fs.createReadStream(filePath).pipe(res);
+            fs.unlink(filePath);
+        }
+        catch(err){
+            console.log(err);
+            response.end(error);
+        }
+    }
+
     async testItems(req, res){
         try{
             let data = JSON.parse(req.body.data);

+ 5 - 2
modules/std_billsGuidance_lib/controllers/viewController.js

@@ -13,14 +13,17 @@ class ViewsController extends BaseController{
     redirectMain(req, res){
         res.render('maintain/billsGuidance_lib/html/main.html',
             {
-                userAccount: req.session.managerData.username
+                userAccount: req.session.managerData.username,
+                manager: req.session.managerData,
             });
     }
     redirectGuidance(req, res){
+        let sessionManager = req.session.managerData;
         res.render('maintain/billsGuidance_lib/html/zhiyin.html',
             {
                 userAccount: req.session.managerData.username,
-                LicenseKey:config.getLicenseKey(process.env.NODE_ENV)
+                LicenseKey:config.getLicenseKey(process.env.NODE_ENV),
+                manager: req.session.managerData,
             });
     }
 }

+ 224 - 3
modules/std_billsGuidance_lib/facade/facades.js

@@ -20,8 +20,14 @@ const stdBillsJobsModel = mongoose.model('std_bills_lib_jobContent');
 const stdRationModel = mongoose.model('std_ration_lib_ration_items');
 const engLibModel = mongoose.model('engineering_lib');
 const compilationModel = mongoose.model('compilation');
+const billMaterialModel = mongoose.model('std_billsGuidance_materials');
+const gljModel = mongoose.model('std_glj_lib_gljList');
+const gljLibModel = mongoose.model('std_glj_lib_map');
+const billClassModel = mongoose.model('billClass');
+const idTree = require('../../../public/id_tree').getTree();
 const _ = require('lodash');
-
+const zhLibID = 'cf851660-3534-11ec-9641-2da8021b8e4e';
+const cqLibID = '90c51220-a740-11e8-a354-ab5db7d42428';
 module.exports = {
     handleCopyItems,
     getComBillsLibInfo,
@@ -31,6 +37,10 @@ module.exports = {
     getLibWithBills,
     getItemsBybills,
     updateItems,
+    getBillMaterials,
+    editBillMaterials,
+    generateClassData,
+    getClassExcelData,
     testItems
 };
 
@@ -174,7 +184,11 @@ async function getComBillsLibInfo() {
     }
 }
 
-async function getBillsGuideLibs(findData) {
+async function getBillsGuideLibs(findData, isTemporary) {
+    if (isTemporary) {
+        const libs = await billsGuideLibModel.find({ ID: { $in: [zhLibID, cqLibID] } }).lean();
+        return libs;
+    }
     return await billsGuideLibModel.find(findData);
 }
 
@@ -274,7 +288,18 @@ async function getLibWithBills(libID){
     if(!billsLib){
         throw '引用的清单规则库不存在!';
     }
-    let bills = await stdBillsModel.find({billsLibId: billsLib.billsLibId}, '-_id code name ID NextSiblingID ParentID jobs items comment');
+    let bills = await stdBillsModel.find({billsLibId: billsLib.billsLibId}, '-_id code name ID NextSiblingID ParentID jobs items comment').lean();
+    const guideItems = await billsGuideItemsModel.find({ libID: guidanceLib[0].ID }, '-_id billsID').lean();
+    const billsMap = {};
+    for (const item of guideItems) {
+        billsMap[item.billsID] = true;
+    }
+    for (const item of bills) {
+        if (billsMap[item.ID]) {
+            item.hasGuide = true;
+        }
+    }
+    
     return {guidanceLib: guidanceLib[0], bills};
 }
 
@@ -388,6 +413,202 @@ async function updateItems(updateDatas) {
     }
 }
 
+// 获取清单材料数据
+async function getBillMaterials(libID, billID) {
+    // 指引下已有定额人材机
+    let allGljList = [];
+    const rationItems = await billsGuideItemsModel.find({ libID, billsID: billID, rationID: { $ne: null } }, '-_id rationID').lean();
+    if (rationItems.length) {
+        const rationIDs = rationItems.map(item => item.rationID);
+        const rations = await stdRationModel.find({ ID: { $in: rationIDs } }, '-_id rationGljList');
+        const gljIDs = [];
+        rations.forEach(ration => {
+            if (ration.rationGljList && ration.rationGljList.length) {
+                gljIDs.push(...ration.rationGljList.map(rGlj => rGlj.gljId));
+            }
+        });
+        if (gljIDs.length) {
+            allGljList = await gljModel.find({ ID: { $in: [...new Set(gljIDs)] } }, '-_id ID code name specs').lean();
+        }
+    }
+    // 清单材料
+    let billMaterials = [];
+    const billMaterial = await billMaterialModel.findOne({ libID, billID }).lean();
+    if (!billMaterial || !billMaterial.materials) {
+        return {
+            billMaterials, 
+            allGljList
+        };
+    }
+    const gljIDs = billMaterial.materials.map(m => m.gljID);
+    const gljList = await gljModel.find({ ID: { $in: gljIDs } }, '-_id ID code name specs').lean();
+    billMaterials = gljList.map(glj => ({ gljID: glj.ID, code: glj.code, name: glj.name, specs: glj.specs }));
+    return {
+        billMaterials, 
+        allGljList
+    }
+}
+
+// 编辑清单材料数据,返回清单材料数据
+async function editBillMaterials(libID, billID, gljCodes, compilationID) {
+    const gljLib = await gljLibModel.findOne({ compilationId: compilationID }, '-_id ID').lean();
+    const gljList = await gljModel.find({ repositoryId: gljLib.ID, code: { $in: gljCodes }, }, '-_id ID code name specs').lean();
+    const gljMap = {};
+    const materials = [];
+    gljList.forEach(glj => {
+        materials.push({ gljID: glj.ID });
+        gljMap[glj.code] = 1;
+    });
+    const missCodes = gljCodes.filter(code => !gljMap[code]);
+    if (missCodes.length) {
+        throw new Error(`没有找到人材机:<br/>${missCodes.join('<br/>')}`);
+    }
+    const billMaterial = await billMaterialModel.findOne({ libID, billID }, '-_id ID').lean();
+    if (billMaterial) {
+        await billMaterialModel.update({ ID: billMaterial.ID }, { $set: { materials } });
+    } else {
+        await billMaterialModel.create({ libID, billID, ID: uuidV1(), materials });
+    }
+    return gljList.map(glj => ({ gljID: glj.ID, code: glj.code, name: glj.name, specs: glj.specs }));
+}
+
+ // 是否为工序行
+ function isProcessNode(node) {
+    return node && node.depth() % 2 === 0 && node.data.type === 0
+}
+
+// 是否是选项行
+function isOptionNode(node) {
+    return node && node.depth() % 2 === 1 && node.data.type === 0
+}
+
+// 从指引节点,获取分类特征数据
+function getItemCharacterData(nodes, prefix) {
+    const processNodes = nodes.filter(node => isProcessNode(node));
+    const classGroups = []; // 同层必填选项的数组(二维数组)
+    processNodes.forEach(processNode => {
+        const classItems = [];
+        const optionNodes = processNode.children.filter(node => isOptionNode(node));
+        optionNodes.forEach(optionNode => {
+            // const name = prefix ? `${prefix}@${optionNode.data.name}` : optionNode.data.name;
+            if (optionNode.parent && optionNode.parent.data.required && (!optionNode.children 
+                || !optionNode.children.length 
+                || (optionNode.children[0].data && optionNode.children[0].data.rationID) 
+                || !optionNode.children.some(node => isProcessNode(node)))) {
+                classItems.push(optionNode.data.name);
+            } else {
+                classItems.push(...getItemCharacterData(optionNode.children, optionNode.parent && optionNode.parent.data.required ? optionNode.data.name : ''));
+            }
+        });
+        if (classItems.length) {
+            classGroups.push(classItems);
+        }
+    });
+    // 拼接上一文本
+    if (classGroups[0] && classGroups[0].length) {
+        classGroups[0] = classGroups[0].map(name => prefix ? `${prefix}@${name}` : name);
+    }
+    // 二维数组内容排列组合
+    while (classGroups.length > 1) {
+        const prevClassItems = classGroups[0];
+        const nextClassItems = classGroups[1];
+        const mergedClassItems = [];
+        for (let i = 0; i < prevClassItems.length; i++) {
+            for (let j = 0; j < nextClassItems.length; j++) {
+                mergedClassItems.push(`${prevClassItems[i]}@${nextClassItems[j]}`);
+            }
+        }
+        classGroups.splice(0, 2, mergedClassItems);
+    }
+    return classGroups[0] ? [...new Set(classGroups[0])] : []; // 去重
+}
+
+// 生成清单分类
+async function generateClassData(libID) {
+    const lib = await billsGuideLibModel.findOne({ ID: libID }).lean();
+    if (!lib) {
+        throw new Error('无有效精灵库');
+    }
+    const guidanceItems = await billsGuideItemsModel.find({ libID }, '-_id').lean();
+    // 清单ID - 指引数据映射
+    const guidanceMap = {};
+    guidanceItems.forEach(item => {
+        (guidanceMap[item.billsID] || (guidanceMap[item.billsID] = [])).push(item);
+    });
+    const bills = await stdBillsModel.find({ billsLibId: lib.billsLibId }, '-_id ID ParentID NextSiblingID name code').lean();
+    const billTree = idTree.createNew({id: 'ID', pid: 'ParentID', nid: 'NextSiblingID', rootId: -1, autoUpdate: true});
+    billTree.loadDatas(bills);
+    // 叶子清单
+    const leaves = billTree.items.filter(node => !node.children || !node.children.length);
+    // 获取分类数据
+    let classNum = 1;
+    const billClassData = [];
+    leaves.forEach(billNode => {
+        const guidanceItems = guidanceMap[billNode.data.ID];
+        if (!guidanceItems || !guidanceItems.length) {
+            return;
+        }
+        const guidanceTree = idTree.createNew({id: 'ID', pid: 'ParentID', nid: 'NextSiblingID', rootId: -1, autoUpdate: true});
+        guidanceTree.loadDatas(guidanceItems);
+        /* if (!guidanceTree.check(guidanceTree.roots)) {
+            console.log('==================================清单下精灵树结构问题=========================================');
+            console.log('==================================清单下精灵树结构问题=========================================');
+            console.log('==================================清单下精灵树结构问题=========================================');
+            console.log('==================================清单下精灵树结构问题=========================================');
+            console.log('==================================清单下精灵树结构问题=========================================');
+            console.log(billNode.data);
+            console.log('==================================清单下精灵树结构问题=========================================');
+            console.log('==================================清单下精灵树结构问题=========================================');
+            console.log('==================================清单下精灵树结构问题=========================================');
+            console.log('==================================清单下精灵树结构问题=========================================');
+            console.log('==================================清单下精灵树结构问题=========================================');
+        } */
+        const itemCharacterData = getItemCharacterData(guidanceTree.roots);
+        itemCharacterData.forEach(itemCharacter => {
+            billClassData.push({
+                itemCharacter,
+                class: classNum++,
+                classCode: `${billNode.data.code}@${itemCharacter}`,
+                compilationID: lib.compilationId,
+                name: billNode.data.name,
+                code: billNode.data.code,
+            });
+        });
+    });
+    console.log(`billClassData.length`);
+    console.log(billClassData.length);
+    // 清空旧的分类数据
+    await billClassModel.deleteMany({ compilationID: lib.compilationId });
+    // 之前遇到一次性插入40w条数据的情况,会卡死,循环插入速度快很多
+    while (billClassData.length > 10000){
+        const list = billClassData.splice(0,10000);
+        await billClassModel.insertMany(list);
+    } 
+    await billClassModel.insertMany(billClassData);
+
+}
+
+// 获取分类excel数据
+async function getClassExcelData(libID) {
+    const lib = await billsGuideLibModel.findOne({ ID: libID }, '-_id compilationId').lean();
+    if (!lib) {
+        throw new Error('无有效精灵库');
+    }
+    const classData = await billClassModel.find({ compilationID: lib.compilationId }).lean();
+    const excelData = [['类别', '编码', '清单名称', '必填特征排列组合', '类别别名']];
+    classData.forEach(item => {
+        const excelItem = [
+            item.class,
+            item.code,
+            item.name,
+            item.itemCharacter,
+            item.classCode
+        ];
+        excelData.push(excelItem);
+    });
+    return excelData;
+}
+
 async function testItems(libID) {
     let items = await billsGuideItemsModel.find({libID: libID});
     //删除垃圾数据

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

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

+ 1 - 1
modules/users/controllers/cld_controller.js

@@ -278,7 +278,7 @@ class CLDController {
                 filter.regtimeMsg = userModel.getDayMsg(regtime);
             }
 
-            //获取注册时间
+            //获取登录时间
             let loginTime = request.query.loginTime;
             if(loginTime !== '' && loginTime !== undefined){
                 filter.loginMsg = userModel.getDayMsg(loginTime);

+ 5 - 1
modules/users/controllers/dashboard_controller.js

@@ -37,7 +37,11 @@ class DashboardController extends BaseController {
         // 获取已发布的通知
         let messageModel = new MessageModel();
         let messageList = await messageModel.getList({status: 1}, 1, 5, {release_time: -1});
-
+        let sessionManager = request.session.managerData;
+        if (sessionManager && sessionManager.isTemporary) {
+            return response.redirect('/billsGuidance/main');
+            
+        }
         let renderData = {
             parentTitle: DashboardController.parentTitle,
             parentIndex: DashboardController.parentIndex,

+ 13 - 3
modules/users/controllers/login_controller.js

@@ -33,7 +33,11 @@ class LoginController extends BaseController {
         };
         let managerSessionData = request.session.managerData;
         if (managerSessionData !== undefined) {
-            return response.redirect("/dashboard");
+            if (managerSessionData.isTemporary) {
+                return response.redirect("/billsGuidance/main");
+            } else {
+                return response.redirect("/dashboard");
+            }
         }
         response.render('users/views/login/index', renderData);
     }
@@ -53,11 +57,15 @@ class LoginController extends BaseController {
         let permissionGroupModel = new PermissionGroupModel();
 
         let responseData = {
+            isTemporary: false,
             error: 0,
             msg: ''
         };
         try {
             let managerData = await managerModel.validLogin(username, password);
+            if (managerData.isTemporary) {
+                responseData.isTemporary = true;
+            }
 
             // 成功后写入session
             let currentTime = new Date().getTime();
@@ -208,13 +216,15 @@ class LoginController extends BaseController {
                 loginTime: currentTime,
                 sessionToken: sessionToken,
                 userID: managerData.id,
-                toolPermission: toolPermissionController.join(','),
+                toolPermission: managerData.isTemporary ? 'billsGuidance' : toolPermissionController.join(','),
                 toolMenuData: toolMenuData,
                 toolAllPermission: toolAllPermission.join(','),
                 menuData: menuData,
-                superAdmin: managerData.super_admin
+                superAdmin: managerData.super_admin,
+                isTemporary: managerData.isTemporary,
             };
             request.session.managerData = managerSession;
+            console.log(managerSession);
 
             // 更新登录信息
             let ip = request.connection.remoteAddress;

+ 1 - 1
modules/users/facade/online_facade.js

@@ -35,7 +35,7 @@ async function getOnlineInfo(filter) {
 async function setOnlineTimes(userList,condition){
     for(let u of userList){
         let filter = {'userID':u._id.toString()};
-        if(u.latest_used) filter["compilationID"] = u.latest_used;
+        // if(u.latest_used) filter["compilationID"] = u.latest_used;
         if(condition.latest_login && condition.latest_login == ""){
             let startTime = condition.latest_login['$gte']; //- 24*60*60*1000 //往前推一天  {'$gte': startTime, '$lt': endTime}latest_login
             filter['dateTime'] = {'$gte': startTime, '$lt':  condition.latest_login['$lt']}

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

@@ -143,6 +143,44 @@ class ManagerModel extends BaseModel {
     }
 
     /**
+     * 财审平台需要临时登录,只能看某个清单精灵库
+     */
+    temporaryLogin(username, password) {
+        const users = [
+            { name: '中洲一', pwd: '123456' },
+            { name: '中洲二', pwd: '123456' },
+            { name: '中洲三', pwd: '123456' },
+            { name: '中洲四', pwd: '123456' },
+            { name: '中洲五', pwd: '123456' },
+            { name: '财审一', pwd: '123456' },
+            { name: '财审二', pwd: '123456' },
+            { name: '财审三', pwd: '123456' },
+            { name: '财审四', pwd: '123456' },
+            { name: '财审五', pwd: '123456' },
+            { name: '财审六', pwd: '123456' },
+            { name: '财审七', pwd: '123456' },
+            { name: '财审八', pwd: '123456' },
+            { name: '财审九', pwd: '123456' },
+            { name: '财审十', pwd: '123456' },
+        ];
+        const user = users.find(item => item.name === username && item.pwd === password);
+        if (!user) {
+            return null;
+        }
+        return {
+            can_login: 1,
+            create_time: Date.now(),
+            id: `tempUser${user.name}`,
+            isNew: false,
+            last_login: Date.now(),
+            login_info: '',
+            login_ip: '',
+            username: user.name,
+            isTemporary: true,
+        }
+    }
+
+    /**
      * 登录信息校验
      *
      * @param {String} username
@@ -150,6 +188,10 @@ class ManagerModel extends BaseModel {
      * @return {Promise}
      */
     async validLogin(username, password) {
+        const tempUser = this.temporaryLogin(username, password);
+        if (tempUser) {
+            return tempUser;
+        }
         let managerData = await this.findDataByCondition({username: username});
 
         // 没有找到对应数据

+ 101 - 83
package-lock.json

@@ -35,7 +35,7 @@
     },
     "addressparser": {
       "version": "1.0.1",
-      "resolved": "http://192.168.1.90:4873/addressparser/-/addressparser-1.0.1.tgz",
+      "resolved": "https://registry.npmjs.org/addressparser/-/addressparser-1.0.1.tgz",
       "integrity": "sha1-R6++GiqSYhkdtoOOT9HTm0CCF0Y=",
       "optional": true
     },
@@ -988,7 +988,7 @@
     },
     "bl": {
       "version": "1.1.2",
-      "resolved": "http://192.168.1.90:4873/bl/-/bl-1.1.2.tgz",
+      "resolved": "https://registry.npmjs.org/bl/-/bl-1.1.2.tgz",
       "integrity": "sha1-/cqHGplxOqANGeO7ukHER4emU5g=",
       "optional": true,
       "requires": {
@@ -997,13 +997,13 @@
       "dependencies": {
         "process-nextick-args": {
           "version": "1.0.7",
-          "resolved": "http://192.168.1.90:4873/process-nextick-args/-/process-nextick-args-1.0.7.tgz",
+          "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz",
           "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=",
           "optional": true
         },
         "readable-stream": {
           "version": "2.0.6",
-          "resolved": "http://192.168.1.90:4873/readable-stream/-/readable-stream-2.0.6.tgz",
+          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz",
           "integrity": "sha1-j5A0HmilPMySh4jaz80Rs265t44=",
           "optional": true,
           "requires": {
@@ -1017,7 +1017,7 @@
         },
         "string_decoder": {
           "version": "0.10.31",
-          "resolved": "http://192.168.1.90:4873/string_decoder/-/string_decoder-0.10.31.tgz",
+          "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
           "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=",
           "optional": true
         }
@@ -1092,7 +1092,7 @@
     },
     "boom": {
       "version": "2.10.1",
-      "resolved": "http://192.168.1.90:4873/boom/-/boom-2.10.1.tgz",
+      "resolved": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz",
       "integrity": "sha1-OciRjO/1eZ+D+UkqhI9iWt0Mdm8=",
       "optional": true,
       "requires": {
@@ -1194,7 +1194,7 @@
     },
     "buildmail": {
       "version": "4.0.1",
-      "resolved": "http://192.168.1.90:4873/buildmail/-/buildmail-4.0.1.tgz",
+      "resolved": "https://registry.npmjs.org/buildmail/-/buildmail-4.0.1.tgz",
       "integrity": "sha1-h393OLeHKYccmhBeO4N9K+EaenI=",
       "optional": true,
       "requires": {
@@ -1209,7 +1209,7 @@
       "dependencies": {
         "punycode": {
           "version": "1.4.1",
-          "resolved": "http://192.168.1.90:4873/punycode/-/punycode-1.4.1.tgz",
+          "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz",
           "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=",
           "optional": true
         }
@@ -1441,7 +1441,7 @@
     },
     "cryptiles": {
       "version": "2.0.5",
-      "resolved": "http://192.168.1.90:4873/cryptiles/-/cryptiles-2.0.5.tgz",
+      "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz",
       "integrity": "sha1-O9/s3GCBR8HGcgL6KR59ylnqo7g=",
       "optional": true,
       "requires": {
@@ -1475,12 +1475,12 @@
     "data-uri-to-buffer": {
       "version": "1.2.0",
       "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-1.2.0.tgz",
-      "integrity": "sha1-dxY+qcINhkG0cH6PGKvfmnjzSDU=",
+      "integrity": "sha512-vKQ9DTQPN1FLYiiEEOQ6IBGFqvjCa5rSK3cWMy/Nespm5d/x3dGFT9UBZnkLxCwua/IXBi2TYnwTEpsOvhC4UQ==",
       "optional": true
     },
     "date-format": {
       "version": "1.2.0",
-      "resolved": "http://192.168.1.90:4873/date-format/-/date-format-1.2.0.tgz",
+      "resolved": "https://registry.npmjs.org/date-format/-/date-format-1.2.0.tgz",
       "integrity": "sha1-YV6CjiM90aubua4JUODOzPpuytg="
     },
     "debug": {
@@ -1653,7 +1653,7 @@
     },
     "double-ended-queue": {
       "version": "2.1.0-0",
-      "resolved": "http://192.168.1.90:4873/double-ended-queue/-/double-ended-queue-2.1.0-0.tgz",
+      "resolved": "https://registry.npmjs.org/double-ended-queue/-/double-ended-queue-2.1.0-0.tgz",
       "integrity": "sha1-ED01J/0xUo9AGIEwyEHv3XgmTlw=",
       "optional": true
     },
@@ -1810,7 +1810,7 @@
     "exit-on-epipe": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/exit-on-epipe/-/exit-on-epipe-1.0.1.tgz",
-      "integrity": "sha1-C92S6H1ShdJn2qgXHQ6wYVlolpI="
+      "integrity": "sha512-h2z5mrROTxce56S+pnvAV890uu7ls7f1kEvVGJbw1OlFH3/mlJ5bkXu0KRyW94v37zzHPiUd55iLn3DA7TjWpw=="
     },
     "express": {
       "version": "4.17.1",
@@ -1978,7 +1978,7 @@
     "file-uri-to-path": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
-      "integrity": "sha1-VTp7hEb/b2hDWcRF8eN6BdrMM90=",
+      "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==",
       "optional": true
     },
     "finalhandler": {
@@ -2139,7 +2139,7 @@
     },
     "generate-object-property": {
       "version": "1.2.0",
-      "resolved": "http://192.168.1.90:4873/generate-object-property/-/generate-object-property-1.2.0.tgz",
+      "resolved": "https://registry.npmjs.org/generate-object-property/-/generate-object-property-1.2.0.tgz",
       "integrity": "sha1-nA4cQDCM6AT0eDYYuTf6iPmdUNA=",
       "optional": true,
       "requires": {
@@ -2180,15 +2180,6 @@
         "once": "^1.3.0"
       },
       "dependencies": {
-        "graceful-fs": {
-          "version": "3.0.12",
-          "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-3.0.12.tgz",
-          "integrity": "sha512-J55gaCS4iTTJfTXIxSVw3EMQckcqkpdRv3IR7gu6sq0+tbC363Zx6KH/SEwXASK9JRbhyZmVjJEVJIOxYsB3Qg==",
-          "dev": true,
-          "requires": {
-            "natives": "^1.1.3"
-          }
-        },
         "minimatch": {
           "version": "1.0.0",
           "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-1.0.0.tgz",
@@ -2204,12 +2195,16 @@
     "globals": {
       "version": "9.18.0",
       "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz",
-      "integrity": "sha1-qjiWs+abSH8X4x7SFD1pqOMMLYo="
+      "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ=="
     },
     "graceful-fs": {
-      "version": "4.2.3",
-      "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz",
-      "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ=="
+      "version": "3.0.12",
+      "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-3.0.12.tgz",
+      "integrity": "sha512-J55gaCS4iTTJfTXIxSVw3EMQckcqkpdRv3IR7gu6sq0+tbC363Zx6KH/SEwXASK9JRbhyZmVjJEVJIOxYsB3Qg==",
+      "dev": true,
+      "requires": {
+        "natives": "^1.1.3"
+      }
     },
     "har-schema": {
       "version": "2.0.0",
@@ -2249,7 +2244,7 @@
     },
     "hawk": {
       "version": "3.1.3",
-      "resolved": "http://192.168.1.90:4873/hawk/-/hawk-3.1.3.tgz",
+      "resolved": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz",
       "integrity": "sha1-B4REvXwWQLD+VA0sm3PVlnjo4cQ=",
       "optional": true,
       "requires": {
@@ -2261,7 +2256,7 @@
     },
     "hipchat-notifier": {
       "version": "1.1.0",
-      "resolved": "http://192.168.1.90:4873/hipchat-notifier/-/hipchat-notifier-1.1.0.tgz",
+      "resolved": "https://registry.npmjs.org/hipchat-notifier/-/hipchat-notifier-1.1.0.tgz",
       "integrity": "sha1-ttJJdVQ3wZEII2d5nTupoPI7Ix4=",
       "optional": true,
       "requires": {
@@ -2271,7 +2266,7 @@
     },
     "hoek": {
       "version": "2.16.3",
-      "resolved": "http://192.168.1.90:4873/hoek/-/hoek-2.16.3.tgz",
+      "resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz",
       "integrity": "sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0=",
       "optional": true
     },
@@ -2360,7 +2355,7 @@
     },
     "httpntlm": {
       "version": "1.6.1",
-      "resolved": "http://192.168.1.90:4873/httpntlm/-/httpntlm-1.6.1.tgz",
+      "resolved": "https://registry.npmjs.org/httpntlm/-/httpntlm-1.6.1.tgz",
       "integrity": "sha1-rQFScUOi6Hc8+uapb1hla7UqNLI=",
       "optional": true,
       "requires": {
@@ -2370,7 +2365,7 @@
     },
     "httpreq": {
       "version": "0.4.24",
-      "resolved": "http://192.168.1.90:4873/httpreq/-/httpreq-0.4.24.tgz",
+      "resolved": "https://registry.npmjs.org/httpreq/-/httpreq-0.4.24.tgz",
       "integrity": "sha1-QzX/2CzZaWaKOUZckprGHWOTYn8=",
       "optional": true
     },
@@ -2519,12 +2514,12 @@
       "resolved": "https://registry.npmjs.org/is-nan/-/is-nan-1.2.1.tgz",
       "integrity": "sha1-n69ltvttskt/XAYoR16nH5iEAeI=",
       "requires": {
-        "define-properties": "1.1.3"
+        "define-properties": "^1.1.1"
       }
     },
     "is-property": {
       "version": "1.0.2",
-      "resolved": "http://192.168.1.90:4873/is-property/-/is-property-1.0.2.tgz",
+      "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz",
       "integrity": "sha1-V/4cTkhHTt1lsJkR8msc1Ald2oQ=",
       "optional": true
     },
@@ -2538,7 +2533,7 @@
     },
     "is-stream": {
       "version": "1.1.0",
-      "resolved": "http://192.168.1.90:4873/is-stream/-/is-stream-1.1.0.tgz",
+      "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz",
       "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=",
       "optional": true
     },
@@ -2617,6 +2612,14 @@
       "integrity": "sha1-NzaitCi4e72gzIO1P6PWM6NcKug=",
       "requires": {
         "graceful-fs": "^4.1.6"
+      },
+      "dependencies": {
+        "graceful-fs": {
+          "version": "4.2.6",
+          "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz",
+          "integrity": "sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==",
+          "optional": true
+        }
       }
     },
     "jsonpointer": {
@@ -2664,13 +2667,13 @@
     },
     "libbase64": {
       "version": "0.1.0",
-      "resolved": "http://192.168.1.90:4873/libbase64/-/libbase64-0.1.0.tgz",
+      "resolved": "https://registry.npmjs.org/libbase64/-/libbase64-0.1.0.tgz",
       "integrity": "sha1-YjUag5VjrF/1vSbxL2Dpgwu3UeY=",
       "optional": true
     },
     "libmime": {
       "version": "3.0.0",
-      "resolved": "http://192.168.1.90:4873/libmime/-/libmime-3.0.0.tgz",
+      "resolved": "https://registry.npmjs.org/libmime/-/libmime-3.0.0.tgz",
       "integrity": "sha1-UaGp50SOy9Ms2lRCFnW7IbwJPaY=",
       "optional": true,
       "requires": {
@@ -2681,7 +2684,7 @@
       "dependencies": {
         "iconv-lite": {
           "version": "0.4.15",
-          "resolved": "http://192.168.1.90:4873/iconv-lite/-/iconv-lite-0.4.15.tgz",
+          "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.15.tgz",
           "integrity": "sha1-/iZaIYrGpXz+hUkn6dBMGYJe3es=",
           "optional": true
         }
@@ -2689,7 +2692,7 @@
     },
     "libqp": {
       "version": "1.1.0",
-      "resolved": "http://192.168.1.90:4873/libqp/-/libqp-1.1.0.tgz",
+      "resolved": "https://registry.npmjs.org/libqp/-/libqp-1.1.0.tgz",
       "integrity": "sha1-9ebgatdLeU+1tbZpiL9yjvHe2+g=",
       "optional": true
     },
@@ -2751,7 +2754,7 @@
       "dependencies": {
         "axios": {
           "version": "0.15.3",
-          "resolved": "http://192.168.1.90:4873/axios/-/axios-0.15.3.tgz",
+          "resolved": "https://registry.npmjs.org/axios/-/axios-0.15.3.tgz",
           "integrity": "sha1-LJ1jiy4ZGgjqHWzJiOrda6W9wFM=",
           "optional": true,
           "requires": {
@@ -2775,7 +2778,7 @@
         },
         "follow-redirects": {
           "version": "1.0.0",
-          "resolved": "http://192.168.1.90:4873/follow-redirects/-/follow-redirects-1.0.0.tgz",
+          "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.0.0.tgz",
           "integrity": "sha1-jjQpjL0uF28lTv/sdaHHjMhJ/Tc=",
           "optional": true,
           "requires": {
@@ -2797,7 +2800,7 @@
     },
     "loggly": {
       "version": "1.1.1",
-      "resolved": "http://192.168.1.90:4873/loggly/-/loggly-1.1.1.tgz",
+      "resolved": "https://registry.npmjs.org/loggly/-/loggly-1.1.1.tgz",
       "integrity": "sha1-Cg/B0/o6XsRP3HuJe+uipGlc6+4=",
       "optional": true,
       "requires": {
@@ -2808,25 +2811,25 @@
       "dependencies": {
         "assert-plus": {
           "version": "0.2.0",
-          "resolved": "http://192.168.1.90:4873/assert-plus/-/assert-plus-0.2.0.tgz",
+          "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.2.0.tgz",
           "integrity": "sha1-104bh+ev/A24qttwIfP+SBAasjQ=",
           "optional": true
         },
         "aws-sign2": {
           "version": "0.6.0",
-          "resolved": "http://192.168.1.90:4873/aws-sign2/-/aws-sign2-0.6.0.tgz",
+          "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.6.0.tgz",
           "integrity": "sha1-FDQt0428yU0OW4fXY81jYSwOeU8=",
           "optional": true
         },
         "caseless": {
           "version": "0.11.0",
-          "resolved": "http://192.168.1.90:4873/caseless/-/caseless-0.11.0.tgz",
+          "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.11.0.tgz",
           "integrity": "sha1-cVuW6phBWTzDMGeSP17GDr2k99c=",
           "optional": true
         },
         "form-data": {
           "version": "2.0.0",
-          "resolved": "http://192.168.1.90:4873/form-data/-/form-data-2.0.0.tgz",
+          "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.0.0.tgz",
           "integrity": "sha1-bwrrrcxdoWwT4ezBETfYX5uIOyU=",
           "optional": true,
           "requires": {
@@ -2837,7 +2840,7 @@
         },
         "har-validator": {
           "version": "2.0.6",
-          "resolved": "http://192.168.1.90:4873/har-validator/-/har-validator-2.0.6.tgz",
+          "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-2.0.6.tgz",
           "integrity": "sha1-zcvAgYgmWtEZtqWnyKtw7s+10n0=",
           "optional": true,
           "requires": {
@@ -2849,7 +2852,7 @@
         },
         "http-signature": {
           "version": "1.1.1",
-          "resolved": "http://192.168.1.90:4873/http-signature/-/http-signature-1.1.1.tgz",
+          "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.1.1.tgz",
           "integrity": "sha1-33LiZwZs0Kxn+3at+OE0qPvPkb8=",
           "optional": true,
           "requires": {
@@ -2860,31 +2863,31 @@
         },
         "node-uuid": {
           "version": "1.4.8",
-          "resolved": "http://192.168.1.90:4873/node-uuid/-/node-uuid-1.4.8.tgz",
+          "resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.8.tgz",
           "integrity": "sha1-sEDrCSOWivq/jTL7HxfxFn/auQc=",
           "optional": true
         },
         "oauth-sign": {
           "version": "0.8.2",
-          "resolved": "http://192.168.1.90:4873/oauth-sign/-/oauth-sign-0.8.2.tgz",
+          "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz",
           "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=",
           "optional": true
         },
         "punycode": {
           "version": "1.4.1",
-          "resolved": "http://192.168.1.90:4873/punycode/-/punycode-1.4.1.tgz",
+          "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz",
           "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=",
           "optional": true
         },
         "qs": {
           "version": "6.2.3",
-          "resolved": "http://192.168.1.90:4873/qs/-/qs-6.2.3.tgz",
+          "resolved": "https://registry.npmjs.org/qs/-/qs-6.2.3.tgz",
           "integrity": "sha1-HPyyXBCpsrSDBT/zn138kjOQjP4=",
           "optional": true
         },
         "request": {
           "version": "2.75.0",
-          "resolved": "http://192.168.1.90:4873/request/-/request-2.75.0.tgz",
+          "resolved": "https://registry.npmjs.org/request/-/request-2.75.0.tgz",
           "integrity": "sha1-0rgmiihtoT6qXQGt9dGMyQ9lfZM=",
           "optional": true,
           "requires": {
@@ -2913,7 +2916,7 @@
         },
         "tough-cookie": {
           "version": "2.3.4",
-          "resolved": "http://192.168.1.90:4873/tough-cookie/-/tough-cookie-2.3.4.tgz",
+          "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.4.tgz",
           "integrity": "sha512-TZ6TTfI5NtZnuyy/Kecv+CnoROnyXn2DN97LontgQpCwsX2XyLYCC0ENhYkehSOwAp8rTQKc/NUIF7BkQ5rKLA==",
           "optional": true,
           "requires": {
@@ -2922,7 +2925,7 @@
         },
         "tunnel-agent": {
           "version": "0.4.3",
-          "resolved": "http://192.168.1.90:4873/tunnel-agent/-/tunnel-agent-0.4.3.tgz",
+          "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.4.3.tgz",
           "integrity": "sha1-Y3PbdpCf5XDgjXNYM2Xtgop07us=",
           "optional": true
         }
@@ -2962,7 +2965,7 @@
     },
     "mailcomposer": {
       "version": "4.0.1",
-      "resolved": "http://192.168.1.90:4873/mailcomposer/-/mailcomposer-4.0.1.tgz",
+      "resolved": "https://registry.npmjs.org/mailcomposer/-/mailcomposer-4.0.1.tgz",
       "integrity": "sha1-DhxEsqB890DuF9wUm6AJ8Zyt/rQ=",
       "optional": true,
       "requires": {
@@ -3197,8 +3200,8 @@
           "integrity": "sha1-TEYTm986HwMt7ZHbSfOO7AFlkFA=",
           "dev": true,
           "requires": {
-            "bson": "1.0.9",
-            "require_optional": "1.0.1"
+            "bson": "~1.0.4",
+            "require_optional": "~1.0.0"
           }
         },
         "process-nextick-args": {
@@ -3296,7 +3299,7 @@
     "mz": {
       "version": "2.7.0",
       "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz",
-      "integrity": "sha1-lQCAV6Vsr63CvGPd5/n/aVWUjjI=",
+      "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==",
       "requires": {
         "any-promise": "^1.0.0",
         "object-assign": "^4.0.1",
@@ -3344,14 +3347,14 @@
     "node-xlsx": {
       "version": "0.11.2",
       "resolved": "https://registry.npmjs.org/node-xlsx/-/node-xlsx-0.11.2.tgz",
-      "integrity": "sha1-C7A85hvprcCxhtVcSw+EBP4yGq4=",
+      "integrity": "sha512-EVKysbKISk0mWzYLq1kED/V/SEEjlMrdyyBN8xu9gilEeYvHX0G1NrvQU+CyYHxUeMh+stuPNhjwUdBuyyYIZw==",
       "requires": {
         "xlsx": "^0.11.10"
       }
     },
     "nodemailer": {
       "version": "2.7.2",
-      "resolved": "http://192.168.1.90:4873/nodemailer/-/nodemailer-2.7.2.tgz",
+      "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-2.7.2.tgz",
       "integrity": "sha1-8kLmSa7q45tsftdA73sGHEBNMPk=",
       "optional": true,
       "requires": {
@@ -3372,7 +3375,7 @@
         },
         "socks": {
           "version": "1.1.9",
-          "resolved": "http://192.168.1.90:4873/socks/-/socks-1.1.9.tgz",
+          "resolved": "https://registry.npmjs.org/socks/-/socks-1.1.9.tgz",
           "integrity": "sha1-Yo1+TQSRJDVEWsC25Fk3bLPm1pE=",
           "optional": true,
           "requires": {
@@ -3384,7 +3387,7 @@
     },
     "nodemailer-direct-transport": {
       "version": "3.3.2",
-      "resolved": "http://192.168.1.90:4873/nodemailer-direct-transport/-/nodemailer-direct-transport-3.3.2.tgz",
+      "resolved": "https://registry.npmjs.org/nodemailer-direct-transport/-/nodemailer-direct-transport-3.3.2.tgz",
       "integrity": "sha1-6W+vuQNYVglH5WkBfZfmBzilCoY=",
       "optional": true,
       "requires": {
@@ -3394,13 +3397,13 @@
     },
     "nodemailer-fetch": {
       "version": "1.6.0",
-      "resolved": "http://192.168.1.90:4873/nodemailer-fetch/-/nodemailer-fetch-1.6.0.tgz",
+      "resolved": "https://registry.npmjs.org/nodemailer-fetch/-/nodemailer-fetch-1.6.0.tgz",
       "integrity": "sha1-ecSQihwPXzdbc/6IjamCj23JY6Q=",
       "optional": true
     },
     "nodemailer-shared": {
       "version": "1.1.0",
-      "resolved": "http://192.168.1.90:4873/nodemailer-shared/-/nodemailer-shared-1.1.0.tgz",
+      "resolved": "https://registry.npmjs.org/nodemailer-shared/-/nodemailer-shared-1.1.0.tgz",
       "integrity": "sha1-z1mU4v0mjQD1zw+nZ6CBae2wfsA=",
       "optional": true,
       "requires": {
@@ -3409,7 +3412,7 @@
     },
     "nodemailer-smtp-pool": {
       "version": "2.8.2",
-      "resolved": "http://192.168.1.90:4873/nodemailer-smtp-pool/-/nodemailer-smtp-pool-2.8.2.tgz",
+      "resolved": "https://registry.npmjs.org/nodemailer-smtp-pool/-/nodemailer-smtp-pool-2.8.2.tgz",
       "integrity": "sha1-LrlNbPhXgLG0clzoU7nL1ejajHI=",
       "optional": true,
       "requires": {
@@ -3420,7 +3423,7 @@
     },
     "nodemailer-smtp-transport": {
       "version": "2.7.2",
-      "resolved": "http://192.168.1.90:4873/nodemailer-smtp-transport/-/nodemailer-smtp-transport-2.7.2.tgz",
+      "resolved": "https://registry.npmjs.org/nodemailer-smtp-transport/-/nodemailer-smtp-transport-2.7.2.tgz",
       "integrity": "sha1-A9ccdjFPFKx9vHvwM6am0W1n+3c=",
       "optional": true,
       "requires": {
@@ -3431,7 +3434,7 @@
     },
     "nodemailer-wellknown": {
       "version": "0.1.10",
-      "resolved": "http://192.168.1.90:4873/nodemailer-wellknown/-/nodemailer-wellknown-0.1.10.tgz",
+      "resolved": "https://registry.npmjs.org/nodemailer-wellknown/-/nodemailer-wellknown-0.1.10.tgz",
       "integrity": "sha1-WG24EB2zDLRDjrVGc3pBqtDPE9U=",
       "optional": true
     },
@@ -3641,7 +3644,7 @@
     },
     "path-proxy": {
       "version": "1.0.0",
-      "resolved": "http://192.168.1.90:4873/path-proxy/-/path-proxy-1.0.0.tgz",
+      "resolved": "https://registry.npmjs.org/path-proxy/-/path-proxy-1.0.0.tgz",
       "integrity": "sha1-GOijaFn8nS8aU7SN7hOFQ8Ag3l4=",
       "optional": true,
       "requires": {
@@ -3650,7 +3653,7 @@
       "dependencies": {
         "inflection": {
           "version": "1.3.8",
-          "resolved": "http://192.168.1.90:4873/inflection/-/inflection-1.3.8.tgz",
+          "resolved": "https://registry.npmjs.org/inflection/-/inflection-1.3.8.tgz",
           "integrity": "sha1-y9Fg2p91sUw8xjV41POWeEvzAU4=",
           "optional": true
         }
@@ -3692,13 +3695,13 @@
     },
     "pinkie": {
       "version": "2.0.4",
-      "resolved": "http://192.168.1.90:4873/pinkie/-/pinkie-2.0.4.tgz",
+      "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz",
       "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=",
       "optional": true
     },
     "pinkie-promise": {
       "version": "2.0.1",
-      "resolved": "http://192.168.1.90:4873/pinkie-promise/-/pinkie-promise-2.0.1.tgz",
+      "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz",
       "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=",
       "optional": true,
       "requires": {
@@ -3924,7 +3927,7 @@
     },
     "redis-parser": {
       "version": "2.6.0",
-      "resolved": "http://192.168.1.90:4873/redis-parser/-/redis-parser-2.6.0.tgz",
+      "resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-2.6.0.tgz",
       "integrity": "sha1-Uu0J2srBCPGmMcB+m2mUHnoZUEs=",
       "optional": true
     },
@@ -4194,7 +4197,7 @@
     },
     "slack-node": {
       "version": "0.2.0",
-      "resolved": "http://192.168.1.90:4873/slack-node/-/slack-node-0.2.0.tgz",
+      "resolved": "https://registry.npmjs.org/slack-node/-/slack-node-0.2.0.tgz",
       "integrity": "sha1-3kuN3aqLeT9h29KTgQT9q/N9+jA=",
       "optional": true,
       "requires": {
@@ -4219,7 +4222,7 @@
     },
     "smtp-connection": {
       "version": "2.12.0",
-      "resolved": "http://192.168.1.90:4873/smtp-connection/-/smtp-connection-2.12.0.tgz",
+      "resolved": "https://registry.npmjs.org/smtp-connection/-/smtp-connection-2.12.0.tgz",
       "integrity": "sha1-1275EnyyPCJZ7bHoNJwujV4tdME=",
       "optional": true,
       "requires": {
@@ -4229,7 +4232,7 @@
     },
     "sntp": {
       "version": "1.0.9",
-      "resolved": "http://192.168.1.90:4873/sntp/-/sntp-1.0.9.tgz",
+      "resolved": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz",
       "integrity": "sha1-ZUEYTMkK7qbG57NeJlkIJEPGYZg=",
       "optional": true,
       "requires": {
@@ -4279,7 +4282,7 @@
     "source-map-support": {
       "version": "0.4.18",
       "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.18.tgz",
-      "integrity": "sha1-Aoam3ovkJkEzhZTpfM6nXwosWF8=",
+      "integrity": "sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA==",
       "requires": {
         "source-map": "^0.5.6"
       }
@@ -4553,7 +4556,7 @@
     },
     "timespan": {
       "version": "2.3.0",
-      "resolved": "http://192.168.1.90:4873/timespan/-/timespan-2.3.0.tgz",
+      "resolved": "https://registry.npmjs.org/timespan/-/timespan-2.3.0.tgz",
       "integrity": "sha1-SQLOBAvRPYRcj1myfp1ZutbzmSk=",
       "optional": true
     },
@@ -4657,7 +4660,7 @@
     },
     "underscore": {
       "version": "1.7.0",
-      "resolved": "http://192.168.1.90:4873/underscore/-/underscore-1.7.0.tgz",
+      "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.7.0.tgz",
       "integrity": "sha1-a7rwh3UA02vjTsqlhODbn+8DUgk=",
       "optional": true
     },
@@ -4811,6 +4814,13 @@
             "graceful-fs": "^4.2.0",
             "jsonfile": "^4.0.0",
             "universalify": "^0.1.0"
+          },
+          "dependencies": {
+            "graceful-fs": {
+              "version": "4.2.6",
+              "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz",
+              "integrity": "sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ=="
+            }
           }
         },
         "get-uri": {
@@ -4881,11 +4891,19 @@
           "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=",
           "requires": {
             "graceful-fs": "^4.1.6"
+          },
+          "dependencies": {
+            "graceful-fs": {
+              "version": "4.2.6",
+              "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz",
+              "integrity": "sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==",
+              "optional": true
+            }
           }
         },
         "lru-cache": {
           "version": "5.1.1",
-          "resolved": "http://192.168.1.90:4873/lru-cache/-/lru-cache-5.1.1.tgz",
+          "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
           "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
           "requires": {
             "yallist": "^3.0.2"
@@ -4893,7 +4911,7 @@
         },
         "ms": {
           "version": "2.1.2",
-          "resolved": "http://192.168.1.90:4873/ms/-/ms-2.1.2.tgz",
+          "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
           "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
         },
         "netmask": {
@@ -5035,7 +5053,7 @@
     },
     "when": {
       "version": "3.7.8",
-      "resolved": "http://192.168.1.90:4873/when/-/when-3.7.8.tgz",
+      "resolved": "https://registry.npmjs.org/when/-/when-3.7.8.tgz",
       "integrity": "sha1-xxMLan6gRpPoQs3J56Hyqjmjn4I=",
       "optional": true
     },

+ 12 - 0
public/id_tree.js

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

+ 4 - 2
public/web/common_ajax.js

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

+ 24 - 0
public/web/id_tree.js

@@ -866,6 +866,30 @@ var idTree = {
             return datas;
         };
 
+        Tree.prototype.getExpState = function (nodes) {
+            let sessionExpanded = [];
+            function getStat(items){
+                for(let item of items){
+                    sessionExpanded.push(item.expanded ? 1 : 0);
+                }
+            }
+            getStat(nodes);
+            let expState = sessionExpanded.join('');
+            return expState;
+        };
+
+        //节点根据展开收起列表'010101'展开收起
+        Tree.prototype.setExpandedByState = function (nodes, expState) {
+            let expStateArr = expState.split('');
+            for(let i = 0; i < nodes.length; i++){
+                let expanded = expStateArr[i] == 1 ? true : false;
+                if(nodes[i].expanded === expanded){
+                    continue;
+                }
+                nodes[i].setExpanded(expanded);
+            }
+        };
+
         // 检查树结构数据有没问题
         Tree.prototype.check = function (roots) {
             return isValid(roots);

+ 14 - 0
public/web/sheet/sheet_common.js

@@ -170,6 +170,20 @@ var sheetCommonObj = {
         sheet.resumePaint();
         //me.shieldAllCells(sheet);
     },
+    getCheckBox(threeState = false){
+        var c = new GC.Spread.Sheets.CellTypes.CheckBox();
+        c.isThreeState(threeState);
+        return c
+    },
+    // 无法勾选的复选框
+    getReadOnlyCheckBox (threeState = false) {
+        function ReadOnlyCheckBox() {}
+        ReadOnlyCheckBox.prototype = this.getCheckBox(threeState);
+        ReadOnlyCheckBox.prototype.processMouseUp = function () {
+            return;
+        };
+        return new ReadOnlyCheckBox();
+    },
     showRowData:function (sheet,setting,row,data,distTypeTree=null) {
         let ch = GC.Spread.Sheets.SheetArea.viewport;
         for (var col = 0; col < setting.header.length; col++) {

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

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

+ 70 - 13
web/maintain/billsGuidance_lib/html/main.html

@@ -5,26 +5,61 @@
   <meta charset="utf-8">
   <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
   <meta http-equiv="x-ua-compatible" content="ie=edge">
-  <title>清单指引编辑器</title>
+  <title><%= manager.isTemporary ? '清单精灵编辑器' : '清单指引编辑器' %></title>
   <link rel="stylesheet" href="/lib/bootstrap/css/bootstrap.min.css">
   <link rel="stylesheet" href="/web/maintain/billsGuidance_lib/css/main.css">
   <link rel="stylesheet" href="/lib/font-awesome/font-awesome.min.css">
+  <style>
+    .avatar {
+      display: flex;
+      align-items: center;
+      height: 38px;
+      cursor: pointer;
+      padding: 0 20px;
+    }
+    .avatar:hover {
+      text-decoration: none;
+      box-shadow: inset 0 3px 5px rgb(0 0 0 / 13%);
+    }
+    .avatar .dropdown-menu a {
+      display: block;
+      padding: 3px 20px;
+      clear: both;
+      font-weight: 400;
+      line-height: 1.42857143;
+      color: #333;
+      white-space: nowrap;
+    }
+  </style>
+    <script>
+      const isTemporary = '<%- manager.isTemporary %>';
+    </script>
 </head>
 
 <body>
   <div class="header">
-    <nav class="navbar navbar-toggleable-lg navbar-light bg-faded p-0 ">
-      <span class="header-logo px-2">清单指引编辑器</span>
-      <div class="navbar-text"></div>
-    </nav>
-    <nav class="navbar navbar-toggleable-lg justify-content-between navbar-light p-0">
-      <ul class="nav navbar-nav px-1">
-        <li class="nav-item">
-          <a class="nav-link" href="javacript:void(0);" aria-haspopup="true" aria-expanded="false" data-toggle="modal"
-            data-target="#add">新建清单指引库</a>
-        </li>
-      </ul>
+    <nav class="navbar navbar-toggleable-lg navbar-light bg-faded p-0 " style="display: flex; justify-content: space-between;">
+      <span class="header-logo px-2"><%= manager.isTemporary ? '清单精灵编辑器' : '清单指引编辑器' %></span>
+      <% if (manager.isTemporary)  { %>
+              <div class="avatar btn-group">
+                  <a class="dropdown-toggle" data-toggle="dropdown">
+                      <span><%= manager.username %></span>
+                  </a>
+                  <ul class="dropdown-menu dropdown-menu-right">
+                      <li><a href="/login/logout">退出登录</a></li>
+                  </ul>
+      <% } %>
     </nav>
+    <% if (!manager.isTemporary)  { %>
+      <nav class="navbar navbar-toggleable-lg justify-content-between navbar-light p-0">
+        <ul class="nav navbar-nav px-1">
+          <li class="nav-item">
+            <a class="nav-link" href="javacript:void(0);" aria-haspopup="true" aria-expanded="false" data-toggle="modal"
+              data-target="#add">新建清单指引库</a>
+          </li>
+        </ul>
+      </nav>
+    <% } %>
   </div>
   <div class="main">
     <div class="content">
@@ -35,12 +70,14 @@
               <table class="table table-hover table-bordered">
                 <thead>
                   <tr>
-                    <th>清单指引名称</th>
+                    <th><%= manager.isTemporary ? '清单精灵名称' : '清单指引名称' %></th>
                     <th width="160">编办</th>
                     <th width="300">清单规则</th>
                     <th width="100">类型</th>
                     <th width="160">添加时间</th>
                     <th width="70">操作</th>
+                    <%- manager.isTemporary ? '' : '<th width="120">生成清单分类</th>' %>
+                    <%- manager.isTemporary ? '' : '<th width="120">导出分类excel</th>' %>
                   </tr>
                 </thead>
                 <tbody>
@@ -164,6 +201,26 @@
       </div>
     </div>
   </div>
+  <!-- 生成清单分类 -->
+  <div class="modal fade" id="generate-class-modal" data-backdrop="static" style="display: none;" aria-hidden="true">
+    <div class="modal-dialog" role="document">
+      <div class="modal-content">
+        <div class="modal-header">
+          <h5 class="modal-title">生成清单分类</h5>
+          <button type="button" class="close" data-dismiss="modal" aria-label="Close">
+            <span aria-hidden="true">×</span>
+          </button>
+        </div>
+        <div class="modal-body">
+          <div id="generate-class-info" style="font-weight: 700;"></div>
+        </div>
+        <div class="modal-footer">
+          <a id="generate-class-confirm" href="javascript:void(0);" class="btn btn-danger">确认</a>
+          <button type="button" class="btn btn-secondary" data-dismiss="modal">取消</button>
+        </div>
+      </div>
+    </div>
+  </div>
   <!-- JS. -->
   <script src="/lib/jquery/jquery.min.js"></script>
   <script src="/lib/tether/tether.min.js"></script>

+ 117 - 4
web/maintain/billsGuidance_lib/html/zhiyin.html

@@ -5,12 +5,37 @@
     <meta charset="utf-8">
     <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
     <meta http-equiv="x-ua-compatible" content="ie=edge">
-    <title>清单指引编辑器</title>
+    <title><%= manager.isTemporary ? '清单精灵编辑器' : '清单指引编辑器' %></title>
     <link rel="stylesheet" href="/lib/bootstrap/css/bootstrap.min.css">
     <link rel="stylesheet" href="/lib/spreadjs/sheets/css/gc.spread.sheets.sc.css"></link>
     <link rel="stylesheet" href="/web/maintain/billsGuidance_lib/css/main.css">
     <link rel="stylesheet" href="/lib/font-awesome/font-awesome.min.css">
     <link rel="stylesheet" href="/lib/jquery-contextmenu/jquery.contextMenu.css" type="text/css">
+    <style>
+        #searchBillsResult .search-item{
+            margin-right: 20px;
+        }
+        #searchBillsResult .search-item.go-to {
+            border-color: rgb(108, 117, 125);
+            background-color: #6c757d;
+            color: white;
+        }
+        #searchBillsResult .search-item.go-to:hover {
+            background-color: #5a6268;;
+        }
+        #insertRation.disabled {
+            opacity: 1;
+            color: #464646;
+            background-color: #5BA6E6;
+            border-color: #5BA6E6;
+        }
+        #insertAll.disabled {
+            opacity: 1;
+            color: #464646;
+            background-color: #5BA6E6;
+            border-color: #5BA6E6;
+        }
+    </style>
     <script>
         let userAccount = '<%= userAccount %>';
     </script>
@@ -19,8 +44,8 @@
 <body>
     <div class="header">
         <nav class="navbar navbar-toggleable-lg navbar-light bg-faded p-0 ">
-            <span class="header-logo px-2">清单指引编辑器</span>
-            <div class="navbar-text" id="libName"><a href="/billsGuidance/main">清单指引库</a><i class="fa fa-angle-right fa-fw"></i>XXX清单指引</div>
+            <span class="header-logo px-2"><%= manager.isTemporary ? '清单精灵编辑器' : '清单指引编辑器' %></span>
+            <div class="navbar-text" id="libName"><a href="/billsGuidance/main"><%= manager.isTemporary ? '清单精灵库' : '清单指引库' %></a><i class="fa fa-angle-right fa-fw"></i>XXX清单指引</div>
         </nav>
         <nav class="navbar navbar-toggleable-lg justify-content-between navbar-light p-0">
               <ul class="nav nav-tabs" role="tablist">
@@ -35,6 +60,26 @@
             <div class="container-fluid">
                 <div class="row" id="dataRow">
                     <div class="main-side p-0" id="leftContent" style="width: 33%">
+                        <div class="side-tools-bar" >
+                            <div style="display: flex; align-items: center; height: 36px;">
+                                <a id="expandToSecond" href="javascript:void(0);" class="btn btn-sm"><i class="fa fa-minus-square-o" aria-hidden="true"></i> 收起</a>
+                                <div class="input-group col-5 pl-0" style="padding-right: 0">
+                                    <input id="searchBillText" type="text" class="form-control form-control-sm" placeholder="搜索清单">
+                                    <span class="input-group-btn">
+                                    <button id="searchBillBtn" class="btn btn-secondary btn-sm" type="button"><i class="fa fa-search" aria-hidden="true"></i></button>
+                                </span>
+                                </div>
+                                <a id="editMaterial" href="javascript:void(0);" class="btn btn-sm"><i class="fa fa-edit" aria-hidden="true"></i> 配置材料</a>
+                            </div>
+                            <div id="searchBillsResult" style="display: none;">
+                                <div style="display: flex; align-items: center; height: 36px;">
+                                    <span class="search-item">搜索结果: <span id="searchBillsCount"></span></span>
+                                    <a class="search-item go-to btn btn-secondary btn-sm" href="javascript:void(0);" id="preBill">上一条</a>
+                                    <a class="search-item go-to btn btn-secondary btn-sm" href="javascript:void(0);" id="nextBills">下一条</a>
+                                    <a class="search-item btn btn-link btn-sm" data-toggle="tooltip" data-placement="bottom" title="关闭搜索" href="javascript:void(0);" id="closeSearchBills"><i class="fa fa-remove" aria-hidden="true"></i></a>
+                                </div>
+                            </div>
+                        </div>
                         <div id="billsSpread" class="main-side-top">
                         </div>
                         <div class="main-side-bottom">
@@ -45,7 +90,7 @@
                       <div class="resize" id="slideResizeLeft" style="width: 1%; height: 100%; resize:horizontal; cursor: w-resize; float: left; background: #F1F1F1"></div>
                       <div style="width: 99%; float: left">
                           <div class="toolsbar px-1 d-flex justify-content-between">
-                              <div class="tools-btn btn-group align-top">
+                              <div class="tools-btn btn-group align-top" style="display: flex; align-items: center; height: 36px; overflow: hidden;">
                                   <a id="insert" href="javascript:void(0);" class="btn btn-sm lock-btn-control" data-toggle="tooltip" data-placement="bottom" title="插入"><i class="fa fa-reply-all" aria-hidden="true"></i> 插入</a>
                                   <a id="del" href="javascript:void(0);" class="btn btn-sm lock-btn-control" data-toggle="tooltip" data-placement="bottom" title="删除"><i class="fa fa-remove" aria-hidden="true"></i></a>
                                   <a id="upLevel" href="javascript:void(0);" class="btn btn-sm lock-btn-control disabled" data-toggle="tooltip" data-placement="bottom" title="升级"><i class="fa fa-arrow-left" aria-hidden="true"></i></a>
@@ -53,6 +98,7 @@
                                   <a id="downMove" href="javascript:void(0);" class="btn btn-sm lock-btn-control" data-toggle="tooltip" data-placement="bottom" title="下移"><i class="fa fa-arrow-down" aria-hidden="true"></i></a>
                                   <a id="upMove" href="javascript:void(0);" class="btn btn-sm lock-btn-control" data-toggle="tooltip" data-placement="bottom" title="上移"><i class="fa fa-arrow-up" aria-hidden="true"></i></a>
                                   <a id="expandContract" href="javascript:void(0);" class="btn btn-sm" data-toggle="tooltip" data-placement="bottom" title="收起定额"><i class="fa fa-minus-square-o" aria-hidden="true"></i> 收起定额</a>
+                                  <!-- <a id="generate-class" href="javascript:void(0);" class="btn btn-sm lock-btn-control" data-toggle="tooltip" data-placement="bottom" title="生成分类">测试生成分类算法</a> -->
                               </div>
                           </div>
                           <div class="main-top-content">
@@ -132,6 +178,73 @@
             </div>
         </div>
     </div>
+    <div class="modal fade" id="delRationAlert" data-backdrop="static" style="display: none;" aria-hidden="true">
+        <div class="modal-dialog" role="document">
+            <div class="modal-content">
+                <div class="modal-header">
+                    <h5 class="modal-title">警告</h5>
+                    <button type="button"  class="close" data-dismiss="modal" aria-label="Close">
+                        <span aria-hidden="true">×</span>
+                    </button>
+                </div>
+                <div class="modal-body">
+                    <h5 class="text-danger">是否删除当前节点下的所有定额?</h5>
+                </div>
+                <div class="modal-footer">
+                    <button type="button" class="btn btn-danger" id="delRationConfirm">确认</button>
+                    <button type="button" class="btn btn-secondary"  data-dismiss="modal">取消</button>
+                </div>
+            </div>
+        </div>
+    </div>
+    <!-- 配置材料 -->
+    <div class="modal fade" id="bill-material-modal" data-backdrop="static" style="display: none;" aria-hidden="true">
+        <div class="modal-dialog" role="document" style="transform: translateX(-45%);">
+            <div class="modal-content" style="width: 900px">
+                <div class="modal-header">
+                    <h5 class="modal-title">配置材料</h5>
+                    <button type="button"  class="close" data-dismiss="modal" aria-label="Close">
+                        <span aria-hidden="true">×</span>
+                    </button>
+                </div>
+                <div class="modal-body">
+                    <div style="display: flex;">
+                        <div style="width: 50%;">
+                            <p class="mb-0">配置材料:</p>
+                            <div id="bill-material-spread" style="height: 400px;"></div>
+                        </div>
+                        <div style="width: 50%;">
+                            <p class="mb-0">材料汇总:双击右侧材料,可添加到左侧配置材料中。</p>
+                            <div id="bill-material-helper-spread" style="height: 400px;"></div>
+                        </div>
+                    </div>
+                    
+                </div>
+                <div class="modal-footer">
+                    <button type="button" class="btn btn-secondary"  data-dismiss="modal">关闭</button>
+                </div>
+            </div>
+        </div>
+    </div>
+    <div class="modal fade" id="alert" data-backdrop="static" style="display: none;" aria-hidden="true">
+        <div class="modal-dialog" role="document">
+            <div class="modal-content">
+                <div class="modal-header">
+                    <h5 class="modal-title">警告</h5>
+                    <button type="button"  class="close" data-dismiss="modal" aria-label="Close">
+                        <span aria-hidden="true">×</span>
+                    </button>
+                </div>
+                <div class="modal-body">
+                    <div id="alert-info" style="font-weight: 700; max-height: 300px; overflow-y: auto;"></div>
+                </div>
+                <div class="modal-footer">
+                    <button type="button" class="btn btn-danger" data-dismiss="modal">确定</button>
+                    <button type="button" class="btn btn-secondary"  data-dismiss="modal">取消</button>
+                </div>
+            </div>
+        </div>
+    </div>
     <!-- JS. -->
     <script src="/lib/jquery/jquery.min.js"></script>
     <script src="/lib/tether/tether.min.js"></script>

File diff suppressed because it is too large
+ 1026 - 53
web/maintain/billsGuidance_lib/js/billsGuidance.js


+ 4 - 3
web/maintain/billsGuidance_lib/js/global.js

@@ -4,9 +4,10 @@ function autoFlashHeight(){
     var topContentHeight = $('#rationSearchResult').is(':visible') ? 0 : $('.top-content').height();
     var toolsBar = $(".toolsbar").height();
     var toolsBarHeightQ = $(".tools-bar-height-q").height();
+    const sideToolsBar = $('.side-tools-bar') ? $('.side-tools-bar').height() : 0;
     $(".content").height($(window).height()-headerHeight);
-    $(".main-side-top").height(($(window).height()-headerHeight) * 0.85);
-    $(".main-side-bottom").height(($(window).height()-headerHeight) * 0.15);
+    $(".main-side-top").height(($(window).height()-headerHeight - sideToolsBar) * 0.85);
+    $(".main-side-bottom").height(($(window).height()-headerHeight - sideToolsBar) * 0.15);
     $('.main-side-bottom').find('textarea').height($('.main-side-bottom').height() - 20);
     $('.main-side-bottom').find('textarea').width($('.main-side-bottom').width() - 25);
     $(".fluid-content").height($(window).height()-headerHeight-1);
@@ -19,7 +20,7 @@ function autoFlashHeight(){
     $('.main-bottom-content').find('textarea').width($('.main-bottom-content').width() - 25);
     $(".main-data").height($('.main-top-content').height());
     $(".main-data-full").height($(window).height()-headerHeight);
-    $(".main-data-bottom").height($(window).height()-headerHeight-toolsBarHeightQ-topContentHeight-$('#rationSearchResult').height() + 30);
+    $(".main-data-bottom").height($(window).height()-headerHeight-toolsBarHeightQ-topContentHeight-$('#rationSearchResult').height() + 29);
     $('.bottom-content').height($('.main-data-bottom').height());
     if (typeof billsGuidance !== 'undefined') {
         billsGuidance.initSlideSize();

+ 41 - 2
web/maintain/billsGuidance_lib/js/main.js

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

+ 8 - 0
web/maintain/price_info_lib/css/index.css

@@ -109,4 +109,12 @@ body {
     float: left;
     width: 59.9%;
     height: 100%;
+}
+
+.main .right .top {
+    height: 70%;
+}
+
+.main .right .bottom {
+    height: 30%;
 }

+ 2 - 1
web/maintain/price_info_lib/html/edit.html

@@ -50,7 +50,8 @@
                 </div>
             </div>
             <div class="right">
-                <div id="price-spread" style="width: 100%; height: 100%"></div>
+                <div class="top" id="price-spread"></div>
+                <div class="bottom" id="keyword-spread"></div>
             </div>
         </div>
     </div>

+ 7 - 1
web/maintain/price_info_lib/html/main.html

@@ -29,6 +29,7 @@
                                     <th width="160">添加时间</th>
                                     <th width="70">操作</th>
                                     <th width="70">原始数据</th>
+                                    <th width="70">关键字</th>
                                 </tr>
                             </thead>
                             <tbody id="showArea">
@@ -52,9 +53,14 @@
                                     </td>
                                     <td>
                                         <a class="btn btn-secondary btn-sm import-data lock-btn-control disabled"
-                                            onclick='handleImportClick("<%= lib.ID%>")' href="javacript:void(0);"
+                                            onclick='handleImportClick("<%= lib.ID%>", "originalData")' href="javacript:void(0);"
                                             title="导入数据"><i class="fa fa-sign-in fa-rotate-90"></i>导入</a>
                                     </td>
+                                    <td>
+                                        <a class="btn btn-secondary btn-sm import-data lock-btn-control disabled"
+                                            onclick='handleImportClick("<%= lib.ID%>", "keys")' href="javacript:void(0);"
+                                            title="导入关键字"><i class="fa fa-sign-in fa-rotate-90"></i>导入</a>
+                                    </td>
                                 </tr>
                                 <% } %>
                             </tbody>

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

@@ -648,16 +648,47 @@ const CLASS_BOOK = (() => {
 
 })();
 
+// 关键字表
+const KEYWORD_BOOK = (() => {
+    const setting = {
+        header: [
+            { headerName: '关键字', headerWidth: 200, dataCode: 'keyword', dataType: 'String', hAlign: 'left', vAlign: 'center' },
+            { headerName: '单位', headerWidth: 70, dataCode: 'unit', dataType: 'String', hAlign: 'center', vAlign: 'center' },
+            { headerName: '关键字效果', headerWidth: 100, dataCode: 'coe', dataType: 'String', hAlign: 'center', vAlign: 'center' },
+            { headerName: '组别', headerWidth: 50, dataCode: 'group', dataType: 'String', hAlign: 'center', vAlign: 'center' },
+            { headerName: '选项号', headerWidth: 70, dataCode: 'optionCode', dataType: 'String', hAlign: 'center', vAlign: 'center' },
+        ],
+    };
+    // 初始化表格
+    const workBook = initSheet($('#keyword-spread')[0], setting);
+    workBook.options.allowUserDragDrop = false;
+    workBook.options.allowUserDragFill = false;
+    lockUtil.lockSpreads([workBook], true);
+    const sheet = workBook.getSheet(0);
+
+    // 显示关键字数据
+    const showKeywordData = (keywordList) => {
+        showData(sheet, keywordList, setting.header);
+    }
+
+    return {
+        showKeywordData 
+    }
+})();
+
 // 价格信息表
 const PRICE_BOOK = (() => {
     const setting = {
         header: [
             { headerName: '编码', headerWidth: 100, dataCode: 'code', dataType: 'String', hAlign: 'left', vAlign: 'center' ,formatter: "@"},
+            { headerName: '别名编码', headerWidth: 70, dataCode: 'classCode', dataType: 'String', hAlign: 'left', vAlign: 'center' ,formatter: "@"},
             { headerName: '名称', headerWidth: 200, dataCode: 'name', dataType: 'String', hAlign: 'left', vAlign: 'center' },
             { headerName: '规格型号', headerWidth: 120, dataCode: 'specs', dataType: 'String', hAlign: 'left', vAlign: 'center' },
             { headerName: '单位', headerWidth: 80, dataCode: 'unit', dataType: 'String', hAlign: 'center', vAlign: 'center' },
             { headerName: '不含税价', headerWidth: 80, dataCode: 'noTaxPrice', dataType: 'String', hAlign: 'right', vAlign: 'center' },
             { headerName: '含税价', headerWidth: 80, dataCode: 'taxPrice', dataType: 'String', hAlign: 'right', vAlign: 'center' },
+            { headerName: '月份备注', headerWidth: 140, dataCode: 'dateRemark', dataType: 'String', hAlign: 'left', vAlign: 'center' },
+            { headerName: '计算式', headerWidth: 100, dataCode: 'expString', dataType: 'String', hAlign: 'left', vAlign: 'center' },
         ],
     };
     // 初始化表格
@@ -682,6 +713,9 @@ const PRICE_BOOK = (() => {
         try {
             cache = await ajaxPost('/priceInfo/getPriceData', { classIDList }, TIME_OUT);
             showData(sheet, cache, setting.header, 5);
+            const row = sheet.getActiveRowIndex();
+            const keywordList = cache[row] && cache[row].keywordList || [];
+            KEYWORD_BOOK.showKeywordData(keywordList);
         } catch (err) {
             cache = [];
             sheet.setRowCount(0);
@@ -783,6 +817,12 @@ const PRICE_BOOK = (() => {
         const changedCells = [{ row: info.row }];
         handleEdit(changedCells);
     });
+    sheet.bind(GC.Spread.Sheets.Events.SelectionChanged, function (e, info) {
+        const row = info.newSelections && info.newSelections[0] ? info.newSelections[0].row : 0;
+        // 显示关键字数据
+        const keywordList = cache[row] && cache[row].keywordList || [];
+        KEYWORD_BOOK.showKeywordData(keywordList);
+    });
     sheet.bind(GC.Spread.Sheets.Events.RangeChanged, function (e, info) {
         const changedRows = [];
         let preRow;
@@ -802,6 +842,7 @@ const PRICE_BOOK = (() => {
 })();
 
 $(document).ready(() => {
+    console.log('进入信息价');
     $('[data-toggle="tooltip"]').tooltip();
     AREA_BOOK.handleSelectionChanged(0);
     const $range = $(document.body);

+ 5 - 1
web/maintain/price_info_lib/js/main.js

@@ -79,9 +79,12 @@ function handleDeleteConfirm() {
     }
 }
 
+let importType = 'originalData';
+
 // 点击导入按钮
-function handleImportClick(libID) {
+function handleImportClick(libID, type) {
     setCurLib(libID);
+    importType = type;
     $('#import').modal('show');
 }
 
@@ -97,6 +100,7 @@ function handleImportConfirm() {
         }
         formData.append('file', file.files[0]);
         formData.append('libID', curLib.id);
+        formData.append('importType', importType);
         $.ajax({
             url: '/priceInfo/importExcel',
             type: 'POST',

+ 11 - 11
web/maintain/ration_repository/js/ration_coe.js

@@ -327,17 +327,17 @@ var rationCoeOprObj = {
                     sheetCommonObj.cleanData(me.sheet, me.setting, -1);
                     if (result.data) {
                         var tempResult = [];
-                        for (let obj of result.data) {
-                            for(let i = 0, len = coeList.length; i < len; i++){
-                                if(obj.ID === coeList[i].ID){
-                                    obj.no = coeList[i].no;
-                                    tempResult.push(obj);
-                                    break;
-                                }
-                            }
-                        };
-
-
+                        let stdMap = _.indexBy(result.data,'ID');
+                        for(let i = 0, len = coeList.length; i < len; i++){
+                            let obj = stdMap[coeList[i].ID];
+                            if(obj){
+                                obj.no = coeList[i].no;  
+                            }else{
+                               obj= {ID:coeList[i].ID,no:coeList[i].no,serialNo:-999,name:'引用错误或子目已被删除'} 
+                            } 
+                            tempResult.push(obj);                           
+                        }
+                        
                         me.cache["_Coe_" + ration.ID] = tempResult;
 
                         me.showCoeItems(ration.ID);

+ 18 - 9
web/over_write/crawler/guangdong_2018_price_crawler.js

@@ -233,11 +233,11 @@ function getDateForApi(journalList, period) {
   // 没匹配到月度数据,去匹配季度
   const month = period.split('-')[1];
   let quaterDate;
-  if (['1', '2', '3'].includes(month)) {
+  if (['1', '01', '2', '02', '3', '03'].includes(month)) {
     quaterDate = '03-15';
-  } else if (['4', '5', '6'].includes(month)) {
+  } else if (['4', '04', '5', '05', '6', '06'].includes(month)) {
     quaterDate = '06-15';
-  } else if (['7', '8', '9'].includes(month)) {
+  } else if (['7', '07', '8', '08', '9', '09'].includes(month)) {
     quaterDate = '09-15';
   } else if (['10', '11', '12'].includes(month)) {
     quaterDate = '12-15';
@@ -265,7 +265,7 @@ function getDateForApi(journalList, period) {
   if (fullYear) {
     return fullYear;
   }
-  return monthPeriod;
+  return null;
 }
 
 // 获取信息价
@@ -273,10 +273,6 @@ async function getPriceInfoSource(token, period, city, county) {
   const province = '广东';
   const area = `${province}-${city}-${county}`;
   const industry = 1;
-  /* const existData = await priceInfoSourceModel.find({ period, area, industry }).lean();
-  if (existData.length) {
-    return existData;
-  } */
   const body = {
     token,
     province,
@@ -288,7 +284,14 @@ async function getPriceInfoSource(token, period, city, county) {
   // 获取期刊数据
   const year = period.split('-')[0];
   const journalRst = await post('/gov/journal_list', { ...body, date: year });
-  const date = journalRst && journalRst.results ? getDateForApi(journalRst.results, period) : `${period}-05`;
+  if (!journalRst || !journalRst.results) {
+    // 不抛出错误,不同地区更新信息价期刊的时间不同,如果导入数据时,有地区没发布数据,直接跳过并提示
+    return `retCode: ${journalRst.retCode} ${journalRst.msg} (${period} ${city} ${county})`;
+  }
+  const date = getDateForApi(journalRst.results, period);
+  if (!date) {
+    return `retCode: 1000 暂无数据 (${period} ${city} ${county})`;
+  }
   const sourceData = await post('/gov/get', { ...body, date });
   if (!sourceData.results) {
     // 不抛出错误,不同地区更新信息价期刊的时间不同,如果导入数据时,有地区没发布数据,直接跳过并提示
@@ -476,6 +479,12 @@ async function crawlData(from, to, compilationID) {
       } else { // 需求变更,需要排序
         await priceInfoAreaModel.update({ ID: areaItem.ID }, { $set: { serialNo } });
       }
+
+      const existCount = await priceInfoItemModel.count({ compilationID, period, areaID: areaItem.ID });
+      if (existCount) {
+        continue;
+      }
+
       // 存入信息价相关数据
       const sourceData = await getPriceInfoSource(token, sourcePeriod, city, county);
       if (typeof sourceData === 'string') {

+ 6 - 2
web/users/js/login.js

@@ -58,8 +58,12 @@ $(document).ready(function() {
             success: function(response) {
                 isLogin = false;
                 if (response.error === 0) {
-                    // 正确则跳转
-                    window.location.href = '/dashboard';
+                    if (response.isTemporary) {
+                        window.location.href = '/billsGuidance/main';
+                    } else {
+                        // 正确则跳转
+                        window.location.href = '/dashboard';
+                    }
                 } else {
                     // 错误则提示
                     show_error(response.msg);