Преглед изворни кода

Merge branch 'dev' of http://192.168.1.41:3000/maixinrong/Calculation into dev

TonyKang пре 4 година
родитељ
комит
6c64970a20
53 измењених фајлова са 1126 додато и 189 уклоњено
  1. 1 1
      app/const/sign.js
  2. 18 0
      app/const/tourist_permission.js
  3. 2 2
      app/controller/advance_controller.js
  4. 35 0
      app/controller/ledger_controller.js
  5. 2 2
      app/controller/material_controller.js
  6. 5 5
      app/controller/profile_controller.js
  7. 40 5
      app/controller/report_archive_controller.js
  8. 7 4
      app/controller/stage_controller.js
  9. 4 3
      app/controller/stage_extra_controller.js
  10. 58 9
      app/controller/tender_controller.js
  11. 1 1
      app/extend/helper.js
  12. 164 9
      app/lib/sum_load.js
  13. 1 0
      app/middleware/material_check.js
  14. 2 1
      app/middleware/session_auth.js
  15. 1 0
      app/middleware/stage_check.js
  16. 2 0
      app/middleware/tender_check.js
  17. 8 4
      app/public/css/main.css
  18. 1 1
      app/public/js/change_information.js
  19. 3 4
      app/public/js/ledger.js
  20. 2 1
      app/public/js/measure_material.js
  21. 1 1
      app/public/js/revise.js
  22. 119 1
      app/public/js/shares/cs_tools.js
  23. 21 1
      app/public/js/shares/tender_select.js
  24. 40 23
      app/public/js/stage.js
  25. 1 1
      app/public/js/stage_pay.js
  26. 16 8
      app/public/report/js/rpt_custom.js
  27. 2 1
      app/router.js
  28. 37 0
      app/service/change.js
  29. 17 0
      app/service/ledger_audit.js
  30. 17 0
      app/service/material_audit.js
  31. 3 2
      app/service/netcasign.js
  32. 2 2
      app/service/revise_bills.js
  33. 2 1
      app/service/stage_audit.js
  34. 40 0
      app/service/stage_bills.js
  35. 9 0
      app/service/stage_change_final.js
  36. 17 2
      app/service/sum_load_history.js
  37. 20 1
      app/service/tender_tourist.js
  38. 15 14
      app/view/change/information.ejs
  39. 8 8
      app/view/ledger/explode.ejs
  40. 0 1
      app/view/material/audit_modal.ejs
  41. 1 1
      app/view/material/file.ejs
  42. 2 2
      app/view/profile/sign.ejs
  43. 3 3
      app/view/report/index_sign.ejs
  44. 6 1
      app/view/stage/index.ejs
  45. 1 0
      app/view/stage/modal.ejs
  46. 3 1
      app/view/stage_extra/bonus_modal.ejs
  47. 263 41
      app/view/tender/detail.ejs
  48. 67 17
      app/view/tender/detail_modal.ejs
  49. 1 1
      build_min.js
  50. 3 3
      config/config.uat.js
  51. 15 0
      dev4qadx.js
  52. 3 0
      sql/update.sql
  53. 14 0
      sql/update20210715.sql

+ 1 - 1
app/const/sign.js

@@ -10,7 +10,7 @@
 
 const path = {
     oss: 'https://measure-sign-pdf.oss-cn-shenzhen.aliyuncs.com/archive',
-    api: 'http://14.18.158.147:9091/eseal',
+    api: 'http://106.52.243.222:9091/eseal',
 };
 
 

+ 18 - 0
app/const/tourist_permission.js

@@ -0,0 +1,18 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date
+ * @version
+ */
+
+const defaultPermission = {
+    file: 0,
+    tag: 0,
+};
+
+module.exports = {
+    defaultPermission,
+};

+ 2 - 2
app/controller/advance_controller.js

