فهرست منبع

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

TonyKang 5 سال پیش
والد
کامیت
6c26c4e693
43فایلهای تغییر یافته به همراه1602 افزوده شده و 383 حذف شده
  1. 2 3
      config/gulpConfig.js
  2. 1 1
      modules/all_models/import_logs.js
  3. 4 0
      modules/all_models/share_list.js
  4. 32 0
      modules/blank/routes/index.js
  5. 16 11
      modules/complementary_glj_lib/models/gljModel.js
  6. 9 0
      modules/main/facade/ration_facade.js
  7. 3 1
      modules/main/models/project_consts.js
  8. 6 4
      modules/main/routes/main_route.js
  9. 16 13
      modules/pm/controllers/pm_controller.js
  10. 221 16
      modules/pm/facade/pm_facade.js
  11. 2 0
      modules/pm/models/project_model.js
  12. 54 2
      modules/ration_glj/controllers/ration_glj_controller.js
  13. 14 0
      modules/ration_glj/facade/ration_glj_facade.js
  14. 1 1
      modules/ration_glj/routes/ration_glj_route.js
  15. 32 0
      public/common_constants.js
  16. 91 86
      public/web/socket/connection.js
  17. 22 0
      socket.js
  18. 10 2
      web/building_saas/complementary_ration_lib/html/dinge.html
  19. 82 29
      web/building_saas/complementary_ration_lib/js/gljSelect.js
  20. 1 0
      web/building_saas/complementary_ration_lib/js/init.js
  21. 1 6
      web/building_saas/complementary_ration_lib/js/ration_glj.js
  22. 0 2
      web/building_saas/complementary_ration_lib/js/section_tree.js
  23. 14 2
      web/building_saas/js/global.js
  24. 5 12
      web/building_saas/main/html/main.html
  25. 3 0
      web/building_saas/main/js/models/calc_program.js
  26. 0 3
      web/building_saas/main/js/models/exportStdInterfaceBase.js
  27. 2 0
      web/building_saas/main/js/models/installation_fee.js
  28. 2 4
      web/building_saas/main/js/views/glj_view.js
  29. 1 1
      web/building_saas/main/js/views/glj_view_contextMenu.js
  30. 7 9
      web/building_saas/main/js/views/installation_fee_view.js
  31. 3 0
      web/building_saas/main/js/views/item_increase_fee_view.js
  32. 5 4
      web/building_saas/main/js/views/mbzm_view.js
  33. 6 1
      web/building_saas/main/js/views/project_view.js
  34. 6 1
      web/building_saas/pm/html/project-management.html
  35. 9 7
      web/building_saas/pm/js/pm_newMain.js
  36. 175 36
      web/building_saas/pm/js/pm_share.js
  37. 1 1
      web/building_saas/unit_price_file/index.html
  38. 32 0
      web/common/components/blank/index.html
  39. 168 4
      web/common/components/share/index.js
  40. 10 0
      web/common/html/header.html
  41. 242 1
      web/over_write/js/guangdong_2018.js
  42. 100 68
      web/over_write/js/guangdong_2018_export.js
  43. 191 52
      web/over_write/js/guangdong_2018_import.js

+ 2 - 3
config/gulpConfig.js

