瀏覽代碼

Merge remote-tracking branch 'remotes/origin/dev' into uat

MaiXinRong 4 年之前
父節點
當前提交
3aa5be0284
共有 97 個文件被更改,包括 6073 次插入712 次删除
  1. 10 0
      app/const/schedule.js
  2. 13 1
      app/const/tender_info.js
  3. 8 6
      app/controller/change_controller.js
  4. 2 1
      app/controller/ledger_controller.js
  5. 69 20
      app/controller/report_controller.js
  6. 127 7
      app/controller/schedule_controller.js
  7. 33 0
      app/controller/signature_controller.js
  8. 5 0
      app/controller/stage_controller.js
  9. 83 2
      app/controller/tender_controller.js
  10. 2 2
      app/lib/analysis_excel.js
  11. 2 0
      app/lib/bills_pos_convert.js
  12. 121 5
      app/lib/rpt_data_analysis.js
  13. 1 1
      app/middleware/url_parse.js
  14. 43 8
      app/public/css/main.css
  15. 39 1
      app/public/css/main_s.css
  16. 43 0
      app/public/css/radar-animation.css
  17. 22 0
      app/public/js/change_information.js
  18. 31 0
      app/public/js/change_information_approval.js
  19. 74 9
      app/public/js/change_information_set.js
  20. 30 0
      app/public/js/change_information_show.js
  21. 201 27
      app/public/js/ledger.js
  22. 201 0
      app/public/js/ledger_check.js
  23. 1 1
      app/public/js/ledger_gather.js
  24. 65 0
      app/public/js/path_tree.js
  25. 35 25
      app/public/js/revise.js
  26. 226 8
      app/public/js/schedule_ledger.js
  27. 521 0
      app/public/js/schedule_plan.js
  28. 345 0
      app/public/js/shares/cs_tools.js
  29. 1 0
      app/public/js/shares/tenders2tree.js
  30. 564 0
      app/public/js/shenpi.js
  31. 287 16
      app/public/js/spreadjs_rela/spreadjs_zh.js
  32. 299 75
      app/public/js/stage.js
  33. 1 0
      app/public/js/stage_pay.js
  34. 2 2
      app/public/report/js/rpt_cfg_const.js
  35. 138 22
      app/public/report/js/rpt_custom.js
  36. 1 1
      app/public/report/js/rpt_main.js
  37. 114 61
      app/public/report/js/rpt_signature.js
  38. 10 1
      app/reports/rpt_component/helper/jpc_helper_field.js
  39. 2 0
      app/reports/rpt_component/jpc_ex.js
  40. 19 16
      app/reports/util/rpt_excel_util.js
  41. 16 3
      app/router.js
  42. 10 4
      app/service/change.js
  43. 14 9
      app/service/change_audit.js
  44. 5 1
      app/service/change_audit_list.js
  45. 78 0
      app/service/ledger_cooperation.js
  46. 102 0
      app/service/ledger_tag.js
  47. 15 0
      app/service/report.js
  48. 44 0
      app/service/report_memory.js
  49. 49 6
      app/service/role_rpt_rel.js
  50. 167 83
      app/service/rpt_gather_memory.js
  51. 31 0
      app/service/schedule.js
  52. 52 0
      app/service/schedule_ledger.js
  53. 65 0
      app/service/schedule_ledger_month.js
  54. 67 0
      app/service/schedule_month.js
  55. 54 15
      app/service/shenpi_audit.js
  56. 17 1
      app/service/stage_audit.js
  57. 7 11
      app/service/stage_detail.js
  58. 0 1
      app/service/stage_pay.js
  59. 0 1
      app/service/tender_info.js
  60. 2 2
      app/view/change/info.ejs
  61. 14 1
      app/view/change/information.ejs
  62. 1 0
      app/view/layout/layout.ejs
  63. 6 1
      app/view/ledger/explode.ejs
  64. 1 1
      app/view/ledger/explode_modal.ejs
  65. 1 1
      app/view/ledger/gather.ejs
  66. 3 3
      app/view/profile/wechat.ejs
  67. 3 3
      app/view/report/index.ejs
  68. 308 13
      app/view/report/rpt_all_popup.ejs
  69. 1 1
      app/view/revise/info.ejs
  70. 1 1
      app/view/revise/info_modal.ejs
  71. 6 6
      app/view/schedule/index.ejs
  72. 16 24
      app/view/schedule/ledger.ejs
  73. 0 21
      app/view/schedule/ledger_modal.ejs
  74. 35 0
      app/view/schedule/modal.ejs
  75. 32 0
      app/view/schedule/plan.ejs
  76. 93 0
      app/view/schedule/plan_modal.ejs
  77. 39 0
      app/view/schedule/stage_gcl.ejs
  78. 48 0
      app/view/schedule/stage_gcl_modal.ejs
  79. 52 0
      app/view/schedule/stage_tp.ejs
  80. 74 0
      app/view/schedule/stage_tp_modal.ejs
  81. 204 0
      app/view/shares/check_modal2.ejs
  82. 0 81
      app/view/shares/ledger_check_modal.ejs
  83. 1 1
      app/view/stage/audit_modal.ejs
  84. 7 2
      app/view/stage/index.ejs
  85. 39 7
      app/view/stage/modal.ejs
  86. 1 1
      app/view/stage/pay_modal.ejs
  87. 125 10
      app/view/tender/detail_modal.ejs
  88. 7 25
      app/view/tender/shenpi.ejs
  89. 45 0
      app/view/tender/shenpi_modal.ejs
  90. 8 0
      app/view/tender/tender_sub_menu.ejs
  91. 8 0
      app/view/tender/tender_sub_mini_menu.ejs
  92. 3 2
      app/view/wap/list.ejs
  93. 131 1
      builder_report_index_define.js
  94. 72 1
      config/web.js
  95. 18 50
      sql/update.sql
  96. 68 0
      sql/update20201126.sql
  97. 16 0
      test/app/lib/rpt_data_analysis.test.js

+ 10 - 0
app/const/schedule.js

@@ -0,0 +1,10 @@
+'use strict';
+
+const plan_mode = {
+    tp: 1,
+    gcl: 2,
+};
+
+module.exports = {
+    plan_mode,
+};

+ 13 - 1
app/const/tender_info.js

@@ -8,7 +8,7 @@
  * @version
  */
 
-const parseInfo = ['deal_info', 'construction_unit', 'tech_param', 'decimal', 'precision', 'deal_param', 'display', 'pay_account', 'shenpi'];
+const parseInfo = ['deal_info', 'construction_unit', 'tech_param', 'decimal', 'precision', 'deal_param', 'display', 'pay_account', 'shenpi', 'bid_info'];
 const arrayInfo = ['chapter'];
 const defaultInfo = {
     // 合同信息
@@ -16,6 +16,9 @@ const defaultInfo = {
         buildName: '',
         dealCode: '',
         dealName: '',
+        projectType: '',
+        dealType: '',
+        finalCode: '',
     },
     // 参建单位
     construction_unit: {
@@ -55,6 +58,14 @@ const defaultInfo = {
         dealPeriod: '',
         startDate: '',
         planEndDate: '',
+        realStartDate: '',
+        realEndDate: '',
+        structureScale: '',
+    },
+    bid_info: {
+        controlPrice: 0,
+        bidPrice: 0,
+        bidStartDate: '',
     },
     // 小数位数
     decimal: {
@@ -97,6 +108,7 @@ const defaultInfo = {
         thousandth: false,
         stage: {
             realComplete: false,
+            correct: true,
         }
     },
     chapter: [

+ 8 - 6
app/controller/change_controller.js

@@ -293,7 +293,7 @@ module.exports = app => {
                     whiteList,
                     auditList,
                     changeList,
-                    tpUnit: ctx.tender.info.decimal.tp,
+                    tpUnit: change.tp_decimal ? change.tp_decimal : ctx.tender.info.decimal.tp,
                     upUnit: ctx.tender.info.decimal.up,
                     authMobile: auth_mobile,
                     shenpiConst,
@@ -559,7 +559,7 @@ module.exports = app => {
                     attList,
                     whiteList,
                     auditList,
-                    tpUnit: ctx.tender.info.decimal.tp,
+                    tpUnit: change.tp_decimal ? change.tp_decimal : ctx.tender.info.decimal.tp,
                     upUnit: ctx.tender.info.decimal.up,
                     authMobile: auth_mobile,
                     shenpiConst,
@@ -637,9 +637,10 @@ module.exports = app => {
                     renderData.auditList3 = auditList3;
 
                     // 获取已选清单
-                    let changeList = await ctx.service.changeAuditList.getAllDataByCondition({ where: { cid: ctx.params.cid } });
+                    // let changeList = await ctx.service.changeAuditList.getAllDataByCondition({ where: { cid: ctx.params.cid } });
+                    const changeList = await ctx.service.changeAuditList.getList(change.cid);
 
-                    changeList = JSON.parse(JSON.stringify(changeList.sort())).sort().sort();
+                    // changeList = JSON.parse(JSON.stringify(changeList.sort())).sort().sort();
                     for (const cl of changeList) {
                         const audit_amount = cl.audit_amount !== null && cl.audit_amount !== '' ? cl.audit_amount.split(',') : '';
                         // 清单表页赋值
@@ -685,9 +686,10 @@ module.exports = app => {
                     renderData.auditList4 = auditList4;
 
                     // 获取已选清单
-                    let changeList = await ctx.service.changeAuditList.getAllDataByCondition({ where: { cid: ctx.params.cid } });
+                    // let changeList = await ctx.service.changeAuditList.getAllDataByCondition({ where: { cid: ctx.params.cid } });
+                    const changeList = await ctx.service.changeAuditList.getList(change.cid);
 
-                    changeList = JSON.parse(JSON.stringify(changeList.sort())).sort().sort();
+                    // changeList = JSON.parse(JSON.stringify(changeList.sort())).sort().sort();
                     for (const cl of changeList) {
                         const audit_amount = cl.audit_amount !== null && cl.audit_amount !== '' ? cl.audit_amount.split(',') : '';
                         // 清单表页赋值

+ 2 - 1
app/controller/ledger_controller.js

@@ -455,7 +455,8 @@ module.exports = app => {
                 const ledgerData = await ctx.service.ledger.getData(ctx.tender.id);
                 const posData = this.ctx.tender.data.measure_type === measureType.tz.value
                     ? await ctx.service.pos.getPosData({ tid: ctx.tender.id }) : [];
-                ctx.body = { err: 0, msg: '', data: { bills: ledgerData, pos: posData } };
+                const ledgerTags = await this.ctx.service.ledgerTag.getDatas(ctx.tender.id);
+                ctx.body = { err: 0, msg: '', data: { bills: ledgerData, pos: posData, tags: ledgerTags } };
             } catch (err) {
                 this.log(err);
                 ctx.body = { err: 1, msg: err.toString(), data: [] };

+ 69 - 20
app/controller/report_controller.js

@@ -49,6 +49,15 @@ module.exports = app => {
             }
         }
 
+        async _chkIfStageAuditor(ctx, stage) {
+            //
+        }
+
+        async _filterStageList(ctx, stageList) {
+            //
+            // throw
+        }
+
         /**
          * 报表显示页面
          *
@@ -61,8 +70,6 @@ module.exports = app => {
                 const pageShow = ctx.session.sessionProject.page_show;
                 const tender = ctx.tender;
                 const stage = ctx.stage;
-                // console.log(tender.data);
-                // console.log(tender.info);
                 let stage_id = -1;
                 let stage_order = -1;
                 let sorder = -1;
@@ -72,6 +79,42 @@ module.exports = app => {
                 const custTreeNodes = await ctx.service.rptTreeNodeCust.getCustFoldersByUserId(this.ctx.session.sessionUser.accountId);
                 const custCfg = await ctx.service.rptCustomizeCfg.getCustomizeCfgByUserId('Administrator');
                 const stageList = await ctx.service.stage.getValidStagesShort(tender.id);
+                // console.log('this.ctx.session.sessionUser.accountId: ' + this.ctx.session.sessionUser.accountId);
+                if (stage && stage.status === auditConst.stage.status.uncheck) {
+                    // 得判断账号是否在审核人列表中(不含原报)
+                    if (stage.auditorList === undefined || stage.auditorList === null) {
+                        stage.auditorList = await ctx.service.stageAudit.getAuditors(ctx.stage.id, ctx.stage.times);
+                    }
+                    let isAudit = false;
+                    for (const audit of stage.auditorList) {
+                        if (audit.aid === this.ctx.session.sessionUser.accountId) {
+                            isAudit = true;
+                            break;
+                        }
+                    }
+                    if (isAudit) {
+                        throw '未上报!';
+                    }
+                } else {
+                    // 剔除未上报期(审核人(非原报人)才需要判断)
+                    for (let idx = stageList.length - 1; idx >= 0; idx--) {
+                        if (stageList[idx].status === auditConst.stage.status.uncheck) {
+                            if (stageList[idx].auditorList === undefined || stageList[idx].auditorList === null) {
+                                stageList[idx].auditorList = await ctx.service.stageAudit.getAuditors(stageList[idx].id, stageList[idx].times);
+                            }
+                            let isAudit = false;
+                            for (const audit of stageList[idx].auditorList) {
+                                if (audit.aid === this.ctx.session.sessionUser.accountId) {
+                                    isAudit = true;
+                                    break;
+                                }
+                            }
+                            if (isAudit) {
+                                stageList.splice(idx, 1);
+                            }
+                        }
+                    }
+                }
                 const prjAccList = await ctx.service.projectAccount.getAllAccountByProjectId(tender.data.project_id);
                 const roleList = await ctx.service.signatureRole.getSignatureRolesByTenderId(tender.id);
                 const usedList = await ctx.service.signatureUsed.getSignatureUsedByTenderId(tender.id);
@@ -771,7 +814,9 @@ async function getAllPagesCommon(ctx, rptTpl, params, option, outputType, baseDi
         }
         const dftOption = params.option || JV.PAGING_OPTION_NORMAL;
         printCom.initialize(rptTpl);
-        // ctx.helper
+        // 在ctx.helper里夹带私货,增加当前期status及times
+        // ctx.helper.current_stage_status = params.stage_status;
+        // ctx.helper.current_stage_times = params.stage_times;
         printCom.analyzeData(ctx.helper, rptTpl, tplData, defProperties, dftOption, outputType, customSelect);
         // console.log(JSON.stringify(rptTpl));
         const maxPages = printCom.totalPages;
@@ -1148,27 +1193,31 @@ function fillWaterMark(pageRstArray) {
 async function encodeSignatureDataUri(roleRel, baseDir) {
     if (roleRel) {
         for (const singleRoleRel of roleRel) {
-            const roleRelContent = JSON.parse(singleRoleRel.rel_content);
-            for (const role of roleRelContent) {
-                // console.log(role);
-                if (role.sign_path !== '') {
-                    const filePath = baseDir + '/app' + role.sign_path;
-                    try {
-                        const res = await isFileExisted(filePath);
-                        if (res) {
-                            const bData = fs.readFileSync(filePath);
-                            const base64Str = bData.toString('base64');
-                            const datauri = 'data:image/png;base64,' + base64Str;
-                            role.sign_pic = datauri;
-                        } else {
-                            console.log('文件不存在:' + filePath);
+            if (singleRoleRel.rel_content !== null && singleRoleRel.rel_content !== undefined && singleRoleRel.rel_content !== '') {
+                const roleRelContent = JSON.parse(singleRoleRel.rel_content);
+                for (const role of roleRelContent) {
+                    // console.log(role);
+                    if (role.sign_path !== '') {
+                        const filePath = baseDir + '/app' + role.sign_path;
+                        try {
+                            const res = await isFileExisted(filePath);
+                            if (res) {
+                                const bData = fs.readFileSync(filePath);
+                                const base64Str = bData.toString('base64');
+                                const datauri = 'data:image/png;base64,' + base64Str;
+                                role.sign_pic = datauri;
+                            } else {
+                                console.log('文件不存在:' + filePath);
+                            }
+                        } catch (err) {
+                            console.error(err);
                         }
-                    } catch (err) {
-                        console.error(err);
                     }
                 }
+                singleRoleRel.rel_content = JSON.stringify(roleRelContent);
+            } else {
+                singleRoleRel.rel_content = [];
             }
-            singleRoleRel.rel_content = JSON.stringify(roleRelContent);
         }
     }
 }

+ 127 - 7
app/controller/schedule_controller.js

@@ -10,18 +10,33 @@
 
 const moment = require('moment');
 const measureType = require('../const/tender').measureType;
+const scheduleConst = require('../const/schedule');
 const billsPosConvert = require('../lib/bills_pos_convert');
+const _ = require('lodash');
 
 module.exports = app => {
     class ScheduleController extends app.BaseController {
+
+        async _getSelectedLedgerList(ctx) {
+            const scheduleLedgerList = await ctx.service.scheduleLedger.getAllDataByCondition({ where: { tid: ctx.tender.id } });
+            return _.map(scheduleLedgerList, 'ledger_id');
+        }
+
+        async _getLastPlanMonth(ctx) {
+            const lastMonth = await ctx.service.scheduleMonth.getLastPlanMonth();
+            return lastMonth && lastMonth[0] && lastMonth[0].yearmonth ? lastMonth[0].yearmonth : null;
+        }
+
         async index(ctx) {
             try {
                 const renderData = {
                     tender: ctx.tender.data,
                     tenderMenu: this.menu.tenderMenu,
+                    planMonth: await this._getLastPlanMonth(ctx),
+                    scheduleLedgerList: await this._getSelectedLedgerList(ctx),
                     preUrl: '/tender/' + ctx.tender.id,
                 };
-                await this.layout('schedule/index.ejs', renderData);
+                await this.layout('schedule/index.ejs', renderData, 'schedule/modal.ejs');
             } catch (err) {
                 this.log(err);
                 ctx.redirect(this.menu.menu.dashboard.url);
@@ -34,9 +49,60 @@ module.exports = app => {
                 tender: tender.data,
                 tenderInfo: tender.info,
                 measureType,
+                scheduleLedgerList: await this._getSelectedLedgerList(ctx),
                 jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.schedule.ledger),
             };
-            await this.layout('schedule/ledger.ejs', renderData, 'schedule/ledger_modal.ejs');
+            await this.layout('schedule/ledger.ejs', renderData);
+        }
+
+        async plan(ctx) {
+            const tender = ctx.tender;
+            const schedule = await ctx.service.schedule.getDataByCondition({ tid: tender.id });
+            const scheduleMonth = await ctx.service.scheduleMonth.getAllDataByCondition({ where: { tid: tender.id }, orders: [['yearmonth', 'asc']] });
+            const renderData = {
+                tender: tender.data,
+                tenderInfo: tender.info,
+                schedule,
+                scheduleMonth,
+                planMonth: await this._getLastPlanMonth(ctx),
+                measureType,
+                mode: scheduleConst.plan_mode,
+                scheduleLedgerList: await this._getSelectedLedgerList(ctx),
+                jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.schedule.plan),
+            };
+            await this.layout('schedule/plan.ejs', renderData, 'schedule/plan_modal.ejs');
+        }
+
+        async stageTp(ctx) {
+            const tender = ctx.tender;
+            const schedule = await ctx.service.schedule.getDataByCondition({ tid: tender.id });
+            const scheduleMonth = await ctx.service.scheduleMonth.getAllDataByCondition({ where: { tid: tender.id }, orders: [['yearmonth', 'asc']] });
+            const renderData = {
+                tender: tender.data,
+                tenderInfo: tender.info,
+                schedule,
+                scheduleMonth,
+                measureType,
+                scheduleLedgerList: await this._getSelectedLedgerList(ctx),
+                jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.schedule.stageTp),
+            };
+            await this.layout('schedule/stage_tp.ejs', renderData, 'schedule/stage_tp_modal.ejs');
+        }
+
+        async stageGcl(ctx) {
+            const tender = ctx.tender;
+            const schedule = await ctx.service.schedule.getDataByCondition({ tid: tender.id });
+            const scheduleMonth = await ctx.service.scheduleMonth.getAllDataByCondition({ where: { tid: tender.id }, orders: [['yearmonth', 'asc']] });
+            const renderData = {
+                tender: tender.data,
+                tenderInfo: tender.info,
+                schedule,
+                scheduleMonth,
+                measureType,
+                scheduleLedgerList: await this._getSelectedLedgerList(ctx),
+                jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.schedule.stageGcl),
+            };
+            await this.layout('schedule/stage_gcl.ejs', renderData, 'schedule/stage_gcl_modal.ejs');
         }
 
         /**
@@ -48,17 +114,71 @@ module.exports = app => {
         async loadLedgerData(ctx) {
             try {
                 const ledgerData = await ctx.service.ledger.getData(ctx.tender.id);
-                const posData = this.ctx.tender.data.measure_type === measureType.tz.value
-                    ? await ctx.service.pos.getPosData({ tid: ctx.tender.id }) : [];
-                const convert = new billsPosConvert(ctx);
-                convert.loadData(ledgerData, posData, []);
-                const result = await convert.convert();
+                // const posData = ctx.tender.data.measure_type === measureType.tz.value
+                //     ? await ctx.service.pos.getPosData({ tid: ctx.tender.id }) : [];
+                // const convert = new billsPosConvert(ctx);
+                // convert.loadData(ledgerData, posData, []);
+                // const result = await convert.convert();
+                const scheduleLedgerMonthData = await ctx.service.scheduleLedgerMonth.getAllDataByCondition({ tid: ctx.tender.id });
+                ctx.body = { err: 0, msg: '', data: { bills: ledgerData, slm: scheduleLedgerMonthData } };
+            } catch (err) {
+                this.log(err);
+                ctx.body = { err: 1, msg: err.toString(), data: [] };
+            }
+        }
+
+        /**
+         * 台账选中提交(Ajax)
+         *
+         * @param ctx
+         * @return {Promise<void>}
+         */
+        async saveLedger(ctx) {
+            try {
+                const data = JSON.parse(ctx.request.body.data);
+                const result = await ctx.service.scheduleLedger.saveLedger(data);
                 ctx.body = { err: 0, msg: '', data: result };
             } catch (err) {
                 this.log(err);
                 ctx.body = { err: 1, msg: err.toString(), data: [] };
             }
         }
+
+        /**
+         * 计划进度计算方式提交(Ajax)
+         *
+         * @param ctx
+         * @return {Promise<void>}
+         */
+        async savePlan(ctx) {
+            try {
+                const data = JSON.parse(ctx.request.body.data);
+                const responseData = {
+                    err: 0,
+                    msg: '',
+                    data: {},
+                };
+                switch (data.type) {
+                    case 'mode':
+                        responseData.data = await ctx.service.schedule.saveMode(data.postData);
+                        break;
+                    case 'addmonth':
+                        responseData.data = await ctx.service.scheduleMonth.add(data.postData);
+                        break;
+                    case 'delmonth':
+                        responseData.data = await ctx.service.scheduleMonth.del(data.postData);
+                        break;
+                    case 'ledger_edit':
+                        responseData.data = await ctx.service.scheduleLedgerMonth.save(data.postData);
+                        break;
+                    default: throw '参数有误';
+                }
+                ctx.body = responseData;
+            } catch (err) {
+                this.log(err);
+                ctx.body = { err: 1, msg: err.toString(), data: null };
+            }
+        }
     }
 
     return ScheduleController;

+ 33 - 0
app/controller/signature_controller.js

@@ -55,6 +55,39 @@ module.exports = app => {
         }
 
         /**
+         * 获取多个标段签名角色关联
+         *
+         * @param {Object} ctx - egg全局context
+         * @return {void}
+         */
+        async getMultiRoleRptRels(ctx) {
+            const params = JSON.parse(ctx.request.body.params);
+            const conParams = params.selectedTenders;
+            const rst = await ctx.service.roleRptRel.getCrossTenderRoleRptRels(conParams);
+            ctx.body = { data: rst };
+            ctx.status = 201;
+        }
+
+        /**
+         * 跨标段更新签名角色关联
+         *
+         * @param {Object} ctx - egg全局context
+         * @return {void}
+         */
+        async updateCrossTendersRoleRelationship(ctx) {
+            const params = JSON.parse(ctx.request.body.params);
+            const conParams = params.selectedTenders;
+            // conParams.push([params.tender_id, params.stage_id, params.rpt_id]); // 传过来的数据是额外项目的,还需要加上自己一个
+            const roleRel = params.rel_content;
+            // updateMultiRoleRelationship
+            // console.log(params.rel_content);
+            await ctx.service.roleRptRel.updateRoleRelationship(params.id, params.tender_id, params.rpt_id, params.stage_id, params.rel_content); // 传过来的数据是额外项目的,还需要单独处理当前的
+            const rst = await ctx.service.roleRptRel.updateMultiRoleRelationship(conParams, roleRel);
+            ctx.body = { data: rst };
+            ctx.status = 201;
+        }
+
+        /**
          * 更新最近使用签名
          *
          * @param {Object} ctx - egg全局context

+ 5 - 0
app/controller/stage_controller.js

@@ -324,6 +324,11 @@ module.exports = app => {
                             responseData.data.dealBills = await ctx.service.dealBills.getAllDataByCondition({
                                 where: { tender_id: this.ctx.tender.id },
                             });
+                        case 'tag':
+                            responseData.data.tags = await ctx.service.ledgerTag.getDatas(ctx.tender.id, ctx.stage.id);
+                        case 'cooperation':
+                            responseData.data.cooperation = await this.ctx.service.ledgerCooperation.getValidData(
+                                ctx.tender.id, ctx.session.sessionUser.accountId);
                             break;
                     }
                 }

+ 83 - 2
app/controller/tender_controller.js

@@ -16,6 +16,10 @@ const auditConst = require('../const/audit');
 const shenpiConst = require('../const/shenpi');
 const accountGroup = require('../const/account_group').group;
 const accountPermission = require('../const/account_permission');
+const measureType = require('../const/tender').measureType;
+const billsPosConvert = require('../lib/bills_pos_convert');
+const path = require('path');
+const sendToWormhole = require('stream-wormhole');
 
 module.exports = app => {
 
@@ -796,14 +800,25 @@ module.exports = app => {
                 });
             }
             const categoryData = await ctx.service.category.getAllCategory(ctx.session.sessionProject.id);
+            // 是否修订中
+            const lastRevise = await ctx.service.ledgerRevise.getLastestRevise(ctx.tender.id);
+            const revising = (lastRevise && lastRevise.status !== auditConst.revise.status.checked) || false;
+
+            const cooperationNum = await ctx.service.ledgerCooperation.count({ tid: ctx.tender.id, status: 1 });
+
             const renderData = {
                 shenpi: shenpiConst,
                 accountList,
                 accountGroup: accountGroupList,
                 tenders,
                 categoryData,
+                auditConst,
+                revising,
+                measureType,
+                cooperationNum,
+                jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.tender.shenpi),
             };
-            await this._list('tender/shenpi.ejs', renderData, 'tender/shenpi_modal.ejs');
+            await this.layout('tender/shenpi.ejs', renderData, 'tender/shenpi_modal.ejs');
         }
 
         async saveTenderInfoShenpi(ctx) {
@@ -828,6 +843,10 @@ module.exports = app => {
                 // } else {
                 const postData = ctx.tender.info.shenpi;
                 postData[data.code] = data.status;
+                if (data.code === shenpiConst.sp_lc[shenpiConst.sp_type.stage - 1].code) {
+                    const status = parseInt(data.status) === shenpiConst.sp_status.gdspl ? 1 : 0;
+                    await ctx.service.ledgerCooperation.changeAllStatus(status);
+                }
                 // }
                 // console.log(postData);
                 await ctx.service.tenderInfo.saveTenderInfo(ctx.tender.id, { shenpi: postData });
@@ -854,6 +873,7 @@ module.exports = app => {
                 if (ctx.session.sessionUser.is_admin === 0) {
                     throw '你没有权限修改审批流程';
                 }
+                let info = '';
                 switch (data.type) {
                     case 'add':
                         const result = await ctx.service.shenpiAudit.addAudit(data);
@@ -870,14 +890,42 @@ module.exports = app => {
                     case 'copy2os':
                         await ctx.service.shenpiAudit.copyAudit2otherShenpi(data);
                         break;
+                    case 'pwd':
+                        info = await ctx.service.ledgerCooperation.save(data);
+                        break;
                     default:break;
                 }
-                ctx.body = { err: 0, msg: '' };
+                ctx.body = { err: 0, msg: '', data: info };
             } catch (err) {
                 this.log(err);
                 ctx.body = this.ajaxErrorBody(err, '保存审批流程设置失败');
             }
         }
+
+        /**
+         * 签名,上传图片 (Ajax)
+         * @param ctx
+         * @return {Promise<void>}
+         */
+        async saveCooperateSign(ctx) {
+            try {
+                const stream = await ctx.getFileStream();
+                const create_time = Date.parse(new Date()) / 1000;
+                const id = stream.fields.id;
+                const fileInfo = path.parse(stream.filename);
+                const fileName = path.join('public/upload', ctx.tender.id.toString(), 'sign', 'signImg_' + create_time + fileInfo.ext);
+                await ctx.helper.saveStreamFile(stream, path.join(this.app.baseDir, 'app', fileName));
+                if (stream) {
+                    await sendToWormhole(stream);
+                }
+                await ctx.service.ledgerCooperation.saveSign(id, fileName);
+                ctx.body = { err: 0, msg: '', data: fileName };
+            } catch (err) {
+                this.log(err);
+                ctx.body = { err: 1, msg: err.toString(), data: null };
+            }
+        }
+
         /**
          * 拷贝设置
          * @param {object} ctx - 上下文
@@ -893,6 +941,39 @@ module.exports = app => {
                 ctx.body = this.ajaxErrorBody(error, '保存审批流程设置失败');
             }
         }
+
+        /**
+         * 获取部位明细数据(Ajax)
+         *
+         * @param ctx
+         * @return {Promise<void>}
+         */
+        async loadLedgerData(ctx) {
+            try {
+                const ledgerData = await ctx.service.ledger.getData(ctx.tender.id);
+                const posData = ctx.tender.data.measure_type === measureType.tz.value
+                    ? await ctx.service.pos.getPosData({ tid: ctx.tender.id }) : [];
+                const convert = new billsPosConvert(ctx);
+                convert.loadData(ledgerData, posData, []);
+                const result = await convert.convert();
+                const ledgerCooperationList = await ctx.service.ledgerCooperation.getAllDataByCondition({ where: { tid: ctx.tender.id, status: 1 } });
+                ctx.body = { err: 0, msg: '', data: { ledgerList: result, ledgerCooperationList } };
+            } catch (err) {
+                this.log(err);
+                ctx.body = { err: 1, msg: err.toString(), data: [] };
+            }
+        }
+
+        async billsTag(ctx) {
+            try {
+                const data = JSON.parse(ctx.request.body.data);
+                const result = await ctx.service.ledgerTag.update(data);
+                ctx.body = { err: 0, msg: '', data: result };
+            } catch (err) {
+                this.log(err);
+                ctx.body = this.ajaxErrorBody(err, '书签数据错误');
+            }
+        }
     }
 
     return TenderController;

+ 2 - 2
app/lib/analysis_excel.js

@@ -279,9 +279,9 @@ class ImportBaseTree {
         const splitChar = this.splitChar;
         const firstPart = this.roots[0];
         firstPart.children.sort(function (a, b) {
-            if (a.code === '') {
+            if (!a.code) {
                 return 1;
-            } else if (b.code === '') {
+            } else if (!b.code) {
                 return -1;
             }
             const codeA = a.code.split(splitChar);

+ 2 - 0
app/lib/bills_pos_convert.js

@@ -241,6 +241,8 @@ class BillsPosConvert {
             node.real_tp = this.ctx.helper.add(node.real_tp, child.real_tp);
             node.estimate_tp = this.ctx.helper.add(node.estimate_tp, child.estimate_tp);
         }
+        if (node.dgn_qty1)
+            node.dgn_price = this.ctx.helper.div(node.total_price, node.dgn_qty1, this.ctx.tender.info.decimal.up);
         node.final_tp = this.ctx.helper.add(node.total_price, node.end_qc_tp);
         node.end_gather_percent = this.ctx.helper.mul(this.ctx.helper.div(node.end_gather_tp, node.final_tp, 4), 100);
     }

+ 121 - 5
app/lib/rpt_data_analysis.js

@@ -627,10 +627,10 @@ const join = {
                 for (const k of options.keyFields) {
                     switch (k.type) {
                         case 'string':
-                            if (x[k.main] !== s[k.sub] && (!_.isNil(x[k.main]) || !_.isNil(s[k.sub]))) return false;
+                            if ((x[k.main] || '') !== (s[k.sub] || '')) return false;
                             break;
                         case 'number':
-                            if (!ctx.helper.checkZero(ctx.helper.sub(x[k.main] - s[k.sub]))) return false;
+                            if (!ctx.helper.checkZero(ctx.helper.sub(x[k.main], s[k.sub]))) return false;
                             break;
                     }
                 }
@@ -1114,7 +1114,7 @@ const datetimeFormat = {
     },
     fun: function (ctx, data, fieldsKey, options, csRela) {
         if (!options.tables) return;
-        const tables = options.tables instanceof Array ? tables : [options.tables];
+        const tables = options.tables instanceof Array ? options.tables : [options.tables];
         if (tables.length === 0) return;
 
         for (const table of tables) {
@@ -1400,8 +1400,6 @@ const stageSelectConverse = {
         if (!gsDefine || !gsDefine.enable || !gsDefine.setting || gsDefine.setting === '') return;
         const gsCustom = csRela.cDefine ? csRela.cDefine.stage_select : null;
         if (gsCustom) {
-            console.log(gsCustom[0]);
-            console.log(data.mem_stage_sum_bills[0]);
             for (const t of options.table) {
                 switch (t) {
                     case 'mem_stage_sum_bills':
@@ -1416,6 +1414,123 @@ const stageSelectConverse = {
         }
     }
 };
+const loadCooperationData = {
+    name: '加载协作数据',
+    hint: '须使用台账、计量审批流程、协作数据作为基础',
+    defaultSetting: {
+        table: 'mem_stage_im_zl',
+        co_sign: [0,1,2,3],
+    },
+    _findSign: function (relaId, stageCooperation, auditor) {
+        if (!stageCooperation) return '';
+        if (relaId.length > 0) {
+            for (const id of relaId) {
+                const c = stageCooperation.find(x => {return x.ledger_id == id});
+                if (c) return c.sign_path;
+            }
+            return '';
+        } else {
+            return '';
+        }
+    },
+    _completeSign: function (auditor) {
+        if (!auditor || !auditor.end_time) return '';
+        return auditor.sign_path ? 'public/upload/sign/' + auditor.sign_path : '';
+    },
+    _loadImCooperationData: function (ctx, data, options, csRela) {
+
+        let coSignOrder = [];
+        if (csRela && csRela.tplDefine && csRela.tplDefine.audit_select && csRela.cDefine && csRela.cDefine.audit_select) {
+            if (csRela.cDefine.audit_select) {
+                for (const asc of csRela.cDefine.audit_select) {
+                    coSignOrder.push(asc.order);
+                }
+            }
+        } else {
+            coSignOrder = options.co_sign || [];
+        }
+
+        const stageCooperation = [];
+        let finish = true;
+        for (const sa of data.stage_audit) {
+            if (sa.end_time) stageCooperation.push(data.ledger_cooperation.filter(x => {return x.user_id === sa.aid}));
+            if (finish && !sa.end_time) finish = false;
+        }
+        for (const d of data[options.table]) {
+            const bills = data.mem_stage_bills.find(x => {return x.id === d.lid});
+            const relaId = bills ? bills.full_path.split('-').reverse() : [];
+
+            d.cooperation = [];
+            for (const [i, sa] of data.stage_audit.entries()) {
+                d.cooperation.push(this._findSign(relaId, stageCooperation[i], sa));
+            }
+
+            if (finish) {
+                for (const [i, cs] of coSignOrder.entries()) {
+                    if (data.stage_audit[cs] && data.stage_audit[cs].end_time) {
+                        d['co_sign' + (i+1)] = d.cooperation[cs] || this._completeSign(data.stage_audit[cs]);
+                        d['co_opinion' + (i+1)] = data.stage_audit[cs].opinion || '';
+                        d['co_time' + (i+1)] = data.stage_audit[cs].end_time;
+                    }
+                }
+            }
+        }
+    },
+    fun: function (ctx, data, fieldsKey, options, csRela) {
+        if (!options || !options.table) return;
+        if (!data[options.table]) return;
+        if (!data.mem_stage_bills) return;
+        if (!data.stage_audit) return;
+        if (!data.ledger_cooperation) return;
+
+        if (['mem_stage_im_zl', 'mem_stage_im_tz'].indexOf(options.table) >= 0)
+            this._loadImCooperationData(ctx, data, options, csRela);
+    }
+};
+const signSelect = {
+    name: '电子签名',
+    hint: '协作模式下,才需使用,其他情况勿需使用',
+    defaultSetting: {
+        table: 'mem_stage_im_zl',
+    },
+    _findSign: function (relaId, stageCooperation, auditor) {
+        if (!stageCooperation) return '';
+        if (relaId.length > 0) {
+            for (const id of relaId) {
+                const c = stageCooperation.find(x => {return x.ledger_id == id});
+                if (c) return c.sign_path;
+            }
+            return '';
+        } else {
+            return '';
+        }
+    },
+    _loadImCooperationData: function (ctx, data, options, csRela) {
+        const stageCooperation = {};
+        for (const sa of data.stage_audit) {
+            if (sa.end_time) stageCooperation[sa.aid] = data.ledger_cooperation.filter(x => {return x.user_id === sa.aid});
+        }
+        for (const d of data[options.table]) {
+            const bills = data.mem_stage_bills.find(x => {return x.id === d.lid});
+            const relaId = bills ? bills.full_path.split('-').reverse() : [];
+
+            for (const ss of data.sign_select) {
+                if (!ss.co_sign) ss.co_sign = [];
+                ss.co_sign.push(this._findSign(relaId, stageCooperation[ss.id], ss) || 'public/upload/sign/' + ss.sign_path);
+            }
+        }
+    },
+    fun: function (ctx, data, fieldsKey, options, csRela) {
+        if (!options || !options.table) return;
+        if (!data[options.table]) return;
+        if (!data.mem_stage_bills) return;
+        if (!data.stage_audit) return;
+        if (!data.ledger_cooperation) return;
+
+        if (['mem_stage_im_zl', 'mem_stage_im_tz'].indexOf(options.table) >= 0)
+            this._loadImCooperationData(ctx, data, options, csRela);
+    }
+};
 
 const analysisObj = {
     changeSort,
@@ -1435,6 +1550,7 @@ const analysisObj = {
     sortPos,
     unionPos,
     splitXmjCode,
+    loadCooperationData,
 };
 const analysisDefine = (function (obj) {
     const result = [];

+ 1 - 1
app/middleware/url_parse.js

@@ -22,7 +22,7 @@ module.exports = options => {
         this.urlInfo = urlInfo;
         // 防止为空
         this.request.headers.referer = this.request.headers.referer === undefined ? '/dashboard' : this.request.headers.referer;
-        // 清空原Message
+        // 清空原Message---有bug,不应该在有message未展示前被清除
         if (this.session) {
             this.session.message = null;
         }

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

@@ -65,7 +65,21 @@ font-size: .875rem;
 }
 .bs-popover-auto[x-placement^="bottom"] .arrow::after, .bs-popover-bottom .arrow::after{
   border-bottom-color:#000;
-}/*
+}
+.custom-control-warning-input:checked ~ .custom-control-warning-label::before{
+  border-color:#da9500 ;
+  background-color:#da9500 
+}
+.custom-control-warning-label{
+  color:#da9500;
+}
+.custom-control-label::before{
+  top:.15rem;
+}
+.custom-control-label::after{
+  top:.15rem;
+}
+/*
 .btn.disabled, .btn:disabled {
   opacity:.4
 }*/
@@ -144,6 +158,12 @@ font-size: .875rem;
 .border-secondary-50{
   border:1px solid #ccc;
 }
+.input-group-cancel{
+  position: absolute;
+  margin-left: -15px;
+  margin-top: 2px;
+  font-size: 14px
+}
 /*在谷歌下移除input[number]的上下箭头*/
 input.nospin[type='number']::-webkit-outer-spin-button,
 input.nospin[type='number']::-webkit-inner-spin-button{
@@ -198,7 +218,7 @@ input.nospin[type="number"]{-moz-appearance:textfield;}
 .sjs-height-4,.sjs-height-5,.sjs-height-6,.sjs-option-height{
   overflow: auto;
 }
-.sjs-bar-1,.sjs-bar-2,.sjs-bar-3,.sjs-bar-4{
+.sjs-bar-1,.sjs-bar-2,.sjs-bar-3,.sjs-bar-4,.sjs-bar-5{
   height:30px;
   padding-top:3px;
 }
@@ -453,6 +473,9 @@ input.nospin[type="number"]{-moz-appearance:textfield;}
 .border-bottom-1 {
   border-bottom:1px solid #dee2e6;
 }
+.save-confirm {
+  position:absolute;
+}
 /*滚动*/
 .scrollbar-auto {
     overflow-y: auto;
@@ -1021,12 +1044,6 @@ legend {
     right:0;
     top:35px;
   }
-.custom-control-label::before{
-  top:.1rem;
-}
-.custom-control-label::after{
-  top:.1rem;
-}
 .form-control-sm{
   height:calc(1.4125rem + 2px);
 }
@@ -1205,4 +1222,22 @@ overflow-y: auto;
 }
 .list-table tr:hover .att-file-btn{
   display: inline-block;
+}
+.context-menu-icon.context-menu-icon--fa.text-success::before {
+    color: #28a745;!important;
+}
+.context-menu-icon.context-menu-icon--fa.text-danger::before {
+    color: #dc3545;!important;
+}
+.context-menu-icon.context-menu-icon--fa.text-warning::before {
+    color: #da9500;!important;
+}
+.context-menu-icon.context-menu-icon--fa.text-info::before {
+    color: #17a2b8;!important;
+}
+.context-menu-icon.context-menu-icon--fa.fa-tag span{
+    color: #2f2f2f;!important;
+}
+.context-menu-icon.context-menu-hover.context-menu-icon--fa.fa-tag span{
+    color: #fff;!important;
 }

+ 39 - 1
app/public/css/main_s.css

@@ -65,7 +65,21 @@ font-size: .875rem;
 }
 .bs-popover-auto[x-placement^="bottom"] .arrow::after, .bs-popover-bottom .arrow::after{
   border-bottom-color:#000;
-}/*
+}
+.custom-control-warning-input:checked ~ .custom-control-warning-label::before{
+  border-color:#da9500 ;
+  background-color:#da9500 
+}
+.custom-control-warning-label{
+  color:#da9500;
+}
+.custom-control-label::before{
+  top:.15rem;
+}
+.custom-control-label::after{
+  top:.15rem;
+}
+/*
 .btn.disabled, .btn:disabled {
   opacity:.4
 }*/
@@ -144,6 +158,12 @@ font-size: .875rem;
 .border-secondary-50{
   border:1px solid #ccc;
 }
+.input-group-cancel{
+  position: absolute;
+  margin-left: -15px;
+  margin-top: 2px;
+  font-size: 14px
+}
 /*在谷歌下移除input[number]的上下箭头*/
 input.nospin[type='number']::-webkit-outer-spin-button,
 input.nospin[type='number']::-webkit-inner-spin-button{
@@ -1205,4 +1225,22 @@ overflow-y: auto;
 }
 .list-table tr:hover .att-file-btn{
   display: inline-block;
+}
+.context-menu-icon.context-menu-icon--fa.text-success::before {
+    color: #28a745;!important;
+}
+.context-menu-icon.context-menu-icon--fa.text-danger::before {
+    color: #dc3545;!important;
+}
+.context-menu-icon.context-menu-icon--fa.text-warning::before {
+    color: #da9500;!important;
+}
+.context-menu-icon.context-menu-icon--fa.text-info::before {
+    color: #17a2b8;!important;
+}
+.context-menu-icon.context-menu-icon--fa.fa-tag span{
+    color: #2f2f2f;!important;
+}
+.context-menu-icon.context-menu-hover.context-menu-icon--fa.fa-tag span{
+    color: #fff;!important;
 }

File diff suppressed because it is too large
+ 43 - 0
app/public/css/radar-animation.css


+ 22 - 0
app/public/js/change_information.js

@@ -216,6 +216,28 @@ $(document).ready(() => {
             $('#syfujian .check-all-file').prop('checked', false)
         }
     });
+
+    // 项目节信息获取
+    const xmjSpreadSetting = {
+        cols: [
+            {title: '项目节编号', colSpan: '1', rowSpan: '2', field: 'xmj_code', hAlign: 0, width: 80},
+            // {title: '名称', colSpan: '1', rowSpan: '2', field: 'name', hAlign: 0, width: 120},
+            {title: '细目', colSpan: '1', rowSpan: '2', field: 'xmj_jldy', hAlign: 0, width: 100},
+            {title: '单位工程', colSpan: '1', rowSpan: '2', field: 'xmj_dwgc', hAlign: 0, width: 100},
+            {title: '分部工程', colSpan: '1', rowSpan: '2', field: 'xmj_fbgc', hAlign: 0, width: 100},
+            {title: '分项工程', colSpan: '1', rowSpan: '2', field: 'xmj_fxgc', hAlign: 0, width: 100},
+            {title: '计量单元', colSpan: '1', rowSpan: '2', field: 'bwmx', hAlign: 0, width: 100},
+            {title: '数量', colSpan: '1', rowSpan: '2', field: 'oamount', hAlign: 2, width: 80},
+        ],
+        emptyRows: 0,
+        headRows: 1,
+        headRowHeight: [25, 25],
+        defaultRowHeight: 21,
+        headerFont: '12px 微软雅黑',
+        font: '12px 微软雅黑',
+        readOnly: true,
+    };
+    SpreadJsObj.initSheet(xmjSpread.getActiveSheet(), xmjSpreadSetting);
 });
 function findDecimal(unit) {
     let value = precision.other.value;

+ 31 - 0
app/public/js/change_information_approval.js

@@ -28,6 +28,20 @@ $(document).ready(() => {
         headerFont: '12px 微软雅黑',
         font: '12px 微软雅黑',
         // readOnly: true,
+        rowHeader:[
+            {
+                rowHeaderType: 'circle',
+                setting: {
+                    size: 5,
+                    indent: 16,
+                    getColor: function (index, data) {
+                        if (!data) return;
+                        if(data.lid != 0) return;
+                        return '#007bff';
+                    }
+                },
+            },
+        ],
     };
     for (const aid of aidList) {
         const userinfo = _.find(auditList2, { 'uid': aid });
@@ -91,6 +105,20 @@ $(document).ready(() => {
                 }
             }
         },
+        resetXmjSpread: function(data = null) {
+            const xmj = [];
+            if (data && data.lid != 0 && data.xmj_code !== '' && data.xmj_code !== null) {
+                if (data.bwmx === data.xmj_jldy) {
+                    data.bwmx = '';
+                }
+                xmj.push(data);
+            }
+            SpreadJsObj.loadSheetData(xmjSpread.getActiveSheet(), SpreadJsObj.DataType.Data, xmj);
+        },
+        selectionChanged: function (e, info) {
+            const data = SpreadJsObj.getSelectObject(info.sheet);
+            changeSpreadObj.resetXmjSpread(data);
+        },
         setRowValueAndSum: function (data, row, col) {
             for (const j in aidList) {
                 const sum = ZhCalc.round(ZhCalc.mul(data.unit_price, parseFloat(changeSpreadSheet.getValue(row, 10 + parseInt(j)*2))), totalPriceUnit);
@@ -252,6 +280,7 @@ $(document).ready(() => {
                 SpreadJsObj.loadSheetData(changeSpreadSheet, SpreadJsObj.DataType.Data, changeList);
                 changeSpreadObj.setAuditValue();
                 changeSpreadObj.makeSjsFooter();
+                changeSpreadObj.resetXmjSpread(SpreadJsObj.getSelectObject(changeSpreadSheet));
             }, function () {
                 SpreadJsObj.reLoadRowData(info.sheet, info.cellRange.row, info.cellRange.rowCount);
                 changeSpreadObj.setAuditValue();
@@ -265,10 +294,12 @@ $(document).ready(() => {
     SpreadJsObj.loadSheetData(changeSpreadSheet, SpreadJsObj.DataType.Data, changeList);
     changeSpreadObj.setAuditValue();
     changeSpreadObj.makeSjsFooter();
+    changeSpreadObj.resetXmjSpread(SpreadJsObj.getSelectObject(changeSpreadSheet));
     const userIndex = aidList.indexOf(parseInt(accountId));
 
     changeSpread.bind(spreadNS.Events.EditEnded, changeSpreadObj.editEnded);
     changeSpread.bind(spreadNS.Events.ClipboardPasted, changeSpreadObj.clipboardPasted);
+    changeSpread.bind(spreadNS.Events.SelectionChanged, changeSpreadObj.selectionChanged);
     SpreadJsObj.addDeleteBind(changeSpread, changeSpreadObj.deletePress);
 
 

+ 74 - 9
app/public/js/change_information_set.js

@@ -81,6 +81,20 @@ $(document).ready(() => {
         headerFont: '12px 微软雅黑',
         font: '12px 微软雅黑',
         readOnly: readOnly,
+        rowHeader:[
+            {
+                rowHeaderType: 'circle',
+                setting: {
+                    size: 5,
+                    indent: 16,
+                    getColor: function (index, data) {
+                        if (!data) return;
+                        if(data.lid != 0) return;
+                        return '#007bff';
+                    }
+                },
+            },
+        ],
     };
 
     const changeCol = {
@@ -138,6 +152,7 @@ $(document).ready(() => {
                     SpreadJsObj.reLoadRowData(changeSpreadSheet, changeList.length - 1);
                     changeSpreadSheet.setStyle(changeSpreadSheet.getRowCount() - 1, -1, style1);
                     changeSpreadSheet.setSelection(changeList.length - 1, 0, 1, 1);
+                    changeSpreadObj.resetXmjSpread();
                 }
             });
         },
@@ -150,6 +165,7 @@ $(document).ready(() => {
                     changeSpreadSheet.deleteRows(index, 1);
                     const sel = changeSpreadSheet.getSelections();
                     changeSpreadSheet.setSelection(0, 0, 1, 1);
+                    changeSpreadObj.resetXmjSpread(SpreadJsObj.getSelectObject(changeSpreadSheet));
                     if (select.lid != 0) {
                         tableDataRemake(changeListData);
                     }
@@ -157,13 +173,24 @@ $(document).ready(() => {
                 });
             }
         },
+        resetXmjSpread: function(data = null) {
+            const xmj = [];
+            if (data && data.lid != 0 && data.xmj_code !== '' && data.xmj_code !== null) {
+                if (data.bwmx === data.xmj_jldy) {
+                    data.bwmx = '';
+                }
+                xmj.push(data);
+            }
+            SpreadJsObj.loadSheetData(xmjSpread.getActiveSheet(), SpreadJsObj.DataType.Data, xmj);
+        },
         selectionChanged: function (e, info) {
             const sel = info.sheet.getSelections()[0];
             const col = info.sheet.zh_setting.cols[sel.col];
             const data = SpreadJsObj.getSelectObject(info.sheet);
-            if (col.field === 'del_list') {
+            if (col && col.field === 'del_list') {
                 changeSpreadObj.del();
             }
+            changeSpreadObj.resetXmjSpread(data);
         },
         deletePress: function (sheet) {
             return;
@@ -306,6 +333,7 @@ $(document).ready(() => {
                 changeList = result;
                 SpreadJsObj.loadSheetData(changeSpreadSheet, SpreadJsObj.DataType.Data, changeList);
                 changeSpreadObj.makeSjsFooter();
+                changeSpreadObj.resetXmjSpread(SpreadJsObj.getSelectObject(changeSpreadSheet));
             }, function () {
                 SpreadJsObj.reLoadRowData(info.sheet, info.cellRange.row, info.cellRange.rowCount);
                 return;
@@ -347,7 +375,7 @@ $(document).ready(() => {
         let gcl_index = 0;
         for (const gcl of changeListData) {
             const unit = gcl.unit !== undefined && gcl.unit !== null ? gcl.unit : '';
-            const quantity = gcl.quantity !== null && gcl.quantity !== undefined ? (unit !== '' ? ZhCalc.round(gcl.quantity, findDecimal(gcl.unit)) : gcl.quantity) : 0;
+            const quantity = gcl.quantity !== 0 && gcl.quantity !== null && gcl.quantity !== undefined ? (unit !== '' ? ZhCalc.round(gcl.quantity, findDecimal(gcl.unit)) : gcl.quantity) : 0;
             const unit_price = gcl.unit_price !== null && gcl.unit_price !== undefined ? gcl.unit_price : 0;
             let gclhtml = gcl.leafXmjs !== undefined && gcl.leafXmjs !== null ? ' data-gcl="' + gcl_index + '"' : '';
             gcl_index = gclhtml !== '' ? ++gcl_index : gcl_index;
@@ -371,6 +399,7 @@ $(document).ready(() => {
         SpreadJsObj.initSheet(changeSpreadSheet, changeSpreadSetting);
         SpreadJsObj.loadSheetData(changeSpreadSheet, SpreadJsObj.DataType.Data, changeList);
         changeSpreadObj.makeSjsFooter();
+        changeSpreadObj.resetXmjSpread(SpreadJsObj.getSelectObject(changeSpreadSheet));
     });
 
     if (!readOnly) {
@@ -412,6 +441,7 @@ $(document).ready(() => {
                     disabled: function (key, opt) {
                         const select = SpreadJsObj.getSelectObject(changeSpreadSheet);
                         const sel = changeSpreadSheet.getSelections()[0];
+                        changeSpreadObj.resetXmjSpread(select);
                         console.log(select, sel);
                         if (!readOnly && select && sel.row !== changeSpreadSheet.getRowCount() - 1) {
                             return false;
@@ -445,7 +475,12 @@ $(document).ready(() => {
                 const quantity = leaf.quantity !== undefined && leaf.quantity !== null ? leaf.quantity : 0;
                 const gcl_id = leaf.gcl_id ? leaf.gcl_id : '';
                 const bwmx = leaf.bwmx !== undefined ? leaf.bwmx : '';
-                const isChecked = data_bwmx.indexOf(leaf.code + '!_!' + (leaf.jldy ? leaf.jldy : '') + '!_!' + (leaf.gcl_id ? leaf.gcl_id : '0') + '!_!' + (bwmx !== '' ? bwmx : leaf.jldy ? leaf.jldy : '') + '*;*' + quantity) !== -1 && isCheck ? 'checked' : '';
+                const isChecked = data_bwmx.indexOf(
+                    leaf.code + '!_!' + (leaf.jldy ? leaf.jldy : '') + '!_!' +
+                    (leaf.dwgc ? leaf.dwgc : '') + '!_!' + (leaf.fbgc ? leaf.fbgc : '') + '!_!' + (leaf.fxgc ? leaf.fxgc : '')
+                    + '!_!' + (leaf.gcl_id ? leaf.gcl_id : '0') + '!_!' +
+                    (bwmx !== '' ? bwmx : leaf.jldy ? leaf.jldy : '') + '*;*' + quantity) !== -1 && isCheck ?
+                    'checked' : '';
                 codeHtml += '<tr quantity="' + quantity + '" gcl_id="' + gcl_id + '"><td>' + leaf.code + '</td>' +
                     '<td>' + (leaf.jldy ? leaf.jldy: '') + '</td>' +
                     '<td>' + (leaf.dwgc ? leaf.dwgc : '') + '</td>' +
@@ -480,7 +515,13 @@ $(document).ready(() => {
                 const tr = $(this).parents('tr');
                 const length = tr.children('td').length;
                 const gcl_id = tr.attr('gcl_id');
-                const bwmx = length === 8 ? tr.children('td').eq(0).text() + '!_!' + tr.children('td').eq(1).text() + '!_!' + gcl_id + '!_!' + (tr.children('td').eq(5).text() !== '' ? tr.children('td').eq(5).text() : tr.children('td').eq(1).text()) : '0';
+                const bwmx = length === 8 ?
+                    tr.children('td').eq(0).text() + '!_!' +
+                    tr.children('td').eq(1).text() + '!_!' +
+                    tr.children('td').eq(2).text() + '!_!' +
+                    tr.children('td').eq(3).text() + '!_!' +
+                    tr.children('td').eq(4).text() + '!_!' + gcl_id + '!_!' +
+                    (tr.children('td').eq(5).text() !== '' ? tr.children('td').eq(5).text() : tr.children('td').eq(1).text()) : '0';
                 const quantity = tr.attr('quantity');
                 const de_qu = bwmx + '*;*' + quantity;
                 data_bwmx.push(de_qu);
@@ -496,7 +537,13 @@ $(document).ready(() => {
                     const tr = $(this).parents('tr');
                     const length = tr.children('td').length;
                     const gcl_id = tr.attr('gcl_id');
-                    const bwmx = length === 8 ? tr.children('td').eq(0).text() + '!_!'+ tr.children('td').eq(1).text() + '!_!' + gcl_id + '!_!' + (tr.children('td').eq(5).text() !== '' ? tr.children('td').eq(5).text() : tr.children('td').eq(1).text()) : '0';
+                    const bwmx = length === 8 ?
+                        tr.children('td').eq(0).text() + '!_!' +
+                        tr.children('td').eq(1).text() + '!_!' +
+                        tr.children('td').eq(2).text() + '!_!' +
+                        tr.children('td').eq(3).text() + '!_!' +
+                        tr.children('td').eq(4).text() + '!_!' + gcl_id + '!_!' +
+                        (tr.children('td').eq(5).text() !== '' ? tr.children('td').eq(5).text() : tr.children('td').eq(1).text()) : '0';
                     const quantity = tr.attr('quantity');
                     const de_qu = bwmx + '*;*' + quantity;
                     data_bwmx.push(de_qu);
@@ -518,6 +565,8 @@ $(document).ready(() => {
             changeList = result;
             SpreadJsObj.loadSheetData(changeSpreadSheet, SpreadJsObj.DataType.Data, changeList);
             changeSpreadObj.makeSjsFooter();
+            const select = SpreadJsObj.getSelectObject(changeSpreadSheet);
+            changeSpreadObj.resetXmjSpread(select);
             $('#addlist').modal('hide');
         }, function () {
             $('#addlist').modal('hide');
@@ -717,7 +766,12 @@ function tableDataRemake(changeListData) {
                         });
                         console.log(leafInfo);
                         if (leafInfo) {
-                            pushbwmx = leafInfo.code + '!_!' + (leafInfo.jldy !== undefined ? leafInfo.jldy : '') + '!_!' + (leafInfo.gcl_id ? leafInfo.gcl_id : '') + '!_!' + (leafInfo.bwmx !== undefined ? leafInfo.bwmx : '') + '*;*' + (leafInfo.quantity !== null ? leafInfo.quantity : 0);
+                            pushbwmx = leafInfo.code + '!_!' + (leafInfo.jldy !== undefined ? leafInfo.jldy : '') + '!_!' +
+                                (leafInfo.dwgc ? leafInfo.dwgc : '') + '!_!' +
+                                (leafInfo.fbgc ? leafInfo.fbgc : '') + '!_!' +
+                                (leafInfo.fxgc ? leafInfo.fxgc : '') + '!_!' +
+                                (leafInfo.gcl_id ? leafInfo.gcl_id : '') + '!_!' +
+                                (leafInfo.bwmx !== undefined ? leafInfo.bwmx : (leafInfo.jldy !== undefined ? leafInfo.jldy : '')) + '*;*' + (leafInfo.quantity !== null ? leafInfo.quantity : 0);
                         } else {
                             toastr.warning('台账清单列表已不存在'+ clinfo.code +',已更新变更清单列表');
                             changeList.splice(index, 1);
@@ -742,7 +796,12 @@ function tableDataRemake(changeListData) {
                             return (item.bwmx === undefined || item.bwmx === clinfo.bwmx || item.jldy === clinfo.bwmx) && (item.quantity !== null ? item.quantity === parseFloat(clinfo.oamount) : 0 === parseFloat(clinfo.oamount));
                         });
                         if (leafInfo) {
-                            pushbwmx = leafInfo.code + '!_!' + (leafInfo.jldy !== undefined ? leafInfo.jldy : '') + '!_!' + (leafInfo.gcl_id ? leafInfo.gcl_id : '') + '!_!' + (leafInfo.bwmx !== undefined ? leafInfo.bwmx : (leafInfo.jldy ? leafInfo.jldy : '')) + '*;*' + (leafInfo.quantity !== null ? leafInfo.quantity : 0);
+                            pushbwmx = leafInfo.code + '!_!' + (leafInfo.jldy !== undefined ? leafInfo.jldy : '') + '!_!' +
+                                (leafInfo.dwgc ? leafInfo.dwgc : '') + '!_!' +
+                                (leafInfo.fbgc ? leafInfo.fbgc : '') + '!_!' +
+                                (leafInfo.fxgc ? leafInfo.fxgc : '') + '!_!' +
+                                (leafInfo.gcl_id ? leafInfo.gcl_id : '') + '!_!' +
+                                (leafInfo.bwmx !== undefined ? leafInfo.bwmx : (leafInfo.jldy !== undefined ? leafInfo.jldy : '')) + '*;*' + (leafInfo.quantity !== null ? leafInfo.quantity : 0);
                         } else {
                             toastr.warning('台账清单列表已不存在'+ clinfo.code +',已更新变更清单列表');
                             changeList.splice(index, 1);
@@ -819,10 +878,13 @@ function remakeChangeSpread() {
 
         for (const b of data_bwmx) {
             const oamount = b.split('*;*')[1] != '' ? b.split('*;*')[1] : 0;
-            let bwmx = b.split('*;*')[0] != 0 ? b.split('*;*')[0].split('!_!')[3] : '';
+            let bwmx = b.split('*;*')[0] != 0 ? b.split('*;*')[0].split('!_!')[6] : '';
             let xmj_code = b.split('*;*')[0] != 0 ? b.split('*;*')[0].split('!_!')[0] : '';
             let xmj_jldy = b.split('*;*')[0] != 0 ? b.split('*;*')[0].split('!_!')[1] : '';
-            let gcl_id = b.split('*;*')[0] != 0 ? b.split('*;*')[0].split('!_!')[2] : '';
+            let xmj_dwgc = b.split('*;*')[0] != 0 ? b.split('*;*')[0].split('!_!')[2] : '';
+            let xmj_fbgc = b.split('*;*')[0] != 0 ? b.split('*;*')[0].split('!_!')[3] : '';
+            let xmj_fxgc = b.split('*;*')[0] != 0 ? b.split('*;*')[0].split('!_!')[4] : '';
+            let gcl_id = b.split('*;*')[0] != 0 ? b.split('*;*')[0].split('!_!')[5] : '';
             let trlist = {
                 code,
                 name,
@@ -835,6 +897,9 @@ function remakeChangeSpread() {
                 lid,
                 xmj_code,
                 xmj_jldy,
+                xmj_dwgc,
+                xmj_fbgc,
+                xmj_fxgc,
                 gcl_id,
             };
             const radionInfo = changeList.find(function (info) {

+ 30 - 0
app/public/js/change_information_show.js

@@ -30,6 +30,20 @@ $(document).ready(() => {
         headerFont: '12px 微软雅黑',
         font: '12px 微软雅黑',
         readOnly: true,
+        rowHeader:[
+            {
+                rowHeaderType: 'circle',
+                setting: {
+                    size: 5,
+                    indent: 16,
+                    getColor: function (index, data) {
+                        if (!data) return;
+                        if(data.lid != 0) return;
+                        return '#007bff';
+                    }
+                },
+            },
+        ],
     };
     for (const aid of aidList) {
         const userinfo = _.find(auditList2, { 'uid': aid });
@@ -83,6 +97,20 @@ $(document).ready(() => {
             changeSpreadSheet.setStyle(changeSpreadSheet.getRowCount() - 1, -1, style1);
             changeSpreadObj.countSum();
         },
+        resetXmjSpread: function(data = null) {
+            const xmj = [];
+            if (data && data.lid != 0 && data.xmj_code !== '' && data.xmj_code !== null) {
+                if (data.bwmx === data.xmj_jldy) {
+                    data.bwmx = '';
+                }
+                xmj.push(data);
+            }
+            SpreadJsObj.loadSheetData(xmjSpread.getActiveSheet(), SpreadJsObj.DataType.Data, xmj);
+        },
+        selectionChanged: function (e, info) {
+            const data = SpreadJsObj.getSelectObject(info.sheet);
+            changeSpreadObj.resetXmjSpread(data);
+        },
         setAuditValue: function () {
             const rowCount = changeSpreadSheet.getRowCount();
             // 用户的数据合计
@@ -135,6 +163,8 @@ $(document).ready(() => {
     changeSpreadObj.setAuditValue();
     changeSpreadObj.makeSjsFooter();
     changeSpreadObj.showHideAudit();
+    changeSpreadObj.resetXmjSpread(SpreadJsObj.getSelectObject(changeSpreadSheet));
+    changeSpread.bind(spreadNS.Events.SelectionChanged, changeSpreadObj.selectionChanged);
 
     // 审批流程展示与隐藏
     $('#show-table-detail').on('click', function (e) {

+ 201 - 27
app/public/js/ledger.js

@@ -34,6 +34,25 @@ function getExprInfo (field) {
 function transExpr(expr) {
     return expr.replace('=', '').replace('%', '/100');
 }
+const checkOption = {
+    sibling: { enable: 1 },
+    empty_code: { enable: 1 },
+    calc: {
+        enable: 1,
+        fields: ['sgfh_qty', 'qtcl_qty', 'sjcl_qty', 'quantity'],
+    },
+    zero: { enable: 1 },
+    tp: {
+        enable: 1,
+        fields: [
+            {qty: 'sgfh_qty', tp: 'sgfh_tp'},
+            {qty: 'sjcl_qty', tp: 'sjcl_tp'},
+            {qty: 'qtcl_qty', tp: 'qtcl_tp'},
+            {qty: 'quantity', tp: 'total_price'},
+            {qty: 'deal_qty', tp: 'deal_tp'},
+        ],
+    }
+};
 
 $(document).ready(function() {
     let stdXmj, stdGcl, dealBills, searchLedger;
@@ -67,6 +86,21 @@ $(document).ready(function() {
     });
     const posSpread = SpreadJsObj.createNewSpread($('#pos-spread')[0]);
 
+    const billsTag = $.billsTag({
+        selector: '#bills-tag',
+        relaSpread: ledgerSpread,
+        updateUrl: window.location.pathname + '/tag',
+        afterModify: function (nodes) {
+            SpreadJsObj.repaintNodesRowHeader(ledgerSpread.getActiveSheet(), nodes);
+        },
+        afterLocated:  function () {
+            posOperationObj.loadCurPosData();
+        },
+        afterShow: function () {
+            ledgerSpread.refresh();
+            if (posSpread) posSpread.refresh();
+        },
+    });
     const errorList = $.cs_errorList({
         tabSelector: '#error-list-tab',
         selector: '#error-list',
@@ -86,7 +120,7 @@ $(document).ready(function() {
         selector: '#check-list',
         relaSpread: ledgerSpread,
         storeKey: 'ledger-check-' + getTenderId(),
-        checkType: ledgerCheckType,
+        checkType: getCheckType(checkOption),
         afterLocated:  function () {
             posOperationObj.loadCurPosData();
         },
@@ -300,6 +334,7 @@ $(document).ready(function() {
                     const rows = [];
                     for (const u of data.update) {
                         rows.push(tree.nodes.indexOf(u));
+                        billsTag.refreshBillsTagView(u);
                     }
                     SpreadJsObj.reLoadRowsData(sheet, rows);
                 }
@@ -963,6 +998,51 @@ $(document).ready(function() {
     sjsSettingObj.setFxTreeStyle(ledgerSpreadSetting, sjsSettingObj.FxTreeStyle.jz);
     if (thousandth) sjsSettingObj.setTpThousandthFormat(ledgerSpreadSetting);
     ledgerTreeCol.initSpreadSetting(ledgerSpreadSetting);
+    // ledgerSpreadSetting.rowHeader = [
+    //     {
+    //         rowHeaderType: 'tag',
+    //         setting: {
+    //             getColor: function (index, data) {
+    //                 if (!data) return;
+    //                 if (index%10 === 0) return '#007bff';
+    //                 if (index%10 === 1) return '#28a745';
+    //                 if (index%10 === 2) return '#dc3545';
+    //                 if (index%10 === 3) return '#da9500';
+    //                 if (index%10 === 4) return '#17a2b8';
+    //             }
+    //         },
+    //     },
+    // ];
+    ledgerSpreadSetting.headColWidth = [50];
+    ledgerSpreadSetting.rowHeader = [
+        {
+            rowHeaderType: 'tag',
+            setting: {
+                indent: 14,
+                tagSize: 0.8,
+                tagFont: '8px 微软雅黑',
+                getColor: function (index, data) {
+                    if (!data) return;
+                    return billsTag.getBillsTagsColor(data.id);
+                },
+                getTagHtml: function (index, data) {
+                    if (!data) return;
+                    const getHtml = function (list) {
+                        if (!list || list.length === 0) return '';
+                        const html = [];
+                        for (const l of list) {
+                            html.push('<div class="row mr-1">');
+                            html.push(`<div class="col-auto pr-1 ${l.tagClass}">`, '<i class="fa fa-tag"></i>', '</div>');
+                            html.push('<div class="col p-0">', '<p>', l.comment, '</p>', '</div>');
+                            html.push('</div>');
+                        }
+                        return html.join('');
+                    };
+                    return getHtml(billsTag.getBillsTagsInfo(data.id));
+                }
+            },
+        },
+    ];
     // ledgerSpreadSetting.cols.push(
     //     {title: 'ledger_id', colSpan: '1', rowSpan: '2', field: 'ledger_id', hAlign: 2, width: 60, type: 'Number', readOnly: true},
     //     {title: 'ledger_pid', colSpan: '1', rowSpan: '2', field: 'ledger_pid', hAlign: 2, width: 60, type: 'Number', readOnly: true},
@@ -1120,10 +1200,29 @@ $(document).ready(function() {
             .keyup((e) => {if (e.keyCode === 13) item.batchInsert($input[0], root);})
             .on('input', function () {this.value = this.value.replace(/[^\d]/g, '');});
     };
+    // $.contextMenu.types.switch = function (item, opt, root) {
+    //     const self = this;
+    //     if ($.isFunction(item.icon)) {
+    //         item._icon = item.icon.call(this, this, $t, key, item);
+    //     } else {
+    //         if (typeof(item.icon) === 'string' && item.icon.substring(0, 3) === 'fa-') {
+    //             // to enable font awesome
+    //             item._icon = root.classNames.icon + ' ' + root.classNames.icon + '--fa fa ' + item.icon;
+    //         } else {
+    //             item._icon = root.classNames.icon + ' ' + root.classNames.icon + '-' + item.icon;
+    //         }
+    //     }
+    //     const html = [];
+    //     html.push('<div>');
+    //     html.push('</div>');
+    //     const $obj = $('<div>' + item.name +)
+    // };
     // 右键菜单
+    let addTagShare = true;
     const billsContextMenuOptions = {
         selector: '#ledger-spread',
         build: function ($trigger, e) {
+            addTagShare = true;
             const target = SpreadJsObj.safeRightClickSelection($trigger, e, ledgerSpread);
             return target.hitTestType === spreadNS.SheetArea.viewport || target.hitTestType === spreadNS.SheetArea.rowHeader;
         },
@@ -1508,6 +1607,83 @@ $(document).ready(function() {
         };
         billsContextMenuOptions.items.sprImport = '-----------';
     }
+    billsContextMenuOptions.items.tag = {
+        name: '书签',
+        items: {
+            tagShare: {
+                name: '参与人可见',
+                type: 'checkbox',
+                selected: true,
+                events: {
+                    change: function () {
+                        addTagShare = this.checked;
+                    }
+                }
+            },
+            tagSpr: '--------------',
+            tagPrimary: {
+                icon: 'fa-tag text-primary mt-2 mb-2',
+                name: '靛青',
+                callback: function (key, opt, menu, e) {
+                    const node = SpreadJsObj.getSelectObject(ledgerSpread.getActiveSheet());
+                    postData(window.location.pathname + '/tag', {add: { color: '#007bff', lid: node.id, share: addTagShare }}, function (data) {
+                        if (data.add) data.add.node = node;
+                        billsTag.updateDatasAndShow(data);
+                        SpreadJsObj.repaintNodesRowHeader(ledgerSpread.getActiveSheet(), node);
+                    });
+                },
+            },
+            tagSuccess: {
+                icon: 'fa-tag text-success mt-2 mb-2',
+                name: '果绿',
+                callback: function (key, opt) {
+                    const node = SpreadJsObj.getSelectObject(ledgerSpread.getActiveSheet());
+                    postData(window.location.pathname + '/tag', {add: { color: '#28a745', lid: node.id, share: addTagShare }}, function (data) {
+                        if (data.add) data.add.node = node;
+                        billsTag.updateDatasAndShow(data);
+                        SpreadJsObj.repaintNodesRowHeader(ledgerSpread.getActiveSheet(), node);
+                    });
+                },
+            },
+            tagDanger: {
+                icon: 'fa-tag text-danger mt-2 mb-2',
+                name: '朱砂',
+                callback: function (key, opt) {
+                    const node = SpreadJsObj.getSelectObject(ledgerSpread.getActiveSheet());
+                    postData(window.location.pathname + '/tag', {add: { color: '#dc3545', lid: node.id, share: addTagShare }}, function (data) {
+                        if (data.add) data.add.node = node;
+                        billsTag.updateDatasAndShow(data);
+                        SpreadJsObj.repaintNodesRowHeader(ledgerSpread.getActiveSheet(), node);
+                    });
+                },
+            },
+            tagWarning: {
+                icon: 'fa-tag text-warning mt-2 mb-2',
+                name: '姜黄',
+                callback: function (key, opt) {
+                    const node = SpreadJsObj.getSelectObject(ledgerSpread.getActiveSheet());
+                    postData(window.location.pathname + '/tag', {add: { color: '#da9500', lid: node.id, share: addTagShare }}, function (data) {
+                        if (data.add) data.add.node = node;
+                        billsTag.updateDatasAndShow(data);
+                        SpreadJsObj.repaintNodesRowHeader(ledgerSpread.getActiveSheet(), node);
+                    });
+                },
+            },
+            tagInfo: {
+                icon: 'fa-tag text-info mt-2 mb-2',
+                name: '天蓝',
+                callback: function (key, opt) {
+                    const node = SpreadJsObj.getSelectObject(ledgerSpread.getActiveSheet());
+                    postData(window.location.pathname + '/tag', {add: { color: '#17a2b8', lid: node.id, share: addTagShare }}, function (data) {
+                        if (data.add) data.add.node = node;
+                        billsTag.updateDatasAndShow(data);
+                        SpreadJsObj.repaintNodesRowHeader(ledgerSpread.getActiveSheet(), node);
+                    });
+                },
+            }
+        },
+    };
+
     $.contextMenu(billsContextMenuOptions);
 
     const posSearch = $.posSearch({selector: '#pos-search', searchSpread: posSpread});
@@ -2073,6 +2249,11 @@ $(document).ready(function() {
         treeOperationObj.loadExprToInput(ledgerSpread.getActiveSheet());
 
         checkList.loadHisCheckData();
+
+        for (const t of data.tags) {
+            t.node = ledgerTree.datas.find(x => {return x.id === t.lid});
+        }
+        billsTag.loadDatas(data.tags);
     }, null, true);
 
     $.divResizer({
@@ -2287,6 +2468,8 @@ $(document).ready(function() {
                     });
                 }
                 searchLedger.spread.refresh();
+            } else if (tab.attr('content') === '#bills-tag') {
+                // do something
             } else if (tab.attr('content') === '#error-list') {
                 errorList.spread.refresh();
             } else if (tab.attr('content') === '#check-list') {
@@ -2970,8 +3153,8 @@ $(document).ready(function() {
     //     $('#account_list').html(account_html);
     // });
 
-    let timer = null
-    let oldSearchVal = null
+    let timer = null;
+    let oldSearchVal = null;
 
     $('#gr-search').bind('input propertychange', function(e) {
         oldSearchVal = e.target.value
@@ -3228,30 +3411,21 @@ $(document).ready(function() {
         return false;
     });
 
-    LedgerChecker({
-        ledgerTree: ledgerTree,
-        ledgerPos: pos,
-        checkList: checkList,
-        decimal: tenderInfo.decimal,
-        checkOption: {
-            sibling: { enable: 1 },
-            empty_code: { enable: 1 },
-            calc: {
-                enable: 1,
-                fields: ['sgfh_qty', 'qtcl_qty', 'sjcl_qty', 'quantity'],
-            },
-            zero: { enable: 1 },
-            tp: {
-                enable: 1,
-                fields: [
-                    {qty: 'sgfh_qty', tp: 'sgfh_tp'},
-                    {qty: 'sjcl_qty', tp: 'sjcl_tp'},
-                    {qty: 'qtcl_qty', tp: 'qtcl_tp'},
-                    {qty: 'quantity', tp: 'total_price'},
-                    {qty: 'deal_qty', tp: 'deal_tp'},
-                ],
-            },
-        }
+    $('#ledger-check2').click(() => {
+        const result = ledgerCheck2({
+            ledgerTree: ledgerTree,
+            ledgerPos: pos,
+            checkList: checkList,
+            decimal: tenderInfo.decimal,
+            checkOption: checkOption,
+        });
+        check2Viewing({
+            extra: ZhCalc.div(ledgerTree.datas.length + pos.datas.length, 10000, 0),
+            randomWait: true,
+            prefix: 'check2-',
+            checks: result,
+            checkList: checkList,
+        })
     });
 });
 

+ 201 - 0
app/public/js/ledger_check.js

@@ -0,0 +1,201 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date
+ * @version
+ */
+
+
+const ledgerCheckType = {
+    sibling: {value: 1, text: '项目节、清单同层'},
+    empty_code: {value: 2, text: '项目节、清单编号同时为空'},
+    calc: {value: 3, text: '清单数量不等于计量单元之和'},
+    zero: {value: 4, text: '清单数量或单价为0'},
+    tp: {value: 5, text: '清单金额≠数量×单价'},
+    over: {value: 6, text: '超计'},
+};
+const ledgerCheckUtil = {
+    checkSibling: function (ledgerTree) {
+        const error = [];
+        for (const node of ledgerTree.nodes) {
+            if (!node.children || node.children.length === 0) continue;
+            let hasXmj, hasGcl;
+            for (const child of node.children) {
+                if (child.b_code) hasXmj = true;
+                if (!child.b_code) hasGcl = true;
+            }
+            if (hasXmj && hasGcl) error.push(node);
+        }
+        return error;
+    },
+    checkCodeEmpty: function (ledgerTree) {
+        const error = [];
+        const checkNodeCode = function (node) {
+            if ((!node.code || node.code === '') && (!node.b_code || node.b_code === '')) error.push(node);
+            if (node.children && node.children.length > 0) {
+                for (const child of node.children) {
+                    checkNodeCode(child);
+                }
+            }
+        }
+        for (const topLevel of ledgerTree.children) {
+            if (topLevel.node_type !== 1) continue;
+
+            checkNodeCode(topLevel);
+        }
+        return error;
+    },
+    checkCalc: function (ledgerTree, ledgerPos, option) {
+        const error = [];
+        for (const node of ledgerTree.nodes) {
+            if (node.children && node.children.length > 0) continue;
+
+            const nodePos = ledgerPos.getLedgerPos(node.id);
+            if (!nodePos || nodePos.length === 0) continue;
+
+            const checkData = {}, calcData = {};
+            for (const f of option.fields) {
+                checkData[f] = node[f] || 0;
+                calcData[f] = 0;
+            }
+            for (const np of nodePos) {
+                for (const f of option.fields) {
+                    calcData[f] = ZhCalc.add(calcData[f], np[f]) || 0;
+                }
+            }
+            if (!_.isMatch(checkData, calcData)) error.push(node);
+        }
+        return error;
+    },
+    checkZero: function (ledgerTree) {
+        const error = [];
+        for (const node of ledgerTree.nodes) {
+            if ((!node.b_code || node.b_code === '')) continue;
+            if (node.children && node.children.length > 0) continue;
+
+            if ((checkZero(node.sgfh_qty) && checkZero(node.qtcl_qty) && checkZero(node.sjcl_qty)
+                    && checkZero(node.deal_qty) && checkZero(node.quantity))
+                || checkZero(node.unit_price)) error.push(node);
+        }
+        return error;
+    },
+    checkTp: function (ledgerTree, decimal, option) {
+        const error = [];
+        for (const node of ledgerTree.nodes) {
+            if (node.children && node.children.length > 0) continue;
+            if (option.filter && option.filter(node)) continue;
+
+            const checkData = {}, calcData = {};
+            for (const f of option.fields) {
+                checkData[f.tp] = node[f.tp] || 0;
+                calcData[f.tp] = ZhCalc.mul(node.unit_price, node[f.qty], decimal.tp) || 0;
+            }
+            if (!_.isMatch(checkData, calcData)) error.push(node);
+        }
+        return error;
+    },
+    checkOver: function(ledgerTree, ledgerPos, option) {
+        const error = [];
+        for (const node of ledgerTree.nodes) {
+            if (node.children && node.children.length > 0) continue;
+
+            if (option.isTz) {
+                const posRange = ledgerPos.getLedgerPos(node.id) || [];
+                if (posRange.length > 0) {
+                    for (const p of posRange) {
+                        if (p.end_contract_qty > p.quantity) {
+                            error.push(node);
+                            continue;
+                        }
+                    }
+                }
+            }
+            if (node.is_tp) {
+                if (option.isTz) {
+                    if (node.end_contract_tp > node.total_price) error.push(node);
+                } else {
+                    if (node.end_contract_tp > node.deal_tp) error.push(node);
+                }
+            } else {
+                if (option.isTz) {
+                    if (node.end_contract_qty > node.quantity) error.push(node);
+                } else {
+                    if (node.end_contract_qty > node.deal_qty) error.push(node);
+                }
+            }
+        }
+        return error;
+    },
+};
+
+const ledgerCheck2 = function (setting) {
+    const ledger = setting.ledgerTree, ledgerPos = setting.ledgerPos, decimal = setting.decimal;
+    const checkOption = setting.checkOption;
+
+    const assignWarningData = function (nodes, checkType, warningData) {
+        for (const node of nodes) {
+            warningData.push({
+                type: checkType,
+                ledger_id: node.ledger_id,
+                code: node.code,
+                b_code: node.b_code,
+                name: node.name,
+            })
+        }
+    };
+
+    const checkData = {
+        check_time: new Date(),
+        warning_data: [],
+    };
+    const progressData = [];
+    if (checkOption.sibling.enable) {
+        const sibling = ledgerCheckUtil.checkSibling(ledger, checkOption.sibling) || [];
+        assignWarningData(sibling, ledgerCheckType.sibling.value, checkData.warning_data);
+        progressData.push({key: 'sibling', caption: ledgerCheckType.sibling.text, error: sibling.length});
+    }
+    if (checkOption.empty_code.enable) {
+        const empty_code = ledgerCheckUtil.checkCodeEmpty(ledger, checkOption.empty_code) || [];
+        assignWarningData(empty_code, ledgerCheckType.empty_code.value, checkData.warning_data);
+        progressData.push({key: 'empty_code', caption: ledgerCheckType.empty_code.text, error: empty_code.length});
+    }
+    if (checkOption.calc.enable) {
+        const calc = ledgerCheckUtil.checkCalc(ledger, ledgerPos, checkOption.calc) || [];
+        assignWarningData(calc, ledgerCheckType.calc.value, checkData.warning_data);
+        progressData.push({key: 'calc', caption: ledgerCheckType.calc.text, error: calc.length});
+    }
+    if (checkOption.zero.enable) {
+        const zero = ledgerCheckUtil.checkZero(ledger, checkOption.zero) || [];
+        assignWarningData(zero, ledgerCheckType.zero.value, checkData.warning_data);
+        progressData.push({key: 'zero', caption: ledgerCheckType.zero.text, error: zero.length});
+    }
+    if (checkOption.tp.enable) {
+        const tp = ledgerCheckUtil.checkTp(ledger, decimal, checkOption.tp) || [];
+        assignWarningData(tp, ledgerCheckType.tp.value, checkData.warning_data);
+        progressData.push({key: 'tp', caption: ledgerCheckType.tp.text, error: tp.length});
+    }
+    if (checkOption.over && checkOption.over.enable) {
+        const over = ledgerCheckUtil.checkOver(ledger, ledgerPos, checkOption.over) || [];
+        assignWarningData(over, ledgerCheckType.over.value, checkData.warning_data);
+        progressData.push({key: 'over', caption: ledgerCheckType.over.text, error: over.length});
+    }
+    setting.checkList.clearCheckData();
+    if (checkData.warning_data.length > 0) {
+        setting.checkList.loadCheckData(checkData);
+    } else {
+        setting.checkList.hide();
+    }
+    return progressData;
+};
+const getCheckType = function (option) {
+    const result = {};
+    for (const o in option) {
+        if (option[o].enable) {
+            result[o] = ledgerCheckType[o];
+        }
+    }
+    return result;
+};

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

@@ -178,7 +178,7 @@ $(document).ready(() => {
 
         const chapterData = gclGatherModel.gatherChapterData(chapter, ['total_price'], filter);
         for (const c of chapterData) {
-            c.compare_tp = ZhCalc.sub(c.deal_bills_tp, c.total_price);
+            c.compare_tp = ZhCalc.sub(c.total_price, c.deal_bills_tp);
         }
         generateChapterHtml(chapterData);
     }, null, true);

+ 65 - 0
app/public/js/path_tree.js

@@ -1118,6 +1118,36 @@ const createNewPathTree = function (type, setting) {
                 this.stageItems[keyName] = data;
             }
         }
+        // 解锁相关
+        _loadPwdCache() {
+            const cache = getLocalCache(this.pwdCacheKey);
+            if (!cache) return;
+
+            const cacheArr = cache.split('|');
+            for (const ca of cacheArr) {
+                if (!ca) continue;
+
+                const [ledger_id, pwd] = ca.split(':');
+                if (!ledger_id || !pwd) continue;
+
+                const p = this.pwd.find(x => {return x.ledger_id == ledger_id});
+                if (p) p.check = p.pwd === pwd;
+            }
+        }
+        loadPwd(data, cacheKey) {
+            this.loadingPwd = true;
+            try {
+                this.pwdCacheKey = cacheKey;
+                this.pwd = data;
+                this._loadPwdCache();
+                for (const p of this.pwd) {
+                    p.node = this.getItems(p.ledger_id);
+                    this.lockNode(p, !p.check);
+                }
+            } catch(err) {}
+            this.loadingPwd = false;
+        }
+
         getStageItems(id) {
             return this.stageItems[itemsPre + id];
         }
@@ -1274,6 +1304,41 @@ const createNewPathTree = function (type, setting) {
             }
             return result;
         }
+
+        _savePwdCache() {
+            const cache = [];
+            for (const p of this.pwd) {
+                if (p.check && p.pwd) cache.push(p.ledger_id + ':' + p.pwd);
+            }
+            setLocalCache(this.pwdCacheKey, cache.join('|'));
+        }
+
+        _getPwdPosterity(node) {
+            const children = [];
+            for (const c of node.children) {
+                const cPwd = this.pwd.find(x => {return x.node && x.node.id === c.id});
+                if (!cPwd) children.push(c);
+            }
+            let posterity = [...children];
+            for (const c of children) {
+                posterity = posterity.concat(this._getPwdPosterity(c));
+            }
+            return posterity;
+        }
+
+        lockNode(p, isLock) {
+            const refresh = [];
+            p.check = !isLock;
+            p.node.lock = isLock;
+            refresh.push(this.getNodeIndex(p.node));
+            const posterity = this._getPwdPosterity(p.node);
+            for (const pn of posterity) {
+                pn.lock = isLock;
+                refresh.push(this.getNodeIndex(pn));
+            }
+            if (!this.loadingPwd) this._savePwdCache();
+            return refresh;
+        }
     }
 
     class MasterTree extends FxTree {

+ 35 - 25
app/public/js/revise.js

@@ -28,6 +28,25 @@ function transExpr(expr) {
     return $.trim(expr).replace('\t', '').replace('=', '').replace('%', '/100');
 }
 const copyBlockTag = 'zh.calc.copyBlock';
+const checkOption = {
+    sibling: { enable: 1 },
+    empty_code: { enable: 1 },
+    calc: {
+        enable: 1,
+        fields: ['sgfh_qty', 'qtcl_qty', 'sjcl_qty', 'quantity'],
+    },
+    zero: { enable: 1 },
+    tp: {
+        enable: 1,
+        fields: [
+            {qty: 'sgfh_qty', tp: 'sgfh_tp'},
+            {qty: 'sjcl_qty', tp: 'sjcl_tp'},
+            {qty: 'qtcl_qty', tp: 'qtcl_tp'},
+            {qty: 'quantity', tp: 'total_price'},
+            {qty: 'deal_qty', tp: 'deal_tp'},
+        ],
+    },
+};
 
 $(document).ready(() => {
     let stdXmj, stdGcl, searchLedger;
@@ -65,7 +84,7 @@ $(document).ready(() => {
         selector: '#check-list',
         relaSpread: billsSpread,
         storeKey: 'revise-check-' + window.location.pathname.split('/')[2] + '-' + window.location.pathname.split('/')[4],
-        checkType: ledgerCheckType,
+        checkType: getCheckType(checkOption),
         afterLocated:  function () {
             posSpreadObj.loadCurPosData();
         },
@@ -2559,30 +2578,21 @@ $(document).ready(() => {
         errorList: errorList,
     });
 
-    LedgerChecker({
-        ledgerTree: billsTree,
-        ledgerPos: pos,
-        checkList: checkList,
-        decimal: decimal,
-        checkOption: {
-            sibling: { enable: 1 },
-            empty_code: { enable: 1 },
-            calc: {
-                enable: 1,
-                fields: ['sgfh_qty', 'qtcl_qty', 'sjcl_qty', 'quantity'],
-            },
-            zero: { enable: 1 },
-            tp: {
-                enable: 1,
-                fields: [
-                    {qty: 'sgfh_qty', tp: 'sgfh_tp'},
-                    {qty: 'sjcl_qty', tp: 'sjcl_tp'},
-                    {qty: 'qtcl_qty', tp: 'qtcl_tp'},
-                    {qty: 'quantity', tp: 'total_price'},
-                    {qty: 'deal_qty', tp: 'deal_tp'},
-                ],
-            },
-        }
+    $('#ledger-check2').click(() => {
+        const result = ledgerCheck2({
+            ledgerTree: billsTree,
+            ledgerPos: pos,
+            checkList: checkList,
+            decimal: decimal,
+            checkOption: checkOption,
+        });
+        check2Viewing({
+            extra: ZhCalc.div(billsTree.datas.length + pos.datas.length, 10000, 0),
+            randomWait: true,
+            prefix: 'check2-',
+            checks: result,
+            checkList: checkList,
+        })
     });
 
     $('[name=revise-start]').submit(function (e) {

+ 226 - 8
app/public/js/schedule_ledger.js

@@ -8,6 +8,7 @@
 function getTenderId() {
     return window.location.pathname.split('/')[2];
 }
+const selects = [];
 $(function () {
     autoFlashHeight();
     // 初始化台账
@@ -19,15 +20,11 @@ $(function () {
         level: 'level',
         rootId: -1,
         fullPath: 'full_path',
-        calcFields: ['total_price']
         //treeCacheKey: 'ledger_bills_fold' + '_' + getTenderId(),
         // markFoldKey: 'bills-fold',
         // markFoldSubKey: window.location.pathname.split('/')[2],
     };
-    treeSetting.calcFun = function (node) {
-        node.dgn_price = ZhCalc.round(ZhCalc.div(node.total_price, node.dgn_qty1), 2);
-    };
-    const ledgerTree = createNewPathTree('base', treeSetting);
+    const ledgerTree = createNewPathTree('filter', treeSetting);
 
     const ledgerSpreadSetting = {
         cols: [
@@ -45,7 +42,7 @@ $(function () {
         defaultRowHeight: 21,
         headerFont: '12px 微软雅黑',
         font: '12px 微软雅黑',
-        readOnly: true,
+        // readOnly: true,
         localCache: {
             key: 'ledger-bills',
             colWidth: true,
@@ -58,11 +55,162 @@ $(function () {
     SpreadJsObj.selChangedRefreshBackColor(ledgerSpread.getActiveSheet());
 
     postData(window.location.pathname + '/load', {}, function (data) {
-        ledgerTree.loadDatas(data);
-        treeCalc.calculateAll(ledgerTree);
+        const baseLedgerTree = createNewPathTree('base', {
+            id: 'ledger_id',
+            pid: 'ledger_pid',
+            order: 'order',
+            level: 'level',
+            rootId: -1,
+            fullPath: 'full_path',
+            calcFields: ['total_price'],
+            calcFun: function (node) {
+                node.dgn_price = ZhCalc.round(ZhCalc.div(node.total_price, node.dgn_qty1), 2);
+            }
+        });
+        const datas = addIsSelect(data.bills);
+        baseLedgerTree.loadDatas(datas);
+        treeCalc.calculateAll(baseLedgerTree);
+        for (const d of baseLedgerTree.nodes) {
+            if (!d.b_code)
+                ledgerTree.addData(d, ['is_select', 'ledger_id', 'ledger_pid', 'order', 'level', 'tender_id', 'full_path',
+                    'code', 'name', 'unit', 'dgn_qty1', 'dgn_qty2', 'dgn_price', 'quantity', 'total_price']);
+        }
+        ledgerTree.sortTreeNode(true);
+
         SpreadJsObj.loadSheetData(ledgerSpread.getActiveSheet(), SpreadJsObj.DataType.Tree, ledgerTree);
     }, null, true);
 
+    const ledgerSpreadObj = {
+        getselectButton: function() {
+            return {
+                select_siblings: $('#select_siblings').is(':checked'),
+                select_other_siblings: $('#select_other_siblings').is(':checked'),
+                select_children: $('#select_children').is(':checked'),
+            }
+        },
+        refreshTree: function (sheet, data) {
+            SpreadJsObj.massOperationSheet(sheet, function () {
+                const tree = sheet.zh_tree;
+                // 处理删除
+                if (data.delete) {
+                    data.delete.sort(function (x, y) {
+                        return y.deleteIndex - x.deleteIndex;
+                    });
+                    for (const d of data.delete) {
+                        sheet.deleteRows(d.deleteIndex, 1);
+                    }
+                }
+                // 处理新增
+                if (data.create) {
+                    const newNodes = data.create;
+                    if (newNodes) {
+                        newNodes.sort(function (a, b) {
+                            return a.index - b.index;
+                        });
+
+                        for (const node of newNodes) {
+                            sheet.addRows(node.index, 1);
+                            SpreadJsObj.reLoadRowData(sheet, tree.nodes.indexOf(node), 1);
+                        }
+                    }
+                }
+                // 处理更新
+                if (data.update) {
+                    const rows = [];
+                    for (const u of data.update) {
+                        rows.push(tree.nodes.indexOf(u));
+                    }
+                    SpreadJsObj.reLoadRowsData(sheet, rows);
+                }
+                // 处理展开
+                if (data.expand) {
+                    const expanded = [];
+                    for (const e of data.expand) {
+                        if (expanded.indexOf(e) === -1) {
+                            const posterity = tree.getPosterity(e);
+                            for (const p of posterity) {
+                                sheet.setRowVisible(tree.nodes.indexOf(p), p.visible);
+                                expanded.push(p);
+                            }
+                        }
+                    }
+                }
+            });
+        },
+        buttonClicked: function (e, info) {
+            if (info.sheet.zh_setting) {
+                const select = SpreadJsObj.getSelectObject(info.sheet);
+                const col = info.sheet.zh_setting.cols[info.col];
+                // if (materialCol.readOnly.isEdit(select)) {
+                //     return;
+                // }
+                if (col.field === 'is_select') {
+                    if (info.sheet.isEditing()) {
+                        info.sheet.endEdit(true);
+                    }
+                    // 选中和去除关联
+                    select.is_select = info.sheet.getValue(info.row, info.col) ? 1 : 0;
+                    selects.splice(0, selects.length);
+                    selects.push(select);
+                    const select_msg = ledgerSpreadObj.getselectButton();
+                    if (select_msg.select_children || select.is_select === 0) {
+                        updateChildrenSelect(select.children, select.is_select);
+                    }
+                    if (!select_msg.select_other_siblings && select_msg.select_siblings) {
+                        updateSiblingsSelect(info.sheet.zh_tree, select.ledger_pid, select.is_select, select_msg);
+                    }
+                    if (select_msg.select_other_siblings) {
+                        updateSiblingsSelect(info.sheet.zh_tree, select.ledger_pid, select.is_select, select_msg);
+                        updateOtherSiblingsSelect(info.sheet.zh_tree, select.ledger_pid, select.is_select, select_msg);
+                    }
+                    if(select.is_select === 1) {
+                        updateParentSelect(info.sheet.zh_tree, select.ledger_pid, select.is_select);
+                    }
+                    // console.log(selects);
+                    const refreshNode = ledgerTree.loadPostData({update: selects});
+                    ledgerSpreadObj.refreshTree(info.sheet, refreshNode);
+                }
+            }
+        },
+    };
+
+    ledgerSpread.bind(spreadNS.Events.ButtonClicked, ledgerSpreadObj.buttonClicked);
+
+    $('#ledger_submit').click(function () {
+        const select_ledger = _.filter(ledgerTree.nodes, { 'is_select': 1 });
+        console.log(select_ledger);
+        if (select_ledger.length === 0) {
+            toastr.error('请选择清单项');
+            return;
+        }
+        $(this).attr('disabled', true);
+        postData(window.location.pathname + '/save',  _.map(select_ledger, 'ledger_id'), function (result) {
+            toastr.success('设置成功');
+            setTimeout(function () {
+                window.location.reload();
+            },1500);
+        });
+    });
+
+    $.subMenu({
+        menu: '#sub-menu', miniMenu: '#sub-mini-menu', miniMenuList: '#mini-menu-list',
+        toMenu: '#to-menu', toMiniMenu: '#to-mini-menu',
+        key: 'menu.1.0.0',
+        miniHint: '#sub-mini-hint', hintKey: 'menu.hint.1.0.1',
+        callback: function (info) {
+            if (info.mini) {
+                $('.panel-title').addClass('fluid');
+                $('#sub-menu').removeClass('panel-sidebar');
+            } else {
+                $('.panel-title').removeClass('fluid');
+                $('#sub-menu').addClass('panel-sidebar');
+            }
+            ledgerSpread.refresh();
+            autoFlashHeight();
+        }
+    });
+
+
     // // 显示层次
     // (function (select, sheet) {
     //     $(select).click(function () {
@@ -90,3 +238,73 @@ $(function () {
     //     });
     // })('a[name=showLevel]', ledgerSpread.getActiveSheet());
 });
+function addIsSelect(datas) {
+    // const newDatas = [];
+    for (const d of datas) {
+        if (!d.b_code) {
+            d.is_select = selectedLedgerList.length === 0 ? 1: selectedLedgerList.indexOf(d.ledger_id) !== -1 ? 1 : 0;
+            // newDatas.push(d);
+        }
+    }
+    return datas;
+}
+
+function updateChildrenSelect(datas, is_select) {
+    for (const data of datas) {
+        data.is_select = is_select;
+        if(data.children && data.children.length > 0) {
+            updateChildrenSelect(data.children, is_select);
+        }
+        selects.push(data);
+    }
+}
+
+function updateSiblingsSelect(tree, pid, is_select, select_msg) {
+    const parent = pid !== -1 ? _.find(tree.nodes, { 'ledger_id': pid }) : tree;
+    if (parent) {
+        for(const d of parent.children) {
+            d.is_select = is_select;
+            if (select_msg.select_children || is_select === 0) {
+                updateChildrenSelect(d.children, is_select);
+            }
+            selects.push(d);
+        }
+    }
+}
+
+function updateOtherSiblingsSelect(tree, pid, is_select, select_msg) {
+    if(pid === -1) {
+        return;
+    }
+    const parent = _.find(tree.nodes, { 'ledger_id': pid });
+    if (parent) {
+        const grandparent = parent.ledger_pid === -1 ? tree : _.find(tree.nodes, { 'ledger_id': parent.ledger_pid });
+        if (grandparent) {
+            for (const d of grandparent.children) {
+                if (d.ledger_id !== pid) {
+                    if(d.children && d.children.length > 0) {
+                        for (const dd of d.children) {
+                            dd.is_select = is_select;
+                            if (select_msg.select_children || is_select === 0) {
+                                updateChildrenSelect(dd.children, is_select);
+                            }
+                            selects.push(dd);
+                        }
+                    }
+                }
+            }
+        }
+    }
+}
+
+function updateParentSelect(tree, pid, is_select) {
+    if (pid !== -1) {
+        const parent = _.find(tree.nodes, { 'ledger_id': pid });
+        if (parent) {
+            const hadselectInfo = is_select === 0 ? _.findIndex(parent.children, { 'is_select': 1 }) : 1;
+            parent.is_select = (hadselectInfo !== -1 && is_select === 0) || is_select === 1 ? 1 : 0;
+            selects.push(parent);
+            updateParentSelect(tree, parent.ledger_pid, is_select);
+        }
+    }
+}

+ 521 - 0
app/public/js/schedule_plan.js

@@ -0,0 +1,521 @@
+/**
+ * 进度台账相关js
+ *
+ * @author Ellisran
+ * @date 2020/11/6
+ * @version
+ */
+function getTenderId() {
+    return window.location.pathname.split('/')[2];
+}
+$(function () {
+    autoFlashHeight();
+    if(schedule && !schedule.mode) {
+        $('#mode').modal('show');
+    }
+    // 初始化台账
+    const ledgerSpread = SpreadJsObj.createNewSpread($('#ledger-spread')[0]);
+    const treeSetting = {
+        id: 'ledger_id',
+        pid: 'ledger_pid',
+        order: 'order',
+        level: 'level',
+        rootId: -1,
+        fullPath: 'full_path',
+        //treeCacheKey: 'ledger_bills_fold' + '_' + getTenderId(),
+        // markFoldKey: 'bills-fold',
+        // markFoldSubKey: window.location.pathname.split('/')[2],
+    };
+    const ledgerTree = createNewPathTree('filter', treeSetting);
+
+    const static_cols = [
+        {title: '编号', colSpan: '1', rowSpan: '2', field: 'code', hAlign: 0, width: 145, formatter: '@', readOnly: true, cellType: 'tree'},
+        {title: '名称', colSpan: '1', rowSpan: '2', field: 'name', hAlign: 0, width: 185, formatter: '@', readOnly: true},
+        {title: '单位', colSpan: '1', rowSpan: '2', field: 'unit', hAlign: 1, width: 50, formatter: '@', readOnly: true},
+        {title: '经济指标', colSpan: '1', rowSpan: '2', field: 'dgn_price', hAlign: 2, width: 60, type: 'Number', readOnly: true},
+        {title: '总设计|工程量', colSpan: '2|1', rowSpan: '1|1', field: 'dgn_qty1', hAlign: 2, width: 70, type: 'Number', readOnly: true},
+        {title: '|金额(万元)', colSpan: '|1', rowSpan: '|1', field: 'total_price', hAlign: 2, width: 70, type: 'Number', readOnly: true},
+    ];
+
+    const ledgerSpreadSetting = {
+        emptyRows: 0,
+        headRows: 2,
+        headRowHeight: [25, 25],
+        defaultRowHeight: 21,
+        headerFont: '12px 微软雅黑',
+        font: '12px 微软雅黑',
+        // readOnly: true,
+        localCache: {
+            key: 'ledger-bills',
+            colWidth: true,
+        }
+    };
+    const monthsCols = [];
+    if(scheduleMonth.length > 0) {
+        for (const sm of scheduleMonth) {
+            const readOnly = sm.sj_gcl !== null || sm.sj_tp !== null;
+            const yearmonth = sm.yearmonth.split('-')[0] + '年' + parseInt(sm.yearmonth.split('-')[1]) + '月';
+            const cols = {title: yearmonth + '|计划工程量', colSpan: '2|1', rowSpan: '1|1', field: sm.yearmonth+'_gcl', hAlign: 2, width: 90, type: 'Number', readOnly: readOnly ? readOnly : 'readOnly.gcl'};
+            const cols2 = {title: '|计划金额(万元)', colSpan: '|1', rowSpan: '|1', field: sm.yearmonth+'_tp', hAlign: 2, width: 90, type: 'Number', readOnly: readOnly ? readOnly : 'readOnly.tp'};
+            monthsCols.push(cols);
+            monthsCols.push(cols2);
+        }
+    }
+    const spreadHeaderCols = static_cols.concat(monthsCols);
+    ledgerSpreadSetting.cols = spreadHeaderCols;
+
+    const ledgerCol = {
+        readOnly: {
+            tp: function (data) {
+                let flag = data.is_leaf;
+                if (data.is_leaf) {
+                    flag = schedule && schedule.mode === mode.tp;
+                }
+                return !flag;
+            },
+            gcl: function (data) {
+                let flag = data.is_leaf;
+                if (data.is_leaf) {
+                    flag = schedule && schedule.mode === mode.gcl;
+                }
+                return !flag;
+            },
+        },
+    };
+
+    sjsSettingObj.setFxTreeStyle(ledgerSpreadSetting, sjsSettingObj.FxTreeStyle.jz);
+    if (thousandth) sjsSettingObj.setTpThousandthFormat(ledgerSpreadSetting);
+    SpreadJsObj.initSpreadSettingEvents(ledgerSpreadSetting, ledgerCol);
+    SpreadJsObj.initSheet(ledgerSpread.getActiveSheet(), ledgerSpreadSetting);
+    SpreadJsObj.selChangedRefreshBackColor(ledgerSpread.getActiveSheet());
+
+    postData('/tender/' + getTenderId() + '/schedule/ledger/load', {}, function (data) {
+        // let treeData = [];
+        // for(const sl of selectedLedgerList) {
+        //     const one = _.find(data, { 'ledger_id' : sl });
+        //     treeData.push(one);
+        // }
+        // treeData = setLeafData(treeData);
+        // console.log(treeData);
+        // let treeData = data;
+        const calcList = ['total_price'];
+        const showList = ['ledger_id', 'ledger_pid', 'order', 'level', 'tender_id', 'full_path',
+            'code', 'name', 'unit', 'dgn_qty1', 'dgn_qty2', 'dgn_price', 'quantity', 'total_price'];
+        for (const m of monthList) {
+            showList.push(m + '_tp');
+            showList.push(m + '_gcl');
+            // calcList.push(m + '_tp');
+            // calcList.push(m + '_gcl');
+        }
+        const baseLedgerTree = createNewPathTree('base', {
+            id: 'ledger_id',
+            pid: 'ledger_pid',
+            order: 'order',
+            level: 'level',
+            rootId: -1,
+            fullPath: 'full_path',
+            calcFields: calcList,
+            calcFun: function (node) {
+                node.dgn_price = ZhCalc.round(ZhCalc.div(node.total_price, node.dgn_qty1), 2);
+            }
+        });
+        const newLedgerList = setMonthToLedger(data.bills, data.slm);
+        baseLedgerTree.loadDatas(newLedgerList);
+        treeCalc.calculateAll(baseLedgerTree);
+        console.log(baseLedgerTree);
+        for (const d of baseLedgerTree.nodes) {
+            if (!d.b_code) {
+                const one = _.find(selectedLedgerList, function (item) {
+                    return item === d.ledger_id;
+                });
+                if(one) {
+                    ledgerTree.addData(d, showList);
+                }
+            }
+        }
+        console.log(ledgerTree);
+        ledgerTree.sortTreeNode(true);
+        // console.log(ledgerTree);
+        SpreadJsObj.loadSheetData(ledgerSpread.getActiveSheet(), SpreadJsObj.DataType.Tree, ledgerTree);
+    }, null, true);
+
+    const ledgerSpreadObj = {
+        refreshTree: function (sheet, data) {
+            SpreadJsObj.massOperationSheet(sheet, function () {
+                const tree = sheet.zh_tree;
+                // 处理删除
+                if (data.delete) {
+                    data.delete.sort(function (x, y) {
+                        return y.deleteIndex - x.deleteIndex;
+                    });
+                    for (const d of data.delete) {
+                        sheet.deleteRows(d.deleteIndex, 1);
+                    }
+                }
+                // 处理新增
+                if (data.create) {
+                    const newNodes = data.create;
+                    if (newNodes) {
+                        newNodes.sort(function (a, b) {
+                            return a.index - b.index;
+                        });
+
+                        for (const node of newNodes) {
+                            sheet.addRows(node.index, 1);
+                            SpreadJsObj.reLoadRowData(sheet, tree.nodes.indexOf(node), 1);
+                        }
+                    }
+                }
+                // 处理更新
+                if (data.update) {
+                    const rows = [];
+                    for (const u of data.update) {
+                        rows.push(tree.nodes.indexOf(u));
+                    }
+                    SpreadJsObj.reLoadRowsData(sheet, rows);
+                }
+                // 处理展开
+                if (data.expand) {
+                    const expanded = [];
+                    for (const e of data.expand) {
+                        if (expanded.indexOf(e) === -1) {
+                            const posterity = tree.getPosterity(e);
+                            for (const p of posterity) {
+                                sheet.setRowVisible(tree.nodes.indexOf(p), p.visible);
+                                expanded.push(p);
+                            }
+                        }
+                    }
+                }
+            });
+        },
+        editEnded: function (e, info) {
+            if (info.sheet.zh_setting) {
+                const select = SpreadJsObj.getSelectObject(info.sheet);
+                const col = info.sheet.zh_setting.cols[info.col];
+                const validText = is_numeric(info.editingText) ? parseFloat(info.editingText) : (info.editingText ? trimInvalidChar(info.editingText) : null);
+                const orgValue = select[col.field];
+                if (orgValue == validText || ((!orgValue || orgValue === '') && (validText === ''))) {
+                    SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                    return;
+                }
+                if (isNaN(validText)) {
+                    toastr.error('不能输入其它非数字类型字符');
+                    SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                    return;
+                }
+                const yearmonth = col.field.split('_')[0];
+                const mode = col.field.split('_')[1];
+                let plan_gcl = 0;
+                let plan_tp = 0;
+                // 判断输入位数,提示
+                if (mode === 'tp') {
+                    const reg = new RegExp('^([-]?)\\d+(\\.\\d{0,'+ parseInt(tenderInfo.decimal.tp) +'})?$');
+                    if (validText !== null && (!reg.test(validText))) {
+                        toastr.error('输入金额小数位数不能大于' + tenderInfo.decimal.tp + '位');
+                        SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                        return;
+                    }
+                    plan_gcl = select.dgn_price && select.dgn_price !== 0 ? ZhCalc.round(ZhCalc.div(validText, select.dgn_price), tenderInfo.decimal.up) : 0;
+                    plan_tp = validText;
+                } else {
+                    const reg = new RegExp('^([-]?)\\d+(\\.\\d{0,'+ parseInt(tenderInfo.decimal.up) +'})?$');
+                    if (validText !== null && (!reg.test(validText))) {
+                        toastr.error('输入工程量小数位数不能大于' + tenderInfo.decimal.up + '位');
+                        SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                        return;
+                    }
+                    plan_gcl = validText;
+                    plan_tp = select.dgn_price && select.dgn_price !== 0 ? ZhCalc.round(ZhCalc.mul(validText, select.dgn_price), tenderInfo.decimal.tp) : 0;
+                }
+                select[col.field] = validText;
+                const updateData = {
+                    lid: select.ledger_id,
+                    yearmonth,
+                    plan_gcl,
+                    plan_tp,
+                };
+                console.log(updateData);
+                postData(window.location.pathname + '/save', {type: 'ledger_edit', postData: updateData}, function (result) {
+                    if (mode === 'tp') {
+                        select[yearmonth + '_gcl'] = plan_gcl;
+                    } else {
+                        select[yearmonth + '_tp'] = plan_tp;
+                    }
+                    SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                },function () {
+                    select[col.field] = orgValue;
+                    SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                })
+            }
+        },
+        deletePress: function (sheet) {
+            return;
+        },
+        clipboardPasted(e, info) {
+            const hint = {
+                cellError: {type: 'error', msg: '粘贴内容超出了表格范围'},
+                numberExpr: {type: 'error', msg: '不能粘贴其它非数字类型字符'},
+                numberCan: {type: 'error', msg: '请粘贴大于0并且小于3位小数的浮点数'},
+            };
+            const range = info.cellRange;
+            const sortData = info.sheet.zh_data || [];
+            if (info.cellRange.row + info.cellRange.rowCount > sortData.length) {
+                toastMessageUniq(hint.cellError);
+                SpreadJsObj.reLoadSheetHeader(materialMonthSpread.getActiveSheet());
+                SpreadJsObj.reLoadSheetData(materialMonthSpread.getActiveSheet());
+                return;
+            }
+            if (sortData.length > 0 && range.col + range.colCount > 4 + months.length) {
+                toastMessageUniq(hint.cellError);
+                SpreadJsObj.reLoadSheetHeader(materialMonthSpread.getActiveSheet());
+                SpreadJsObj.reLoadSheetData(materialMonthSpread.getActiveSheet());
+                return;
+            }
+            const data = [];
+            for (let iRow = 0; iRow < range.rowCount; iRow++) {
+                let bPaste = true;
+                const curRow = range.row + iRow;
+                const materialMonthData = sortData[curRow];
+                const hintRow = range.rowCount > 1 ? curRow : '';
+                let sameCol = 0;
+                for (let iCol = 0; iCol < range.colCount; iCol++) {
+                    const curCol = range.col + iCol;
+                    const colSetting = info.sheet.zh_setting.cols[curCol];
+                    if (!colSetting) continue;
+
+                    let validText = info.sheet.getText(curRow, curCol);
+                    validText = is_numeric(validText) ? parseFloat(validText) : (validText ? trimInvalidChar(validText) : null);
+                    const orgValue = sortData[curRow][colSetting.field];
+                    if (orgValue == validText || ((!orgValue || orgValue === '') && (validText === ''))) {
+                        sameCol++;
+                        if (range.colCount === sameCol)  {
+                            bPaste = false;
+                        }
+                        continue;
+                    }
+                    const num = parseFloat(validText);
+                    if (isNaN(validText)) {
+                        toastMessageUniq(getPasteHint(hint.numberExpr, hintRow));
+                        bPaste = false;
+                        continue;
+                    }
+                    if (validText !== null && (num < 0 || !/^\d+(\.\d{1,3})?$/.test(num))) {
+                        toastMessageUniq(getPasteHint(hint.numberCan, hintRow));
+                        bPaste = false;
+                        continue;
+                    }
+                    materialMonthData[colSetting.field] = validText;
+                    sortData[curRow][colSetting.field] = validText;
+                }
+                if (bPaste) {
+                    data.push(materialMonthData);
+                } else {
+                    SpreadJsObj.reLoadRowData(info.sheet, curRow);
+                }
+            }
+            if (data.length === 0) {
+                SpreadJsObj.reLoadRowData(info.sheet, info.cellRange.row, info.cellRange.rowCount);
+                return;
+            }
+            // // 更新至服务器
+            // postData(window.location.pathname + '/month/save', { type:'paste', updateData: data }, function (result) {
+            //     SpreadJsObj.reLoadSheetData(materialMonthSpread.getActiveSheet());
+            //     materialBillsData = result.materialBillsData;
+            //     SpreadJsObj.loadSheetData(materialSpread.getActiveSheet(), SpreadJsObj.DataType.Data, materialBillsData);
+            //     m_tp = result.m_tp;
+            //     resetTpTable();
+            // }, function () {
+            //     SpreadJsObj.reLoadRowData(info.sheet, info.cellRange.row, info.cellRange.rowCount);
+            //     return;
+            // });
+        },
+    };
+
+    ledgerSpread.bind(spreadNS.Events.EditEnded, ledgerSpreadObj.editEnded);
+    SpreadJsObj.addDeleteBind(ledgerSpread, ledgerSpreadObj.deletePress);
+
+    // 进度计算方式选择
+    $('.mode-select').on('click', function () {
+        const _self = $(this);
+        postData(window.location.pathname + '/save', {type: 'mode', postData: $(this).data('mode')}, function (result) {
+            _self.addClass('disabled').attr('disabled', true);
+            _self.parents('.col-6').siblings('.col-6').find('button').removeClass('disabled').removeAttr('disabled');
+            $('#mode-tips').show();
+            $('#mode-cancel').show();
+            $('#mode').modal('hide');
+            schedule.mode = _self.data('mode');
+            SpreadJsObj.reLoadSheetData(ledgerSpread.getActiveSheet());
+        })
+    });
+
+    // 月份添加
+    $('#add-month').click(function () {
+        const range = $('#month-range').val();
+        if(range === '') {
+            toastr.error('请选择计划周期时间');
+            return;
+        }
+        const addMonthList = [];
+        const cycle = range.split(' ~ ');
+        if(cycle.length === 1) {
+            addMonthList.push(cycle[0]);
+        } else {
+            // 多个月份
+            const back_year = parseInt(cycle[1].split('-')[0]);
+            const back_month = parseInt(cycle[1].split('-')[1]);
+            const front_year = parseInt(cycle[0].split('-')[0]);
+            const front_month = parseInt(cycle[0].split('-')[1]);
+            if(back_year > front_year) {
+                const num = getDistanceMonth(cycle[0], cycle[1]);
+                let j = 1;
+                for (let i = 0; i <= num; i++) {
+                    if(front_month + i > 12*j) {
+                        j = j + 1;
+                    }
+                    const m = (front_month + i)%12 === 0 ? 12 : (front_month + i)%12;
+                    addMonthList.push((front_year + (j-1)) + '-' + (m < 10 ? '0' + m : m));
+                }
+            } else if (back_year === front_year) {
+                // 小于1年并没有跨年
+                for (let i = front_month; i <= back_month; i++) {
+                    addMonthList.push(back_year + '-' + (i < 10 ? '0' + i : i));
+                }
+            }
+        }
+        // 判断是否已添加本月份
+        if (addMonthList.length > 0) {
+            const hadmonth = [];
+            for (const m of addMonthList) {
+                const one = _.find(scheduleMonth, { yearmonth: m });
+                console.log(one, m);
+                if (one) {
+                    hadmonth.push(m);
+                }
+            }
+            if (hadmonth.length > 0) {
+                let html = '';
+                for (const hm of hadmonth) {
+                    html += `<div class="alert alert-danger">${hm} 已创建</div>`;
+                }
+                $('#add-month-error-list').html(html);
+                $('#add-month-error-list').show();
+                return;
+            }
+        } else {
+            toastr.error('请选择计划周期时间');
+            return;
+        }
+        $('#add-month-error-list').html('');
+        $('#add-month-error-list').hide();
+        const _self = $(this);
+        postData(window.location.pathname + '/save', {type: 'addmonth', postData: addMonthList}, function (result) {
+            _self.addClass('disabled').attr('disabled', true);
+            toastr.success('新增成功');
+            setTimeout(function () {
+                window.location.reload();
+            }, 500)
+
+        })
+    });
+
+    $('#month-table input[type="checkbox"]').click(function () {
+        const selectedMonth = [];
+        $('#month-table input:checkbox:checked').each(function () {
+            selectedMonth.push('「' + $(this).parents('td').siblings('td').text() + '」');
+        });
+        if(selectedMonth.length > 0) {
+            $('#del-month-list').text(selectedMonth.join(''));
+            $('#del-month-list').parent().show();
+            $('#del-month').removeAttr('disabled');
+        } else {
+            $('#del-month-list').parent().hide();
+            $('#del-month').attr('disabled', true);
+        }
+    });
+
+    $('#del-month').click(function () {
+        const selectedMonth = [];
+        $('#month-table input:checkbox:checked').each(function () {
+            selectedMonth.push($(this).parents('td').siblings().text());
+        });
+        if (selectedMonth.length === 0) {
+            toastr.error('请选择删除的计划周期');
+            return;
+        }
+        const _self = $(this);
+        postData(window.location.pathname + '/save', {type: 'delmonth', postData: selectedMonth}, function (result) {
+            _self.addClass('disabled').attr('disabled', true);
+            toastr.success('删除成功');
+            setTimeout(function () {
+                window.location.reload();
+            }, 500)
+
+        })
+    });
+
+    $.subMenu({
+        menu: '#sub-menu', miniMenu: '#sub-mini-menu', miniMenuList: '#mini-menu-list',
+        toMenu: '#to-menu', toMiniMenu: '#to-mini-menu',
+        key: 'menu.1.0.0',
+        miniHint: '#sub-mini-hint', hintKey: 'menu.hint.1.0.1',
+        callback: function (info) {
+            if (info.mini) {
+                $('.panel-title').addClass('fluid');
+                $('#sub-menu').removeClass('panel-sidebar');
+            } else {
+                $('.panel-title').removeClass('fluid');
+                $('#sub-menu').addClass('panel-sidebar');
+            }
+            ledgerSpread.refresh();
+            autoFlashHeight();
+        }
+    });
+});
+// 月份间隔
+function getDistanceMonth(startTime,endTime){
+    startTime = new Date(startTime);
+    endTime = new Date(endTime);
+    var dateToMonth = 0;
+    var startDate=startTime.getDate() + startTime.getHours()/24 + startTime.getMinutes()/24/60;
+    var endDate=endTime.getDate()  +endTime.getHours()/24 + endTime.getMinutes()/24/60;
+    if(endDate >= startDate){
+        dateToMonth = 0;
+    }else{
+        dateToMonth = -1;
+    }
+    let yearToMonth = (endTime.getYear() - startTime.getYear()) * 12;
+    let monthToMonth = endTime.getMonth() - startTime.getMonth();
+    return yearToMonth + monthToMonth + dateToMonth;
+}
+function setLeafData(tree) {
+    const newtree = [];
+    for (const t of tree) {
+        const child = _.find(tree, { 'ledger_pid': t.ledger_id });
+        if (!child && !t.is_leaf) {
+            t.is_leaf = true;
+        }
+        newtree.push(t);
+    }
+    return newtree;
+}
+
+function setMonthToLedger(ledgerList, slm) {
+    if (slm.length > 0) {
+        for(const s of slm) {
+            const index = _.findIndex(ledgerList, { 'ledger_id': s.lid });
+            if (index && index !== -1) {
+                ledgerList[index][s.yearmonth + '_tp'] = s.plan_tp;
+                ledgerList[index][s.yearmonth + '_gcl'] = s.plan_gcl;
+            }
+        }
+    }
+    return ledgerList;
+}
+const is_numeric = (value) => {
+    if (typeof(value) === 'object') {
+        return false;
+    } else {
+        return !Number.isNaN(Number(value)) && value.toString().trim() !== '';
+    }
+};

+ 345 - 0
app/public/js/shares/cs_tools.js

@@ -41,6 +41,17 @@ const showSideTools = function (show) {
     }
 };
 
+const showSelectTab = function(select, spread, afterShow) {
+    const tab = $(select), tabPanel = $(tab.attr('content'));
+    $('a', '#side-menu').removeClass('active');
+    tab.addClass('active');
+    $('.tab-content .tab-pane').removeClass('active');
+    tabPanel.addClass('active');
+    showSideTools(true);
+    spread && spread.refresh();
+    if (afterShow) afterShow();
+};
+
 (function($){
     /**
      * 错误列表
@@ -580,4 +591,338 @@ const showSideTools = function (show) {
         });
         return {spread: resultSpread};
     };
+
+    $.billsTag = function (setting) {
+        if (!setting.selector || !setting.relaSpread) return;
+        if (!setting.tagType) setting.tagType = [
+            {tagClass: 'text-primary', color: '#007bff'},
+            {tagClass: 'text-success', color: '#28a745'},
+            {tagClass: 'text-danger', color: '#dc3545'},
+            {tagClass: 'text-warning', color: '#da9500'},
+            {tagClass: 'text-info', color: '#17a2b8'},
+        ];
+        const obj = $(setting.selector);
+        const html = [], pageLength = 15;
+        let billsTags = [], classIndexes = [], billsIndexes = {}, curShow = [];
+        html.push('<div class="sjs-bar d-flex justify-content-between">');
+        // 下拉过滤
+        html.push('<div class="dropdown mr-2">');
+        html.push('<a class="btn btn-sm btn-outline-secondary" id="dmb-bills-tag" data-toggle="dropdown" title="优先显示" aria-expanded="false"><i class="fa fa-list-ol" id="bills-tag-filter"></i></a>');
+        html.push('<div class="dropdown-menu" aria-labelledby="dmb-bills-tag" style="min-width: 60px; position: absolute; transform: translate3d(0px, 22px, 0px); top: 0px; left: 0px; will-change: transform;" x-placement="bottom-start">');
+        html.push('<a class="dropdown-item" href="javascript: void(0);" tagType="all" ><i class="fa fa-list-ol"></i></a>');
+        for (const t of setting.tagType) {
+            html.push(`<a class="dropdown-item ${t.tagClass}" href="javascript: void(0);" tagType="${t.tagClass}" ><i class="fa fa-tag"></i></a>`);
+            t.tags = [];
+            classIndexes.push(t);
+        }
+        html.push('</div>', '</div>');
+        // 搜索框
+        html.push('<div class="input-group input-group-sm">');
+        html.push('<input type="text" class="form-control" placeholder="可查找 项目节编号 / 清单编号 /名称" id="bills-tag-keyword">');
+        html.push('<div class="input-group-append">', '<div class="input-group-cancel">',
+            '<a href="javascript: void(0);" id="bills-tag-clear" class="text-danger"><i class="fa fa-times-circle" title="移除搜索结果"></i></a>', '</div>',
+            '<button class="btn btn-outline-secondary" type="button" id="bills-tag-search">搜索</button>', '</div>');
+        html.push('</div>');
+        html.push('</div>');
+        // 书签列表
+        html.push('<div class="sjs-sh" style="overflow: auto;" id="bills-tag-list"></div>');
+        obj.html(html.join(''));
+
+        const clearViewTags = function () {
+            const viewTags = $('.tag-item', obj);
+            if (viewTags && viewTags.length > 0) viewTags.remove();
+            billsTags.forEach(x => {x.display = false});
+        };
+
+        const getTagEditHtml = function(tag) {
+            const tagClass = classIndexes.find(x => {return x.color === tag.color});
+            const tagHtml = [];
+            tagHtml.push('<div name="tag-edit">');
+            tagHtml.push('<div class="card-header p-2"><div class="dropdown">');
+            tagHtml.push(`<a class="pull-left mr-2" href="javascript: void(0);" id="tag-change-color" tag-color="${tag.color}" data-toggle="dropdown" aria-expanded="false"><i class="fa fa-tag ${tagClass.tagClass}" title="修改书签颜色"></i></a>`);
+            // 下拉选择颜色
+            tagHtml.push('<div class="dropdown-menu" aria-labelledby="tag-change-color" style="min-width:60px">',
+                '<a class="dropdown-item text-primary" href="javascript: void(0);" name="tag-color"><i class="fa fa-tint"></i></a>',
+                '<a class="dropdown-item text-success " href="javascript: void(0);" name="tag-color"><i class="fa fa-tint"></i></a>',
+                '<a class="dropdown-item text-danger " href="javascript: void(0);" name="tag-color"><i class="fa fa-tint"></i></a>',
+                '<a class="dropdown-item text-warning " href="javascript: void(0);" name="tag-color"><i class="fa fa-tint"></i></a>',
+                '<a class="dropdown-item text-info " href="javascript: void(0);" name="tag-color"><i class="fa fa-tint"></i></a>', '</div>');
+            tagHtml.push('</div>');
+            tag.node && tagHtml.push((tag.node.code || '') + (tag.node.b_code || ''), ' / ', tag.node.name || '');
+            tagHtml.push('</div>');
+
+            tagHtml.push('<div class="card-body p-2">');
+            tagHtml.push('<p class="card-text">', '<textarea class="form-control" id="tag-comment">', tag.comment, '</textarea>', '</p>');
+
+            tagHtml.push('<div class="d-flex justify-content-between">');
+            // 参与人可见
+            tagHtml.push('<div class="custom-control custom-switch mr-2">');
+            tagHtml.push('<input type="checkbox" class="custom-control-input custom-control-warning-input" id="tag-share"', tag.share ? 'checked' : '', '>');
+            tagHtml.push('<label class="custom-control-label custom-control-warning-label" for="tag-share" data-toggle="tooltip" data-placement="bottom" title="" data-original-title="所有参与台帐审批管理的用户都可以看到这条书签"><i class="fa fa-users"></i> 参与者可见</label>');
+            tagHtml.push('</div>');
+            // 编辑按钮
+            tagHtml.push('<div>', '<button type="button" class="btn btn-sm btn-outline-danger mr-3" id="tag-del"><i class="fa fa-close"></i>  删除书签</button>',
+                '<button type="button" class="btn btn-sm btn-outline-success mr-1" id="tag-edit-ok"><i class="fa fa-check"></i> 确定</button>',
+                '<button type="button" class="btn btn-sm btn-outline-secondary" id="tag-edit-cancel">取消</button>', '</div>');
+            tagHtml.push('</div>');
+
+            tagHtml.push('</div>');
+
+            tagHtml.push('</div>');
+            return tagHtml.join('');
+        };
+
+        const getTagDisplayHtml = function (tag) {
+            const tagClass = classIndexes.find(x => {return x.color === tag.color});
+            const tagHtml = [];
+            tagHtml.push('<div name="tag-view">');
+            tagHtml.push('<div class="card-header p-2"><div class="dropdown">');
+            tagHtml.push(`<div class="pull-left mr-2"><i class="fa fa-tag ${tagClass.tagClass}"></i></div>`);
+            tagHtml.push('</div>');
+            tag.node && tagHtml.push((tag.node.code || '') + (tag.node.b_code || ''), ' / ', tag.node.name || '');
+            if (tag.share) {
+                tagHtml.push('<i class="fa fa-users pull-right text-warning" data-toggle="tooltip" data-placement="bottom" title="" data-original-title="所有参与台帐审批管理的用户都可以看到这条书签"></i>')
+            }
+            tagHtml.push('<div class="pull-right edit-tag-btn">');
+            const lid = tag.node ? tag.node.ledger_id : -1;
+            tagHtml.push(`<a class="mr-1" name="bills-tag-locate" href="javascript: void(0);" lid="${lid}"><i class="fa fa-crosshairs"></i> 定位</a>`);
+            if (tag.uid === userID) tagHtml.push(`<a href="javascript: void(0);" name="bills-tag-edit" tag-id="${tag.id}"><i class="fa fa-edit"></i> 编辑</a>`);
+            tagHtml.push('</div>');
+            tagHtml.push('<div class="card-body p-2">', '<p class="card-text">', tag.comment, '</p>', '</div>');
+            tagHtml.push('</div>');
+            return tagHtml.join('');
+        };
+
+        const searchTagsAndShow = function () {
+            const keyword = $('#bills-tag-keyword').val();
+            const filterClass = $('#bills-tag-filter')[0].classList;
+            const tagClass = filterClass.length > 2 ? filterClass[2] : null;
+            const ci = tagClass ? classIndexes.find(x => {return x.tagClass === tagClass}) : null;
+            curShow = billsTags.filter(x => {
+                if (ci && ci.color !== x.color) return false;
+                if (!keyword) return true;
+                if (x.node.code && x.node.code.indexOf(keyword) >= 0) return true;
+                if (x.node.b_code && x.node.b_code.indexOf(keyword) >= 0) return true;
+                if (x.node.name && x.node.name.indexOf(keyword) >= 0) return true;
+                return false;
+            });
+            reloadViewTags();
+        };
+
+        const refreshTagView = function (tag) {
+            const obj = $('#bills-tag-' + tag.id);
+            if (obj && obj.length > 0) {
+                obj.html(getTagDisplayHtml(tag));
+            }
+        };
+
+        const refreshBillsTagView = function (bills) {
+            const bi = billsIndexes[bills.id] || [];
+
+            for (const tag of bi) {
+                refreshTagView(tag);
+            }
+        };
+
+        const reviewTag = function (tag, isTop = false) {
+            const obj = $('#bills-tag-' + tag.id);
+            if (obj && obj.length > 0) {
+                obj.html(getTagDisplayHtml(tag));
+            } else {
+                const objHtml = [];
+                objHtml.push(`<div class="card border-primary my-2 tag-item" id="bills-tag-${tag.id}" tag-id="${tag.id}">`);
+                objHtml.push(getTagDisplayHtml(tag));
+                objHtml.push('</div>');
+                if (isTop) {
+                    $('#bills-tag-list').prepend(objHtml.join(''));
+                } else {
+                    $('#bills-tag-list').append(objHtml.join(''));
+                }
+            }
+            tag.display = true;
+        };
+
+        const loadViewTags = function () {
+            let showCount = 0;
+            for (const t of curShow) {
+                if (showCount >= pageLength) continue;
+                if (t.display) continue;
+                reviewTag(t);
+                showCount++;
+            }
+        };
+
+        const reloadViewTags = function () {
+            clearViewTags();
+            $("#bills-tag-lis").scrollTop(0);
+            loadViewTags();
+        };
+
+        const _addToBillsIndex = function(data, isTop = false) {
+            let bi = billsIndexes[data.lid];
+            if (!bi) {
+                bi = [];
+                billsIndexes[data.lid] = bi;
+            }
+            isTop ? bi.unshift(data) : bi.push(data);
+        };
+
+        const loadDatas = function (datas) {
+            billsTags = [];
+            billsIndexes = {};
+            for (const d of datas) {
+                billsTags.push(d);
+                _addToBillsIndex(d);
+            }
+            curShow = billsTags;
+            reloadViewTags();
+        };
+
+        const updateDatas = function (data) {
+            const refresh = {};
+            if (data.add) {
+                billsTags.push(data.add);
+                _addToBillsIndex(data.add, true);
+                refresh.add = data.add;
+            }
+            if (data.del) {
+                const delTag = billsTags.find(x => {return x.id === data.del});
+                billsTags.splice(billsTags.indexOf(delTag), 1);
+                if (delTag.node) {
+                    const bi = billsIndexes[delTag.node.id];
+                    bi.splice(bi.indexOf(delTag), 1);
+                }
+                refresh.del = delTag;
+            }
+            if (data.update) {
+                const updateTag = billsTags.find(x => {return x.id === data.update.id});
+                for (const prop in data.update) {
+                    updateTag[prop] = data.update[prop];
+                }
+                refresh.update = updateTag;
+            }
+            return refresh;
+        };
+
+        const updateDatasAndShow = function (data) {
+            const relaBills = [];
+            const refresh = updateDatas(data);
+            if (refresh.add) {
+                reviewTag(refresh.add, true);
+                relaBills.push(refresh.add.node);
+            }
+            if (refresh.del) {
+                $('#bills-tag-' + refresh.del.id).remove();
+                relaBills.push(refresh.del.node);
+            }
+            if (refresh.update) {
+                refreshTagView(refresh.update);
+                relaBills.push(refresh.update.node);
+            }
+            return relaBills;
+        };
+
+        const show = function () {
+            showSelectTab(setting.selector, null, setting.afterShow);
+        };
+
+        const getBillsTagsColor = function (id) {
+            const billsTags = billsIndexes[id] || [];
+            return billsTags.length > 0 ? billsTags.map(x => {return x.color}) : undefined;
+        };
+
+        const getBillsTagsInfo = function (id) {
+            const billsTags = billsIndexes[id] || [];
+            return billsTags.length > 0 ? billsTags.map(x => {
+                const tagClass = classIndexes.find(tc => {return tc.color === x.color});
+                return {color: x.color, comment: x.comment, tagClass: tagClass.tagClass};
+            }) : undefined;
+        };
+
+        $('body').on('click', '[name=bills-tag-locate]', function () {
+            const lid = parseInt(this.getAttribute('lid'));
+            SpreadJsObj.locateTreeNode(setting.relaSpread.getActiveSheet(), lid);
+            setting.afterLocated && setting.afterLocated();
+        });
+        $('body').on('click', '[name=bills-tag-edit]', function () {
+            const tagId = this.getAttribute('tag-id');
+            const tag = billsTags.find(x => {return x.id == tagId});
+            if (tag) {
+                const obj = $('#bills-tag-' + tag.id);
+                $('[name=tag-view]', obj).hide();
+                obj.append(getTagEditHtml(tag));
+            }
+        });
+        $('body').on('click', '#tag-edit-cancel', function () {
+            const obj = $('[name=tag-edit]').parent();
+            $('[name=tag-edit]').remove();
+            $('[name=tag-view]', obj).show();
+        });
+        $('body').on('click', '#tag-del', function () {
+            const obj = $('[name=tag-edit]').parent();
+            postData(setting.updateUrl, {del: parseInt(obj.attr('tag-id'))}, function (result) {
+                if (!result.del) return;
+
+                const bills = updateDatasAndShow(result);
+                setting.afterModify && setting.afterModify(bills);
+            });
+        });
+        $('body').on('click', '#tag-edit-ok', function () {
+            const obj = $('[name=tag-edit]').parent();
+            const data = {
+                id: parseInt(obj.attr('tag-id')),
+                share: $('#tag-share')[0].checked,
+                comment: $('#tag-comment').val(),
+                color: $('#tag-change-color').attr('tag-color'),
+            };
+            postData(setting.updateUrl, {update: data}, function (result) {
+                if (!result.update) return;
+
+                const bills = updateDatasAndShow(result);
+                setting.afterModify && setting.afterModify(bills);
+            });
+        });
+        $('body').on('click', '[name=tag-color]', function () {
+            const tagClass = this.classList[1];
+            const ci = classIndexes.find(tc => {return tc.tagClass === tagClass});
+            const tcc = $('#tag-change-color');
+            tcc.attr('tag-color', ci.color);
+            tcc.find('i').attr('class', 'fa fa-tag ' + tagClass);
+        });
+        $('body').on('click', '[tagType]', function () {
+            const tagClass = this.getAttribute('tagType');
+            if (tagClass === 'all') {
+                $('#bills-tag-filter').attr('class', 'fa fa-list-ol');
+            } else {
+                $('#bills-tag-filter').attr('class', 'fa fa-tag ' + tagClass);
+            }
+            searchTagsAndShow();
+        });
+        // 防抖
+        function debounce(fun, delay) {
+            let timer = null;
+            return function () {
+                if (timer) {
+                    clearTimeout(timer);
+                }
+                timer = setTimeout(fun, delay);
+            }
+        }
+        $('#bills-tag-list').bind('scroll', debounce(function (e) {
+            const obj = $('#bills-tag-list');
+            var sum = obj[0].scrollHeight;
+            if (sum <= obj.scrollTop() + obj.height()) {
+                loadViewTags();
+            }
+        }, 300));
+        $('#bills-tag-clear').bind('click', () => {
+            if (!$('#bills-tag-keyword').val()) return;
+            $('#bills-tag-keyword').val('');
+            searchTagsAndShow();
+        });
+        $('#bills-tag-search').bind('click', () => {searchTagsAndShow();});
+        $('#bills-tag-keyword').bind('keydown', e => {if (e.keyCode === 13) searchTagsAndShow();});
+
+        return { loadDatas, updateDatasAndShow, show, getBillsTagsColor, getBillsTagsInfo, refreshBillsTagView, }
+    }
 })(jQuery);

+ 1 - 0
app/public/js/shares/tenders2tree.js

@@ -85,6 +85,7 @@ const Tender2Tree = (function () {
                 tid: t.id,
                 name: t.name,
                 phase: t.lastStage ? '第' + t.lastStage.order + '期' : '台账',
+                stageCount: t.lastStage ? t.lastStage.order : 0,
             };
             if (ledgerAuditConst && stageAuditConst) {
                 node.status = t.lastStage ? stageAuditConst.statusString[t.lastStage.status] : ledgerAuditConst.statusString[t.ledger_status];

+ 564 - 0
app/public/js/shenpi.js

@@ -9,6 +9,7 @@
  */
 const tenderTree = [];
 let parentId = 0;
+let selects;
 // 查询方法
 function findNode (key, value, arr) {
     for (const a of arr) {
@@ -305,6 +306,7 @@ $(document).ready(function () {
                 }
                 addhtml += '</ul>\n';
                 _self.parents('.form-group').siblings('.lc-show').html(addhtml);
+
             } else if (this_status === sp_status.gdzs) {
                 let addhtml = '<ul class="list-unstyled">\n' +
                     '                                        <li class="d-flex justify-content-start mb-3">\n' +
@@ -317,6 +319,14 @@ $(document).ready(function () {
                 addhtml += '</ul>\n';
                 _self.parents('.form-group').siblings('.lc-show').html(addhtml);
             }
+
+            if(this_code === 'stage') {
+                if(this_status === sp_status.gdspl) {
+                    $('#stage_cooperation').show();
+                } else {
+                    $('#stage_cooperation').hide();
+                }
+            }
         });
     });
 
@@ -559,4 +569,558 @@ $(document).ready(function () {
             }, 1000)
         })
     });
+
+    const ledgerSpread = SpreadJsObj.createNewSpread($('#ledger-spread')[0]);
+    const treeSetting = {
+        id: 'ledger_id',
+        pid: 'ledger_pid',
+        order: 'order',
+        level: 'level',
+        rootId: -1,
+        fullPath: 'full_path',
+    };
+    const ledgerTree = createNewPathTree('base', treeSetting);
+
+    const ledgerSpreadSetting = {
+        cols: [
+            {title: '编号', colSpan: '1', rowSpan: '2', field: 'code', hAlign: 0, width: 165, formatter: '@', readOnly: true, cellType: 'tree'},
+            {title: '名称', colSpan: '1', rowSpan: '2', field: 'name', hAlign: 0, width: 185, formatter: '@', readOnly: true},
+            {title: '密码', colSpan: '1', rowSpan: '2', field: 'pwd', hAlign: 0, width: 100, formatter: '@', getValue:'getValue.pwd', readOnly: 'readOnly.pwd'},
+        ],
+        emptyRows: 0,
+        headRows: 1,
+        headRowHeight: [25, 25],
+        defaultRowHeight: 21,
+        headerFont: '12px 微软雅黑',
+        font: '12px 微软雅黑',
+        // readOnly: true,
+        localCache: {
+            key: 'ledger-cooperation',
+            colWidth: true,
+        }
+    };
+
+    const ledgerCol = {
+        getValue: {
+            pwd: function (data) {
+                let txt = '';
+                // console.log(data);
+                // if (data.is_leaf) {
+                if (data.pwd && data.pwd !== '' && data.pwd !== null) {
+                    txt = data.pwd;
+                } else if(data.can_edit) {
+                    txt = '请输入密码';
+                }
+                // }
+                return txt;
+            }
+        },
+        readOnly: {
+            pwd: function (data) {
+                return !data.can_edit;
+            },
+        },
+    };
+
+    const ledgerSpreadObj = {
+        setFontColor: function(row = null) {
+            if(row) {
+                const value = ledgerSpread.getActiveSheet().getValue(row, 2);
+                if (value === '请输入密码') {
+                    ledgerSpread.getActiveSheet().getCell(row, 2).foreColor('#007bff');
+                } else {
+                    ledgerSpread.getActiveSheet().getCell(row, 2).foreColor('#000');
+                }
+            } else {
+                const rowCount = ledgerSpread.getActiveSheet().getRowCount();
+                for(let i = 0; i < rowCount; i++){
+                    const value = ledgerSpread.getActiveSheet().getValue(i, 2);
+                    if (value === '请输入密码') {
+                        ledgerSpread.getActiveSheet().getCell(i, 2).foreColor('#007bff');
+                    } else {
+                        ledgerSpread.getActiveSheet().getCell(i, 2).foreColor('#000');
+                    }
+                }
+            }
+        },
+        setAllRightPwd: function(uid) {
+            selects = [];
+            for (const l of ledgerTree.datas) {
+                const coo = _.find(ledger_cooperation_list, { 'ledger_id': l.ledger_id, 'user_id': parseInt(uid) });
+                if (l.pwd && !coo) {
+                    delete l.pwd;
+                    l.can_edit = true;
+                    selects.push(l);
+                } else if(coo) {
+                    l.pwd = coo.pwd;
+                    l.can_edit = true;
+                    selects.push(l);
+                } else if (l.can_edit === false) {
+                    l.can_edit = true;
+                    selects.push(l);
+                }
+            }
+            if(selects.length > 0) {
+                // updateByCanEdit(ledgerTree.nodes, _.filter(ledger_cooperation_list, { user_id: parseInt(uid) }), false);
+                const refreshNode = ledgerTree.loadPostData({update: selects});
+                ledgerSpreadObj.refreshTree(ledgerSpread.getActiveSheet(), refreshNode);
+                ledgerSpreadObj.setFontColor();
+            }
+        },
+        refreshTree: function (sheet, data) {
+            SpreadJsObj.massOperationSheet(sheet, function () {
+                const tree = sheet.zh_tree;
+                // 处理删除
+                if (data.delete) {
+                    data.delete.sort(function (x, y) {
+                        return y.deleteIndex - x.deleteIndex;
+                    });
+                    for (const d of data.delete) {
+                        sheet.deleteRows(d.deleteIndex, 1);
+                    }
+                }
+                // 处理新增
+                if (data.create) {
+                    const newNodes = data.create;
+                    if (newNodes) {
+                        newNodes.sort(function (a, b) {
+                            return a.index - b.index;
+                        });
+
+                        for (const node of newNodes) {
+                            sheet.addRows(node.index, 1);
+                            SpreadJsObj.reLoadRowData(sheet, tree.nodes.indexOf(node), 1);
+                        }
+                    }
+                }
+                // 处理更新
+                if (data.update) {
+                    const rows = [];
+                    for (const u of data.update) {
+                        rows.push(tree.nodes.indexOf(u));
+                    }
+                    SpreadJsObj.reLoadRowsData(sheet, rows);
+                }
+                // 处理展开
+                if (data.expand) {
+                    const expanded = [];
+                    for (const e of data.expand) {
+                        if (expanded.indexOf(e) === -1) {
+                            const posterity = tree.getPosterity(e);
+                            for (const p of posterity) {
+                                sheet.setRowVisible(tree.nodes.indexOf(p), p.visible);
+                                expanded.push(p);
+                            }
+                        }
+                    }
+                }
+            });
+        },
+        enterCell: function(e, info) {
+            if (info.sheet.zh_setting && info.col === 2) {
+                if (ledgerSpread.getActiveSheet().getValue(info.row, info.col) !== '') {
+                    const col = info.sheet.zh_setting.cols[info.col];
+                    info.sheet.setActiveCell(info.row, 2);
+                    info.sheet.startEdit(true);
+                }
+            }
+        },
+        editStarting: function (e, info) {
+            if (info.sheet.zh_setting) {
+                const select = SpreadJsObj.getSelectObject(info.sheet);
+                const col = info.sheet.zh_setting.cols[info.col];
+                ledgerSpread.getActiveSheet().getCell(info.row, 2).foreColor('#000');
+                if(col.getValue(select) === '请输入密码') {
+                    ledgerSpread.getActiveSheet().setValue(info.row, 2, '');
+                }
+            }
+        },
+        editEnded: function (e, info) {
+            if (info.sheet.zh_setting) {
+                const select = SpreadJsObj.getSelectObject(info.sheet);
+                const col = info.sheet.zh_setting.cols[info.col];
+                const validText = trimInvalidChar(info.editingText);
+                const user_id = parseInt($('#stage_audits').val());
+                const orgValue = select[col.field];
+                const reg = /^[0-9a-zA-Z]+$/;
+                if(validText !== '' && !reg.test(validText)) {
+                    toastr.error('不能输入非数字和字母的字符');
+                    SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                    ledgerSpreadObj.setFontColor(info.row);
+                    return;
+                }
+                if((validText === '' && orgValue === undefined) || validText == orgValue) {
+                    SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                    ledgerSpreadObj.setFontColor(info.row);
+                    return;
+                }
+                // const num = _.filter(ledger_cooperation_list, { pwd: validText, status: 1, user_id });
+                // console.log(num, ledger_cooperation_list);
+                // if(num.length > 0) {
+                //     toastr.error('同一个审批人密码不能相同');
+                //     SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                //     ledgerSpreadObj.setFontColor(info.row);
+                //     return;
+                // }
+                select.pwd = validText;
+                const data = {
+                    type: 'pwd',
+                    ledger_id: select.ledger_id,
+                    user_id,
+                    pwd: validText,
+                };
+                info.sheet.setSelection(info.row, 0, 1, 1);
+                postData('/tender/' + cur_tenderid + '/shenpi/audit/save', data, function (result) {
+                    const lcindex = _.findIndex(ledger_cooperation_list, { ledger_id: select.ledger_id, user_id });
+                    let flag = false;
+                    if(lcindex !== -1) {
+                        validText === '' ? ledger_cooperation_list.splice(lcindex, 1) : ledger_cooperation_list.splice(lcindex, 1, result);
+                        flag = validText === '';
+                    } else {
+                        ledger_cooperation_list.push(result);
+                        flag = false;
+                    }
+                    $('#cooperation-num').text(ledger_cooperation_list.length);
+                    setLeftTable(ledgerTree.datas, ledger_cooperation_list, user_id, $('#stage_audits option:selected').text());
+                    selects = [select];
+                    // if(select) {
+                    //     setAllChildrenCanEdit(select, flag);
+                    //     setParentCanEdit(ledgerTree.nodes, select.ledger_pid, flag);
+                    // }
+                    const refreshNode = ledgerTree.loadPostData({update: selects});
+                    ledgerSpreadObj.refreshTree(info.sheet, refreshNode);
+                    ledgerSpreadObj.setFontColor();
+                });
+            }
+        },
+        deletePress: function (sheet) {
+            return;
+        },
+        clipboardPasted(e, info) {
+            // 禁止复制粘贴
+            SpreadJsObj.reLoadSheetHeader(ledgerSpread.getActiveSheet());
+            SpreadJsObj.reLoadSheetData(ledgerSpread.getActiveSheet());
+            return;
+        }
+    };
+
+    sjsSettingObj.setFxTreeStyle(ledgerSpreadSetting, sjsSettingObj.FxTreeStyle.jz);
+    if (thousandth) sjsSettingObj.setTpThousandthFormat(ledgerSpreadSetting);
+    SpreadJsObj.initSpreadSettingEvents(ledgerSpreadSetting, ledgerCol);
+    SpreadJsObj.initSheet(ledgerSpread.getActiveSheet(), ledgerSpreadSetting);
+    SpreadJsObj.selChangedRefreshBackColor(ledgerSpread.getActiveSheet());
+    ledgerSpread.bind(spreadNS.Events.EditStarting, ledgerSpreadObj.editStarting);
+    ledgerSpread.bind(spreadNS.Events.EditEnded, ledgerSpreadObj.editEnded);
+    SpreadJsObj.addDeleteBind(ledgerSpread, ledgerSpreadObj.deletePress);
+    ledgerSpread.bind(spreadNS.Events.ClipboardPasted, ledgerSpreadObj.clipboardPasted);
+    ledgerSpread.bind(spreadNS.Events.EnterCell, ledgerSpreadObj.enterCell);
+    let ledger_data, ledger_cooperation_list = [];
+
+    // 多人协同
+    $('#cooperation').on('shown.bs.modal', function () {
+        // 执行一些动作...
+        // 更新新的多人协同表格信息
+        const newUidList = [];
+        $('.stage_div ul li').each(function (k, v) {
+            const uid = $(v).find('a').data('id');
+            if(uid) newUidList.push(uid);
+        });
+        const oldUidList = [];
+        $('#stage_audits option').each(function (k, v) {
+            const uid = parseInt($(v).val());
+            if(k !== 0) oldUidList.push(uid);
+        });
+        if (!_.isEqual(oldUidList, newUidList)) {
+            const yb = _.find(accountList, { 'id': cur_uid });
+            let newhtml = '<option value="' + yb.id + '">' + yb.name + '(原报)</option>';
+            if(newUidList.length > 0) {
+                for (const [i,id] of newUidList.entries()) {
+                    const audit = _.find(accountList, { 'id': id });
+                    newhtml += '<option value="' + audit.id + '">' + audit.name + '(' + transFormToChinese(i+1) + '审)</option>';
+                }
+            }
+            $('#stage_audits').html(newhtml);
+            if(ledger_data) {
+                setLeftTable(ledgerTree.datas, ledger_cooperation_list, cur_uid, yb.name + '(原报)');
+                ledgerSpreadObj.setAllRightPwd(cur_uid);
+            }
+        }
+        if(!ledger_data) {
+            postData('/tender/' + cur_tenderid + '/shenpi/ledger/load', {}, function (data) {
+                ledger_data = true;
+                const ledgerList = setRightData(data.ledgerList, data.ledgerCooperationList);
+                ledgerTree.loadDatas(ledgerList);
+                ledger_cooperation_list = data.ledgerCooperationList;
+                const yb = _.find(accountList, { 'id': cur_uid });
+                setLeftTable(ledgerList, ledger_cooperation_list, cur_uid, yb.name + '(原报)');
+                // treeCalc.calculateAll(ledgerTree);
+                selects = [];
+                // updateByCanEdit(ledgerTree.nodes, _.filter(ledger_cooperation_list, { user_id: cur_uid }), false);
+                ledgerTree.loadPostData({update: selects});
+                console.log(ledgerTree);
+                SpreadJsObj.loadSheetData(ledgerSpread.getActiveSheet(), SpreadJsObj.DataType.Tree, ledgerTree);
+                ledgerSpreadObj.setFontColor();
+            }, null, true);
+        }
+        ledgerSpread.refresh();
+    });
+
+    $('#stage_audits').change(function () {
+        const uid = $(this).val();
+        const title = $("#stage_audits option:selected").text();
+        setLeftTable(ledgerTree.datas, ledger_cooperation_list, uid, title);
+        ledgerSpreadObj.setAllRightPwd(uid);
+    });
+
+    $('body').on('click', '.del-pwd', function () {
+        const ledger_id = parseInt($(this).data('lid'));
+        const user_id = parseInt($(this).data('uid'));
+        const data = {
+            type: 'pwd',
+            user_id,
+            ledger_id,
+            pwd: '',
+        };
+        postData('/tender/' + cur_tenderid + '/shenpi/audit/save', data, function (result) {
+            const lcindex = _.findIndex(ledger_cooperation_list, { ledger_id, user_id });
+            ledger_cooperation_list.splice(lcindex, 1);
+            setLeftTable(ledgerTree.datas, ledger_cooperation_list, user_id, $('#stage_audits option:selected').text());
+            const select = _.find(ledgerTree.datas, { ledger_id });
+            delete select.pwd;
+            // const refreshNode = ledgerTree.loadPostData({update: select});
+            selects = [select];
+            // if(select) {
+            //     setAllChildrenCanEdit(select, true);
+                // setParentCanEdit(ledgerTree.nodes, select.ledger_pid, true);
+            // }
+            const refreshNode = ledgerTree.loadPostData({update: selects});
+            ledgerSpreadObj.refreshTree(ledgerSpread.getActiveSheet(), refreshNode);
+            ledgerSpreadObj.setFontColor();
+            $('#cooperation-num').text(ledger_cooperation_list.length);
+        });
+    });
+
+    $('body').on('click', '.edit-pwd', function () {
+        const pwd = $(this).data('pwd');
+        const html = `<div class="input-group input-group-sm">
+            <input type="text" class="form-control" value="${pwd}" placeholder="输入新密码" aria-describedby="button-${$(this).data('lid')}" style="width:50px">
+            <div class="input-group-append" id="button-${$(this).data('lid')}">
+                <button class="btn btn-outline-primary confirm-btn" data-pwd="${pwd}" data-lid="${$(this).data('lid')}" data-uid="${$(this).data('uid')}" type="button">确认</button>
+                <button class="btn btn-outline-secondary cancel-btn" data-pwd="${pwd}" data-lid="${$(this).data('lid')}" data-uid="${$(this).data('uid')}" type="button">取消</button>
+            </div>
+            </div>`;
+        $(this).parents('td').html(html);
+    });
+
+    $('body').on('click', '.cancel-btn', function () {
+        const html = `<p class="mb-0">${$(this).data('pwd')}</p><a href="javascript:void(0);" data-lid="${$(this).data('lid')}" data-uid="${$(this).data('uid')}" data-pwd="${$(this).data('pwd')}" class="edit-pwd">修改</a> <a href="javascript:void(0)" data-lid="${$(this).data('lid')}" data-uid="${$(this).data('uid')}" class="del-pwd text-danger">移除</a>`;
+        $(this).parents('td').html(html);
+    });
+
+    $('body').on('click', '.confirm-btn', function () {
+        const validText = $(this).parents('td').find('input').val();
+        const orgValue = $(this).data('pwd');
+        const ledger_id = parseInt($(this).data('lid'));
+        const user_id = parseInt($(this).data('uid'));
+        const reg = /^[0-9a-zA-Z]+$/;
+        if(!reg.test(validText)) {
+            toastr.error('不能输入非数字和字母的字符');
+            return;
+        }
+        if(validText == orgValue) {
+            const html = `<p class="mb-0">${$(this).data('pwd')}</p><a href="javascript:void(0);" data-lid="${$(this).data('lid')}" data-uid="${$(this).data('uid')}" data-pwd="${$(this).data('pwd')}" class="edit-pwd">修改</a> <a href="javascript:void(0)" data-lid="${$(this).data('lid')}" data-uid="${$(this).data('uid')}" class="del-pwd text-danger">移除</a>`;
+            $(this).parents('td').html(html);
+            return;
+        }
+        // const num = _.filter(ledger_cooperation_list, { pwd: validText, status: 1, user_id });
+        // if(num.length > 0) {
+        //     toastr.error('同一个审批人密码不能相同');
+        //     return;
+        // }
+        const data = {
+            type: 'pwd',
+            ledger_id,
+            user_id,
+            pwd: validText,
+        };
+        postData('/tender/' + cur_tenderid + '/shenpi/audit/save', data, function (result) {
+            const lcindex = _.findIndex(ledger_cooperation_list, { ledger_id, user_id });
+            ledger_cooperation_list.splice(lcindex, 1, result);
+            setLeftTable(ledgerTree.datas, ledger_cooperation_list, user_id, $('#stage_audits option:selected').text());
+            const select = _.find(ledgerTree.datas, { ledger_id });
+            select.pwd = validText;
+            const refreshNode = ledgerTree.loadPostData({update: select});
+            ledgerSpreadObj.refreshTree(ledgerSpread.getActiveSheet(), refreshNode);
+            ledgerSpreadObj.setFontColor();
+        });
+    });
+
+    // 上传图片
+    $('body').on('click', '.upload-img', function () {
+        $(this).siblings('input').trigger('click');
+        // $('.upload-img-file').trigger('click');
+    });
+    $('body').on('change', '.upload-img-file', function () {
+        const file = this.files[0];
+        const ext = file.name.toLowerCase().split('.').splice(-1)[0];
+        const imgStr = /(png|PNG)$/;
+        if (!imgStr.test(ext)) {
+            toastr.error('请上传签名大小为600x300,格式PNG透明背景。');
+            return
+        }
+        if ($(this).val()) {
+            var _self = $(this);
+            const formData = new FormData();
+            formData.append('id', $(this).data('id'));
+            formData.append('file', this.files[0]);
+            postDataWithFile(window.location.pathname + '/save-sign', formData, function (result) {
+                _self.siblings('img').attr('src', '/' + result);
+                _self.siblings('img').show();
+                _self.siblings('a').removeClass('btn btn-outline-primary btn-sm').addClass('d-inline-flex').text('更改');
+                _self.val('');
+                const lcindex = _.findIndex(ledger_cooperation_list, { id: _self.data('id') });
+                ledger_cooperation_list[lcindex].sign_path = result;
+                ledger_cooperation_list.splice(lcindex, 1, ledger_cooperation_list[lcindex]);
+            });
+        }
+    });
+
+
+    $.subMenu({
+        menu: '#sub-menu', miniMenu: '#sub-mini-menu', miniMenuList: '#mini-menu-list',
+        toMenu: '#to-menu', toMiniMenu: '#to-mini-menu',
+        key: 'menu.1.0.0',
+        miniHint: '#sub-mini-hint', hintKey: 'menu.hint.1.0.1',
+        callback: function (info) {
+            if (info.mini) {
+                $('.panel-title').addClass('fluid');
+                $('#sub-menu').removeClass('panel-sidebar');
+            } else {
+                $('.panel-title').removeClass('fluid');
+                $('#sub-menu').addClass('panel-sidebar');
+            }
+            autoFlashHeight();
+        }
+    });
 });
+
+function setRightData(datas, coolist) {
+    const newdatas = [];
+    const reg = /(^GD([0-9\-]+))|(^([0-9\-]+)$)/;
+    for (const l of datas) {
+        // if (reg.test(l.code) && l.level <= 4) {
+        if (reg.test(l.code)) {
+            // if(l.level === 4 && l.is_leaf === false) {
+            //     l.is_leaf = true;
+            // }
+            const coo = _.find(coolist, { 'ledger_id': l.ledger_id, 'user_id': cur_uid });
+            if(coo) {
+                l.pwd = coo.pwd;
+            }
+            l.can_edit = true;
+            newdatas.push(l);
+        }
+    }
+    // for (const nd of newdatas) {
+    //     const child = _.find(newdatas, { 'ledger_pid': nd.ledger_id });
+    //     if (!child && !nd.is_leaf) {
+    //         nd.is_leaf = true;
+    //     }
+    // }
+    return newdatas;
+}
+function updateByCanEdit(datas, coolist, flag) {
+    for (const coo of coolist) {
+        const ledgerInfo = _.find(datas, { 'ledger_id': coo.ledger_id });
+        if(ledgerInfo) {
+            setAllChildrenCanEdit(ledgerInfo, flag);
+            // setParentCanEdit(datas, ledgerInfo.ledger_pid, flag);
+        }
+    }
+}
+function setParentCanEdit(datas, id, flag) {
+    if(id !== -1) {
+        const pl = _.find(datas, { 'ledger_id': id });
+        // 判断父节点下所有子节点有无pwd,有则为false
+        if (pl) {
+            const existfalse = _.find(pl.children, function (item) {
+                return item.can_edit === false || (item.pwd && item.pwd !== '' && item.pwd !== null);
+            });
+            pl.can_edit = existfalse ? false : flag;
+            selects.push(pl);
+            setParentCanEdit(datas, pl.ledger_pid, flag);
+        }
+    }
+}
+function setAllChildrenCanEdit(ledgerInfo, flag) {
+    if (ledgerInfo.children && ledgerInfo.children.length > 0) {
+        for(const li of ledgerInfo.children) {
+            if (li.pwd === undefined || li.pwd === null || li.pwd === '') {
+                li.can_edit = flag;
+                selects.push(li);
+                setAllChildrenCanEdit(li, flag);
+            }
+        }
+    }
+}
+// 编号排序,多重判断
+function sortByCode(a, b) {
+    let code1 = a.code.split('-');
+    let code2 = b.code.split('-');
+    let code1length = code1.length;
+    let code2length = code2.length;
+    for (let i = 0; i < code1length; i ++) {
+        if (i+1 <= code2length) {
+            if (code1[i] != code2[i]) {
+                if (!/^\d+$/.test(code1[i])) {
+                    return code1[i].charCodeAt() - code2[i].charCodeAt();
+                } else {
+                    return parseInt(code1[i]) - parseInt(code2[i]);
+                }
+            } else if (i+1 == code1length && code1[i] == code2[i]) {
+                if (code1length == code2length) {
+                    return 0;
+                } else {
+                    return code1length - code2length;
+                }
+            }
+        } else {
+            if (i+1 >= code1length) {
+                return 1;
+            } else {
+                return -1;
+            }
+        }
+    }
+}
+function setLeftTable(ledgerList, coolist, uid, title) {
+    $('#stage_audit').text(title);
+    const showCooList = _.filter(coolist, { 'user_id': parseInt(uid) });
+    const removeList = [];
+    for (const sc of showCooList) {
+        const info = _.find(ledgerList, { 'ledger_id': sc.ledger_id });
+        if (info) {
+            sc.code = info.code;
+            sc.name = info.name;
+        } else {
+            removeList.push(sc);
+        }
+    }
+    if (removeList.length > 0) {
+        // 移除
+        _.pull(showCooList, ...removeList);
+        console.log(removeList);
+    }
+
+    let html = '';
+    for (const sc of showCooList.sort(sortByCode)) {
+        const pichtml = sc.sign_path ? `<img src="/${sc.sign_path}" width="60"><input type="file" data-id="${sc.id}" class="upload-img-file" style="display: none;"><a href="javascript: void(0);" class="d-inline-flex upload-img">更改</a>`
+            : `<img src="" style="display: none" width="60"><input type="file" data-id="${sc.id}" class="upload-img-file" style="display: none;"><a href="javascript: void(0);" class="btn btn-outline-primary btn-sm upload-img">上传签名</a>`;
+        html += `<tr>` +
+            `<td>${sc.code} ${sc.name}</td>` +
+            `<td><p class="mb-0">${sc.pwd}</p><a href="javascript:void(0);" data-lid="${sc.ledger_id}" data-uid="${sc.user_id}" data-pwd="${sc.pwd}" class="edit-pwd">修改</a> <a href="javascript:void(0)" data-lid="${sc.ledger_id}" data-uid="${sc.user_id}" class="del-pwd text-danger">移除</a></td>` +
+            `<td>${pichtml}</td>` +
+            `</tr>`;
+    }
+    $('#coo_table').html(html);
+}

+ 287 - 16
app/public/js/spreadjs_rela/spreadjs_zh.js

@@ -375,6 +375,7 @@ const SpreadJsObj = {
      * @param setting
      */
     initSheet: function (sheet, setting) {
+        const self = this;
         this.beginMassOperation(sheet);
         this._loadCacheSetting(sheet, setting);
         setting.pos = sheet.getParent().pos;
@@ -389,6 +390,10 @@ const SpreadJsObj = {
         this._initSheetHeader(sheet);
         sheet.setRowCount(sheet.zh_setting.emptyRows);
         sheet.extendCellType = {};
+        sheet.extendRowHeader = {};
+        sheet.zh_setting.rowHeader && sheet.zh_setting.rowHeader.forEach(function (col, j) {
+            self._defineRowHeader(sheet, j, col);
+        });
         sheet.borderLine = new spreadNS.LineBorder('#cccccc', spreadNS.LineStyle.thin);
         sheet.getRange(0, 0, sheet.getRowCount(), sheet.getColumnCount()).locked(setting.readOnly).setBorder(sheet.borderLine, {all: true});
         if (setting.selectedBackColor) {
@@ -467,7 +472,7 @@ const SpreadJsObj = {
             return backColor;
         }
     },
-    _loadRowStyle: function (sheet, row) {
+    _loadRowStyle: function (sheet, data, row) {
         sheet.zh_setting.cols.forEach(function (col, j) {
             const cell = sheet.getCell(row, j);
 
@@ -479,10 +484,11 @@ const SpreadJsObj = {
                 cell.foreColor(col.foreColor);
             }
 
-            if (col.readOnly && Object.prototype.toString.apply(col.readOnly) !== "[object Function]") {
-                cell.locked(col.readOnly || sheet.zh_setting.readOnly || false);
-            }
-            cell.vAlign(1).hAlign(col.hAlign);
+            const readOnly1 = (sheet.zh_setting.readOnly && Object.prototype.toString.apply(sheet.zh_setting.readOnly) === "[object Function]")
+                ? sheet.zh_setting.readOnly(data) : (sheet.zh_setting.readOnly || false);
+            const readOnly2 = (col.readOnly && Object.prototype.toString.apply(col.readOnly) === "[object Function]")
+                ? col.readOnly(data) : col.readOnly || false;
+            cell.locked(readOnly1 || readOnly2).vAlign(1).hAlign(col.hAlign);
 
             if(col.type === 'Number') {
                 if (col.formatter) {
@@ -525,11 +531,11 @@ const SpreadJsObj = {
                 }
             }
 
-            if (col.readOnly && Object.prototype.toString.apply(col.readOnly) === "[object Function]") {
-                cell.locked(col.readOnly(data) || sheet.zh_setting.readOnly || false).vAlign(1).hAlign(col.hAlign);
-            } else {
-                cell.locked(col.readOnly || sheet.zh_setting.readOnly || false).vAlign(1).hAlign(col.hAlign);
-            }
+            const readOnly1 = (sheet.zh_setting.readOnly && Object.prototype.toString.apply(sheet.zh_setting.readOnly) === "[object Function]")
+                ? sheet.zh_setting.readOnly(data) : (sheet.zh_setting.readOnly || false);
+            const readOnly2 = (col.readOnly && Object.prototype.toString.apply(col.readOnly) === "[object Function]")
+                ? col.readOnly(data) : col.readOnly || false;
+            cell.locked(readOnly1 || readOnly2).vAlign(1).hAlign(col.hAlign);
 
             if(col.type === 'Number') {
                 if (col.formatter) {
@@ -573,6 +579,20 @@ const SpreadJsObj = {
             sheet.AcitveRefresh = true;
         }
     },
+    _defineRowHeader: function (sheet, col, colSetting) {
+        if (colSetting.rowHeaderType === 'tag') {
+            if (!sheet.extendRowHeader.tag) {
+                sheet.extendRowHeader.tag = this.RowHeader.getTagRowHeader(colSetting.setting);
+            }
+            sheet.getRange(-1, col, -1, 1, spreadNS.SheetArea.rowHeader).cellType(sheet.extendRowHeader.tag);
+        }
+        if (colSetting.rowHeaderType === 'circle') {
+            if (!sheet.extendRowHeader.multiTag) {
+                sheet.extendRowHeader.multiTag = this.RowHeader.getCircleTagRowHeader(colSetting.setting);
+            }
+            sheet.getRange(-1, col, -1, 1, spreadNS.SheetArea.rowHeader).cellType(sheet.extendRowHeader.multiTag);
+        }
+    },
     _defineColCellType: function (sheet, col, colSetting) {
         sheet.AcitveComboRefresh = false;
         if(colSetting.cellType === 'ellipsis') {
@@ -700,9 +720,9 @@ const SpreadJsObj = {
                     self._loadRowData(sheet, data, i);
                     sheet.setRowVisible(i, data.visible);
                 });
-                for (let iRow = sortData.length - 1; iRow < totalRow; iRow++) {
-                    self._loadRowStyle(sheet, iRow);
-                }
+                // for (let iRow = sortData.length - 1; iRow < totalRow; iRow++) {
+                //     self._loadRowStyle(sheet, iRow);
+                // }
             }
             // 设置列单元格格式
             sheet.zh_setting.cols.forEach(function (col, j) {
@@ -785,6 +805,19 @@ const SpreadJsObj = {
             this.endMassOperation(sheet);
         }
     },
+    reloadRowsReadonly: function (sheet, rows) {
+        const sortData = sheet.zh_dataType === 'tree' ? sheet.zh_tree.nodes : sheet.zh_data;
+
+        this.beginMassOperation(sheet);
+        try {
+            for (const row of rows) {
+                if (row < 0) { continue; }
+                this._loadRowStyle(sheet, sortData[row], row);
+            };
+        } catch (err) {
+        }
+        this.endMassOperation(sheet);
+    },
     reloadColData: function (sheet, col, count = 1) {
         const cols = [];
         for (let i = 0; i < count; i++) {
@@ -819,13 +852,30 @@ const SpreadJsObj = {
     },
     reLoadNodesData: function (sheet, nodes) {
         this.beginMassOperation(sheet);
-        nodes = nodes instanceof Array ? nodes : [nodes];
+        nodes = nodes instanceof Array ? nodes : [nodes];rectangle
+        const sortData = sheet.zh_dataType === 'tree' ? sheet.zh_tree.nodes : sheet.zh_data;
         for (const node of nodes) {
-            const sortData = sheet.zh_dataType === 'tree' ? sheet.zh_tree.nodes : sheet.zh_data;
             this._loadRowData(sheet, node, sortData.indexOf(node));
         }
         this.endMassOperation(sheet);
     },
+    repaintNodesRowHeader: function (sheet, nodes) {
+        nodes = nodes instanceof Array ? nodes : [nodes];
+        const sortData = sheet.zh_dataType === 'tree' ? sheet.zh_tree.nodes : sheet.zh_data;
+        const rowIndex = [];
+        for (const node of nodes) {
+            rowIndex.push(sortData.indexOf(node));
+        }
+        this.repaintRowsRowHeader(sheet, rowIndex);
+    },
+    repaintRowsRowHeader: function (sheet, rows) {
+        for (const r of rows) {
+            const cellRect = sheet.getCellRect(r, 0);
+            cellRect.width = cellRect.x;
+            cellRect.x = 0;
+            sheet.repaint(cellRect);
+        }
+    },
     /**
      * 根据data加载sheet数据,合并了一般数据和树结构数据的加载
      * @param {GC.Spread.Sheets.Worksheet} sheet
@@ -981,6 +1031,19 @@ const SpreadJsObj = {
             }
         }
     },
+    getRowObject: function (sheet, row) {
+        if (!sheet) {
+            return null;
+        } else if (sheet.zh_dataType) {
+            if (sheet.zh_dataType === this.DataType.Tree) {
+                return sheet.zh_tree.nodes[row];
+            } else if (sheet.zh_dataType === this.DataType.Data) {
+                return sheet.zh_data[row];
+            } else {
+                return null;
+            }
+        }
+    },
     /**
      * 刷新列显示
      * @param sheet
@@ -1206,6 +1269,7 @@ const SpreadJsObj = {
                     canvas.clearRect(x, y, w, h);
                 }
 
+
                 const tree = options.sheet.zh_tree;
                 // 使用TreeCellType前,需定义sheet.tree
                 if (tree) {
@@ -1960,7 +2024,7 @@ const SpreadJsObj = {
         getHtmlCellType: function () {
             const HTMLCellType = function (){};
             HTMLCellType.prototype = new spreadNS.CellTypes.Text;
-            const proto = ImageCellType.prototype;
+            const proto = HTMLCellType.prototype;
             proto.paint = function (ctx, value, x, y, w, h, style, context) {
                 let DOMURL = window.URL || window.webkitURL || window;
                 let cell = context.sheet.getCell(context.row, context.col);
@@ -2185,6 +2249,213 @@ const SpreadJsObj = {
         }
     },
 
+    RowHeader: {
+        getTagRowHeader: function (setting) {
+            const indent = setting.indent || 18, maxHintWidth = 200, borderIndent = 10;
+            const height = setting.tagSize ? 11 * setting.tagSize : 11, width = setting.tagSize ? 10 * setting.tagSize : 10;
+            const tagFont = setting.tagFont || '9px 微软雅黑';
+            const drawTag = function (canvas, x, y, fillColor) {
+                canvas.save();
+                // 设置偏移量
+                canvas.translate(0.5, 0.5);
+                canvas.beginPath();
+                canvas.moveTo(x, y);
+                canvas.lineTo(x, y+height/11*5);
+                canvas.lineTo(x+width/10*5.5, y+height);
+                canvas.lineTo(x+width, y+height-width/10*4.5);
+                canvas.lineTo(x+width/11*5, y);
+                canvas.lineTo(x, y);
+                canvas.stroke();
+                canvas.fillStyle = fillColor instanceof Array ? fillColor[0] : fillColor;
+                canvas.fill();
+
+                canvas.beginPath();
+                canvas.arc(x+2, y+2, 1, 0, Math.PI*2, true);
+                canvas.closePath();
+                canvas.fillStyle = 'white';
+                canvas.fill();
+
+                if (fillColor instanceof Array && fillColor.length > 1) {
+                    canvas.fillStyle = '#444444';
+                    canvas.font = tagFont;
+                    canvas.fillText(fillColor.length, x+width/11*13, y+height/11*14);
+                }
+
+                canvas.restore();
+            };
+
+            let TagCellType = function (){};
+            TagCellType.prototype = new spreadNS.CellTypes.RowHeader();
+            const proto = TagCellType.prototype;
+            proto.getTagColor = setting.getColor;
+            proto.getTagHtml = setting.getTagHtml;
+            proto.basePaint = proto.paint;
+            proto.paint = function (canvas, value, x, y, w, h, style, options) {
+                spreadNS.CellTypes.RowHeader.prototype.paint.apply(this, [canvas, '', x, y, w , h, style, options]);
+                spreadNS.CellTypes.Text.prototype.paint.apply(this, [canvas, value, x, y, w - indent, h, style, options]);
+                const node = SpreadJsObj.getRowObject(options.sheet, options.row);
+                const color = this.getTagColor(options.row, node);
+                const centerX = x + w - indent + height/2, centerY = y + h/2;
+                color && drawTag(canvas, centerX - height/2, centerY - width/2, color);
+            };
+            /**
+             * 获取点击信息
+             * @param {Number} x
+             * @param {Number} y
+             * @param {Object} cellStyle
+             * @param {Object} cellRect
+             * @param {Object} context
+             * @returns {{x: *, y: *, row: *, col: *|boolean|*[]|number|{}|UE.dom.dtd.col, cellStyle: *, cellRect: *, sheet: *|StyleSheet, sheetArea: *}}
+             */
+            proto.getHitInfo = function (x, y, cellStyle, cellRect, context) {
+                return {
+                    x: x,
+                    y: y,
+                    row: context.row,
+                    col: context.col,
+                    cellStyle: cellStyle,
+                    cellRect: cellRect,
+                    sheet: context.sheet,
+                    sheetArea: context.sheetArea,
+                    ctx: context.sheet.getParent().xs,
+                };
+            };
+            /**
+             * 鼠标进入单元格事件 - 显示悬浮提示
+             * @param {Object} hitinfo - 见getHitInfo返回值
+             */
+            proto.processMouseEnter = function (hitinfo) {
+                const node = SpreadJsObj.getRowObject(hitinfo.sheet, hitinfo.row);
+                let html = this.getTagHtml(hitinfo.row, node);
+                const pos = SpreadJsObj.getObjPos(hitinfo.sheet.getParent().qo);
+                if (pos && html) {
+                    if (!this._tagTipElement) {
+                        let div = $('#autoTip')[0];
+                        if (!div) {
+                            div = document.createElement("div");
+                            $(div).css("position", "absolute")
+                                .css("border", "1px #C0C0C0 solid")
+                                .css("box-shadow", "1px 2px 5px rgba(0,0,0,0.4)")
+                                .css("font", "9pt Arial")
+                                .css("background", "white")
+                                .css("padding", 5)
+                                .css("width", maxHintWidth)
+                                .css("z-index", 999)
+                                .attr("id", 'autoTagTip')
+                                .css("top", pos.y + hitinfo.y + indent)
+                                .css("left", pos.x + hitinfo.x + indent)
+                                .html(html);
+                            document.body.insertBefore(div, null);
+                        }
+                        this._tagTipElement = div;
+                        $(div).show("fast");
+                    }
+                }
+            };
+            /**
+             * 鼠标移出单元格事件 - 隐藏悬浮提示
+             * @param {Object} hitinfo - 见getHitInfo返回值
+             */
+            proto.processMouseLeave = function (hitinfo) {
+                if (this._tagTipElement) {
+                    $(this._tagTipElement).hide().remove();
+                    this._tagTipElement = null;
+                }
+            };
+            return new TagCellType();
+        },
+        getCircleTagRowHeader: function (setting) {
+            const drawCircle = function (canvas, x, y, r, fillColor) {
+                canvas.save();
+                canvas.beginPath();
+                canvas.arc(x, y, r, 0, Math.PI*2, true);
+                canvas.closePath();
+                canvas.fillStyle = fillColor;
+                canvas.fill();
+                canvas.restore();
+            };
+            const drawCircle2 = function (canvas, x, y, r, lineColor, fillColor) {
+                canvas.save();
+                canvas.beginPath();
+                canvas.arc(x, y, r, 0, Math.PI*2, true);
+                canvas.closePath();
+                canvas.fillStyle = lineColor;
+                canvas.fill();
+                canvas.beginPath();
+                canvas.arc(x, y, r-2, 0, Math.PI*2, true);
+                canvas.closePath();
+                canvas.fillStyle = fillColor;
+                canvas.fill();
+                canvas.restore();
+            };
+
+            let CircleTagCellType = function (){};
+            CircleTagCellType.prototype = new spreadNS.CellTypes.RowHeader();
+            const proto = CircleTagCellType.prototype;
+            proto.indent = setting.indent || 16;
+            proto.size = setting.size || 6;
+            proto.getTagColor = setting.getColor;
+            proto.basePaint = proto.paint;
+            proto.paint = function (canvas, value, x, y, w, h, style, options) {
+                spreadNS.CellTypes.RowHeader.prototype.paint.apply(this, [canvas, '', x, y, w , h, style, options]);
+                spreadNS.CellTypes.Text.prototype.paint.apply(this, [canvas, value, x, y, w - this.indent, h, style, options]);
+                const node = SpreadJsObj.getRowObject(options.sheet, options.row);
+                let backColor = style.backColor;
+                if (!backColor) {
+                    const tag = options.sheet.getTag(options.row, options.col, options.sheetArea);
+                    if (tag && tag === 'hover') backColor = '#dddfe1';
+                    if (!backColor) {
+                        let sel = options.sheet.getSelections();
+                        sel = sel ? sel[0] : null;
+                        backColor = (sel && options.row >= sel.row && options.row < sel.row + sel.rowCount ? '#dddfe1' : '#e9ecef');
+                    }
+                }
+                let color = this.getTagColor(options.row, node);
+                color = color instanceof Array ? color : [color];
+                for (let i = color.length - 1; i >= 0; i--) {
+                    drawCircle2(canvas, x + w - this.indent + 5 + i*5 , y + h/2, this.size, backColor, color[i]);
+                }
+            };
+            /**
+             * 获取点击信息
+             * @param {Number} x
+             * @param {Number} y
+             * @param {Object} cellStyle
+             * @param {Object} cellRect
+             * @param {Object} context
+             * @returns {{x: *, y: *, row: *, col: *|boolean|*[]|number|{}|UE.dom.dtd.col, cellStyle: *, cellRect: *, sheet: *|StyleSheet, sheetArea: *}}
+             */
+            proto.getHitInfo = function (x, y, cellStyle, cellRect, context) {
+                return {
+                    x: x,
+                    y: y,
+                    row: context.row,
+                    col: context.col,
+                    cellStyle: cellStyle,
+                    cellRect: cellRect,
+                    sheet: context.sheet,
+                    sheetArea: context.sheetArea,
+                    ctx: context.sheet.getParent().xs,
+                };
+            };
+            /**
+             * 鼠标进入单元格事件 - 显示悬浮提示
+             * @param {Object} hitinfo - 见getHitInfo返回值
+             */
+            proto.processMouseEnter = function (hitinfo) {
+                hitinfo.sheet.setTag(hitinfo.row, hitinfo.col, 'hover', hitinfo.sheetArea);
+            };
+            /**
+             * 鼠标移出单元格事件 - 隐藏悬浮提示
+             * @param {Object} hitinfo - 见getHitInfo返回值
+             */
+            proto.processMouseLeave = function (hitinfo) {
+                hitinfo.sheet.setTag(hitinfo.row, hitinfo.col, undefined, hitinfo.sheetArea);
+            };
+            return new CircleTagCellType();
+        },
+    },
+
     Formatter: {
         baseNumberFormatter: function () {
             const formatter = function () {};

+ 299 - 75
app/public/js/stage.js

@@ -166,8 +166,31 @@ function getDaglText(data) {
     return def ? def.name : '';
 }
 
+
 $(document).ready(() => {
     let detail, searchLedger, checkedChanges;
+    const checkOption = {
+        sibling: { enable: 0 },
+        empty_code: { enable: 0 },
+        calc: {
+            enable: 1,
+            fields: ['contract_qty', 'qc_qty'],
+        },
+        zero: { enable: 0 },
+        tp: {
+            enable: 1,
+            fields: [
+                {qty: 'contract_qty', tp: 'contract_tp'},
+                {qty: 'qc_qty', tp: 'qc_tp'},
+            ],
+            filter: function (node) {
+                return node.is_tp;
+            }
+        },
+        over: {
+            enable: 1, isTz: checkTzMeasureType(),
+        }
+    };
     // 界面布局
     autoFlashHeight();
     // 初始化 台账树结构 数据结构
@@ -204,7 +227,7 @@ $(document).ready(() => {
             if (node.end_contract_qty) {
                 node.end_correct_tp = ZhCalc.add(node.end_qc_tp, ZhCalc.mul(node.end_contract_qty, node.unit_price, tenderInfo.decimal.tp));
             } else {
-                node.end_correct_tp = node.end_gather;
+                node.end_correct_tp = node.end_gather_tp;
             }
         }
         node.end_gather_percent = ZhCalc.mul(ZhCalc.div(node.end_gather_tp, node.end_final_tp), 100, 2);
@@ -231,6 +254,16 @@ $(document).ready(() => {
     };
     const stagePos = new StagePosData(stagePosSetting);
 
+    const reloadCooperationHtml = function () {
+        const html = [];
+        for (const p of stageTree.pwd) {
+            html.push('<tr>', `<td>${p.node.code}</td>`, `<td>${p.node.name}</td>`);
+            html.push('<td>', p.check ? `已解锁:${p.pwd}` : `<a name="ledger-unlock" lid="${p.ledger_id}" href="javascript: void(0);">解锁</a>`, '</td>');
+            html.push('</tr>');
+        }
+        $('#cooperationList').html(html.join(''));
+    };
+
     class Changes {
         constructor(obj) {
             const self = this;
@@ -507,12 +540,12 @@ $(document).ready(() => {
     const slSpread = SpreadJsObj.createNewSpread($('#stage-ledger')[0]);
     customizeStageTreeSetting(ledgerSpreadSetting, customColDisplay());
     // 数量变更列,添加按钮
-    const col = _.find(ledgerSpreadSetting.cols, {field: 'qc_qty'});
-    col.readOnly = true;
-    col.cellType = 'activeImageBtn';
-    col.normalImg = '#ellipsis-icon';
-    col.indent = 5;
-    col.showImage = function (data) {
+    const qcCol = _.find(ledgerSpreadSetting.cols, {field: 'qc_qty'});
+    qcCol.readOnly = true;
+    qcCol.cellType = 'activeImageBtn';
+    qcCol.normalImg = '#ellipsis-icon';
+    qcCol.indent = 5;
+    qcCol.showImage = function (data) {
         if (!data || (data.children && data.children.length > 0) || !(data.b_code && data.b_code !== '')) {
             return false;
         } else {
@@ -520,8 +553,10 @@ $(document).ready(() => {
             return !(nodePos && nodePos.length > 0);
         }
     };
+    const ratioCol = ledgerSpreadSetting.cols.find(x => {return x.field === 'end_gather_percent' || x.field === 'end_correct_percent'});
+    ratioCol.field = tenderInfo.display.stage.correct ? 'end_correct_percent' : 'end_gather_percent';
     ledgerSpreadSetting.imageClick = function (data) {
-        if (data.children && data.children.length > 0) return;
+        if (data.children && data.children.length > 0 || data.lock) return;
 
         const nodePos = stagePos.getLedgerPos(data.id);
         if (nodePos && nodePos.length > 0) return;
@@ -570,6 +605,40 @@ $(document).ready(() => {
     sjsSettingObj.setPropValue(ledgerSpreadSetting, ['gxby'], 'getValue', getGxbyText);
     sjsSettingObj.setPropValue(ledgerSpreadSetting, ['dagl'], 'getValue', getDaglText);
     if (thousandth) sjsSettingObj.setTpThousandthFormat(ledgerSpreadSetting);
+    ledgerSpreadSetting.headColWidth = [50];
+    ledgerSpreadSetting.rowHeader = [
+        {
+            rowHeaderType: 'tag',
+            setting: {
+                indent: 14,
+                tagSize: 0.8,
+                tagFont: '8px 微软雅黑',
+                getColor: function (index, data) {
+                    if (!data) return;
+                    return billsTag.getBillsTagsColor(data.id);
+                },
+                getTagHtml: function (index, data) {
+                    if (!data) return;
+                    const getHtml = function (list) {
+                        if (!list || list.length === 0) return '';
+                        const html = [];
+                        for (const l of list) {
+                            html.push('<div class="row mr-1">');
+                            html.push(`<div class="col-auto pr-1 ${l.tagClass}">`, '<i class="fa fa-tag"></i>', '</div>');
+                            html.push('<div class="col p-0">', '<p>', l.comment, '</p>', '</div>');
+                            html.push('</div>');
+                        }
+                        return html.join('');
+                    };
+                    return getHtml(billsTag.getBillsTagsInfo(data.id));
+                }
+            },
+        },
+    ];
+    ledgerSpreadSetting.readOnly = function (data) {
+        if (!data) return false;
+        return data.lock || false;
+    };
     SpreadJsObj.initSheet(slSpread.getActiveSheet(), ledgerSpreadSetting);
     slSpread.getActiveSheet().frozenColumnCount(5);
     slSpread.getActiveSheet().options.frozenlineColor = '#93b5e4';
@@ -589,6 +658,7 @@ $(document).ready(() => {
     };
     posSpreadSetting.imageClick = function (data) {
         const node = SpreadJsObj.getSelectObject(slSpread.getActiveSheet());
+        if (node.lock) return;
         changesObj.loadChanges({bills: node, pos: data});
     };
     posSpreadSetting.getColor = function (sheet, data, row, col, defaultColor) {
@@ -605,7 +675,9 @@ $(document).ready(() => {
                 if (def && def.color) return def.color;
             }
         }
-        return data && data.end_contract_qty > data.quantity ? '#f8d7da' : defaultColor;
+        if (checkTzMeasureType()) {
+            return data && data.end_contract_qty > data.quantity ? '#f8d7da' : defaultColor;
+        }
     };
     sjsSettingObj.setGridSelectStyle(posSpreadSetting);
     if (thousandth) sjsSettingObj.setTpThousandthFormat(posSpreadSetting);
@@ -613,6 +685,21 @@ $(document).ready(() => {
     sjsSettingObj.setPropValue(posSpreadSetting, ['dagl'], 'getValue', getDaglText);
     SpreadJsObj.initSheet(spSpread.getActiveSheet(), posSpreadSetting);
 
+    const billsTag = $.billsTag({
+        selector: '#bills-tag',
+        relaSpread: slSpread,
+        updateUrl: window.location.pathname + '/tag',
+        afterModify: function (nodes) {
+            SpreadJsObj.repaintNodesRowHeader(slSpread.getActiveSheet(), nodes);
+        },
+        afterLocated:  function () {
+            stagePosSpreadObj.loadCurPosData();
+        },
+        afterShow: function () {
+            slSpread.refresh();
+            if (spSpread) spSpread.refresh();
+        },
+    });
     const errorList = $.cs_errorList({
         tabSelector: '#error-list-tab',
         selector: '#error-list',
@@ -626,14 +713,13 @@ $(document).ready(() => {
             if (spSpread) spSpread.refresh();
         },
     });
-
     const checkList = $.ledger_checkList({
         id: 'check-list',
         tabSelector: '#check-list-tab',
         selector: '#check-list',
         relaSpread: slSpread,
         storeKey: 'stage-check-' + window.location.pathname.split('/')[2] + '-' + window.location.pathname.split('/')[4],
-        checkType: ledgerCheckType,
+        checkType: getCheckType(checkOption),
         afterLocated:  function () {
             stagePosSpreadObj.loadCurPosData();
         },
@@ -794,7 +880,7 @@ $(document).ready(() => {
                 const datas = [], dgnDatas = [], mainDatas = [];
                 for (let iRow = sel.row; iRow < sel.row + sel.rowCount; iRow++) {
                     const node = sortData[iRow];
-                    if (node) {
+                    if (node && !node.lock) {
                         const data = { lid: node.id }, dgnData = { id: node.id }, mainData = { id: node.id };
                         let filter = true, filterDgn = true, filterMain = true;
                         for (const iCol of validCols) {
@@ -1197,11 +1283,11 @@ $(document).ready(() => {
         });
     }
     stageTreeSpreadObj.loadExprToInput(slSpread.getActiveSheet());
-    //let check_correct = false;
+    let addTagShare = true;
     $.contextMenu({
         selector: '#stage-ledger',
         build: function ($trigger, e) {
-            e.data.items.correct_percent.selected = check_correct;
+            addTagShare = true;
             const target = SpreadJsObj.safeRightClickSelection($trigger, e, slSpread);
             return target.hitTestType === spreadNS.SheetArea.viewport || target.hitTestType === spreadNS.SheetArea.rowHeader;
         },
@@ -1238,22 +1324,82 @@ $(document).ready(() => {
                     }
                 },
             },
-            // 'correct_percent': {
-            //     name: '使用数量矫正完成率',
-            //     type: 'checkbox',
-            //     events: {
-            //         change: function (e) {
-            //             check_correct = e.data.commands.correct_percent.$input[0].checked;
-            //             const sheet = slSpread.getActiveSheet();
-            //             const col = sheet.zh_setting.cols.find(x => {
-            //                 return x.field === 'end_gather_percent' || x.field === 'end_correct_percent'
-            //             });
-            //             col.field = check_correct ? 'end_correct_percent' : 'end_gather_percent';
-            //             SpreadJsObj.reLoadColsData(sheet, [col]);
-            //             e.data.$menu.hide();
-            //         }
-            //     },
-            // }
+            tag: {
+                name: '书签',
+                items: {
+                    tagShare: {
+                        name: '参与人可见',
+                        type: 'checkbox',
+                        selected: true,
+                        events: {
+                            change: function () {
+                                addTagShare = this.checked;
+                            }
+                        }
+                    },
+                    tagSpr: '--------------',
+                    tagPrimary: {
+                        icon: 'fa-tag text-primary mt-2 mb-2',
+                        name: '靛青',
+                        callback: function (key, opt, menu, e) {
+                            const node = SpreadJsObj.getSelectObject(slSpread.getActiveSheet());
+                            postData(window.location.pathname + '/tag', {add: { color: '#007bff', lid: node.id, share: addTagShare }}, function (data) {
+                                if (data.add) data.add.node = node;
+                                billsTag.updateDatasAndShow(data);
+                                SpreadJsObj.repaintNodesRowHeader(slSpread.getActiveSheet(), node);
+                            });
+                        },
+                    },
+                    tagSuccess: {
+                        icon: 'fa-tag text-success mt-2 mb-2',
+                        name: '果绿',
+                        callback: function (key, opt) {
+                            const node = SpreadJsObj.getSelectObject(slSpread.getActiveSheet());
+                            postData(window.location.pathname + '/tag', {add: { color: '#28a745', lid: node.id, share: addTagShare }}, function (data) {
+                                if (data.add) data.add.node = node;
+                                billsTag.updateDatasAndShow(data);
+                                SpreadJsObj.repaintNodesRowHeader(slSpread.getActiveSheet(), node);
+                            });
+                        },
+                    },
+                    tagDanger: {
+                        icon: 'fa-tag text-danger mt-2 mb-2',
+                        name: '朱砂',
+                        callback: function (key, opt) {
+                            const node = SpreadJsObj.getSelectObject(slSpread.getActiveSheet());
+                            postData(window.location.pathname + '/tag', {add: { color: '#dc3545', lid: node.id, share: addTagShare }}, function (data) {
+                                if (data.add) data.add.node = node;
+                                billsTag.updateDatasAndShow(data);
+                                SpreadJsObj.repaintNodesRowHeader(slSpread.getActiveSheet(), node);
+                            });
+                        },
+                    },
+                    tagWarning: {
+                        icon: 'fa-tag text-warning mt-2 mb-2',
+                        name: '姜黄',
+                        callback: function (key, opt) {
+                            const node = SpreadJsObj.getSelectObject(slSpread.getActiveSheet());
+                            postData(window.location.pathname + '/tag', {add: { color: '#da9500', lid: node.id, share: addTagShare }}, function (data) {
+                                if (data.add) data.add.node = node;
+                                billsTag.updateDatasAndShow(data);
+                                SpreadJsObj.repaintNodesRowHeader(slSpread.getActiveSheet(), node);
+                            });
+                        },
+                    },
+                    tagInfo: {
+                        icon: 'fa-tag text-info mt-2 mb-2',
+                        name: '天蓝',
+                        callback: function (key, opt) {
+                            const node = SpreadJsObj.getSelectObject(slSpread.getActiveSheet());
+                            postData(window.location.pathname + '/tag', {add: { color: '#17a2b8', lid: node.id, share: addTagShare }}, function (data) {
+                                if (data.add) data.add.node = node;
+                                billsTag.updateDatasAndShow(data);
+                                SpreadJsObj.repaintNodesRowHeader(slSpread.getActiveSheet(), node);
+                            });
+                        },
+                    }
+                },
+            },
         }
     });
 
@@ -1284,7 +1430,9 @@ $(document).ready(() => {
          * 加载计量单元 根据当前台账选择节点
          */
         loadCurPosData: function () {
-            const node = SpreadJsObj.getSelectObject(slSpread.getActiveSheet());
+            const sheet = slSpread.getActiveSheet();
+            const node = SpreadJsObj.getSelectObject(sheet);
+            spSpread.getActiveSheet().zh_setting.readOnly = node.lock;
             if (node) {
                 const posData = stagePos.ledgerPos[itemsPre + node.id] || [];
                 SpreadJsObj.loadSheetData(spSpread.getActiveSheet(), 'data', posData);
@@ -1556,6 +1704,8 @@ $(document).ready(() => {
             }
         },
         deletePress: function (sheet) {
+            if (sheet.zh_setting.readOnly) return;
+
             if (sheet.zh_setting && sheet.zh_data) {
                 const sortData = sheet.zh_data;
                 if (!sortData || sortData.length === 0) { return; }
@@ -1670,12 +1820,19 @@ $(document).ready(() => {
     });
 
     // 加载计量单元数据 - 暂时统一加载,如有需要,切换成动态加载并缓存
-    postData(window.location.pathname + '/load', { filter: 'ledger;pos;detail;change' }, function (result) {
+    postData(window.location.pathname + '/load', { filter: 'ledger;pos;detail;change;tag;cooperation' }, function (result) {
         // 加载树结构
         stageTree.loadDatas(result.ledgerData);
         // stageTree.loadCurStageData(curStageData);
         // stageTree.loadPreStageData(preStageData);
         treeCalc.calculateAll(stageTree);
+        // 加载解锁相关
+        if (result.cooperation) {
+            stageTree.loadPwd(result.cooperation, 'bills-p-' + userID + '-' + window.location.pathname.split('/')[2]);
+            $('#cooperationCount').html(stageTree.pwd.length || '');
+            if (stageTree.pwd.length > 0) $('#cooperationCount').parent().show();
+            reloadCooperationHtml();
+        }
         // 加载部位明细
         stagePos.loadDatas(result.posData);
         stagePos.calculateAll();
@@ -1686,6 +1843,11 @@ $(document).ready(() => {
         // 加载中间计量
         stageIm.init(stage, imType, tenderInfo.decimal);
         stageIm.loadData(result.ledgerData, result.posData, result.detailData, result.changeData);
+
+        for (const t of result.tags) {
+            t.node = stageTree.datas.find(x => {return x.id === t.lid});
+        }
+        billsTag.loadDatas(result.tags);
         errorList.loadHisErrorData();
         checkList.loadHisCheckData();
     }, null, true);
@@ -1757,6 +1919,9 @@ $(document).ready(() => {
                     return !checkTzMeasureType();
                 },
                 disabled: function (key, opt) {
+                    const node = SpreadJsObj.getSelectObject(slSpread.getActiveSheet());
+                    if (!node || node.lock) return true;
+
                     const sheet = spSpread.getActiveSheet();
                     if (sheet.zh_data && !readOnly) {
                         const selection = sheet.getSelections();
@@ -1793,7 +1958,7 @@ $(document).ready(() => {
                 },
                 disabled: function (key, opt) {
                     const node = SpreadJsObj.getSelectObject(slSpread.getActiveSheet());
-                    return _.isNil(node) || _.isNil(node.b_code) || node.b_code === '';
+                    return _.isNil(node) || _.isNil(node.b_code) || node.b_code === '' || node.lock;
                 },
                 callback: function (key, opt) {
                     mergePeg.show();
@@ -1805,6 +1970,10 @@ $(document).ready(() => {
                     const data = spSpread.getActiveSheet().zh_data;
                     return data && data.length > 0;
                 },
+                disabled: function (key, opt) {
+                    const node = SpreadJsObj.getSelectObject(slSpread.getActiveSheet());
+                    return node.lock;
+                },
                 callback: function (key, opt) {
                     $('#cbr-ratio').val('');
                     $('#apply2sibling')[0].checked = false;
@@ -2634,11 +2803,14 @@ $(document).ready(() => {
                         if (newValue !== '') {
                             update[field] = newValue;
                             if (data.custom_define.indexOf(field) === -1) update.custom_define.push(field);
+                            return true;
                         }
                     } else if (newValue !== org[field]){
                         update[field] = newValue;
                         if (data.custom_define.indexOf(field) === -1) update.custom_define.push(field);
+                        return true;
                     }
+                    return false;
                 }
 
                 const data = SpreadJsObj.getSelectObject(self.spread.getActiveSheet());
@@ -2655,30 +2827,34 @@ $(document).ready(() => {
                     updateData.pos_name = data.pos_name;
                     updateData.custom_define = [];
                 }
-                check('bw', $('#bw-name'), data, updateData);
-                check('peg', $('#peg'), data, updateData);
-                check('xm', $('#xm-name'), data, updateData);
-                check('position', $('#position'), data, updateData);
-                check('jldy', $('#jldy'), data, updateData);
-                check('drawing_code', $('#drawing-code'), data, updateData);
-                check('calc_memo', $('#calc-memo'), data, updateData);
+                let infoUpdate = false;
+                infoUpdate = check('bw', $('#bw-name'), data, updateData) || infoUpdate;
+                infoUpdate = check('peg', $('#peg'), data, updateData) || infoUpdate;
+                infoUpdate = check('xm', $('#xm-name'), data, updateData) || infoUpdate;
+                infoUpdate = check('position', $('#position'), data, updateData) || infoUpdate;
+                infoUpdate = check('jldy', $('#jldy'), data, updateData) || infoUpdate;
+                infoUpdate = check('drawing_code', $('#drawing-code'), data, updateData) || infoUpdate;
+                infoUpdate = check('calc_memo', $('#calc-memo'), data, updateData) || infoUpdate;
                 updateData.custom_define = updateData.custom_define.join(',');
-                postData(window.location.pathname + '/detail/save', updateData, function (result) {
-                    stageIm.loadUpdateDetailData(result);
-                    self.reLoadDetailData();
-                    if (self.updateImageData && !self.updateImageData.uuid) {
-                        self.updateImageData.uuid = result.uuid;
+                if (infoUpdate) {
+                    postData(window.location.pathname + '/detail/save', updateData, function (result) {
+                        stageIm.loadUpdateDetailData(result);
+                        self.reLoadDetailData();
+                        if (!self.updateImageData) return;
+
+                        if (!self.updateImageData.uuid) self.updateImageData.uuid = result.uuid;
                         postData(window.location.pathname + '/detail/merge-img', self.updateImageData, function (result) {
                             stageIm.loadUpdateDetailData(result);
                             self.reLoadDetailData();
                         });
-                    }
-                });
-                if (self.updateImageData && self.updateImageData.uuid) {
+                    });
+                } else if (self.updateImageData) {
                     postData(window.location.pathname + '/detail/merge-img', self.updateImageData, function (result) {
                         stageIm.loadUpdateDetailData(result);
                         self.reLoadDetailData();
                     });
+                } else {
+                    self.reLoadDetailData();
                 }
             });
             // 取消
@@ -2789,7 +2965,7 @@ $(document).ready(() => {
             $('#edit-img-ok').click(function () {
                 // 记录上传的图片的信息
                 const items = $('.img-item');
-                const img_remark = $('#text-edit').val()
+                const img_remark = $('#text-edit').val();
                 const data = SpreadJsObj.getSelectObject(self.spread.getActiveSheet());
                 if (items.length > 0) {
                     const itemInfo = [];
@@ -2820,6 +2996,13 @@ $(document).ready(() => {
                     const updateData = {updateType: 'update', lid: data.lid, pid: data.pid};
                     if (data.uuid) {
                         updateData.uuid = data.uuid;
+                    } else {
+                        updateData.code = data.code;
+                        updateData.name = data.name;
+                        updateData.unit = data.unit;
+                        updateData.unit_price = data.unit_price;
+                        updateData.pid = data.pid;
+                        updateData.pos_name = data.pos_name;
                     }
 
                     updateData.img = canvas.toDataURL('image/png');
@@ -3060,8 +3243,29 @@ $(document).ready(() => {
                             if (changeBills.gcl_id) {
                                 const node = stageTree.nodes.find(x => {return x.id === changeBills.gcl_id});
                                 SpreadJsObj.locateTreeNode(slSpread.getActiveSheet(), node.ledger_id);
+                                stagePosSpreadObj.loadCurPosData();
                             } else {
-                                toastr.warning('该清单无法定位');
+                                const cb = {
+                                    b_code: changeBills.code || '',
+                                    name: changeBills.name || '',
+                                    unit: changeBills.unit || '',
+                                    unit_price: changeBills.unit_price || 0,
+                                };
+                                for (const node of stageTree.nodes) {
+                                    if (node.children && node.children.length > 0) continue;
+
+                                    const b = {
+                                        b_code: node.b_code || '',
+                                        name: node.name || '',
+                                        unit: node.unit || '',
+                                        unit_price: node.unit_price || 0,
+                                    };
+                                    if (_.isMatch(cb, b)) {
+                                        SpreadJsObj.locateTreeNode(slSpread.getActiveSheet(), node.ledger_id);
+                                        stagePosSpreadObj.loadCurPosData();
+                                        return;
+                                    }
+                                }
                             }
                         },
                         disabled: function (key, opt) {
@@ -3586,25 +3790,7 @@ $(document).ready(() => {
         ledgerPos: stagePos,
         checkList: checkList,
         decimal: tenderInfo.decimal,
-        checkOption: {
-            sibling: { enable: 0 },
-            empty_code: { enable: 0 },
-            calc: {
-                enable: 1,
-                fields: ['contract_qty', 'qc_qty'],
-            },
-            zero: { enable: 0 },
-            tp: {
-                enable: 1,
-                fields: [
-                    {qty: 'contract_qty', tp: 'contract_tp'},
-                    {qty: 'qc_qty', tp: 'qc_tp'},
-                ],
-                filter: function (node) {
-                    return node.is_tp;
-                }
-            },
-        }
+        checkOption: checkOption,
     };
     if (!checkTzMeasureType()) {
         stageCheckerSetting.checkOption.calc.fields.push('sgfh_qty', 'sjcl_qty', 'qtcl_qty', 'quantity');
@@ -3615,7 +3801,16 @@ $(document).ready(() => {
             {qty: 'quantity', tp: 'total_price'},
         );
     }
-    LedgerChecker(stageCheckerSetting);
+    $('#ledger-check2').click(() => {
+        const result = ledgerCheck2(stageCheckerSetting);
+        check2Viewing({
+            extra: ZhCalc.div(stageTree.datas.length + stagePos.datas.length, 10000, 0),
+            randomWait: true,
+            prefix: 'check2-',
+            checks: result,
+            checkList: checkList,
+        })
+    });
 
     const dataChecker = DataChecker({
         checkUrl: window.location.pathname + '/check',
@@ -3766,10 +3961,39 @@ $(document).ready(() => {
         }
         stageTreeSpreadObj.measureByBatch(posName, ratio, apply2sibling);
     });
-    $('#correct_percent').click(function () {
-        const sheet = slSpread.getActiveSheet();
-        const col = sheet.zh_setting.cols.find(x => {return x.field === 'end_gather_percent' || x.field === 'end_correct_percent'});
-        col.field = this.checked ? 'end_correct_percent' : 'end_gather_percent';
-        SpreadJsObj.reLoadColsData(sheet, [sheet.zh_setting.cols.indexOf(col)]);
+
+    $('body').on('click', '[name=ledger-unlock]', function() {
+        $('#cooperation').modal('hide');
+        const lid = this.getAttribute('lid');
+        if (!lid) return;
+
+        const p = stageTree.pwd.find(x => {return x.ledger_id == lid});
+        if (!p) return;
+
+        $('#unlock-info').html(`${p.node.code} ${p.node.name} 解锁密码<b class="text-danger">*</b>`);
+        $('#unlock-pwd').val('').removeClass('is-invalid');
+        $('.invalid-feedback', '#unlock').hide();
+        $('.alert-warning', '#unlock').hide();
+        $('#unlock-ok').attr('lid', lid);
+        $('#unlock').modal('show');
+    });
+    $('#unlock-ok').click(function () {
+        const lid = this.getAttribute('lid');
+        if (!lid) return;
+
+        const p = stageTree.pwd.find(x => {return x.ledger_id == lid});
+        if (!p) return;
+
+        if (p.pwd === $('#unlock-pwd').val()) {
+            const refresh = stageTree.lockNode(p, false);
+            SpreadJsObj.reloadRowsReadonly(slSpread.getActiveSheet(), refresh);
+            stagePosSpreadObj.loadCurPosData();
+            $('#unlock').modal('hide');
+            reloadCooperationHtml();
+        } else {
+            $('#unlock-pwd').addClass('is-invalid');
+            $('.invalid-feedback', '#unlock').show();
+            $('.alert-warning', '#unlock').show();
+        }
     })
 });

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

@@ -930,6 +930,7 @@ $(document).ready(() => {
 
     const deadlineObj = {
         initView(pay) {
+            $('#dl-pay-name').html(pay.name);
             // 模式
             $('[name=dl-type][value=' + pay.dl_type +']')[0].checked = true;
             // 计量期数

+ 2 - 2
app/public/report/js/rpt_cfg_const.js

@@ -4,7 +4,7 @@
 
 const   TPL_TYPE_NODE = 1,
         TPL_TYPE_TEMPLATE = 2;
-let rpt_tpl_setting = {
+const rpt_tpl_setting = {
     view: {
         selectedMulti: false
     },
@@ -30,7 +30,7 @@ let rpt_tpl_setting = {
     }
 };
 
-let rpt_prj_folder_setting = {
+const rpt_prj_folder_setting = {
     view: {
         selectedMulti: false
     },

+ 138 - 22
app/public/report/js/rpt_custom.js

@@ -43,11 +43,28 @@ const rptCustomObj = (function () {
         headColWidth: []
     };
     const gatherSelectSpreadObj = {
+        _getStageSelectHtml: function (valid) {
+            const html = [];
+            for (let i = 1; i <= valid; i++) {
+                html.push(`<option value="${i}">第${i}期</option>`);
+            }
+            return html.join('');
+        },
+        _rebuildStageSelect: function () {
+            if (gsObj.setting.type === 'stage') {
+                const validStage = _.min(_.map(gsObj.grArray, 'stageCount'));
+                $('#gather-stage').html(this._getStageSelectHtml(validStage));
+            } else if (gsObj.setting.type === 'stage-zone') {
+                const validStage = _.max(_.map(gsObj.grArray, 'stageCount'));
+                $('#gather-stage-begin').html(this._getStageSelectHtml(validStage));
+                $('#gather-stage-end').html(this._getStageSelectHtml(validStage));
+            }
+        },
         _addTender: function (tender) {
             const gr = gsObj.grArray.find(function (x) {
                 return x.tid === tender.tid;
             });
-            const t = {tid: tender.tid, name: tender.name}
+            const t = {tid: tender.tid, name: tender.name, stageCount: tender.stageCount};
             if (!gr) gsObj.grArray.push(t);
             return t;
         },
@@ -59,6 +76,7 @@ const rptCustomObj = (function () {
         },
         reloadResultData: function () {
             SpreadJsObj.reLoadSheetData(gsObj.grSheet);
+            this._rebuildStageSelect();
         },
         gsButtonClicked: function (e, info) {
             if (!info.sheet.zh_setting) return;
@@ -165,6 +183,12 @@ const rptCustomObj = (function () {
     const initAuditSelect = function (asSetting, asSelect) {
         const setting = JSON.parse(asSetting), select = asSelect;
         $('#audit-select-title').html(setting.title);
+        if (setting.hideSign) {
+            $('#pnl_eSignature').hide();
+        } else {
+            $('#pnl_eSignature').show();
+        }
+        $('#pnl_audit_select div button').html('<i class="fa fa-pencil"></i><br>' + (setting.caption || '审批人选择'));
         const html = [];
         for (const [i, s] of setting.select.entries()) {
             html.push('<tr>');
@@ -217,26 +241,30 @@ const rptCustomObj = (function () {
         $('#gather-select-title').html(gsObj.setting.title + (rptName ? '-' + rptName : ''));
         initGrSpreadSetting(gsObj.setting);
         SpreadJsObj.initSheet(gsObj.grSheet, grSpreadSetting);
-        if (gsObj.setting.type === 'month') {
-            $('#gather-by-month').show();
-            $('#gather-by-zone').hide();
-        } else if (gsObj.setting.type === 'zone') {
-            $('#gather-by-month').hide();
-            $('#gather-by-zone').show();
-        } else {
-            $('#gather-by-month').hide();
-            $('#gather-by-zone').hide();
-        }
-        SpreadJsObj.loadSheetData(gsObj.grSheet, SpreadJsObj.DataType.Data, gsObj.grArray);
+
         // 初始化选择结果
+        SpreadJsObj.loadSheetData(gsObj.grSheet, SpreadJsObj.DataType.Data, gsObj.grArray);
+        gatherSelectSpreadObj.initSelectTenders(gsSelect ? gsSelect.tenders : []);
+
+        $('[name=gather-type]').hide();
+        if (gsObj.setting.type === 'month') $('#gather-by-month').show();
+        if (gsObj.setting.type === 'zone') $('#gather-by-zone').show();
+        if (gsObj.setting.type === 'stage') $('#gather-by-stage').show();
+        if (gsObj.setting.type === 'stage-zone') $('#gather-by-stage-zone').show();
+
         if (gsSelect) {
             if (gsSelect.zone) {
                 $('#gather-zone').val(gsSelect.zone ? gsSelect.zone : '');
             } else if (gsSelect.month) {
                 $('#gather-month').val(gsSelect.month ? gsSelect.month: '');
+            } else if (gsSelect.stage) {
+                $('#gather-stage').val(gsSelect.stage || '');
+            } else if (gsSelect.stage_zone) {
+                const [stageBegin, stageEnd] = gsSelect.stage_zone ? gsSelect.stage_zone.split(':') : ['', ''];
+                $('#gather-stage-begin').val(stageBegin);
+                $('#gather-stage-end').val(stageEnd);
             }
         }
-        gatherSelectSpreadObj.initSelectTenders(gsSelect ? gsSelect.tenders : []);
 
         // 初始化
         $("#gather-select").modal('show');
@@ -269,6 +297,7 @@ const rptCustomObj = (function () {
             $('#pnl_audit_select').show();
             initAuditSelect(cDefine[sAuditSelect].setting, cSelect ? cSelect[sAuditSelect] : []);
         } else {
+            $('#pnl_eSignature').show();
             $('#pnl_audit_select').hide();
         }
         if (cDefine && cDefine[sGatherSelect] && cDefine[sGatherSelect].enable && cDefine[sGatherSelect].setting) {
@@ -423,6 +452,34 @@ const rptCustomObj = (function () {
                 hintObj.html('请选择 完整汇总周期').show();
                 return;
             }
+        } else if (gsObj.setting.type === 'stage') {
+            data[sGatherSelect].stage = _.toInteger($('#gather-stage').val()) || 0;
+            const validStage = _.min(_.map(gsObj.grArray, 'stageCount'));
+            if (!data[sGatherSelect].stage) {
+                hintObj.html('请选择 汇总期').show();
+                return;
+            }
+            if (data[sGatherSelect].stage > validStage) {
+                hintObj.html('选择的期无效,请重新选择').show();
+                return;
+            }
+        } else if (gsObj.setting.type === 'stage-zone') {
+            const stageBegin = _.toInteger($('#gather-stage-begin').val()) || 0;
+            const stageEnd = _.toInteger($('#gather-stage-end').val()) || 0;
+            const validStage = _.max(_.map(gsObj.grArray, 'stageCount'));
+            if (!stageBegin || !stageEnd) {
+                hintObj.html('请选择 汇总开始期与结束期').show();
+                return;
+            }
+            if (stageEnd <= stageBegin) {
+                hintObj.html('结束期应大于开始期').show();
+                return;
+            }
+            if (stageEnd > validStage) {
+                hintObj.html('选择的期无效,请重新选择').show();
+                return;
+            }
+            data[sGatherSelect].stage_zone = stageBegin + ':' + stageEnd;
         }
         hintObj.hide();
         if (resolve) {
@@ -476,8 +533,7 @@ const rptCustomObj = (function () {
         }
     };
 
-    const initTenderTreeForCross = function (tenders, category, rstItems) {
-        //用户跨标段设置电子签名用
+    const _createTenderTreeForCross = function (tenders, category, rstItems) {
         if (rstItems instanceof Array && rstItems.length === 0) {
             //1. 先确定category方式(‘年份’、‘姓名’、‘类型’)及顺序
             let ctArr = [];
@@ -499,12 +555,14 @@ const rptCustomObj = (function () {
                     }
                     for (let idx = 0; idx < ctArr[startIdx].value.length; idx++) {
                         item.name = ctArr[startIdx].value[idx].value;
-                        item.id = ctArr[startIdx].value[idx].id;
-                        item.cid = ctArr[startIdx].value[idx].cid;
-                        item.pid = ctArr[startIdx].value[idx].pid;
+                        item.id = ctArr[startIdx].id; //这个相当于类型id,如68:年份 69:类型,105:姓名
+                        item.value_id = ctArr[startIdx].value[idx].id; //每个大类下又有小类,如:2018/2019, 土建/房建, 具体用户姓名...
+                        item.cid = ctArr[startIdx].value[idx].cid; //这个值 = item.id
+                        item.pid = ctArr[startIdx].value[idx].pid; //project id?
                         item.tenderId = -1;
                         item.selected = false;
                         item.isParent = true;
+                        item.last_stage = -1;
                         item.items = [];
                         _buildDeftNodes(startIdx + 1, item);
                     }
@@ -514,15 +572,72 @@ const rptCustomObj = (function () {
             //2. 创建基本结构
             _buildDeftNodes(0, rstItems);
             //3. 挂上标段
-            for (let tender of tenders) {
-                let minAmt = Math.min(tender.category.length, ctArr.length);
-                for (let idx = 0; idx < minAmt; idx++) {
-                    //
+            const _putupTheTender = function (tender) {
+                const _findType = function (parentItem) {
+                    for (let cat of tender.category) {
+                        if (cat.cid === parentItem.cid && cat.value === parentItem.value_id) {
+                            if (parentItem.items.length === 0) {
+                                // 到底了,挂上
+                                let lastStage = -1;
+                                if (tender.lastStage) {
+                                    lastStage = tender.lastStage.times;
+                                }
+                                let item = {
+                                    name: tender.name,
+                                    id  : -1,
+                                    value_id: cat.value,
+                                    cid : cat.cid,
+                                    pid : -1,
+                                    tenderId: tender.id,
+                                    selected : false,
+                                    isParent: true,
+                                    last_stage: lastStage,
+                                    items : [],
+                                }
+                                parentItem.items.push(item);
+                            } else {
+                                for (let nodeItem of parentItem.items) {
+                                    _findType(tender, nodeItem);
+                                }
+                            }
+                            break;
+                        }
+                    }
+                };
+                for (let nodeItem of rstItems) {
+                    _findType(nodeItem);
                 }
+            };
+            for (let tender of tenders) {
+                _putupTheTender(tender);
             }
         }
     };
 
+    const initTenderTreeForCross = function (tenders, category) {
+        //用户跨标段设置电子签名用
+        let rstItems = [];
+        _createTenderTreeForCross(tenders, category, rstItems);
+        _buildTenderRow('batch_projects_individual', rstItems);
+    };
+
+    const _buildTenderRow = function(tbDomId, topTreeNodes) {
+        let tbDom = $("#" + tbDomId);
+        tbDom.empty();
+        tbDom.append('<tr><th>名称</th><th>计量期</th><th>签名</th><th>选择</th></tr>');
+        let _pushRptLine = function (nodeItem, level) {
+            if (nodeItem.isParent) {
+                tbDom.append('<tr><td class="in-'+ (level + 1) + '"><i class="fa fa-folder-o"></i>&nbsp;' + nodeItem.name + '</td><td></td><td></td><td></td></tr>')
+                //<td class="in-1"><i class="fa fa-folder-o"></i>&nbsp;2019</td>
+            } else {
+                //
+            }
+        };
+        for (const topItem of topTreeNodes) {
+            _pushRptLine(topItem, 0);
+        }
+    }
+
     const initTenderTree = function (tenders, category) {
         const gsSpread = SpreadJsObj.createNewSpread($('#gather-source-spread')[0]);
         gsObj.gsSheet = gsSpread.getActiveSheet();
@@ -638,6 +753,7 @@ const rptCustomObj = (function () {
         init,
         resetAuditSelect, resetGatherSelect, resetStageSelect,
         initTenderTree,
+        initTenderTreeForCross,
         getCustomSelect,
         showMaterialSelect, changeMaterial,
     };

+ 1 - 1
app/public/report/js/rpt_main.js

@@ -354,7 +354,7 @@ let zTreeOprObj = {
     requestNormalReport: function (params) {
         let me = zTreeOprObj;
         $.bootstrapLoading.start();
-        CommonAjax.postXsrfEx("/tender/report_api/getReport", params, 60000, true, getCookie('csrfToken'),
+        CommonAjax.postXsrfEx("/tender/report_api/getReport", params, 300000, true, getCookie('csrfToken'),
             function(result){
                 $.bootstrapLoading.end();
                 let pageRst = result.data;

+ 114 - 61
app/public/report/js/rpt_signature.js

@@ -189,74 +189,84 @@ let rptSignatureHelper = {
     },
     resetESignature: function (pageRst, signatureDivId) {
         // let body = $('#eSignatureBodyDiv');
-        let body = $('#' + signatureDivId);
-        body.empty();
-        const signature_cells = [];
-        const singatureNameArr = [];
-        for (const page of pageRst.items) {
-            if (page.signature_cells) {
-                for (const sCell of page.signature_cells) {
-                    if (sCell.signature_name !== null && sCell.signature_name !== undefined && sCell.signature_name !== 'dummy_pic') {
-                        if (singatureNameArr.indexOf(sCell.signature_name) < 0) {
-                            signature_cells.push(sCell);
-                            singatureNameArr.push(sCell.signature_name);
+        if (pageRst !== null) {
+            let body = $('#' + signatureDivId);
+            body.empty();
+            const signature_cells = [];
+            const singatureNameArr = [];
+            for (const page of pageRst.items) {
+                if (page.signature_cells) {
+                    for (const sCell of page.signature_cells) {
+                        if (sCell.signature_name !== null && sCell.signature_name !== undefined && sCell.signature_name !== 'dummy_pic') {
+                            if (singatureNameArr.indexOf(sCell.signature_name) < 0) {
+                                signature_cells.push(sCell);
+                                singatureNameArr.push(sCell.signature_name);
+                            }
                         }
                     }
                 }
             }
-        }
-        if (signature_cells.length > 0) {
-            const elementsStrArr = [];
-            const elementsDateStrArr = [];
-            for (let scIdx = 0; scIdx < signature_cells.length; scIdx++) {
-                const sCell = signature_cells[scIdx];
-                elementsStrArr.push('<div class="form-group row">');
-                elementsStrArr.push('<label for="staticEmail" class="col-sm-3 col-form-label pr-0">' + sCell.signature_name + '</label>');
-                elementsStrArr.push('<div class="col-sm-9">');
-                elementsStrArr.push('<ul class="list-group">');
-                elementsStrArr.push('<li class="list-group-item">');
-                let hasPic = false;
-                //新需求中,即使没有审核,也可以设置签名
-                for (let idx = 0; idx < ROLE_REL_LIST.length; idx++) {
-                    const role_rel = ROLE_REL_LIST[idx];
-                    if (role_rel.signature_name === sCell.signature_name) {
-                        if (role_rel.type === '用户') {
-                            rptSignatureHelper.pushDomElementByUser(elementsStrArr, role_rel.user_name, role_rel.role);
-                        } else {
-                            //角色
-                            rptSignatureHelper.pushDomElementByRole(elementsStrArr, role_rel.role_name, role_rel.user_name);
-                        }
-                        const idSuffixStr = 'dtp_' + role_rel.signature_name;
-                        elementsStrArr.push('<div class="">');
-                        if (role_rel.sign_date !== '') {
-                            const dt = new Date(role_rel.sign_date);
-                            const dtVal = dt.Format('yyyy-MM-dd');
-                            //elementsStrArr.push('<input class="datepicker-here form-control form-control-sm mt-0" placeholder="选择签名日期" data-language="zh" type="text" value="' + (new Date(role_rel.sign_date)).Format('yyyy-M-d') + '">');
-                            // elementsStrArr.push('<input id="' + idSuffixStr + '" class="datepicker-here form-control form-control-sm mt-0" placeholder="选择签名日期" data-language="zh" type="text" readonly="true" value="' + dtVal + '"');
-                            elementsStrArr.push('<input id="' + idSuffixStr + '" class="form-control form-control-sm mt-0" placeholder="选择签名日期" type="date" value="' + dtVal + '"');
-                        } else {
-                            // elementsStrArr.push('<input id="' + idSuffixStr + '" class="datepicker-here form-control form-control-sm mt-0" placeholder="选择签名日期" data-language="zh" type="text" readonly="true"');
-                            elementsStrArr.push('<input id="' + idSuffixStr + '" class="form-control form-control-sm mt-0" placeholder="选择签名日期" type="date"');
+            if (signature_cells.length > 0) {
+                const elementsStrArr = [];
+                const elementsDateStrArr = [];
+                for (let scIdx = 0; scIdx < signature_cells.length; scIdx++) {
+                    const sCell = signature_cells[scIdx];
+                    elementsStrArr.push('<div class="form-group row">');
+                    elementsStrArr.push('<label for="staticEmail" class="col-sm-3 col-form-label pr-0">' + sCell.signature_name + '</label>');
+                    elementsStrArr.push('<div class="col-sm-9">');
+                    elementsStrArr.push('<ul class="list-group">');
+                    elementsStrArr.push('<li class="list-group-item">');
+                    let hasPic = false;
+                    //新需求中,即使没有审核,也可以设置签名
+                    for (let idx = 0; idx < ROLE_REL_LIST.length; idx++) {
+                        const role_rel = ROLE_REL_LIST[idx];
+                        if (role_rel.signature_name === sCell.signature_name) {
+                            if (role_rel.type === '用户') {
+                                rptSignatureHelper.pushDomElementByUser(elementsStrArr, role_rel.user_name, role_rel.role);
+                            } else {
+                                //角色
+                                rptSignatureHelper.pushDomElementByRole(elementsStrArr, role_rel.role_name, role_rel.user_name);
+                            }
+                            const idSuffixStr = 'dtp_' + role_rel.signature_name;
+                            elementsStrArr.push('<div class="">');
+                            if (role_rel.sign_date !== '') {
+                                const dt = new Date(role_rel.sign_date);
+                                const dtVal = dt.Format('yyyy-MM-dd');
+                                //elementsStrArr.push('<input class="datepicker-here form-control form-control-sm mt-0" placeholder="选择签名日期" data-language="zh" type="text" value="' + (new Date(role_rel.sign_date)).Format('yyyy-M-d') + '">');
+                                // elementsStrArr.push('<input id="' + idSuffixStr + '" class="datepicker-here form-control form-control-sm mt-0" placeholder="选择签名日期" data-language="zh" type="text" readonly="true" value="' + dtVal + '"');
+                                elementsStrArr.push('<input id="' + idSuffixStr + '" class="form-control form-control-sm mt-0" placeholder="选择签名日期" type="date" value="' + dtVal + '"');
+                            } else {
+                                // elementsStrArr.push('<input id="' + idSuffixStr + '" class="datepicker-here form-control form-control-sm mt-0" placeholder="选择签名日期" data-language="zh" type="text" readonly="true"');
+                                elementsStrArr.push('<input id="' + idSuffixStr + '" class="form-control form-control-sm mt-0" placeholder="选择签名日期" type="date"');
+                            }
+                            hasPic = true;
+                            break;
                         }
-                        hasPic = true;
-                        break;
                     }
+                    if (!hasPic) {
+                        // 在交互操作中,有可能实际上是没有
+                        elementsStrArr.push('<a href="#add-sign" onclick="rptSignatureHelper.currentSelectedESignAccDom = this.parentNode; rptSignatureHelper.currentSelectedESignAccName = \'' + sCell.signature_name + '\'" data-toggle="modal" data-target="#add-sign"><i class="fa fa-plus"></i> 添加签名</a>');
+                    }
+                    // if (sCell.path || sCell.pic) {
+                    // } else {
+                    //     elementsStrArr.push('<a href="#add-sign" onclick="rptSignatureHelper.currentSelectedESignAccDom = this.parentNode; rptSignatureHelper.currentSelectedESignAccName = \'' + sCell.signature_name + '\'" data-toggle="modal" data-target="#add-sign"><i class="fa fa-plus"></i> 添加签名</a>');
+                    // }
+                    elementsStrArr.push('</li>');
+                    elementsStrArr.push('</ul>');
+                    elementsStrArr.push('</div>');
+                    elementsStrArr.push('</div>');
+                    //还有签名日期(用不用得上不管,先放上去再说)
                 }
-                if (!hasPic) {
-                    // 在交互操作中,有可能实际上是没有
-                    elementsStrArr.push('<a href="#add-sign" onclick="rptSignatureHelper.currentSelectedESignAccDom = this.parentNode; rptSignatureHelper.currentSelectedESignAccName = \'' + sCell.signature_name + '\'" data-toggle="modal" data-target="#add-sign"><i class="fa fa-plus"></i> 添加签名</a>');
-                }
-                // if (sCell.path || sCell.pic) {
-                // } else {
-                //     elementsStrArr.push('<a href="#add-sign" onclick="rptSignatureHelper.currentSelectedESignAccDom = this.parentNode; rptSignatureHelper.currentSelectedESignAccName = \'' + sCell.signature_name + '\'" data-toggle="modal" data-target="#add-sign"><i class="fa fa-plus"></i> 添加签名</a>');
-                // }
-                elementsStrArr.push('</li>');
-                elementsStrArr.push('</ul>');
-                elementsStrArr.push('</div>');
-                elementsStrArr.push('</div>');
-                //还有签名日期(用不用得上不管,先放上去再说)
+                body.append(elementsStrArr.join(' '));
             }
-            body.append(elementsStrArr.join(' '));
+        }
+    },
+    checkAndShowCrossTendersESignature: function () {
+        let btnDom = $('#btn_cross_tender')[0];
+        if (zTreeOprObj.currentNode) {
+            btnDom.style.display = '';
+        } else {
+            btnDom.style.display = 'none';
         }
     },
     pushDomElementByUser: function (elementsStrArr, userName, userRole) {
@@ -397,6 +407,47 @@ let rptSignatureHelper = {
             }
         }
     },
+    setupAfterSelectMultiTenders: function (selectedTenders) {
+        //跨标段选择,有不少要注意的交互:
+        //0. 签名日期
+        rptSignatureHelper.resetSignDate();
+        rptSignatureHelper.resetSignAudit();
+        //1. 重刷page
+        if (current_stage_status === 3) {
+            for (const page of zTreeOprObj.currentRptPageRst.items) {
+                if (page.signature_cells) {
+                    for (const sCell of page.signature_cells) {
+                        if (sCell.hasOwnProperty('pre_path')) {
+                            sCell.path = sCell.pre_path;
+                            delete sCell.pre_path;
+                        }
+                    }
+                }
+            }
+            zTreeOprObj.showPage(zTreeOprObj.currentPage, zTreeOprObj.canvas);
+        }
+        //2. 集中请求
+        let params = {};
+        params.id = CURRENT_ROLE_REL_ID;
+        params.tender_id = TENDER_ID;
+        params.stage_id = getStageId();
+        params.rpt_id = zTreeOprObj.currentNode.refId;
+        params.rel_content = ROLE_REL_LIST;
+        params.selectedTenders = selectedTenders;
+        rptSignatureHelper.originalRoleRelList = JSON.parse(JSON.stringify(ROLE_REL_LIST));
+        CommonAjax.postXsrfEx("/tender/report_api/updateMultiRoleRelationship", params, 10000, true, getCookie('csrfToken'),
+            function(result){
+                console.log(result);
+                if (result.data && result.data.insertId > 0) {
+                    CURRENT_ROLE_REL_ID = result.data.insertId;
+                }
+            }, function(err){
+                // hintBox.unWaitBox();
+            }, function(ex){
+                // hintBox.unWaitBox();
+            }
+        );
+    },
     setupAfterSelectSignature: function () {
         //0. 签名日期
         rptSignatureHelper.resetSignDate();
@@ -617,7 +668,9 @@ let rptSignatureHelper = {
         for (const page of pageData.items) {
             if (page.signature_date_cells) {
                 for (const sCell of page.signature_date_cells) {
-                    sCell.Value = _getSignDateDftName();
+                    if (sCell.Value === undefined || sCell.Value === null && sCell.Value === '') {
+                        sCell.Value = _getSignDateDftName();
+                    }
                     for (const role_rel of currRoleRelList) {
                         if (sCell.signature_name === role_rel.signature_name + '_签字日期') {
                             if (role_rel.sign_date !== '') {

+ 10 - 1
app/reports/rpt_component/helper/jpc_helper_field.js

@@ -86,7 +86,16 @@ const JpcFieldHelper = {
             if (showZero && showZero === 'F') {
                 const val = parseFloat(cell[JV.PROP_VALUE]);
                 if (val === 0) {
-                    cell[JV.PROP_VALUE] = '';
+                    let chkRst = true;
+                    if (typeof cell[JV.PROP_VALUE] === 'string' && cell[JV.PROP_VALUE].length > 1) {
+                        for (let idx = 0; idx < cell[JV.PROP_VALUE].length; idx++) {
+                            if (cell[JV.PROP_VALUE][idx] !== '0' && cell[JV.PROP_VALUE][idx] !== '.') {
+                                chkRst = false;
+                                break;
+                            }
+                        }
+                    }
+                    if (chkRst) cell[JV.PROP_VALUE] = '';
                 }
             }
         }

+ 2 - 0
app/reports/rpt_component/jpc_ex.js

@@ -241,6 +241,8 @@ JpcExSrv.prototype.createNew = function() {
                     try {
                         eval(expression);
                     } catch (ex) {
+                        console.log("current expression idx: " + execFmlIdx);
+                        console.log(expression);
                         console.log(ex);
                     }
                 }

+ 19 - 16
app/reports/util/rpt_excel_util.js

@@ -871,23 +871,25 @@ function writeSheet(pageData, sheetData, paperSize, sharedStrList, stylesObj, ap
                 const styleIdx = private_getStyleId(cells[cIdx]);
                 rowIdx1 = theYPos.indexOf(cells[cIdx][JV.PROP_AREA][JV.PROP_TOP]);
                 colIdx1 = xPos.indexOf(cells[cIdx][JV.PROP_AREA][JV.PROP_LEFT]);
-                let cellObj = rows[rowIdx1 - 1].items[colIdx1 - 1];
-                cellObj.s = styleIdx;
-                cellObj.isBlank = false;
-                if (!(strUtil.isEmptyString(cells[cIdx][JV.PROP_VALUE]))) {
-                    const valIdx = private_getSharedStrIdx(cells[cIdx][JV.PROP_VALUE]);
-                    cellObj.v = valIdx;
-                }
+                if (rowIdx1 <= rows.length) {
+                    let cellObj = rows[rowIdx1 - 1].items[colIdx1 - 1];
+                    cellObj.s = styleIdx;
+                    cellObj.isBlank = false;
+                    if (!(strUtil.isEmptyString(cells[cIdx][JV.PROP_VALUE]))) {
+                        const valIdx = private_getSharedStrIdx(cells[cIdx][JV.PROP_VALUE]);
+                        cellObj.v = valIdx;
+                    }
 
-                rowIdx2 = theYPos.indexOf(cells[cIdx][JV.PROP_AREA][JV.PROP_BOTTOM]);
-                colIdx2 = xPos.indexOf(cells[cIdx][JV.PROP_AREA][JV.PROP_RIGHT]);
-                if ((rowIdx2 - rowIdx1 > 1) || (colIdx2 - colIdx1 > 1)) {
-                    for (let i = 0; i < rowIdx2 - rowIdx1; i++) {
-                        for (let j = 0; j < colIdx2 - colIdx1; j++) {
-                            if (i === 0 && j === 0) continue;
-                            cellObj = rows[rowIdx1 - 1 + i].items[colIdx1 - 1 + j];
-                            cellObj.s = styleIdx;
-                            cellObj.isBlank = true;
+                    rowIdx2 = theYPos.indexOf(cells[cIdx][JV.PROP_AREA][JV.PROP_BOTTOM]);
+                    colIdx2 = xPos.indexOf(cells[cIdx][JV.PROP_AREA][JV.PROP_RIGHT]);
+                    if ((rowIdx2 - rowIdx1 > 1) || (colIdx2 - colIdx1 > 1)) {
+                        for (let i = 0; i < rowIdx2 - rowIdx1; i++) {
+                            for (let j = 0; j < colIdx2 - colIdx1; j++) {
+                                if (i === 0 && j === 0) continue;
+                                cellObj = rows[rowIdx1 - 1 + i].items[colIdx1 - 1 + j];
+                                cellObj.s = styleIdx;
+                                cellObj.isBlank = true;
+                            }
                         }
                     }
                 }
@@ -957,6 +959,7 @@ function writeSheet(pageData, sheetData, paperSize, sharedStrList, stylesObj, ap
                 currentPageMergePos = shtItemData[JV.PAGE_SPECIAL_MERGE_POS];
                 currentMergeBorder = shtItemData[JV.PROP_PAGE_MERGE_BORDER];
                 const tmpPos = yMultiPos[i];
+                // console.log(yMultiPos[i]);
                 cellIdx = 0;
                 if (hasSignature && shtItemData[JV.PROP_SIGNATURE_CELLS] && shtItemData[JV.PROP_SIGNATURE_CELLS].length > 0) {
                     // 有签名情况下,还是有必要创建一个dummy cell的(画框用)

+ 16 - 3
app/router.js

@@ -112,9 +112,11 @@ module.exports = app => {
     app.post('/tender/:id/save', sessionAuth, tenderCheck, 'tenderController.saveTenderInfo');
     app.post('/tender/rule', sessionAuth, 'tenderController.rule');
     app.post('/tender/:id/rule/first', sessionAuth, tenderCheck, 'tenderController.ruleFirst');
-    app.get('/tender/:id/shenpi', sessionAuth, tenderCheck, 'tenderController.shenpiSet');
-    app.post('/tender/:id/shenpi/save', sessionAuth, tenderCheck, 'tenderController.saveTenderInfoShenpi');
-    app.post('/tender/:id/shenpi/audit/save', sessionAuth, tenderCheck, 'tenderController.saveShenpiAudit');
+    app.get('/tender/:id/shenpi', sessionAuth, tenderCheck, uncheckTenderCheck, 'tenderController.shenpiSet');
+    app.post('/tender/:id/shenpi/save', sessionAuth, tenderCheck, uncheckTenderCheck, 'tenderController.saveTenderInfoShenpi');
+    app.post('/tender/:id/shenpi/audit/save', sessionAuth, tenderCheck, uncheckTenderCheck, 'tenderController.saveShenpiAudit');
+    app.post('/tender/:id/shenpi/ledger/load', sessionAuth, tenderCheck, uncheckTenderCheck, 'tenderController.loadLedgerData');
+    app.post('/tender/:id/shenpi/save-sign', sessionAuth, tenderCheck, uncheckTenderCheck, 'tenderController.saveCooperateSign');
     app.post('/tender/:id/copy-setting', sessionAuth, tenderCheck, 'tenderController.copyTender');
 
     // 预付款
@@ -303,6 +305,8 @@ module.exports = app => {
     app.post('/tender/report_api/createSignatureRole', sessionAuth, 'signatureController.createSignatureRole');
     app.post('/tender/report_api/updateSignatureUsed', sessionAuth, datetimeFill, 'signatureController.updateSignatureUsed');
     app.post('/tender/report_api/updateRoleRelationship', sessionAuth, 'signatureController.updateRoleRel');
+    app.post('/tender/report_api/updateMultiRoleRelationship', sessionAuth, 'signatureController.updateCrossTendersRoleRelationship');
+    app.post('/tender/report_api/getMultiRoleRelationships', sessionAuth, 'signatureController.getMultiRoleRptRels');
     app.post('/tender/report_api/createRoleRelationship', sessionAuth, 'signatureController.createRoleRel');
     app.post('/tender/report_api/updateCustNode', sessionAuth, 'reportController.updateCustNode');
     app.post('/report/cDefine', sessionAuth, 'reportController.setCustomDefine');
@@ -434,4 +438,13 @@ module.exports = app => {
     app.get('/tender/:id/schedule', sessionAuth, tenderCheck, uncheckTenderCheck, 'scheduleController.index');
     app.get('/tender/:id/schedule/ledger', sessionAuth, tenderCheck, uncheckTenderCheck, 'scheduleController.ledger');
     app.post('/tender/:id/schedule/ledger/load', sessionAuth, tenderCheck, uncheckTenderCheck, 'scheduleController.loadLedgerData');
+    app.post('/tender/:id/schedule/ledger/save', sessionAuth, tenderCheck, uncheckTenderCheck, 'scheduleController.saveLedger');
+    app.get('/tender/:id/schedule/plan', sessionAuth, tenderCheck, uncheckTenderCheck, 'scheduleController.plan');
+    app.post('/tender/:id/schedule/plan/save', sessionAuth, tenderCheck, uncheckTenderCheck, 'scheduleController.savePlan');
+    app.get('/tender/:id/schedule/stage', sessionAuth, tenderCheck, uncheckTenderCheck, 'scheduleController.stageTp');
+    app.get('/tender/:id/schedule/stage/gcl', sessionAuth, tenderCheck, uncheckTenderCheck, 'scheduleController.stageGcl');
+
+    // 书签
+    app.post('/tender/:id/ledger/tag', sessionAuth, tenderCheck, uncheckTenderCheck, 'tenderController.billsTag');
+    app.post('/tender/:id/measure/stage/:order/tag', sessionAuth, tenderCheck, uncheckTenderCheck, stageCheck, 'tenderController.billsTag');
 };

+ 10 - 4
app/service/change.js

@@ -487,6 +487,7 @@ module.exports = app => {
                     charge: postData.charge,
                     w_code: postData.w_code,
                     total_price,
+                    tp_decimal: this.ctx.tender.info.decimal.tp,
                 };
                 const options = {
                     where: {
@@ -579,13 +580,14 @@ module.exports = app => {
                 // 清单数据更新
                 const bills_list = postData.bills_list.split(',');
                 let total_price = 0;
+                const tp_decimal = changeData.tp_decimal ? changeData.tp_decimal : this.ctx.tender.info.decimal.tp;
                 for (const bl of bills_list) {
                     const listInfo = bl.split('_');
                     const lid = listInfo[0];
                     const amount = listInfo[1];
                     const changeListInfo = await this.ctx.service.changeAuditList.getDataById(lid);
                     if (changeListInfo !== undefined) {
-                        total_price = this.ctx.helper.add(total_price, this.ctx.helper.mul(changeListInfo.unit_price, amount, this.ctx.tender.info.decimal.tp));
+                        total_price = this.ctx.helper.add(total_price, this.ctx.helper.mul(changeListInfo.unit_price, amount, tp_decimal));
                         const audit_amount = changeListInfo.audit_amount !== null && changeListInfo.audit_amount !== '' ? changeListInfo.audit_amount.split(',') : [];
                         audit_amount.push(amount);
                         const list_update = {
@@ -780,8 +782,9 @@ module.exports = app => {
                     where: { cid: changeInfo.cid },
                 });
                 let total_price = 0;
+                const tp_decimal = changeData.tp_decimal ? changeData.tp_decimal : this.ctx.tender.info.decimal.tp;
                 for (const cl of changeList) {
-                    total_price = this.ctx.helper.add(total_price, this.ctx.helper.mul(cl.unit_price, cl.camount, this.ctx.tender.info.decimal.tp));
+                    total_price = this.ctx.helper.add(total_price, this.ctx.helper.mul(cl.unit_price, cl.camount, tp_decimal));
                 }
                 // 设置变更令退回
                 const change_update = {
@@ -790,6 +793,7 @@ module.exports = app => {
                     times: newTimes,
                     cin_time: Date.parse(new Date()) / 1000,
                     total_price,
+                    tp_decimal: null,
                 };
                 const options = {
                     where: {
@@ -919,6 +923,7 @@ module.exports = app => {
                     where: { cid: changeInfo.cid },
                 });
                 let total_price = 0;
+                const tp_decimal = changeData.tp_decimal ? changeData.tp_decimal : this.ctx.tender.info.decimal.tp;
                 for (const cl of changeList) {
                     const audit_amount = cl.audit_amount.split(',');
                     const last_amount = audit_amount[audit_amount.length - 1];
@@ -928,7 +933,7 @@ module.exports = app => {
                         audit_amount: audit_amount.join(','),
                         spamount: parseFloat(last_amount),
                     };
-                    total_price = this.ctx.helper.add(total_price, this.ctx.helper.mul(cl.unit_price, parseFloat(last_amount), this.ctx.tender.info.decimal.tp));
+                    total_price = this.ctx.helper.add(total_price, this.ctx.helper.mul(cl.unit_price, parseFloat(last_amount), tp_decimal));
                     await this.transaction.update(this.ctx.service.changeAuditList.tableName, list_update);
                 }
 
@@ -1231,6 +1236,7 @@ module.exports = app => {
 
                 // 审批列表数据也要回退
                 let total_price = 0;
+                const tp_decimal = changeInfo.tp_decimal ? changeInfo.tp_decimal : this.ctx.tender.info.decimal.tp;
                 const changeList = await this.ctx.service.changeAuditList.getAllDataByCondition({
                     where: { cid: changeInfo.cid },
                 });
@@ -1243,7 +1249,7 @@ module.exports = app => {
                         audit_amount: audit_amount.join(','),
                         samount: '',
                     };
-                    total_price = this.ctx.helper.add(total_price, this.ctx.helper.mul(cl.unit_price, parseFloat(last_amount), this.ctx.tender.info.decimal.tp));
+                    total_price = this.ctx.helper.add(total_price, this.ctx.helper.mul(cl.unit_price, parseFloat(last_amount), tp_decimal));
                     await this.transaction.update(this.ctx.service.changeAuditList.tableName, list_update);
                 }
 

+ 14 - 9
app/service/change_audit.js

@@ -382,7 +382,7 @@ module.exports = app => {
                     order++;
                     uSort++;
                 }
-                await transaction.insert(this.tableName, newAuditors);
+                if (newAuditors.length > 0) await transaction.insert(this.tableName, newAuditors);
                 await transaction.commit();
             } catch (err) {
                 await transaction.rollback();
@@ -566,14 +566,6 @@ module.exports = app => {
             const transaction = await this.db.beginTransaction();
             try {
                 await transaction.update(this.tableName, { id: audit.id, status: auditConst.auditStatus.checking, sin_time: new Date() });
-                const options = {
-                    where: {
-                        cid: cid,
-                    },
-                };
-                await transaction.update(this.ctx.service.change.tableName, {
-                    status: auditConst.status.checking,
-                }, options);
                 // 更新原报人审批状态
                 await transaction.update(this.tableName, {
                     id: yBAudit.id,
@@ -581,9 +573,11 @@ module.exports = app => {
                     sin_time: new Date(),
                 });
                 const changeList = await this.ctx.service.changeAuditList.getList(cid);
+                let total_price = 0;
                 // 更新清单spamount的值
                 const updateListData = [];
                 for (const cl of changeList) {
+                    total_price = this.ctx.helper.accAdd(total_price, this.ctx.helper.mul(cl.unit_price, cl.camount, this.ctx.tender.info.decimal.tp));
                     if(cl.camount !== cl.spamount) {
                         const uld = {
                             id: cl.id,
@@ -593,6 +587,17 @@ module.exports = app => {
                     }
                 }
                 if(updateListData.length > 0) await transaction.updateRows(this.ctx.service.changeAuditList.tableName, updateListData);
+                const options = {
+                    where: {
+                        cid: cid,
+                    },
+                };
+                const updateData = {
+                    total_price,
+                    tp_decimal: this.ctx.tender.info.decimal.tp,
+                    status: auditConst.status.checking,
+                };
+                await transaction.update(this.ctx.service.change.tableName, updateData, options);
 
                 // 添加短信通知-需要审批提醒功能
                 const sms = new SMS(this.ctx);

+ 5 - 1
app/service/change_audit_list.js

@@ -57,6 +57,9 @@ module.exports = app => {
                 spamount: 0,
                 xmj_code: null,
                 xmj_jldy: null,
+                xmj_dwgc: null,
+                xmj_fbgc: null,
+                xmj_fxgc: null,
                 gcl_id: '',
             };
             // 新增工料
@@ -189,8 +192,9 @@ module.exports = app => {
             const sqlParam = [this.tableName, this.ctx.change.cid];
             const changeList = await transaction.query(sql, sqlParam);
             let total_price = 0;
+            const tp_decimal = this.ctx.change.tp_decimal ? this.ctx.change.tp_decimal : this.ctx.tender.info.decimal.tp;
             for (const cl of changeList) {
-                total_price = this.ctx.helper.accAdd(total_price, this.ctx.helper.mul(cl.unit_price, cl.spamount, this.ctx.tender.info.decimal.tp));
+                total_price = this.ctx.helper.accAdd(total_price, this.ctx.helper.mul(cl.unit_price, cl.spamount, tp_decimal));
             }
             const updateData = {
                 total_price,

+ 78 - 0
app/service/ledger_cooperation.js

@@ -0,0 +1,78 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date 2018/6/1
+ * @version
+ */
+
+module.exports = app => {
+    class LedgerCooperation extends app.BaseService {
+        /**
+         * 构造函数
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        constructor(ctx) {
+            super(ctx);
+            this.tableName = 'ledger_cooperation';
+        }
+
+        async save(data) {
+            delete data.type;
+            data.tid = this.ctx.tender.id;
+            const info = await this.getDataByCondition({ tid: this.ctx.tender.id, ledger_id: data.ledger_id, user_id: data.user_id });
+            if (data.pwd === '' && info) {
+                await this.deleteById(info.id);
+            } else if (data.pwd !== '' && !info) {
+                // const ledgerInfo = await this.ctx.service.ledger.getDataByCondition({ ledger_id: data.ledger_id });
+                // if (ledgerInfo) {
+                //     data.lid = ledgerInfo.id;
+                const result = await this.db.insert(this.tableName, data);
+                data.id = result.insertId;
+                data.sign_path = null;
+                data.status = 1;
+                // }
+            } else if (data.pwd !== '' && info) {
+                data.id = info.id;
+                await this.db.update(this.tableName, data);
+                data.sign_path = info.sign_path;
+            }
+            return data;
+        }
+
+        async changeAllStatus(status) {
+            const options = {
+                where: {
+                    tid: this.ctx.tender.id,
+                },
+            };
+            const updateData = {
+                status,
+            };
+            return await this.db.update(this.ctx.service.ledgerCooperation.tableName, updateData, options);
+        }
+
+        async saveSign(id, path) {
+            const updateData = {
+                id,
+                sign_path: path,
+            };
+            return await this.db.update(this.tableName, updateData);
+        }
+
+        async getValidData(tid, uid) {
+            const condition = { where: { tid, status: 1 } };
+            if (uid) {
+                condition.where.user_id = uid;
+                condition.colums = ['ledger_id', 'pwd'];
+            }
+            return await this.getAllDataByCondition(condition);
+        }
+    }
+
+    return LedgerCooperation;
+};

+ 102 - 0
app/service/ledger_tag.js

@@ -0,0 +1,102 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date
+ * @version
+ */
+
+const validField = ['lid', 'share', 'color', 'comment'];
+
+module.exports = app => {
+
+    class StageBillsDgn extends app.BaseService {
+        /**
+         * 构造函数
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        constructor(ctx) {
+            super(ctx);
+            this.tableName = 'ledger_tag';
+        }
+
+        /**
+         * 获取台账、期所有标段
+         * @param {Number} tid - 标段id
+         * @param {Number} sid - 期id(-1时查询台账分解全部标签)
+         * @returns {Promise<void>}
+         */
+        async getDatas(tid, sid = -1) {
+            // return await this.db.select(this.tableName, {
+            //     where: {tid: tid, sid: -1},
+            //     columns: ['id', 'uid', 'lid', 'share', 'color', 'comment'],
+            //     orders: [['create_time', 'desc']],
+            // });
+            const sql = 'SELECT id, uid, lid, share, color, comment FROM ' + this.tableName +
+                '  WHERE tid = ? and sid = ? and (uid = ? or share) ORDER BY create_time DESC';
+            return await this.db.query(sql, [tid, sid, this.ctx.session.sessionUser.accountId]);
+        }
+
+        /**
+         * 过滤无效字段,容错
+         * @param data
+         * @private
+         */
+        _filterInvalidField(data) {
+            for (const prop in data) {
+                if (validField.indexOf(prop) === -1) {
+                    delete data[prop];
+                }
+            }
+        }
+
+        async _addTag(data) {
+            this._filterInvalidField(data);
+            data.create_time = new Date();
+            data.modify_time = data.create_time;
+            data.uid = this.ctx.session.sessionUser.accountId;
+            data.tid = this.ctx.tender.id;
+            data.pid = this.ctx.tender.data.project_id;
+            if (this.ctx.stage) {
+                data.sid = this.ctx.stage.id;
+                data.sorder = this.ctx.stage.order;
+            }
+            const result = await this.db.insert(this.tableName, data);
+            data.id = result.insertId;
+            return data;
+        }
+
+        async _delTag(id) {
+            const tag = await this.getDataById(id);
+            if (tag.uid !== this.ctx.session.sessionUser.accountId) throw '您无权删除该数据';
+
+            await this.deleteById(id);
+            return id;
+        }
+
+        async _updateTag(data) {
+            const tag = await this.getDataById(data.id);
+            if (tag.uid !== this.ctx.session.sessionUser.accountId) throw '您无权修改该数据';
+
+            this._filterInvalidField(data);
+            data.modify_time = new Date();
+            data.id = tag.id;
+            const result = await this.db.update(this.tableName, data);
+            if (result.affectedRows === 1) return data;
+        }
+
+        async update(data) {
+            const result = {};
+            if (data.add) result.add = await this._addTag(data.add);
+            if (data.del) result.del = await this._delTag(data.del);
+            if (data.update) result.update = await this._updateTag(data.update);
+            return result;
+        }
+    }
+
+    return StageBillsDgn;
+};

+ 15 - 0
app/service/report.js

@@ -84,6 +84,10 @@ module.exports = app => {
                             runnableRst.push(service.reportMemory.getMonthProgress(params.tender_id, memFieldKeys[filter]));
                             runnableKey.push(filter);
                             break;
+                        case 'stage_audit':
+                            runnableRst.push(service.reportMemory.getStageAuditors(params.tender_id, params.stage_id));
+                            runnableKey.push(filter);
+                            break;
                         case 'mem_stage_bills':
                             runnableRst.push(service.reportMemory.getStageBillsData(params.tender_id, params.stage_id, memFieldKeys[filter]));
                             runnableKey.push(filter);
@@ -111,6 +115,7 @@ module.exports = app => {
                         case 'change_audit_list':
                             runnableRst.push(service.changeAuditList.getChangeAuditBills(params.tender_id)); // 获取所有审核通过的变更清单
                             runnableKey.push(filter);
+                            console.log(filter);
                             break;
                         case 'mem_stage_jgcl':
                             runnableRst.push(service.reportMemory.getStageJgcl(params.tender_id, params.stage_id, memFieldKeys[filter]));
@@ -162,6 +167,14 @@ module.exports = app => {
                                 customDefine.stage_select, customSelect ? customSelect.stage_select : null));
                             runnableKey.push(filter);
                             break;
+                        case 'ledger_cooperation':
+                            runnableRst.push(service.ledgerCooperation.getValidData(params.tender_id));
+                            runnableKey.push(filter);
+                            break;
+                        case 'mem_sign_select':
+                            runnableRst.push(service.reportMemory.getSignSelect(params.tender_id, params.stage_id, customSelect));
+                            runnableKey.push(filter);
+                            break;
                         default:
                             break;
                     }
@@ -171,6 +184,8 @@ module.exports = app => {
             for (let idx = 0; idx < runnableKey.length; idx++) {
                 rst[runnableKey[idx]] = queryRst[idx];
             }
+            this.ctx.helper.saveBufferFile(JSON.stringify(queryRst, '', '\t'), this.ctx.app.baseDir + '/reportDataOrg.json');
+            this.ctx.helper.saveBufferFile(JSON.stringify(rst, '', '\t'), this.ctx.app.baseDir + '/reportData.json');
             for (const filter of filters) {
                 switch (filter) {
                     case 'mem_stage_im_tz':

+ 44 - 0
app/service/report_memory.js

@@ -975,6 +975,50 @@ module.exports = app => {
                 return [];
             }
         }
+
+        async getStageAuditors(tid, sid) {
+            await this.ctx.service.tender.checkTender(tid);
+            await this.ctx.service.stage.checkStage(sid);
+
+            const auditors = await this.ctx.service.stageAudit.getFinalAuditGroup(this.ctx.stage.id, this.ctx.stage.curTimes);
+            const user = await this.ctx.service.projectAccount.getDataById(this.ctx.stage.user_id);
+            const result = [{
+                aid: user.id,
+                name: user.name,
+                company: user.company,
+                role: user.role,
+                mobile: user.mobile,
+                telephone: user.telephone,
+                sign_path: user.sign_path,
+                opinion: user.opinion,
+                end_time: auditors && auditors.length > 0 ? auditors[0].begin_time : null,
+                sort: 0,
+            }, ...auditors];
+            return result;
+        }
+
+        async getSignSelect(tid, sid, customSelect) {
+            await this.ctx.service.tender.checkTender(tid);
+            await this.ctx.service.stage.checkStage(sid);
+
+            const signSelect = customSelect.sign_select, result = {};
+            for (const [i, ss] of signSelect.entries()) {
+                const user = await this.ctx.service.projectAccount.getDataById(ss.id);
+                const sign = {
+                    id: ss.id, name: user.name, company: user.company, role: user.role,
+                    mobile: user.mobile, telephone: user.telephone,
+                };
+                if (ss.id !== this.ctx.stage.user_id) {
+                    const audit = this.ctx.stage.auditors.find(x => {return x.aid === ss.id});
+                    user.end_time = audit ? audit.end_time : ss.audit_time;
+                } else {
+                    user.end_time = this.ctx.stage.auditors[0].end_time;
+                }
+                if (user.end_time) sign.sign_path = user.sign_path;
+                result['sign' + (i+1)] = sign;
+            }
+            return result;
+        }
     }
 
     return ReportMemory;

+ 49 - 6
app/service/role_rpt_rel.js

@@ -53,6 +53,27 @@ module.exports = app => {
             return list;
         }
 
+        async getCrossTenderRoleRptRels(selectedTenders) {
+            let rst = [];
+            // 条件比较特别,需要直接写sql
+            if (selectedTenders.length > 0) {
+                let where_sql = '';
+                for (let idx = 0; idx < selectedTenders.length; idx++) {
+                    if (idx === 0) {
+                        where_sql = '(tender_id = ' + selectedTenders[idx][0] + ' AND sid = ' + selectedTenders[idx][1] + ' AND rpt_id = ' + selectedTenders[idx][2] + ')';
+                    } else {
+                        where_sql = where_sql + ' OR (tender_id = ' + selectedTenders[idx][0] + ' AND sid = ' + selectedTenders[idx][1] + ' AND rpt_id = ' + selectedTenders[idx][2] + ')';
+                    }
+                }
+                const sql = 'SELECT ?? FROM ?? WHERE ' + where_sql;
+                const columns = ['id', 'tender_id', 'rpt_id', 'sid', 'rel_content'];
+                const sqlParam = [columns, this.tableName];
+                rst = await this.db.query(sql, sqlParam);
+                // console.log(rst);
+            }
+            return rst;
+        }
+
         async getRoleRptRelByDetailIds(tenderId, rptId, sid) {
             this.initSqlBuilder();
             this.sqlBuilder.setAndWhere('tender_id', {
@@ -76,6 +97,9 @@ module.exports = app => {
             });
             this.sqlBuilder.columns = ['id', 'tender_id', 'rpt_id', 'sid', 'rel_content'];
             const [sql, sqlParam] = this.sqlBuilder.build(this.tableName);
+            // console.log(sql);
+            // console.log(this.tableName);
+            // console.log(sqlParam);
             const list = await this.db.query(sql, sqlParam);
             // console.log(list);
             return list;
@@ -86,11 +110,12 @@ module.exports = app => {
             this.transaction = await this.db.beginTransaction();
             try {
                 const data = {
-                    tender_id: tender_id,
-                    rpt_id: rpt_id,
-                    sid: sid,
+                    tender_id,
+                    rpt_id,
+                    sid,
                     rel_content: JSON.stringify(relArr),
                 };
+                // console.log(data);
                 rst = await this.transaction.insert(this.tableName, data);
                 await this.transaction.commit();
             } catch (ex) {
@@ -129,13 +154,13 @@ module.exports = app => {
         async updateRoleRelationship(id, tender_id, rpt_id, sid, relArr) {
             let rst = null;
             if (id < 0) {
-                rst = this.createRoleRelationship(tender_id, rpt_id, sid, relArr);
+                rst = await this.createRoleRelationship(tender_id, rpt_id, sid, relArr);
             } else {
                 this.transaction = await this.db.beginTransaction();
                 try {
-                    const data = { id: id, tender_id: tender_id, rpt_id: rpt_id, sid: sid, rel_content: JSON.stringify(relArr) };
+                    const data = { id, tender_id, rpt_id, sid, rel_content: JSON.stringify(relArr) };
                     rst = await this.transaction.update(this.tableName, data);
-                    this.transaction.commit();
+                    await this.transaction.commit();
                 } catch (ex) {
                     console.log(ex);
                     // 回滚
@@ -144,6 +169,24 @@ module.exports = app => {
             }
             return rst;
         }
+
+        async updateMultiRoleRelationship(orgParams, newRelArr) {
+            for (let idx = 0; idx < orgParams.length; idx++) {
+                const param = orgParams[idx];
+                const data = { tender_id: param[0], sid: param[1], rpt_id: param[2] };
+                this.transaction = await this.db.beginTransaction();
+                try {
+                    await this.transaction.delete(this.tableName, data);
+                    this.transaction.commit();
+                    await this.createRoleRelationship(param[0], param[2], param[1], newRelArr);
+                } catch (ex) {
+                    console.log(ex.toString());
+                    // 回滚
+                    await this.transaction.rollback();
+                }
+            }
+            return true;
+        }
     }
     return RoleRptRel;
 };

+ 167 - 83
app/service/rpt_gather_memory.js

@@ -67,6 +67,9 @@ const gatherUtils = {
         gatherNode['s_' + "qty"] = helper.add(gatherNode['s_' + "qty"], sourceNode.quantity);
         gatherNode['s_' + "tp"] = helper.add(gatherNode['s_' + "tp"], sourceNode.total_price);
 
+        gatherNode['s_' + "dgn_qty1"] = helper.add(gatherNode['s_' + "dgn_qty1"], sourceNode.dgn_qty1);
+        gatherNode['s_' + "dgn_qty2"] = helper.add(gatherNode['s_' + "dgn_qty2"], sourceNode.dgn_qty2);
+
         gatherNode['s_' + "contract_qty"] = helper.add(gatherNode['s_' + "contract_qty"], sourceNode.contract_qty);
         gatherNode['s_' + "contract_tp"] = helper.add(gatherNode['s_' + "contract_tp"], sourceNode.contract_tp);
         gatherNode['s_' + "qc_qty"] = helper.add(gatherNode['s_' + "qc_qty"], sourceNode.qc_qty);
@@ -87,6 +90,11 @@ const gatherUtils = {
         gatherNode['s_' + "end_qc_tp"] = helper.add(gatherNode['s_' + "end_qc_tp"], sourceNode.end_qc_tp);
         gatherNode['s_' + "end_gather_qty"] = helper.add(gatherNode['s_' + "end_gather_qty"], sourceNode.end_gather_qty);
         gatherNode['s_' + "end_gather_tp"] = helper.add(gatherNode['s_' + "end_gather_tp"], sourceNode.end_gather_tp);
+
+        gatherNode['s_' + "deal_dgn_qty1"] = helper.add(gatherNode['s_' + "deal_dgn_qty1"], sourceNode.deal_dgn_qty1);
+        gatherNode['s_' + "deal_dgn_qty2"] = helper.add(gatherNode['s_' + "deal_dgn_qty2"], sourceNode.deal_dgn_qty2);
+        gatherNode['s_' + "c_dgn_qty1"] = helper.add(gatherNode['s_' + "c_dgn_qty1"], sourceNode.c_dgn_qty1);
+        gatherNode['s_' + "c_dgn_qty2"] = helper.add(gatherNode['s_' + "c_dgn_qty2"], sourceNode.c_dgn_qty2);
     },
     gatherZone: function (tender, gatherNode, sourceNode, prefix, helper) {
         gatherNode[prefix + 'id'] = tender.id;
@@ -202,6 +210,35 @@ module.exports = app => {
             return '';
         }
 
+        async _getTimeZoneStages(tender, zone) {
+            const times = zone.split(' - ');
+            if (times.length !== 2) throw '选择的汇总周期无效';
+            const beginTime = moment(times[0], 'YYYY-MM');
+            const endTime = moment(times[1], 'YYYY-MM');
+
+            const stages = await this._getValidStages(tender.id), validStages = [];
+            for (const stage of stages) {
+                const sTime = moment(stage.s_time, 'YYYY-MM');
+                if (sTime.isBetween(beginTime, endTime, null, '[]')) {
+                    validStages.push(stage);
+                }
+            }
+            return validStages;
+        }
+
+        async _getOrderZoneStages (tender, zone) {
+            let [iBegin, iEnd] = zone.split(':');
+            iBegin = this.ctx.helper._.toInteger(iBegin) || 0;
+            iEnd = this.ctx.helper._.toInteger(iEnd) || 0;
+            const stages = await this._getValidStages(tender.id), validStages = [];
+            for (const stage of stages) {
+                if (stage.order < iBegin || stage.order > iEnd) continue;
+
+                validStages.push(stage);
+            }
+            return validStages;
+        }
+
         /**
          * 台账数据
          */
@@ -270,15 +307,10 @@ module.exports = app => {
 
         }
 
-        async _gatherMonthData(sTender, completeData, month, hasPre) {
-            const tender = await this.ctx.service.tender.getCheckTender(sTender.tid);
-            const stages = await this._getValidStages(tender.id);
-            const stage = this.ctx.helper._.find(stages, {s_time: month});
-            await this._gatherStageData(completeData, tender, stage, hasPre);
-        }
-
-        async _gatherZoneData(sTender, completeData, zone) {
+        async _gatherStagesData(completeData, tender, stages) {
             const helper = this.ctx.helper;
+            completeData.id = tender.id;
+            completeData.name = tender.name;
             /**
              * 汇总并合并 相关数据
              * @param {Array} index - 主数据
@@ -302,7 +334,6 @@ module.exports = app => {
                     loadFields(r.data, r.fields, r.prefix, r.relaId);
                 }
             };
-
             const billsTree = new Ledger.billsTree(this.ctx, {
                 id: 'ledger_id',
                 pid: 'ledger_pid',
@@ -319,9 +350,6 @@ module.exports = app => {
                     node.gather_tp = helper.add(node.contract_tp, node.qc_tp);
                 }
             });
-            const tender = await this.ctx.service.tender.getCheckTender(sTender.tid);
-            completeData.id = tender.id;
-            completeData.name = tender.name;
             const billsData = await this.ctx.service.ledger.getData(tender.id);
 
             const dgnData = await this.ctx.service.stageBillsDgn.getDgnData(tender.id);
@@ -335,37 +363,55 @@ module.exports = app => {
                 billsIndexData[indexPre + bd.id] = bd;
             }
 
-            const times = zone.split(' - ');
-            if (times.length !== 2) throw '选择的汇总周期无效';
-            const beginTime = moment(times[0], 'YYYY-MM');
-            const endTime = moment(times[1], 'YYYY-MM');
-
-            const stages = await this._getValidStages(tender.id);
             for (const stage of stages) {
-                const sTime = moment(stage.s_time, 'YYYY-MM');
-                if (sTime.isBetween(beginTime, endTime, null, '[]')) {
-                    await this.ctx.service.stage.doCheckStage(stage);
-                    if (stage.readOnly) {
-                        const curStage = await this.ctx.service.stageBills.getAuditorStageData(tender.id,
-                            stage.id, stage.curTimes, stage.curOrder);
-                        sumAssignRelaData(billsIndexData, [
-                            {data: curStage, fields: ['contract_qty', 'contract_tp', 'qc_qty', 'qc_tp'], prefix: '', relaId: 'lid'}
-                        ]);
-                    } else {
-                        const curStage = await this.ctx.service.stageBills.getLastestStageData(tender.id, stage.id);
-                        sumAssignRelaData(billsIndexData, [
-                            {data: curStage, fields: ['contract_qty', 'contract_tp', 'qc_qty', 'qc_tp'], prefix: '', relaId: 'lid'}
-                        ]);
-                    }
+                await this.ctx.service.stage.doCheckStage(stage);
+                if (stage.readOnly) {
+                    const curStage = await this.ctx.service.stageBills.getAuditorStageData(tender.id,
+                        stage.id, stage.curTimes, stage.curOrder);
+                    sumAssignRelaData(billsIndexData, [
+                        {data: curStage, fields: ['contract_qty', 'contract_tp', 'qc_qty', 'qc_tp'], prefix: '', relaId: 'lid'}
+                    ]);
+                } else {
+                    const curStage = await this.ctx.service.stageBills.getLastestStageData(tender.id, stage.id);
+                    sumAssignRelaData(billsIndexData, [
+                        {data: curStage, fields: ['contract_qty', 'contract_tp', 'qc_qty', 'qc_tp'], prefix: '', relaId: 'lid'}
+                    ]);
                 }
             }
+
             billsTree.loadDatas(billsData);
             billsTree.calculateAll();
             this.resultTree.loadGatherTree(billsTree, function (gatherNode, sourceNode) {
-                gatherUtils.gatherZone(gatherNode, sourceNode, completeData.prefix, helper);
+                gatherUtils.gatherZone(tender, gatherNode, sourceNode, completeData.prefix, helper);
             });
         }
 
+        async _gatherMonthData(sTender, completeData, month, hasPre) {
+            const tender = await this.ctx.service.tender.getCheckTender(sTender.tid);
+            const stages = await this._getValidStages(tender.id);
+            const stage = this.ctx.helper._.find(stages, {s_time: month});
+            await this._gatherStageData(completeData, tender, stage, hasPre);
+        }
+
+        async _gatherIndexData(sTender, completeData, index, hasPre) {
+            const tender = await this.ctx.service.tender.getCheckTender(sTender.tid);
+            const stages = await this._getValidStages(tender.id);
+            const stage = this.ctx.helper._.find(stages, {order: index});
+            await this._gatherStageData(completeData, tender, stage, hasPre);
+        }
+
+        async _gatherZoneData(sTender, completeData, zone) {
+            const tender = await this.ctx.service.tender.getCheckTender(sTender.tid);
+            const stages = await this._getTimeZoneStages(tender, zone);
+            await this._gatherStagesData(completeData, tender, stages);
+        }
+
+        async _gatherIndexZoneData(sTender, completeData, stageZone) {
+            const tender = await this.ctx.service.tender.getCheckTender(sTender.tid);
+            const stages = await this._getOrderZoneStages(tender, stageZone);
+            await this._gatherStagesData(completeData, tender, stages);
+        }
+
         async _gatherFinalData(sTender, completeData, hasPre) {
             const tender = await this.ctx.service.tender.getCheckTender(sTender.tid);
             const stages = await this._getValidStages(tender.id);
@@ -450,6 +496,12 @@ module.exports = app => {
                         case 'ledger':
                             await this._gatherLedgerData(tender, completeData);
                             break;
+                        case 'stage':
+                            await this._gatherIndexData(tender, completeData, gsCustom.stage, gsSetting.hasPre);
+                            break;
+                        case 'stage-zone':
+                            await this._gatherIndexZoneData(tender, completeData, gsCustom.stage_zone);
+                            break;
                     }
                     commonIndex++;
                 } else {
@@ -487,6 +539,7 @@ module.exports = app => {
             info.pay_account = tender.info.pay_account;
             info.deal_param.contractPriceWithoutZL = this.ctx.helper.sub(info.deal_param.contractPrice,
                 info.deal_param.zanLiePrice);
+            info.bid_info = tender.info.bid_info;
             return info;
         }
         async _getStageTenderInfo(stage, info) {
@@ -513,6 +566,20 @@ module.exports = app => {
             }
         }
 
+        async _getStagesTenderInfo(stages, info) {
+            const helper = this.ctx.helper;
+            for (const stage of stages) {
+                await this.ctx.service.stage.doCheckStage(stage);
+                await this.ctx.service.stage.checkStageGatherData(stage);
+
+                info.contract_tp = helper.add(info.contract_tp, stage.contract_tp);
+                info.qc_tp = helper.add(info.qc_tp, stage.qc_tp);
+
+                info.yf_tp = helper.add(info.yf_tp, stage.yf_tp);
+            }
+            info.gather_tp = helper.add(info.contract_tp, info.qc_tp);
+        }
+
         async _gatherMonthTenderInfo(sTender, index, month, hasPre) {
             const tender = await this.ctx.service.tender.getCheckTender(sTender.tid);
             const info = await this._getBaseTenderInfo(tender);
@@ -522,31 +589,28 @@ module.exports = app => {
             this.resultTenderInfo.push(info);
         }
 
-        async _gatherZoneTenderInfo(sTender, index, zone) {
-            const helper = this.ctx.helper;
+        async _gatherOrderTenderInfo(sTender, index, order, hasPre) {
             const tender = await this.ctx.service.tender.getCheckTender(sTender.tid);
             const info = await this._getBaseTenderInfo(tender);
+            const stages = await this._getValidStages(tender.id);
+            const stage = this.ctx.helper._.find(stages, {order: order});
+            await this._getStageTenderInfo(stage, info);
+            this.resultTenderInfo.push(info);
+        }
 
-            const times = zone.split(' - ');
-            if (times.length !== 2) throw '选择的汇总周期无效';
-            const beginTime = moment(times[0], 'YYYY-MM');
-            const endTime = moment(times[1], 'YYYY-MM');
-
-            const stages = await this._getValidStages(tender.id, true);
-            for (const stage of stages) {
-                const sTime = moment(stage.s_time, 'YYYY-MM');
-                if (sTime.isBetween(beginTime, endTime, null, '[]')) {
-                    await this.ctx.service.stage.doCheckStage(stage);
-                    await this.ctx.service.stage.checkStageGatherData(stage);
-
-                    info.contract_tp = helper.add(info.contract_tp, stage.contract_tp);
-                    info.qc_tp = helper.add(info.qc_tp, stage.qc_tp);
-
-                    info.yf_tp = helper.add(info.yf_tp, stage.yf_tp);
-                }
-            }
-            info.gather_tp = helper.add(info.contract_tp, info.qc_tp);
+        async _gatherZoneTenderInfo(sTender, index, zone) {
+            const tender = await this.ctx.service.tender.getCheckTender(sTender.tid);
+            const info = await this._getBaseTenderInfo(tender);
+            const stages = await this._getTimeZoneStages(tender, zone);
+            await this._getStagesTenderInfo(stages, info);
+            this.resultTenderInfo.push(info);
+        }
 
+        async _gatherOrderZoneTenderInfo(sTender, index, stageZone) {
+            const tender = await this.ctx.service.tender.getCheckTender(sTender.tid);
+            const info = await this._getBaseTenderInfo(tender);
+            const stages = await this._getOrderZoneStages(tender, stageZone);
+            await this._getStagesTenderInfo(stages, info);
             this.resultTenderInfo.push(info);
         }
 
@@ -606,6 +670,12 @@ module.exports = app => {
                         case 'ledger':
                             await this._gatherLedgerTenderInfo(tender, commonIndex);
                             break;
+                        case 'stage':
+                            await this._gatherOrderTenderInfo(tender, commonIndex, gsCustom.stage, gsSetting.hasPre);
+                            break;
+                        case 'stage-zone':
+                            await this._gatherOrderZoneTenderInfo(tender, commonIndex, gsCustom.stage_zone);
+                            break;
                     }
                     commonIndex++;
                 } else {
@@ -676,6 +746,29 @@ module.exports = app => {
             }
         }
 
+        async _gatherStagesPay(completeData, tender, stages) {
+            const helper = this.ctx.helper;
+            completeData.id = tender.id;
+            completeData.name = tender.name;
+
+            for (const stage of stages) {
+                await this.ctx.service.stage.doCheckStage(stage);
+
+                const dealPay = await this.ctx.service.stagePay.getStagePays(stage);
+                await this._checkStagePayCalc(tender, stage, dealPay);
+                for (const dp of dealPay) {
+                    dp.end_tp = helper.add(dp.pre_tp, dp.tp);
+                    this._gatherPayRecord(dp, function (gatherData, sourceData) {
+                        gatherData[completeData.prefix + 'id'] = tender.id;
+                        gatherData[completeData.prefix + 'name'] = tender.name;
+
+                        gatherData[completeData.prefix + 'tp'] = helper.add(gatherData[completeData.prefix + 'tp'], sourceData.tp);
+                        gatherData['s_' + 'tp'] = helper.add(gatherData['s_' + 'tp'], sourceData.tp);
+                    });
+                }
+            }
+        }
+
         async _gatherMonthStagePay(sTender, completeData, month, hasPre) {
             const tender = await this.ctx.service.tender.getCheckTender(sTender.tid);
             const stages = await this._getValidStages(tender.id);
@@ -683,38 +776,23 @@ module.exports = app => {
             await this._gatherStagePay(completeData, tender, stage, hasPre);
         }
 
-        async _gatherZoneStagePay(sTender, completeData, zone) {
-            const helper = this.ctx.helper;
-
+        async _gatherOrderStagePay(sTender, completeData, order, hasPre) {
             const tender = await this.ctx.service.tender.getCheckTender(sTender.tid);
-            completeData.id = tender.id;
-            completeData.name = tender.name;
             const stages = await this._getValidStages(tender.id);
+            const stage = this.ctx.helper._.find(stages, {order: order});
+            await this._gatherStagePay(completeData, tender, stage, hasPre);
+        }
 
-            const times = zone.split(' - ');
-            if (times.length !== 2) throw '选择的汇总周期无效';
-            const beginTime = moment(times[0], 'YYYY-MM');
-            const endTime = moment(times[1], 'YYYY-MM');
+        async _gatherZoneStagePay(sTender, completeData, zone) {
+            const tender = await this.ctx.service.tender.getCheckTender(sTender.tid);
+            const stages = await this._getTimeZoneStages(tender, zone);
+            await this._gatherStagesPay(completeData, tender, stages);
+        }
 
-            for (const stage of stages) {
-                const sTime = moment(stage.s_time, 'YYYY-MM');
-                if (sTime.isBetween(beginTime, endTime, null, '[]')) {
-                    await this.ctx.service.stage.doCheckStage(stage);
-
-                    const dealPay = await this.ctx.service.stagePay.getStagePays(stage);
-                    await this._checkStagePayCalc(tender, stage, dealPay);
-                    for (const dp of dealPay) {
-                        dp.end_tp = helper.add(dp.pre_tp, dp.tp);
-                        this._gatherPayRecord(dp, function (gatherData, sourceData) {
-                            gatherData[completeData.prefix + 'id'] = tender.id;
-                            gatherData[completeData.prefix + 'name'] = tender.name;
-
-                            gatherData[completeData.prefix + 'tp'] = helper.add(gatherData[completeData.prefix + 'tp'], sourceData.tp);
-                            gatherData['s_' + 'tp'] = helper.add(gatherData['s_' + 'tp'], sourceData.tp);
-                        });
-                    }
-                }
-            }
+        async _gatherOrderZoneStagePay(sTender, completeData, stageZone) {
+            const tender = await this.ctx.service.tender.getCheckTender(sTender.tid);
+            const stages = await this._getOrderZoneStages(tender, stageZone)
+            await this._gatherStagesPay(completeData, tender, stages);
         }
 
         async _gatherFinalStagePay(sTender, completeData, hasPre) {
@@ -757,6 +835,12 @@ module.exports = app => {
                         case 'checked-final':
                             await this._gatherCheckedFinalStagePay(tender, completeData, gsSetting.hasPre);
                             break;
+                        case 'stage':
+                            await this._gatherOrderStagePay(tender, completeData, gsCustom.stage, gsSetting.hasPre);
+                            break;
+                        case 'stage-zone':
+                            await this._gatherOrderZoneStagePay(tender, completeData, gsCustom.stage_zone);
+                            break;
                     }
                     commonIndex++;
                 }

+ 31 - 0
app/service/schedule.js

@@ -0,0 +1,31 @@
+'use strict';
+
+module.exports = app => {
+    class Schedule extends app.BaseService {
+        constructor(ctx) {
+            super(ctx);
+            this.tableName = 'schedule';
+        }
+
+        async saveMode(data) {
+            const transaction = await this.db.beginTransaction();
+            try {
+                const options = {
+                    where: {
+                        tid: this.ctx.tender.id,
+                    },
+                };
+                const updateData = {
+                    mode: data,
+                };
+                await transaction.update(this.tableName, updateData, options);
+                await transaction.commit();
+                return true;
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
+        }
+    }
+    return Schedule;
+};

+ 52 - 0
app/service/schedule_ledger.js

@@ -0,0 +1,52 @@
+'use strict';
+
+module.exports = app => {
+    class ScheduleLedger extends app.BaseService {
+        constructor(ctx) {
+            super(ctx);
+            this.tableName = 'schedule_ledger';
+        }
+
+        async saveLedger(datas) {
+            const transaction = await this.db.beginTransaction();
+            try {
+                const oldDatas = await this.getAllDataByCondition({
+                    where: { tid: this.ctx.tender.id },
+                });
+                const oldLids = this._.map(oldDatas, 'ledger_id');
+                const insertDatas = [];
+                for (const l of datas) {
+                    if (oldLids.indexOf(l) === -1) {
+                        const data = {
+                            tid: this.ctx.tender.id,
+                            ledger_id: l,
+                        };
+                        insertDatas.push(data);
+                    } else {
+                        this._.pull(oldLids, l);
+                    }
+                }
+                if (oldLids.length > 0) {
+                    for (const ol of oldLids) {
+                        await transaction.delete(this.tableName, { tid: this.ctx.tender.id, ledger_id: ol });
+                    }
+                }
+                if (insertDatas.length > 0) await transaction.insert(this.tableName, insertDatas);
+                // 判断是否已创建了形象进度表
+                const scheduleInfo = await this.ctx.service.schedule.getDataByCondition({ tid: this.ctx.tender.id });
+                if (!scheduleInfo) {
+                    const newSchedule = {
+                        tid: this.ctx.tender.id,
+                    };
+                    await transaction.insert(this.ctx.service.schedule.tableName, newSchedule);
+                }
+                await transaction.commit();
+                return true;
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
+        }
+    }
+    return ScheduleLedger;
+};

+ 65 - 0
app/service/schedule_ledger_month.js

@@ -0,0 +1,65 @@
+'use strict';
+
+module.exports = app => {
+    class ScheduleLedgerMonth extends app.BaseService {
+        constructor(ctx) {
+            super(ctx);
+            this.tableName = 'schedule_ledger_month';
+        }
+
+        async save(data) {
+            // 判断是添加,删除,还是修改
+            const transaction = await this.db.beginTransaction();
+            try {
+                const info = await this.getDataByCondition({ tid: this.ctx.tender.id, lid: data.lid, yearmonth: data.yearmonth });
+                if (info) {
+                    if (data.plan_gcl === null && data.plan_tp === null) {
+                        await transaction.delete(this.tableName, { id: info.id });
+                    } else {
+                        const updateData = {
+                            id: info.id,
+                            plan_gcl: data.plan_gcl,
+                            plan_tp: data.plan_tp,
+                        };
+                        await transaction.update(this.tableName, updateData);
+                    }
+                } else {
+                    const insertData = {
+                        tid: this.ctx.tender.id,
+                        lid: data.lid,
+                        yearmonth: data.yearmonth,
+                        plan_gcl: data.plan_gcl,
+                        plan_tp: data.plan_tp,
+                    };
+                    await transaction.insert(this.tableName, insertData);
+                }
+                // 重新计算本月、总 计划金额和计划工程量
+                await this.calcMonthPlan(transaction, this.ctx.tender.id, data.yearmonth);
+                await this.ctx.service.scheduleMonth.calcPlan(transaction, this.ctx.tender.id);
+                await transaction.commit();
+                return true;
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
+        }
+
+        async calcMonthPlan(transaction, tid, yearmonth) {
+            const sql = 'SELECT SUM(`plan_gcl`) as total_plan_gcl, SUM(`plan_tp`) as total_plan_tp FROM ?? WHERE tid = ? and yearmonth = ?';
+            const sqlParam = [this.tableName, tid, yearmonth];
+            const result = await transaction.queryOne(sql, sqlParam);
+            const updateData = {
+                plan_gcl: result.total_plan_gcl,
+                plan_tp: result.total_plan_tp,
+            };
+            const option = {
+                where: {
+                    tid,
+                    yearmonth,
+                },
+            };
+            return await transaction.update(this.ctx.service.scheduleMonth.tableName, updateData, option);
+        }
+    }
+    return ScheduleLedgerMonth;
+};

+ 67 - 0
app/service/schedule_month.js

@@ -0,0 +1,67 @@
+'use strict';
+
+module.exports = app => {
+    class ScheduleMonth extends app.BaseService {
+        constructor(ctx) {
+            super(ctx);
+            this.tableName = 'schedule_month';
+        }
+
+        async getLastPlanMonth() {
+            const sql = 'SELECT `yearmonth` FROM ?? WHERE `tid` = ? ORDER BY `yearmonth` DESC Limit 0,1';
+            const sqlParam = [this.tableName, this.ctx.tender.id];
+            return await this.db.query(sql, sqlParam);
+        }
+
+        async add(data) {
+            const transaction = await this.db.beginTransaction();
+            try {
+                const insertData = [];
+                for (const m of data) {
+                    insertData.push({ tid: this.ctx.tender.id, yearmonth: m });
+                }
+                if (insertData.length > 0) await transaction.insert(this.tableName, insertData);
+                await transaction.commit();
+                return true;
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
+        }
+
+        async del(data) {
+            const transaction = await this.db.beginTransaction();
+            try {
+                for (const m of data) {
+                    const delData = { tid: this.ctx.tender.id, yearmonth: m };
+                    await transaction.delete(this.tableName, delData);
+                    await transaction.delete(this.ctx.service.scheduleLedgerMonth.tableName, delData);
+                }
+                // 重新计算总 计划金额和计划工程量
+                await this.calcPlan(transaction, this.ctx.tender.id);
+                await transaction.commit();
+                return true;
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
+        }
+
+        async calcPlan(transaction, tid) {
+            const sql = 'SELECT SUM(`plan_gcl`) as total_plan_gcl, SUM(`plan_tp`) as total_plan_tp FROM ?? WHERE tid = ?';
+            const sqlParam = [this.tableName, tid];
+            const result = await transaction.queryOne(sql, sqlParam);
+            const updateData = {
+                plan_gcl: result.total_plan_gcl,
+                plan_tp: result.total_plan_tp,
+            };
+            const option = {
+                where: {
+                    tid,
+                },
+            };
+            return await transaction.update(this.ctx.service.schedule.tableName, updateData, option);
+        }
+    }
+    return ScheduleMonth;
+};

+ 54 - 15
app/service/shenpi_audit.js

@@ -39,24 +39,63 @@ module.exports = app => {
         }
 
         async addAudit(data) {
-            const insertData = {
-                tid: this.ctx.tender.id,
-                sp_type: data.code,
-                sp_status: data.status,
-                audit_id: data.audit_id,
-            };
-            const result = await this.db.insert(this.tableName, insertData);
-            return result.effectRows === 1;
+            const transaction = await this.db.beginTransaction();
+            try {
+                if (parseInt(data.code) === shenpiConst.sp_type.stage && parseInt(data.status) === shenpiConst.sp_status.gdspl) {
+                    const options = {
+                        where: {
+                            tid: this.ctx.tender.id,
+                            user_id: data.audit_id,
+                        },
+                    };
+                    const updateData = {
+                        status: 1,
+                    };
+                    await transaction.update(this.ctx.service.ledgerCooperation.tableName, updateData, options);
+                }
+                const insertData = {
+                    tid: this.ctx.tender.id,
+                    sp_type: data.code,
+                    sp_status: data.status,
+                    audit_id: data.audit_id,
+                };
+                const result = await transaction.insert(this.tableName, insertData);
+                await transaction.commit();
+                return result.effectRows === 1;
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
         }
 
         async removeAudit(data) {
-            const delData = {
-                tid: this.ctx.tender.id,
-                sp_type: data.code,
-                sp_status: data.status,
-                audit_id: data.audit_id,
-            };
-            return await this.db.delete(this.tableName, delData);
+            const transaction = await this.db.beginTransaction();
+            try {
+                const delData = {
+                    tid: this.ctx.tender.id,
+                    sp_type: data.code,
+                    sp_status: data.status,
+                    audit_id: data.audit_id,
+                };
+                await transaction.delete(this.tableName, delData);
+                if (parseInt(data.code) === shenpiConst.sp_type.stage && parseInt(data.status) === shenpiConst.sp_status.gdspl) {
+                    const options = {
+                        where: {
+                            tid: this.ctx.tender.id,
+                            user_id: data.audit_id,
+                        },
+                    };
+                    const updateData = {
+                        status: 0,
+                    };
+                    await transaction.update(this.ctx.service.ledgerCooperation.tableName, updateData, options);
+                }
+                await transaction.commit();
+                return true;
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
         }
 
         async copyAudit2otherTender(data) {

+ 17 - 1
app/service/stage_audit.js

@@ -82,7 +82,7 @@ module.exports = app => {
          */
         async getAuditors(stageId, times = 1, order_sort = 'asc') {
             const sql =
-                'SELECT la.`aid`, pa.`name`, pa.`company`, pa.`role`, pa.`mobile`, pa.`telephone`, la.`times`, la.`order`, la.`status`, la.`opinion`, la.`begin_time`, la.`end_time`, g.`sort` ' +
+                'SELECT la.`aid`, pa.`name`, pa.`company`, pa.`role`, pa.`mobile`, pa.`telephone`, pa.sign_path, la.`times`, la.`order`, la.`status`, la.`opinion`, la.`begin_time`, la.`end_time`, g.`sort` ' +
                 'FROM ?? AS la, ?? AS pa, (SELECT `aid`,(@i:=@i+1) as `sort` FROM ??, (select @i:=0) as it WHERE `sid` = ? AND `times` = ? GROUP BY `aid`) as g ' +
                 'WHERE la.`sid` = ? and la.`times` = ? and la.`aid` = pa.`id` and g.`aid` = la.`aid` order by la.`order` ' +
                 order_sort;
@@ -1306,6 +1306,22 @@ module.exports = app => {
                 throw err;
             }
         }
+
+        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 ' +
+                '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.opinion = auditor.opinion;
+                r.begin_time = auditor.begin_time;
+                r.end_time = auditor.end_time;
+            }
+            return result;
+        }
     }
 
     return StageAudit;

+ 7 - 11
app/service/stage_detail.js

@@ -135,17 +135,13 @@ module.exports = app => {
                     await this.db.update(this.tableName, newData);
                     return newData;
                 } else {
-                    data.code = org.code;
-                    data.name = org.name;
-                    data.unit = org.unit;
-                    data.unit_price = org.unit_price;
-                    data.uuid = org.uuid;
-                    data.tid = this.ctx.tender.id;
-                    data.sid = this.ctx.stage.id;
-                    data.times = this.ctx.stage.times;
-                    data.order = order;
-                    await this.db.insert(this.tableName, data);
-                    return data;
+                    const nd = this._.assign(org, data);
+                    delete nd.id;
+                    nd.times = this.ctx.stage.times;
+                    nd.order = order;
+
+                    await this.db.insert(this.tableName, nd);
+                    return nd;
                 }
             } else {
                 data.uuid = this.uuid.v4();

+ 0 - 1
app/service/stage_pay.js

@@ -269,7 +269,6 @@ module.exports = app => {
             const stagePays = stage.status === auditConst.stage.status.checked
                 ? await this.getStageLastestPays(stage.id)
                 : await this.getStagePays(stage);
-            console.log(stagePays);
             const sf = this._.find(stagePays, {ptype: payConst.payType.sf});
             return sf;
         }

+ 0 - 1
app/service/tender_info.js

@@ -307,7 +307,6 @@ module.exports = app => {
             }
 
             const [billsService] = await this._getLedgerService();
-            console.log(billsService);
 
             const changeBills = await this._reCalcLedger(tenderId, billsService, newDecimal, oldDecimal);
             const [changeSj, changeSb, changeSo] = await this._reCalcStageExtra(tenderId, newDecimal, oldDecimal);

+ 2 - 2
app/view/change/info.ejs

@@ -782,8 +782,8 @@
 <script>
     const changeUnits = JSON.parse('<%- JSON.stringify(changeUnits) %>');
     const precision = JSON.parse('<%- JSON.stringify(precision) %>');
-    const accountGroup = JSON.parse('<%- JSON.stringify(accountGroup) %>');
-    const accountList = JSON.parse('<%- JSON.stringify(accountList) %>');
+    const accountGroup = JSON.parse(unescape('<%- escape(JSON.stringify(accountGroup)) %>'));
+    const accountList = JSON.parse(unescape('<%- escape(JSON.stringify(accountList)) %>'));
     const shenpi_status = <%- ctx.tender.info.shenpi.change %>;
     const shenpiConst =  JSON.parse('<%- JSON.stringify(shenpiConst) %>');
     const changesUid =  <%- change.uid %>;

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

@@ -352,7 +352,19 @@
                 </div>
             </div>
             <div class="c-body col-8">
-                <div class="sjs-height-0" id="change-spread">
+                <div class="sjs-height-1" id="change-spread">
+                </div>
+                <!--下半部分-->
+                <div class="bcontent-wrap">
+                    <div class="bc-bar mb-1">
+                        <ul class="nav nav-tabs">
+                            <li class="nav-item">
+                                <a class="nav-link active" href="#">所属项目节</a>
+                            </li>
+                        </ul>
+                    </div>
+                    <div class="sp-wrap" id="xmj-spread">
+                    </div>
                 </div>
             </div>
         </div>
@@ -379,6 +391,7 @@
     const readOnly = <%- change.readOnly %>;
     const changeSpread = SpreadJsObj.createNewSpread($('#change-spread')[0]);
     const changeSpreadSheet = changeSpread.getActiveSheet();
+    const xmjSpread = SpreadJsObj.createNewSpread($('#xmj-spread')[0]);
     let changeList = JSON.parse(unescape('<%- escape(JSON.stringify(changeList)) %>'));
     const style1 = new GC.Spread.Sheets.Style();
     style1.locked = true;

+ 1 - 0
app/view/layout/layout.ejs

@@ -20,6 +20,7 @@
     <link rel="stylesheet" href="/public/css/datepicker/datepicker.min.css" type="text/css">
     <link rel="stylesheet" href="/public/css/toastr.css">
     <link rel="shortcut icon" href="/public/images/favicon.ico">
+    <link rel="stylesheet" href="/public/css/radar-animation.css">
     <!-- JS. -->
     <% for (const file of jsFiles) { %>
     <script type="text/javascript" src="<%- file %>"></script>

+ 6 - 1
app/view/ledger/explode.ejs

@@ -45,7 +45,7 @@
             </div>
             <div class="ml-auto">
                 <% if (tender.ledger_status !== auditConst.status.checked) { %>
-                    <a class="btn btn-sm btn-primary mr-1" href="#ledger-check-modal" data-toggle="modal" data-target="#ledger-check-modal">数据检查</a>
+                    <a class="btn btn-sm btn-primary mr-1" id="ledger-check2" href="javascript: void(0);">数据检查</a>
                 <% } %>
                 <% if (tender.ledger_status === auditConst.status.checkNo) { %>
                     <a href="#sp-list" data-type="hide" data-toggle="modal" data-target="#sp-list" class="btn btn-outline-warning btn-sm text-dark sp-list-btn">审批退回</a>
@@ -150,6 +150,8 @@
                         <div id="deal-bills-spread" class="sjs-sh-4">
                         </div>
                     </div>
+                    <div id="bills-tag" class="tab-pane">
+                    </div>
                     <div id="error-list" class="tab-pane">
                     </div>
                     <div id="check-list" class="tab-pane">
@@ -173,6 +175,9 @@
                     <a class="nav-link" content="#deal-bills" href="javascript: void(0);">签约清单</a>
                 </li>
                 <li class="nav-item">
+                    <a class="nav-link" content="#bills-tag" href="javascript: void(0);">书签</a>
+                </li>
+                <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">

+ 1 - 1
app/view/ledger/explode_modal.ejs

@@ -402,4 +402,4 @@
 <% include ../shares/import_excel_modal.ejs %>
 <% include ../shares/delete_hint_modal.ejs %>
 <% include ../shares/check_data_modal.ejs %>
-<% include ../shares/ledger_check_modal.ejs %>
+<% include ../shares/check_modal2.ejs %>

+ 1 - 1
app/view/ledger/gather.ejs

@@ -46,7 +46,7 @@
                         <div class="side-bar-1"></div>
                         <div class="sjs-sh-1">
                             <table class="table table-bordered">
-                                <tr><th>章节</th><th>章节名称</th><th>签约金额</th><th>台账金额</th><th>签约-台账</th></tr>
+                                <tr><th>章节</th><th>章节名称</th><th>签约金额</th><th>台账金额</th><th>台账-签约</th></tr>
                                 <tbody id="chapter-list"></tbody>
                             </table>
                         </div>

+ 3 - 3
app/view/profile/wechat.ejs

@@ -14,19 +14,19 @@
                     <!--已绑定手机-->
                     <div class="form-group">
                         <label>微信账号</label>
-                        <input class="form-control-plaintext" readonly="" value="<%= accountData.wx_name %>">
+                        <input class="form-control-plaintext" disabled="" value="<%= accountData.wx_name %>">
                         <a href="#remove-wechat" class="btn btn-sm btn-outline-primary" data-toggle="modal" data-target="#remove-wechat">解绑</a>
                     </div>
                     <% } else { %>
                     <div class="form-group">
                         <label>微信账号</label>
-                        <input class="form-control-plaintext" readonly="" value="未绑定">
+                        <input class="form-control-plaintext" disabled="" value="未绑定">
                     </div>
                     <% } %>
                     <!--二维码-->
                     <div class="form-group">
                         <label>扫码或搜索 关注服务号</label>
-                        <div><img class="w-50" src="/public/images/wechat.png"></div>
+                        <div><img class="w-100" src="/public/images/wechat.png"></div>
                     </div>
                     <% if (accountData.wx_openid !== null && accountData.wx_openid !== '') { %>
                     <!--短信通知开关(已有认证手机后显示)-->

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

@@ -114,7 +114,7 @@
                             </div>
                             <div class="panel" id="pnl_eSignature">
                                 <div class="panel-body">
-                                    <button class="btn btn-outline-primary btn-sm" type="button" data-toggle="modal" data-target="#eSignature" onclick="rptSignatureHelper.resetESignature(zTreeOprObj.currentRptPageRst, 'eSignatureBodyDiv')">
+                                    <button class="btn btn-outline-primary btn-sm" type="button" data-toggle="modal" data-target="#eSignature" onclick="rptSignatureHelper.resetESignature(zTreeOprObj.currentRptPageRst, 'eSignatureBodyDiv'); rptSignatureHelper.checkAndShowCrossTendersESignature();">
                                         <i class="fa fa-pencil"></i><br>
                                         电子签名
                                     </button>
@@ -423,7 +423,7 @@
                 if (TOP_TREE_NODES[tnIdx].pid !== -1) {
                     TOP_TREE_NODES[tnIdx].isParent = true;
                     TOP_TREE_NODES[tnIdx].nodeType = 1;
-                    individualNode.items.push(TOP_TREE_NODES[tnIdx]);
+                    individualNode.items.unshift(TOP_TREE_NODES[tnIdx]);
                     TOP_TREE_NODES.splice(tnIdx, 1);
                 }
             }
@@ -437,7 +437,7 @@
                 if (ORG_TOP_TREE_NODES[tnIdx].pid !== -1) {
                     ORG_TOP_TREE_NODES[tnIdx].isParent = true;
                     ORG_TOP_TREE_NODES[tnIdx].nodeType = 1;
-                    individualNodeOrg.items.push(ORG_TOP_TREE_NODES[tnIdx]);
+                    individualNodeOrg.items.unshift(ORG_TOP_TREE_NODES[tnIdx]);
                     ORG_TOP_TREE_NODES.splice(tnIdx, 1);
                 }
             }

+ 308 - 13
app/view/report/rpt_all_popup.ejs

@@ -204,7 +204,7 @@
             </div>
             <div class="modal-footer">
                 <!--
-                <button type="button" class="btn btn-sm btn-link float-left" data-dismiss="modal" data-toggle="modal" data-target="#batch-eSignature" id="batch-setupProjSignature" onclick="rptSignatureHelper.resetESignature(zTreeOprObj.currentRptPageRst, 'batch-eSignatureBodyDiv')">批量设置其他标段</button>
+                <button type="button" id="btn_cross_tender" class="btn btn-sm btn-link float-left" data-dismiss="modal" data-toggle="modal" data-target="#batch-eSignature" id="batch-setupProjSignature" onclick="rptSignatureHelper.resetESignature(zTreeOprObj.currentRptPageRst, 'batch-eSignatureBodyDiv'); buildTendersTree();">批量设置其他标段</button>
 
                 <button type="button" class="btn btn-sm btn-link float-left" data-toggle="modal" data-target="#batch-eSignature" id="hidden_show_batch_eSignature" style="display:none"></button>
                 <button type="button" class="btn btn-sm btn-link float-left" data-dismiss="modal" onclick="setTimeout(function(){$('#hidden_show_batch_eSignature').trigger('click');}, 50);">批量设置其他标段</button>
@@ -225,15 +225,20 @@
                 </button>
             </div>
             <div class="modal-body">
-                <div class="row">
-                    <div class="col-6" id="batch-ProjectsBodyDiv"></div>
-                    <div class="col-6" id="batch-eSignatureBodyDiv"></div>
+                <div class="modal-height-300">
+                    <div class="row">
+                        <div class="col-6">
+                            <table class="table table-hover table-bordered" id="batch_tenders_individual">
+                            </table>
+                        </div>
+                        <div class="col-6" id="batch-eSignatureBodyDiv"></div>
+                    </div>
                 </div>
                 <div class="alert alert-warning">批量操作会覆盖已设置好的电子签名,请谨慎操作。</div>
             </div>
             <div class="modal-footer">
                 <button type="button" class="btn btn-sm btn-secondary" data-dismiss="modal">取消</button>
-                <a href="" class="btn btn-sm btn-primary">确定</a>
+                <a href="javascript:void(0);" onclick="rptSignatureHelper.setupAfterSelectMultiTenders(SELECTED_TENDERS)" class="btn btn-sm btn-primary" data-dismiss="modal">确定</a>
             </div>
         </div>
     </div>
@@ -354,7 +359,7 @@
                         <h5>已选标段 </h5>
                         <div class="modal-height-300" id="gather-result-spread">
                         </div>
-                        <div class="mt-1" id="gather-by-month" style="width: 60%">
+                        <div class="mt-1" id="gather-by-month" style="width: 60%" name="gather-type">
                             <div class="input-group input-group-sm">
                                 <div class="input-group-prepend">
                                     <span class="input-group-text">汇总年月</span>
@@ -362,7 +367,7 @@
                                 <input id="gather-month" class="datepicker-here form-control form-control-sm" auto-close="true" autocomplete="off" placeholder="点击选择年月" data-view="months" data-min-view="months" data-date-format="yyyy-MM" data-language="zh" type="text" autocomplete="off">
                             </div>
                         </div>
-                        <div id="gather-by-zone">
+                        <div id="gather-by-zone" name="gather-type">
                             <div class="input-group input-group-sm">
                                 <div class="input-group-prepend">
                                     <span class="input-group-text">汇总周期</span>
@@ -370,6 +375,32 @@
                                 <input id="gather-zone" class="datepicker-here form-control mt-0" placeholder="点击选择周期" data-range="true" data-multiple-dates-separator=" - "  data-min-view="months" data-view="months" data-date-format="yyyy-MM" data-language="zh" type="text" autocomplete="off">
                             </div>
                         </div>
+                        <div id="gather-by-stage" name="gather-type">
+                            <div class="input-group input-group-sm">
+                                <div class="input-group-prepend">
+                                    <span class="input-group-text">选择期</span>
+                                </div>
+                                <select class="form-control" id="gather-stage">
+                                    <option>第1期</option>
+                                </select>
+                            </div>
+                        </div>
+                        <div id="gather-by-stage-zone" name="gather-type">
+                            <div class="input-group input-group-sm">
+                                <div class="input-group-prepend">
+                                    <span class="input-group-text">汇总</span>
+                                </div>
+                                <select class="form-control" id="gather-stage-begin">
+                                    <option>第1期</option>
+                                </select>
+                                <div class="input-group-prepend">
+                                    <span class="input-group-text">至</span>
+                                </div>
+                                <select class="form-control" id="gather-stage-end">
+                                    <option>第5期</option>
+                                </select>
+                            </div>
+                        </div>
                     </div>
                     <div class="text-danger text-center ml-3" id="gather-hint">我是提示呀</div>
                 </div>
@@ -459,9 +490,211 @@
     zTreeOprObj.iniFontCfgDom(CUST_CFG);
     buildCustRptCommon('report_cust_group_common', ORG_TOP_TREE_NODES[1], CUST_TREE_NODES.common, 'true');
     buildCustRptCommon('report_cust_group_individual', ORG_TOP_TREE_NODES[0], CUST_TREE_NODES.customize, 'false');
+    let SELECTED_TENDERS = [];
 
     function buildTendersTree() {
-        //
+        let sortedCat = [];
+        SELECTED_TENDERS = [];
+        for (const cat of category) {
+            if (cat.level > 0) {
+                sortedCat.push(cat);
+            }
+        }
+        let treeCache = [];
+        //1. 选择用户已设置的分类
+        if (sortedCat.length > 0) {
+            sortedCat.sort(function(i1, i2){
+                return i1.level - i2.level;
+            });
+            const _createItems = function (parentItems, grpLv) {
+                if (grpLv < sortedCat.length) {
+                    let cat = sortedCat[grpLv];
+                    for (const nv of cat.value) {
+                        let item = {
+                            name: nv.value,
+                            id : nv.id,
+                            cid : nv.cid,
+                            items: [],
+                            isParent: true
+                        };
+                        parentItems.push(item);
+                        _createItems(item.items, grpLv + 1);
+                    }
+                }
+            }
+            _createItems(treeCache, 0);
+        }
+        //2. 建立treeNodes
+        let hasCat = false;
+        for (const tender of tenders) {
+            if (tender.category && tender.category.length > 0) {
+                hasCat = true;
+                break;
+            }
+        }
+        //3. 把标段挂上去
+        let tmpCacheArr = []; //这个临时用来储存相应标段节点挂接信息
+        let _putTender = function (catLv, tender, parentItems) {
+            if (tender.lastStage !== undefined && tender.lastStage !== null) {
+                if (hasCat) {
+                    let isSorted = false;
+                    for (let sCat of sortedCat) {
+                        if (sCat.id === tender.category[catLv].cid && sCat.level > 0) {
+                            isSorted = true;
+                            break;
+                        }
+                    }
+                    if (isSorted) {
+                        for (const item of parentItems) {
+                            if (item.cid === tender.category[catLv].cid && item.id === tender.category[catLv].value) {
+                                if (item.items.length > 0) {
+                                    //递归循环
+                                    _putTender(catLv + 1, tender, item.items);
+                                } else {
+                                    //就是这个了
+                                    let tdItem = {
+                                        name: tender.name,
+                                        id : -1,
+                                        cid : -1,
+                                        tender_id: tender.id,
+                                        last_stage: tender.lastStage.order,
+                                        last_stage_id: tender.lastStage.id,
+                                        report_id: zTreeOprObj.currentNode.refId,
+                                        signature: '临时',
+                                        items: [],
+                                        isParent: false
+                                    }; //标段节点
+                                    //
+                                    tmpCacheArr.push([item, tdItem]);
+                                }
+                            }
+                        }
+                    }
+                } else {
+                    // 没有设置category
+                    let tdItem = {
+                        name: tender.name,
+                        id : -1,
+                        cid : -1,
+                        tender_id: tender.id,
+                        last_stage: tender.lastStage.order,
+                        last_stage_id: tender.lastStage.id,
+                        report_id: zTreeOprObj.currentNode.refId,
+                        signature: '临时',
+                        items: [],
+                        isParent: false
+                    }; //标段节点
+                    //
+                    tmpCacheArr.push([null, tdItem]);
+                }
+            }
+        }
+        for (const tender of tenders) {
+            _putTender(0, tender, treeCache);
+        }
+        if (hasCat) {
+            for (const pairItem of tmpCacheArr) {
+                pairItem[0].items.push(pairItem[1]);
+            }
+        }
+        //4. 剔除空items的项
+        if (hasCat) {
+            let _removeEmptyItems = function (parentItem) {
+                let rst = false;
+                if (parentItem.items.length === 0) {
+                    if (parentItem.isParent) {
+                        rst = true;
+                    }
+                } else {
+                    for (let idx = parentItem.items.length - 1; idx >= 0; idx--) {
+                        if (_removeEmptyItems(parentItem.items[idx])) {
+                            parentItem.items.splice(idx, 1);
+                        }
+                    }
+                    rst = parentItem.items.length === 0;
+                }
+                return rst;
+            }
+            for (let idx = treeCache.length - 1; idx >= 0; idx--) {
+                if (hasCat) {
+                    if (_removeEmptyItems(treeCache[idx])) {
+                        treeCache.splice(idx, 1);
+                    }
+                }
+            }
+        }
+        //console.log(treeCache);
+        //还要请求各标段的原签名数据
+        let params = {};
+        params.selectedTenders = [];
+        let _getTenderRelatedInfos = function(parentItem, rstArr) {
+            if (parentItem.items.length === 0) {
+                if (!parentItem.isParent) {
+                    rstArr.push([parentItem.tender_id, parentItem.last_stage_id, parentItem.report_id]);
+                }
+            } else {
+                for (let idx = parentItem.items.length - 1; idx >= 0; idx--) {
+                    _getTenderRelatedInfos(parentItem.items[idx], rstArr);
+                }
+            }
+        };
+        let _addHint = function(parentItem, relArr) {
+            if (parentItem.items.length === 0) {
+                if (!parentItem.isParent) {
+                    for (const rel of relArr) {
+                        if (rel.tender_id === parentItem.tender_id && rel.sid === parentItem.last_stage_id && rel.rpt_id === parentItem.report_id) {
+                            let hintStr = '';
+                            if (rel.rel_content instanceof Array) {
+                                for (let idx = 0; idx < rel.rel_content.length; idx++) {
+                                    const dtlRel = rel.rel_content[idx];
+                                    if (idx > 0) {
+                                        hintStr = hintStr + '、';
+                                    }
+                                    hintStr = hintStr + '(' + dtlRel.signature_name + ')' + dtlRel.user_name;
+                                }
+                            }
+                            parentItem.signature = hintStr;
+                            break;
+                        }
+                    }
+                }
+            } else {
+                for (let idx = parentItem.items.length - 1; idx >= 0; idx--) {
+                    _getTenderRelatedInfos(parentItem.items[idx], relArr);
+                }
+            }
+        };
+        if (hasCat) {
+            for (let idx = treeCache.length - 1; idx >= 0; idx--) {
+                _getTenderRelatedInfos(treeCache[idx], params.selectedTenders);
+            }
+        } else {
+            for (let idx = tmpCacheArr.length - 1; idx >= 0; idx--) {
+                _getTenderRelatedInfos(tmpCacheArr[idx][1], params.selectedTenders);
+                treeCache.push(tmpCacheArr[idx][1]);
+            }
+        }
+        $.bootstrapLoading.start();
+        CommonAjax.postXsrfEx("/tender/report_api/getMultiRoleRelationships", params, 30000, true, getCookie('csrfToken'),
+            function(result){
+                $.bootstrapLoading.end();
+                let relArr = result.data;
+                for (const rel of relArr) {
+                    rel.rel_content = JSON.parse(rel.rel_content);
+                }
+                console.log(relArr);
+                //5. 加hint
+                for (let idx = treeCache.length - 1; idx >= 0; idx--) {
+                    _addHint(treeCache[idx], relArr);
+                }
+                //6. 建树
+                _buildTenderTree('batch_tenders_individual', treeCache);
+            }, function(err){
+                $.bootstrapLoading.end();
+            }, function(ex){
+                $.bootstrapLoading.end();
+            }
+        );
     }
 
     function searchAccount() {
@@ -470,6 +703,49 @@
         }
     }
 
+    function _buildTenderTree(tbDomId, tendersArr) {
+        let tbDom = $("#" + tbDomId);
+        tbDom.empty();
+        let domStrs = [];
+        domStrs.push('<thead><tr><th>名称</th><th width="50">计量期</th><th width="40">签名</th><th width="40">选择</th></tr></thead>');
+        domStrs.push('<tbody>');
+        const _createRow = function (rowItem, lv) {
+            let tdClassStr = 'in-' + lv;
+            if (rowItem.isParent) {
+                //tbDom.append('<tr><td class="' + tdClassStr + '"><i class="fa fa-folder-o"></i>&nbsp;&nbsp;' + rowItem.name + '</td><td></td><td></td><td></td></tr>');
+                domStrs.push('<tr><td class="' + tdClassStr + '"><i class="fa fa-folder-o"></i>&nbsp;&nbsp;' + rowItem.name + '</td><td></td><td></td><td></td></tr>');
+            } else {
+                let hrefStr = '/tender/' + rowItem.tender_id;
+                //<a href="biaoduan-panel.html" target="_blank">WWUJ-1</a>
+                let lastStgStr = '';
+                if (rowItem.last_stage === -1) {
+                    lastStgStr = '台账';
+                } else {
+                    lastStgStr = '第' + rowItem.last_stage + '期';
+                }
+                //let chgStr = 'changeCrossTender(this, SELECTED_TENDERS,' + rowItem.tender_id + ', ' + rowItem.last_stage_id + ', ' + rowItem.report_id + ')';
+                if (rowItem.signature !== undefined && rowItem.signature !== null && rowItem.signature !== '') {
+                    let hintStr = '<i class="fa fa-exclamation-circle text-primary" data-container="body" data-toggle="tooltip" data-placement="bottom" data-original-title="' + rowItem.signature + '"></i>';
+                    domStrs.push('<tr><td class="' + tdClassStr + '"><a href="' + hrefStr + '" target="_blank">' + rowItem.name + '</a></td><td>' + lastStgStr + '</td><td>' + hintStr + '</td><td><input type="checkbox" onclick="changeCrossTender(this, SELECTED_TENDERS, ' + rowItem.tender_id + ', ' + rowItem.last_stage_id + ', ' + rowItem.report_id + ')' + '"></td></tr>');
+                } else {
+                    domStrs.push('<tr><td class="' + tdClassStr + '"><a href="' + hrefStr + '" target="_blank">' + rowItem.name + '</a></td><td>' + lastStgStr + '</td><td></td><td><input type="checkbox" onclick="changeCrossTender(this, SELECTED_TENDERS, ' + rowItem.tender_id + ', ' + rowItem.last_stage_id + ', ' + rowItem.report_id + ')' + '"></td></tr>');
+                }
+            }
+            if (rowItem.items && rowItem.items.length > 0) {
+                for (const subItem of rowItem.items) {
+                    _createRow(subItem, lv + 1);
+                }
+            }
+        }
+        let grpLv = 1;
+        for (const tItem of tendersArr) {
+            _createRow(tItem, grpLv);
+        }
+        domStrs.push('</tbody>');
+        tbDom.append(domStrs.join(''));
+
+    }
+
     function buildCustRptCommon(tbDomId, topTreeNode, checkingArr, isCommonStr) {
         let tbDom = $("#" + tbDomId);
         tbDom.empty();
@@ -491,21 +767,26 @@
             if (rptItem.nodeType === 1) {
                 let amt = _countAvailableTpls(rptItem);
                 if (amt > 0) {
-                    let classStr = '';
+                    let padding_leftStr = '';
+                    // classStr = '',
                     if (level > 0) {
-                        classStr = 'pl-' + (level + 3);
+                        // classStr = 'pl-' + (level + 3);
+                        let rem = 1.5 * level;
+                        padding_leftStr = 'padding-left: ' + rem + 'rem!important'; //不受层数限制
                     }
                     if (needChk) {
                         let chkName = parentItem.name + FOLDER_SEPERATER + rptItem.name;
                         let checkedStr = (checkingArr.indexOf(chkName) >= 0) ? ' checked' : '';
                         let sIdStr = parentNodeIdStr + '_sub_' + thisItemSeq;
-                        tbDom.append('<tr><td class="' + classStr + '">' + rptItem.name + '</td><td>' + amt + '</td><td><input id="' + sIdStr + '" onchange="changeFolder(this, ' + isCommonStr + ', \'' + parentNodeIdStr + '\' )" hiddenval="' + chkName + '" type="checkbox"' + checkedStr + '></td></tr>');
+                        // tbDom.append('<tr><td class="' + classStr + '">' + rptItem.name + '</td><td>' + amt + '</td><td><input id="' + sIdStr + '" onchange="changeFolder(this, ' + isCommonStr + ', \'' + parentNodeIdStr + '\' )" hiddenval="' + chkName + '" type="checkbox"' + checkedStr + '></td></tr>');
+                        tbDom.append('<tr><td style="' + padding_leftStr + '">' + rptItem.name + '</td><td>' + amt + '</td><td><input id="' + sIdStr + '" onchange="changeFolder(this, ' + isCommonStr + ', \'' + parentNodeIdStr + '\' )" hiddenval="' + chkName + '" type="checkbox"' + checkedStr + '></td></tr>');
                     } else {
-                        tbDom.append('<tr><td class="' + classStr + '">' + rptItem.name + '</td><td>' + amt + '</td><td></td></tr>');
+                        // tbDom.append('<tr><td class="' + classStr + '">' + rptItem.name + '</td><td>' + amt + '</td><td></td></tr>');
+                        tbDom.append('<tr><td style="' + padding_leftStr + '">' + rptItem.name + '</td><td>' + amt + '</td><td></td></tr>');
                     }
                     if (rptItem.items && rptItem.items.length > 0) {
                         for (const subItem of rptItem.items) {
-                            _pushRptLine(rptItem, subItem, level + 1, false, '');
+                            _pushRptLine(rptItem, subItem, level + 1, true, '');
                         }
                     }
                 }
@@ -568,6 +849,20 @@
         buildCustRptCommon('report_cust_group_individual', ORG_TOP_TREE_NODES[0], CUST_TREE_NODES.customize, 'false');
     }
 
+    function changeCrossTender(dom, rstArr, tenderId, stageId, reportId) {
+        if (dom.checked) {
+            let item = [tenderId, stageId, reportId];
+            rstArr.push(item);
+        } else {
+            for (let idx = rstArr.length - 1; idx >= 0; idx--) {
+                if (rstArr[idx][0] === tenderId && rstArr[idx][1] === stageId && rstArr[idx][2] === reportId) {
+                    rstArr.splice(idx, 1); //删除当前标段
+                    //这里不break,防止一些冗余数据
+                }
+            }
+        }
+    }
+
     function changeFolder(dom, isCommon, parentIdStr) {
         let prop = null;
         if (isCommon) {

+ 1 - 1
app/view/revise/info.ejs

@@ -45,7 +45,7 @@
             <% } %>
             <div class="ml-auto">
                 <% if (revise.status !== audit.status.checked) { %>
-                    <a class="btn btn-sm btn-primary mr-1" href="#ledger-check-modal" data-toggle="modal" data-target="#ledger-check-modal">数据检查</a>
+                    <a class="btn btn-sm btn-primary mr-1" id="ledger-check2" href="javascript: void(0);">数据检查</a>
                 <% } %>
                 <% if (revise.status === audit.status.uncheck && revise.uid === ctx.session.sessionUser.accountId) { %>
                 <a href="#sub-sp" data-toggle="modal" data-target="#sub-sp" class="btn btn-primary btn-sm mr-1">上报审批</a>

+ 1 - 1
app/view/revise/info_modal.ejs

@@ -720,7 +720,7 @@
     <% include ../shares/import_excel_modal.ejs %>
     <% include ../shares/delete_hint_modal.ejs %>
     <% include ../shares/check_data_modal.ejs %>
-    <% include ../shares/ledger_check_modal.ejs %>
+    <% include ../shares/check_modal2.ejs %>
     <% if(ctx.session.sessionUser.accountId === revise.uid && (revise.status === auditConst.status.uncheck || revise.status === auditConst.status.checkNo)) { %>
     <script>
         const cur_uid = '<%- ctx.session.sessionUser.accountId %>';

+ 6 - 6
app/view/schedule/index.ejs

@@ -4,7 +4,7 @@
         <div class="title-main d-flex">
             <% include ../tender/tender_sub_mini_menu.ejs %>
             <h2>
-                计划至至 2020年12月
+                <% if (planMonth) { %>计划至 <%- planMonth.split('-')[0] %>年<%- parseInt(planMonth.split('-')[1]) %>月 <% } %>
             </h2>
             <div class="ml-auto">
                 <a href="/tender/<%- ctx.tender.id %>/schedule/ledger" class="btn btn-sm btn-outline-primary">进度台帐</a>
@@ -295,11 +295,11 @@
                 精度:3,
             }
         ];
-        const spread = new GC.Spread.Sheets.Workbook($('#option-spread1')[0], {
-            sheetCount: 1
-        });
-        spread.getActiveSheet().setDataSource(data);
-        spread.options.tabStripVisible = false;
+        // const spread = new GC.Spread.Sheets.Workbook($('#option-spread1')[0], {
+        //     sheetCount: 1
+        // });
+        // spread.getActiveSheet().setDataSource(data);
+        // spread.options.tabStripVisible = false;
     })
 </script>
 <script src="/public/js/sub_menu.js"></script>

+ 16 - 24
app/view/schedule/ledger.ejs

@@ -7,23 +7,33 @@
                 <div class="d-inline-block">
                     <a class="btn btn-sm btn-light">
                         <div class="custom-control custom-checkbox">
-                            <input type="checkbox" class="custom-control-input" id="customCheckDisabled" checked="">
-                            <label class="custom-control-label text-primary" for="customCheckDisabled">自动选择同级项</label>
+                            <input type="checkbox" class="custom-control-input" id="select_siblings" checked>
+                            <label class="custom-control-label text-primary" for="select_siblings">自动选择同级项</label>
                         </div>
                     </a>
                 </div>
                 <div class="d-inline-block">
                     <a class="btn btn-sm btn-light">
                         <div class="custom-control custom-checkbox">
-                            <input type="checkbox" class="custom-control-input" id="customCheckDisabled2">
-                            <label class="custom-control-label text-primary" for="customCheckDisabled2">跨级选择同级项</label>
+                            <input type="checkbox" class="custom-control-input" id="select_other_siblings">
+                            <label class="custom-control-label text-primary" for="select_other_siblings">跨级选择同级项</label>
+                        </div>
+                    </a>
+                </div>
+                <div class="d-inline-block">
+                    <a class="btn btn-sm btn-light">
+                        <div class="custom-control custom-checkbox">
+                            <input type="checkbox" class="custom-control-input" id="select_children" checked>
+                            <label class="custom-control-label text-primary" for="select_children">自动选择子项</label>
                         </div>
                     </a>
                 </div>
             </div>
+            <% if (tender.user_id === ctx.session.sessionUser.accountId) { %>
             <div class="ml-auto">
-                <a href="#add-qi" data-toggle="modal" data-target="#add-qi" class="btn btn-primary btn-sm pull-right">确认提交<div></div></a>
+                <button type="button" id="ledger_submit" class="btn btn-primary btn-sm pull-right">确认提交</button>
             </div>
+            <% } %>
         </div>
     </div>
     <div class="content-wrap">
@@ -36,28 +46,10 @@
         </div>
     </div>
 </div>
-<script src="/public/js/sub_menu.js"></script>
 <script>
     const tender = JSON.parse('<%- JSON.stringify(tender) %>');
     const tenderInfo = JSON.parse(unescape('<%- escape(JSON.stringify(tenderInfo)) %>'));
     const thousandth = <%- ctx.tender.info.display.thousandth %>;
     const measureType = JSON.parse('<%- JSON.stringify(measureType) %>');
-</script>
-<script>
-    $.subMenu({
-        menu: '#sub-menu', miniMenu: '#sub-mini-menu', miniMenuList: '#mini-menu-list',
-        toMenu: '#to-menu', toMiniMenu: '#to-mini-menu',
-        key: 'menu.1.0.0',
-        miniHint: '#sub-mini-hint', hintKey: 'menu.hint.1.0.1',
-        callback: function (info) {
-            if (info.mini) {
-                $('.panel-title').addClass('fluid');
-                $('#sub-menu').removeClass('panel-sidebar');
-            } else {
-                $('.panel-title').removeClass('fluid');
-                $('#sub-menu').addClass('panel-sidebar');
-            }
-            autoFlashHeight();
-        }
-    });
+    const selectedLedgerList = JSON.parse('<%- JSON.stringify(scheduleLedgerList) %>');
 </script>

+ 0 - 21
app/view/schedule/ledger_modal.ejs

@@ -1,21 +0,0 @@
-<!--首次使用提示-->
-<div class="modal fade" id="first" data-backdrop="static">
-    <div class="modal-dialog" role="document">
-        <div class="modal-content">
-            <div class="modal-header">
-                <h5 class="modal-title">进度台帐</h5>
-            </div>
-            <div class="modal-body">
-                <h5>首次使用形象进度需要进行进度台帐初始化设置</h5>
-            </div>
-            <div class="modal-footer">
-                <button type="button" class="btn btn-sm btn-primary" data-dismiss="modal">开始设置</button>
-            </div>
-        </div>
-    </div>
-</div>
-<script type="text/javascript">
-    $(function () {
-        // $('#first').modal('show');
-    })
-</script>

+ 35 - 0
app/view/schedule/modal.ejs

@@ -0,0 +1,35 @@
+<!--首次使用提示-->
+<div class="modal fade" id="first" data-backdrop="static">
+    <div class="modal-dialog" role="document">
+        <div class="modal-content">
+            <div class="modal-header">
+                <h5 class="modal-title">进度台帐</h5>
+            </div>
+            <% if (tender.user_id !== ctx.session.sessionUser.accountId) { %>
+            <div class="modal-body">
+                <h5>未设置进度台账无法查看</h5>
+            </div>
+            <div class="modal-footer">
+                <a href="/tender/<%- ctx.tender.id %>" class="btn btn-sm btn-primary">回到标段概况</a>
+            </div>
+            <% } else { %>
+            <div class="modal-body">
+                <h5>首次使用形象进度需要进行进度台帐初始化设置</h5>
+            </div>
+            <div class="modal-footer">
+                <a href="/tender/<%- ctx.tender.id %>/schedule/ledger" class="btn btn-sm btn-primary">开始设置</a>
+            </div>
+            <% } %>
+        </div>
+    </div>
+</div>
+<script>
+    const selectedLedgerList = JSON.parse('<%- JSON.stringify(scheduleLedgerList) %>');
+</script>
+<script type="text/javascript">
+    $(function () {
+        if (selectedLedgerList.length === 0) {
+            $('#first').modal('show');
+        }
+    })
+</script>

+ 32 - 0
app/view/schedule/plan.ejs

@@ -0,0 +1,32 @@
+<% include ../tender/tender_sub_menu.ejs %>
+<div class="panel-content">
+    <div class="panel-title">
+        <div class="title-main d-flex">
+            <% include ../tender/tender_sub_mini_menu.ejs %>
+            <h2>
+                <% if (planMonth) { %>计划至 <%- planMonth.split('-')[0] %>年<%- parseInt(planMonth.split('-')[1]) %>月 <% } %>
+            </h2>
+            <div class="ml-auto">
+                <a href="#mode" data-toggle="modal" data-target="#mode" class="btn btn-sm btn-outline-primary">计算方式</a>
+                <a href="#edit-plan" data-toggle="modal" data-target="#edit-plan" class="btn btn-sm btn-outline-primary">管理计划</a>
+                <a href="#add-plan" data-toggle="modal" data-target="#add-plan" class="btn btn-sm btn-primary">新增计划</a>
+            </div>
+        </div>
+    </div>
+    <div class="content-wrap">
+        <div class="c-body">
+            <div class="sjs-height-0" style="overflow: auto;" id="ledger-spread">
+            </div>
+        </div>
+    </div>
+</div>
+<script>
+    const tender = JSON.parse('<%- JSON.stringify(tender) %>');
+    const tenderInfo = JSON.parse(unescape('<%- escape(JSON.stringify(tenderInfo)) %>'));
+    const thousandth = <%- ctx.tender.info.display.thousandth %>;
+    const measureType = JSON.parse('<%- JSON.stringify(measureType) %>');
+    const schedule = JSON.parse('<%- JSON.stringify(schedule) %>');
+    const scheduleMonth = JSON.parse('<%- JSON.stringify(scheduleMonth) %>');
+    const monthList = _.map(scheduleMonth, 'yearmonth');
+    const mode = JSON.parse('<%- JSON.stringify(mode) %>');
+</script>

+ 93 - 0
app/view/schedule/plan_modal.ejs

@@ -0,0 +1,93 @@
+<!--新增计划-->
+<div class="modal fade" id="add-plan" data-backdrop="static">
+    <div class="modal-dialog" role="document">
+        <div class="modal-content">
+            <div class="modal-header">
+                <h5 class="modal-title">新增计划</h5>
+            </div>
+            <div class="modal-body">
+                <div class="form-group">
+                    <label>计划周期</label>
+                    <input id="month-range" readonly class="datepicker-here form-control" placeholder="点击选择时间" data-view="months" data-min-view="months" data-date-format="yyyy-MM" data-range="true" data-multiple-dates-separator=" ~ " data-language="zh" type="text">
+                </div>
+                <div id="add-month-error-list" style="display: none">
+                </div>
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-sm btn-secondary" data-dismiss="modal">取消</button>
+                <button type="button" class="btn btn-sm btn-primary" id="add-month">确认新增</button>
+            </div>
+        </div>
+    </div>
+</div>
+<!--管理计划-->
+<div class="modal fade" id="edit-plan" data-backdrop="static">
+    <div class="modal-dialog" role="document">
+        <div class="modal-content">
+            <div class="modal-header">
+                <h5 class="modal-title">管理计划</h5>
+            </div>
+            <div class="modal-body">
+                <table class="table table-bordered table-hover" id="month-table">
+                    <tr><th>计划月</th><th width="100">删除</th></tr>
+                    <% for (const m of scheduleMonth) { %>
+                    <tr>
+                        <td><%- m.yearmonth %></td>
+                        <td><% if (m.sj_gcl === null && m.sj_tp === null) { %><label></label><input type="checkbox" value="<%- m.id %>"><% } %></td>
+                    </tr>
+                    <% } %>
+                </table>
+                <div class="alert alert-danger" style="display: none">确认删除<span id="del-month-list"></span>计划进度?删除后,数据无法恢复,请谨慎操作。</div>
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-sm btn-secondary" data-dismiss="modal">取消</button>
+                <button type="button" class="btn btn-sm btn-danger" disabled id="del-month" >确认删除</button>
+            </div>
+        </div>
+    </div>
+</div>
+<!--选择计算方式-->
+<div class="modal fade" id="mode" data-backdrop="static">
+    <div class="modal-dialog" role="document">
+        <div class="modal-content">
+            <div class="modal-header">
+                <h5 class="modal-title">选择计划进度计算方式</h5>
+            </div>
+            <div class="modal-body">
+                <div class="row mb-2">
+                    <div class="col-6">
+                        <div class="card">
+                            <div class="card-body">
+                                <h5 class="card-title"><i class="fa fa-bookmark"></i> 金额</h5>
+                                <p class="card-text">填写计划金额,通过经济指标反算工程量</p>
+                                <% if (schedule && schedule.mode === mode.tp) { %>
+                                <button class="btn btn-primary btn-sm disabled mode-select" disabled data-mode="<%- mode.tp %>">当前</button>
+                                <% } else { %>
+                                <button class="btn btn-sm btn-primary mode-select" data-mode="<%- mode.tp %>">选择</button>
+                                <% } %>
+                            </div>
+                        </div>
+                    </div>
+                    <div class="col-6">
+                        <div class="card">
+                            <div class="card-body">
+                                <h5 class="card-title"><i class="fa fa-bookmark"></i> 工程量</h5>
+                                <p class="card-text">填写工程量,金额=工程量×经济指标</p>
+                                <% if (schedule && schedule.mode === mode.gcl) { %>
+                                <button class="btn btn-primary btn-sm disabled mode-select" disabled data-mode="<%- mode.gcl %>">当前</button>
+                                <% } else { %>
+                                <button class="btn btn-sm btn-primary mode-select" data-mode="<%- mode.gcl %>">选择</button>
+                                <% } %>
+                            </div>
+                        </div>
+                    </div>
+                </div>
+                <div class="alert alert-danger" id="mode-tips" style=" <% if (schedule && !schedule.mode) { %>display: none<% } %>">更改计算方式,未使用的计划月数据都将按新计算方式重新计算,请谨慎选择。</div>
+            </div>
+            <div class="modal-footer" id="mode-cancel" style=" <% if (schedule && !schedule.mode) { %>display: none <% } %>">
+                <button type="button" class="btn btn-sm btn-outline-secondary" data-dismiss="modal">关闭</button>
+            </div>
+        </div>
+    </div>
+</div>
+<% include ./modal.ejs %>

+ 39 - 0
app/view/schedule/stage_gcl.ejs

@@ -0,0 +1,39 @@
+<% include ../tender/tender_sub_menu.ejs %>
+<div class="panel-content">
+    <div class="panel-title">
+        <div class="title-main d-flex">
+            <% include ../tender/tender_sub_mini_menu.ejs %>
+            <div>
+                <div class="d-inline-block">
+                    <div class="btn-group group-tab">
+                        <a class="btn btn-sm btn-light " href="/tender/<%- tender.id %>/schedule/stage">
+                            金额模式
+                        </a>
+                        <a class="btn btn-sm btn-light active" href="javascript:void(0);">
+                            工程量模式
+                        </a>
+                    </div>
+                </div>
+            </div>
+            <div class="ml-auto">
+                <a href="#edit-计量" data-toggle="modal" data-target="#edit-plan" class="btn btn-sm btn-outline-primary">管理计量</a>
+                <a href="" class="btn btn-sm btn-primary" data-toggle="modal" data-target="#add">创建实际计量</a>
+            </div>
+        </div>
+    </div>
+    <div class="content-wrap">
+        <div class="c-body">
+            <div class="sjs-height-0" style="overflow: auto;" id="ledger-spread">
+            </div>
+        </div>
+    </div>
+</div>
+<script>
+    const tender = JSON.parse('<%- JSON.stringify(tender) %>');
+    const tenderInfo = JSON.parse(unescape('<%- escape(JSON.stringify(tenderInfo)) %>'));
+    const thousandth = <%- ctx.tender.info.display.thousandth %>;
+    const measureType = JSON.parse('<%- JSON.stringify(measureType) %>');
+    const schedule = JSON.parse('<%- JSON.stringify(schedule) %>');
+    const scheduleMonth = JSON.parse('<%- JSON.stringify(scheduleMonth) %>');
+    const monthList = _.map(scheduleMonth, 'yearmonth');
+</script>

+ 48 - 0
app/view/schedule/stage_gcl_modal.ejs

@@ -0,0 +1,48 @@
+<!--创建新计量进度数据-->
+<div class="modal fade" id="add" data-backdrop="static">
+    <div class="modal-dialog" role="document">
+        <div class="modal-content">
+            <div class="modal-header">
+                <h5 class="modal-title">创建实际计量</h5>
+            </div>
+            <div class="modal-body">
+                <div class="form-group">
+                    <label>计划进度月</label>
+                    <select class="form-control">
+                        <option>2020年1月</option>
+                        <option>2020年2月</option>
+                        <option>2020年3月</option>
+                    </select>
+                </div>
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-sm btn-secondary" data-dismiss="modal">取消</button>
+                <button type="button" class="btn btn-sm btn-primary" >确认创建</button>
+            </div>
+        </div>
+    </div>
+</div>
+<!--管理计量-->
+<div class="modal fade" id="edit-plan" data-backdrop="static">
+    <div class="modal-dialog" role="document">
+        <div class="modal-content">
+            <div class="modal-header">
+                <h5 class="modal-title">管理计量</h5>
+            </div>
+            <div class="modal-body">
+                <table class="table table-bordered table-hover">
+                    <tr><th>计量月</th><th width="100">删除</th></tr>
+                    <tr><td>2020-1</td><td></td></tr>
+                    <tr><td>2020-2</td><td><label><input type="checkbox"></label></td></tr>
+                    <tr><td>2020-3</td><td><label><input type="checkbox"></label></td></tr>
+                </table>
+                <div class="alert alert-danger">确认删除「2020-2」「2020-3」计量进度?删除后,数据无法恢复,请谨慎操作。</div>
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-sm btn-secondary" data-dismiss="modal">取消</button>
+                <button type="button" class="btn btn-sm btn-danger" >确认删除</button>
+            </div>
+        </div>
+    </div>
+</div>
+<% include ./modal.ejs %>

+ 52 - 0
app/view/schedule/stage_tp.ejs

@@ -0,0 +1,52 @@
+<% include ../tender/tender_sub_menu.ejs %>
+<div class="panel-content">
+    <div class="panel-title">
+        <div class="title-main d-flex">
+            <% include ../tender/tender_sub_mini_menu.ejs %>
+            <div>
+                <div class="d-inline-block">
+                    <div class="btn-group group-tab">
+                        <a class="btn btn-sm btn-light active" href="javascript:void(0);">
+                            金额模式
+                        </a>
+                        <a class="btn btn-sm btn-light" href="/tender/<%- tender.id %>/schedule/stage/gcl">
+                            工程量模式
+                        </a>
+                    </div>
+                </div>
+                <div class="d-inline-block">
+                    <div class="dropdown">
+                        <button class="btn btn-sm btn-light dropdown-toggle text-primary" type="button" id="dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
+                            2020年3月(第3期)
+                        </button>
+                        <div class="dropdown-menu" aria-labelledby="dropdownMenuButton" x-placement="bottom-start" style="position: absolute; transform: translate3d(0px, 26px, 0px); top: 0px; left: 0px; will-change: transform;">
+                            <a class="dropdown-item" href="#">2020年2月(第2期)</a>
+                        </div>
+                    </div>
+                </div>
+                <div class="d-inline-flex">
+                    <a href="#delete" data-toggle="modal" data-target="#delete" class="btn btn-sm btn-outline-danger">删除本期进度</a>
+                </div>
+            </div>
+            <div class="ml-auto">
+                <a href="" class="btn btn-sm btn-primary" data-toggle="modal" data-target="#add">创建新计量进度</a>
+                <a href="" class="btn btn-sm btn-warning" data-toggle="modal" data-target="#re-build">重新生成本月进度</a>
+            </div>
+        </div>
+    </div>
+    <div class="content-wrap">
+        <div class="c-body">
+            <div class="sjs-height-0" style="overflow: auto;" id="ledger-spread">
+            </div>
+        </div>
+    </div>
+</div>
+<script>
+    const tender = JSON.parse('<%- JSON.stringify(tender) %>');
+    const tenderInfo = JSON.parse(unescape('<%- escape(JSON.stringify(tenderInfo)) %>'));
+    const thousandth = <%- ctx.tender.info.display.thousandth %>;
+    const measureType = JSON.parse('<%- JSON.stringify(measureType) %>');
+    const schedule = JSON.parse('<%- JSON.stringify(schedule) %>');
+    const scheduleMonth = JSON.parse('<%- JSON.stringify(scheduleMonth) %>');
+    const monthList = _.map(scheduleMonth, 'yearmonth');
+</script>

+ 74 - 0
app/view/schedule/stage_tp_modal.ejs

@@ -0,0 +1,74 @@
+<!--创建新计量进度数据-->
+<div class="modal fade" id="add" data-backdrop="static">
+    <div class="modal-dialog" role="document">
+        <div class="modal-content">
+            <div class="modal-header">
+                <h5 class="modal-title">创建实际计量</h5>
+            </div>
+            <div class="modal-body">
+                <div class="form-group">
+                    <label>计划进度月</label>
+                    <select class="form-control">
+                        <option>2020年1月</option>
+                        <option>2020年2月</option>
+                        <option>2020年3月</option>
+                    </select>
+                </div>
+                <div class="form-group">
+                    <label>计量期</label>
+                    <select class="form-control">
+                        <option>第3期</option>
+                    </select>
+                </div>
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-sm btn-secondary" data-dismiss="modal">取消</button>
+                <button type="button" class="btn btn-sm btn-primary" >确认创建</button>
+            </div>
+        </div>
+    </div>
+</div>
+<!--重新生成本月计量进度-->
+<div class="modal fade" id="re-build" data-backdrop="static">
+    <div class="modal-dialog" role="document">
+        <div class="modal-content">
+            <div class="modal-header">
+                <h5 class="modal-title">重新生成计量进度</h5>
+            </div>
+            <div class="modal-body">
+                <div class="form-group">
+                    <label>计划进度月:2020年3月</label>
+                </div>
+                <div class="form-group">
+                    <label>计量期</label>
+                    <select class="form-control">
+                        <option>第3期</option>
+                    </select>
+                </div>
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-sm btn-secondary" data-dismiss="modal">取消</button>
+                <button type="button" class="btn btn-sm btn-primary" >确认生成</button>
+            </div>
+        </div>
+    </div>
+</div>
+<!--删除期-->
+<div class="modal fade" id="delete" data-backdrop="static">
+    <div class="modal-dialog" role="document">
+        <div class="modal-content">
+            <div class="modal-header">
+                <h5 class="modal-title">删除本期计量进度</h5>
+            </div>
+            <div class="modal-body">
+                <h6>确认删除「2020年3月(第3期)」计量进度?</h6>
+                <h6>删除后,数据无法恢复,请谨慎操作。</h6>
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-sm btn-secondary" data-dismiss="modal">取消</button>
+                <button type="button" class="btn btn-sm btn-danger">确定删除</button>
+            </div>
+        </div>
+    </div>
+</div>
+<% include ./modal.ejs %>

+ 204 - 0
app/view/shares/check_modal2.ejs

@@ -0,0 +1,204 @@
+<!--数据检查-->
+<div class="modal fade" id="checking2" data-backdrop="static">
+    <div class="modal-dialog modal-lg" role="document">
+        <div class="modal-content">
+            <div class="modal-header">
+                <h5 class="modal-title">数据检查</h5>
+            </div>
+            <div class="modal-body">
+                <div class="row">
+                    <div class="col-6">
+                        <!--雷达扫描动画-->
+                        <div class="L_transform">
+                            <div class="trans_bg">
+                                <div class="bg_shade"></div>
+                                <div class="circles topcircle active">
+                                    <div class="trio"><p class="c-icon"></p></div>
+                                </div>
+                                <div class="circles leftcircle ">
+                                    <div class="trio"><p class="c-icon"></p></div>
+                                </div>
+                                <div class="circles leftcircle2 ">
+                                    <div class="trio"><p class="c-icon"></p></div>
+                                </div>
+                                <div class="circles rightcircle ">
+                                    <div class="trio"><p class="c-icon"></p></div>
+                                </div>
+                                <div class="circles rightcircle2">
+                                    <div class="trio"><p class="c-icon"></p></div>
+                                </div>
+                                <div class="heart_box">
+                                    <div class="heart"></div>
+                                    <div class="shan" style="transform: rotate(274deg);"></div>
+                                </div>
+                            </div>
+                        </div>
+                        <!--雷达扫描动画结束-->
+                    </div>
+                    <div class="col-6">
+                        <p>数据检查,将检查罗列台账中以下内容:</p>
+                        <div id="check2-list">
+                            <div class="card p-2 border-success border-0">
+                                <div class="d-flex justify-content-start">
+                                    <span class="text-success mr-2"><i class="fa fa-check-circle fa-2x"></i></span>
+                                    <div class="w-100">
+                                        项目节、清单同层
+                                        <div class="progress mt-1">
+                                            <div class="progress-bar bg-success" role="progressbar" style="width: 100%;" aria-valuenow="25" aria-valuemin="0" aria-valuemax="100">100%</div>
+                                        </div>
+                                    </div>
+                                </div>
+                            </div>
+                            <div class="card p-2 border-success border-0">
+                                <div class="d-flex justify-content-start">
+                                    <span class="text-warning mr-2"><i class="fa fa-info-circle fa-2x"></i></span>
+                                    <div class="w-100">
+                                        项目节、清单编号同时为空
+                                        <div class="progress mt-1">
+                                            <div class="progress-bar bg-warning" role="progressbar" style="width: 100%;" aria-valuenow="25" aria-valuemin="0" aria-valuemax="100">100%</div>
+                                        </div>
+                                    </div>
+                                </div>
+                            </div>
+                            <div class="card p-2 border-success border-0">
+                                <div class="d-flex justify-content-start">
+                                    <span class="text-primary mr-2"><i class="fa fa-cog fa-spin fa-2x"></i></span>
+                                    <div class="w-100">
+                                        清单数量不等于计量单元之和
+                                        <div class="progress mt-1">
+                                            <div class="progress-bar" role="progressbar" style="width: 30%;" aria-valuenow="30" aria-valuemin="0" aria-valuemax="100">30%</div>
+                                        </div>
+                                    </div>
+                                </div>
+                            </div>
+                        </div>
+                        <p class="text-center text-primary" id="check2-hint">检查中,请等待...</p>
+                    </div>
+                </div>
+                <div class="modal-footer">
+                    <button type="button" class="btn btn-sm btn-secondary" data-dismiss="modal">关闭</button>
+                    <button type="button" class="btn btn-sm btn-primary" id="check2-result">查看结果</button>
+                </div>
+            </div>
+        </div>
+    </div>
+</div>
+<script>
+    const getmatrix = function(a,b,c,d,e,f){
+        var aa=Math.round(180*Math.asin(a)/ Math.PI);
+        var bb=Math.round(180*Math.acos(b)/ Math.PI);
+        var cc=Math.round(180*Math.asin(c)/ Math.PI);
+        var dd=Math.round(180*Math.acos(d)/ Math.PI);
+        var deg=0;
+        if(aa==bb||-aa==bb){
+            deg=dd;
+        }else if(-aa+bb==180){
+            deg=180+cc;
+        }else if(aa+bb==180){
+            deg=360-cc||360-dd;
+        }
+        return deg>=360?0:deg;
+
+    }
+    const radarFind = function (select) {
+        $('.circles').removeClass('active');
+        $(select).addClass('active');
+    }
+    let radarHandle, progressHandle;
+    const check2Viewing = function (setting) {
+        if (!setting.prefix) setting.prefix = 'check2-';
+        if (setting.randomWait) {
+            for (const c of setting.checks) {
+                c.wait = _.random(2, 4) + setting.extra;
+            }
+        }
+
+        // 雷达
+        let radarObj=$('.shan');
+        radarHandle = setInterval(function(){
+            var transform = radarObj.css('transform');
+            var deg= transform === 'none' ? 0 : eval('get'+ transform);//构造getmatrix函数,返回上次旋转度数
+            var step=45;
+            radarObj.css({'transform':'rotate('+(deg+step)%360+'deg)'});
+            var dd = radarObj.css({'transform':'rotate('+(deg+step)%360+'deg)'});
+            //上
+            if(deg>300&&deg<360 ||deg>0 &&deg<20){
+                radarFind('.topcircle');
+                // 右
+            }else if(deg>20 && deg<45){
+                radarFind('.rightcircle');
+            }else if(deg>70 && deg<90){
+                radarFind('.rightcircle2');
+                // 左
+            }else if(deg>150 && deg<180){
+                radarFind('.leftcircle2');
+            }else if(deg>230 && deg<300){
+                radarFind('.leftcircle');
+            }
+        }, 100);
+
+        const initCheckList = function (prefix, checks) {
+            $('#check2-result').hide();
+            $('#check2-hint').removeClass('text-warning').removeClass('text-success').addClass('text-primary').html('检查中,请等待...');
+            const html = [];
+            for (const c of checks) {
+                const domid = prefix + c.key;
+                html.push(`<div class="card p-2 border-success border-0" id="${domid}">`);
+                html.push('<div class="d-flex justify-content-start">');
+                html.push('<span class="text-muted mr-2"><i class="fa fa-circle-o fa-2x"></i></span>');
+                html.push('<div class="w-100">', c.caption);
+                html.push('<div class="progress mt-1">', '<div class="progress-bar" role="progressbar" style="width: 0%;" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100">0%</div>', '</div>');
+                html.push('</div>');
+                html.push('</div>');
+                html.push('</div>');
+            }
+            $('#check2-list').html(html.join(''));
+        }
+        const doSomeCheck = function (setting, index) {
+            const check = setting.checks[index];
+            console.log(check.wait);
+            if (!check) return;
+
+            setting.total = setting.total + (check.error || 0);
+            const obj = $('#' + setting.prefix + check.key);
+            $('.fa-circle-o', obj).removeClass('fa-circle-o').addClass('fa-cog');
+            $('.text-muted').removeClass('text-muted').addClass('text-primary');
+            $('.progress-bar', obj).attr('aria-valuemax', check.wait*10);
+            const handle = setInterval(function () {
+                const now = _.toInteger($('.progress-bar', obj).attr('aria-valuenow')) || 0;
+                if (now < check.wait * 10) {
+                    const percent = Math.min(ZhCalc.div((now + 1) * 10, check.wait, 0), 100);
+                    $('.progress-bar', obj).attr('aria-valuenow', Math.min(now + 1, check.wait*10)).html(percent + '%').width(percent + '%');
+                }
+            }, 100);
+            setTimeout(() => {
+                clearInterval(handle);
+                $('.fa-cog', obj).removeClass('fa-cog').addClass(check.error > 0 ? 'fa-info-circle' : 'fa-check-circle');
+                $('.text-primary', obj).removeClass('text-primary').addClass(check.error > 0 ? 'text-warning' : 'text-success');
+                $('.progress-bar', obj).addClass(check.error > 0 ? 'bg-warning' : 'bg-success');
+                if (index === setting.checks.length - 1) {
+                    if (setting.total > 0) {
+                        $('#check2-result').show();
+                        $('#check2-hint').removeClass('text-primary').addClass('text-warning').html('检查完成,发现问题,请查阅检查结果。');
+                    } else {
+                        $('#check2-hint').removeClass('text-primary').addClass('text-success').html('检查完成,没有任何问题。');
+                    }
+                } else {
+                    doSomeCheck(setting, index + 1);
+                }
+            }, check.wait*1000 + 500);
+        }
+
+        initCheckList(setting.prefix, setting.checks);
+        $('#checking2').modal('show');
+        setting.total = 0;
+        doSomeCheck(setting, 0);
+        $('#check2-result').click(() => {
+            $('#checking2').modal('hide');
+            setting.checkList.show();
+        });
+    }
+    $('#checking2').bind('hidden.bs.modal', function () {
+        clearInterval(radarHandle);
+    });
+</script>

+ 0 - 81
app/view/shares/ledger_check_modal.ejs

@@ -112,87 +112,6 @@
             return result;
         }
 
-        const ledgerCheckUtil = {
-            checkSibling: function (ledgerTree) {
-                const error = [];
-                for (const node of ledgerTree.nodes) {
-                    if (!node.children || node.children.length === 0) continue;
-                    let hasXmj, hasGcl;
-                    for (const child of node.children) {
-                        if (child.b_code) hasXmj = true;
-                        if (!child.b_code) hasGcl = true;
-                    }
-                    if (hasXmj && hasGcl) error.push(node);
-                }
-                return error;
-            },
-            checkCodeEmpty: function (ledgerTree) {
-                const error = [];
-                const checkNodeCode = function (node) {
-                    if ((!node.code || node.code === '') && (!node.b_code || node.b_code === '')) error.push(node);
-                    if (node.children && node.children.length > 0) {
-                        for (const child of node.children) {
-                            checkNodeCode(child);
-                        }
-                    }
-                }
-                for (const topLevel of ledgerTree.children) {
-                    if (topLevel.node_type !== 1) continue;
-
-                    checkNodeCode(topLevel);
-                }
-                return error;
-            },
-            checkCalc: function (ledgerTree, option) {
-                const error = [];
-                for (const node of ledgerTree.nodes) {
-                    if (node.children && node.children.length > 0) continue;
-
-                    const nodePos = ledgerPos.getLedgerPos(node.id);
-                    if (!nodePos || nodePos.length === 0) continue;
-
-                    const checkData = {}, calcData = {};
-                    for (const f of option.fields) {
-                        checkData[f] = node[f] || 0;
-                        calcData[f] = 0;
-                    }
-                    for (const np of nodePos) {
-                        for (const f of option.fields) {
-                            calcData[f] = ZhCalc.add(calcData[f], np[f]) || 0;
-                        }
-                    }
-                    if (!_.isMatch(checkData, calcData)) error.push(node);
-                }
-                return error;
-            },
-            checkZero: function (ledgerTree) {
-                const error = [];
-                for (const node of ledgerTree.nodes) {
-                    if ((!node.b_code || node.b_code === '')) continue;
-                    if (node.children && node.children.length > 0) continue;
-
-                    if ((checkZero(node.sgfh_qty) && checkZero(node.qtcl_qty) && checkZero(node.sjcl_qty)
-                        && checkZero(node.deal_qty) && checkZero(node.quantity))
-                        || checkZero(node.unit_price)) error.push(node);
-                }
-                return error;
-            },
-            checkTp: function (ledgerTree, option) {
-                const error = [];
-                for (const node of ledgerTree.nodes) {
-                    if (node.children && node.children.length > 0) continue;
-                    if (option.filter && option.filter(node)) continue;
-
-                    const checkData = {}, calcData = {};
-                    for (const f of option.fields) {
-                        checkData[f.tp] = node[f.tp] || 0;
-                        calcData[f.tp] = ZhCalc.mul(node.unit_price, node[f.qty], decimal.tp) || 0;
-                    }
-                    if (!_.isMatch(checkData, calcData)) error.push(node);
-                }
-                return error;
-            }
-        };
 
         const assignWarningData = function (nodes, checkType, warningData) {
             for (const node of nodes) {

+ 1 - 1
app/view/stage/audit_modal.ejs

@@ -644,7 +644,7 @@
                                                                 id="inlineRadio2"
                                                                 value="<%- auditConst.status.checkNoPre %>">
                                                             <label class="form-check-label" for="inlineRadio2">退回上一审批人
-                                                                <%- auditors[index-1].status === auditConst.status.checkAgain ? auditors[index-3].name : auditors[index-1].name %></label>
+                                                                <%- auditors[index-1].status === auditConst.status.checkAgain || auditors[index-1].status === auditConst.status.checkNoPre ? auditors[index-3].name : auditors[index-1].name %></label>
                                                         </div>
                                                         <% } %>
                                                     </div>

+ 7 - 2
app/view/stage/index.ejs

@@ -34,10 +34,10 @@
                 </div>
                 <div class="d-inline-block ml-3">
                     <a id="exportExcel" class="btn btn-primary btn-sm" href="javascript: void(0)">导出计量台账Excel</a>
-                    <a class="btn btn-sm btn-primary" href="#ledger-check-modal" data-toggle="modal" data-target="#ledger-check-modal">数据检查</a>
+                    <a class="btn btn-sm btn-primary" href="javascript: void(0);" id="ledger-check2">数据检查</a>
                 </div>
                 <div class="d-inline-block">
-                    <a class="btn btn-sm btn-primary" href="#correct" data-toggle="modal" data-target="#correct"><i class="fa fa-cog"></i></a>
+                    <a class="btn btn-sm btn-danger" href="#cooperation" data-toggle="modal" data-target="#cooperation" style="display: none;">多人协同 <i class="fa fa-lock"></i> <span id="cooperationCount"></span></a>
                 </div>
             </div>
             <div class="ml-auto">
@@ -537,6 +537,8 @@
                         <div class="sjs-bottom" id="ccb-spread">
                         </div>
                     </div>
+                    <div id="bills-tag" class="tab-pane">
+                    </div>
                     <div id="error-list" class="tab-pane">
                     </div>
                     <div id="check-list" class="tab-pane">
@@ -563,6 +565,9 @@
                     <a class="nav-link" content="#checked-change" href="javascript: void(0);">变更令</a>
                 </li>
                 <li class="nav-item">
+                    <a class="nav-link" content="#bills-tag" href="javascript: void(0);">书签</a>
+                </li>
+                <li class="nav-item">
                     <a class="nav-link" content="#check-list" id="check-list-tab" href="javascript: void(0);" style="display: none;">检查错误</a>
                 </li>
                 <li class="nav-item">

+ 39 - 7
app/view/stage/modal.ejs

@@ -450,24 +450,56 @@
         </div>
     </div>
 </div>
-<div class="modal fade" id="correct" data-backdrop="static">
+<!--多人协同-->
+<div class="modal fade" id="cooperation" data-backdrop="static">
     <div class="modal-dialog" role="document">
         <div class="modal-content">
             <div class="modal-header">
-                <h5 class="modal-title">操作确认</h5>
+                <h5 class="modal-title">多人协同</h5>
             </div>
             <div class="modal-body">
-                <div class="custom-control custom-checkbox">
-                    <input type="checkbox" class="custom-control-input" id="correct_percent">
-                    <label class="custom-control-label " for="correct_percent">使用数量纠正完成率</label>
+                <div class="alert alert-warning">以下项目节及其子项被锁定,请输入密码解锁。</div>
+                <div class="modal-height-300">
+                    <table class="table table-hover table-bordered">
+                        <thead>
+                        <tr><th>项目节编号</th><th>项目节名称</th><th>解锁  </th></tr>
+                        </thead>
+                        <tbody id="cooperationList">
+                        </tbody>
+                    </table>
+                </div>
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-sm btn-secondary" data-dismiss="modal">关闭</button>
+                <button type="button" class="btn btn-sm btn-primary">确认</button>
+            </div>
+        </div>
+    </div>
+</div>
+<!--解锁-->
+<div class="modal fade" id="unlock" data-backdrop="static">
+    <div class="modal-dialog" role="document">
+        <div class="modal-content">
+            <div class="modal-header">
+                <h5 class="modal-title">解锁项目节</h5>
+            </div>
+            <div class="modal-body">
+                <div class="form-group">
+                    <label id="unlock-info">1-2-2  挖方 解锁密码<b class="text-danger">*</b></label>
+                    <input class="form-control form-control-sm is-invalid" type="password" id="unlock-pwd">
+                    <div class="invalid-feedback">
+                        密码错误
+                    </div>
                 </div>
+                <div class="alert alert-warning">忘记密码请联系管理员。</div>
             </div>
             <div class="modal-footer">
-                <button type="button" class="btn btn-sm btn-primary" data-dismiss="modal">确认</button>
+                <button type="button" class="btn btn-sm btn-secondary" data-dismiss="modal">关闭</button>
+                <button type="button" class="btn btn-sm btn-primary" id="unlock-ok">确认</button>
             </div>
         </div>
     </div>
 </div>
 <% include ./audit_modal.ejs %>
 <% include ../shares/merge_peg_modal.ejs %>
-<% include ../shares/ledger_check_modal.ejs %>
+<% include ../shares/check_modal2.ejs %>

+ 1 - 1
app/view/stage/pay_modal.ejs

@@ -6,7 +6,7 @@
                 <h5 class="modal-title">设置计提期限</h5>
             </div>
             <div class="modal-body">
-                <p>请设置付(扣)款项 <b>本期应付</b> 的计提期限</p>
+                <p>请设置付(扣)款项 <b id="dl-pay-name">本期应付</b> 的计提期限</p>
                 <div class="form-group">
                     <label for="formGroupExampleInput">限制模式为:</label>
                     <div>

+ 125 - 10
app/view/tender/detail_modal.ejs

@@ -17,6 +17,9 @@
                         <li class="nav-item">
                             <a class="nav-link" data-toggle="tab" href="#jscs" role="tab">技术参数</a>
                         </li>
+                        <li class="nav-item">
+                            <a class="nav-link" data-toggle="tab" href="#zbxx" role="tab">中标信息</a>
+                        </li>
                     </ul>
                     <div class="tab-content">
                         <div class="tab-pane active" id="htxx">
@@ -38,7 +41,7 @@
                                             <input type="text" class="form-control" value="" id="deal-code" maxlength="100" oninput="limitReturn(this)">
                                         </div>
                                     </div>
-                                    <div class="col-12">
+                                    <div class="col-12 mb-2">
                                         <div class="input-group input-group-sm">
                                             <div class="input-group-prepend">
                                                 <span class="input-group-text" style="width:90px">合同名称</span>
@@ -46,6 +49,36 @@
                                             <input type="text" class="form-control" value="" id="deal-name" maxlength="100" oninput="limitReturn(this)">
                                         </div>
                                     </div>
+                                    <div class="col-12 mb-2">
+                                        <div class="input-group input-group-sm">
+                                            <div class="input-group-prepend">
+                                                <span class="input-group-text" style="width:90px">工程类别</span>
+                                            </div>
+                                            <select class="form-control" id="project-type">
+                                                <option value="">请选择</option>
+                                                <option value="设计">设计</option>
+                                                <option value="监理">监理</option>
+                                                <option value="施工">施工</option>
+                                                <option value="其他">其他</option>
+                                            </select>
+                                        </div>
+                                    </div>
+                                    <div class="col-12 mb-2">
+                                        <div class="input-group input-group-sm">
+                                            <div class="input-group-prepend">
+                                                <span class="input-group-text" style="width:90px">合同类别</span>
+                                            </div>
+                                            <input type="text" class="form-control" value="" id="deal-type">
+                                        </div>
+                                    </div>
+                                    <div class="col-12">
+                                        <div class="input-group input-group-sm">
+                                            <div class="input-group-prepend">
+                                                <span class="input-group-text" style="width:90px">结算书编号</span>
+                                            </div>
+                                            <input type="text" class="form-control" value="" id="final-code">
+                                        </div>
+                                    </div>
                                 </div>
                             </div>
                         </div>
@@ -270,6 +303,60 @@
                                             <input type="date" class="form-control" value="" id="plan-end-date">
                                         </div>
                                     </div>
+                                    <div class="col-6 pr-0 mb-2">
+                                        <div class="input-group input-group-sm">
+                                            <div class="input-group-prepend">
+                                                <span class="input-group-text">实际开工日期</span>
+                                            </div>
+                                            <input type="date" class="form-control" value="" id="real-start-date">
+                                        </div>
+                                    </div>
+                                    <div class="col-6">
+                                        <div class="input-group input-group-sm">
+                                            <div class="input-group-prepend">
+                                                <span class="input-group-text">实际完工日期</span>
+                                            </div>
+                                            <input type="date" class="form-control" value="" id="real-end-date">
+                                        </div>
+                                    </div>
+                                    <div class="col-12">
+                                        <div class="input-group input-group-sm">
+                                            <div class="input-group-prepend">
+                                                <span class="input-group-text">构造物规模</span>
+                                            </div>
+                                            <textarea class="form-control" id="structure-scale"></textarea>
+                                        </div>
+                                    </div>
+                                </div>
+                            </div>
+                        </div>
+                        <div class="tab-pane" id="zbxx">
+                            <div class="form-group">
+                                <div class="row">
+                                    <div class="col-12 mb-2">
+                                        <div class="input-group input-group-sm">
+                                            <div class="input-group-prepend">
+                                                <span class="input-group-text">业主控制价</span>
+                                            </div>
+                                            <input type="number" class="form-control nospin" value="" id="control-price" onchange="checkNumberValid(this)">
+                                        </div>
+                                    </div>
+                                    <div class="col-12 mb-2">
+                                        <div class="input-group input-group-sm">
+                                            <div class="input-group-prepend">
+                                                <span class="input-group-text">中标价</span>
+                                            </div>
+                                            <input type="number" class="form-control nospin" value="" id="bid-price" onchange="checkNumberValid(this)">
+                                        </div>
+                                    </div>
+                                    <div class="col-12 mb-2">
+                                        <div class="input-group input-group-sm">
+                                            <div class="input-group-prepend">
+                                                <span class="input-group-text">开标日期</span>
+                                            </div>
+                                            <input type="date" class="form-control" value="" id="bid-start-date">
+                                        </div>
+                                    </div>
                                 </div>
                             </div>
                         </div>
@@ -538,20 +625,24 @@
             <div class="modal-body">
                 <div class="form-group">
                     <div class="custom-control custom-checkbox mb-2">
-                        <input type="checkbox" class="form-check-input" id="ledger-dgn-qty" checked="">
-                        <label class="form-check-label" for="ledger-dgn-qty">项目节数量</label>
+                        <input type="checkbox" class="custom-control-input" id="ledger-dgn-qty" checked="">
+                        <label class="custom-control-label" for="ledger-dgn-qty">项目节数量</label>
                     </div>
                     <div class="custom-control custom-checkbox mb-2">
-                        <input type="checkbox" class="form-check-input" id="ledger-cl-qty" checked="">
-                        <label class="form-check-label" for="ledger-cl-qty">错漏增减</label>
+                        <input type="checkbox" class="custom-control-input" id="ledger-cl-qty" checked="">
+                        <label class="custom-control-label" for="ledger-cl-qty">错漏增减</label>
                     </div>
                     <div class="custom-control custom-checkbox mb-2">
-                        <input type="checkbox" class="form-check-input" id="thousandth" checked="">
-                        <label class="form-check-label" for="thousandth">千分位</label>
+                        <input type="checkbox" class="custom-control-input" id="thousandth" checked="">
+                        <label class="custom-control-label" for="thousandth">千分位</label>
                     </div>
                     <div class="custom-control custom-checkbox mb-2">
-                        <input type="checkbox" class="form-check-input" id="stage-rc" checked="">
-                        <label class="form-check-label" for="stage-rc">实际完成量</label>
+                        <input type="checkbox" class="custom-control-input" id="stage-rc" checked="">
+                        <label class="custom-control-label" for="stage-rc">实际完成量</label>
+                    </div>
+                    <div class="custom-control custom-checkbox mb-2">
+                        <input type="checkbox" class="custom-control-input" checked="" id="stage-correct">
+                        <label class="custom-control-label" for="stage-correct">使用数量纠正完成率</label>
                     </div>
                 </div>
             </div>
@@ -705,6 +796,9 @@
         $('#build-name').val(property.deal_info.buildName);
         $('#deal-code').val(property.deal_info.dealCode);
         $('#deal-name').val(property.deal_info.dealName);
+        $('#project-type').val(property.deal_info.projectType);
+        $('#deal-type').val(property.deal_info.dealType);
+        $('#final-code').val(property.deal_info.finalCode);
 
         // 参建单位
         // 建设单位
@@ -737,6 +831,14 @@
         $('#deal-period').val(property.tech_param.dealPeriod);
         $('#start-date').val(property.tech_param.startDate);
         $('#plan-end-date').val(property.tech_param.planEndDate);
+        $('#real-start-date').val(property.tech_param.realStartDate);
+        $('#real-end-date').val(property.tech_param.realEndDate);
+        $('#structure-scale').val(property.tech_param.structureScale);
+
+        // 中标信息
+        $('#control-price').val(property.bid_info.controlPrice);
+        $('#bid-price').val(property.bid_info.bidPrice);
+        $('#bid-start-date').val(property.bid_info.bidStartDate);
     }
 
     $('#bd-set-1').on('show.bs.modal', function () {
@@ -748,6 +850,9 @@
                 buildName: $('#build-name').val(),
                 dealCode: $('#deal-code').val(),
                 dealName: $('#deal-name').val(),
+                projectType: $('#project-type').val(),
+                dealType: $('#deal-type').val(),
+                finalCode: $('#final-code').val(),
             },
             construction_unit: {
                 build: {
@@ -785,6 +890,14 @@
                 dealPeriod: $('#deal-period').val(),
                 startDate: $('#start-date').val(),
                 planEndDate: $('#plan-end-date').val(),
+                realStartDate: $('#real-start-date').val(),
+                realEndDate: $('#real-end-date').val(),
+                structureScale: $('#structure-scale').val(),
+            },
+            bid_info: {
+                controlPrice: _.toNumber($('#control-price').val()),
+                bidPrice: _.toNumber($('#bid-price').val()),
+                bidStartDate: $('#bid-start-date').val(),
             }
         };
         const tenderId = window.location.pathname.split('/')[2];
@@ -792,6 +905,7 @@
             property.deal_info = data.deal_info;
             property.construction_unit = data.construction_unit;
             property.tech_param = data.tech_param;
+            property.bid_info = data.bid_info;
             $('#bd-set-1').modal('hide');
         });
     }
@@ -1197,6 +1311,7 @@
         $('#ledger-cl-qty')[0].checked = property.display.ledger.clQty;
         $('#thousandth')[0].checked = property.display.thousandth;
         $('#stage-rc')[0].checked = property.display.stage.realComplete;
+        $('#stage-correct')[0].checked = property.display.stage.correct;
     }
     $('#bd-set-5').on('show.bs.modal', function () {
         loadDisplayProperty();
@@ -1206,7 +1321,7 @@
             display: {
                 ledger: { dgnQty: $('#ledger-dgn-qty')[0].checked, clQty: $('#ledger-cl-qty')[0].checked, },
                 thousandth: $('#thousandth')[0].checked,
-                stage: { realComplete: $('#stage-rc')[0].checked, },
+                stage: { realComplete: $('#stage-rc')[0].checked, correct: $('#stage-correct')[0].checked },
             },
         };
         const tenderId = window.location.pathname.split('/')[2];

+ 7 - 25
app/view/tender/shenpi.ejs

@@ -16,6 +16,9 @@
                                 <div class="card-body <%- sp.code %>_div">
                                     <a class="pull-right set-otherTender" data-name="<%- sp.name %>" data-code="<%- sp.code %>" href="#batch" data-toggle="modal" data-target="#batch">设置其他标段</a>
                                     <a class="pull-right set-otherShenpi mr-3" data-name="<%- sp.name %>" data-code="<%- sp.code %>" href="#batch2" data-toggle="modal" data-target="#batch2">设置其他流程</a>
+                                    <% if (sp.code === 'stage' && !revising && ctx.tender.data.ledger_status === auditConst.ledger.status.checked) { %>
+                                    <a class="pull-right mr-3" id="stage_cooperation" <% if (sp.status !== shenpi.sp_status.gdspl) { %>style="display: none"<% } %> data-name="<%- sp.name %>" data-code="<%- sp.code %>" href="#cooperation" data-toggle="modal" data-target="#cooperation">多人协同 <i class="fa fa-lock"></i> <span id="cooperation-num"><%- cooperationNum %></span></a>
+                                    <% } %>
                                     <h5 class="card-title"><%- sp.name %></h5>
                                     <div class="form-group">
                                         <div class="form-group form-check">
@@ -51,9 +54,9 @@
                                                     <button class="btn btn-outline-primary btn-sm dropdown-toggle" type="button" id="<%- sp.code %>_dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
                                                         选择审批人
                                                     </button>
-                                                    <div class="dropdown-menu dropdown-menu-right" aria-labelledby="<%- sp.code %>_dropdownMenuButton" style="width:220px">
+                                                    <div class="dropdown-menu dropdown-menu-right" id="<%- sp.code %>_dropdownMenu" aria-labelledby="<%- sp.code %>_dropdownMenuButton" style="width:220px">
                                                         <div class="mb-2 p-2"><input class="form-control form-control-sm gr-search"
-                                                                                     placeholder="姓名/手机 检索" autocomplete="off"></div>
+                                                                                     placeholder="姓名/手机 检索" autocomplete="off" data-code="<%- sp.code %>"></div>
                                                         <dl class="list-unstyled book-list">
                                                             <% accountGroup.forEach((group, idx) => { %>
                                                                 <dt><a href="javascript: void(0);" class="acc-btn" data-groupid="<%- idx %>" data-type="hide"><i class="fa fa-plus-square"></i></a> <%- group.groupName %></dt>
@@ -139,7 +142,6 @@
         </div>
     </div>
 </div>
-<script src="/public/js/sub_menu.js"></script>
 <script>
     const sp_lc = JSON.parse('<%- JSON.stringify(shenpi.sp_lc) %>');
     const sp_type = JSON.parse('<%- JSON.stringify(shenpi.sp_type) %>');
@@ -151,26 +153,6 @@
     const cur_tenderid = parseInt('<%- ctx.tender.id %>');
     const tenders = JSON.parse(unescape('<%- escape(JSON.stringify(tenders)) %>'));
     const category = JSON.parse(unescape('<%- escape(JSON.stringify(categoryData)) %>'));
-</script>
-<script src="/public/js/decimal.min.js"></script>
-<script src="/public/js/zh_calc.js"></script>
-<script src="/public/js/shenpi.js"></script>
-<script src="/public/js/tender_showhide.js"></script>
-<script>
-    $.subMenu({
-        menu: '#sub-menu', miniMenu: '#sub-mini-menu', miniMenuList: '#mini-menu-list',
-        toMenu: '#to-menu', toMiniMenu: '#to-mini-menu',
-        key: 'menu.1.0.0',
-        miniHint: '#sub-mini-hint', hintKey: 'menu.hint.1.0.1',
-        callback: function (info) {
-            if (info.mini) {
-                $('.panel-title').addClass('fluid');
-                $('#sub-menu').removeClass('panel-sidebar');
-            } else {
-                $('.panel-title').removeClass('fluid');
-                $('#sub-menu').addClass('panel-sidebar');
-            }
-            autoFlashHeight();
-        }
-    });
+    const thousandth = <%- ctx.tender.info.display.thousandth %>;
+    const measureType = JSON.parse('<%- JSON.stringify(measureType) %>');
 </script>

+ 45 - 0
app/view/tender/shenpi_modal.ejs

@@ -79,3 +79,48 @@
         </div>
     </div>
 </div>
+<!--多人协同-->
+<div class="modal fade" id="cooperation" data-backdrop="static">
+    <div class="modal-dialog modal-xl" role="document">
+        <div class="modal-content">
+            <div class="modal-header">
+                <h5 class="modal-title">多人协同</h5>
+            </div>
+            <div class="modal-body">
+                <div class="row">
+                    <div class="col-6">
+                        <% const yb = ctx.helper._.find(accountList, { id: ctx.tender.data.user_id }) %>
+                        <div class="modal-height-300">
+                            <table class="table table-hover table-bordered">
+                                <thead>
+                                <tr><th colspan="3" class="text-center" id="stage_audit"><%- yb.name %>(原报)</th></tr>
+                                <tr><th>项目节编号/名称</th><th>密码</th><th>签名</th></tr>
+                                </thead>
+                                <tbody id="coo_table">
+                                </tbody>
+                            </table>
+                        </div>
+                    </div>
+                    <div class="col-6">
+                        <div class="mb-2">
+                            <select class="form-control form-control-sm" id="stage_audits">
+                                <option value="<%- ctx.tender.data.user_id %>"><%- yb.name %>(原报)</option>
+                                <% if (shenpi.sp_lc[shenpi.sp_type.stage-1].auditList) { %>
+                                <% for (const [i, audit] of shenpi.sp_lc[shenpi.sp_type.stage-1].auditList.entries()) { %>
+                                <option value="<%- audit.audit_id %>"><%- audit.name %>(<%- ctx.helper.transFormToChinese(i+1) %>审)</option>
+                                <% } %>
+                                <% } %>
+                            </select>
+                        </div>
+                        <div class="modal-height-300" style="overflow: auto;" id="ledger-spread">
+                        </div>
+                    </div>
+                </div>
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-sm btn-secondary" data-dismiss="modal">关闭</button>
+                <!--<button type="button" class="btn btn-sm btn-primary">确认</button>-->
+            </div>
+        </div>
+    </div>
+</div>

+ 8 - 0
app/view/tender/tender_sub_menu.ejs

@@ -43,6 +43,14 @@
                 <li <% if (ctx.url === '/tender/' + ctx.tender.id + '/measure/material') { %>class="active"<% } %>><a href="/tender/<%- ctx.tender.id %>/measure/material"><i class="fa fa-line-chart"></i> <span>材料调差</span></a></li>
             </ul>
         </div>
+        <!--<div class="nav-box">-->
+            <!--<h3><i class="fa fa-bar-chart "></i> 形象进度</h3>-->
+            <!--<ul class="nav-list list-unstyled sub-list">-->
+                <!--<li <% if (ctx.url === '/tender/' + ctx.tender.id + '/schedule' || ctx.url === '/tender/' + ctx.tender.id + '/schedule/ledger') { %>class="active"<% } %>><a href="/tender/<%- ctx.tender.id %>/schedule"><span>进度概况</span></a></li>-->
+                <!--<li <% if (ctx.url === '/tender/' + ctx.tender.id + '/schedule/plan') { %>class="active"<% } %>><a href="/tender/<%- ctx.tender.id %>/schedule/plan"><span>计划进度</span></a></li>-->
+                <!--<li <% if (ctx.url === '/tender/' + ctx.tender.id + '/schedule/stage' || ctx.url === '/tender/' + ctx.tender.id + '/schedule/stage/gcl') { %>class="active"<% } %>><a href="/tender/<%- ctx.tender.id %>/schedule/stage"><span>计量进度</span></a></li>-->
+            <!--</ul>-->
+        <!--</div>-->
         <div class="nav-box">
             <ul class="nav-list list-unstyled">
                 <li <% if (ctx.url === '/tender/' + ctx.tender.id + '/report') { %>class="active"<% } %>><a href="/tender/<%- ctx.tender.id %>/report"><i class="fa fa-file-text-o"></i> <span>报表</span></a></li>

+ 8 - 0
app/view/tender/tender_sub_mini_menu.ejs

@@ -41,6 +41,14 @@
                 <li <% if (ctx.url === '/tender/' + ctx.tender.id + '/measure/material') { %>class="active"<% } %>><a href="/tender/<%- ctx.tender.id %>/measure/material"><i class="fa fa-line-chart"></i> <span>材料调差</span></a></li>
             </ul>
         </div>
+        <!--<div class="nav-box">-->
+            <!--<h3><i class="fa fa-bar-chart "></i> 形象进度</h3>-->
+            <!--<ul class="nav-list list-unstyled sub-list">-->
+                <!--<li <% if (ctx.url === '/tender/' + ctx.tender.id + '/schedule' || ctx.url === '/tender/' + ctx.tender.id + '/schedule/ledger') { %>class="active"<% } %>><a href="/tender/<%- ctx.tender.id %>/schedule"><span>进度概况</span></a></li>-->
+                <!--<li <% if (ctx.url === '/tender/' + ctx.tender.id + '/schedule/plan') { %>class="active"<% } %>><a href="/tender/<%- ctx.tender.id %>/schedule/plan"><span>计划进度</span></a></li>-->
+                <!--<li <% if (ctx.url === '/tender/' + ctx.tender.id + '/schedule/stage' || ctx.url === '/tender/' + ctx.tender.id + '/schedule/stage/gcl') { %>class="active"<% } %>><a href="/tender/<%- ctx.tender.id %>/schedule/stage"><span>计量进度</span></a></li>-->
+            <!--</ul>-->
+        <!--</div>-->
         <div class="nav-box">
             <ul class="nav-list list-unstyled">
                 <li <% if (ctx.url === '/tender/' + ctx.tender.id + '/report') { %>class="active"<% } %>><a href="/tender/<%- ctx.tender.id %>/report"><i class="fa fa-file-text-o"></i> <span>报表</span></a></li>

+ 3 - 2
app/view/wap/list.ejs

@@ -57,8 +57,8 @@
     </nav>
 </div>
 <script>
-    const tenders = JSON.parse('<%- JSON.stringify(tenderList) %>');
-    const category = JSON.parse('<%- JSON.stringify(categoryData) %>');
+    const tenders = JSON.parse(unescape('<%- escape(JSON.stringify(tenderList)) %>'));
+    const category = JSON.parse(unescape('<%- escape(JSON.stringify(categoryData)) %>'));
     const uid = '<%- uid %>';
     const pid = '<%- pid %>';
     const uphlname = 'user_' + uid + '_pro_' + pid + '_category_wap_hide_list';
@@ -74,6 +74,7 @@
 <script src="/public/js/wap/global.js"></script>
 <script src="/public/js/decimal.min.js"></script>
 <script src="/public/js/zh_calc.js"></script>
+<script src="/public/js/PinYinOrder.bundle.js"></script>
 <script src="/public/js/shares/tender_list_order.js"></script>
 <script src="/public/js/tender_showhide.js"></script>
 <script src="/public/js/wap/list.js"></script>

+ 131 - 1
builder_report_index_define.js

@@ -17,6 +17,7 @@ const dataType = {
     double: 'double',
     currency: 'currency',
     time: 'string',
+    arr: 'array',
 };
 const tag = {
     tp: { type: 'tp' },
@@ -46,6 +47,21 @@ const advance_pay = {
         { name: '结束时间', field: 'end_time', type: dataType.str },
     ],
 };
+const ledger_cooperation = {
+    name: '台账-协作(ledger_cooperation)',
+    remark: '',
+    id: 45,
+    key: 'ledger_cooperation',
+    prefix: '台账-协作',
+    cols: [
+        { name: 'id', field: 'id', type: dataType.int },
+        { name: '标段id', field: 'tid', type: dataType.int },
+        { name: '审批人id', field: 'user_id', type: dataType.int },
+        { name: '台账id', field: 'ledger_id', type: dataType.int },
+        { name: '密码', field: 'pwd', type: dataType.str },
+        { name: '电子签名地址', field: 'sign_path', type: dataType.str },
+    ]
+};
 // 其他台账
 const stage_jgcl = {
     name: '期-甲供材料(mem_stage_jgcl)',
@@ -214,6 +230,9 @@ const change_bills = {
         { name: '金额_9', field: 'tp_9', type: dataType.currency, tag: { type: 'tp' } },
         { name: '项目节编号', field: 'xmj_code', type: dataType.str },
         { name: '细目', field: 'xmj_jldy', type: dataType.str },
+        { name: '单位工程', field: 'xmj_dwgc', type: dataType.str },
+        { name: '分部工程', field: 'xmj_fbgc', type: dataType.str },
+        { name: '分项工程', field: 'xmj_fxgc', type: dataType.str },
     ],
 };
 // 期 - 清单
@@ -663,6 +682,19 @@ const stage_im_zl = {
         { name: '位置', field: 'position', type: dataType.str },
         { name: '计量单元', field: 'jldy', type: dataType.str },
         { name: '草图备注', field: 'calc_memo_remark', type: dataType.str },
+        { name: '签名列表', field: 'cooperation', type: dataType.arr },
+        { name: '签名1', field: 'co_sign1', type: dataType.str, isPic: true },
+        { name: '签名2', field: 'co_sign2', type: dataType.str, isPic: true },
+        { name: '签名3', field: 'co_sign3', type: dataType.str, isPic: true },
+        { name: '签名4', field: 'co_sign4', type: dataType.str, isPic: true },
+        { name: '审批时间1', field: 'co_time1', type: dataType.time },
+        { name: '审批时间2', field: 'co_time2', type: dataType.time },
+        { name: '审批时间3', field: 'co_time3', type: dataType.time },
+        { name: '审批时间4', field: 'co_time4', type: dataType.time },
+        { name: '审批意见1', field: 'co_opinion1', type: dataType.time },
+        { name: '审批意见2', field: 'co_opinion2', type: dataType.time },
+        { name: '审批意见3', field: 'co_opinion3', type: dataType.time },
+        { name: '审批意见4', field: 'co_opinion4', type: dataType.time },
     ],
 };
 const stage_im_tz = {
@@ -699,6 +731,19 @@ const stage_im_tz = {
         { name: '位置', field: 'position', type: dataType.str },
         { name: '计量单元', field: 'jldy', type: dataType.str },
         { name: '草图备注', field: 'calc_img_remark', type: dataType.str },
+        { name: '签名列表', field: 'cooperation', type: dataType.arr },
+        { name: '签名1', field: 'co_sign1', type: dataType.str, isPic: true },
+        { name: '签名2', field: 'co_sign2', type: dataType.str, isPic: true },
+        { name: '签名3', field: 'co_sign3', type: dataType.str, isPic: true },
+        { name: '签名4', field: 'co_sign4', type: dataType.str, isPic: true },
+        { name: '审批时间1', field: 'co_time1', type: dataType.time },
+        { name: '审批时间2', field: 'co_time2', type: dataType.time },
+        { name: '审批时间3', field: 'co_time3', type: dataType.time },
+        { name: '审批时间4', field: 'co_time4', type: dataType.time },
+        { name: '审批意见1', field: 'co_opinion1', type: dataType.time },
+        { name: '审批意见2', field: 'co_opinion2', type: dataType.time },
+        { name: '审批意见3', field: 'co_opinion3', type: dataType.time },
+        { name: '审批意见4', field: 'co_opinion4', type: dataType.time },
     ],
 };
 const stage_im_tz_bills = {
@@ -836,6 +881,13 @@ const gather_stage_bills = {
         { name: '(标段)变更-设计数量2', field: 't_c_dgn_qty2', type: dataType.currency },
 
         { name: '树结构-是否子项', field: 'is_leaf', type: dataType.int },
+
+        { name: '(合计)台账-设计数量1', field: 's_dgn_qty1', type: dataType.currency },
+        { name: '(合计)台账-设计数量2', field: 's_dgn_qty2', type: dataType.currency },
+        { name: '(合计)合同-设计数量1', field: 's_deal_dgn_qty1', type: dataType.currency },
+        { name: '(合计)合同-设计数量2', field: 's_deal_dgn_qty2', type: dataType.currency },
+        { name: '(合计)变更-设计数量1', field: 's_c_dgn_qty1', type: dataType.currency },
+        { name: '(合计)变更-设计数量2', field: 's_c_dgn_qty2', type: dataType.currency },
     ],
 };
 const gather_tender_info = {
@@ -922,6 +974,18 @@ const gather_tender_info = {
         { name: '付款账号-农民工工资专用账户-分账划拨比例(%)', field: 'pay_account.worker.rate', type: dataType.str },
         { name: '付款账号-农民工工资专用账户-联系人', field: 'pay_account.worker.contact', type: dataType.str },
         { name: '付款账号-农民工工资专用账户-联系电话', field: 'pay_account.worker.phone', type: dataType.str },
+
+        { name: '合同信息-工程类别', field: 'deal_info.projectType', type: dataType.str},
+        { name: '合同信息-合同类别', field: 'deal_info.dealType', type: dataType.str},
+        { name: '合同信息-结算书编号', field: 'deal_info.finalCode', type: dataType.str},
+
+        { name: '技术参数-实际开工日期', field: 'tech_param.realStartDate', type: dataType.str},
+        { name: '技术参数-实际完工日期', field: 'tech_param.realEndDate', type: dataType.str},
+        { name: '技术参数-构造物规模', field: 'tech_param.structureScale', type: dataType.str},
+
+        { name: '中标信息-业主控制价', field: 'bid_info.controlPrice', type: dataType.currency },
+        { name: '中标信息-中标价', field: 'bid_info.bidPrice', type: dataType.currency },
+        { name: '中标信息-开标日期', field: 'bid_info.bidStartDate', type: dataType.str },
     ],
 };
 const gather_stage_pay = {
@@ -1123,6 +1187,71 @@ const stage_sum_pay = {
     ],
 };
 
+const stage_audit = {
+    name: '期-审批人 列表(stage_audit)',
+    remark: '',
+    id: 46,
+    key: 'stage_audit',
+    prefix: '期-审批人',
+    cols: [
+        { name: '审批人id', field: 'aid', type: dataType.int },
+        { name: '姓名', field: 'name', type: dataType.str },
+        { name: '公司', field: 'company', type: dataType.str },
+        { name: '角色', field: 'role', type: dataType.str },
+        { name: '手机', field: 'mobile', type: dataType.str },
+        { name: '电话', field: 'telephone', type: dataType.str },
+        { name: '审批意见', field: 'opinion', type: dataType.str },
+        { name: '审批时间', field: 'end_time', type: dataType.str },
+        { name: '审批顺序', field: 'sort', type: dataType.str },
+    ],
+};
+
+const sign_select = {
+    name: '电子签名(mem_sign_select)',
+    remark: '',
+    id: 47,
+    key: 'mem_sign_select',
+    prefix: '电子签名',
+    cols: [
+        { name: 'id-1', field: 'sign1.id', type: dataType.int },
+        { name: '名字-1', field: 'sign1.name', type: dataType.str },
+        { name: '审批意见-1', field: 'sign1.opinion', type: dataType.str },
+        { name: '审批时间-1', field: 'sign1.end_time', type: dataType.str },
+        { name: '签名-1', field: 'sign1.sign_path', type: dataType.str, isPic: true },
+        { name: '协作签名-1', field: 'sign1.co_sign', type: dataType.str, isPic: true },
+        { name: 'id-2', field: 'sign2.id', type: dataType.int },
+        { name: '名字-2', field: 'sign2.name', type: dataType.str },
+        { name: '审批意见-2', field: 'sign2.opinion', type: dataType.str },
+        { name: '审批时间-2', field: 'sign2.end_time', type: dataType.str },
+        { name: '签名-2', field: 'sign2.sign_path', type: dataType.str, isPic: true },
+        { name: '协作签名-2', field: 'sign2.co_sign', type: dataType.str, isPic: true },
+        { name: 'id-3', field: 'sign3.id', type: dataType.int },
+        { name: '名字-3', field: 'sign3.name', type: dataType.str },
+        { name: '审批意见-3', field: 'sign3.opinion', type: dataType.str },
+        { name: '审批时间-3', field: 'sign3.end_time', type: dataType.str },
+        { name: '签名-3', field: 'sign3.sign_path', type: dataType.str, isPic: true },
+        { name: '协作签名-3', field: 'sign3.co_sign', type: dataType.str, isPic: true },
+        { name: 'id-4', field: 'sign4.id', type: dataType.int },
+        { name: '名字-4', field: 'sign4.name', type: dataType.str },
+        { name: '审批意见-4', field: 'sign4.opinion', type: dataType.str },
+        { name: '审批时间-4', field: 'sign4.end_time', type: dataType.str },
+        { name: '签名-4', field: 'sign4.sign_path', type: dataType.str, isPic: true },
+        { name: '协作签名-4', field: 'sign4.co_sign', type: dataType.str, isPic: true },
+        { name: 'id-5', field: 'sign5.id', type: dataType.int },
+        { name: '名字-5', field: 'sign5.name', type: dataType.str },
+        { name: '审批意见-5', field: 'sign5.opinion', type: dataType.str },
+        { name: '审批时间-5', field: 'sign5.end_time', type: dataType.str },
+        { name: '签名-5', field: 'sign5.sign_path', type: dataType.str, isPic: true },
+        { name: '协作签名-5', field: 'sign5.co_sign', type: dataType.str, isPic: true },
+        { name: 'id-6', field: 'sign6.id', type: dataType.int },
+        { name: '名字-6', field: 'sign6.name', type: dataType.str },
+        { name: '审批意见-6', field: 'sign6.opinion', type: dataType.str },
+        { name: '审批时间-6', field: 'sign6.end_time', type: dataType.str },
+        { name: '签名-6', field: 'sign6.sign_path', type: dataType.str, isPic: true },
+        { name: '协作签名-6', field: 'sign6.co_sign', type: dataType.str, isPic: true },
+    ],
+};
+
 const recursiveMkdirSync = async function(pathName) {
     if (!fs.existsSync(pathName)) {
         const upperPath = path.dirname(pathName);
@@ -1214,6 +1343,7 @@ const exportTableDefine = async function(define) {
 };
 
 const defines = [
+    ledger_cooperation,
     advance_pay,
     union_data,
     month_progress,
@@ -1225,7 +1355,7 @@ const defines = [
     stage_im_zl, stage_im_tz, stage_im_tz_bills,
     gather_stage_bills, gather_tender_info, gather_stage_pay, gather_deal_bills,
     material, materialGl,
-    stage_sum_bills, stage_sum_pay
+    stage_sum_bills, stage_sum_pay, stage_audit, sign_select,
 ];
 for (const d of defines) {
     exportTableDefine(d);

+ 72 - 1
config/web.js

@@ -108,6 +108,23 @@ const JsFiles = {
                 ],
                 mergeFile: 'tender',
             },
+            shenpi: {
+                files: [
+                    '/public/js/spreadjs/sheets/v11/gc.spread.sheets.all.11.2.2.min.js',
+                    '/public/js/decimal.min.js',
+                ],
+                mergeFiles: [
+                    '/public/js/sub_menu.js',
+                    '/public/js/div_resizer.js',
+                    '/public/js/spreadjs_rela/spreadjs_zh.js',
+                    '/public/js/shares/sjs_setting.js',
+                    '/public/js/zh_calc.js',
+                    '/public/js/path_tree.js',
+                    '/public/js/shenpi.js',
+                    '/public/js/tender_showhide.js',
+                ],
+                mergeFile: 'tender_shenpi',
+            },
         },
         ledger: {
             explode: {
@@ -132,6 +149,7 @@ const JsFiles = {
                     '/public/js/path_tree.js',
                     '/public/js/ledger_tree_col.js',
                     '/public/js/std_lib.js',
+                    '/public/js/ledger_check.js',
                     '/public/js/ledger.js',
                 ],
                 mergeFile: 'explode',
@@ -226,6 +244,7 @@ const JsFiles = {
                     '/public/js/zh_calc.js',
                     '/public/js/path_tree.js',
                     '/public/js/std_lib.js',
+                    '/public/js/ledger_check.js',
                     '/public/js/revise.js',
                 ],
                 mergeFile: 'revise',
@@ -274,6 +293,7 @@ const JsFiles = {
                     '/public/js/sub_menu.js',
                     '/public/js/div_resizer.js',
                     '/public/js/spreadjs_rela/spreadjs_zh.js',
+                    '/public/js/shares/sjs_setting.js',
                     '/public/js/zh_calc.js',
                     '/public/js/path_tree.js',
                     '/public/js/shares/gcl_gather_compare.js',
@@ -304,6 +324,7 @@ const JsFiles = {
                     '/public/js/zh_calc.js',
                     '/public/js/path_tree.js',
                     '/public/js/stage_im.js',
+                    '/public/js/ledger_check.js',
                     '/public/js/stage.js',
                     '/public/js/stage_audit.js',
                 ],
@@ -673,7 +694,57 @@ const JsFiles = {
                     '/public/js/path_tree.js',
                     '/public/js/schedule_ledger.js',
                 ],
-                mergeFile: 'ledger',
+                mergeFile: 'schedule_ledger',
+            },
+            plan: {
+                files: [
+                    '/public/js/spreadjs/sheets/v11/gc.spread.sheets.all.11.2.2.min.js',
+                    '/public/js/decimal.min.js',
+                ],
+                mergeFiles: [
+                    '/public/js/sub_menu.js',
+                    '/public/js/div_resizer.js',
+                    '/public/js/spreadjs_rela/spreadjs_zh.js',
+                    '/public/js/shares/sjs_setting.js',
+                    '/public/js/zh_calc.js',
+                    '/public/js/path_tree.js',
+                    '/public/js/schedule_plan.js',
+                    '/public/js/datepicker/datepicker.min.js',
+                    '/public/js/datepicker/datepicker.zh.js',
+                ],
+                mergeFile: 'schedule_plan',
+            },
+            stageTp: {
+                files: [
+                    '/public/js/spreadjs/sheets/v11/gc.spread.sheets.all.11.2.2.min.js',
+                    '/public/js/decimal.min.js',
+                ],
+                mergeFiles: [
+                    '/public/js/sub_menu.js',
+                    '/public/js/div_resizer.js',
+                    '/public/js/spreadjs_rela/spreadjs_zh.js',
+                    '/public/js/shares/sjs_setting.js',
+                    '/public/js/zh_calc.js',
+                    '/public/js/path_tree.js',
+                    // '/public/js/schedule_stage_tp.js',
+                ],
+                mergeFile: 'schedule_stage_tp',
+            },
+            stageGcl: {
+                files: [
+                    '/public/js/spreadjs/sheets/v11/gc.spread.sheets.all.11.2.2.min.js',
+                    '/public/js/decimal.min.js',
+                ],
+                mergeFiles: [
+                    '/public/js/sub_menu.js',
+                    '/public/js/div_resizer.js',
+                    '/public/js/spreadjs_rela/spreadjs_zh.js',
+                    '/public/js/shares/sjs_setting.js',
+                    '/public/js/zh_calc.js',
+                    '/public/js/path_tree.js',
+                    // '/public/js/schedule_stage_gcl.js',
+                ],
+                mergeFile: 'schedule_stage_gcl',
             },
         },
         change: {

+ 18 - 50
sql/update.sql

@@ -1,68 +1,26 @@
-ALTER TABLE `zh_tender`
-ADD COLUMN `copy_id` INT(10) NULL COMMENT '被拷贝标段id' AFTER `uuid`;
+ALTER TABLE `zh_change` ADD `tp_decimal` TINYINT(3) NULL DEFAULT NULL COMMENT '金额位数' AFTER `sin_time`;
 
+ALTER TABLE `zh_tender_info`
+ADD COLUMN `bid_info`  varchar(200) NULL AFTER `tech_param`;
 
-ALTER TABLE `zh_change_audit_list` ADD `gcl_id` VARCHAR(255) NOT NULL DEFAULT '' COMMENT '台账对应id(非准确)' AFTER `xmj_jldy`;
-UPDATE `zh_change_audit_list` SET `gcl_id`=`lid` WHERE `lid` != '0';
+--
+-- 表的结构 `zh_ledger_cooperation`
+--
 
-ALTER TABLE `zh_s2b_proj`
-ADD COLUMN `merge_code`  tinyint(1) UNSIGNED NOT NULL DEFAULT 0 AFTER `common_option`;
-
-CREATE TABLE `zh_tender_tag` (
-  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
+CREATE TABLE `zh_ledger_cooperation` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
   `tid` int(11) NOT NULL COMMENT '标段id',
-  `pid` int(11) NOT NULL COMMENT '项目id',
-  `ledger_time` datetime DEFAULT NULL COMMENT '台账分解审批完成时间',
-  `revise_time` datetime DEFAULT NULL COMMENT '最后一次台账修订审批完成时间',
-  `stage_time` datetime DEFAULT NULL COMMENT '最后一次期审批完成时间',
-  `bgl_time` datetime DEFAULT NULL COMMENT '最后一次变更令审批完成时间',
-  PRIMARY KEY (`id`)
-) ENGINE=InnoDB AUTO_INCREMENT=2049 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
-
-CREATE TABLE `zh_s2b_proj_push` (
-  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
-  `pid` int(11) NOT NULL COMMENT '项目id-zh_s2b_proj中的id',
-  `name` varchar(50) COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '名称',
-  `url` varchar(255) CHARACTER SET ascii NOT NULL DEFAULT '' COMMENT '推送的url',
-  `ledger` tinyint(1) NOT NULL DEFAULT '1' COMMENT '台账审批通过-是否推送',
-  `revise` tinyint(1) NOT NULL DEFAULT '1' COMMENT '台账修订审批通过-是否推送',
-  `stage` tinyint(1) NOT NULL DEFAULT '1' COMMENT '期审批通过-是否推送',
-  `bgl` tinyint(1) NOT NULL DEFAULT '1' COMMENT '变更令审批通过-是否需要推送',
-  `add_time` datetime(4) DEFAULT NULL COMMENT '创建时间',
-  `lastest_time` datetime(4) DEFAULT NULL COMMENT '最后修改时间',
-  `push_time` datetime(4) DEFAULT NULL COMMENT '最近一次推送时间',
-  PRIMARY KEY (`id`)
-) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
-
-Insert Into zh_tender_tag (tid, pid) Select id, project_id From zh_tender Where 1=1;
-
-CREATE TABLE `zh_s2b_push_log` (
-  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
-  `pid` int(11) NOT NULL COMMENT '项目id',
-  `push_id` int(11) NOT NULL COMMENT '推送id',
-  `url` varchar(255) COLLATE utf8_unicode_ci NOT NULL COMMENT '推送url',
-  `push_data` text COLLATE utf8_unicode_ci COMMENT '推送内容',
-  `push_time` datetime NOT NULL COMMENT '推送时间',
-  `push_result` text COLLATE utf8_unicode_ci COMMENT '推送结果',
-  `push_err` varchar(1000) CHARACTER SET utf8 DEFAULT NULL COMMENT '推送错误',
+  `ledger_id` int(11) NOT NULL COMMENT '台账id',
+  `user_id` int(11) NOT NULL COMMENT '审批人id',
+  `pwd` varchar(255) COLLATE utf8_unicode_ci NOT NULL COMMENT '密码',
+  `sign_path` varchar(500) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT '电子签名地址',
+  `status` tinyint(1) NOT NULL DEFAULT '1' COMMENT '是否调用',
   PRIMARY KEY (`id`)
-) ENGINE=InnoDB AUTO_INCREMENT=24 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci COMMENT='台账多人协同表';
 
-ALTER TABLE `zh_s2b_c_proj`
-ADD COLUMN `push_tender`  varchar(1000) CHARACTER SET ascii NOT NULL DEFAULT '' COMMENT '推送的标段' AFTER `base_data`;
 
-ALTER TABLE `zh_s2b_proj`
-ADD COLUMN `filter_tender`  varchar(1000) CHARACTER SET ascii NOT NULL DEFAULT '' COMMENT '过滤标段' AFTER `merge_code`;
+CREATE UNIQUE INDEX `idx_tid_sid_rid`  ON `zh_rpt_custom_define` (tid, sid, rid) COMMENT '电子签名数据索引' ALGORITHM DEFAULT LOCK DEFAULT;
 
-ALTER TABLE `zh_category_value` ADD `sort` int(11) DEFAULT NULL COMMENT '排序' AFTER `value`;
-UPDATE `zh_category_value` SET `sort`=`id`;
+ALTER TABLE `zh_change_audit_list` ADD `xmj_dwgc` varchar(255) NULL DEFAULT NULL COMMENT '单位工程' AFTER `xmj_jldy`;
+ALTER TABLE `zh_change_audit_list` ADD `xmj_fbgc` varchar(255) NULL DEFAULT NULL COMMENT '分部工程' AFTER `xmj_dwgc`;
+ALTER TABLE `zh_change_audit_list` ADD `xmj_fxgc` varchar(255) NULL DEFAULT NULL COMMENT '分项工程' AFTER `xmj_fbgc`;

+ 68 - 0
sql/update20201126.sql

@@ -0,0 +1,68 @@
+ALTER TABLE `zh_tender`
+ADD COLUMN `copy_id` INT(10) NULL COMMENT '被拷贝标段id' AFTER `uuid`;
+
+
+ALTER TABLE `zh_change_audit_list` ADD `gcl_id` VARCHAR(255) NOT NULL DEFAULT '' COMMENT '台账对应id(非准确)' AFTER `xmj_jldy`;
+UPDATE `zh_change_audit_list` SET `gcl_id`=`lid` WHERE `lid` != '0';
+
+-- sync2bim相关
+ALTER TABLE `zh_s2b_proj`
+ADD COLUMN `merge_code`  tinyint(1) UNSIGNED NOT NULL DEFAULT 0 AFTER `common_option`;
+
+-- ----------------------------
+-- Table structure for zh_tender_tag
+-- ----------------------------
+CREATE TABLE `zh_tender_tag` (
+  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
+  `tid` int(11) NOT NULL COMMENT '标段id',
+  `pid` int(11) NOT NULL COMMENT '项目id',
+  `ledger_time` datetime DEFAULT NULL COMMENT '台账分解审批完成时间',
+  `revise_time` datetime DEFAULT NULL COMMENT '最后一次台账修订审批完成时间',
+  `stage_time` datetime DEFAULT NULL COMMENT '最后一次期审批完成时间',
+  `bgl_time` datetime DEFAULT NULL COMMENT '最后一次变更令审批完成时间',
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=2049 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
+
+-- ----------------------------
+-- Table structure for zh_s2b_proj_push
+-- ----------------------------
+CREATE TABLE `zh_s2b_proj_push` (
+  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
+  `pid` int(11) NOT NULL COMMENT '项目id-zh_s2b_proj中的id',
+  `name` varchar(50) COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '名称',
+  `url` varchar(255) CHARACTER SET ascii NOT NULL DEFAULT '' COMMENT '推送的url',
+  `ledger` tinyint(1) NOT NULL DEFAULT '1' COMMENT '台账审批通过-是否推送',
+  `revise` tinyint(1) NOT NULL DEFAULT '1' COMMENT '台账修订审批通过-是否推送',
+  `stage` tinyint(1) NOT NULL DEFAULT '1' COMMENT '期审批通过-是否推送',
+  `bgl` tinyint(1) NOT NULL DEFAULT '1' COMMENT '变更令审批通过-是否需要推送',
+  `add_time` datetime(4) DEFAULT NULL COMMENT '创建时间',
+  `lastest_time` datetime(4) DEFAULT NULL COMMENT '最后修改时间',
+  `push_time` datetime(4) DEFAULT NULL COMMENT '最近一次推送时间',
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
+
+Insert Into zh_tender_tag (tid, pid) Select id, project_id From zh_tender Where 1=1;
+
+-- ----------------------------
+-- Table structure for zh_s2b_push_log
+-- ----------------------------
+CREATE TABLE `zh_s2b_push_log` (
+  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
+  `pid` int(11) NOT NULL COMMENT '项目id',
+  `push_id` int(11) NOT NULL COMMENT '推送id',
+  `url` varchar(255) COLLATE utf8_unicode_ci NOT NULL COMMENT '推送url',
+  `push_data` text COLLATE utf8_unicode_ci COMMENT '推送内容',
+  `push_time` datetime NOT NULL COMMENT '推送时间',
+  `push_result` text COLLATE utf8_unicode_ci COMMENT '推送结果',
+  `push_err` varchar(1000) CHARACTER SET utf8 DEFAULT NULL COMMENT '推送错误',
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=24 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
+
+ALTER TABLE `zh_s2b_c_proj`
+ADD COLUMN `push_tender`  varchar(1000) CHARACTER SET ascii NOT NULL DEFAULT '' COMMENT '推送的标段' AFTER `base_data`;
+
+ALTER TABLE `zh_s2b_proj`
+ADD COLUMN `filter_tender`  varchar(1000) CHARACTER SET ascii NOT NULL DEFAULT '' COMMENT '过滤标段' AFTER `merge_code`;
+
+ALTER TABLE `zh_category_value` ADD `sort` int(11) DEFAULT NULL COMMENT '排序' AFTER `value`;
+UPDATE `zh_category_value` SET `sort`=`id`;

+ 16 - 0
test/app/lib/rpt_data_analysis.test.js

@@ -188,6 +188,22 @@ describe('test/app/service/report_memory.test.js', () => {
         const chapter400 = ctx.helper._.find(data.mem_stage_bills, {code: '400'});
         assert(chapter400.total_price.toFixed(0) == 1231018);
     });
+    it('test loadCooperationData', function* () {
+        const ctx = app.mockContext(mockData);
+        const stage = yield ctx.service.stage.getDataByCondition({tid: 3301, order: 1});
+        const params = {
+            tender_id: stage.tid,
+            stage_id: stage.id,
+        };
+        const data = yield ctx.service.report.getReportData(params, ['mem_stage_bills', 'stage', 'stage_audit', 'ledger_cooperation', 'mem_stage_im_zl'], {
+            mem_stage_bills: [
+                'id', 'tender_id', 'ledger_id', 'ledger_pid', 'level', 'order', 'full_path', 'is_leaf'
+            ],
+            mem_stage_im_zl: ['lid', 'code'],
+        });
+        reportDataAnalysis.analysisObj.loadCooperationData.fun(ctx, data, [], {table: 'mem_stage_im_zl', co_sign: [0, 1, 2, 3]}, null);
+        console.log(data.mem_stage_im_zl);
+    });
     it('test join', function* () {
         const ctx = app.mockContext(mockData);