@@ -126,7 +126,7 @@ module.exports = app => {
             if (ctx.advance.status === auditConst.status.uncheck) {
                 if (ctx.session.sessionUser.accountId !== ctx.advance.uid && !ctx.tender.isTourist) {
                     throw '无权访问';
-                } else if (ctx.session.sessionUser.accountId === ctx.advance.uid) {
+                } else if (ctx.session.sessionUser.accountId === ctx.advance.uid || ctx.tender.touristPermission.file) {
                     ctx.advance.filePermission = true;
                 }
             } else {
@@ -134,7 +134,7 @@ module.exports = app => {
                 const cur_uid = ctx.session.sessionUser.accountId;
                 if (auditors.findIndex(item => item.audit_id === cur_uid) === -1 && !ctx.tender.isTourist) {
                     throw '无权访问';
-                } else if (auditors.findIndex(item => item.audit_id === cur_uid) !== -1 || ctx.session.sessionUser.accountId === ctx.advance.uid) {
+                } else if (auditors.findIndex(item => item.audit_id === cur_uid) !== -1 || ctx.session.sessionUser.accountId === ctx.advance.uid || ctx.tender.touristPermission.file) {
                     ctx.advance.filePermission = true;
                 }
             }

+ 35 - 0
app/controller/ledger_controller.js

@@ -753,6 +753,41 @@ module.exports = app => {
             const renderData = {};
             await this.layout('ledger/index.ejs', renderData);
         }
+
+        /**
+         * 审批流程(Get)
+         * @param ctx
+         * @return {Promise<void>}
+         */
+        async ledgerAuditors(ctx) {
+            try {
+                const responseData = {
+                    err: 0, msg: '', data: {},
+                };
+                const tender = ctx.tender;
+                const times = tender.data.ledger_status === auditConst.status.checkNo ? tender.data.ledger_times - 1 : tender.data.ledger_times;
+                const auditors = tender.data.ledger_status === auditConst.status.checkNo && tender.data.user_id !== ctx.session.sessionUser.accountId && !ctx.tender.isTourist ?
+                    await ctx.service.ledgerAudit.getAuditorsWithOwner(tender.id, times) :
+                    await ctx.service.ledgerAudit.getAuditorsWithOwner(tender.id, tender.data.ledger_times);
+                const user = await ctx.service.projectAccount.getAccountInfoById(ctx.tender.data.user_id);
+                const auditHistory = [];
+                if (times >= 1) {
+                    for (let i = 1; i <= times; i++) {
+                        auditHistory.push(await ctx.service.ledgerAudit.getAuditors(ctx.tender.id, i));
+                    }
+                }
+                responseData.data.auditHistory = auditHistory;
+                // 获取审批流程中左边列表
+                responseData.data.auditors = auditors;
+
+                responseData.data.user = user;
+                ctx.body = responseData;
+            } catch (error) {
+                this.log(error);
+                ctx.body = { err: 1, msg: error.toString(), data: null };
+            }
+        }
+
         /**
          * 上传附件
          * @param {Object} ctx - egg全局变量

+ 2 - 2
app/controller/material_controller.js

@@ -98,7 +98,7 @@ module.exports = app => {
                 }
                 responseData.data.auditHistory = auditHistory;
                 // 获取审批流程中左边列表
-                responseData.data.auditors = await ctx.service.materialAudit.getAuditGroupByList(materialInfo.id, times);
+                responseData.data.auditors = await ctx.service.materialAudit.getAuditorsWithOwner(materialInfo.id, times);
 
                 responseData.data.user = await ctx.service.projectAccount.getAccountInfoById(materialInfo.user_id);
                 // 获取原报信息
@@ -883,7 +883,7 @@ module.exports = app => {
             const auditors = await ctx.service.materialAudit.getAuditorsWithOwner(ctx.material.id, ctx.material.times);
             // console.log(auditors);
 
-            if (auditors.findIndex(item => item.aid === accountId) === -1) {
+            if (auditors.findIndex(item => item.aid === accountId) === -1 && !ctx.tender.touristPermission.file) {
                 throw '该调差期当前您无权操作';
             }
             // if (!ctx.material.curAuditor) {

+ 5 - 5
app/controller/profile_controller.js

@@ -289,7 +289,7 @@ module.exports = app => {
                 let signData;
                 switch (data.type) {
                     case 'bind':
-                        signData = await ctx.service.netcasign.getDataByCondition({ keyId: data.updateData.keyId });
+                        signData = await ctx.service.netcasign.getDataByCondition({ pid: ctx.session.sessionProject.id, keyId: data.updateData.keyId });
                         if (signData) {
                             const accountData = await ctx.service.projectAccount.getDataByCondition({ id: signData.uid });
                             throw '该Ukey已绑定于 ' + accountData.name + ', 不可重复绑定';
@@ -302,21 +302,21 @@ module.exports = app => {
                         response.data = await ctx.service.netcasign.getDataByCondition({ uid: sessionUser.accountId });
                         break;
                     case 'unbind':
-                        signData = await ctx.service.netcasign.getDataByCondition({ uid: sessionUser.accountId });
+                        signData = await ctx.service.netcasign.getDataByCondition({ pid: ctx.session.sessionProject.id, uid: sessionUser.accountId });
                         if (!signData) {
                             throw '当前用户不存在绑定证书,解除绑定失败';
                         }
-                        await ctx.service.netcasign.del(sessionUser.accountId);
+                        await ctx.service.netcasign.del(signData.id);
                         break;
                     case 'savesign':
-                        signData = await ctx.service.netcasign.getDataByCondition({ uid: sessionUser.accountId });
+                        signData = await ctx.service.netcasign.getDataByCondition({ pid: ctx.session.sessionProject.id, uid: sessionUser.accountId });
                         if (!signData) {
                             throw '当前用户不存在绑定证书';
                         }
                         await ctx.service.netcasign.save({ sign_base64: data.sign_base64 }, signData.id);
                         break;
                     case 'delsign':
-                        signData = await ctx.service.netcasign.getDataByCondition({ uid: sessionUser.accountId });
+                        signData = await ctx.service.netcasign.getDataByCondition({ pid: ctx.session.sessionProject.id, uid: sessionUser.accountId });
                         if (!signData) {
                             throw '当前用户不存在绑定证书';
                         }

+ 40 - 5
app/controller/report_archive_controller.js

@@ -501,12 +501,14 @@ module.exports = app => {
                         const postData2 = {
                             requestJson: JSON.stringify(data.requestJson),
                         };
-                        const result2 = await ctx.helper.sendMoreRequest(netcaSignApi + '/assemblyPdf', postData2, 'POST');
-                        console.log(result2);
+
+                        const result3 = await this.roundNetcaSign(ctx, postData2);
+                        // const result2 = await ctx.helper.sendMoreRequest(netcaSignApi + '/assemblyPdf', postData2, 'POST');
                         // 上传到oss
-                        if (result2.code === 0) {
-                            const result3 = await ctx.helper.sendMoreRequest(netcaSignApi + result2.data);
-                            const oss_reuslt = await ctx.oss.put('archive/sign/' + data.requestJson.fileName + '.PDF', result3);
+                        // console.log(result3);
+                        if (result3.code === 0) {
+                            // const result3 = await ctx.helper.sendMoreRequest(netcaSignApi + result2.data);
+                            const oss_reuslt = await ctx.oss.put('archive/sign/' + data.requestJson.fileName + '.PDF', result3.data);
                             if (oss_reuslt && oss_reuslt.res && oss_reuslt.res.status === 200) {
                                 if (data.end) {
                                     const versionId = oss_reuslt.res.headers['x-oss-version-id'];
@@ -520,6 +522,8 @@ module.exports = app => {
                             } else {
                                 throw '上传文件失败';
                             }
+                        } else {
+                            throw result3.msg;
                         }
                         break;
                     // 移除签名和已签移除pdf
@@ -547,6 +551,37 @@ module.exports = app => {
 
             ctx.body = response;
         }
+
+        async roundNetcaSign(ctx, postData2, round = 3) {
+            let response = {
+                code: 0,
+                data: '',
+            };
+            // 无法获取到result3,因为result2生成的pdf已损坏,请重复获取result2,直到获取成功或尝试3次失败后报错为止
+            try {
+                const netcaSignApi = signConst.path.api;
+                const result2 = await ctx.helper.sendMoreRequest(netcaSignApi + '/assemblyPdf', postData2, 'POST');
+                if (result2.code === 0) {
+                    const result3 = await ctx.curl(netcaSignApi + result2.data, {
+                        timeout: 300000, // 超时 5分钟中
+                    });
+                    if (result3) {
+                        response.data = result3.data;
+                    } else {
+                        if (round > 0) {
+                            round = round - 1;
+                            response = await this.roundNetcaSign(ctx, postData2, round);
+                        } else {
+                            throw 'pdf获取失败,网证通接口无法生成pdf';
+                        }
+                    }
+                }
+            } catch (error) {
+                response.code = 1;
+                response.msg = error;
+            }
+            return response;
+        }
     }
     return ReportArchiveController;
 };

+ 7 - 4
app/controller/stage_controller.js

@@ -774,9 +774,10 @@ module.exports = app => {
                 }
 
                 // 用户有无权限上传和删除附件
-                renderData.uploadPermission = ((ctx.stage.status === auditConst.status.uncheck || ctx.stage.status === auditConst.status.checkNo) && ctx.session.sessionUser.accountId === ctx.stage.user_id) ||
+                renderData.uploadPermission = (!(ctx.stage.readOnly || ctx.stage.revising) && ((ctx.stage.status === auditConst.status.uncheck || ctx.stage.status === auditConst.status.checkNo) && ctx.session.sessionUser.accountId === ctx.stage.user_id) ||
                     (ctx.stage.status === auditConst.status.checkNoPre && ctx.session.sessionUser.accountId === ctx.stage.curAuditor.aid) ||
-                    (ctx.stage.status === auditConst.status.checking && ctx.stage.curAuditor && ctx.stage.curAuditor.aid === ctx.session.sessionUser.accountId);
+                    (ctx.stage.status === auditConst.status.checking && ctx.stage.curAuditor && ctx.stage.curAuditor.aid === ctx.session.sessionUser.accountId)) ||
+                    (ctx.tender.isTourist && ctx.tender.touristPermission.file);
 
                 if (!ctx.stage.readOnly || ctx.tender.isTourist) {
                     // 计算 本期金额
@@ -1619,7 +1620,8 @@ module.exports = app => {
             };
             let stream;
             try {
-                this._checkStageCanModify(ctx);
+                // this._checkStageCanModify(ctx);
+                this._checkStageCanModifyRe(ctx);
 
                 const parts = ctx.multipart({ autoFields: true });
                 const files = [];
@@ -1726,7 +1728,8 @@ module.exports = app => {
                 data: '',
             };
             try {
-                this._checkStageCanModify(ctx);
+                // this._checkStageCanModify(ctx);
+                this._checkStageCanModifyRe(ctx);
 
                 const data = JSON.parse(ctx.request.body.data);
                 const payInfo = await ctx.service.stagePay.getDataById(data.id);

+ 4 - 3
app/controller/stage_extra_controller.js

@@ -28,7 +28,7 @@ module.exports = app => {
 
         /**
          * 甲供材料(Get)
-         * 
+         *
          * @param {Object} ctx - egg全局变量
          */
         async jgcl (ctx) {
@@ -85,14 +85,15 @@ module.exports = app => {
 
         /**
          * 奖罚金(Get)
-         * 
+         *
          * @param {Object} ctx - egg全局变量
          */
         async bonus (ctx) {
             try {
                 const renderData = {
                     jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.stageExtra.bonus),
-                    auditConst: auditConst,
+                    auditConst,
+                    stage: ctx.stage,
                 };
                 await this.layout('stage_extra/bonus.ejs', renderData, 'stage_extra/bonus_modal.ejs');
             } catch (err) {

+ 58 - 9
app/controller/tender_controller.js

@@ -408,6 +408,39 @@ module.exports = app => {
                     tender.cur_ratio = ctx.helper.mul(ctx.helper.div(tender.gather_tp, tender.sum, 2), 100);
                     tender.other_tp = ctx.helper.sub(ctx.helper.sub(tender.sum, tender.pre_gather_tp), tender.gather_tp);
                     tender.other_ratio = Math.max(0, 100 - tender.pre_ratio - tender.cur_ratio);
+                    tender.end_yf_tp = ctx.helper.add(lastStage.yf_tp, lastStage.pre_yf_tp);
+                    tender.end_sf_tp = ctx.helper.add(lastStage.sf_tp, lastStage.pre_sf_tp);
+                    const change_tp = await ctx.service.change.getChangeTp(tender.id);
+                    tender.undone_tp = ctx.helper.sub(ctx.helper.sub(ctx.helper.add(tender.total_price, change_tp), tender.end_contract_tp), tender.end_qc_tp);
+                    if (lastStage.status === auditConst.stage.status.uncheck) {
+                        const status_name = await this.ctx.service.projectAccount.getAccountInfoById(lastStage.user_id);
+                        lastStage.status_users = status_name ? status_name.name : '';
+                        lastStage.auditors = [];
+                    } else {
+                        lastStage.status = lastStage.status === auditConst.stage.status.checkNoPre ? auditConst.stage.status.checking : lastStage.status;
+                        let cur;
+                        if (lastStage.status === auditConst.stage.status.checked) {
+                            cur = await this.ctx.service.stageAudit.getLastestAuditor(lastStage.id, lastStage.times, auditConst.stage.status.checked);
+                        } else if (lastStage.status === auditConst.stage.status.checking) {
+                            cur = await this.ctx.service.stageAudit.getCurAuditor(lastStage.id, lastStage.times);
+                        } else {
+                            cur = await this.ctx.service.stageAudit.getAuditorByStatus(lastStage.id, lastStage.status, lastStage.times);
+                        }
+                        // const status_name = await this.ctx.service.stageAudit.getAuditorByStatus(lastStage.id, lastStage.status, lastStage.times);
+                        lastStage.status_users = cur ? cur.name : '';
+                        const times = lastStage.status === auditConst.stage.status.checkNo ? lastStage.times - 1 : lastStage.times;
+                        lastStage.auditors = await ctx.service.stageAudit.getFinalAuditGroup(lastStage.id, times);
+                    }
+                } else {
+                    if (tender.ledger_status !== auditConst.ledger.status.uncheck) {
+                        const status_name = await this.ctx.service.ledgerAudit.getStatusName(tender.id, tender.ledger_times);
+                        tender.status_users = status_name ? status_name.name : '';
+                        const times = tender.status === auditConst.ledger.status.checkNo ? tender.times - 1 : tender.times;
+                        tender.auditors = await ctx.service.ledgerAudit.getFinalAuditGroup(tender.id, times);
+                    } else {
+                        const status_name = await this.ctx.service.projectAccount.getAccountInfoById(tender.user_id);
+                        tender.status_users = status_name ? status_name.name : '';
+                    }
                 }
                 const monthProgress = [];
                 for (const s of stages) {
@@ -437,9 +470,9 @@ module.exports = app => {
                 const categoryData = await ctx.service.category.getAllCategory(ctx.session.sessionProject.id);
 
                 // 变更图表数据
-                const change_done_total = await ctx.service.change.getCountByStatus(tender.id, auditConst.filter.status.checked);
-                const change_doing_total = await ctx.service.change.getCountByStatus(tender.id, auditConst.filter.status.checking);
-                const change_uncheck_total = await ctx.service.change.getCountByStatus(tender.id, auditConst.filter.status.uncheck);
+                const change_done_total = await ctx.service.change.getCountByStatus2(tender.id, auditConst.filter.status.checked);
+                const change_doing_total = await ctx.service.change.getCountByStatus2(tender.id, auditConst.filter.status.checking);
+                const change_uncheck_total = await ctx.service.change.getCountByStatus2(tender.id, auditConst.filter.status.uncheck);
                 const change_status_total = [
                     { num: change_uncheck_total, name: '待上报' },
                     { num: change_doing_total, name: '审批中' },
@@ -460,10 +493,17 @@ module.exports = app => {
                     materialData = materials[0];
                     materialData.curAuditor = await ctx.service.materialAudit.getAuditorByStatus(materialData.id, materialData.status, materialData.times);
                     const times = materialData.status === auditConst.material.status.checkNo ? materialData.times - 1 : materialData.times;
-                    materialData.auditors = await ctx.service.materialAudit.getAuditors(materialData.id, times);
+                    materialData.auditors = materialData.status === auditConst.material.status.uncheck ? [] : await ctx.service.materialAudit.getFinalAuditGroup(materialData.id, times);
                 }
                 // 修订完成数目
                 const reviseNum = await ctx.service.ledgerRevise.count({ tid: tender.id, status: auditConst.revise.status.checked });
+                // 计量完成概况
+                // tender.total_price
+                const stage_total = [
+                    { num: tender.end_contract_tp ? tender.end_contract_tp : 0, name: '合同完成' },
+                    { num: tender.end_qc_tp ? tender.end_qc_tp : 0, name: '变更完成' },
+                    { num: tender.undone_tp ? tender.undone_tp : 0, name: '未完成' },
+                ];
                 const renderData = {
                     tenders,
                     categoryData,
@@ -481,10 +521,14 @@ module.exports = app => {
                     change_quality_total,
                     materialData,
                     reviseNum,
+                    stage_total,
                     jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.tender.tenderInfo),
                 };
                 if (ctx.session.sessionUser.is_admin) {
                     renderData.tourists = await ctx.service.tenderTourist.getTourists(tender.id);
+                    for (const t of renderData.tourists) {
+                        t.permission = await ctx.service.tenderTourist.getTouristPermission(t);
+                    }
                     // 获取所有项目参与者
                     const accountList = await ctx.service.projectAccount.getAllDataByCondition({
                         where: { project_id: ctx.session.sessionProject.id, enable: 1 },
@@ -1019,6 +1063,9 @@ module.exports = app => {
                     case 'del':
                         await ctx.service.tenderTourist.removeAudit(data);
                         break;
+                    case 'permission':
+                        await ctx.service.tenderTourist.setPermission(data);
+                        break;
                     default:break;
                 }
                 ctx.body = { err: 0, msg: '', data: info };
@@ -1052,12 +1099,13 @@ module.exports = app => {
 
         async billsTag(ctx) {
             try {
+                const isValidTourist = ctx.tender.isTourist && ctx.tender.touristPermission.tag;
                 if (ctx.stage) {
-                    if (ctx.stage.users.indexOf(this.ctx.session.sessionUser.accountId) < 0)
-                        throw '您无权进行该操作';
+                    const isAuditor = ctx.stage.users.indexOf(this.ctx.session.sessionUser.accountId) >= 0;
+                    if (!isAuditor && !isValidTourist) throw '您无权进行该操作';
                 } else {
-                    if (ctx.tender.ledgerUsers.indexOf(this.ctx.session.sessionUser.accountId) < 0)
-                        throw '您无权进行该操作';
+                    const isAuditor = ctx.tender.ledgerUsers.indexOf(this.ctx.session.sessionUser.accountId) >= 0;
+                    if (!isAuditor && !isValidTourist) throw '您无权进行该操作';
                 }
                 const data = JSON.parse(ctx.request.body.data);
                 const result = await ctx.service.ledgerTag.update(data);
@@ -1144,7 +1192,8 @@ module.exports = app => {
                         ctx.body = {err: 0, msg: '', data: reviseData};
                         break;
                     case 'stage':
-                        ctx.body = await this._sumLoadStage(data.tid, data.lid, data.tenders);
+                        const stageData = await this.ctx.service.stageBills.sumLoad(data.lid, data.tenders);
+                        ctx.body = {err: 0, msg: '', data: stageData};
                         break;
                     default:
                         throw '数据错误';

+ 1 - 1
app/extend/helper.js

@@ -279,7 +279,7 @@ module.exports = {
                 data,
                 dataType,
             }) : await this.ctx.curl(url);
-            console.log(response);
+            // console.log(response);
             if (response.status !== 200) {
                 if (count > 0) {
                     count = count - 1;

+ 164 - 9
app/lib/sum_load.js

@@ -33,13 +33,16 @@ class loadGclBaseTree {
      * @param {String} code - 子项编号
      * @returns {*}
      */
-    findNode(node, parent) {
+    findNode(node, parent, check) {
         parent = parent || this.parent;
         if (!parent.children) return null;
 
         for (const child of parent.children) {
             const checkLeaf = (child.is_leaf && node.is_leaf) || (!child.is_leaf && !node.is_leaf);
-            if (child.b_code === node.b_code && child.name === node.name && child.unit === node.unit && checkLeaf) return child;
+            if (child.b_code === node.b_code && child.name === node.name && child.unit === node.unit
+                && checkLeaf && (!check || check(child, node))) {
+                return child;
+            }
         }
         return null;
     }
@@ -50,11 +53,9 @@ class loadGclBaseTree {
      * @param {Object} parent - 父项
      * @returns {*}
      */
-    addNode(source, parent) {
-        if (source.b_code === '101') console.log('101 parent', parent);
+    addNode(source, parent, check) {
         parent = parent ? parent : this.parent;
-        let node = this.findNode(source, parent);
-        if (source.b_code === '101') console.log('101 cur', node);
+        let node = this.findNode(source, parent, check);
         if (!node) {
             if (!parent.children) parent.children = [];
             node = {
@@ -114,6 +115,7 @@ class updateReviseGclTree extends loadGclBaseTree {
     constructor (ctx, setting) {
         super(ctx, setting);
         this.baseNodes = [];
+        this.errors = [];
     }
     loadBase(datas) {
         datas.sort((x, y) => { return x.level === y.level ? x.order - y.order : x.level - y.level; });
@@ -151,7 +153,6 @@ class updateReviseGclTree extends loadGclBaseTree {
     }
     gather(source, parent) {
         const node = this.addNode(source, parent);
-        if (source.b_code === '207-2-1') console.log('207-2-1', node, source);
         node.sgfh_qty = this.ctx.helper.add(node.sgfh_qty, source.sgfh_qty);
         node.qtcl_qty = this.ctx.helper.add(node.qtcl_qty, source.qtcl_qty);
         node.sjcl_qty = this.ctx.helper.add(node.sjcl_qty, source.sjcl_qty);
@@ -167,10 +168,13 @@ class updateReviseGclTree extends loadGclBaseTree {
                 if (bn.children && bn.children.length > 0) continue;
 
                 if (bn.sjcl_qty < bn.org_sjcl_qty || bn.qtcl_qty < bn.org_qtcl_qty || bn.sgfh_qty < bn.org_sgfh_qty) {
-                    result.errors.push(bn);
+                    result.errors.push({
+                        b_code: bn.b_code, name: bn.name, unit: bn.unit,
+                        sgfh_qty: bn.sgfh_qty, sjcl_qty: bn.sjcl_qty, qtcl_qty: bn.qtcl_qty, qty: bn.quantity, type: 'less',
+                    });
                 } else if (bn.sjcl_qty !== bn.org_sjcl_qty || bn.qtcl_qty !== bn.org_qtcl_qty || bn.sgfh_qty !== bn.org_sgfh_qty) {
                     result.update.push({
-                        id: bn.id, sgfh_qty: bn.sgfh_qty, sjcl_qty: bn.sjcl_qty, qtcl_qty: bn.qtcl_qty, quantity: bn.quantity,
+                        id: bn.id, sgfh_qty: bn.sgfh_qty, sjcl_qty: bn.sjcl_qty, qtcl_qty: bn.qtcl_qty, qty: bn.quantity
                     })
                 }
             }
@@ -187,6 +191,99 @@ class updateReviseGclTree extends loadGclBaseTree {
     }
 }
 
+class gatherStageGclTree extends loadGclBaseTree {
+    constructor (ctx, setting) {
+        super(ctx, setting);
+        this.baseNodes = [];
+    }
+    loadBase(datas) {
+        datas.sort((x, y) => { return x.level === y.level ? x.order - y.order : x.level - y.level; });
+        const Index = {};
+        for (const d of datas) {
+            const parent = this.parent.ledger_id === d.ledger_pid ? this.parent : Index[d.ledger_pid];
+            if (!parent) continue;
+
+            if (!parent.children) parent.children = [];
+            const baseNode = {
+                id: d.id,
+                ledger_id: d.ledger_id,
+                ledger_pid: d.ledger_pid,
+                level: d.level,
+                is_leaf: d.is_leaf,
+                full_path: d.full_path,
+                b_code: d.b_code,
+                name: d.name,
+                unit: d.unit,
+                unit_price: d.unit_price,
+                org_contract_qty: d.contract_qty || 0,
+                org_contract_tp: d.contract_tp || 0,
+                org_order: d.order,
+                contract_qty: 0,
+                contract_tp: 0,
+            };
+            parent.children.push(baseNode);
+            Index[baseNode.ledger_id] = baseNode;
+            this.baseNodes.push(baseNode);
+        }
+    }
+    _gatherChange(node, source) {
+        if (!source.change_detail || source.change_detail.length === 0) return;
+
+        if (!node.change_detail) node.change_detail = [];
+        for (const cd of source.change_detail) {
+            if (!cd.qty) continue;
+            let ncd = node.change_detail.find(x => { return x.cid === cd.cid; });
+            if (!ncd) {
+                ncd = { cid: cd.cid, c_code: cd.c_code };
+                node.change_detail.push(ncd);
+            }
+            ncd.qty = this.ctx.helper.add(ncd.qty, cd.qty);
+        }
+    }
+    gather(source, parent) {
+        parent = parent ? parent : this.parent;
+        const node = this.addNode(source, parent, function (node, source) {
+            return (source.is_tp && node.is_tp) || (!source.is_tp && !node.is_tp);
+        });
+        if (node.is_tp) {
+            node.contract_tp = this.ctx.helper.add(node.contract_tp, source.contract_tp);
+        } else {
+            node.contract_qty = this.ctx.helper.add(node.contract_qty, source.contract_qty);
+            node.contract_tp = this.ctx.helper.mul(node.unit_price, node.contract_qty, this.ctx.tender.info.decimal.tp);
+        }
+        this._gatherChange(node, source);
+        return node;
+    }
+    getUpdateData() {
+        const result = {update: [], errors: []};
+        for (const bn of this.baseNodes) {
+            if (bn.contract_qty !== bn.org_contract_qty || bn.contract_tp !== bn.org_contract_tp) {
+                result.update.push({lid: bn.id, contract_qty: bn.contract_qty, contract_tp: bn.contract_tp });
+            }
+            if (bn.change_detail && bn.change_detail.length > 0) {
+                for (const cd of bn.change_detail) {
+                    result.errors.push({
+                        b_code: bn.b_code, name: bn.name, unit: bn.unit,
+                        c_code: cd.c_code, qty: cd.qty, type: 'qc',
+                    });
+                }
+            }
+        }
+        for (const i of this.items) {
+            result.errors.push({ b_code: i.b_code, name: i.name, unit: i.unit, qty: i.contract_qty, type: 'miss' });
+            if (i.change_detail && i.change_detail.length > 0) {
+                for (const cd of i.change_detail) {
+                    result.errors.push({
+                        b_code: i.b_code, name: i.name, unit: i.unit,
+                        c_code: cd.c_code, qty: cd.qty, type: 'miss-qc',
+                    });
+                }
+            }
+        }
+        return result;
+    }
+}
+
 class sumLoad {
     constructor (ctx) {
         this.ctx = ctx;
@@ -250,6 +347,64 @@ class sumLoad {
         }
         return this.loadTree;
     }
+
+    _loadCurStageAndChange(billsData, curStageBills, curStageChange) {
+        const billsIndex = {};
+        for (const b of billsData) {
+            billsIndex[b.id] = b;
+        }
+        for (const csb of curStageBills) {
+            const b = billsIndex[csb.lid];
+            if (!b) continue;
+            b.contract_qty = csb.contract_qty;
+            b.contract_tp = csb.contract_tp;
+        }
+        for (const csc of curStageChange) {
+            if (!csc.qty) continue;
+
+            const b = billsIndex[csc.lid];
+            if (!b) continue;
+            if (!b.change_detail) b.change_detail = [];
+            let c = b.change_detail.find(x => { return x.cid === csc.cid });
+            if (!c) {
+                c = { cid: csc.cid };
+                b.change_detail.push(c);
+            }
+            c.qty = this.ctx.helper.add(c.qty, csc.qty);
+        }
+    }
+
+    async stageGatherGcl(select, maxId, tenders, defaultData) {
+        this.loadTree = new gatherStageGclTree(this.ctx, {
+            parent: select, maxId, type: 'ledger', defaultData,
+        });
+        const posterity = await this.ctx.service.ledger.getPosterityByParentId(this.ctx.tender.id, select.ledger_id);
+        this.loadTree.loadBase(posterity);
+
+        for (const tender of tenders) {
+            const billsData = await this.ctx.service.ledger.getData(tender.tid);
+            const stage = await this.ctx.service.stage.getDataByCondition({tid: tender.tid, order: tender.stage});
+            if (!stage) throw '选择的期不存在';
+            const curStageData = await this.ctx.service.stageBills.getLastestStageData(tender.tid, stage.id);
+            const curStageChange = await this.ctx.service.stageChangeFinal.getSumLoadFinalData(stage.id);
+            this._loadCurStageAndChange(billsData, curStageData, curStageChange);
+            const billsTree = new Ledger.billsTree(this.ctx, {
+                id: 'ledger_id',
+                pid: 'ledger_pid',
+                order: 'order',
+                level: 'level',
+                rootId: -1,
+                keys: ['id', 'tender_id', 'ledger_id'],
+                stageId: 'id',
+            });
+            billsTree.loadDatas(billsData);
+            for (const top of billsTree.children) {
+                if ([1].indexOf(top.node_type) < 0) continue;
+                this.recusiveLoadGatherGcl(top, null);
+            }
+        }
+        return this.loadTree;
+    }
 }
 
 module.exports = sumLoad;

+ 1 - 0
app/middleware/material_check.js

@@ -80,6 +80,7 @@ module.exports = options => {
                 } else {
                     material.curOrder = material.curAuditor.order;
                 }
+                material.filePermission = this.tender.touristPermission.file || auditorIds.indexOf(accountId) !== -1;
             } else if (auditorIds.indexOf(accountId) !== -1) { // 审批人
                 if (material.status === status.uncheck) {
                     throw '您无权查看该数据';

+ 2 - 1
app/middleware/session_auth.js

@@ -32,7 +32,8 @@ module.exports = options => {
             // 获取用户新建标段权利
             const accountInfo = yield this.service.projectAccount.getDataById(this.session.sessionUser.accountId);
             this.session.sessionUser.permission = accountInfo !== undefined && accountInfo.permission !== '' ? JSON.parse(accountInfo.permission) : null;
-
+            const projectData = yield this.service.project.getDataById(this.session.sessionProject.id);
+            this.session.sessionProject.page_show = yield this.service.projectAccount.getPageShow(projectData.page_show);
 
             // 同步消息
             yield this.service.notify.syncNotifyData();

+ 1 - 0
app/middleware/stage_check.js

@@ -94,6 +94,7 @@ module.exports = options => {
                 } else {
                     stage.curOrder = stage.curAuditor.order;
                 }
+                stage.filePermission = this.tender.touristPermission.file || auditorIds.indexOf(accountId) !== -1;
             } else if (auditorIds.indexOf(accountId) !== -1) { // 审批人
                 if (stage.status === status.uncheck) {
                     throw '您无权查看该数据';

+ 2 - 0
app/middleware/tender_check.js

@@ -64,6 +64,8 @@ module.exports = options => {
             const isTenderTourist = yield this.service.tenderTourist.getDataByCondition({ tid: tender.id, user_id: accountId });
             // 判断访问人是否具有游客身份
             tender.isTourist = isTenderTourist !== null;
+            // 游客权限
+            tender.touristPermission = yield this.service.tenderTourist.getTouristPermission(isTenderTourist);
             if (auditorsId.indexOf(accountId) === -1 && tender.data.user_id !== accountId &&
                 (tenderPermission === null || tenderPermission === undefined || tenderPermission.indexOf('2') === -1) &&
                 stageAuditorsId.indexOf(accountId) === -1 && changeAuditorsId.indexOf(accountId) === -1 &&

+ 8 - 4
app/public/css/main.css

@@ -1298,16 +1298,16 @@ overflow-y: auto;
   position: relative;
 }
 .circle{
-  width: 46px; 
-  height: 46px;
+  width: 42px; 
+  height: 42px;
   border-radius: 50%;
   background: none; 
   border: 3px solid #D7B014;
 }
 .circle-num{
   position: absolute;
-  top: 4px;
-  left: 16px;
+  top: 2px;
+  left: 15px;
   font-size: 24px;
   font-weight: 500;
 }
@@ -1316,4 +1316,8 @@ overflow-y: auto;
   margin-left: 20px;
   margin-top: 10px;
   font-size: 16px;
+}
+.dropdown-wd{
+  width: 80px;
+  text-align: center;
 }

+ 1 - 1
app/public/js/change_information.js

@@ -81,7 +81,7 @@ $(document).ready(() => {
             formData.append('size', filesize);
             formData.append('file[]', file);
         }
-        if (auditList.findIndex(item => item.uid === parseInt(accountId)) === -1) {
+        if (auditList.findIndex(item => item.uid === parseInt(accountId)) === -1 && !touristPermission) {
             return toastr.error('暂无权限上传!');
         }
         postDataWithFile(window.location.pathname + '/file/upload', formData, function (data) {

+ 3 - 4
app/public/js/ledger.js

@@ -1603,7 +1603,7 @@ $(document).ready(function() {
         };
         billsContextMenuOptions.items.importGclBills2Xmj = {
             name: '导入(其他标段)工程量清单至项目节',
-            icon: 'fa-file-excel-o',
+            icon: 'fa-link',
             disabled: function (key, opt) {
                 const node = SpreadJsObj.getSelectObject(ledgerSpread.getActiveSheet());
                 return readOnly || !node
@@ -2359,7 +2359,7 @@ $(document).ready(function() {
         if (!tab.hasClass('active')) {
             // const close = $('.active', '#side-menu').length === 0;
             $('a', '#side-menu').removeClass('active');
-            $('.tab-content .tab-pane.active').removeClass('active');
+            $('.tab-content .tab-select-show.tab-pane.active').removeClass('active');
             tab.addClass('active');
             tabPanel.addClass('active');
             // $('.tab-content .tab-pane').removeClass('active');
@@ -2436,10 +2436,10 @@ $(document).ready(function() {
             } else if (tab.attr('content') === '#check-list') {
                 checkList.spread.refresh();
             } else if (tab.attr('content') === '#fujian') {
-              $('#dqjiedian').addClass('active')
               $('#showAttachment').hide()
               const node = SpreadJsObj.getSelectObject(ledgerSpread.getActiveSheet());
               getNodeList(node.id);
+              getAllList();
             }
         } else { // 收起工具栏
             tab.removeClass('active');
@@ -3722,7 +3722,6 @@ function getAllList(currPageNum = 1) {
       </div>
       </td><td>${att.username}</td></tr>`
   }
-  console.log(attData);
   $('#alllist-table').html(html);
   $('#alllist-table').on('click', 'tr', function() {
       $('#alllist-table tr').removeClass('bg-light')

+ 2 - 1
app/public/js/measure_material.js

@@ -40,6 +40,7 @@ $(function () {
             })
             $('#auditor-list').empty()
             $('#auditor-list').append(auditorsHTML)
+            const leftAuditors = auditors;
             auditHistory.forEach((auditors, idx) => {
                 if(idx === auditHistory.length - 1 && auditHistory.length !== 1) {
                     historyHTML += `<div class="text-right"><a href="javascript: void(0);" id="fold-btn" data-target="show"
@@ -147,7 +148,7 @@ $(function () {
                                             class="pull-right
                                                             ${auditConst.statusClass[auditor.status]}">${auditor.status !== auditConst.status.uncheck ? auditConst.statusString[auditor.status] : ''}
                                             ${auditor.status === auditConst.status.checkNo ? user.name : ''}
-                                            ${auditor.status === auditConst.status.checkNoPre ? auditors[index-1].name : ''}
+                                            ${auditor.status === auditConst.status.checkNoPre ? (leftAuditors.find(item => item.order === auditor.sort-1) ? leftAuditors.find(item => item.order === auditor.sort-1).name : '') : ''}
                                         </span>
                                     </p>
                                     <p class="text-muted mb-0">${auditor.role}</p>

+ 1 - 1
app/public/js/revise.js

@@ -1268,7 +1268,7 @@ $(document).ready(() => {
         });
         billsContextMenuOptions.items.importGclBills2Xmj = {
             name: '更新(其他标段)工程量清单至项目节',
-            icon: 'fa-file-excel-o',
+            icon: 'fa-link',
             disabled: function (key, opt) {
                 const node = SpreadJsObj.getSelectObject(billsSheet);
                 return readOnly || !node || (!_.isNil(node.b_code) && node.b_code !== '');

+ 119 - 1
app/public/js/shares/cs_tools.js

@@ -931,5 +931,123 @@ const showSelectTab = function(select, spread, afterShow) {
         $('#bills-tag-keyword').bind('keydown', e => {if (e.keyCode === 13) searchTagsAndShow();});
 
         return { loadDatas, updateDatasAndShow, show, getBillsTagsColor, getBillsTagsInfo, refreshBillsTagView, }
-    }
+    };
+
+    $.sumLoadMiss = function (setting) {
+        if (!setting.spreadSetting) {
+            setting.spreadSetting = {
+                cols: [
+                    { title: '清单编号', field: 'b_code', width: 80, formatter: '@' },
+                    { title: '清单名称', field: 'name', width: 120, formatter: '@' },
+                    { title: '单位', field: 'unit', width: 50, formatter: '@' },
+                    { title: '数量', field: 'qty', width: 60 },
+                    {
+                        title: '类型', field: 'type', width: 100, getValue: function (x) {
+                            switch (x.type) {
+                                case 'less': return '数量变小';
+                                case 'miss': return '找不到清单';
+                                case 'qc': return '变更';
+                                case 'miss-qc': return '变更(找不到清单)';
+                                default: return '';
+                            }
+                        }
+                    }
+                ],
+                emptyRows: 0,
+                headRows: 1,
+                headRowHeight: [32],
+                defaultRowHeight: 21,
+                headerFont: '12px 微软雅黑',
+                font: '12px 微软雅黑',
+                selectedBackColor: '#fffacd',
+                readOnly: true,
+            };
+        }
+
+        const clearMissData = function () {
+            if (setting.storeKey) removeLocalCache(setting.storeKey);
+        };
+
+        const autoShowHistory = function (show) {
+            if (setting.storeKey) {
+                setLocalCache(setting.storeKey + '-showHis', show.toString());
+            }
+        };
+
+        if (setting.selector && setting.relaSpread) {
+            const resultId = setting.id + '-spread';
+            const obj = $(setting.selector);
+            obj.html(
+                '                        <div id="' + resultId + '" class="sjs-sh">\n' +
+                '                        </div>'
+            );
+            autoFlashHeight();
+
+            const spread = SpreadJsObj.createNewSpread($('#' + resultId)[0]);
+            const sheet = spread.getActiveSheet();
+            SpreadJsObj.initSheet(sheet, setting.spreadSetting);
+
+            const loadMissData = function (data, his = false) {
+                const sourceTree = setting.relaSpread.getActiveSheet().zh_tree;
+                if (!sourceTree) return;
+
+                for (const d of data) {
+                    d.serialNo = sourceTree.getNodeIndex(sourceTree.getItems(d.ledger_id)) + 1;
+                }
+                data.sort(function (a, b) {
+                    return a.serialNo - b.serialNo;
+                });
+
+                SpreadJsObj.loadSheetData(sheet, SpreadJsObj.DataType.Data, data);
+                if (!his && setting.storeKey) {
+                    setLocalCache(setting.storeKey, JSON.stringify(data));
+                }
+                $(setting.tabSelector).show();
+            };
+            const showMissList = function () {
+                const tab = $(setting.tabSelector), tabPanel = $(tab.attr('content'));
+                $('a', '.side-menu').removeClass('active');
+                tab.addClass('active');
+                $('.tab-content .tab-pane').removeClass('active');
+                tabPanel.addClass('active');
+                showSideTools(true);
+                spread.refresh();
+                if (setting.afterShow) setting.afterShow();
+            };
+            const loadHisMissData = function () {
+                if (setting.storeKey) {
+                    const storeStr = getLocalCache(setting.storeKey);
+
+                    const storeData = storeStr ? JSON.parse(storeStr) : [];
+                    if (storeData.length > 0) {
+                        loadMissData(storeData, true);
+                        const showHis = getLocalCache(setting.storeKey + '-showHis');
+                        if (showHis === 'true') {
+                            showMissList();
+                            removeLocalCache(setting.storeKey + '-showHis');
+                        }
+                    }
+                }
+            };
+            return {
+                spread: spread,
+                loadMissData: loadMissData,
+                clearMissData: clearMissData,
+                loadHisMissData: loadHisMissData,
+                show: showMissList,
+                autoShowHistory: autoShowHistory,
+            };
+        } else {
+            const loadMissData = function (data) {
+                if (setting.storeKey) {
+                    setLocalCache(setting.storeKey, JSON.stringify(data));
+                }
+            };
+            return {
+                loadErrorData: loadMissData,
+                clearErrorData: clearMissData,
+                autoShowHistory: autoShowHistory,
+            };
+        }
+    };
 })(jQuery);

+ 21 - 1
app/public/js/shares/tender_select.js

@@ -20,6 +20,8 @@ const TenderSelect = function (setting) {
                     return items;
                 };
                 for (let i = 0; i < tsObj.resultSheet.getRowCount(); i++) {
+                    const items = getItems(tsObj.trArray[i]);
+                    console.log(items);
                     const cellType2 = new spreadNS.CellTypes.ComboBox().itemHeight(10).editorValueType(spreadNS.CellTypes.EditorValueType.value).items(getItems(tsObj.trArray[i]));
                     tsObj.resultSheet.getCell(i, 1).cellType(cellType2);
                 }
@@ -144,6 +146,13 @@ const TenderSelect = function (setting) {
                 headerFont: '12px 微软雅黑',
                 font: '12px 微软雅黑',
                 headColWidth: [],
+                getColor: function (sheet, data, row, col, defaultColor) {
+                    if (data) {
+                        return data.invalid ? '#ddd' : defaultColor;
+                    } else {
+                        return defaultColor;
+                    }
+                }
             };
             if (this.setting.type === 'ledger' || this.setting.type === 'revise') {
                 resultSpreadSetting.cols.push(
@@ -173,10 +182,21 @@ const TenderSelect = function (setting) {
         },
         resetSelect: function () {
             tsObj.trArray.length = 0;
-            if (tsObj.trHistory.tenders) tsObj.trArray.push(...tsObj.trHistory.tenders);
+            if (tsObj.trHistory.tenders) {
+                for (const trh of tsObj.trHistory.tenders) {
+                    const source = tsObj.tenderSourceTree.nodes.find(x => { return x.tid === trh.tid; });
+                    if (source) {
+                        trh.stageCount = source.stageCount;
+                    } else {
+                        trh.invalid = true;
+                    }
+                    tsObj.trArray.push(trh);
+                }
+            }
             tsObj._initSelected();
             SpreadJsObj.loadSheetData(tsObj.tenderSheet, SpreadJsObj.DataType.Tree, tsObj.tenderSourceTree);
             SpreadJsObj.loadSheetData(tsObj.resultSheet, SpreadJsObj.DataType.Data, tsObj.trArray);
+            tsObj._rebuildStageSelect();
             $('#tender-select-hint').hide();
         }
     };

+ 40 - 23
app/public/js/stage.js

@@ -181,7 +181,6 @@ function getHintMsg () {
 
 
 $(document).ready(() => {
-    const tenderSelect = TenderSelect({type: 'stage'});
     const exportExcelSetting = {
         cols: [
             {title: '项目节编号', colSpan: '1', rowSpan: '2', field: 'code', hAlign: 0, width: 145, formatter: '@', cellType: 'tree'},
@@ -832,6 +831,17 @@ $(document).ready(() => {
             if (spSpread) spSpread.refresh();
         },
     });
+    const sumLoadMiss = $.sumLoadMiss({
+        tabSelector: '#sum-load-miss-tab',
+        selector: '#sum-load-miss',
+        relaSpread: slSpread,
+        storeKey: 'stage-slm-' + stage.id,
+        id: 'stage-slm',
+        afterShow: function () {
+            slSpread.refresh();
+            if (spSpread) spSpread.refresh();
+        },
+    });
 
     const stageTreeSpreadObj = {
         loadExprToInput(sheet) {
@@ -1440,6 +1450,24 @@ $(document).ready(() => {
     }
     stageTreeSpreadObj.loadExprToInput(slSpread.getActiveSheet());
     const addTag = newTag({ledgerSheet: slSpread.getActiveSheet(), billsTag});
+    const tenderSelect = TenderSelect({
+        type: 'stage',
+        afterLoad: function (result) {
+            const nodes = stageTree.loadPostStageData(result);
+            stageTreeSpreadObj.refreshTreeNodes(slSpread.getActiveSheet(), nodes);
+            if (detail) {
+                detail.loadStageLedgerUpdateData(result, nodes);
+            } else {
+                stageIm.loadUpdateLedgerData(result, nodes);
+            }
+            if (result.sumLoadHis.errors.length > 0) {
+                sumLoadMiss.loadMissData(result.sumLoadHis.errors);
+                sumLoadMiss.show();
+            } else {
+                sumLoadMiss.clearMissData();
+            }
+        }
+    });
     $.contextMenu({
         selector: '#stage-ledger',
         build: function ($trigger, e) {
@@ -1521,16 +1549,13 @@ $(document).ready(() => {
             importSpr: '---',
             importStageGcl: {
                 name: '导入(其他标段)工程量清单计量数据',
-                icon: 'fa-file-excel-o',
+                icon: 'fa-link',
                 disabled: function (key, opt) {
                     const node = SpreadJsObj.getSelectObject(slSpread.getActiveSheet());
                     return readOnly || !node || (!_.isNil(node.b_code) && node.b_code !== '');
                 },
                 callback: function (key, opt) {
                     tenderSelect.showSelect(SpreadJsObj.getSelectObject(slSpread.getActiveSheet()));
-                },
-                visible: function (key, opt) {
-                    return !readOnly;
                 }
             },
         }
@@ -1999,6 +2024,7 @@ $(document).ready(() => {
 
         errorList.loadHisErrorData();
         checkList.loadHisCheckData();
+        sumLoadMiss.loadHisMissData();
     }, null, true);
     spSpread.bind(spreadNS.Events.EditEnded, stagePosSpreadObj.editEnded);
     spSpread.bind(spreadNS.Events.ClipboardPasting, stagePosSpreadObj.clipboardPasting);
@@ -2163,20 +2189,13 @@ $(document).ready(() => {
             autoFlashHeight();
             slSpread.refresh();
             spSpread.refresh();
-            if (searchLedger) {
-                searchLedger.spread.refresh();
-            }
-            if (detail) {
-                detail.spread.refresh();
-            }
+            if (searchLedger) searchLedger.spread.refresh();
+            if (detail) detail.spread.refresh();
             if (checkedChanges) checkedChanges.refresh();
 
-            if (errorList && errorList.spread) {
-                errorList.spread.refresh();
-            }
-            if (checkList) {
-                checkList.spread.refresh();
-            }
+            if (errorList && errorList.spread) errorList.spread.refresh();
+            if (checkList) checkList.spread.refresh();
+            if (sumLoadMiss) sumLoadMiss.spread.refresh();
         }
     });
 
@@ -2356,12 +2375,9 @@ $(document).ready(() => {
                 detail.spread.refresh();
             }
             if (checkedChanges) checkedChanges.refresh();
-            if (errorList && errorList.spread) {
-                errorList.spread.refresh();
-            }
-            if (checkList) {
-                checkList.spread.refresh();
-            }
+            if (errorList && errorList.spread) errorList.spread.refresh();
+            if (checkList) checkList.spread.refresh();
+            if (sumLoadMiss) sumLoadMiss.spread.refresh();
             window.getSelection ? window.getSelection().removeAllRanges() : document.selection.empty();
         }
     });
@@ -3656,6 +3672,7 @@ $(document).ready(() => {
             if (tab.attr('content') === '#check-list') {
                 checkList.spread.refresh();
             }
+            if (tab.attr('content') === '#sum-load-miss') sumLoadMiss.spread.refresh();
         } else {
             tab.removeClass('active');
             tabPanel.removeClass('active');

+ 1 - 1
app/public/js/stage_pay.js

@@ -34,7 +34,7 @@ function makeAttTable(payNode) {
     let html = '';
     if (attachment !== null) {
         for (const [index, att] of attachment.entries()) {
-            const delhtml = !readOnly && uploadPermission && (parseInt(att.uid) === userID || payNode.uid === userID || (payNode.uid === -1 && userID === stage.user_id))
+            const delhtml = uploadPermission && (parseInt(att.uid) === userID || payNode.uid === userID || (payNode.uid === -1 && userID === stage.user_id))
                 ? '<a class="delete-att text-danger" href="javascript:void(0);" data-payid="'+ id +'" data-attindex="'+ index +'" title="删除"><i class="fa fa-remove "></i></a>'
                 : '';
             html += '<tr><td style="width: 200px">' + att.filename + att.fileext + '</td><td>' + att.username + '</td><td>' + att.in_time + '</td>' +

+ 16 - 8
app/public/report/js/rpt_custom.js

@@ -269,13 +269,16 @@ const rptCustomObj = (function () {
 
         // 初始化
         if (resolve) {
-            setTimeout(() => { $("#gather-select").modal('show'); }, 500);
+            setTimeout(() => { $("#gather-select").modal('show'); }, 1000);
         } else {
             $("#gather-select").modal('show');
         }
 
         $('#gather-select-ok').unbind('click');
-        $('#gather-select-ok').bind('click', () => {rptCustomObj.resetGatherSelect(resolve);});
+        $('#gather-select-ok').bind('click', () => {
+            rptCustomObj.resetGatherSelect(resolve);
+            // $("#gather-select").modal('hide');
+        });
     };
     const initStageSelect = function (gsSetting, gsSelect, rptName, resolve = null) {
         const setting = JSON.parse(gsSetting);
@@ -292,13 +295,16 @@ const rptCustomObj = (function () {
             }
         }
         if (resolve) {
-            setTimeout(() => { $("#stage-select").modal('show'); }, 500);
+            setTimeout(() => { $("#stage-select").modal('show'); }, 1000);
         } else {
             $("#stage-select").modal('show');
         }
 
         $('#stage-select-ok').unbind('click');
-        $('#stage-select-ok').bind('click', () => {rptCustomObj.resetStageSelect(resolve);});
+        $('#stage-select-ok').bind('click', () => {
+            rptCustomObj.resetStageSelect(resolve);
+            // $("#stage-select").modal('hide');
+        });
     };
     const init = function (cDefine, sfData, cSelect, rptName, resolve = null) {
         stageFlow = sfData;
@@ -721,9 +727,10 @@ const rptCustomObj = (function () {
                     params.customSelect.push(data);
                 } else {
                     const chkNode = chkNodes.find(function (x) { return x.refId === rptId});
-                    params.customSelect.push(await comfirmSelectPromise(chkNode ? chkNode.name : '', gather_select));
+                    const select = await comfirmSelectPromise(chkNode ? chkNode.name : '', gather_select)
+                    params.customSelect.push(select);
+                    $('#gather-select').modal('hide');
                 }
-                $('#gather-select').modal('hide');
             } else if (stage_select && stage_select.custom_define && stage_select.custom_define[sStageSelect].enable) {
                 if (rptId === currentRptId) {
                     const data = {};
@@ -731,9 +738,10 @@ const rptCustomObj = (function () {
                     params.customSelect.push(data);
                 } else {
                     const chkNode = chkNodes.find(function (x) { return x.refId === rptId});
-                    params.customSelect.push(await comfirmSelectPromise(chkNode ? chkNode.name : '', stage_select));
+                    const select = await comfirmSelectPromise(chkNode ? chkNode.name : '', stage_select);
+                    params.customSelect.push(select);
+                    $('#stage-select').modal('hide');
                 }
-                $('#stage-select').modal('hide');
             } else {
                 params.customSelect.push(null);
             }

+ 2 - 1
app/router.js

@@ -168,6 +168,7 @@ module.exports = app => {
     app.post('/tender/:id/pos/paste', sessionAuth, tenderCheck, uncheckTenderCheck, 'ledgerController.posPaste');
     app.post('/tender/:id/ledger/deal2sgfh', sessionAuth, tenderCheck, uncheckTenderCheck, 'ledgerController.deal2sgfh');
     app.post('/tender/:id/ledger/check', sessionAuth, tenderCheck, uncheckTenderCheck, 'ledgerController.check');
+    app.post('/tender/:id/measure/ledger/auditors', sessionAuth, tenderCheck, uncheckTenderCheck, 'ledgerController.ledgerAuditors');
 
     // 台账附件
     app.post('/tender/:id/ledger/upload/file', sessionAuth, tenderCheck, uncheckTenderCheck, 'ledgerController.uploadFile');
@@ -515,7 +516,7 @@ module.exports = app => {
 
     // 总分包
     app.post('/tender/:id/ledger/sumLoad', sessionAuth, tenderCheck, uncheckTenderCheck, 'tenderController.sumLoad');
-    app.post('/tender/:id/measure/stage/:order/sumLoad', sessionAuth, tenderCheck, uncheckTenderCheck, 'tenderController.sumLoad');
+    app.post('/tender/:id/measure/stage/:order/sumLoad', sessionAuth, tenderCheck, uncheckTenderCheck, stageCheck, 'tenderController.sumLoad');
     app.post('/tender/:id/revise/info/sumLoad', sessionAuth, tenderCheck, uncheckTenderCheck, reviseCheck, 'tenderController.sumLoad');
 
     // 扫码登录

+ 37 - 0
app/service/change.js

@@ -381,6 +381,43 @@ module.exports = app => {
         }
 
         /**
+         * 获取变更令个数
+         * @param {int} tenderId - 标段id
+         * @param {int} status - 状态
+         * @return {void}
+         */
+        async getCountByStatus2(tenderId, status) {
+            if (status === audit.filter.status.uncheck) {
+                const sql =
+                    'SELECT count(*) AS count FROM ?? WHERE ' +
+                    'tid = ? AND (status = ? OR status = ? OR status = ?)';
+                const sqlParam = [
+                    this.tableName,
+                    tenderId,
+                    audit.flow.status.uncheck,
+                    audit.flow.status.back,
+                    audit.flow.status.revise,
+                ];
+                const result = await this.db.queryOne(sql, sqlParam);
+                return result ? result.count : 0;
+            }
+            return await this.db.count(this.tableName, { tid: tenderId, status });
+        }
+
+        /**
+         * 获取已批复变更令的总金额
+         * @param {int} tenderId - 标段id
+         * @param {int} quality - 变更性质
+         * @return {void}
+         */
+        async getChangeTp(tenderId) {
+            const sql = 'SELECT SUM(`total_price`) AS tp FROM ?? WHERE tid = ? AND status = ?';
+            const sqlParam = [this.tableName, tenderId, audit.flow.status.checked];
+            const result = await this.db.queryOne(sql, sqlParam);
+            return result ? result.tp : 0;
+        }
+
+        /**
          * 上报或重新上报或保存修改功能
          * @param {int} postData - 表单提交的数据
          * @param {int} tenderId - 标段id

+ 17 - 0
app/service/ledger_audit.js

@@ -110,6 +110,23 @@ module.exports = app => {
             return result;
         }
 
+        async getFinalAuditGroup(tenderId, times = 1) {
+            const sql =
+                'SELECT la.`audit_id`, pa.`name`, pa.`company`, pa.`role`, pa.`mobile`, pa.`telephone`, pa.`sign_path`, la.`times`, la.`tender_id`, Max(la.`audit_order`) as max_order ' +
+                'FROM ?? AS la, ?? AS pa ' +
+                'WHERE la.`tender_id` = ? and la.`times` = ? and la.`audit_id` = pa.`id` GROUP BY la.`audit_id` ORDER BY la.`audit_order`';
+            const sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, tenderId, times];
+            const result = await this.db.query(sql, sqlParam);
+            for (const r of result) {
+                const auditor = await this.getDataByCondition({tender_id: tenderId, times: r.times, audit_order: r.max_order});
+                r.status = auditor.status;
+                r.opinion = auditor.opinion;
+                r.begin_time = auditor.begin_time;
+                r.end_time = auditor.end_time;
+            }
+            return result;
+        }
+
         /**
          * 获取标段当前审核人
          *

+ 17 - 0
app/service/material_audit.js

@@ -66,6 +66,23 @@ module.exports = app => {
             return result;
         }
 
+        async getFinalAuditGroup(materialId, times = 1) {
+            const sql =
+                'SELECT la.`aid`, pa.`name`, pa.`company`, pa.`role`, pa.`mobile`, pa.`telephone`, pa.`sign_path`, la.`times`, la.`mid`, Max(la.`order`) as max_order ' +
+                'FROM ?? AS la, ?? AS pa ' +
+                'WHERE la.`mid` = ? and la.`times` = ? and la.`aid` = pa.`id` GROUP BY la.`aid` ORDER BY la.`order`';
+            const sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, materialId, times];
+            const result = await this.db.query(sql, sqlParam);
+            for (const r of result) {
+                const auditor = await this.getDataByCondition({mid: materialId, times: r.times, order: r.max_order});
+                r.status = auditor.status;
+                r.opinion = auditor.opinion;
+                r.begin_time = auditor.begin_time;
+                r.end_time = auditor.end_time;
+            }
+            return result;
+        }
+
         /**
          * 获取 当前审核人
          *

+ 3 - 2
app/service/netcasign.js

@@ -25,6 +25,7 @@ module.exports = app => {
 
         async add(data, uid) {
             const insertData = {
+                pid: this.ctx.session.sessionProject.id,
                 uid,
                 name: data.name,
                 keyId: data.keyId,
@@ -34,8 +35,8 @@ module.exports = app => {
             return operate.affectedRows > 0;
         }
 
-        async del(uid) {
-            return await this.db.delete(this.tableName, { uid });
+        async del(id) {
+            return await this.db.delete(this.tableName, { id });
         }
 
         /**

+ 2 - 2
app/service/revise_bills.js

@@ -290,11 +290,11 @@ module.exports = app => {
                 const result = loadTree.getUpdateData();
 
                 this._cacheMaxLid(this.ctx.tender.id, loadTree.keyNodeId);
-                await this.ctx.service.sumLoadHistory.saveReviseHistory(this.ctx.tender.id, rid, lid, tenders, result.errors);
+                const his = await this.ctx.service.sumLoadHistory.saveReviseHistory(this.ctx.tender.id, rid, lid, tenders, result.errors);
                 if (result.update.length > 0) await conn.updateRows(this.tableName, result.update);
                 if (result.create.length > 0)await conn.insert(this.tableName, result.create);
                 await conn.commit();
-                return result;
+                return { create: result.create, update: result.update, sumLoadHis: his};
             } catch (err) {
                 await conn.rollback();
                 throw (err.stack ? err : '导入工程量数据出错');

+ 2 - 1
app/service/stage_audit.js

@@ -1317,13 +1317,14 @@ module.exports = app => {
 
         async getFinalAuditGroup(stageId, times) {
             const sql =
-                'SELECT la.`aid`, pa.`name`, pa.`company`, pa.`role`, pa.`mobile`, pa.`telephone`, pa.`sign_path`, la.`times`, la.`sid`, la.`aid`, Max(la.`order`) as max_order ' +
+                'SELECT la.`aid`, pa.`name`, pa.`company`, pa.`role`, pa.`mobile`, pa.`telephone`, pa.`sign_path`, la.`times`, la.`sid`, Max(la.`order`) as max_order ' +
                 'FROM ?? AS la, ?? AS pa ' +
                 'WHERE la.`sid` = ? and la.`times` = ? and la.`aid` = pa.`id` GROUP BY la.`aid` ORDER BY la.`order`';
             const sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, stageId, times];
             const result = await this.db.query(sql, sqlParam);
             for (const r of result) {
                 const auditor = await this.getDataByCondition({sid: stageId, times: r.times, order: r.max_order});
+                r.status = auditor.status;
                 r.opinion = auditor.opinion;
                 r.begin_time = auditor.begin_time;
                 r.end_time = auditor.end_time;

+ 40 - 0
app/service/stage_bills.js

@@ -10,6 +10,7 @@
 const calcFields = ['contract_qty', 'qc_qty'];
 const auditConst = require('../const/audit');
 const timesLen = auditConst.stage.timesLen;
+const SumLoad = require('../lib/sum_load');
 
 module.exports = app => {
     class StageBills extends app.BaseService {
@@ -475,6 +476,45 @@ module.exports = app => {
             }
             return { contract_tp, qc_tp };
         }
+
+        async sumLoad(lid, tenders) {
+            const conn = await this.db.beginTransaction();
+            try {
+                const maxId = await this.ctx.service.ledger._getMaxLid(this.ctx.tender.id);
+                const select = await this.ctx.service.ledger.getDataById(lid);
+                const sumLoad = new SumLoad(this.ctx);
+                const loadTree = await sumLoad.stageGatherGcl(select, maxId, tenders);
+                const result = loadTree.getUpdateData();
+
+                const stageBills = await this.getLastestStageData(this.ctx.tender.id, this.ctx.stage.id);
+                const updateStageBills = [], insertStageBills = [];
+                for (const u of result.update) {
+                    const sb = stageBills.find(x => { return x.lid === u.lid; });
+                    if (!sb || sb.times !== this.ctx.stage.curTimes || sb.order !== this.ctx.stage.curOrder) {
+                        u.qc_qty = sb ? sb.qc_qty : null;
+                        u.qc_tp = sb ? sb.qc_tp : null;
+                        u.postil = sb ? sb.postil : null;
+                        u.tid = this.ctx.tender.id;
+                        u.sid = this.ctx.stage.id;
+                        u.said = this.ctx.session.sessionUser.accountId;
+                        u.times = this.ctx.stage.curTimes;
+                        u.order = this.ctx.stage.curOrder;
+                        insertStageBills.push(u);
+                    } else {
+                        u.id = sb.id;
+                        updateStageBills.push(u);
+                    }
+                }
+
+                const his = await this.ctx.service.sumLoadHistory.saveStageHistory(this.ctx.tender.id, this.ctx.stage.id, lid, tenders, result.errors);
+                await conn.commit();
+                return { curStageData: result.update, sumLoadHis: his };
+            } catch (err) {
+                this.ctx.helper.log(err);
+                await conn.rollback();
+                throw (err.stack ? '导入工程量数据出错' : err);
+            }
+        }
     }
 
     return StageBills;

+ 9 - 0
app/service/stage_change_final.js

@@ -82,6 +82,15 @@ module.exports = app => {
             }
             await transaction.delete(this.tableName, { tid: tender.id, sid: stage.id });
         }
+
+        async getSumLoadFinalData(sid) {
+            const sql = 'Select cf.lid, cf.cid, sum(cf.qty) as qty, c.code As c_code' +
+                '  FROM ' + this.tableName + ' cf' +
+                '  Left Join ' + this.ctx.service.change.tableName + ' c ON cf.cid = c.cid' +
+                '  Where cf.sid = ?' +
+                '  Group By cf.lid, cf.cid';
+            return await this.db.query(sql, [sid]);
+        }
     }
 
     return StageChangeFinal;

+ 17 - 2
app/service/sum_load_history.js

@@ -55,6 +55,9 @@ module.exports = app => {
                 tenders: JSON.stringify(tenders), errors: errors ? JSON.stringify(errors) : '',
             };
             await this.db.insert(this.tableName, data);
+            data.tenders = tenders;
+            data.errors = errors;
+            return data;
         }
 
         async saveReviseHistory(tid, rid, lid, tenders, errors) {
@@ -64,6 +67,9 @@ module.exports = app => {
                 tenders: JSON.stringify(tenders), errors: errors ? JSON.stringify(errors) : '',
             };
             await this.db.insert(this.tableName, data);
+            data.tenders = tender;
+            data.errors = errors;
+            return data;
         }
 
         async saveStageHistory(tid, sid, lid, tenders, errors) {
@@ -73,18 +79,27 @@ module.exports = app => {
                 tenders: JSON.stringify(tenders), errors: errors ? JSON.stringify(errors) : '',
             };
             await this.db.insert(this.tableName, data);
+            data.tenders = tenders;
+            data.errors = errors;
+            return data;
         }
 
         async getReviseLastestData(rid) {
             const sql = 'SELECT * FROM ' + this.tableName +
                 '  WHERE id in ( SELECT top 1 id FROM ' + this.tableName + ' WHERE rid = ? order by load_time desc)';
-            return await this.db.query(sql, [rid])
+            const data = await this.db.query(sql, [rid]);
+            if (data.tenders) data.tenders = JSON.parse(data.tenders);
+            if (data.errors) data.errors = JSON.parse(data.errors);
+            return data;
         }
 
         async getStageLastestData(sid) {
             const sql = 'SELECT * FROM ' + this.tableName +
                 '  WHERE id in ( SELECT top 1 id FROM ' + this.tableName + ' WHERE sid = ? order by load_time desc)';
-            return await this.db.query(sql, [sid])
+            const data = await this.db.query(sql, [sid]);
+            if (data.tenders) data.tenders = JSON.parse(data.tenders);
+            if (data.errors) data.errors = JSON.parse(data.errors);
+            return data;
         }
     }
 

+ 20 - 1
app/service/tender_tourist.js

@@ -7,7 +7,7 @@
  * @date 2021/4/8
  * @version
  */
-
+const touristPermissionConst = require('../const/tourist_permission').defaultPermission;
 module.exports = app => {
 
     class TenderTourist extends app.BaseService {
@@ -60,6 +60,25 @@ module.exports = app => {
                 throw err;
             }
         }
+
+        async getTouristPermission(info) {
+            if (info && info.permission) {
+                return JSON.parse(info.permission);
+            }
+            return touristPermissionConst;
+        }
+
+        async setPermission(data) {
+            const transaction = await this.db.beginTransaction();
+            try {
+                await transaction.update(this.tableName, { id: data.id, permission: JSON.stringify(data.permission) });
+                await transaction.commit();
+                return true;
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
+        }
     }
 
     return TenderTourist;

+ 15 - 14
app/view/change/information.ejs

@@ -96,7 +96,7 @@
                             <!--所有附件 翻页-->
                             <a href="javascript: void(0);" data-toggle="modal" class="btn btn-sm btn-primary" id="bach-download"><i class="fa fa-download "></i> 批量下载</a>
                             <a href="javascript:void(0);" class="page-select ml-3" content="pre"><i class="fa fa-chevron-left"></i></a> <span id="currentPage">1</span>/<span id="totalPage">10</span> <a href="javascript:void(0);" class="page-select mr-3" content="next"><i class="fa fa-chevron-right"></i></a>
-                            <% if (auditStatus !== 8) { %>
+                            <% if (auditStatus !== 8 || (auditStatus === 8 && ctx.tender.touristPermission.file)) { %>
                             <a href="#addfujian" data-toggle="modal" class="btn btn-sm btn-light text-primary" data-placement="bottom" title="" data-original-title="上传附件"><i class="fa fa-cloud-upload" aria-hidden="true"></i> 上传附件</a>
                             <% } %>
                             <a href="" id="downloadZip" style="display: none;" download></a>
@@ -388,6 +388,7 @@
     const ledgeStatus = '<%- tender.ledger_status %>';
     const ledgerConsts = JSON.parse('<%- JSON.stringify(ledgerConsts) %>');
     const auditStatus = parseInt('<%- auditStatus %>');
+    const touristPermission = parseInt('<%- ctx.tender.touristPermission.file %>');
     const auditList = JSON.parse(unescape('<%- escape(JSON.stringify(auditList)) %>'));
     const precision = JSON.parse('<%- JSON.stringify(precision) %>');
     const whiteList = JSON.parse('<%- JSON.stringify(whiteList) %>');
@@ -419,23 +420,23 @@
     const changesUid = <%- change.uid %>;
 
     let back_changeInfo = {
-        code: '<%- change.code %>',
-        name: '<%- change.name %>',
-        peg: '<%- change.peg %>',
-        org_name: '<%- change.org_name %>',
-        org_code: '<%- change.org_code %>',
-        new_name: '<%- change.new_name %>',
-        new_code: '<%- change.new_code %>',
-        content: '<%- ctx.helper.replaceRntoBr(change.content) %>',
-        basis: '<%- ctx.helper.replaceRntoBr(change.basis) %>',
-        expr: '<%- ctx.helper.replaceRntoBr(change.expr) %>',
-        memo: '<%- ctx.helper.replaceRntoBr(change.memo) %>',
+        code: JSON.parse(unescape('<%- escape(JSON.stringify(change.code ? change.code : '')) %>')),
+        name: JSON.parse(unescape('<%- escape(JSON.stringify(change.name ? change.name : '')) %>')),
+        peg: JSON.parse(unescape('<%- escape(JSON.stringify(change.peg ? change.peg : '')) %>')),
+        org_name: JSON.parse(unescape('<%- escape(JSON.stringify(change.org_name ? change.org_name: '')) %>')),
+        org_code: JSON.parse(unescape('<%- escape(JSON.stringify(change.org_code ? change.org_code : '')) %>')),
+        new_name: JSON.parse(unescape('<%- escape(JSON.stringify(change.new_name ? change.new_name : '')) %>')),
+        new_code: JSON.parse(unescape('<%- escape(JSON.stringify(change.new_code ? change.new_code : '')) %>')),
+        content: JSON.parse(unescape('<%- escape(JSON.stringify(change.content ? ctx.helper.replaceRntoBr(change.content) : '')) %>')),
+        basis: JSON.parse(unescape('<%- escape(JSON.stringify(change.basis ? ctx.helper.replaceRntoBr(change.basis) : '')) %>')),
+        expr: JSON.parse(unescape('<%- escape(JSON.stringify(change.expr ? ctx.helper.replaceRntoBr(change.expr) : '')) %>')),
+        memo: JSON.parse(unescape('<%- escape(JSON.stringify(change.memo ? ctx.helper.replaceRntoBr(change.memo) : '')) %>')),
         type: '<%- change.type %>',
         class: '<%- change.class %>',
         quality: '<%- change.quality %>',
-        company: '<%- (change.company ? change.company : (companyList && companyList[0] ? companyList[0].name : '')) %>',
+        company: JSON.parse(unescape('<%- escape(JSON.stringify((change.company ? change.company : (companyList && companyList[0] ? companyList[0].name : '')))) %>')),
         charge: '<%- change.charge %>',
-        w_code: '<%- change.w_code %>',
+        w_code: JSON.parse(unescape('<%- escape(JSON.stringify(change.w_code ? change.w_code : '')) %>')),
     };
     let changeInfo = Object.assign({}, back_changeInfo);
     let changeUsedData = JSON.parse(unescape('<%- escape(JSON.stringify(changeUsedData)) %>'));

+ 8 - 8
app/view/ledger/explode.ejs

@@ -108,9 +108,9 @@
             <div class="c-body" id="right-view" style="display: none; width: 33%;">
                 <div class="resize-x" id="right-spr" r-Type="width" div1="#left-view" div2="#right-view" title="调整大小" a-type="percent"><!--调整左右高度条--></div>
                 <div class="tab-content">
-                    <div id="search" class="tab-pane">
+                    <div id="search" class="tab-pane tab-select-show">
                     </div>
-                    <div id="std-xmj" class="tab-pane">
+                    <div id="std-xmj" class="tab-pane tab-select-show">
                         <div class="sjs-bar-2">
                             <div class="pb-1">
                                 <select class="form-control form-control-sm">
@@ -123,7 +123,7 @@
                         <div id="std-xmj-spread" class="sjs-sh-2">
                         </div>
                     </div>
-                    <div id="std-gcl" class="tab-pane">
+                    <div id="std-gcl" class="tab-pane tab-select-show">
                         <div class="sjs-bar-3">
                             <div class="pb-1">
                                 <select class="form-control form-control-sm">
@@ -136,7 +136,7 @@
                         <div id="std-gcl-spread" class="sjs-sh-3">
                         </div>
                     </div>
-                    <div id="deal-bills" class="tab-pane">
+                    <div id="deal-bills" class="tab-pane tab-select-show">
                         <div class="sjs-bar-4">
                             <div class="pb-1">
                                 <% if (dealBillsPermission) { %>
@@ -150,13 +150,13 @@
                         <div id="deal-bills-spread" class="sjs-sh-4">
                         </div>
                     </div>
-                    <div id="bills-tag" class="tab-pane">
+                    <div id="bills-tag" class="tab-pane tab-select-show">
                     </div>
-                    <div id="error-list" class="tab-pane">
+                    <div id="error-list" class="tab-pane tab-select-show">
                     </div>
-                    <div id="check-list" class="tab-pane">
+                    <div id="check-list" class="tab-pane tab-select-show">
                     </div>
-                    <div id="fujian" class="tab-pane">
+                    <div id="fujian" class="tab-pane tab-select-show">
                       <div class="sjs-bar">
                           <ul class="nav nav-tabs">
                               <li class="nav-item">

+ 0 - 1
app/view/material/audit_modal.ejs

@@ -215,7 +215,6 @@
                                             <div class="card-body p-3">
                                                 <div class="card-text">
                                                     <p class="mb-1"><span class="h5"><%- auditor.name %></span>
-                                                        <% console.log(auditor.status) %>
                                                         <span
                                                             class="pull-right
                                                                             <%- auditConst.statusClass[auditor.status] %>"><%- auditor.status !== auditConst.status.uncheck ? auditConst.statusString[auditor.status] : ''%>

+ 1 - 1
app/view/material/file.ejs

@@ -13,7 +13,7 @@
           </div>
         </div>
         <div class="d-inline-block">
-          <% if (!ctx.tender.isTourist && material.filePermission) { %>
+          <% if (material.filePermission) { %>
           <a href="#addfujian" data-toggle="modal" class="btn btn-sm btn-light text-primary" data-toggle="tooltip" data-placement="bottom" title="" data-original-title="添加清单"><i class="fa fa-cloud-upload" aria-hidden="true"></i> 上传附件</a>
           <% } %>
           <a href="javascript: void(0);" data-toggle="modal" class="btn btn-sm btn-light text-primary" id="bach-download"><i class="fa fa-download "></i> 批量下载</a>

+ 2 - 2
app/view/profile/sign.ejs

@@ -28,12 +28,12 @@
                                     <label class="form-check-label" for="sign-type-2">上传签名图</label>
                                 </div>
                             </div>
-                            <div class="form-group" id="show-upload" style="display: none">
+                            <div class="form-group show-upload" style="display: none">
                                 <label>上传签名图</label>
                                 <input type="file" class="form-control-file" id="sign-upload">
                                 <small class="form-text text-danger">图片大小为600x300,格式PNG透明背景。</small>
                             </div>
-                            <div class="form-group" id="show-qrcode">
+                            <div class="form-group show-qrcode">
                                 <label>在线手写签名</label>
                                 <div><img src="/profile/qrCode" width="150"></div>
                                 <small class="form-text text-danger">微信扫码使用在线手写程序</small>

+ 3 - 3
app/view/report/index_sign.ejs

@@ -444,7 +444,7 @@
                                 })
                             }
                         }
-                        function signPdf(res) {
+                        function signPdf(res2) {
                             const certEncode = "";
                             const tbs = utf8_to_b64(signDigest);
                             const _tsaURL = "http://tsa.cnca.net/NETCATimeStampServer/TSAServer.jsp";
@@ -464,7 +464,7 @@
                                 tsaURL: _tsaURL,
                                 includeCertOption: _includeCertOption//整数	包含证书的标识
                             };
-                            const curr_res = res;
+                            const curr_res = res2;
                             NetcaPKI.signedDataSign(params)
                                 .Then(function (res) {
                                     _self.text('签名中,请勿关闭本页...');
@@ -505,7 +505,7 @@
                                             ++curr_sign_page;
                                             setTimeout(function () {
                                                 successGetCertEncodeCallBack(curr_res);
-                                            }, 3000);
+                                            }, 2000);
                                         }
                                     }, function () {
                                         _self.attr('disabled', false);

+ 6 - 1
app/view/stage/index.ejs

@@ -276,7 +276,7 @@
                                     <!-- <% if (!ctx.tender.isTourist && stage.filePermission) { %>
                                     <a href="#upload" data-toggle="modal" data-target="#upload"  class="btn btn-sm btn-outline-primary ml-3">上传</a>
                                     <% } %> -->
-                                    <% if (!ctx.tender.isTourist) { %>
+                                    <% if (!ctx.tender.isTourist || (ctx.tender.isTourist && ctx.tender.touristPermission.file) || stage.filePermission) { %>
                                     <a href="#upload" data-toggle="modal" data-target="#upload"  class="btn btn-sm btn-outline-primary ml-3">上传</a>
                                     <% } %>
                                 </li>
@@ -548,6 +548,8 @@
                     </div>
                     <div id="check-list" class="tab-pane tab-select-show">
                     </div>
+                    <div id="sum-load-miss" class="tab-pane tab-select-show">
+                    </div>
                 </div>
             </div>
         </div>
@@ -578,6 +580,9 @@
                 <li class="nav-item">
                     <a class="nav-link" content="#error-list" id="error-list-tab" href="javascript: void(0);" style="display: none;">错误列表</a>
                 </li>
+                <li class="nav-item">
+                    <a class="nav-link" content="#sum-load-miss" id="sum-load-miss-tab" href="javascript: void(0);" style="display: none;">导入信息</a>
+                </li>
             </ul>
         </div>
     </div>

+ 1 - 0
app/view/stage/modal.ejs

@@ -521,3 +521,4 @@
 <% include ../shares/merge_peg_modal.ejs %>
 <% include ../shares/check_modal2.ejs %>
 <% include ../shares/new_tag_modal.ejs %>
+<% include ../shares/tender_select_modal.ejs %>

+ 3 - 1
app/view/stage_extra/bonus_modal.ejs

@@ -6,10 +6,12 @@
                 <h5 class="modal-title">附件</h5>
             </div>
             <div class="modal-body">
+                <% if (ctx.stage.filePermission) { %>
                 <div class="form-group" id="upload-file-panel">
                     <label for="formGroupExampleInput">大小限制:30MB,支持<span data-toggle="tooltip" data-placement="bottom" title="doc,docx,xls,xlsx,ppt,pptx,pdf">office等文档格式</span>、<span data-toggle="tooltip" data-placement="bottom" title="jpg,png,bmp">图片格式</span>、<span data-toggle="tooltip" data-placement="bottom" title="rar,zip">压缩包格式</span></label>
                     <input type="file" class="" id="upload-file" multiple>
                 </div>
+                <% } %>
                 <div class="modal-height-500" style="overflow:auto;">
                     <table class="table table-sm table-bordered" style="word-break:break-all; table-layout: fixed">
                         <thead>
@@ -31,4 +33,4 @@
             </div>
         </div>
     </div>
-</div>
+</div>

Разлика између датотеке није приказан због своје велике величине
+ 263 - 41
app/view/tender/detail.ejs


+ 67 - 17
app/view/tender/detail_modal.ejs

@@ -1708,17 +1708,34 @@
                         </dl>
                     </div>
                 </div>
-                <div class="card mt-3">
-                    <div class="card-header">
-                        游客列表
-                    </div>
-                    <div class="modal-height-300">
-                        <ul class="list-group list-group-flush" id="tourist-users">
+                <div class="mt-3">
+                    <!-- <div class="card-header">
+                      游客列表
+                    </div> -->
+                    <div class="">
+                        <table class="table table-bordered">
+                            <tr><th>用户</th><th>附件</th><th >书签</th><th>设置</th></tr>
+                            <tbody id="tourist-users">
                             <% for (const t of tourists) { %>
-                                <li class="list-group-item"  data-id="<%- t.user_id %>">
-                                    <a href="javascript:void(0);" class="text-danger pull-right remove-tourist-user" data-id="<%- t.id %>">移除</a><%- t.user_name %>  <small class="text-muted"><%- t.user_role %></small><p class="m-0 ml-2"><small class="text-muted"><%- t.user_company %></small></p></li>
+                                <tr data-id="<%- t.user_id %>">
+                                    <td><b class="col-3 pl-0"><%- t.user_name %></b></td>
+                                    <td>
+                                        <div class="custom-control custom-checkbox mb-2">
+                                            <input type="checkbox" id="<%- t.id %>_file" data-id="<%- t.id %>" name="file" class="custom-control-input set-tourist-permission" <% if(t.permission.file) { %>checked<% } %>>
+                                            <label class="custom-control-label" for="<%- t.id %>_file"></label>
+                                        </div>
+                                    </td>
+                                    <td>
+                                        <div class="custom-control custom-checkbox mb-2">
+                                            <input type="checkbox" id="<%- t.id %>_tag" data-id="<%- t.id %>" name="tag" class="custom-control-input set-tourist-permission" <% if(t.permission.tag) { %>checked<% } %>>
+                                            <label class="custom-control-label" for="<%- t.id %>_tag"></label>
+                                        </div>
+                                    </td>
+                                    <td><a href="javascript:void(0);" data-id="<%- t.id %>" class="text-danger remove-tourist-user">移除</a></td>
+                                </tr>
                             <% } %>
-                        </ul>
+                            </tbody>
+                        </table>
                     </div>
                 </div>
             </div>
@@ -1857,8 +1874,8 @@
                     return item.id === id;
                 });
                 const saIdList = [];
-                for (let i = 0; i < $('#tourist-users li').length; i++) {
-                    saIdList.push(parseInt($('#tourist-users li').eq(i).data('id')));
+                for (let i = 0; i < $('#tourist-users tr').length; i++) {
+                    saIdList.push(parseInt($('#tourist-users tr').eq(i).data('id')));
                 }
                 if (_.includes(saIdList, id)) {
                     toastr.error('该用户已存在列表中,无需重复添加');
@@ -1870,12 +1887,28 @@
                     type: 'add',
                 };
                 postData('/tender/' + cur_tenderid + '/tourist/audit/save', prop, function (data) {
-                    const html = '<li class="list-group-item"  data-id="' + user.id + '">\n' +
-                        '<a href="javascript:void(0);" class="text-danger pull-right remove-tourist-user" data-id="' + data.id + '">移除</a>' + user.name + '  ' +
-                        '<small class="text-muted">' + user.role + '</small><p class="m-0 ml-2"><small class="text-muted">' + user.company + '</small></p></li>';
+                    // const html = '<li class="list-group-item"  data-id="' + user.id + '">\n' +
+                    //     '<a href="javascript:void(0);" class="text-danger pull-right remove-tourist-user" data-id="' + data.id + '">移除</a>' + user.name + '  ' +
+                    //     '<small class="text-muted">' + user.role + '</small><p class="m-0 ml-2"><small class="text-muted">' + user.company + '</small></p></li>';
+                    const html = '<tr data-id="' + user.id + '">\n' +
+                        '                                    <td><b class="col-3 pl-0">' + user.name + '</b></td>\n' +
+                        '                                    <td>\n' +
+                        '                                        <div class="custom-control custom-checkbox mb-2">\n' +
+                        '                                            <input type="checkbox" id="'+ data.id +'_file" data-id="'+ data.id +'" name="file" class="custom-control-input set-tourist-permission">\n' +
+                        '                                            <label class="custom-control-label" for="'+ data.id +'_file"></label>\n' +
+                        '                                        </div>\n' +
+                        '                                    </td>\n' +
+                        '                                    <td>\n' +
+                        '                                        <div class="custom-control custom-checkbox mb-2">\n' +
+                        '                                            <input type="checkbox" id="'+ data.id +'_tag" data-id="'+ data.id +'" name="tag" class="custom-control-input set-tourist-permission">\n' +
+                        '                                            <label class="custom-control-label" for="'+ data.id +'_tag"></label>\n' +
+                        '                                        </div>\n' +
+                        '                                    </td>\n' +
+                        '                                    <td><a href="javascript:void(0);" data-id="' + data.id + '" class="text-danger remove-tourist-user">移除</a></td>\n' +
+                        '                                </tr>';
                     $('#tourist-users').append(html);
                     // 外面显示游客数量
-                    const num = $('#tourist-users li').length;
+                    const num = $('#tourist-users tr').length;
                     if (!$('#tourist-num').hasClass('badge')) {
                         $('#tourist-num').addClass('badge badge-secondary').text(num);
                     } else {
@@ -1895,9 +1928,9 @@
                 };
                 const _self = $(this);
                 postData('/tender/' + cur_tenderid + '/tourist/audit/save', prop, function (data) {
-                    _self.parents('li').remove();
+                    _self.parents('tr').remove();
                     // 外面显示游客数量
-                    const num = $('#tourist-users li').length;
+                    const num = $('#tourist-users tr').length;
                     if (num == 0) {
                         $('#tourist-num').removeClass('badge badge-secondary').text('');
                     } else {
@@ -1906,6 +1939,23 @@
                 });
             }
         });
+
+        // 权限设置
+        $('body').on('click', '#tourist-users .set-tourist-permission', function () {
+            const id = parseInt($(this).data('id'));
+            const permission = {
+                file: ($(this).attr('name') === 'file' ? $(this).is(':checked') : $('#' + id + '_file').is(':checked')) ? 1 : 0,
+                tag: ($(this).attr('name') === 'tag' ? $(this).is(':checked') : $('#' + id + '_tag').is(':checked')) ? 1 : 0,
+            }
+            const prop = {
+                id,
+                type: 'permission',
+                permission,
+            }
+            console.log(prop);
+            postData('/tender/' + cur_tenderid + '/tourist/audit/save', prop, function (data) {
+            });
+        });
     });
 </script>
 <% } %>

+ 1 - 1
build_min.js

@@ -43,7 +43,7 @@ fs.writeFileSync(needMinFileName, Uglyfy.minify(needMinCode, { mangle: true }).c
 
 const needVersionCss = ['/public/css/main'];
 for (const nvc of needVersionCss) {
-    fs.copyFileSync(nvc + '.css', nvc + '.' + version + '.css');
+    fs.copyFileSync(__dirname + '/app' + nvc + '.css', __dirname + '/app' + nvc + '.' + version + '.css');
 }
 
 

+ 3 - 3
config/config.uat.js

@@ -17,9 +17,9 @@ module.exports = appInfo => {
             // 端口号
             port: '3306',
             // 用户名
-            user: 'smartcost',
+            user: 'zh_calc',
             // 密码
-            password: 'zongheng_@)!(_jlzf',
+            password: 'zh@)!(3850calc',
             // 数据库名
             database: 'calculation',
             multipleStatements: true,
@@ -37,7 +37,7 @@ module.exports = appInfo => {
         client: {
             host: '127.0.0.1',
             port: '6379',
-            password: 'zongheng_@)!(_jlzf',
+            password: 'zh@)!(3850calc',
             db: '0',
         },
         agent: true,

+ 15 - 0
dev4qadx.js

@@ -0,0 +1,15 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date
+ * @version
+ */
+
+const fs = require('fs');
+const packageJSON = JSON.parse(fs.readFileSync(__dirname + '/package.json', 'utf8'));
+packageJSON.name = 'calc_dx' ;
+packageJSON.scripts['start-qadx'] = "egg-scripts start --daemon --port 7006";
+fs.writeFileSync(__dirname + '/package.json', JSON.stringify(packageJSON, '', '\t'));

+ 3 - 0
sql/update.sql

@@ -27,3 +27,6 @@ CREATE TABLE `zh_ledger_attachment` (
   `in_time` VARCHAR(15) NOT NULL COMMENT '创建时间',
   `extra_upload` TINYINT(1) NOT NULL DEFAULT '0' COMMENT '是否为审核通过后再次上传的文件,0为否',
   PRIMARY KEY (`id`));
+
+
+ALTER TABLE `zh_tender_tourist` ADD `permission` VARCHAR(1000) NULL DEFAULT NULL COMMENT '游客权限JSON格式' AFTER `user_id`;

+ 14 - 0
sql/update20210715.sql

@@ -0,0 +1,14 @@
+CREATE TABLE `calculation`.`zh_rpt_archive_encryption` (
+  `id` INT NOT NULL AUTO_INCREMENT,
+  `prj_id` INT NULL,
+  `stage_id` INT NULL,
+  `content` JSON NULL,
+  PRIMARY KEY (`id`),
+  INDEX `PRJ_STG` (`prj_id` ASC, `stage_id` ASC))
+COMMENT = '归档文档的需要加密签名的坐标及其他key信息(如签名角色id,签名角色名称等)';
+
+ALTER TABLE `zh_material_list` ADD `expr` VARCHAR(500) NULL DEFAULT '' COMMENT '公式' AFTER `quantity`;
+
+ALTER TABLE `zh_s2b_proj`
+ADD COLUMN `gxby_ratio_valid`  tinyint(1) NULL DEFAULT 0 COMMENT '工序报验,ratio是否生效' AFTER `gxby_status`,
+ADD COLUMN `dagl_ratio_valid`  tinyint(1) NOT NULL DEFAULT 1 COMMENT '工序报验,ratio是否生效' AFTER `dagl_status`;