@@ -18,6 +18,8 @@ module.exports = {
         'lib/lodash/lodash.js',
         'public/web/commonAlert.js',
         'public/web/headerOpr.js',
+        'public/common_util.js',
+        'public/common_constants.js',
         'lib/jquery-editable-select/jquery.editable-select.min.js'
     ],
     common_css:[
@@ -56,7 +58,6 @@ module.exports = {
         'web/building_saas/glj/js/socket.io.slim.js',
         'public/web/socket/connection.js',
         'public/billsUtil.js',
-        'public/common_constants.js',
         'web/building_saas/main/js/models/importStdInterfaceBase.js',
         'web/common/components/share/index.js',
         'web/building_saas/pm/js/**/*.js',
@@ -79,10 +80,8 @@ module.exports = {
         // 'test/tmp_data/test_ration_calc/ration_calc_base.js',
         'lib/pinyinjs/pinyin_dict_firstletter.js',
         'lib/pinyinjs/pinyinUtil.js',
-        'public/common_constants.js',
         'web/building_saas/main/js/models/main_consts.js',
         'web/over_write/config/compilation_config.js',
-        'public/web/common_util.js',
         'public/web/encoding_util.js',
         'web/building_saas/glj/js/project_glj.js',
         'web/building_saas/glj/js/composition.js',

+ 1 - 1
modules/all_models/import_logs.js

@@ -18,7 +18,7 @@ let modelSchema = {
     // 状态
     status:String,
     // 建设项目ID
-    projectID: Number,
+    projectID: Array,
     // 创建时间
     create_time: Number,
     errorMsg:""

+ 4 - 0
modules/all_models/share_list.js

@@ -9,6 +9,10 @@ const Schema = mongoose.Schema;
 const shareSchema = new Schema({
     ID: String,
     projectID: Number,
+    isRead: {
+        type: Boolean,
+        default: false
+    },
     owner: String, // 项目拥有者ID
     receiver: String, // 接收者ID
     allowCopy: {

+ 32 - 0
modules/blank/routes/index.js

@@ -0,0 +1,32 @@
+const { BlankType } = require('../../../public/common_constants');
+
+function getCardHtml(info, btn) {
+    return `<div class="row">
+    <div class="col-lg-8 mx-auto mt-5">
+        <div class="card newuser-input">
+            <div class="card-body">
+                <h4 class="mt-2 mb-5 text-center">${info}</h4>
+                ${btn ? `<div class="form-group text-center"><a class="btn btn-primary" href="${btn.href}">${btn.title}</a></div>` : ''}
+            </div>
+        </div>
+    </div>
+</div>`;
+}
+
+module.exports = function (app) {
+    app.get('/blank', function (req, res) {
+        const { type } = req.query;
+        let html = '';
+        switch (+type) {
+            case BlankType.SHARE_CANCEL:
+                html = getCardHtml('分享设置已被修改,当前项目无权查看。', { href: '/pm', title: '返回项目管理' });
+                break;
+            case BlankType.NOT_FOUND:
+            default:
+                html = getCardHtml('很抱歉,您要访问的页面不存在。');
+                break;
+
+        }
+        res.render('common/components/blank/index.html', { html });
+    });
+}

+ 16 - 11
modules/complementary_glj_lib/models/gljModel.js

@@ -144,29 +144,34 @@ class GljDao {
     }
 
     //获得用户的补充工料机和用户当前所在编办的标准工料机
-    async getGljItems (stdGljLibId, userId, compilationId, projection, callback){
+    async getGljItems(stdGljLibId, userId, compilationId, projection, callback) {
         let me = this;
-        let rst = {stdGljs: [], complementaryGljs: []};
+        let rst = { stdGljs: [], complementaryGljs: [] };
         //批量获取异步
         async.parallel([
             async function (cb) {
-                try{
-                    let stdGljs = await stdGljModel.find({repositoryId: stdGljLibId}, projection).lean();
+                try {
+                    let stdGljs = stdGljLibId ? await stdGljModel.find({ repositoryId: stdGljLibId }, projection).lean() : [];
                     me.sortToNumber(stdGljs);
                     rst.stdGljs = stdGljs;
                     cb(null);
                 }
-                catch (err){
+                catch (err) {
                     cb(err);
                 }
 
             },
             function (cb) {
-                complementaryGljModel.find({userId: userId, compilationId: compilationId}, '-_id', {lean: true}, function (err, complementaryGljs) {
-                    if(err){
+                if (!userId || !compilationId) {
+                    rst.complementaryGljs = [];
+                    cb(null);
+                    return;
+                }
+                complementaryGljModel.find({ userId: userId, compilationId: compilationId }, '-_id', { lean: true }, function (err, complementaryGljs) {
+                    if (err) {
                         cb(err);
                     }
-                    else{
+                    else {
                         me.sortToNumber(complementaryGljs);
                         rst.complementaryGljs = complementaryGljs;
                         cb(null);
@@ -174,15 +179,15 @@ class GljDao {
                 });
             }
         ], function (err) {
-            if(err){
+            if (err) {
                 callback(err, null);
             }
-            else{
+            else {
                 callback(0, rst);
             }
         })
 
-    };
+    }
 
     async getStdItems (stdGljLibId, projection, callback) {
         try {

+ 9 - 0
modules/main/facade/ration_facade.js

@@ -626,6 +626,7 @@ async function getUnitPriceData(newProjectGLJList,gljCodes,unitPriceFileId){
       market_price: np.market_price,
       unit_price_file_id: unitPriceFileId,
       name: np.name,
+      taxRate:np.taxRate,
       specs:np.specs?np.specs:'',
       original_code:np.original_code,
       unit:np.unit?np.unit:'',
@@ -857,6 +858,14 @@ function getProjectGLJNewData(tmp,projectId,ext){
       materialCoe: tmp.materialCoe,
       base_price: tmp.basePrice,
       market_price: tmp.basePrice,
+      is_evaluate:0,
+      is_eval_material:0,
+      no_tax_eqp:0,
+      is_adjust_price:0,
+      is_main_material:0,
+      is_contractor_material:0,
+      supply_quantity:0,
+      supply:0,
       from:tmp.from?tmp.from:"std"
   };
   // 现在定额库可以引用其他费用定额的,比如广东可能套用部颁的定额,因此就算广东费用定额是多单价的,也可能会引用单个单价的人材机

+ 3 - 1
modules/main/models/project_consts.js

@@ -73,7 +73,9 @@ const rationType = {
     ration: 1,
     volumePrice: 2,
     gljRation: 3,
-    install:4
+    install:4,
+    overHeight: 5, // 超高子目
+    itemIncrease: 6//子目增加
 };
 module.exports = {
     projectConst: projectConst,

+ 6 - 4
modules/main/routes/main_route.js

@@ -17,8 +17,8 @@ module.exports =function (app) {
     const baseController = new BaseController();
     app.get('/main', baseController.init, function(req, res) {
         let pm = require('../../pm/controllers/pm_controller');
-
-        pm.checkProjectRight(req.session.sessionUser.id, req.query.project, async function (hasRight, projectData, allowCooperate) {
+        const projectID = +req.query.project;
+        pm.checkProjectRight(req.session.sessionUser.id, projectID, async function (hasRight, projectData, isOpenShareProject, allowCooperate) {
             if (hasRight) {
                 //分享的项目,只读、协作(允许编辑)
                 let projectReadOnly = false,
@@ -30,7 +30,7 @@ module.exports =function (app) {
                     projectReadOnly = !projectCooperate;
                 }
                 let fileKind = '1'; //默认投标文件
-                let constructProject = await pmFacade.getConstructionProject(req.query.project);
+                let constructProject = await pmFacade.getConstructionProject(projectID);
                 if (constructProject && constructProject.property && constructProject.property.fileKind) {
                     fileKind = constructProject.property.fileKind;
                 }
@@ -39,6 +39,7 @@ module.exports =function (app) {
                 if(options){
                     options = await optionsDao.saveOptions(req.session.sessionUser.id, req.session.sessionCompilation._id, optionSetting);
                 }
+                const markReadProjectIDs = isOpenShareProject ? await pmFacade.markShareItemsRead(projectID, req.session.sessionUser.id) : [];
                 res.render('building_saas/main/html/main.html',
                     {
                         userAccount: req.session.userAccount,
@@ -52,7 +53,8 @@ module.exports =function (app) {
                         LicenseKey:config.getLicenseKey(process.env.NODE_ENV),
                         overWriteUrl:req.session.sessionCompilation.overWriteUrl,
                         fileKind: fileKind,
-                        options:JSON.stringify(options)
+                        options:JSON.stringify(options),
+                        markReadProjectIDs: JSON.stringify(markReadProjectIDs)
                     });
             } else {
                 res.redirect('/pm');

+ 16 - 13
modules/pm/controllers/pm_controller.js

@@ -61,13 +61,15 @@ module.exports = {
              * userId(String): Session.userID
              */
             let shareInfo = null;
+            let isOpenShareProject = false;
             //判断是否是打开分享的项目,分享项目shareInfo不为null
             if (userId !== result.userID) {
                 shareInfo = await pm_facade.getShareInfo(userId, result.ID);
+                isOpenShareProject = true;
             }
             if ((userId === result.userID || shareInfo) && result._doc.projType === projType.tender) {
                 const allowCooperate = (shareInfo || {}).allowCooperate;
-                callback(true, result, allowCooperate);
+                callback(true, result, isOpenShareProject, allowCooperate);
             } else {
                 callback(false);
             }
@@ -76,13 +78,9 @@ module.exports = {
         });
     },
     getProjects: async function (req, res) {
-        await ProjectsData.getUserProjects(req.session.sessionUser.id, req.session.sessionCompilation._id, function (err, message, projects) {
+        await ProjectsData.getUserProjects(req.session.sessionUser.id, req.session.sessionCompilation._id, function (err, message, data) {
             console.log(err);
-            if (projects) {
-                callback(req, res, err, message, projects);
-            } else {
-                callback(req, res, err, message, null);
-            }
+            callback(req, res, err, message, data);
         });
     },
     updateProjects: async function (req, res) {
@@ -317,8 +315,7 @@ module.exports = {
     },
     // 项目管理首页
     index: async function (request, response) {
-        // TODO 上线后删除,处理旧的分享数据
-        await pm_facade.prepareShareList();
+        //await pm_facade.prepareShareList();
         // 获取编办信息
         let sessionCompilation = request.session.sessionCompilation;
         if (sessionCompilation === undefined || sessionCompilation === null) {
@@ -347,7 +344,10 @@ module.exports = {
         let overWriteUrl = fs.existsSync(absoluteUrl) && fs.statSync(absoluteUrl).isFile() ? compilationData.overWriteUrl : null;
         //欢迎页显示控制
         let [isShow, context] = await pm_facade.getWelcomeInfo(sessionCompilation._id, request.session.sessionUser, request.session.compilationVersion.includes('免费'));
+        // 未读的分享项目数量
+        const unreadShareList = await pm_facade.getUnreadShareListByCompilation(request.session.sessionUser.id, sessionCompilation._id);
         let renderData = {
+            unreadShareList: JSON.stringify(unreadShareList),
             isFirst: isFirst,
             isShow: isShow,
             context: context,
@@ -636,7 +636,7 @@ module.exports = {
     share: async function (req, res) {
         try {
             const data = JSON.parse(req.body.data);
-            const { type, shareData, projectID, count } = data;
+            const { type, permissionType, shareData, projectID, count } = data;
             const userID = req.session.sessionUser.id;
             const shareDate = moment(Date.now()).format('YYYY-MM-DD HH:mm:ss');
             shareData.forEach(item => item.shareDate = shareDate);
@@ -698,11 +698,14 @@ module.exports = {
             const rstTask = [
                 pm_facade.getRecentShareList(userID, count),
                 userModelObj.getContacts(userID)
-            ]
-            let rst = null;
+            ];
+            // 获取需要广播推送的单位工程
+            // shareData数组的形式是以前需求需要,现在的需求下,shareData数组必定只有一个元素
+            const emitTenders = await pm_facade.getShareInfoAfterChangePermission(permissionType, shareData[0].userID, projectID);
+            let rst = { emitTenders };
             if (!isSimpleUpdate) {
                 const [recentUsers, contacts] = await Promise.all(rstTask);
-                rst = { recentUsers, contacts };
+                Object.assign(rst, { recentUsers, contacts });
             }
             callback(req, res, 0, 'success', rst);
         }

+ 221 - 16
modules/pm/facade/pm_facade.js

@@ -17,8 +17,11 @@ module.exports={
     getShareInfoMap,
     getRecentShareList,
     getProjectShareList,
+    getUnreadShareListByCompilation,
     getShareTip,
     getShareState,
+    markShareItemsRead,
+    getShareInfoAfterChangePermission,
     moveProject:moveProject,
     accessToCopyProject,
     copyProject:copyProject,
@@ -107,7 +110,7 @@ import SectionTreeDao from '../../complementary_ration_lib/models/sectionTreeMod
 let sectionTreeDao = new SectionTreeDao();
 import CounterModel from "../../glj/models/counter_model";
 import moment from 'moment-timezone';
-const { fixedFlag } = require('../../../public/common_constants');
+const { fixedFlag, SharePermissionChangeType } = require('../../../public/common_constants');
 const notDeleted = [{deleteInfo: null}, {'deleteInfo.deleted': false}];
 import {
     defaultDecimal,
@@ -295,6 +298,23 @@ async function getProjectShareList(projectID, limit = null) {
     return users;
 }
 
+// 获取某用户某费用定额的未读的被分享记录
+async function getUnreadShareListByCompilation(userID, compilationID, isGetCount) {
+    const fields = isGetCount ? '-_id projectID ' : '-_id';
+    const list = await shareListModel.find({ receiver: userID, isRead: false }, fields);
+    const idList = list.reduce((acc, cur) => {
+        acc.push(cur.projectID);
+        return acc;
+    }, []);
+    const notDeleted = [{ deleteInfo: null }, { 'deleteInfo.deleted': false }];
+    const query = { ID: { $in: idList }, compilation: compilationID, $or: notDeleted };
+    if (isGetCount) {
+        return await projectModel.count(query)
+    }
+    const items = await projectModel.find(query, '-_id ID').lean();
+    return items.map(item => item.ID);
+}
+
 // 获取分享的提示(造价书分享按钮tooltip使用)
 async function getShareTip(projectID, limit) {
     const task = [
@@ -329,6 +349,96 @@ async function getShareState(projectID, receiver) {
     };
 }
 
+// 自身及父链上的分享数据(链距离最近的一个)
+async function getUpChainShareData(projectID, receiver) {
+    while (projectID != -1) {
+        const shareData = await shareListModel.findOne({ projectID, receiver }).lean();
+        if (shareData) {
+            return shareData;
+        }
+        const project = await projectModel.findOne({ ID: projectID }, '-_id ParentID').lean();
+        projectID = project ? project.ParentID : -1;
+    }
+    return null;
+}
+
+// 标记已读分享条目
+// 打开任意的单位工程,都会将其父分享条目标记和自身为已读 
+// eg:被分享了一个建设项目,和其下一个单位工程,打开该一个单位工程,建设项目标记为已读、该单位工程也标记为已读
+async function markShareItemsRead(projectID, userID) {
+    const upChainIDs = await getUpChainIDs(projectID);
+    const unreadList = await shareListModel.find({ projectID: { $in: upChainIDs }, receiver: userID, isRead: false }, '-_id ID projectID').lean();
+    const markReadProjectIDs = [];
+    const bulks = [];
+    unreadList.forEach(item => {
+        markReadProjectIDs.push(item.projectID);
+        bulks.push({
+            updateOne: {
+                filter: { ID: item.ID },
+                update: { isRead: true }
+            }
+        });
+    });
+    if (bulks.length) {
+        await shareListModel.bulkWrite(bulks);
+    }
+    return markReadProjectIDs;
+}
+
+/**
+ * 分享权限变更后,根据变更操作,获取相关项目的分享权限信息
+ * 不能简单根据某个项目的分享信息获得其权限,因为目前的分享可以重复分享(分享单位工程A、分享包含单位工程A的父项) 
+ * 最新的分享权限是由项目链上(所有子项、自身、父项...)最新的分享信息决定的
+ * @param {Number} permissionType - 分享权限变更类型
+ * @param {String} receiver - 用户ID
+ * @param {Number} projectID - 项目ID
+ */
+async function getShareInfoAfterChangePermission(permissionType, receiver, projectID) {
+    const project = await projectModel.findOne({ ID: projectID }, '-_id ID projType').lean();
+    if (!project) {
+        return [];
+    }
+    // UPDATE_COPY不触发推送给主页面的消息
+    const propChange = [SharePermissionChangeType.UPDATE_COOPERATE].includes(permissionType);
+    const cancelShare = permissionType === SharePermissionChangeType.CANCEL;
+    // 需要处理的单位工程
+    if (propChange && project.projType === projectType.tender) {
+        const shareData = await shareListModel.findOne({ projectID, receiver }, '-_id allowCooperate allowCopy updateDate').lean();
+        const tender = { ID: projectID };
+        if (shareData) {
+            Object.assign(tender, shareData);
+        }
+        return [tender];
+    }
+    if (propChange && project.projType !== projectType.tender) {
+        const projects = await getPosterityProjects([projectID], false, '-_id ID projType');
+        const tenders = projects.filter(p => p.projType === projectType.tender);
+        const shareData = await shareListModel.findOne({ projectID, receiver }, '-_id allowCooperate allowCopy updateDate').lean();
+        if (shareData) {
+            tenders.forEach(tender => Object.assign(tender, shareData));
+        }
+        return tenders
+    }
+    if (cancelShare && project.projType === projectType.tender) {
+        const shareData = await getUpChainShareData(projectID, receiver);
+        return shareData ? [] : [{ ID: projectID }];
+    }
+    if (cancelShare && project.projType !== projectType.tender) {
+        const projects = await getPosterityProjects([projectID], false, '-_id ID projType');
+        const tenders = [];
+        for (const p of projects) {
+            if (p.projType === projectType.tender) {
+                const shareData = await getUpChainShareData(p.ID, receiver);
+                if (!shareData) {
+                    tenders.push({ ID: p.ID });
+                }
+            }
+        }
+        return tenders;
+    }
+    return [];
+}
+
 //拷贝例题项目
 //@param {String}userID {Array}projIDs拷贝的例题项目ID(建设项目、文件夹)@return {Boolean}
 async function copyExample(userID, compilation, projIDs){
@@ -368,9 +478,14 @@ async function copyExample(userID, compilation, projIDs){
     let newDate = new Date(),
         parentBulks = [];
     const projectMaps = [];
+    // 传入项目ID的新ID
+    const newProjIDs = [];
     for (let data of allProjs) {
         let orgID = data.ID;
         data.ID = IDMapping[data.ID];
+        if (projIDs.indexOf(orgID) >= 0) {
+            newProjIDs.push(data.ID);
+        }
         data.ParentID = IDMapping[data.ParentID] ? IDMapping[data.ParentID] : -1;
         data.NextSiblingID = IDMapping[data.NextSiblingID] ? IDMapping[data.NextSiblingID] : -1;
         data.createDateTime = newDate;
@@ -403,7 +518,7 @@ async function copyExample(userID, compilation, projIDs){
     // 处理项目数据
     const projectTasks = [projectModel.bulkWrite(parentBulks), createProject(projectMaps)];
     await Promise.all(projectTasks);
-    return true;
+    return newProjIDs;
 }
 
 async function accessToCopyProject(userID, compilationID, data, newProjectID) {
@@ -424,7 +539,7 @@ async function handleCopyProject(key, userID, compilationID, data, newProjectID)
     const doc = { status: 'finish' };
     try {
         const projectMap = await copyProject(userID, compilationID, data, newProjectID);
-        doc.projectID = projectMap.copy.document.ID;
+        doc.projectID = [projectMap.copy.document.ID];
     } catch (err) {
         doc.status = 'error';
         doc.errorMsg = String(err);
@@ -1477,10 +1592,10 @@ async function getUpChainIDs(projectID) {
 }
 
 //获取projectIDs文件下所有子项目(默认不包括projectIDs本身)
-async function getPosterityProjects(projectIDs, includeSelf = false) {
+async function getPosterityProjects(projectIDs, includeSelf = false, fields = '-_id') {
     let rst = [];
     if (includeSelf) {
-        const projects = await projectModel.find({ID: {$in: projectIDs}, $or: notDeleted}, '-_id').lean();
+        const projects = await projectModel.find({ID: {$in: projectIDs}, $or: notDeleted}, fields).lean();
         if (projects) {
             rst.push(...projects);
         }
@@ -1488,7 +1603,7 @@ async function getPosterityProjects(projectIDs, includeSelf = false) {
     async function getProjects(parentIDs) {
         if (parentIDs.length > 0) {
             let newIDs = [];
-            let projs = await projectModel.find({ParentID: {$in: parentIDs}, $or: notDeleted}, '-_id').lean();
+            let projs = await projectModel.find({ParentID: {$in: parentIDs}, $or: notDeleted}, fields).lean();
             for (let proj of projs) {
                 // 兼容旧的错误数据,可能ParentID和ID相同
                 if (parentIDs.includes(proj.ID)) {
@@ -1554,7 +1669,6 @@ async function importChongqingProject(data) {
     doImport(data.user_id,data.session.sessionCompilation._id,data.session.sessionCompilation.adProjects,data.key);
 
      return "start importing";
-
     async function doImport(user_id,compilationId,projectIDs,key) {
         let doc = {status:"finish"};
         try {
@@ -1562,6 +1676,8 @@ async function importChongqingProject(data) {
            if(r == false){
                doc.errorMsg = "导入失败,请检查项目是否存在!";
                doc.status = "error";
+           } else {
+               doc.projectID = r;
            }
         }catch (error){
             console.log(error);
@@ -1796,8 +1912,15 @@ async function getProjectPlaceholder(data) {
 /*
 * 接口导入 项目详细数据都导入完成了,再生成项目数据(项目管理界面数据)
 * */
-async function importProject(importObj, userID, compilationID) {
+async function importProject(importObj, userID, compilationID, overWriteUrl) {
     let toInsertProjects = [importObj];  //待新增项目数据
+    let billsCalcMode = 0;
+    if (overWriteUrl) {
+        const overWrite = require('../../..' + overWriteUrl);
+        if(overWrite.getBillsCalcMode) { // 重写清单计费取费方式
+            billsCalcMode = overWrite.getBillsCalcMode();
+        }
+    }
     await setupProject(importObj);
     //设置项目ID及相关数据
     for (let curEng of importObj.engs) {
@@ -1904,7 +2027,7 @@ async function importProject(importObj, userID, compilationID) {
         //呈现选项
         data.property.displaySetting = displaySetting;
 
-        data.property.billsCalcMode = 0;
+        data.property.billsCalcMode = billsCalcMode;
         data.property.zanguCalcMode = 0;
         //计算选项
         data.property.calcOptions = calcOptions;
@@ -1934,10 +2057,13 @@ async function importProject(importObj, userID, compilationID) {
                 }
             });
             featureLib.feature.push(...unmatched);
-            //设置工程专业的值为费用标准的值..
             featureLib.feature.forEach(f => {
+                //设置工程专业的值为费用标准的值..
                 if (f.key === 'engineering') {
                     f.value = data.property.feeStandardName;
+                } else if (f.key === 'projType' && !f.value) {
+                    const options = f.options && f.options.split('@') || [];
+                    f.value = options[0] || '';
                 }
             });
         }
@@ -2055,8 +2181,15 @@ async function setupStdData(tenderData) {
     //标准人材机code - 映射
     let stdGLJCodeMap = {};
     stdGLJs.forEach(stdGLJ => stdGLJCodeMap[stdGLJ.code] = stdGLJ);
+    // 取费ID-计算程序子目映射
+    const programIDCalculationMap = {};
     //更新定额数据
+    const toSetFakeProgramIDData = [];
     tenderData.ration.forEach(r => {
+        if (r.setFakeProgramID) {
+            toSetFakeProgramIDData.push(r);
+            delete r.setFakeProgramID;
+        }
         let stdRation = stdRationCodeMap[r.code];
         if (stdRation) {
             r.caption = stdRation.caption;
@@ -2076,7 +2209,70 @@ async function setupStdData(tenderData) {
             r.from = 'cpt';
             r.prefix = '补';
         }
+        if (r.calculationItems && r.calculationItems.length > 0 && !programIDCalculationMap[r.programID]) {
+            programIDCalculationMap[r.programID] = r.calculationItems;
+        }
+        delete r.calculationItems;
     });
+    // 更新计算程序
+    const newTemplates = [];
+    const calcFileID = tenderData.tender.property.calcProgramFile.ID;
+    let calcProgramFile = null;
+    if (Object.keys(programIDCalculationMap).length > 0) {
+        calcProgramFile = await calcProgramsModel.findOne({ ID: calcFileID }).lean();
+        if (calcProgramFile) {
+            for (const template of calcProgramFile.templates) {
+                const matched = programIDCalculationMap[template.ID];
+                if (!matched) {
+                    newTemplates.push(template);
+                    continue;
+                }
+                for (const newCalcItem of matched) {
+                    for (const oldCalcItem of template.calcItems) {
+                        if (newCalcItem.fieldName === oldCalcItem.fieldName &&
+                            !(newCalcItem.feeRate === 100 && !oldCalcItem.feeRateID && !oldCalcItem.feeRate)) {
+                                oldCalcItem.feeRate = newCalcItem.feeRate;
+                                oldCalcItem.feeRateID = null; // feeRateID优先级比较高,不赋为空的话为被覆盖
+                        }
+                    }
+                }
+                newTemplates.push(template);
+            }
+        }
+    }
+    // 增加假的计算程序
+    if (toSetFakeProgramIDData.length) {
+        if (!calcProgramFile) {
+            calcProgramFile = await calcProgramsModel.findOne({ ID: calcFileID }).lean();
+        }
+        if (calcProgramFile) {
+            const programIDTemplateMap = {};
+            const programIDNewTemplateMap = {};
+            calcProgramFile.templates.forEach(template => programIDTemplateMap[template.ID] = template);
+            let curID = calcProgramFile.templates.length + 1;
+            toSetFakeProgramIDData.forEach(r => {
+                const matchedNewTempate = programIDNewTemplateMap[r.programID];
+                if (matchedNewTempate) {
+                    r.programID = matchedNewTempate.ID;
+                    return;
+                }
+                const matched = programIDTemplateMap[r.programID];
+                if (matched) {
+                    const newData = {
+                        ID: curID++,
+                        name: `${matched.name}_1`,
+                        calcItems: []
+                    };
+                    newTemplates.push(newData);
+                    programIDNewTemplateMap[r.programID] = newData;
+                    r.programID = newData.ID;
+                }
+            });
+        }
+    }
+    if (newTemplates.length) {
+        await calcProgramsModel.update({ ID: calcFileID }, { $set: { templates: newTemplates } });
+    }
     //更新定额人材机数据
     tenderData.rationGLJ.forEach(rGLJ => {
         rGLJ.from = 'cpt';
@@ -2134,7 +2330,7 @@ async function setupStdData(tenderData) {
     });
     //更新组成物数据
     tenderData.mixRatio.forEach(ratio => {
-        let stdGLJ = stdGLJCodeMap[ratio.code];
+        let stdGLJ = stdGLJCodeMap[ratio.code] || stdGLJCodeMap[ratio.original_code];
         if (stdGLJ) {
             ratio.glj_id = stdGLJ.ID;
             ratio.type = stdGLJ.gljType;
@@ -2253,7 +2449,7 @@ async function doDownLoadAndImport(privateDownloadUrl,info) {
                 doc.errorMsg = result.msg;
                 doc.status = "error";
             } else {
-                doc.projectID = result.constructionProjectID;
+                doc.projectID = [result.constructionProjectID];
             }
         }catch (error){
             console.log(error);
@@ -2275,14 +2471,22 @@ async function importProcessChecking(data){
             result.status = "complete";
             await importLogsModel.remove(query);
             // 获取导入的项目数据
-            if (log.projectID) {
+            if (log.projectID && log.projectID.length > 0) {
+                const projects = await getPosterityProjects(log.projectID, true);
+                if (!(projects.length === 1 && projects[0].projType === projectType.tender)) { // 处理的是建设项目数据
+                    const summaryInfo = await getSummaryInfo(log.projectID);
+                    setupSummaryFields(summaryInfo, projects);
+                }
+                result.data = projects;
+            }
+            /* if (log.projectID && log.projectID.length > 0) {
                 const projects = await getPosterityProjects([log.projectID], true);
                 if (!(projects.length === 1 && projects[0].projType === projectType.tender)) { // 处理的是建设项目数据
                     const summaryInfo = await getSummaryInfo([log.projectID]);
                     setupSummaryFields(summaryInfo, projects);
                 }
                 result.data = projects;
-            }
+            } */
         }else if(log.status == "start"){
             result.status = "processing";
             result.content = log.content;
@@ -2358,8 +2562,9 @@ async function handleImportInterface(key, session) {
         if (await isTenderOverrun(tenderCount, session)) {
             throw '您创建的项目个数超限,请联系我们的客服人员,或者导出建设项目保存到本地备份,删除云上数据。';
         }
-        const projectID = await importProject(importData, userID, compilationID);
-        doc.projectID = projectID;
+        const overWriteUrl = session.sessionCompilation.overWriteUrl;
+        const projectID = await importProject(importData, userID, compilationID, overWriteUrl);
+        doc.projectID = [projectID];
     } catch (err) {
         doc.errorMsg = typeof err === 'string' ? err : '导入接口失败,请检查接口文件!';
         doc.status = 'error';

+ 2 - 0
modules/pm/models/project_model.js

@@ -81,6 +81,8 @@ ProjectsDAO.prototype.getUserProjects = async function (userId, compilation, cal
         projects.forEach(project => {
             project.shareInfo = shareMap[project.ID] || [];
         });
+        // 当前费用定额未读的分享的条目数量
+        
         // 设置汇总字段
         let summaryInfo = await pmFacade.getSummaryInfo(projIDs);
         pmFacade.setupSummaryFields(summaryInfo, projects);

+ 54 - 2
modules/ration_glj/controllers/ration_glj_controller.js

@@ -5,7 +5,7 @@ let mongoose = require("mongoose")
 let ration_glj_facade = require('../facade/ration_glj_facade')
 import EngineeringLibModel from "../../users/models/engineering_lib_model";
 let logger = require("../../../logs/log_helper").logger;
-const { COMPLEMENTARY_LIB } = require('../../../public/common_constants');
+const { COMPILATION, COMPLEMENTARY_LIB } = require('../../../public/common_constants');
 
 module.exports={
     createRationGLJ:createRationGLJ,
@@ -102,7 +102,7 @@ async function getGLJDataPaging(req, res) {
     }
 }
 
-async function getGLJData(req, res) {
+/* async function getGLJData(req, res) {
     let result={
         error:0
     }
@@ -125,6 +125,58 @@ async function getGLJData(req, res) {
         result.message = err.message;
         res.json(result);
     }
+} */
+async function getGLJData(req, res) {
+    let result={
+        error:0
+    }
+    try {
+        let libData = null;
+        let { engineerID, gljLibId, isInitial } = req.params;
+        if (gljLibId !== COMPLEMENTARY_LIB) {
+            gljLibId = +gljLibId;
+        }
+        isInitial = JSON.parse(isInitial);
+        if (!gljLibId || isInitial) { // 替换人材机的话,可能存在gljLibID,但是是初始化的操作
+            libData = engineerID === COMPILATION 
+            ? await ration_glj_facade.getLibOptionsForCompilation(req.session.sessionCompilation._id)
+            : await ration_glj_facade.getLibOptions(engineerID);
+            libData.push({ name: '补充工料机', gljLibId: COMPLEMENTARY_LIB });
+            if (gljLibId) { // 替换人材机初始化会触发此条件
+                const orgDefalutLib = libData.find(lib => lib.isDefault);
+                const newDefaultLib = libData.find(lib => lib.gljLibId === gljLibId);
+                if (orgDefalutLib && newDefaultLib) {
+                    orgDefalutLib.isDefault = false;
+                    newDefaultLib.isDefault = true;
+                }
+            }
+        }
+        let skipGLJ = gljLibId === 0;
+        if (!gljLibId && libData) {
+            gljLibId = (libData.find(lib => lib.isDefault) || {}).gljLibId;
+        }
+        const info = gljLibId === COMPLEMENTARY_LIB 
+            ? { gljLibId: null, userID: req.session.sessionUser.id, compilationId: req.session.sessionCompilation._id }
+            : { gljLibId, userID: null, compilationId: null };
+        info.skipGLJ = skipGLJ;
+        ration_glj_facade.getGLJData(info,function (err,datas) {
+            if(err){
+                result.error=1;
+                result.message = err.message;
+            }else {
+                //多单价字段
+                if(req.session.sessionCompilation.priceProperties) datas.priceProperties = req.session.sessionCompilation.priceProperties;
+                result.datas = datas;
+                result.datas.libData = libData;
+            }
+            res.json(result);
+        });
+    }catch (err){
+        logger.err(err);
+        result.error=1;
+        result.message = err.message;
+        res.json(result);
+    }
 }
 
 async function addGLJ(req, res){

+ 14 - 0
modules/ration_glj/facade/ration_glj_facade.js

@@ -640,6 +640,12 @@ function getGLJData(info, callback) {
             }
         },
         function (cb) {
+            if (info.skipGLJ) {
+                datas.stdGLJ = [];
+                datas.complementaryGLJs = [];
+                cb(null);
+                return;
+            }
             gljDao.getGljItems(info.gljLibId, info.userID, info.compilationId, {_id: 0}, function (err, data) {
                 if (err) {
                     cb(err);
@@ -683,6 +689,14 @@ function getGLJSearchInfo(ration_glj) {
         materialCoe:ration_glj.materialCoe,
         materialIndexType:ration_glj.materialIndexType,
         materialIndexUnit:ration_glj.materialIndexUnit,
+        is_evaluate:ration_glj.isEstimate?ration_glj.isEstimate:0,
+        is_eval_material:0,
+        no_tax_eqp:0,
+        is_adjust_price:0,
+        is_main_material:0,
+        is_contractor_material:0,
+        supply_quantity:0,
+        supply:0,
         materialIndexCoe:ration_glj.materialIndexCoe,
         from: ration_glj.from ? ration_glj.from : 'std'//std:标准工料机库, cpt:补充工料机库
     };

+ 1 - 1
modules/ration_glj/routes/ration_glj_route.js

@@ -8,7 +8,7 @@ let rgController = require('../controllers/ration_glj_controller');
 module.exports = function (app) {
 
     var rgRouter = express.Router();
-    rgRouter.get('/getGLJData/:engineerID', rgController.getGLJData);
+    rgRouter.get('/getGLJData/:engineerID/:gljLibId/:isInitial', rgController.getGLJData);
     rgRouter.post('/getGLJDataPaging', rgController.getGLJDataPaging);
     rgRouter.post('/getGLJDataByCodes',rgController.getGLJDataByCodes);
     rgRouter.post('/addGLJ',rgController.addGLJ);

+ 32 - 0
public/common_constants.js

@@ -122,6 +122,8 @@
         BFJG: 1,
         //完全甲供
         WQJG: 2,
+        // 甲供材料 (“完全甲供”改文字为“甲供材料”)
+        JGCL: 2,
         //甲定乙供
         JDYG: 3
     };
@@ -147,6 +149,32 @@
     // 费用定额
     const COMPILATION = 'compilation';
 
+    // 缓存key
+    const StorageKey = {
+        // 主界面一旦出现这个缓存,马上提示
+        ONCE_MAIN_LOADED: 'onceMainLoaded'
+    };
+    
+    // 分享权限变更的类型
+    const SharePermissionChangeType = {
+        UPDATE_COOPERATE: 1,
+        UPDATE_COPY: 2,
+        CANCEL: 3,
+        SHARE: 4,
+        READ: 5
+    };
+
+    // 页面目标
+    const PageTarget = {
+        PM: 1, // 项目管理
+        MAIN: 2 // 造价书
+    };
+
+    const BlankType = {
+        NOT_FOUND: 1,
+        SHARE_CANCEL: 2,
+    };
+
     return {
         fixedFlag,
         billType,
@@ -159,5 +187,9 @@
         DEFAULT_REGION,
         COMPILATION,
         COMPLEMENTARY_LIB,
+        StorageKey,
+        SharePermissionChangeType,
+        PageTarget,
+        BlankType
     };
 });

+ 91 - 86
public/web/socket/connection.js

@@ -2,95 +2,100 @@
  * Created by zhangweicheng on 2017/8/7.
  */
 socketObject = {
-  roomInfo: null,
-  messages: [],
-  connect: function (from, payload) {
-      // 连接socket服务器
-      var hostName = window.location.hostname;
-      let me = this;
-      let port = window.location.protocol === 'http:' ? 3300 : 3301;
-      socket = io(window.location.protocol + '//' + hostName + ':' + port);
-      socket.on('connect', function () {
-          if (from == 'pm') {
-              me.roomInfo = {
-                  userID: userID
-              };
-        }else if(from == 'unitPrice'){
-          me.roomInfo = {
-            unitFile:unitPriceFileID
-          }
-        } else {
-            me.roomInfo={
-                feeRate:me.getFeeRateRoomID(),
-                unitFile:me.getUnitFileRoomID()
+    roomInfo: null,
+    messages: [],
+    connect: function (from, payload) {
+        // 连接socket服务器
+        var hostName = window.location.hostname;
+        let me = this;
+        let port = window.location.protocol === 'http:' ? 3300 : 3301;
+        socket = io(window.location.protocol + '//' + hostName + ':' + port);
+        socket.on('connect', function () {
+            if (from == 'pm') {
+                me.roomInfo = {
+                    compilationUser: `${userID}@${compilationData._id}`,
+                    userID: userID
+                };
+            } else if (from == 'unitPrice') {
+                me.roomInfo = {
+                    unitFile: unitPriceFileID
+                }
+            } else {
+                me.roomInfo = {
+                    feeRate: me.getFeeRateRoomID(),
+                    unitFile: me.getUnitFileRoomID(),
+                    userIDProjectID: `${userID}@${projectObj.project.ID()}`
+                };
+                if (payload && payload.projectReadOnly === false) {
+                    me.roomInfo.projectID = `projectID${projectObj.project.ID()}`;
+                }
+            }
+            const emitData = {
+                roomData: me.roomInfo,
+                payload
             };
-              if (payload && payload.projectReadOnly === false) {
-                  me.roomInfo.projectID = `projectID${projectObj.project.ID()}`;
-              }
-          }
-          const emitData = {
-              roomData: me.roomInfo,
-              payload
-          };
-          socket.emit('join', emitData);
-          if (me.messages.length > 0) {//发送缓存消息;
-              for (let m of me.messages) {
-                  socket.emit(m.message, m.data);
-              }
-          }
-          console.log('连接成功');
-      });
+            socket.emit('join', emitData);
+            if (me.messages.length > 0) {//发送缓存消息;
+                for (let m of me.messages) {
+                    socket.emit(m.message, m.data);
+                }
+            }
+            if (typeof markReadProjectIDs !== 'undefined' && markReadProjectIDs.length) {
+                SHARE_TO.emitPermissionChange(commonConstants.SharePermissionChangeType.READ, userID, null, [], { markReadProjectIDs });
+            }
+            console.log('连接成功');
+        });
 
-      //=========================================================
-      //造价书页面接收消息部分
-      socket.on('feeRateChange', function (data) {
-          //data = JSON.parse(data);
+        //=========================================================
+        //造价书页面接收消息部分
+        socket.on('feeRateChange', function (data) {
+            //data = JSON.parse(data);
 
-          $("#message").html('费率文件已被修改,<a href="javascript:void(0);" id="load-data" onclick="window.location.reload()">点击加载并重新进行造价计算</a>');
-          $("#notify").show();
-          //alert('费率文件已经修改,请刷新页面');
-          //window.location.reload();
-      });
-      socket.on('unitFileChange', function (data) {
-          /*console.log(data);
-          if (data.newValue === undefined) {
-              return false;
-          }*/
-          $("#message").html('市场单价已被修改,<a href="javascript:void(0);" id="load-data" onclick="window.location.reload()">点击加载并重新进行造价计算</a>');
-          $("#notify").show();
-      });
-      socket.on('changeFileNotify', function (data) {//收到文件改变的消息
-          if (data.projectID == projectObj.project.ID()) {//如果是同个项目,则给出提示,否则忽略
-              let preString = "";
-              if (data.name == 'feeRate') {
-                  preString = "费率文件";
-              }
-              if (data.name == 'unitFile') {
-                  preString = "单价文件";
-              }
-              $("#message").html(preString + '已被修改,<a href="javascript:void(0);" id="load-data" onclick="window.location.reload()">点击加载并重新进行造价计算</a>');
-              $("#notify").show();
-          }
-      });
-      socket.on('handleAvatarList', function ({ editingUsers }) {
-          projectInfoObj.handleAvatarList(editingUsers);
-      });
+            $("#message").html('费率文件已被修改,<a href="javascript:void(0);" id="load-data" onclick="window.location.reload()">点击加载并重新进行造价计算</a>');
+            $("#notify").show();
+            //alert('费率文件已经修改,请刷新页面');
+            //window.location.reload();
+        });
+        socket.on('unitFileChange', function (data) {
+            /*console.log(data);
+            if (data.newValue === undefined) {
+                return false;
+            }*/
+            $("#message").html('市场单价已被修改,<a href="javascript:void(0);" id="load-data" onclick="window.location.reload()">点击加载并重新进行造价计算</a>');
+            $("#notify").show();
+        });
+        socket.on('changeFileNotify', function (data) {//收到文件改变的消息
+            if (data.projectID == projectObj.project.ID()) {//如果是同个项目,则给出提示,否则忽略
+                let preString = "";
+                if (data.name == 'feeRate') {
+                    preString = "费率文件";
+                }
+                if (data.name == 'unitFile') {
+                    preString = "单价文件";
+                }
+                $("#message").html(preString + '已被修改,<a href="javascript:void(0);" id="load-data" onclick="window.location.reload()">点击加载并重新进行造价计算</a>');
+                $("#notify").show();
+            }
+        });
+        socket.on('handleAvatarList', function ({ editingUsers }) {
+            projectInfoObj.handleAvatarList(editingUsers);
+        });
+        SHARE_TO.permissionChangeListener();
 
+        //=============================================================================================
+        //项目管理页面接收消息部分
 
-      //=============================================================================================
-      //项目管理页面接收消息部分
-
-      socket.on('refreshProjectIcon', function (data) {//收到刷新图标消息
-          if (data.projectID && typeof projTreeObj !== 'undefined') projTreeObj.refreshProjectIcon(data.projectID);
-      });
-      socket.on('fileDataChange', function (data) {//收到单价文件、费率文件内容修改、文件切换、另存(暂时能共用,以后有需要可分离)推送消息
-          if (data.projectID && typeof projTreeObj !== 'undefined') projTreeObj.refreshWhenFileDateChange(data.projectID);
-      });
-  },
-  getFeeRateRoomID: function () {
-      return projectObj.project.FeeRate.getActivateFeeRateFileID();
-  },
-  getUnitFileRoomID: function () {
-      return projectObj.project.projectGLJ.datas.constData.roomId ? projectObj.project.projectGLJ.datas.constData.roomId : roomId;
-  }
+        socket.on('refreshProjectIcon', function (data) {//收到刷新图标消息
+            if (data.projectID && typeof projTreeObj !== 'undefined') projTreeObj.refreshProjectIcon(data.projectID);
+        });
+        socket.on('fileDataChange', function (data) {//收到单价文件、费率文件内容修改、文件切换、另存(暂时能共用,以后有需要可分离)推送消息
+            if (data.projectID && typeof projTreeObj !== 'undefined') projTreeObj.refreshWhenFileDateChange(data.projectID);
+        });
+    },
+    getFeeRateRoomID: function () {
+        return projectObj.project.FeeRate.getActivateFeeRateFileID();
+    },
+    getUnitFileRoomID: function () {
+        return projectObj.project.projectGLJ.datas.constData.roomId ? projectObj.project.projectGLJ.datas.constData.roomId : roomId;
+    }
 }

+ 22 - 0
socket.js

@@ -6,6 +6,8 @@
  * @version
  */
 import socket from "socket.io";
+const moment = require('moment');
+const { PageTarget } = require('./public/common_constants');
 
 const socketIO = socket(3300);
 
@@ -100,6 +102,26 @@ socketIO.on('connection', function(socket) {
         }
     });
 
+    // 分享权限变更
+    socket.on('sharePermissionChange', async function ({ permissionType, userID, compilationID, projectID, emitTenders, payload }) {
+        const emitPayload = { projectID };
+        if (payload) {
+            if (payload.prop) {
+                payload.prop.updateDate = moment(Date.now()).format('YYYY-MM-DD HH:mm:ss');
+            }
+            Object.assign(emitPayload, payload);
+        }
+        // 推送给项目管理页面
+        socket.broadcast.to(`${userID}@${compilationID}`).emit('sharePermissionChange', { permissionType, target: PageTarget.PM, payload: emitPayload });
+        // 推送给主页面
+        for (const tender of emitTenders) {
+            const emitPayload = {};
+            if (typeof tender.allowCooperate !== 'undefined') {
+                emitPayload.allowCooperate = tender.allowCooperate;
+            }
+            socket.broadcast.to(`${userID}@${tender.ID}`).emit('sharePermissionChange', { permissionType, target: PageTarget.MAIN, payload: emitPayload });
+        }
+    });
 
     socket.on('disconnect', function () {
         // 由于用户可以重复打开项目,因此不做唯一性处理,只删除一个数据,不删除所有的同用户数据

+ 10 - 2
web/building_saas/complementary_ration_lib/html/dinge.html

@@ -18,6 +18,10 @@
     <link rel="shortcut icon" href="/web/building_saas/css/favicon.ico">
     <link rel="icon" type="image/gif" href="/web/building_saas/css/animated_favicon1.gif">
     <style type="text/css">
+        .form-control-inline {
+            display: inline-block !important;
+            width: 80% !important;
+        }
         div.resize-y{
             height: 5px;
             background: #efefef;
@@ -521,8 +525,12 @@
             <div class="modal-body" style="padding-left: 0; padding-right: 3px; margin-left: 0;">
                 <div style="width: 36%; float: left;">
                     &nbsp;
-                    <input type="radio" class="glj-radio" name="glj" value="stdGljs">标准&nbsp;&nbsp;
-                    <input type="radio" class="glj-radio" name="glj" value="complementaryGljs">补充&nbsp;&nbsp;
+                    <select class="form-control  form-control-sm form-control-inline" id="glj-lib-select">
+                        <option value="1">1</option>
+                        <option value="2">2</option>
+                    </select>
+                    <!-- <input type="radio" class="glj-radio" name="glj" value="stdGljs">标准&nbsp;&nbsp;
+                    <input type="radio" class="glj-radio" name="glj" value="complementaryGljs">补充&nbsp;&nbsp; -->
                     <div  class="modal-auto-height" id="gljSelTreeDiv" style="height: 435px; overflow: hidden">
                         <div style="width: 100%; height: 100%; overflow: auto">
                             <ul id="selGljTree" class="ztree"></ul>

+ 82 - 29
web/building_saas/complementary_ration_lib/js/gljSelect.js

@@ -3,10 +3,10 @@
  */
 
 let gljSelOprObj = {
+    distTypeTree: null,
     parentNodeIds: {},
     treeObj:null,
     rootNode: null,//分类树根节点
-    radiosSelected: null,//allGljs, stdGljs, complementaryGljs
     workBook: null,
     selectedList: [],//选中的工料机
     setting: {
@@ -39,7 +39,7 @@ let gljSelOprObj = {
             delete glj.ID;
         }
     },
-    getSelGljItems: function(gljData) {
+    /* getSelGljItems: function(gljData) {
         this.stdGljList = gljData.stdGljs;
         //兼容多单价,计算补充定额价格时,套多单价人材机的时候,默认取第一个价格
         for(let sGlj of this.stdGljList){
@@ -61,7 +61,7 @@ let gljSelOprObj = {
                 glj.basePrice = glj.priceProperty && glj.priceProperty[priceField] ? glj.priceProperty[priceField] : 0;
             }
         }
-    },
+    }, */
     initClassTree: function (type, treeData) {
         let me = this;
         if (me.treeObj) {
@@ -81,11 +81,11 @@ let gljSelOprObj = {
             }
         }
     },
-    getGljClassTree: function (mixedTreeData) {
+    /* getGljClassTree: function (mixedTreeData) {
         this.treeData = mixedTreeData;
         this.initClassTree('std', this.treeData.std);
         gljSelOprObj.buildSheet($('#gljSelSheet')[0]);
-    },
+    }, */
     buildSheet: function (container) {
         let me = gljSelOprObj;
         me.workBook = sheetCommonObj.buildSheet(container, me.setting, 30);
@@ -96,7 +96,69 @@ let gljSelOprObj = {
         me.workBook.getSheet(0).bind(GC.Spread.Sheets.Events.ClipboardPasting, me.onClipboardPasting);
         me.workBook.bind(GC.Spread.Sheets.Events.ButtonClicked, me.onButtonClicked);//复选框点击事件
         me.bindBtnOpr($('#gljSelY'));
-        me.radiosChange();
+    },
+    setGLJItems: function(stdGLJs, complementaryGLJs) {
+        this.stdGljList = stdGLJs;
+        //兼容多单价,计算补充定额价格时,套多单价人材机的时候,默认取第一个价格
+        for(let sGlj of this.stdGljList){
+            if(sGlj.priceProperty && typeof sGlj.priceProperty.price1 !== 'undefined'){
+                sGlj.basePrice = sGlj.priceProperty.price1;
+            }
+        }
+        this.complementaryGljList = complementaryGLJs;
+        this.switchToGljId(this.stdGljList);
+        this.switchToGljId(this.complementaryGljList);
+        this.setProp('type', 'std', this.stdGljList);
+        this.setProp('type', 'complementary', this.complementaryGljList);
+        this.sortGlj(this.stdGljList);
+        this.sortGlj(this.complementaryGljList);
+    },
+    initLibOptions: function (libData) {
+        const html = libData.reduce((acc, lib) => acc += `<option ${lib.isDefault ? 'selected="selected"' : ''} value="${lib.gljLibId}">${lib.name}</option>`, '');
+        $('#glj-lib-select').html(html);
+    },
+    // TODO 改成分页的形式
+    // 初始化选择页面
+    initView: async function (gljLibId, isInitial) {
+        try {
+            $.bootstrapLoading.start();
+            let { libData, treeData, distTypeTree, stdGLJ, complementaryGLJs } = await this.getViewData(gljLibId);
+            if (isInitial) {
+                this.initLibOptions(libData);
+                $('#gljSearchKeyword').val('');
+                setTimeout(() => $('#selGlj').modal('show'), 200);
+            }
+            if (!this.workBook) {
+                this.buildSheet($('#gljSelSheet')[0]);
+            }
+            this.distTypeTree = distTypeTree;
+            if (gljLibId === 0) {
+                stdGLJ = pageOprObj.mixedGLJData.stdGljs;
+                complementaryGLJs = pageOprObj.mixedGLJData.complementaryGljs;
+            }
+            this.setGLJItems(stdGLJ, complementaryGLJs);
+            this.selectedList = [].concat(rationGLJOprObj.cache['_GLJ_' + rationGLJOprObj.currentRationItem.ID]);
+            this.showGljList = [];
+            this.setShowGljList(this.stdGljList, true);
+            if (treeData.std.length) {
+                this.initClassTree('std', treeData.std);
+            } else if (treeData.comple.length) {
+                this.initClassTree('comple', treeData.comple);
+            } else {
+                throw '没有有效的分类树。';
+            }
+            this.filterDatasAndShow();
+        } catch (err) {
+            console.log(err);
+            alert(err);
+        } finally {
+            $.bootstrapLoading.end();
+        }
+    },
+    getViewData: async function (gljLibId) {
+        const url = `/rationGlj/getGLJData/${commonConstants.COMPILATION}/${gljLibId}/true`;
+        const data = await ajaxGet(url);
+        return data.datas;
     },
     onClipboardPasting: function (sender, args) {
         args.cancel = true;
@@ -162,7 +224,7 @@ let gljSelOprObj = {
         }
     },
     //初始默认radio
-    initRadio: function () {
+    /* initRadio: function () {
         let me = gljSelOprObj;
         $('#gljSearchKeyword').val('');//恢复搜索文本
         me.selectedList = [].concat(rationGLJOprObj.cache['_GLJ_' + rationGLJOprObj.currentRationItem.ID]);
@@ -179,23 +241,17 @@ let gljSelOprObj = {
             //me.setShowGljList(me.complementaryGljList, true);
             me.sortGlj(me.showGljList);
         }
-    },
+    }, */
     filterDatasAndShow: function () {
+        const gljLib = $('#glj-lib-select').val();
         let me = gljSelOprObj;
-        let val = $("input[name='glj']:checked").val();
-        me.radiosSelected = val;
         //选择改变,数据重新筛选显示
         me.showGljList = [];
-        if(me.radiosSelected === 'allGljs'){
-            me.setShowGljList(me.stdGljList);
+        if (gljLib === commonConstants.COMPLEMENTARY_LIB) {
             me.setShowGljList(me.complementaryGljList);
-        }
-        else if(me.radiosSelected === 'stdGljs'){
+        } else {
             me.setShowGljList(me.stdGljList);
         }
-        else if(me.radiosSelected === 'complementaryGljs'){
-            me.setShowGljList(me.complementaryGljList);
-        }
         //搜索匹配
         let searchStr = $('#gljSearchKeyword').val();
         if(searchStr && searchStr.trim() != ''){
@@ -204,10 +260,9 @@ let gljSelOprObj = {
                 return reg.test(data.code) || reg.test(data.name);
             });
         }
-        me.sortGlj(me.showGljList);
+        //me.sortGlj(me.showGljList);
         //重新显示
         me.showGljItems(me.showGljList, me.gljCurTypeId);
-        //切换radio后更新cache
         if (me.currentOprParent = 1) {
             if(me.parentNodeIds["_pNodeId_" + me.gljCurTypeId]){
                 me.currentCache = me.getParentCache(me.parentNodeIds["_pNodeId_" + me.gljCurTypeId]);
@@ -220,7 +275,7 @@ let gljSelOprObj = {
         }
     },
     //监听radios选择事件
-    radiosChange: function () {
+    /* radiosChange: function () {
         let me = gljSelOprObj;
         $('.glj-radio').change(function () {
             if($(this).val() === 'stdGljs') {
@@ -230,7 +285,7 @@ let gljSelOprObj = {
             }
             me.filterDatasAndShow();
         });
-    },
+    }, */
 
     getParentCache: function (nodes) {
         let me = gljSelOprObj, rst = [];
@@ -338,15 +393,13 @@ let gljSelTreeOprObj = {
 };
 
 $(document).ready(function () {
-    $('#gljSearchKeyword').change(function () {
-        gljSelOprObj.filterDatasAndShow();
-    });
-    $('#gljSearchKeyword').bind('keypress', function (e) {
-        if(e.keyCode === 13){
-            $(this).blur();
-            return false;
-        }
+    $('#glj-lib-select').change(function () {
+        const gljLibId = $(this).val();
+        gljSelOprObj.initView(gljLibId, false);
     });
+    $('#gljSearchKeyword').on('keyup', _.debounce(function () {
+        gljSelOprObj.filterDatasAndShow();  
+    }, 300));
     $('#selGlj').on('shown.bs.modal', function () {
         if (gljSelOprObj.workBook) {
             gljSelOprObj.workBook.refresh();

+ 1 - 0
web/building_saas/complementary_ration_lib/js/init.js

@@ -40,6 +40,7 @@ const initialization = (() => {
                 rdSpreadEscSheets.push({sheet: rdSpread.getSheet(2), editStarting: rationCoeOprObj.onEditStarting, editEnded: rationCoeOprObj.onEditEnded});
                 rdSpreadEscSheets.push({sheet: rdSpread.getSheet(3), editStarting: rationInstObj.onEditStarting, editEnded: rationInstObj.onEditEnded});
                 sheetCommonObj.bindEscKey(rdSpread, rdSpreadEscSheets);
+                gljAdjOprObj.gljList = data.mixedGLJData.stdGljs.concat(data.mixedGLJData.complementaryGljs);
                 pageOprObj.initPage();
 
                 $("#linkGLJ").click(function(){

+ 1 - 6
web/building_saas/complementary_ration_lib/js/ration_glj.js

@@ -127,12 +127,7 @@ var rationGLJOprObj = {
                         items: {
                             "add": {
                                 name: "添加人材机", disabled: addDis, icon: "fa-plus", callback: function (key, opt) {
-                                    //默认radio所有工料机
-                                    gljSelOprObj.initRadio();
-                                    gljSelOprObj.gljCurTypeId = null;
-                                    gljSelOprObj.initClassTree('std', gljSelOprObj.treeData.std);
-                                    //弹出窗口
-                                    $('#selGlj').modal('show');
+                                    gljSelOprObj.initView(0, true);
                                 }
                             },
                             "delete": {

+ 0 - 2
web/building_saas/complementary_ration_lib/js/section_tree.js

@@ -20,8 +20,6 @@ let pageOprObj = {
         $('#fzAddConBtn').click(annotationOprObj.bindAddConBtn());
         $('#fzUpdateConBtn').click(annotationOprObj.bindUpdateConBtn());
         annotationOprObj.bindAllEvents($('#fzTxtareaAll'));
-        gljSelOprObj.getGljClassTree(this.mixedTreeData);
-        gljSelOprObj.getSelGljItems(this.mixedGLJData);
     },
     getRationLibInfo: function (rationLibId, callback) {
         CommonAjax.post('api/getRationLib', {rationRepId: rationLibId}, callback);

+ 14 - 2
web/building_saas/js/global.js

@@ -99,13 +99,25 @@ $(function () {
  * @param {String|Number} value
  * @return {void}
  */
+let storageErrCount = 0;
 function setLocalCache(key, value) {
     const storage = window.localStorage;
     if (!storage || key === '' || value === '') {
         return;
     }
-
-    storage.setItem(key, value);
+    try {
+        storage.setItem(key, value);
+        storageErrCount = 0;
+    } catch (err) {
+        storageErrCount++;
+        if (storageErrCount > 1) {
+            alert(`字段“${key}”数据过大,存入本地缓存失败。`)
+            storageErrCount = 0;
+            return;
+        }
+        storage.clear();
+        setLocalCache(key, value);
+    }
 }
 
 /**

+ 5 - 12
web/building_saas/main/html/main.html

@@ -45,6 +45,7 @@
         const overWriteUrl = '<%- overWriteUrl %>';
         console.log(projectCooperate);
         const G_SHOW_BLOCK_LIB = true;
+        const markReadProjectIDs = JSON.parse('<%- markReadProjectIDs %>');
 //        const G_SHOW_BLOCK_LIB = false;
     </script>
 </head>
@@ -58,14 +59,6 @@
 <input type="hidden" id="fileKind" value="<%= fileKind %>">
 <img src="/web/dest/css/img/question.png" id="question_pic" style="display: none">
     <div class="header">
-         <div class="top-msg clearfix">
-            <div class="alert alert-warning alert-dismissible" role="alert" id="notify" style="display: none">
-                <button type="button" class="close" aria-label="Close" onclick="$('#notify').hide();">
-                  <span aria-hidden="true">&times;</span>
-                </button>
-                <strong id="message"></strong>-
-            </div>
-        </div>
         <%include ../../../common/html/header.html %>
     </div>
     <div class="main">
@@ -105,7 +98,9 @@
                     <a class="dropdown-toggle" href="#" data-toggle="dropdown"><i class="fa  fa-code-fork"></i></a>
                     <div class="dropdown-menu" id="exportMenu">
                         <a class="dropdown-item" href="#export" data-toggle="modal" data-target="#export">导出<%= region %>电子招投标数据文件</a>
-                        <a class="dropdown-item" href="#exportIndex"  data-toggle="modal" data-target="#exportIndex" >导出<%= region %>指标成果文件</a>
+                        <% if (region !== '广东省') { %>
+                            <a class="dropdown-item" href="#exportIndex"  data-toggle="modal" data-target="#exportIndex" >导出<%= region %>指标成果文件</a>
+                        <% } %>
                     </div>
                 </span>
                       <% } %>
@@ -2432,7 +2427,7 @@
     <img src="/web/dest/css/img/engineering.png" id="eng_pic" style="display: none">
     <img src="/web/dest/css/img/tender.png" id="tender_pic" style="display: none">
 
-    <img src="/web/dest/css/img/blockLib.png" id="blockLib_pic" style="display: none">
+    <img src="/web/dest/css/img/blocklib.png" id="blockLib_pic" style="display: none">
     <img src="/web/dest/css/img/folder_open.png" id="folder_pic" style="display: none">
     <img src="/web/dest/css/img/tender.png" id="block_pic" style="display: none">
 
@@ -2459,10 +2454,8 @@
     <!--<script type="text/javascript" src="/test/tmp_data/test_ration_calc/ration_calc_base.js"></script>-->
     <script src="/lib/pinyinjs/pinyin_dict_firstletter.js"></script>
     <script src="/lib/pinyinjs/pinyinUtil.js"></script>
-    <script src="/public/common_constants.js"></script>
     <script type="text/javascript" src="/web/building_saas/main/js/models/main_consts.js"></script>
     <script type="text/javascript" src="/web/over_write/config/compilation_config.js"></script>
-    <script type="text/javascript" src="/public/web/common_util.js"></script>
     <script type="text/javascript" src="/public/web/encoding_util.js"></script>
     <script type="text/javascript" src="/web/building_saas/glj/js/project_glj.js"></script>
     <script type="text/javascript" src="/web/building_saas/glj/js/composition.js"></script>

+ 3 - 0
web/building_saas/main/js/models/calc_program.js

@@ -150,6 +150,9 @@ let calcTools = {
     isRationItem: function(treeNode){
         return this.isRationCategory(treeNode) && treeNode.data.type === rationType.ration;
     },
+    isCalcManageRation:function(treeNode){
+      return this.isRationCategory(treeNode) && (treeNode.data.type === rationType.ration || treeNode.data.type === rationType.install|| treeNode.data.type === rationType.itemIncrease);
+    },
     isVolumePrice: function (treeNode) {
         return this.isRationCategory(treeNode) && treeNode.data.type === rationType.volumePrice;
     },

+ 0 - 3
web/building_saas/main/js/models/exportStdInterfaceBase.js

@@ -905,9 +905,6 @@ const XML_EXPORT_BASE = (() => {
         for (let ele of eles) {
             rst += _startTag(ele);
             if (ele.children.length > 0) {
-                if (ele.children.some(c => !c)) {
-                    debugger;
-                }
                 rst += _toXMLStr(ele.children);
                 rst += _endTag(ele);
             }

+ 2 - 0
web/building_saas/main/js/models/installation_fee.js

@@ -663,6 +663,8 @@ var installation_fee = {
                 data.quantity = '1';
                 data.contain ='1';
                 data.nextID = nextID;
+                //取管理费费率
+                if(installationFeeObj.feeRateMap[rule.code]) data.manageFeeRate = installationFeeObj.feeRateMap[rule.code];
                 data.glj = {
                     'RGFTZ':{
                         rationID:data.ID,

+ 2 - 4
web/building_saas/main/js/views/glj_view.js

@@ -1120,10 +1120,8 @@ var gljOprObj = {
             let projectGljs = projectObj.project.projectGLJ.datas.gljList;
             let glj = _.find(projectGljs, {'id': recode.projectGLJID});
             if(glj){
-                glj["is_evaluate"]=newval;
-                args.fromRG=true;
-                pspread.postUpdate(recode.projectGLJID,"is_evaluate",newval,args,null,successTrigger);
-            }
+              configMaterialObj.updateConfigMaterial("is_evaluate",newval,glj);
+          }
         }
     },
     showLibGLJSheetData: function () {

+ 1 - 1
web/building_saas/main/js/views/glj_view_contextMenu.js

@@ -474,7 +474,7 @@ var gljContextMenu = {
             gljOprObj.gljLibSheet.setActiveCell(index, 0);
             gljOprObj.initSelection({row: index});
             gljOprObj.gljLibSpresd.focus(true);
-        } else if (actionType === 'add' || actionType === 'addMix'|| actionType === 'unitPriceAddMix') {
+        } else if (actionType === 'add' || actionType === 'insert' || actionType === 'addMix'|| actionType === 'unitPriceAddMix') {
             gljOprObj.locateZTree(null);
             sheetCommonObj.appendData(gljOprObj.gljLibSheet, 0, 0, gljOprObj.gljLibSheetSetting, gljOprObj.AllRecode);
             gljOprObj.gljLibSheet.showRow(0, GC.Spread.Sheets.VerticalPosition.top);

+ 7 - 9
web/building_saas/main/js/views/installation_fee_view.js

@@ -2,6 +2,7 @@
  * Created by zhang on 2018/1/30.
  */
 let installationFeeObj={
+    feeRateMap:{},
     rationInstallSheet:null,
     rationInstallData:[],
     rationInstallSetting:{
@@ -272,7 +273,6 @@ let installationFeeObj={
         me.feeItemSheet.repaint();
     },
     onFeeDetailSelectionChange:function (e,info) {
-        console.log('detail selection change');
         let me = installationFeeObj;
         if(me.detailReloadSelection){
             info.sheet.setSelection(me.detailReloadSelection.row,me.detailReloadSelection.col,1,1);
@@ -361,7 +361,7 @@ let installationFeeObj={
         }
 
     },
-    showFeeDetailData:function (libID,feeItemId) {
+    showFeeDetailData:function (libID,feeItemId,row,col) {
         this.feeDetailSheet.setRowCount(0);
         if(libID&&feeItemId){
             this.feeDetailData = this.getFeeDetailData(libID,feeItemId);
@@ -387,6 +387,7 @@ let installationFeeObj={
                 this.lockDetailSheet(i,this.feeDetailData[i],feeItem);
                 this.feeDetailSheet.autoFitRow(i);
             }
+            if(row && col) this.feeDetailSheet.setSelection(row,col,1,1);
             this.feeDetailSheet.resumeEvent();
             this.feeDetailSheet.resumePaint();
         }
@@ -475,14 +476,12 @@ let installationFeeObj={
         let options=[{text:"无",value:""}];
         for(let ir of detail.impactRules){
             options.push({text:ir.rule,value:ir.ID});
-            options.push(ir.rule);
         }
         options.push({text:"更多",value:'more'});
         let dynamicCombo = sheetCommonObj.getDynamicCombo();//new GC.Spread.Sheets.CellTypes.ComboBox();
         dynamicCombo.items(options);
         dynamicCombo.editorValueType(GC.Spread.Sheets.CellTypes.EditorValueType.value);
         sheet.setCellType(row, col, dynamicCombo, GC.Spread.Sheets.SheetArea.viewport);
-        sheet.getCell(row,col).wordWrap(true);
     },
     getFeeItemData:function (datas) {
         let feeItemShowArray = [];
@@ -821,7 +820,6 @@ let installationFeeObj={
         let me = this,installation_fee = projectObj.project.installation_fee;
         let modifyRule = me.modifyFeeRuleData[0];
         let updateMap = null;
-        console.log(me.modifyEditedObj);
         if(me.modifyEditedObj==null){
             $("#modify_feeRule").modal('hide');
             return;
@@ -875,7 +873,6 @@ let installationFeeObj={
         modifyFee.position = recode.code;
         modifyFee.billID = recode.ID;
         me.modifyFeeRuleSheet.setValue(riselection.row,positionCol,me.getDisplayText(modifyFee));
-        console.log(me.modifyEditedObj);
     },
     updateRationInstallPosition:function(recode){
         let me = this, installation_fee = projectObj.project.installation_fee;
@@ -1040,7 +1037,6 @@ let installationFeeObj={
                   }
               }
          }
-        console.log(updateData);
          if(updateData.length > 0){
              installation_fee.updateInstallation(updateData,function () {
                  me.refreshView();
@@ -1120,7 +1116,6 @@ let installationFeeObj={
         }
         me.modifyEditedObj[fieldID] = value;
         me.modifyFeeRuleData[info.row][fieldID] = value;
-        console.log(me.modifyEditedObj);
     },
     onFeeDetailValueChange:function (e,info) {
         if(info.newValue==info.oldValue){
@@ -1189,7 +1184,10 @@ let installationFeeObj={
                         feeRule[key] = updateDoc[key];
                     }
                 }
-              me.refreshFeeDetailRow(info.row);
+                let sel_f = me.feeItemSheet.getSelections()[0];
+                let oldData = sel_f.row<me.feeItemData.length?me.feeItemData[sel_f.row]:null;
+                me.showFeeDetailData(oldData.libID,oldData.ID,info.row,info.col); 
+              //me.refreshFeeDetailRow(info.row);
             }
         });
     },

+ 3 - 0
web/building_saas/main/js/views/item_increase_fee_view.js

@@ -356,9 +356,11 @@ let itemIncreaseFeeObj = {
         let td = getDecimal("ration.totalPrice");
         let gd = getDecimal('glj.quantity');
         let preID="",serialNo=1;
+        let manageFeeRate = null;
         for(let rationNode of node.children){
             rationCodeMap[rationNode.data.code] = rationNode;
             if(rationNode.data.type == rationType.ration || rationNode.data.type == rationType.volumePrice ){//先只汇总定额和量价类型,不考虑自动生成的
+                if(manageFeeRate == null) manageFeeRate = rationNode.data.manageFeeRate;
                 //计算人工费,材料费,机械费
                 if(rationNode.data.feesIndex){
                     let labour = rationNode.data.feesIndex.labour && rationNode.data.feesIndex.labour.totalFee?parseFloat(rationNode.data.feesIndex.labour.totalFee):0;
@@ -410,6 +412,7 @@ let itemIncreaseFeeObj = {
                         serialNo = serialNo+1;
                         let newRationData = this.inserNewItemNodes(node.data.ID,node.data.quantity,preID,serialNo,code,s.name,total,datas);
                         preID = newRationData.ID;
+                        newRationData.manageFeeRate = manageFeeRate;
                     }
                 }else { //如果total小于0,但又存在的话,删除定额(同时后端处理时记得要删除定额工料机)
                     if(ZMZJFnode){

+ 5 - 4
web/building_saas/main/js/views/mbzm_view.js

@@ -289,7 +289,7 @@ let mbzm_obj={
         if(rations.create.length > 0){
             serialNo = rations.create[rations.create.length -1].newData.serialNo + 1
         }
-        let n_ration = this.createNewRationData(data,newID,mainRation.data.ID,mainRation.data.billsItemID,serialNo,quantity,mainRation.data.libID);
+        let n_ration = this.createNewRationData(data,newID,mainRation.data.ID,mainRation.data.billsItemID,serialNo,quantity,mainRation.data.libID,false,mainRation.data.manageFeeRate);
         rations.create.push(n_ration);
         //处理其它兄弟节点的序号
         let br = projectObj.project.Ration.getBillsSortRation();
@@ -369,7 +369,7 @@ let mbzm_obj={
         let serialNo = 1;
         serialNo =  this.checkAndGetSerialNo(serialNo,billsID,rations);
         //先查看刚生成的定额中有没有相同父清单,有的话取最后的一个序列号加1
-        let n_ration = this.createNewRationData(data,newID,mainRation.data.ID,billsID,serialNo,quantity,mainRation.data.libID);
+        let n_ration = this.createNewRationData(data,newID,mainRation.data.ID,billsID,serialNo,quantity,mainRation.data.libID,false,mainRation.data.manageFeeRate);
         rations.create.push(n_ration);
     },
     createNewRationInFBFX:function (data,mainRation,quantity,rations,bills) {
@@ -384,7 +384,7 @@ let mbzm_obj={
             let newID =  uuid.v1();
             let serialNo = 1;
              serialNo =  this.checkAndGetSerialNo(serialNo,billsID,rations);
-            let n_ration = this.createNewRationData(data,newID,mainRation.data.ID,billsID,serialNo,quantity,mainRation.data.libID,true);
+            let n_ration = this.createNewRationData(data,newID,mainRation.data.ID,billsID,serialNo,quantity,mainRation.data.libID,true,mainRation.data.manageFeeRate);
             rations.create.push(n_ration);
         }
     },
@@ -418,13 +418,14 @@ let mbzm_obj={
         }
         return newData;
     },
-    createNewRationData:function (data,newID,referenceRationID,billsID,serialNo,quantity,libID,isFBFX) {
+    createNewRationData:function (data,newID,referenceRationID,billsID,serialNo,quantity,libID,isFBFX,manageFeeRate) {
         let newData = projectObj.project.Ration.getTempRationData(newID,billsID,serialNo,rationType.ration);
         newData.referenceRationID = referenceRationID;
         newData.quantity = quantity;
         newData.quantityEXP = gljUtil.isDef(data.coe) && data.coe != "0"?"MBGCL":quantity+"";
         //这里还要生成根据清单生成含量,父清单如果是刚自动生成的,其工程量为0,含量也为0
         newData.contain = projectObj.project.Ration.getContain(projectObj.project.mainTree.findNode(billsID),quantity);
+        newData.manageFeeRate = manageFeeRate;
         let temRation = this.getDefaultRationCreateData(newData,data.code,libID,isFBFX);
         return temRation;
     },

+ 6 - 1
web/building_saas/main/js/views/project_view.js

@@ -1042,6 +1042,11 @@ var projectObj = {
             else {
 
             }
+            const onceAlert = getLocalCache(commonConstants.StorageKey.ONCE_MAIN_LOADED);
+            if (onceAlert) {
+                alert(onceAlert);
+                removeLocalCache(commonConstants.StorageKey.ONCE_MAIN_LOADED);
+            }
         });
 
     },
@@ -1430,7 +1435,7 @@ var projectObj = {
                                 }
                             }
                         }
-                        getGLJData('insert');// ProjectController.addRation(project, controller, rationType.volumePrice);
+                        getGLJData('insert', null, true, null);// ProjectController.addRation(project, controller, rationType.volumePrice);
                     },
                     visible: function(key, opt){//2018-11-08  新需求,这个按钮先隐藏,有需要再放开
                         return true;

+ 6 - 1
web/building_saas/pm/html/project-management.html

@@ -23,6 +23,7 @@
         var userID = '<%- userID %>';
         let isFirst = JSON.parse('<%- isFirst %>');
         let isShow = JSON.parse('<%- isShow %>');
+        let unreadShareList = JSON.parse('<%- unreadShareList %>');
 
     </script>
     <style type="text/css">
@@ -86,6 +87,11 @@
                         </li>-->
                         <li class="nav-item" data-original-title="分享" data-placement="right" data-toggle="tooltip">
                             <a class="nav-link" href="#pm_share" id="tab_pm_share" data-toggle="tab"><i class="fa fa-share-alt"></i></a>
+                            <% if (JSON.parse(unreadShareList).length > 0) { %>
+                                <span class="badge badge-danger" id="unread-share-count"><%- JSON.parse(unreadShareList).length %></span>
+                            <% } else {%>
+                                <span class="badge badge-danger hide-area" id="unread-share-count"><%- JSON.parse(unreadShareList).length %></span>
+                            <% } %>
                         </li>
                         <!--<li class="nav-item" data-original-title="协同工作" data-placement="right" data-toggle="tooltip">
                             <a class="nav-link" href="javascript:void(0);"><i class="fa fa-users"></i></a>
@@ -947,7 +953,6 @@
 <script src="/web/building_saas/glj/js/socket.io.slim.js"></script>
 <script src="/public/web/socket/connection.js"></script>
 <script src="/public/billsUtil.js"></script>
-<script src="/public/common_constants.js"></script>
 <script src="/web/building_saas/main/js/models/importStdInterfaceBase.js"></script>
 <!-- <script src="/web/building_saas/main/js/models/importStandardInterface.js"></script> -->
 <script src="/web/common/components/share/index.js" ></script>

+ 9 - 7
web/building_saas/pm/js/pm_newMain.js

@@ -903,9 +903,9 @@ const projTreeObj = {
             let newTab = window.open('about:blank');
             BeforeOpenProject(ID, {'fullFolder': GetFullFolder(parent)}, function () {
                 let mainUrl = `/main?project=${ID}`;
-                CommonAjax.get(mainUrl, [], function () {
-                    newTab.location.replace(mainUrl); //不能后退
-                });
+                newTab.location.replace(mainUrl); //不能后退
+                /* CommonAjax.get(mainUrl, [], function () {
+                }); */
             });
         }, timeoutTime);
     },
@@ -2644,10 +2644,12 @@ $(document).ready(function() {
             rename? projectData['name'] = rename:'';
             projectMap['copy'] = {document:projectData};
             $("#copy-to-dialog").modal('hide');
-            $.bootstrapLoading.progressStart('拷贝项目', true);
-            $("#progress_modal_body").text('正在拷贝项目,请稍候……');
-            await ajaxPost('/pm/api/copyProjects', {projectMap:projectMap,user_id: userID, tenderCount: 1});
-            await importProcessChecking(null, null, (newProjectData) => handleTenderAfterChecking(newProjectData, projectData));
+            const copyRst = await ajaxPost('/pm/api/copyProjects', {projectMap:projectMap,user_id: userID, tenderCount: 1});
+            if (!copyRst.error) {
+                $.bootstrapLoading.progressStart('拷贝项目', true);
+                $("#progress_modal_body").text('正在拷贝项目,请稍候……');
+                await importProcessChecking(null, null, (newProjectData) => handleTenderAfterChecking(newProjectData, projectData));
+            }
         } catch (err) {
             alert(err);
         }

+ 175 - 36
web/building_saas/pm/js/pm_share.js

@@ -500,8 +500,38 @@ const pmShare = (function () {
         };
         return new InteractionCell();
     }
+
+    function isUnread(unreadList, node) {
+        if (!unreadList) {
+            return false;
+        }
+        const actualID = node && node.data && node.data.actualTreeInfo && node.data.actualTreeInfo.ID || null;
+        if (!actualID) {
+            return false;
+        }
+        return !!unreadList.find(ID => ID === actualID);
+    }
     const foreColor = '#007bff';
-    const cancelForeColor = 'red';
+    const dangerColor = '#dc3545';
+    // 标记已读
+    // 打开任意的单位工程,都会将其父分享条目标记和自身为已读 
+    // eg:被分享了一个建设项目,和其下一个单位工程,打开该一个单位工程,建设项目标记为已读、该单位工程也标记为已读
+    function handleMarkRead(unreadList, markReadProjectIDs) {
+        const col = headers.findIndex(item => item.dataCode === 'shareDate');
+        const sheet = spreadObj.workBook.getActiveSheet();
+        const style = new GC.Spread.Sheets.Style();
+        projTreeObj.renderSheetFuc(sheet, function () {
+            markReadProjectIDs.forEach(projectID => {
+                SHARE_TO.removeUnread(projectID, unreadList);
+                tree.items.forEach(node => {
+                    if (node.data && node.data.actualTreeInfo && node.data.actualTreeInfo.ID === projectID) {
+                        const row = node.serialNo();
+                        sheet.setStyle(row, col, style);
+                    }
+                });
+            });
+        });
+    }
     //显示树结构数据
     //@param {Array}nodes {Array}headers @return {void}
     function showTreeData(nodes, headers){
@@ -520,6 +550,12 @@ const pmShare = (function () {
                         style.foreColor = foreColor;
                         sheet.setStyle(i, j, style);
                         sheet.getCell(i, j).cellType(getInteractionCell());
+                    } else if (dataCode === 'shareDate') {
+                        let style = new GC.Spread.Sheets.Style();
+                        if (isUnread(unreadShareList, nodes[i])) {
+                            style.foreColor = dangerColor;
+                        }
+                        sheet.setStyle(i, j, style);
                     }
                     sheet.setValue(i, j, nodes[i].data[dataCode] !== null && typeof nodes[i].data[dataCode] !== 'undefined' ? nodes[i].data[dataCode] : '');
                 }
@@ -527,6 +563,30 @@ const pmShare = (function () {
         };
         renderSheetFunc(sheet, fuc);
     }
+    // 获取实际树ID为某值的所有节点
+    function getNodesByActualID(ID, items) {
+        return items.filter(node => node.data && node.data.actualTreeInfo && node.data.actualTreeInfo.ID === ID);
+    }
+    // 刷新显示某些节点
+    function refreshNodes(nodes, headers) {
+        const sheet = spreadObj.workBook.getActiveSheet();
+        const fuc = function(){
+            for (let i = 0; i < nodes.length; i++) {
+                const row = nodes[i].serialNo();
+                for (let j = 0; j < headers.length; j++) {
+                    const dataCode = headers[j].dataCode;
+                    if (dataCode === 'from') {
+                        const style = new GC.Spread.Sheets.Style();
+                        style.foreColor = foreColor;
+                        sheet.setStyle(row, j, style);
+                        sheet.getCell(row, j).cellType(getInteractionCell());
+                    }
+                    sheet.setValue(row, j, nodes[i].data[dataCode] !== null && typeof nodes[i].data[dataCode] !== 'undefined' ? nodes[i].data[dataCode] : '');
+                }
+            }
+        };
+        renderSheetFunc(sheet, fuc);
+    }
     //同一棵树,可能存在相同数据显示多条的问题(传入的datas中不存在相同数据)
     //将真实树结构数据存在actualTreeInfo中,外部树结构数据用uuid重置。
     //@param {Array}datas
@@ -698,6 +758,7 @@ const pmShare = (function () {
         $.bootstrapLoading.start();
         //获取分享数据
         CommonAjax.post('/pm/api/receiveProjects', {user_id: userID}, function (rstData) {
+            actualIDShareInfo = {};
             // 排序 --分享的文件按照时间先后顺序排序,分享文件下的子文件,按照原本树结构显示,不需要排序
             sortByDate(rstData.grouped);
             sortByDate(rstData.ungrouped);
@@ -894,6 +955,98 @@ const pmShare = (function () {
         }
         return `${orgName} (${userInfo.name}分享拷贝)`;
     }
+    //清除了该节点后,可能还有该节点的数据在树上(树允许有重复数据),需要更新分享信息
+    function updateAfterCancel(userID, projectID) {
+        for (let item of tree.items) {
+            if (item.data.actualTreeInfo && item.data.actualTreeInfo.ID === projectID) {
+                _.remove(item.data.shareInfo, function (data) {
+                    return data.userID === userID;
+                });
+            }
+        }
+    }
+    // 处理节点操作属性变更(是否可拷贝、是否可编辑)
+    function handlePropChange(projectID, prop) {
+        const actualShareData = actualIDShareInfo[projectID];
+        if (actualShareData) {
+            const shareItem = actualShareData.shareInfo.find(s => s.userID === userID);
+            if(shareItem) {
+                Object.assign(shareItem, prop);
+            }
+        }
+        const nodes = getNodesByActualID(projectID, tree.items);
+        nodes.forEach(node => {
+            const shareItem = node.data.shareInfo.find(s => s.userID === userID);
+            if(shareItem) {
+                Object.assign(shareItem, prop);
+            }
+        });
+        const treeData = tree.items.map(item => item.data);
+        setPermissionsInfo(treeData);
+        showTreeData(tree.items, headers);
+    }
+    // 处理清除
+    /* function handleCancelShare(cancelProjID, callback) {
+        $.bootstrapLoading.start();
+        CommonAjax.post('/pm/api/share', {user_id: userID, type: oprType.cancel,  projectID: cancelProjID, shareData:[{userID: userID}]}, function (rstData) {
+            const node = tree.items.find(item => item.data.actualTreeInfo && item.data.actualTreeInfo.ID === cancelProjID);
+            if (node) {
+                tree.removeNode(node);
+            }
+            //更新与清除节点数据相同,且未被清除缓存分享信息
+            updateAfterCancel(userID, cancelProjID);
+            //重新设置actualIDShareInfo,以正确更新权限(清除了分享信息后,可能会导致权限变化 eg:清除了新的分享,则存留的分享项目采用旧的)
+            actualIDShareInfo = {};
+            let treeDatas = [];
+            for (let item of tree.items) {
+                treeDatas.push(item.data);
+                let actualTreeInfo = item.data.actualTreeInfo;
+                if (actualTreeInfo && !actualIDShareInfo[actualTreeInfo.ID]) {
+                    actualIDShareInfo[actualTreeInfo.ID] = {
+                        ID: actualTreeInfo.ID,
+                        ParentID: actualTreeInfo.ParentID,
+                        NextSiblingID: actualTreeInfo.NextSiblingID,
+                        shareInfo: item.data.shareInfo
+                    };
+                }
+            }
+            //重新设置权限
+            setPermissionsInfo(treeDatas);
+            showTreeData(tree.items, headers);
+            $.bootstrapLoading.end();
+            if (callback) {
+                callback(rstData);
+            }
+        }, function () {
+            $.bootstrapLoading.end();
+        });
+    } */
+    function handleCancelShare(cancelProjID) {
+        const node = tree.items.find(item => item.data.actualTreeInfo && item.data.actualTreeInfo.ID === cancelProjID);
+        if (node) {
+            tree.removeNode(node);
+        }
+        //更新与清除节点数据相同,且未被清除缓存分享信息
+        updateAfterCancel(userID, cancelProjID);
+        //重新设置actualIDShareInfo,以正确更新权限(清除了分享信息后,可能会导致权限变化 eg:清除了新的分享,则存留的分享项目采用旧的)
+        actualIDShareInfo = {};
+        let treeDatas = [];
+        for (let item of tree.items) {
+            treeDatas.push(item.data);
+            let actualTreeInfo = item.data.actualTreeInfo;
+            if (actualTreeInfo && !actualIDShareInfo[actualTreeInfo.ID]) {
+                actualIDShareInfo[actualTreeInfo.ID] = {
+                    ID: actualTreeInfo.ID,
+                    ParentID: actualTreeInfo.ParentID,
+                    NextSiblingID: actualTreeInfo.NextSiblingID,
+                    shareInfo: item.data.shareInfo
+                };
+            }
+        }
+        //重新设置权限
+        setPermissionsInfo(treeDatas);
+        showTreeData(tree.items, headers);
+    }
     //事件监听器
     //@return void
     function eventListener(){
@@ -968,41 +1121,18 @@ const pmShare = (function () {
             copyShareProject(tree.selected, parseInt(selProj), parseInt(selEng));
         });
         //清除分享
-        //清除了该节点后,可能还有该节点的数据在树上(树允许有重复数据),需要更新分享信息
-        function updateAfterCancel(userID, projectID) {
-            for (let item of tree.items) {
-                if (item.data.actualTreeInfo && item.data.actualTreeInfo.ID === projectID) {
-                    _.remove(item.data.shareInfo, function (data) {
-                        return data.userID === userID;
-                    });
-                }
-            }
-        }
-        $('#cancelShareConfirm').click(function () {
+        $('#cancelShareConfirm').click(function (data) {
             $.bootstrapLoading.start();
-            let cancelProjID = tree.selected.data.actualTreeInfo.ID;
-            CommonAjax.post('/pm/api/share', {user_id: userID, type: oprType.cancel,  projectID: cancelProjID, shareData:[{userID: userID}]}, function (rstData) {
-                tree.removeNode(tree.selected);
-                //更新与清除节点数据相同,且为被清除缓存分享信息
-                updateAfterCancel(userID, cancelProjID);
-                //重新设置actualIDShareInfo,以正确更新权限(清除了分享信息后,可能会导致权限变化 eg:清除了新的分享,则存留的分享项目采用旧的)
-                actualIDShareInfo = {};
-                let treeDatas = [];
-                for (let item of tree.items) {
-                    treeDatas.push(item.data);
-                    let actualTreeInfo = item.data.actualTreeInfo;
-                    if (actualTreeInfo && !actualIDShareInfo[actualTreeInfo.ID]) {
-                        actualIDShareInfo[actualTreeInfo.ID] = {
-                            ID: actualTreeInfo.ID,
-                            ParentID: actualTreeInfo.ParentID,
-                            NextSiblingID: actualTreeInfo.NextSiblingID,
-                            shareInfo: item.data.shareInfo
-                        };
-                    }
-                }
-                //重新设置权限
-                setPermissionsInfo(treeDatas);
-                showTreeData(tree.items, headers);
+            const cancelProjID = tree.selected.data.actualTreeInfo.ID;
+            const permissionType = commonConstants.SharePermissionChangeType.CANCEL;
+            CommonAjax.post('/pm/api/share', { user_id: userID, type: oprType.cancel, permissionType, projectID: cancelProjID, shareData: [{ userID: userID }] }, function (rstData) {
+                handleCancelShare(cancelProjID);
+
+                // 推送已打开的项目,通知已取消分享
+                SHARE_TO.emitPermissionChange(permissionType, userID, cancelProjID, rstData.emitTenders);
+                // 清除已读
+                SHARE_TO.removeUnread(cancelProjID, unreadShareList);
+
                 $.bootstrapLoading.end();
             }, function () {
                 $.bootstrapLoading.end();
@@ -1010,7 +1140,16 @@ const pmShare = (function () {
         });
     }
 
-    return {spreadObj, headers, initView, eventListener}
+    return {
+        spreadObj, 
+        headers, 
+        initView, 
+        eventListener, 
+        initShareTree, 
+        handlePropChange,
+        handleCancelShare,
+        handleMarkRead
+    }
 })();
 
 $(document).ready(function () {

+ 1 - 1
web/building_saas/unit_price_file/index.html

@@ -174,7 +174,7 @@
     <img src="/web/dest/css/img/engineering.png" id="eng_pic" style="display: none">
     <img src="/web/dest/css/img/tender.png" id="tender_pic" style="display: none">
 
-    <img src="/web/dest/css/img/blockLib.png" id="blockLib_pic" style="display: none">
+    <img src="/web/dest/css/img/blocklib.png" id="blockLib_pic" style="display: none">
     <img src="/web/dest/css/img/folder_open.png" id="folder_pic" style="display: none">
     <img src="/web/dest/css/img/tender.png" id="block_pic" style="display: none">
 

+ 32 - 0
web/common/components/blank/index.html

@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<html lang="en">
+
+<head>
+    <meta charset="utf-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
+    <meta http-equiv="x-ua-compatible" content="ie=edge">
+    <title>大司空云计价</title>
+    <link rel="stylesheet" href="/lib/bootstrap/css/bootstrap.min.css">
+    <link rel="stylesheet" href="/web/building_saas/css/main.css">
+    <link rel="shortcut icon" href="/web/building_saas/css/favicon.ico">
+    <link rel="icon" type="image/gif" href="/web/building_saas/css/animated_favicon1.gif">
+</head>
+
+<body>
+    <div class="header">
+        <nav class="navbar navbar-expand-lg p-0 d-flex">
+            <div class="header-logo">
+            </div>
+        </nav>
+    </div>
+    <div class="container mt-3">
+        <%- html %>
+    </div>
+    <!-- JS. -->
+    <script src="/lib/jquery/jquery-3.2.1.min.js"></script>
+    <script src="/lib/popper/popper.min.js"></script>
+    <script src="/lib/bootstrap/bootstrap.min.js"></script>
+    <script src="/web/building_saas/js/global.js"></script>
+</body>
+
+</html>

+ 168 - 4
web/common/components/share/index.js

@@ -1,3 +1,4 @@
+
 const SHARE_TO = (() => {
 
     const ShareType = {
@@ -6,6 +7,8 @@ const SHARE_TO = (() => {
         CANCEL: 'cancel',
     };
 
+    const { SharePermissionChangeType: PermissionType, PageTarget } = commonConstants;
+
     // 当前分享的项目ID
     let curProjectID;
 
@@ -224,20 +227,23 @@ const SHARE_TO = (() => {
             let shareData;
             let type = ShareType.UPDATE;
             if (shareType === ShareType.CREATE) {
-                const allowCopy = !!$('#allow-copy').attr('checked');
-                const allowCooperate = !!$('#allow-edit').attr('checked');
+                const allowCopy = $('#allow-copy').prop('checked');
+                const allowCooperate = $('#allow-edit').prop('checked');
                 shareData = [{ userID: receiver, allowCopy, allowCooperate }];
                 type = ShareType.CREATE; // 上传的服务器的type,删除跟更新是一样的
             } else if (shareType === ShareType.UPDATE) {
-                const allowCopy = !!$(`[data-user=${receiver}].allow-copy`).attr('checked');
-                const allowCooperate = !!$(`[data-user=${receiver}].allow-edit`).attr('checked');
+                const allowCopy = $(`[data-user=${receiver}].allow-copy`).prop('checked');
+                const allowCooperate = $(`[data-user=${receiver}].allow-edit`).prop('checked');
                 shareData = [{ userID: receiver, allowCopy, allowCooperate }];
             } else {
                 shareData = [{ userID: receiver, isCancel: true }];
             }
+            // 获取权限变更的类型
+            const permissionType = getPermissionType(shareType, curSharedUsers, shareData[0]);
             const postData = {
                 user_id: userID,
                 type,
+                permissionType,
                 projectID: curProjectID,
                 count: rencentCount,
                 shareData
@@ -263,6 +269,18 @@ const SHARE_TO = (() => {
                 initSharedView(curSharedUsers);
                 refreshShareTip(curSharedUsers);
                 refreshTreeView();
+            } else {
+                const matchItem = curSharedUsers.find(item => item._id === receiver);
+                if (matchItem) {
+                    matchItem.allowCopy = shareData[0].allowCopy;
+                    matchItem.allowCooperate = shareData[0].allowCooperate;
+                }
+            }
+            if (permissionType !== null) {
+                const payload = [PermissionType.UPDATE_COOPERATE, PermissionType.UPDATE_COPY].includes(permissionType)
+                    ? { prop: { allowCopy: shareData[0].allowCopy, allowCooperate: shareData[0].allowCooperate } }
+                    : null;
+                emitPermissionChange(permissionType, receiver, curProjectID, rst.emitTenders, payload);
             }
         } catch (err) {
             console.log(err);
@@ -273,6 +291,147 @@ const SHARE_TO = (() => {
         }
     }
 
+    // 获取权限变更的类型
+    function getPermissionType(shareType, cache, curShareData) {
+        if (shareType === ShareType.CANCEL) {
+            return PermissionType.CANCEL;
+        }
+        if (shareType === ShareType.CREATE) {
+            return PermissionType.SHARE;
+        }
+        if (!cache) {
+            return null;
+        }
+        const match = cache.find(item => item._id === curShareData.userID);
+        if (!match) {
+            return null;
+        }
+        if (match.allowCooperate !== curShareData.allowCooperate) {
+            return PermissionType.UPDATE_COOPERATE;
+        }
+        if (match.allowCopy !== curShareData.allowCopy) {
+            return PermissionType.UPDATE_COPY;
+        }
+        return null;
+    }
+
+    // 权限变更消息推送
+    function emitPermissionChange(permissionType, userID, projectID, emitTenders, payload) {
+        const compilationID = typeof projectObj !== 'undefined' ? projectObj.project.projectInfo.compilation : compilationData._id;
+        socket.emit('sharePermissionChange', { permissionType, userID, compilationID, projectID, emitTenders, payload });
+    }
+
+    // 权限变更处理监听
+    function permissionChangeListener() {
+        socket.on('sharePermissionChange', ({ permissionType, target, payload }) => {
+            const type = `${permissionType}-${target}`;
+            switch (type) {
+                case `${PermissionType.READ}-${PageTarget.PM}`:
+                    handleShareItemRead(payload);
+                    break;
+                case `${PermissionType.UPDATE_COOPERATE}-${PageTarget.PM}`:
+                case `${PermissionType.UPDATE_COPY}-${PageTarget.PM}`:
+                    handlePMPropChange(payload);
+                    break;
+                case `${PermissionType.UPDATE_COOPERATE}-${PageTarget.MAIN}`:
+                    handleMainCooperateChange(payload);
+                    break;
+                case `${PermissionType.CANCEL}-${PageTarget.PM}`:
+                    handlePMCancelPermission(payload);
+                    break;
+                case `${PermissionType.CANCEL}-${PageTarget.MAIN}`:
+                    handleMainCancelPermission();
+                    break;
+                case `${PermissionType.SHARE}-${PageTarget.PM}`:
+                    handlePMSharePermission(payload);
+                    break;
+            }
+        });
+    }
+
+    // 项目管理页面分享权限变更后相关处理函数
+
+    // 已读分享项目
+    function handleShareItemRead({ markReadProjectIDs }) {
+        const isActive = $('#tab_pm_share').hasClass('active');
+        if (isActive) {
+            pmShare.handleMarkRead(unreadShareList, markReadProjectIDs);
+        } else {
+            markReadProjectIDs.forEach(projectID => {
+                removeUnread(projectID, unreadShareList);
+            });
+        }
+    }
+    // 分享条数属性变更(是否可拷贝、是否可编辑)
+    // @param {Object} prop - 属性变更对象 eg: { allowCopy: false }
+    function handlePMPropChange({ projectID, prop }) {
+        // 如果当前在项目管理的分享页面,需要刷新相关节点
+        const isActive = $('#tab_pm_share').hasClass('active');
+        if (isActive) {
+            pmShare.handlePropChange(projectID, prop);
+        }
+    }
+
+    // 已读某未读分享项目
+    function removeUnread(projectID, list) {
+        const idx = list.indexOf(projectID);
+        if (idx >= 0) {
+            list.splice(idx, 1);
+            refreshUnreadCount(list.length);
+        }
+    }
+
+    // 取消分享后项目管理页面接收到推送后到操作
+    function handlePMCancelPermission({ projectID }) {
+        removeUnread(projectID, unreadShareList);
+        // 如果当前停留在分享标签界面,清楚分享项目
+        const isActive = $('#tab_pm_share').hasClass('active');
+        if (isActive) {
+            pmShare.handleCancelShare(projectID);
+        }
+    }
+    // 通知信息点击事件
+    function notify() {
+        pmShare.initShareTree();
+        $("#notify").hide();
+    }
+    // 新增分享后项目管理页面接收到推送后的操作
+    function handlePMSharePermission({ projectID }) {
+        unreadShareList.push(projectID);
+        unreadShareList = [...new Set(unreadShareList)];
+        refreshUnreadCount(unreadShareList.length);
+        // 如果当前停留在分享标签界面,需要弹出提示
+        const isActive = $('#tab_pm_share').hasClass('active');
+        if (isActive) {
+            $("#message").html('您有新的分享项目,<a href="javascript:void(0);" id="load-data" onclick="SHARE_TO.notify()">点击刷新列表</a>');
+            $("#notify").show();
+        }
+    }
+
+    // 刷新未读分享标记
+    function refreshUnreadCount(count) {
+        if (count) {
+            $('#unread-share-count').text(count);
+            $('#unread-share-count').show();
+        } else {
+            $('#unread-share-count').hide();
+        }
+    }
+
+
+    
+    // 主页面的分享权限变更后相关处理函数
+    function handleMainCooperateChange({ allowCooperate }) {
+        if (projectObj.project.projectInfo.shareState && projectObj.project.projectInfo.shareState.allowCooperate !== allowCooperate) {
+            setLocalCache(commonConstants.StorageKey.ONCE_MAIN_LOADED, '分享设置已被修改,当前项目已自动刷新。');
+            window.location.reload();
+        }
+    }
+    function handleMainCancelPermission() {
+        // 定位到空白页
+        window.location.replace(`/blank?type=${commonConstants.BlankType.SHARE_CANCEL}`);
+    }
+
     // 刷新项目管理树视图
     // 如果是在项目管理页面,需要刷新树(分享图标可能需要清除)
     function refreshTreeView() {
@@ -424,6 +583,7 @@ const SHARE_TO = (() => {
         }
         $('#share-phone').on('keyup', function () {
             delayKeyup(function () {
+                console.log(curSharedUsers);
                 handleSearch();
             });
         });
@@ -437,6 +597,10 @@ const SHARE_TO = (() => {
     return {
         initModal,
         handleEventListener,
+        permissionChangeListener,
+        emitPermissionChange,
         getAvatarHTML,
+        notify,
+        removeUnread,
     }
 })();

+ 10 - 0
web/common/html/header.html

@@ -1,5 +1,13 @@
 <img id="f_btn" src="/web/dest/css/img/feeRate_btn.jpg" alt="" style="display: none" />
 <!-- <nav class="navbar p-0 "> -->
+<div class="top-msg clearfix">
+    <div class="alert alert-warning alert-dismissible" role="alert" id="notify" style="display: none">
+        <button type="button" class="close" aria-label="Close" onclick="$('#notify').hide();">
+            <span aria-hidden="true">&times;</span>
+        </button>
+        <strong id="message"></strong>-
+    </div>
+</div>
 <nav class="navbar navbar-expand-lg p-0 d-flex <%= versionName.includes('免费') ? 'free-version' : 'pro-version' %>">
     <% if(controller === 'boot' || controller === 'pm'){ %>
     <!--<a style="text-decoration: none" href="javascript:void(0);" class="header-logo">-->
@@ -329,5 +337,7 @@
 <script type="text/javascript" src="/lib/lodash/lodash.js"></script>
 <script type="text/javascript" src="/public/web/commonAlert.js"></script>
 <script type="text/javascript" src="/public/web/headerOpr.js"></script>
+<script type="text/javascript" src="/public/common_util.js"></script>
+<script src="/public/common_constants.js"></script>
 <script type="text/javascript" src="/lib/jquery-editable-select/jquery.editable-select.min.js"></script>
 <!-- endinject -->

+ 242 - 1
web/over_write/js/guangdong_2018.js

@@ -30,7 +30,7 @@ function overwriteRationCalcBases (){
     // 广东建筑管理费特殊计算:管理费=ROUND(∑(人工费+施工机具费)*定额的管理费费率*0.01,精度)。与定额同级的量价、人材机则按管理费费率为0计算。
     rationCalcBases['管理费'] = function (node, isTender) {
         let rst = 0;
-        if (calcTools.isRationItem(node) && node.data.gljList && node.data.manageFeeRate) {
+        if (calcTools.isCalcManageRation(node)  && node.data.gljList && node.data.manageFeeRate) {
             rst = (rationCalcBases['人工费'](node, isTender) + rationCalcBases['机具费'](node, isTender)) * node.data.manageFeeRate * 0.01;
             rst = rst.toDecimal(decimalObj.ration.unitPrice);
         };
@@ -100,6 +100,11 @@ if (typeof module !== 'undefined') {
         $('#add-content').val('3');
     });
 
+    // 隐藏项目属性中:取费方式中的子目含量取费选项
+    if ($('#rationContent')) {
+        $('#rationContent').parent().hide();
+    }
+
     //清单计算基数相关
     if(typeof baseFigureMap !== 'undefined'){
         baseFigureMap = {
@@ -168,6 +173,242 @@ if (typeof gljOprObj !== 'undefined') {
     ]
 }
 
+if (typeof installationFeeObj !== 'undefined') {
+  installationFeeObj.feeRateMap={
+    "AZF0101":"39.86",
+    "AZF0102":"39.92",
+    "AZF0103":"39.92",
+    "AZF0104":"39.86",
+    "AZF0105":"39.86",
+    "AZF0106":"28.74",
+    "AZF0107":"30.66",
+    "AZF0108":"29.43",
+    "AZF0109":"27.72",
+    "AZF0110":"29.35",
+    "AZF0111":"27.73",
+    "AZF0112":"27.72",
+    "AZF0113":"29.44",
+    "AZF0114":"20.56",
+    "AZF0115":"39.86",
+    "AZF0116":"39.86",
+    "AZF0117":"39.86",
+    "AZF0118":"39.86",
+    "AZF0119":"39.86",
+    "AZF0120":"39.86",
+    "AZF0121":"39.86",
+    "AZF0122":"39.86",
+    "AZF0123":"39.86",
+    "AZF0124":"28.74",
+    "AZF0125":"30.66",
+    "AZF0126":"30.66",
+    "AZF0127":"30.66",
+    "AZF0128":"27.72",
+    "AZF0129":"29.35",
+    "AZF0130":"29.35",
+    "AZF0131":"27.73",
+    "AZF0132":"27.73",
+    "AZF0133":"27.73",
+    "AZF0134":"27.73",
+    "AZF0135":"27.72",
+    "AZF0136":"27.72",
+    "AZF0137":"27.72",
+    "AZF0138":"27.72",
+    "AZF0139":"29.44",
+    "AZF0140":"29.44",
+    "AZF0141":"29.44",
+    "AZF0142":"20.56",
+    "AZF0143":"20.56",
+    "AZF0144":"20.56",
+    "AZF0145":"20.56",
+    "AZF0146":"20.56",
+    "AZF0147":"20.56",
+    "AZF0148":"20.56",
+    "AZF0149":"20.56",
+    "AZF0150":"39.92",
+    "AZF0151":"28.74",
+    "AZF0152":"30.66",
+    "AZF0153":"29.43",
+    "AZF0154":"27.72",
+    "AZF0155":"29.35",
+    "AZF0156":"27.73",
+    "AZF0157":"27.72",
+    "AZF0158":"29.44",
+    "AZF0159":"20.56",
+    "AZF0160":"28.74",
+    "AZF0161":"28.74",
+    "AZF0162":"28.74",
+    "AZF0163":"28.74",
+    "AZF0164":"28.74",
+    "AZF0165":"28.74",
+    "AZF0166":"28.74",
+    "AZF0167":"28.74",
+    "AZF0168":"28.74",
+    "AZF0169":"28.74",
+    "AZF0170":"28.74",
+    "AZF0171":"28.74",
+    "AZF0172":"28.74",
+    "AZF0173":"28.74",
+    "AZF0174":"28.74",
+    "AZF0175":"28.74",
+    "AZF0176":"28.74",
+    "AZF0177":"28.74",
+    "AZF0178":"28.74",
+    "AZF0179":"28.74",
+    "AZF0180":"28.74",
+    "AZF0181":"28.74",
+    "AZF0182":"28.74",
+    "AZF0183":"28.74",
+    "AZF0184":"30.66",
+    "AZF0185":"30.66",
+    "AZF0186":"30.66",
+    "AZF0187":"30.66",
+    "AZF0188":"30.66",
+    "AZF0189":"30.66",
+    "AZF0190":"30.66",
+    "AZF0191":"30.66",
+    "AZF0192":"30.66",
+    "AZF0193":"30.66",
+    "AZF0194":"30.66",
+    "AZF0195":"30.66",
+    "AZF0196":"30.66",
+    "AZF0197":"30.66",
+    "AZF0198":"30.66",
+    "AZF0199":"30.66",
+    "AZF0200":"30.66",
+    "AZF0201":"30.66",
+    "AZF0202":"30.66",
+    "AZF0203":"30.66",
+    "AZF0204":"30.66",
+    "AZF0205":"30.66",
+    "AZF0206":"30.66",
+    "AZF0207":"30.66",
+    "AZF0208":"27.72",
+    "AZF0209":"27.72",
+    "AZF0210":"27.72",
+    "AZF0211":"27.72",
+    "AZF0212":"27.72",
+    "AZF0213":"27.72",
+    "AZF0214":"27.72",
+    "AZF0215":"27.72",
+    "AZF0216":"27.72",
+    "AZF0217":"27.72",
+    "AZF0218":"27.72",
+    "AZF0219":"27.72",
+    "AZF0220":"27.72",
+    "AZF0221":"27.72",
+    "AZF0222":"27.72",
+    "AZF0223":"27.72",
+    "AZF0224":"27.72",
+    "AZF0225":"27.72",
+    "AZF0226":"27.72",
+    "AZF0227":"27.72",
+    "AZF0228":"27.72",
+    "AZF0229":"27.72",
+    "AZF0230":"27.72",
+    "AZF0231":"27.72",
+    "AZF0232":"27.73",
+    "AZF0233":"27.73",
+    "AZF0234":"27.73",
+    "AZF0235":"27.73",
+    "AZF0236":"27.73",
+    "AZF0237":"27.73",
+    "AZF0238":"27.73",
+    "AZF0239":"27.73",
+    "AZF0240":"27.73",
+    "AZF0241":"27.73",
+    "AZF0242":"27.73",
+    "AZF0243":"27.73",
+    "AZF0244":"27.73",
+    "AZF0245":"27.73",
+    "AZF0246":"27.73",
+    "AZF0247":"27.73",
+    "AZF0248":"27.73",
+    "AZF0249":"27.73",
+    "AZF0250":"27.73",
+    "AZF0251":"27.73",
+    "AZF0252":"27.73",
+    "AZF0253":"27.73",
+    "AZF0254":"27.73",
+    "AZF0255":"27.73",
+    "AZF0256":"27.72",
+    "AZF0257":"27.72",
+    "AZF0258":"27.72",
+    "AZF0259":"27.72",
+    "AZF0260":"27.72",
+    "AZF0261":"27.72",
+    "AZF0262":"27.72",
+    "AZF0263":"27.72",
+    "AZF0264":"27.72",
+    "AZF0265":"27.72",
+    "AZF0266":"27.72",
+    "AZF0267":"27.72",
+    "AZF0268":"27.72",
+    "AZF0269":"27.72",
+    "AZF0270":"27.72",
+    "AZF0271":"27.72",
+    "AZF0272":"27.72",
+    "AZF0273":"27.72",
+    "AZF0274":"27.72",
+    "AZF0275":"27.72",
+    "AZF0276":"27.72",
+    "AZF0277":"27.72",
+    "AZF0278":"27.72",
+    "AZF0279":"27.72",
+    "AZF0280":"29.44",
+    "AZF0281":"29.44",
+    "AZF0282":"29.44",
+    "AZF0283":"29.44",
+    "AZF0284":"29.44",
+    "AZF0285":"29.44",
+    "AZF0286":"29.44",
+    "AZF0287":"29.44",
+    "AZF0288":"29.44",
+    "AZF0289":"29.44",
+    "AZF0290":"29.44",
+    "AZF0291":"29.44",
+    "AZF0292":"29.44",
+    "AZF0293":"29.44",
+    "AZF0294":"29.44",
+    "AZF0295":"29.44",
+    "AZF0296":"29.44",
+    "AZF0297":"29.44",
+    "AZF0298":"29.44",
+    "AZF0299":"29.44",
+    "AZF0300":"29.44",
+    "AZF0301":"29.44",
+    "AZF0302":"29.44",
+    "AZF0303":"29.44",
+    "AZF0304":"20.56",
+    "AZF0305":"20.56",
+    "AZF0306":"20.56",
+    "AZF0307":"20.56",
+    "AZF0308":"20.56",
+    "AZF0309":"20.56",
+    "AZF0310":"20.56",
+    "AZF0311":"20.56",
+    "AZF0312":"20.56",
+    "AZF0313":"20.56",
+    "AZF0314":"20.56",
+    "AZF0315":"20.56",
+    "AZF0316":"20.56",
+    "AZF0317":"20.56",
+    "AZF0318":"20.56",
+    "AZF0319":"20.56",
+    "AZF0320":"20.56",
+    "AZF0321":"20.56",
+    "AZF0322":"20.56",
+    "AZF0323":"20.56",
+    "AZF0324":"20.56",
+    "AZF0325":"20.56",
+    "AZF0326":"20.56",
+    "AZF0327":"20.56",
+    "AZF0328":"27.72",
+    "AZF0329":"27.72",
+    "AZF0330":"39.86",
+    "AZF0331":"27.72"
+    }
+}
+
 // 导入导出接口
 // 需要用固定类别关联的费用字典,用固定类别来映射
 // 数据节选自标准pdf文件《《建设工程政府投资项目造价数据标准》信息公开版》,附录C-费用名称与费用代号

+ 100 - 68
web/over_write/js/guangdong_2018_export.js

@@ -312,8 +312,11 @@ const XMLStandard = (function () {
                 return feeCode;
             }
             const name = bills.name || '';
+            // 新需求: 不处理重名问题
+            feeCode = pinyinUtil.getFirstLetter(name);
+            return feeCode;
             // 出现重名,应以“费用代号_顺序号”规则,顺序号为自然数,从 1 开始(整个建设项目)
-            const tempFeeCode = pinyinUtil.getFirstLetter(name);
+            /* const tempFeeCode = pinyinUtil.getFirstLetter(name);
             const sameFeeCodes = firstLetterMap[tempFeeCode] ? firstLetterMap[tempFeeCode] : firstLetterMap[tempFeeCode] = [];
             feeCode = sameFeeCodes.includes(bills.ID)
                 ? `${tempFeeCode}${sameFeeCodes.indexOf(bills.ID) === 0 ? '' : '_' + sameFeeCodes.indexOf(bills.ID)}`
@@ -323,7 +326,7 @@ const XMLStandard = (function () {
             if (!sameFeeCodes.includes(bills.ID)) {
                 sameFeeCodes.push(bills.ID);
             }
-            return feeCode;
+            return feeCode; */
         } else { // 非ID引用基数获取费用字典
             return FormulaFeeCodeMap[formula] || '';
         }
@@ -1194,12 +1197,14 @@ const XMLStandard = (function () {
         }
         // 单位工程费用汇总标题
         function UnitWorksSummaryGroup(bills) {
+            const node = tenderDetail.mainTree.nodes[`id_${bills.ID}`];
+            const row = node.serialNo() + 1;
             const attrs = [
                 // 费用编号
                 { name: 'Number', value: bills.code },
                 // 费用名称
                 {
-                    name: 'Name', dName: '名称', required: true,
+                    name: 'Name', dName: '名称', required: true, minLen: 1, failHint: `第${row}行清单-“项目名称”`,
                     value: bills.name
                 },
                 // 单位
@@ -1240,12 +1245,13 @@ const XMLStandard = (function () {
             _base.Element.call(this, 'UnitWorksSummaryGroup', attrs, '单位工程费用汇总标题');
         }
         // 单位工程费汇总明细
-        function UnitWorksSummaryItem(node) {
+        function UnitWorksSummaryItem(node, isEmptyCode = false) {
             const bills = node.data;
+            const row = node.serialNo() + 1;
             const attrs = [
                 { name: 'Number', value: bills.code },
                 {
-                    name: 'Name', dName: '名称', required: true,
+                    name: 'Name', dName: '名称', required: true, minLen: 1, failHint: `第${row}行清单-“项目名称”`,
                     value: bills.name
                 },
                 { name: 'Unit', value: bills.unit },
@@ -1280,7 +1286,7 @@ const XMLStandard = (function () {
                 // 费用代号
                 {
                     name: 'Code', dName: '费用代号', required: true,
-                    value: getFeeCode(bills)
+                    value: isEmptyCode ? '' : getFeeCode(bills)
                 },
                 // 汇总类型
                 {
@@ -1396,7 +1402,7 @@ const XMLStandard = (function () {
                 { name: 'Number', value: bills.code },
                 // 名称
                 {
-                    name: 'Name', dName: '名称',
+                    name: 'Name', dName: '名称', minLen: 1,
                     value: bills.name
                 },
                 // 特征
@@ -1424,7 +1430,8 @@ const XMLStandard = (function () {
                     value: '4'
                 },
                 // 费用字典,拼音首字母
-                { name: 'Code', value: getFeeCode(bills) },
+                //{ name: 'Code', value: getFeeCode(bills) },
+                { name: 'Code', value: '' },
                 // 备注
                 { name: 'Remark', value: bills.remark }
             ];
@@ -1477,7 +1484,8 @@ const XMLStandard = (function () {
                 // 清单标识
                 { name: 'ListingIdentity', value: 'GB50500-2013 1 GD' },
                 // 费用代号
-                { name: 'Code', value: getFeeCode(bills) },
+                //{ name: 'Code', value: getFeeCode(bills) },
+                { name: 'Code', value: '' },
                 // 备注
                 { name: 'Remark', value: bills.remark }
             ];
@@ -1508,7 +1516,7 @@ const XMLStandard = (function () {
         function WorkContent(contentText, fee) {
             const attrs = [
                 // 定额工作内容
-                { name: 'Name', dName: '定额工作内容', required: true, value: contentText },
+                { name: 'Name', dName: '定额工作内容', required: true, value: contentText, minLen: 1 },
                 // 取此工作内容下定额子目/量价/定额同级人材机的综合合价之和
                 { name: 'Total', type: _type.DECIMAL, value: fee },
                 { name: 'Remark', value: '' }
@@ -1624,9 +1632,11 @@ const XMLStandard = (function () {
         }
         // 其他项目汇总子目
         function SundryItem(eleName, bills) {
+            const node = tenderDetail.mainTree.nodes[`id_${bills.ID}`];
+            const row = node.serialNo() + 1;
             const attrs = [
                 // 名称
-                { name: 'Name', dName: '名称', required: true, value: bills.name },
+                { name: 'Name', dName: '名称', required: true, minLen: 1, value: bills.name, failHint: `第${row}行清单-“项目名称”`},
                 // 金额
                 { name: 'Total', type: _type.DECIMAL, value: _util.getFee(bills.fees, 'common.totalFee') },
                 // 费用代号
@@ -1641,9 +1651,10 @@ const XMLStandard = (function () {
             const bills = node.data;
             // 材料暂估价取不汇总,其他为汇总
             const kind = node.getFlag() === fixedFlag.MATERIAL_PROVISIONAL ? SummrayKind.NO : SummrayKind.YES;
+            const row = node.serialNo() + 1;
             const attrs = [
                 // 名称
-                { name: 'Name', dName: '名称', required: true, value: bills.name },
+                { name: 'Name', dName: '名称', required: true, minLen: 1, value: bills.name, failHint: `第${row}行清单-“项目名称”` },
                 // 金额
                 { name: 'Total', type: _type.DECIMAL, value: _util.getFee(bills.fees, 'common.totalFee') },
                 // 费用代号
@@ -1660,9 +1671,10 @@ const XMLStandard = (function () {
             const bills = node.data;
             // 材料暂估价取不汇总,其他为汇总
             const kind = node.getFlag() === fixedFlag.MATERIAL_PROVISIONAL ? SummrayKind.NO : SummrayKind.YES;
+            const row = node.serialNo() + 1;
             const attrs = [
                 // 名称
-                { name: 'Name', dName: '名称', required: true, value: bills.name },
+                { name: 'Name', dName: '名称', required: true, minLen: 1, value: bills.name, failHint: `第${row}行清单-“项目名称”` },
                 // 单位
                 { name: 'Unit', value: bills.unit },
                 // 工程量
@@ -1694,9 +1706,10 @@ const XMLStandard = (function () {
         // 暂列金额标题
         function ProvisionalSumsGroup(node) {
             const bills = node.data;
+            const row = node.serialNo() + 1;
             const attrs = [
                 // 名称
-                { name: 'Name', dName: '名称', required: true, value: bills.name },
+                { name: 'Name', dName: '名称', required: true, minLen: 1, value: bills.name, failHint: `第${row}行清单-“项目名称”` },
                 // 金额
                 { name: 'Total', type: _type.DECIMAL, value: _util.getFee(bills.fees, 'common.totalFee') },
                 // 汇总类型
@@ -1709,9 +1722,10 @@ const XMLStandard = (function () {
         // 暂列金额明细
         function ProvisionalSumsItem(node) {
             const bills = node.data;
+            const row = node.serialNo() + 1;
             const attrs = [
                 // 名称
-                { name: 'Name', dName: '名称', required: true, value: bills.name },
+                { name: 'Name', dName: '名称', required: true, minLen: 1, value: bills.name, failHint: `第${row}行清单-“项目名称”` },
                 // 单位
                 { name: 'Unit', value: bills.unit },
                 // 工程量
@@ -1745,7 +1759,7 @@ const XMLStandard = (function () {
                 // 编码
                 { name: 'Number', value: glj.code },
                 // 名称
-                { name: 'Name', dName: '名称', required: true, value: glj.name },
+                { name: 'Name', dName: '名称', required: true, minLen: 1, value: glj.name },
                 // 型号规格
                 { name: 'Specification', value: glj.specs },
                 // 单位
@@ -1764,9 +1778,10 @@ const XMLStandard = (function () {
         // 专业工程暂估价标题
         function SpecialtyProvisionalPriceGroup(node) {
             const bills = node.data;
+            const row = node.serialNo() + 1;
             const attrs = [
                 // 名称
-                { name: 'Name', dName: '名称', required: true, value: bills.name },
+                { name: 'Name', dName: '名称', required: true, minLen: 1, value: bills.name, failHint: `第${row}行清单-“项目名称”` },
                 // 工程内容
                 { name: 'Content', value: bills.engineeringContent },
                 // 金额
@@ -1781,9 +1796,10 @@ const XMLStandard = (function () {
         // 专业工程暂估价明细
         function SpecialtyProvisionalPriceItem(node) {
             const bills = node.data;
+            const row = node.serialNo() + 1;
             const attrs = [
                 // 名称
-                { name: 'Name', dName: '名称', required: true, value: bills.name },
+                { name: 'Name', dName: '名称', required: true, minLen: 1, value: bills.name, failHint: `第${row}行清单-“项目名称”` },
                 // 工程内容
                 { name: 'Content', value: bills.engineeringContent },
                 // 单位
@@ -1814,9 +1830,10 @@ const XMLStandard = (function () {
         // 计日工标题
         function DayWorkRateGroup(node) {
             const bills = node.data;
+            const row = node.serialNo() + 1;
             const attrs = [
                 // 名称
-                { name: 'Name', dName: '名称', required: true, value: bills.name },
+                { name: 'Name', dName: '名称', required: true, minLen: 1, value: bills.name, failHint: `第${row}行清单-“项目名称”` },
                 // 金额
                 { name: 'Total', type: _type.DECIMAL, value: _util.getFee(bills.fees, 'common.totalFee') },
                 // 费用代号
@@ -1829,11 +1846,12 @@ const XMLStandard = (function () {
         // 计日工明细
         function DayWorkRateItem(node) {
             const bills = node.data;
+            const row = node.serialNo() + 1;
             const attrs = [
                 // 编码
                 { name: 'Number', value: bills.code },
                 // 名称
-                { name: 'Name', dName: '名称', required: true, value: bills.name },
+                { name: 'Name', dName: '名称', required: true, minLen: 1, value: bills.name, failHint: `第${row}行清单-“项目名称”` },
                 // 型号规格
                 { name: 'Specification', value: '' },
                 // 单位
@@ -1852,9 +1870,10 @@ const XMLStandard = (function () {
         // 总承包服务费标题
         function MainContractorAttendanceGroup(node) {
             const bills = node.data;
+            const row = node.serialNo() + 1;
             const attrs = [
                 // 名称
-                { name: 'Name', dName: '名称', required: true, value: bills.name },
+                { name: 'Name', dName: '名称', required: true, minLen: 1, value: bills.name, failHint: `第${row}行清单-“项目名称”` },
                 // 金额
                 { name: 'Total', type: _type.DECIMAL, value: _util.getFee(bills.fees, 'common.totalFee') },
                 // 汇总类型
@@ -1867,9 +1886,10 @@ const XMLStandard = (function () {
         // 总承包服务费明细
         function MainContractorAttendanceItem(node) {
             const bills = node.data;
+            const row = node.serialNo() + 1;
             const attrs = [
                 // 名称
-                { name: 'Name', dName: '名称', required: true, value: bills.name },
+                { name: 'Name', dName: '名称', required: true, minLen: 1, value: bills.name, failHint: `第${row}行清单-“项目名称”` },
                 // 项目价值,计算基数对应的金额
                 { name: 'Quantity', type: _type.DECIMAL, value: bills.tenderCalcBaseValue },
                 // 服务内容
@@ -1890,9 +1910,10 @@ const XMLStandard = (function () {
         // 索赔、签证费用标题
         function ClaimVisaCostGroup(eleName, node) {
             const bills = node.data;
+            const row = node.serialNo() + 1;
             const attrs = [
                 // 名称
-                { name: 'Name', dName: '名称', required: true, value: bills.name },
+                { name: 'Name', dName: '名称', required: true, minLen: 1, value: bills.name, failHint: `第${row}行清单-“项目名称”` },
                 // 金额
                 { name: 'Total', type: _type.DECIMAL, value: _util.getFee(bills.fees, 'common.totalFee') },
                 // 汇总类型
@@ -1905,9 +1926,10 @@ const XMLStandard = (function () {
         // 索赔费用明细
         function ClaimVisaCostItem(eleName, node) {
             const bills = node.data;
+            const row = node.serialNo() + 1;
             const attrs = [
                 // 名称
-                { name: 'Name', dName: '名称', required: true, value: bills.name },
+                { name: 'Name', dName: '名称', required: true, minLen: 1, value: bills.name, failHint: `第${row}行清单-“项目名称”` },
                 // 单位
                 { name: 'Unit', value: bills.unit },
                 // 数量
@@ -1938,7 +1960,7 @@ const XMLStandard = (function () {
         // 规费
         function StatutoryFees() {
             const attrs = [
-                { name: 'Name', dName: '名称', required: true, value: '规费' },
+                { name: 'Name', dName: '名称', required: true, minLen: 1, value: '规费' },
                 { name: 'Code', dName: '费用代号', required: true, value: 'GF' }
             ];
             _base.Element.call(this, 'StatutoryFees', attrs, '规费');
@@ -1946,9 +1968,10 @@ const XMLStandard = (function () {
         // 税金
         function Tax(eleName, node) {
             const bills = node.data;
+            const row = node.serialNo() + 1;
             const attrs = [
                 // 名称
-                { name: 'Name', dName: '名称', required: true, value: bills.name },
+                { name: 'Name', dName: '名称', required: true, minLen: 1, value: bills.name, failHint: `第${row}行清单-“项目名称”` },
                 // 计算基数说明
                 { name: 'CalBasis', value: getCalBasis(node) },
                 // 计算基数
@@ -2425,7 +2448,7 @@ const XMLStandard = (function () {
             const fbfx = tenderDetail.mainTree.roots.find(node => node.getFlag() === fixedFlag.SUB_ENGINERRING);
             const fbfxGroup = new UnitWorksSummaryGroup(fbfx.data);
             // 分部分项工程的子项,取项目名称的首字母拼接
-            const fbfxChildren = fbfx.children.map(node => new UnitWorksSummaryItem(node));
+            const fbfxChildren = fbfx.children.map(node => new UnitWorksSummaryItem(node, true));
             fbfxGroup.children.push(...fbfxChildren);
             // 措施项目
             const csxm = tenderDetail.mainTree.roots.find(node => node.getFlag() === fixedFlag.MEASURE);
@@ -2600,7 +2623,7 @@ const XMLStandard = (function () {
                     ele = groupFactory(node);
                     // 递归获取子元素
                     ele.children = loadGroupAndItems(node.children, groupFactory, itemFactory);
-                } else { // 无子清单的是分项
+                } else { // 无子清单的是子目(除了计日工,计日工最底层节点也需要是标题,否则检测平台会报错)
                     ele = itemFactory(node);
                 }
                 return ele;
@@ -2658,10 +2681,11 @@ const XMLStandard = (function () {
             if (!isValidDepth) {
                 _failList.push('计日工子项超过两层')
             } else {
+                // 计日工最底层节点也需要是标题,否则检测平台会报错
                 dayworkRate.children = loadGroupAndItems(
                     daywork.children,
                     (node) => new DayWorkRateGroup(node),
-                    (node) => new DayWorkRateItem(node)
+                    (node) => new DayWorkRateGroup(node)
                 );
             }
             // 总承包服务费
@@ -2872,54 +2896,62 @@ const XMLStandard = (function () {
         return await loadData(projectData);
     }
 
+    // 导出名称为建设项目名称
+    let zipFileBaseName = '';
+
     /*
      * 重置工程编号,广东18 3.0的接口,导出时的文件会用到这个编号
      * 因此重置工程编号后,还需要重置文件名数据
      * @param  {Array}codes 工程编号
-     *         {Array}extractData 提取的数据
+     *         {Array}ungroupedExtractData 提取的数据(可能存在不同导出类型,需要分组。比如同时导出招标文件和控制价文件)
      * @return {void}
      * */
-    function resetContentCode(codes, extractData) {
+    function resetContentCode(codes, ungroupedExtractData) {
         // 提取到的数据分两种类型:
         // 1.根元素为建设项目:需要修改建设项目下的单位工程元素的编码和文件名
         // 2.根元素为单位工程:需要修改单位工程的编码和文件名
         // 获取建设项目数据下的单位工程元素
-        const extractProject = extractData.find(data => data.dataType === DATA_TYPE.PROJECT);
-        const extractTenders = extractData.filter(data => data.dataType === DATA_TYPE.TENDER)
-        // 建设项目元素下建筑安装工程费元素
-        const projectInstallations = _util.getElementFromSrc(extractProject.data, 'ProjectInstallationWorkCost');
-        // 建筑安装工程费元素下单项工程元素
-        const sectionalWorks = projectInstallations.reduce((acc, cur) => {
-            const sections = _util.getElementFromSrc(cur, 'SectionalWorks');
-            acc.push(...sections);
-            return acc;
-        }, []);
-        let idx = 0;
-        // 单项工程
-        sectionalWorks.forEach(sectionalWork => {
-            _util.setAttr(sectionalWork, 'Number', codes[idx++]);
-        });
-        // 建筑安装工程费元素下单位工程元素, 顺序与提取的单位工程文件(extractTenders)是一一对应的
-        const unitWorks = sectionalWorks.reduce((acc, cur) => {
-            const tenders = _util.getElementFromSrc(cur, 'UnitWorks');
-            acc.push(...tenders);
-            return acc;
-        }, []);
-        unitWorks.forEach((unitWork, index) => {
-            // 更改编号
-            const code = codes[idx++];
-            _util.setAttr(unitWork, 'Number', code);
-            // 更改文件名:@_单位工程编码_单位工程名称.xml
-            const name = _util.getAttr(unitWork, 'Name');
-            const fileName = `@_${code}_${name}.xml`;
-            _util.setAttr(unitWork, 'FileName', fileName);
-            // 更改提取的单位工程详细文件的导出名称、单位工程节点编号、文件名
-            const extractTender = extractTenders[index];
-            extractTender.fileName = fileName;
-            const extractUnitWork = extractTender.data;
-            _util.setAttr(extractUnitWork, 'Number', code);
-            _util.setAttr(extractUnitWork, 'FileName', fileName);
-        });
+        const groupedData = _.groupBy(ungroupedExtractData, 'exportKind');
+        for (const exportKind in groupedData) {
+            const extractData = groupedData[exportKind];
+            const extractProject = extractData.find(data => data.dataType === DATA_TYPE.PROJECT);
+            zipFileBaseName = _util.getAttr(extractProject.data, 'Name');
+            const extractTenders = extractData.filter(data => data.dataType === DATA_TYPE.TENDER)
+            // 建设项目元素下建筑安装工程费元素
+            const projectInstallations = _util.getElementFromSrc(extractProject.data, 'ProjectInstallationWorkCost');
+            // 建筑安装工程费元素下单项工程元素
+            const sectionalWorks = projectInstallations.reduce((acc, cur) => {
+                const sections = _util.getElementFromSrc(cur, 'SectionalWorks');
+                acc.push(...sections);
+                return acc;
+            }, []);
+            let idx = 0;
+            // 单项工程
+            sectionalWorks.forEach(sectionalWork => {
+                _util.setAttr(sectionalWork, 'Number', codes[idx++]);
+            });
+            // 建筑安装工程费元素下单位工程元素, 顺序与提取的单位工程文件(extractTenders)是一一对应的
+            const unitWorks = sectionalWorks.reduce((acc, cur) => {
+                const tenders = _util.getElementFromSrc(cur, 'UnitWorks');
+                acc.push(...tenders);
+                return acc;
+            }, []);
+            unitWorks.forEach((unitWork, index) => {
+                // 更改编号
+                const code = codes[idx++];
+                _util.setAttr(unitWork, 'Number', code);
+                // 更改文件名:@_单位工程编码_单位工程名称.xml
+                const name = _util.getAttr(unitWork, 'Name');
+                const fileName = `@_${code}_${name}.xml`;
+                _util.setAttr(unitWork, 'FileName', fileName);
+                // 更改提取的单位工程详细文件的导出名称、单位工程节点编号、文件名
+                const extractTender = extractTenders[index];
+                extractTender.fileName = fileName;
+                const extractUnitWork = extractTender.data;
+                _util.setAttr(extractUnitWork, 'Number', code);
+                _util.setAttr(extractUnitWork, 'FileName', fileName);
+            });
+        }
     }
 
     /*
@@ -2964,7 +2996,7 @@ const XMLStandard = (function () {
             // TypedBuffer to ArrayBuffer to blob
             const saveAsBlob = new Blob([zipFile.buffer]);
             const exportKindName = _config.EXPORT_KIND_NAME[exportKind];
-            saveAs(saveAsBlob, `广东标准交换数据(${exportKindName}).COS`);
+            saveAs(saveAsBlob, `${zipFileBaseName}(${exportKindName}).COS`);
         }
     }
 

+ 191 - 52
web/over_write/js/guangdong_2018_import.js

@@ -28,6 +28,8 @@ const importXML = (() => {
     //导入的文件类型,界面选的文件类型是生成项目的文件类型,这里的文件类型指的是,要导入文件的类型,
     //导入文件类型不同,导入数据不同
     let importFileKind = '';
+    // 投标文件和控制价文件是完全导入数据的
+    let isFullyImport = false;
     // 文件类型
     const FileKind = {
         '6': 1, // 投标
@@ -65,10 +67,10 @@ const importXML = (() => {
         // 默认、中间过程
         PROCESS: 6
     };
-    // 根据上方精度要求得到的项目属性,小数位数的值
+    // 根据上方精度要求得到的项目属性,小数位数的值(定额工程量小数位数需求改成六位)
     const tenderPropertyDecimal = {
         bills: { unitPrice: Decimal.FEE, totalPrice: Decimal.FEE },
-        ration: { quantity: Decimal.QUANTITY, unitPrice: Decimal.FEE, totalPrice: Decimal.FEE },
+        ration: { quantity: Decimal.PROCESS, unitPrice: Decimal.FEE, totalPrice: Decimal.FEE },
         glj: { quantity: Decimal.GLJ, unitPriceHasMix: Decimal.FEE, unitPrice: Decimal.FEE },
         feeRate: Decimal.RATE,
         quantity_detail: 4,
@@ -97,6 +99,7 @@ const importXML = (() => {
         const projectXMLObj = xmlObjMap['Project.xml'];
         const projectSrc = getValue(projectXMLObj, ['ConstructionProject']);
         importFileKind = FileKind[getValue(projectSrc, ['_FileKind'])]; // 标记当前导入的文件类型
+        isFullyImport = [FileKind.tender, FileKind.control].includes(importFileKind);
         const rst = {
             projType: projectType.Project,
             name: getValue(projectSrc, ['_Name']),
@@ -107,7 +110,7 @@ const importXML = (() => {
             basicInformation: extractBasicInfo(projectSrc)
         };
         countData.unitPriceCount = countData.projectGLJCount;
-        if (importFileKind !== FileKind.tender) {
+        if (!isFullyImport) {
             countData = {
                 projectCount: countData.projectCount,
                 unitPriceFileCount: countData.unitPriceFileCount
@@ -122,6 +125,7 @@ const importXML = (() => {
         const bidderInfo = getValue(projectSrc, ['ConstructionInfo', 'BidderInfo']); // 投标信息
         return [
             { key: 'projNum', value: getValue(projectSrc, ['_Number']) }, // 编码
+            { key: 'projectType', value: getValue(projectSrc, ['_ProjectType']) }, // 工程类型
             { key: 'projectCategory', value: getValue(projectSrc, ['_ProjectCategory']) }, // 工程类别
             { key: 'constructionType', value: getValue(projectSrc, ['_ConstructionType']) }, // 建设性质
             { key: 'regionalCategories', value: AreaKind[getValue(projectSrc, ['_AreaKind'])] }, // 地区类被
@@ -197,6 +201,8 @@ const importXML = (() => {
         });
     }
     // 从xml对象中提取单位工程数据
+
+    // 额外新增的数据(文件里没有,需要额外生成)
     function extractTenders(sectionWorkSrc, xmlObjMap) {
         const unitWorks = arrayValue(sectionWorkSrc, ['UnitWorks']);
         return unitWorks.map(unitWorckSrc => {
@@ -217,6 +223,7 @@ const importXML = (() => {
                 csxm: extractCSXM(src),
                 other: extractOther(src),
                 tax: extractTax(src),
+                evalGLJFromOther: extractProvisionalMaterialEquipmentItem(src),
                 ...extractGLJSummary(src),
                 bidEvaluationSummary: extractBidEvaluationSummary(src)
             };
@@ -231,6 +238,29 @@ const importXML = (() => {
             value: getValue(src, ['_Value'])
         }));
         clearEmptyItems(featureData);
+        console.log(featureData);
+        // 特殊处理几个工程特征(有的文件AttrInfo中不包含一些工程特征)
+        const hasProjectType = featureData.some(item => item.dipName === '工程类型');
+        const hasScale = featureData.some(item => item.dipName === '建设规模');
+        const hasScaleUnit = featureData.some(item => item.dipName === '建设规模单位');
+        if (!hasProjectType) {
+            featureData.push({
+                dispName: '工程类型',
+                value: getValue(tenderSrc, ['_ProjectType'])
+            });
+        }
+        if (!hasScale) {
+            featureData.push({
+                dispName: '建设规模',
+                value: getValue(tenderSrc, ['_Scale'])
+            });
+        }
+        if (!hasScaleUnit) {
+            featureData.push({
+                dispName: '建设规模单位',
+                value: getValue(tenderSrc, ['_Unit'])
+            });
+        }
         return featureData;
 
         function clearEmptyItems(items) {
@@ -267,7 +297,7 @@ const importXML = (() => {
                 const feeRate = getValue(src, ['_Rate']);
                 item.feeRate = +feeRate !== 100 && feeRate || undefined;
             }
-            if (importFileKind !== FileKind.tender) {
+            if (!isFullyImport) {
                 delete item.feeRate
                 delete item.fees;
             }
@@ -327,7 +357,7 @@ const importXML = (() => {
                 item.feeCode = FlagFeeCodeMap[fixedFlag.OTHER_MEASURE_FEE];
             }
         }
-        if (importFileKind === FileKind.tender) {
+        if (isFullyImport) {
             const summaryFees = getFeesFromBasicCost(divisionalSrc);
             const fees = [{ fieldName: 'common', totalFee: getValue(divisionalSrc, ['_Total']), unitFee: getValue(divisionalSrc, ['_TechnicalAndEconomicIndex']) }];
             item.fees = mergeFees(fees, summaryFees);
@@ -360,21 +390,19 @@ const importXML = (() => {
             bills.feeRate = +feeRate !== 100 && feeRate || undefined;
         }
         // 投标和控制价,需要导入最高限价
-        if ([FileKind.tender, FileKind.control].includes(importFileKind)) {
+        if (isFullyImport) {
             const maxPrice = getValue(workElementSrc, ['_PriceHigh']);
             //不为0才输出
             if (maxPrice && maxPrice !== '0') {
                 bills.outPutMaxPrice = true;
                 bills.maxPrice = maxPrice;
             }
-            if (importFileKind === FileKind.tender) {
-                const summaryFees = getFeesFromBasicCost(workElementSrc);
-                const fees = [
-                    { fieldName: 'common', unitFee: getValue(workElementSrc, ['_Price']), totalFee: getValue(workElementSrc, ['_Total']) },
-                    { fieldName: 'equipment', unitFee: getValue(workElementSrc, ['_EquipmentPrice']) },
-                ];
-                bills.fees = mergeFees(fees, summaryFees);
-            }
+            const summaryFees = getFeesFromBasicCost(workElementSrc);
+            const fees = [
+                { fieldName: 'common', unitFee: getValue(workElementSrc, ['_Price']), totalFee: getValue(workElementSrc, ['_Total']) },
+                { fieldName: 'equipment', unitFee: getValue(workElementSrc, ['_EquipmentPrice']) },
+            ];
+            bills.fees = mergeFees(fees, summaryFees);
         }
         return bills;
         // 获取项目特征文本
@@ -436,7 +464,7 @@ const importXML = (() => {
     }
     // 提取定额
     function extractRations(workElementSrc) {
-        if (importFileKind !== FileKind.tender) {
+        if (!isFullyImport) {
             return [];
         }
         // 正常情况下定额肯定是在WorkContent内的,但是也做定额不在WorkContent内的处理(外层定额)
@@ -473,6 +501,46 @@ const importXML = (() => {
             }
             return rationType.ration;
         }
+        // 根据定额计算程序子目,获取量价类型
+        function getVolumePriceTypeByCalcItems(rationSrc) {
+            const calculationOfItems = arrayValue(rationSrc, ['UnitPriceCalculationOfItem']);
+            for (const item of calculationOfItems) {
+                const code = getValue(item, ['_Code']);
+                const total = +getValue(item, ['_Total']);
+                if (code === 'RGF' && total) {
+                    return { type: rationType.volumePrice, subType: 1 };
+                }
+                if (code === 'CLF' && total) {
+                    return { type: rationType.volumePrice, subType: 201 };
+                }
+                if (code === 'JXF' && total) {
+                    return { type: rationType.volumePrice, subType: 301 };
+                }
+            }
+            return null;
+        }
+        // 提取计算程序费率数据
+        function extractCalculationOfItems(rationSrc) {
+            // 需要提取费率的字段映射
+            const codeFiedNameMap = {
+                'RGF': 'labour',
+                'CLF': 'material',
+                'JXF': 'machine',
+                'GLF': 'manage',
+                'LR': 'profit',
+                'DJ': 'common',
+            };
+            const calculationOfItems = arrayValue(rationSrc, ['UnitPriceCalculationOfItem']);
+            const rst = [];
+            calculationOfItems.forEach(item => {
+                const fieldName = codeFiedNameMap[getValue(item, ['_Code'])];
+                const feeRate = +getValue(item, ['_Rate']);
+                if (fieldName && !isNaN(feeRate)) {
+                    rst.push({ fieldName, feeRate });
+                }
+            });
+            return rst;
+        }
         // 从定额的计算程序提取价格
         function getFeesFromCalculationOfItem(rationSrc) {
             const calculationOfItems = arrayValue(rationSrc, ['UnitPriceCalculationOfItem']);
@@ -486,7 +554,6 @@ const importXML = (() => {
                 'SBF': 'equipment',
                 'LR': 'profit',
                 //'DJ': 'common',定额本身已经有单价这个字段,不需要从这读取
-
             };
             calculationOfItems.forEach(item => {
                 const totalFee = +getValue(item, ['_Total']);
@@ -507,9 +574,13 @@ const importXML = (() => {
         }
         // TODO 目前无法确定定额的取费专业programID,暂时用专业类别Specialty
         function extractRation(rationSrc, jobContentText) {
+            // 需要处理定额编码如易达的某文件,定额编码有“E1-3-52*3”,需要截取“*”之前的作为定额编码
+            let code = getValue(rationSrc, ['_Number']);
+            const codeReg = /([^*]+)\*.*/;
+            code = code.replace(codeReg, '$1');
             const ration = {
                 serialNo: serialNo++,
-                code: getValue(rationSrc, ['_Number']),
+                code,
                 name: getValue(rationSrc, ['_Name']),
                 unit: getValue(rationSrc, ['_Unit']),
                 quantity: getValue(rationSrc, ['_Quantity']),
@@ -523,27 +594,20 @@ const importXML = (() => {
             const feesFromCalcItem = getFeesFromCalculationOfItem(rationSrc);
             ration.fees = mergeFees(fees, feesFromCalcItem);
             ration.rationGLJs = extractRationGLJs(rationSrc);
+            // 没有定额人材机则当作是增加费定额、同时导入后成为量价
+            if (!ration.rationGLJs.length) {
+                const typeData = getVolumePriceTypeByCalcItems(rationSrc);
+                if (typeData) {
+                    Object.assign(ration, typeData);
+                    ration.setFakeProgramID = true;
+                }
+            }
             ration.quantityDetails = extractQuantityDetails(rationSrc);
+            ration.calculationItems = extractCalculationOfItems(rationSrc);
             return ration;
         }
     }
     // 提取措施项目
-    /* function extractCSXM(tenderSrc) {
-        const csxmSrc = getValue(tenderSrc, ['Preliminaries']);
-        const fields = [['DivisionalWorks'], ['WorkElement']];
-        const fees = getFeesFromBasicCost(csxmSrc); // 措施项目的汇总价
-        const items = getItemsRecur(csxmSrc, fields, (itemSrc, curField) => {
-            if (curField[0] === fields[0][0]) {
-                return extractDivisionalWorks(itemSrc, billType.BILL);
-            } else {
-                return extractWorkElement(itemSrc, billType.BILL);
-            }
-        });
-        return {
-            fees,
-            items
-        };
-    } */
     function extractCSXM(tenderSrc) {
         const csxmSrc = getValue(tenderSrc, ['Preliminaries']);
         const fields = [['DivisionalWorks'], ['WorkElement']];
@@ -560,6 +624,21 @@ const importXML = (() => {
             items
         };
     }
+    // 提取额外的暂估材料表
+    // 除了人材机汇总中的is_evaluate = true的材料为暂估材料外,还需要处理ProvisionalMaterialEquipmentItem的数据
+    // 如易达的一些文件,ProvisionalMaterialEquipmentItem中存在人材机汇总没有的数据
+    function extractProvisionalMaterialEquipmentItem(tenderSrc) {
+        const evalGLJs = arrayValue(tenderSrc, ['Sundry', 'ProvisionalMaterialEquipment', 'ProvisionalMaterialEquipmentItem']);
+        return evalGLJs.map(glj => ({
+            code: getValue(glj, ['_Number']),
+            name: getValue(glj, ['_Name']),
+            unit: getValue(glj, ['_Unit']),
+            quantity: getValue(glj, ['_Quantity']),
+            market_price: getValue(glj, ['_Price']),
+            remark: getValue(glj, ['_Remark'])
+        }));
+
+    }
     // 提取其他项目
     function extractOther(tenderSrc) {
         const sundry = getValue(tenderSrc, ['Sundry']);
@@ -569,7 +648,7 @@ const importXML = (() => {
         const sundryFees = [{ fieldName: 'common', totalFee: getValue(sundryCosts, ['_Total']) }];
         const provisionalMaterialEquipment = getValue(sundry, ['ProvisionalMaterialEquipment']);
         return {
-            fees: importFileKind === FileKind.tender ? sundryFees : [],
+            fees: isFullyImport ? sundryFees : [],
             sundryCosts: extractData(sundry, 'SundryCosts', [['SundryCostsGroup'], ['SundryCostsItem']]),
             provisional: extractData(sundry, 'ProvisionalSums', [['ProvisionalSumsGroup'], ['ProvisionalSumsItem']]),
             ProvisionalMaterialEquipment: {
@@ -591,7 +670,7 @@ const importXML = (() => {
             if (isGroup) {
                 source = {
                     name: getValue(itemSrc, ['_Name']),
-                    fees: importFileKind === FileKind.tender ? [{ fieldName: 'common', totalFee: getValue(itemSrc, ['_Total']) }] : [],
+                    fees: isFullyImport ? [{ fieldName: 'common', totalFee: getValue(itemSrc, ['_Total']) }] : [],
                     feeCode: getValue(itemSrc, ['_Code']),
                     remark: getValue(itemSrc, ['_Remark'])
                 };
@@ -602,7 +681,7 @@ const importXML = (() => {
                     unit: getValue(itemSrc, ['_Unit']),
                     calcBase: getValue(itemSrc, ['_QtyFormula']),
                     feeRate: +feeRate !== 100 && feeRate || undefined,
-                    fees: importFileKind === FileKind.tender ? [{ fieldName: 'common', unitFee: getValue(itemSrc, ['_Price']), totalFee: getValue(itemSrc, ['_Total']) }] : [],
+                    fees: isFullyImport ? [{ fieldName: 'common', unitFee: getValue(itemSrc, ['_Price']), totalFee: getValue(itemSrc, ['_Total']) }] : [],
                     feeCode: getValue(itemSrc, ['_Code']),
                     remark: getValue(itemSrc, ['_Remark'])
                 };
@@ -616,7 +695,7 @@ const importXML = (() => {
         function extractData(sundry, srcName, fields, groupExtendMap, itemExtendMap) {
             const src = getValue(sundry, [srcName]);
             return {
-                fees: importFileKind === FileKind.tender ? [{ fieldName: 'common', totalFee: getValue(src, ['_Total']) }] : [],
+                fees: isFullyImport ? [{ fieldName: 'common', totalFee: getValue(src, ['_Total']) }] : [],
                 feeCode: getValue(src, ['_Code']),
                 items: extractItemsRecur(src, fields, (itemSrc, curField) => {
                     const groupExtend = groupExtendMap
@@ -652,7 +731,7 @@ const importXML = (() => {
                 name: getValue(src, ['_Name']),
                 calcBase: getValue(src, ['_QtyFormula']),
                 feeRate: +feeRate !== 100 && feeRate || undefined,
-                fees: importFileKind === FileKind.tender ? [{ fieldName: 'common', totalFee: getValue(src, ['_Total']) }] : [],
+                fees: isFullyImport ? [{ fieldName: 'common', totalFee: getValue(src, ['_Total']) }] : [],
                 feeCode: getValue(src, ['_Code']),
                 remark: getValue(src, ['_Remark'])
             };
@@ -661,15 +740,21 @@ const importXML = (() => {
 
     // 供料方式
     const Provider = {
-        '1': commonConstants.supplyType.ZXCG,
-        '2': commonConstants.supplyType.BFJG,
-        '3': commonConstants.supplyType.WQJG
+        '1': commonConstants.supplyType.ZXCG, // 自行采购
+        '2': commonConstants.supplyType.JGCL, // 甲供材料
+        '3': commonConstants.supplyType.JDYG // 甲定乙供
     };
 
+    // 获取原始编码
+    function getOriginalCode(code) {
+        //编码后面有-\d+的形式,去掉-\d+取前面的字符串作为原始代码
+        return code.replace(/(.*)-\d+$/, '$1');
+    }
+
     // 提取人材机汇总相关(人材机汇总表、承包材料表)
     function extractGLJSummary(tenderSrc) {
         const initData = { gljSummary: [], differentiaSummary: [], exponentialSummary: [] };
-        if (importFileKind !== FileKind.tender) {
+        if (!isFullyImport) {
             return initData;
         }
         const taxType = getValue(tenderSrc, ['_TaxModel']); // 计税方式
@@ -687,11 +772,6 @@ const importXML = (() => {
             return acc;
         }, initData);
 
-        // 获取原始编码
-        function getOriginalCode(code) {
-            //编码后面有-\d+的形式,去掉-\d+取前面的字符串作为原始代码
-            return code.replace(/(.*)-\d+$/, '$1');
-        }
         // TODO (不靠谱) 获取人材机类型数据
         // 导入的源文件没有细化区分人材机类型,这里只是暂时大概给个值。后续会在后端与标准人材机进行配对。也因此无法准确处理补充人材机
         function getTypeData(gljSrc) {
@@ -743,6 +823,9 @@ const importXML = (() => {
         function extractGLJ(gljSrc) {
             countData.projectGLJCount++;
             const code = getValue(gljSrc, ['_Number']);
+            /* if (code.includes('99450680')) {
+                debugger;
+            } */
             return {
                 code,
                 original_code: getOriginalCode(code),
@@ -805,7 +888,7 @@ const importXML = (() => {
     }
     // 提取评标材料
     function extractBidEvaluationSummary(tenderSrc) {
-        if (importFileKind !== FileKind.tender) {
+        if (!isFullyImport) {
             return [];
         }
         const srcList = arrayValue(tenderSrc, ['BidEvaluationMainMaterial']);
@@ -969,6 +1052,18 @@ const importXML = (() => {
         mergeCSXM(tenderData.csxm);
         mergeOther(tenderData.other);
         mergeTax(tenderData.tax);
+        // 需要清空各种原因导入的父项的工程量
+        /* const parentMap = {};
+        mergedBills.forEach(bills => {
+            if (bills.ParentID !== -1) {
+                parentMap[bills.ParentID] = 1;
+            }
+        })
+        mergedBills.forEach(bills => {
+            if (parentMap[bills.ID] && commonUtil.isDef(bills.quantity)) {
+                delete bills.quantity;
+            }
+        }) */
         return mergedBills;
 
         // 合并清单数据
@@ -983,6 +1078,10 @@ const importXML = (() => {
             const fixedBillsFlag = getFlag(fixedBills);
             let preBills = billsTemplate.find(bills => bills.ParentID === fixedBills.ID && bills.NextSiblingID === -1);
             items.forEach(bills => {
+                // 特殊处理绿色施工,否则可能会重复导入(易达绿色施工安全防护措施费的code为AQWMSGF,feeCode为空)
+                if (bills.name === '绿色施工安全防护措施费' && bills.code === 'AQWMSGF') {
+                    bills.feeCode = 'AQWMSGF';
+                }
                 // 计日工需要特殊处理,兼容广联达的数据,广联达的计日工下的人工、材料、机械的费用字典不对,因此用名称匹配
                 let matched;
                 if (fixedBillsFlag === fixedFlag.DAYWORK) {
@@ -995,6 +1094,9 @@ const importXML = (() => {
                 }
                 if (matched) {
                     assignAttr(matched, bills, ['items'], true);
+                    if (commonUtil.isDef(bills.feeRate) && commonUtil.isDef(matched.feeRateID)) {
+                        delete matched.feeRateID; // 不清除的话,赋值过去的feeRate会被后端覆盖
+                    }
                     if (bills.items && bills.items.length) {
                         mergeitems(matched, bills.items);
                         //mergedBills.push(...mergeDataRecur(fixedBills, [bills]))    
@@ -1122,7 +1224,8 @@ const importXML = (() => {
     }
     // 检查清单是否引用了自身,比如广联达导出文件中 材料保管费基数为CLBGF
     function isCalcBaseCycle(bills) {
-        return bills.calcBase.match(new RegExp(`\\b${bills.feeCode}\\b`)); // \b: 匹配前一个字符和后一个字符不全为\w的位置
+        const matched = bills.calcBase.match(new RegExp(`\\b${bills.feeCode}\\b`)); // \b: 匹配前一个字符和后一个字符不全为\w的位置
+        return !!(matched && matched[0]);
     }
     // 转换计算基数
     // 1.有子项数据,则清空基数
@@ -1220,6 +1323,12 @@ const importXML = (() => {
             const hasChild = billsData.some(data => data.ParentID === bills.ID);
             let unitFee = getFee(bills.fees, ['common', 'unitFee']);
             const totalFee = getFee(bills.fees, ['common', 'totalFee']);
+            // 如果费率为0、同时合价不为0,则需要清空计算基数和费率(当作数量单价的形式。易达某文件出现了费率为0、有金额且有基数的数据)
+            // 不这么处理的话,导入后造价计算,此清单金额算出来会是0
+            if (+bills.feeRate === 0 && +totalFee > 0) {
+                bills.calcBase = '';
+                bills.feeRate = null;
+            }
             if (!hasChild && !parseFloat(unitFee) && totalFee && parseFloat(totalFee)) {
                 // 不存工程量
                 if (!bills.quantity || !parseFloat(bills.quantity)) {
@@ -1290,6 +1399,10 @@ const importXML = (() => {
                 projectGLJ.ratios.forEach(ratio => {
                     const matched = projectGLJMap[ratio.code];
                     ratio.code = matched.code;
+                    // 为了后端匹配标准数据,如易达有组成物"99450680-0001"
+                    // 项目人材机、单价文件根据original_code匹配上了标准人材机,修改了gljType等
+                    // mixRatio根据code匹配标准数据匹配不上标准数据,因此type与项目人材机的type不同,导致组成物丢失
+                    ratio.original_code = getOriginalCode(ratio.code);
                     ratio.projectGLJID = projectGLJ.id;
                     ratio.id = IDPlaceholder.ratio++;
                     ratio.unit_price_file_id = tenderData.property.unitPriceFile.id;
@@ -1340,7 +1453,30 @@ const importXML = (() => {
         }
         // 转换暂估价材料
         function transformEvalSummary() {
-            const evalGLJList = tenderData.gljSummary
+            //evalGLJFromOther
+            const existMap = {};
+            const evalItems = [];
+            tenderData.gljSummary.forEach(glj => {
+                if (glj.is_evaluate) {
+                    existMap[glj.code] = true;
+                    evalItems.push(glj);
+                }
+            });
+            tenderData.evalGLJFromOther.forEach(glj => {
+                if (!existMap[glj.code]) {
+                    evalItems.push(glj);
+                }
+            });
+            const evalGLJList = evalItems.map(glj => {
+                const evalGLJ = {};
+                assignAttr(evalGLJ, glj, [
+                    'code', 'name', 'specs', 'unit', 'quantity', 'market_price',
+                    'originPlace', 'vender', 'remark'
+                ]);
+                evalGLJ.seq = evalGLJ.code;
+                return evalGLJ;
+            });
+            /* const evalGLJList = tenderData.gljSummary
                 .filter(glj => glj.is_evaluate)
                 .map(glj => {
                     const evalGLJ = {};
@@ -1350,7 +1486,7 @@ const importXML = (() => {
                     ]);
                     evalGLJ.seq = evalGLJ.code;
                     return evalGLJ;
-                });
+                }); */
             transformRelatedGLJList(evalGLJList, rst.evaluationList, 'is_evaluate');
         }
         // 生成单价文件
@@ -1380,7 +1516,7 @@ const importXML = (() => {
             ration: [],
             rationGLJ: [],
             rationCoe: [],
-            rationQuantityDetails: []
+            rationQuantityDetails: [],
         };
         billsData
             .filter(bills => bills.rations && bills.rations.length)
@@ -1604,7 +1740,10 @@ const importXML = (() => {
             throw '无有效数据';
         }
         const xmlObjMap = {};
+        // 导入易达的文件,经过unzipFile的decodeFileName,xml文件头部变成了<?xml version="1.0" encoding="utf-8"?>,需要将前面的乱码去除,否则DOMParser转换后是null
+        const reg = /.*<\?xml version="1.0" encoding="utf-8"\?>/i;
         for (const fileName in fileMap) {
+            fileMap[fileName] = fileMap[fileName].replace(reg, '<?xml version="1.0" encoding="utf-8"?>');
             // x2js转换xml使用了DomParser接口,会将一些字符实体进行转义。若不想被自动转义,则需要调用escapeXMLEntity
             const xmlStr = escape ? util.escapeXMLEntity(fileMap[fileName]) : fileMap[fileName];
             //将xml格式良好的字符串转换成对象