Browse Source

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

MaiXinRong 4 years ago
parent
commit
19d4467980
68 changed files with 5995 additions and 3862 deletions
  1. 11 0
      Dockerfile
  2. 1 1
      app/base/base_bills_service.js
  3. 6 0
      app/const/schedule.js
  4. 7 3
      app/controller/report_controller.js
  5. 337 91
      app/controller/schedule_controller.js
  6. 10 4
      app/controller/stage_extra_controller.js
  7. 17 0
      app/controller/tender_controller.js
  8. 43 0
      app/middleware/schedule_check.js
  9. 12 0
      app/middleware/tender_check.js
  10. 11 0
      app/public/js/change_information_set.js
  11. 7 0
      app/public/js/global.js
  12. 49 23
      app/public/js/ledger.js
  13. 53 20
      app/public/js/ledger_audit.js
  14. 2 0
      app/public/js/material.js
  15. 2 0
      app/public/js/material_exponent.js
  16. 54 21
      app/public/js/path_tree.js
  17. 41 19
      app/public/js/revise.js
  18. 26 19
      app/public/js/revise_history.js
  19. 190 16
      app/public/js/schedule_ledger.js
  20. 205 87
      app/public/js/schedule_plan.js
  21. 318 149
      app/public/js/schedule_stage_gcl.js
  22. 66 18
      app/public/js/schedule_stage_tp.js
  23. 6 6
      app/public/js/se_bonus.js
  24. 87 6
      app/public/js/shenpi.js
  25. 18 1
      app/public/js/spreadjs_rela/spreadjs_zh.js
  26. 163 98
      app/public/js/stage.js
  27. 52 22
      app/public/js/stage_compare.js
  28. 79 21
      app/public/js/stage_im.js
  29. 1 1
      app/public/js/tender.js
  30. 13 13
      app/public/js/xlsx-populate/xlsx-populate.js
  31. 10 3
      app/public/report/js/rpt_custom.js
  32. 1 0
      app/public/report/js/rpt_main.js
  33. 59 1
      app/public/report/js/rpt_preview_common.js
  34. 39 3
      app/public/report/js/rpt_print.js
  35. 11 7
      app/router.js
  36. 16 7
      app/service/ledger_revise.js
  37. 3 0
      app/service/revise_audit.js
  38. 29 1
      app/service/schedule.js
  39. 54 0
      app/service/schedule_audit.js
  40. 86 2
      app/service/schedule_ledger.js
  41. 11 0
      app/service/schedule_ledger_history.js
  42. 95 1
      app/service/schedule_ledger_month.js
  43. 52 0
      app/service/schedule_stage.js
  44. 4 0
      app/service/stage.js
  45. 0 1
      app/service/stage_bills.js
  46. 1 1
      app/service/stage_bonus.js
  47. 9 0
      app/service/stage_pay.js
  48. 8 8
      app/view/change/information.ejs
  49. 12 0
      app/view/report/rpt_print.ejs
  50. 8 0
      app/view/report/rpt_printA3.ejs
  51. 8 8
      app/view/revise/info.ejs
  52. 158 132
      app/view/schedule/index.ejs
  53. 4 3
      app/view/schedule/ledger.ejs
  54. 64 1
      app/view/schedule/modal.ejs
  55. 2 0
      app/view/schedule/plan.ejs
  56. 11 9
      app/view/schedule/stage_gcl.ejs
  57. 4 0
      app/view/schedule/stage_tp.ejs
  58. 1 0
      app/view/stage_extra/bonus.ejs
  59. 0 2
      app/view/stage_extra/bonus_modal.ejs
  60. 3 0
      app/view/tender/detail.ejs
  61. 224 0
      app/view/tender/detail_modal.ejs
  62. 9 1
      app/view/tender/shenpi_modal.ejs
  63. 10 8
      app/view/tender/tender_sub_menu.ejs
  64. 10 8
      app/view/tender/tender_sub_mini_menu.ejs
  65. 2957 2984
      package-lock.json
  66. 1 1
      package.json
  67. 90 31
      sql/update.sql
  68. 44 0
      sql/update20210129.sql

+ 11 - 0
Dockerfile

@@ -0,0 +1,11 @@
+FROM node:12
+
+COPY . /app/calc
+
+WORKDIR /app/calc
+
+RUN npm install --registery=https://registery.npm.taobao.org
+
+EXPOSE 7005
+
+CMD ["npm", "run", "dev-docker"]

+ 1 - 1
app/base/base_bills_service.js

@@ -594,7 +594,7 @@ class BaseBillsSerivce extends TreeService {
                     unit_price: this.ctx.helper.round(d.unit_price, this.ctx.tender.info.decimal.up),
                     source: d.source,
                     remark: d.remark,
-                    drawing_code: d.drawaing_code,
+                    drawing_code: d.drawing_code,
                     memo: d.memo,
                     node_type: d.node_type,
                     sgfh_expr: d.sgfh_expr,

+ 6 - 0
app/const/schedule.js

@@ -4,7 +4,13 @@ const plan_mode = {
     tp: 1,
     gcl: 2,
 };
+const permission = {
+    no: 0,
+    show: 1,
+    edit: 2,
+}
 
 module.exports = {
     plan_mode,
+    permission,
 };

+ 7 - 3
app/controller/report_controller.js

@@ -342,7 +342,9 @@ module.exports = app => {
             // console.log('after role stage!');
             // console.log(roleRel);
             await encodeSignatureDataUri(roleRel, this.app.baseDir);
-            await encodeDummySignatureDataUri(pageRst, this.app.baseDir); // 注意草图数据量问题!
+            if (params.getPicFlag) {
+                await encodeDummySignatureDataUri(pageRst, this.app.baseDir); // 注意草图数据量问题!
+            }
             const stageFlow = await ctx.service.stageAudit.getAuditGroupByListWithOwner(params.stage_id, params.stage_times);
 
             // console.log('encodeSignatureDataUri!');
@@ -448,8 +450,10 @@ module.exports = app => {
             // console.log('roleRel: ');
             // console.log(roleRel);
             await encodeSignatureDataUri(roleRel, this.app.baseDir);
-            for (const pageRst of pageRstArr) {
-                await encodeDummySignatureDataUri(pageRst, this.app.baseDir); // 注意草图数据量问题!
+            if (params.getPicFlag) {
+                for (const pageRst of pageRstArr) {
+                    await encodeDummySignatureDataUri(pageRst, this.app.baseDir); // 注意草图数据量问题!
+                }
             }
             // console.log(pageRstArr);
             ctx.body = { data: pageRstArr, signatureRelInfo: roleRel, stageAudit: stgAudit, waterMarkStr };

+ 337 - 91
app/controller/schedule_controller.js

@@ -9,6 +9,7 @@
  */
 
 const moment = require('moment');
+const reviseStatus = require('../const/audit').revise.status;
 const measureType = require('../const/tender').measureType;
 const scheduleConst = require('../const/schedule');
 const billsPosConvert = require('../lib/bills_pos_convert');
@@ -27,14 +28,50 @@ module.exports = app => {
             return lastMonth && lastMonth[0] && lastMonth[0].yearmonth ? lastMonth[0].yearmonth : null;
         }
 
+        async _getLastReviseStatus(ctx) {
+            const lastRevise = await ctx.service.ledgerRevise.getLastestRevise(ctx.tender.id);
+            return (lastRevise && lastRevise.status !== reviseStatus.checked) || false;
+        }
+
+        async _checkScheduleCanModify(ctx) {
+            if (await this._getLastReviseStatus(ctx)) {
+                throw '台账修订中,请勿修改提交进度数据';
+            }
+            if (ctx.tender.schedule_permission !== scheduleConst.permission.edit) {
+                throw '权限不足,无法修改进度数据';
+            }
+        }
+
         async index(ctx) {
             try {
+                const schedule = await ctx.service.schedule.getDataByCondition({ tid: ctx.tender.id });
+                const scheduleMonth = await ctx.service.scheduleMonth.getAllDataByCondition({ where: { tid: ctx.tender.id }, orders: [['yearmonth', 'asc']] });
+                const scheduleStage = await ctx.service.scheduleStage.getAllDataByCondition({ where: { tid: ctx.tender.id }, orders: [['yearmonth', 'asc']] });
+                // 汇总并统计前几个计划月总计划额
+                for (const i in scheduleStage) {
+                    let nowIndex = 0;
+                    let lastIndex = 0;
+                    if (i > 0) {
+                        nowIndex = _.findIndex(scheduleMonth, { yearmonth: scheduleStage[i].yearmonth }) + 1;
+                        lastIndex = _.findIndex(scheduleMonth, { yearmonth: scheduleStage[i - 1].yearmonth }) + 1;
+                    } else {
+                        nowIndex = _.findIndex(scheduleMonth, { yearmonth: scheduleStage[i].yearmonth }) + 1;
+                    }
+                    // 获取新计划月数组
+                    const newSm = scheduleMonth.slice(lastIndex, nowIndex);
+                    scheduleStage[i].plan_tp = _.sumBy(newSm, 'plan_tp');
+                }
                 const renderData = {
+                    schedule,
+                    scheduleMonth,
+                    scheduleStage,
                     tender: ctx.tender.data,
                     tenderMenu: this.menu.tenderMenu,
                     planMonth: await this._getLastPlanMonth(ctx),
                     scheduleLedgerList: await this._getSelectedLedgerList(ctx),
                     preUrl: '/tender/' + ctx.tender.id,
+                    scPermission: scheduleConst.permission,
+                    revising: await this._getLastReviseStatus(ctx),
                 };
                 await this.layout('schedule/index.ejs', renderData, 'schedule/modal.ejs');
             } catch (err) {
@@ -45,14 +82,35 @@ module.exports = app => {
 
         async ledger(ctx) {
             const tender = ctx.tender;
+            const schedule = await ctx.service.schedule.getDataByCondition({ tid: ctx.tender.id });
+            const scheduleLedgerList = await this._getSelectedLedgerList(ctx);
+            const allSlmList = await ctx.service.scheduleLedgerMonth.getAllDataByCondition({ where: { tid: ctx.tender.id } });
+            const scheduleStage = await ctx.service.scheduleStage.getAllDataByCondition({ where: { tid: tender.id }, orders: [['order', 'desc']] });
+            const hadDataLidList = [];
+            for (const sl of scheduleLedgerList) {
+                const info = _.find(allSlmList, function(item) {
+                    return item.lid === sl && ((item.plan_tp !== null && item.plan_tp !== 0) ||
+                        (item.plan_gcl !== null && item.plan_gcl !== 0) ||
+                        (item.sj_tp !== null && item.sj_tp !== 0) ||
+                        (item.sj_gcl !== null && item.sj_gcl !== 0));
+                });
+                if (info) {
+                    hadDataLidList.push(info.lid);
+                }
+            }
             const renderData = {
+                schedule,
                 tender: tender.data,
                 tenderInfo: tender.info,
                 measureType,
-                scheduleLedgerList: await this._getSelectedLedgerList(ctx),
+                scheduleLedgerList,
+                hadDataLidList,
+                scheduleStage,
+                scPermission: scheduleConst.permission,
                 jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.schedule.ledger),
+                revising: await this._getLastReviseStatus(ctx),
             };
-            await this.layout('schedule/ledger.ejs', renderData);
+            await this.layout('schedule/ledger.ejs', renderData, 'schedule/modal.ejs');
         }
 
         async plan(ctx) {
@@ -67,8 +125,10 @@ module.exports = app => {
                 planMonth: await this._getLastPlanMonth(ctx),
                 measureType,
                 mode: scheduleConst.plan_mode,
+                scPermission: scheduleConst.permission,
                 scheduleLedgerList: await this._getSelectedLedgerList(ctx),
                 jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.schedule.plan),
+                revising: await this._getLastReviseStatus(ctx),
             };
             await this.layout('schedule/plan.ejs', renderData, 'schedule/plan_modal.ejs');
         }
@@ -76,44 +136,8 @@ module.exports = app => {
         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 stageOrderList = await ctx.service.stage.getAllDataByCondition({ columns: ['id', 's_time', 'order'], where: { tid: tender.id } });
-            const scheduleStage = await ctx.service.scheduleStage.getAllDataByCondition({ where: { tid: tender.id }, orders: [['order', 'desc']] });
-            let curScheduleStage = scheduleStage.length > 0 ? _.maxBy(scheduleStage, 'order') : null;
-            let slmList = [];
-            let nextSlmList = [];
-            let endSlmList = [];
-            let yearSlmList = [];
-            let curYearStageData = [];
-            if (ctx.params.order && scheduleStage.length > 0) {
-                curScheduleStage = _.find(scheduleStage, { order: parseInt(ctx.params.order) });
-            }
-            if (curScheduleStage) {
-                const newSS = _.sortBy(scheduleStage, 'yearmonth');
-                const nowScheduleStage = _.findIndex(newSS, { yearmonth: curScheduleStage.yearmonth });
-                slmList = await ctx.service.scheduleLedgerMonth.getAllDataByCondition({ where: { tid: tender.id, yearmonth: curScheduleStage.yearmonth } });
-                const nextScheduleStage = nowScheduleStage >= 0 && nowScheduleStage + 1 <= newSS.length - 1 ? newSS[nowScheduleStage + 1] : null;
-                if (nextScheduleStage) nextSlmList = await ctx.service.scheduleLedgerMonth.getAllDataByCondition({ where: { tid: tender.id, yearmonth: nextScheduleStage.yearmonth } });
-                if (nowScheduleStage === 0) {
-                    endSlmList = slmList;
-                } else if (nowScheduleStage > 0) {
-                    const endYearmonthCollection = _.map(_.take(newSS, nowScheduleStage + 1), 'yearmonth');
-                    endSlmList = await ctx.service.scheduleLedgerMonth.getConllectionList(tender.id, endYearmonthCollection);
-                }
-                const yearConllection = _.map(_.filter(newSS, function(item) {
-                    return item.yearmonth.indexOf(curScheduleStage.yearmonth.split('-')[0]) !== -1;
-                }), 'yearmonth');
-                yearSlmList = await ctx.service.scheduleLedgerMonth.getConllectionList(tender.id, yearConllection);
-                // 获取本年完成计量数据
-                const curStage = _.find(stageOrderList, { order: curScheduleStage.order });
-                const stageList = _.filter(stageOrderList, function(item) {
-                    return item.s_time.indexOf(curStage.s_time.split('-')[0]) !== -1;
-                });
-                const stageIdList = _.map(_.filter(stageList, function(item) {
-                    return _.find(newSS, { order: item.order });
-                }), 'id');
-                curYearStageData = await ctx.service.stageBills.getStagesData(ctx.tender.id, stageIdList.join(','));
-            }
+            const { slmList, nextSlmList, endSlmList, yearSlmList, curYearStageData,
+                scheduleMonth, stageOrderList, scheduleStage, curScheduleStage } = await this._getStageAndPlanData(ctx);
             const renderData = {
                 tender: tender.data,
                 tenderInfo: tender.info,
@@ -128,7 +152,9 @@ module.exports = app => {
                 endSlmList,
                 yearSlmList,
                 curYearStageData,
+                scPermission: scheduleConst.permission,
                 scheduleLedgerList: await this._getSelectedLedgerList(ctx),
+                revising: await this._getLastReviseStatus(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');
@@ -138,17 +164,21 @@ module.exports = app => {
             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 scheduleStage = await ctx.service.scheduleStage.getAllDataByCondition({ where: { tid: tender.id }, orders: [['order', 'desc']] });
-            const curScheduleStage = scheduleStage.length > 0 ? _.maxBy(scheduleStage, 'order') : null;
+            // const scheduleStage = await ctx.service.scheduleStage.getAllDataByCondition({ where: { tid: tender.id }, orders: [['order', 'desc']] });
+            const gclScheduleMonth = _.orderBy(_.filter(scheduleMonth, { stage_gcl_used: 1 }), ['yearmonth'], ['desc']);
+            const curScheduleMonth = scheduleMonth.length > 0 ? _.maxBy(gclScheduleMonth, 'yearmonth') : null;
             const renderData = {
                 tender: tender.data,
                 tenderInfo: tender.info,
                 schedule,
                 scheduleMonth,
                 measureType,
-                scheduleStage,
-                curScheduleStage,
+                // scheduleStage,
+                curScheduleMonth,
+                gclScheduleMonth,
+                scPermission: scheduleConst.permission,
                 scheduleLedgerList: await this._getSelectedLedgerList(ctx),
+                revising: await this._getLastReviseStatus(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');
@@ -162,57 +192,224 @@ module.exports = app => {
          */
         async loadTpLedgerData(ctx) {
             try {
-                const tender = ctx.tender;
-                // const ledgerData = await ctx.controller.stage._getStageLedgerData(ctx);
-                // console.log(ledgerData);
-                const stageOrderList = await ctx.service.stage.getAllDataByCondition({ columns: ['id', 's_time', 'order'], where: { tid: tender.id } });
-                const scheduleStage = await ctx.service.scheduleStage.getAllDataByCondition({ where: { tid: tender.id }, orders: [['order', 'desc']] });
-                const curScheduleStage = _.find(scheduleStage, { order: parseInt(ctx.params.order) });
-                let slmList = [];
-                let nextSlmList = [];
-                let endSlmList = [];
-                let yearSlmList = [];
-                let curYearStageData = [];
-                if (curScheduleStage) {
-                    const newSS = _.sortBy(scheduleStage, 'yearmonth');
-                    const nowScheduleStage = _.findIndex(newSS, { yearmonth: curScheduleStage.yearmonth });
-                    slmList = await ctx.service.scheduleLedgerMonth.getAllDataByCondition({ where: { tid: tender.id, yearmonth: curScheduleStage.yearmonth } });
-                    const nextScheduleStage = nowScheduleStage >= 0 && nowScheduleStage + 1 <= newSS.length - 1 ? newSS[nowScheduleStage + 1] : null;
-                    if (nextScheduleStage) nextSlmList = await ctx.service.scheduleLedgerMonth.getAllDataByCondition({ where: { tid: tender.id, yearmonth: nextScheduleStage.yearmonth } });
-                    if (nowScheduleStage === 0) {
-                        endSlmList = slmList;
-                    } else if (nowScheduleStage > 0) {
-                        const endYearmonthCollection = _.map(_.take(newSS, nowScheduleStage + 1), 'yearmonth');
-                        endSlmList = await ctx.service.scheduleLedgerMonth.getConllectionList(tender.id, endYearmonthCollection);
-                    }
-                    const yearConllection = _.map(_.filter(newSS, function(item) {
-                        return item.yearmonth.indexOf(curScheduleStage.yearmonth.split('-')[0]) !== -1;
-                    }), 'yearmonth');
-                    yearSlmList = await ctx.service.scheduleLedgerMonth.getConllectionList(tender.id, yearConllection);
-                    // 获取本年完成计量数据
-                    const curStage = _.find(stageOrderList, { order: curScheduleStage.order });
-                    const stageList = _.filter(stageOrderList, function(item) {
-                        return item.s_time.indexOf(curStage.s_time.split('-')[0]) !== -1;
-                    });
-                    const stageIdList = _.map(_.filter(stageList, function(item) {
-                        return _.find(newSS, { order: item.order });
-                    }), 'id');
-                    curYearStageData = await ctx.service.stageBills.getStagesData(tender.id, stageIdList.join(','));
+                const ledgerData = await this._getStageLedgerData(ctx, ctx.params.order);
+                const postData = { ledgerData };
+                const data = JSON.parse(ctx.request.body.data);
+                if (data.filter && data.filter === 'gcl') {
+                    const { slmList, nextSlmList, endSlmList, yearSlmList, curYearStageData } = await this._getStageAndPlanData(ctx);
+                    _.assignIn(postData, { slmList, nextSlmList, endSlmList, yearSlmList, curYearStageData });
                 }
-                ctx.body = { err: 0, msg: '', data: {
-                    // ledgerData,
-                    slmList,
-                    nextSlmList,
-                    endSlmList,
-                    yearSlmList,
-                    curYearStageData,
-                } };
+                ctx.body = { err: 0, msg: '', data: postData };
+            } catch (err) {
+                this.log(err);
+                ctx.body = { err: 1, msg: err.toString(), data: [] };
+            }
+        }
+        /**
+         * 获取工程量模式下台账数据(Ajax)
+         *
+         * @param ctx
+         * @return {Promise<void>}
+         */
+        async loadGclLedgerData(ctx) {
+            try {
+                const ledgerData = await ctx.service.ledger.getData(ctx.tender.id);
+                const postData = { ledgerData };
+                const { slmList, nextSlmList, endSlmList, yearSlmList } = await this._getGclAndPlanData(ctx);
+                _.assignIn(postData, { slmList, nextSlmList, endSlmList, yearSlmList });
+                ctx.body = { err: 0, msg: '', data: postData };
             } catch (err) {
                 this.log(err);
                 ctx.body = { err: 1, msg: err.toString(), data: [] };
             }
         }
 
+
+        /**
+         * 获取所有期下台账数据(Ajax)
+         *
+         * @param ctx
+         * @return {Promise<void>}
+         */
+        async _getAllStageLedgerData(ctx) {
+            let ledgerData = await ctx.service.ledger.getData(ctx.tender.id);
+            const stageList = await ctx.service.scheduleStage.getAllDataByCondition({ where: { tid: ctx.tender.id }, orders: [['order', 'desc']] });
+            if (stageList.length > 0) {
+                const dgnData = await ctx.service.stageBillsDgn.getDgnData(ctx.tender.id);
+                for (const d of dgnData) {
+                    const l = ctx.app._.find(ledgerData, { id: d.id });
+                    ctx.app._.assignIn(l, d);
+                }
+                for (const s of stageList) {
+                    const stageInfo = await ctx.service.stage.getDataByCondition({
+                        tid: ctx.tender.id,
+                        order: s.order,
+                    });
+                    // let preStageData;
+                    // 当前操作人查看最新数据,其他人查看历史数据
+                    const curStageData = await ctx.service.stageBills.getLastestStageData(ctx.tender.id, stageInfo.id);
+                    // // 查询截止上期数据
+                    // if (stageInfo.order > 1) {
+                    //     preStageData = await ctx.service.stageBillsFinal.getFinalData(ctx.tender.data, stageInfo.order - 1);
+                    // } else {
+                    //     preStageData = [];
+                    // }
+                    this.ctx.helper.assignRelaData(ledgerData, [
+                        { data: curStageData, fields: ['contract_tp', 'qc_tp'], prefix: s.order + '_', relaId: 'lid' },
+                        // { data: preStageData, fields: ['contract_qty', 'contract_tp', 'qc_qty', 'qc_tp', 'used'], prefix: s.order + '_pre_', relaId: 'lid' },
+                    ]);
+                }
+                ledgerData = await this._addFinalStageData(ctx, ledgerData);
+            }
+            return ledgerData;
+        }
+
+        async _getStageLedgerData(ctx, stageOrder) {
+            let ledgerData = await ctx.service.ledger.getData(ctx.tender.id);
+            const dgnData = await ctx.service.stageBillsDgn.getDgnData(ctx.tender.id);
+            for (const d of dgnData) {
+                const l = ctx.app._.find(ledgerData, { id: d.id });
+                ctx.app._.assignIn(l, d);
+            }
+            const stageInfo = await ctx.service.stage.getDataByCondition({
+                tid: ctx.tender.id,
+                order: stageOrder,
+            });
+            let preStageData;
+            // 当前操作人查看最新数据,其他人查看历史数据
+            const curStageData = await ctx.service.stageBills.getLastestStageData(ctx.tender.id, stageInfo.id);
+            // 查询截止上期数据
+            if (stageInfo.order > 1) {
+                preStageData = await ctx.service.stageBillsFinal.getFinalData(ctx.tender.data, stageInfo.order - 1);
+            } else {
+                preStageData = [];
+            }
+            this.ctx.helper.assignRelaData(ledgerData, [
+                { data: curStageData, fields: ['contract_qty', 'contract_expr', 'contract_tp', 'qc_qty', 'qc_tp', 'postil'], prefix: '', relaId: 'lid' },
+                { data: preStageData, fields: ['contract_qty', 'contract_tp', 'qc_qty', 'qc_tp', 'used'], prefix: 'pre_', relaId: 'lid' },
+            ]);
+            // 获取进度最新期的数据
+            ledgerData = await this._addFinalStageData(ctx, ledgerData);
+            return ledgerData;
+        }
+
+        /**
+         * 添加最新一期的进度期下期数据到台账数据中(Ajax)
+         *
+         * @param ctx
+         * @return {Promise<void>}
+         */
+        async _addFinalStageData(ctx, ledgerData) {
+            const scheduleStage = await ctx.service.scheduleStage.getAllDataByCondition({ where: { tid: ctx.tender.id }, orders: [['order', 'desc']] });
+            if (scheduleStage && scheduleStage.length > 0) {
+                let prefinalStageData;
+                const finalStageInfo = await ctx.service.stage.getDataByCondition({
+                    tid: ctx.tender.id,
+                    order: scheduleStage[0].order,
+                });
+                const finalStageData = await ctx.service.stageBills.getLastestStageData(ctx.tender.id, finalStageInfo.id);
+                if (finalStageInfo.order > 1) {
+                    prefinalStageData = await ctx.service.stageBillsFinal.getFinalData(ctx.tender.data, finalStageInfo.order - 1);
+                } else {
+                    prefinalStageData = [];
+                }
+                this.ctx.helper.assignRelaData(ledgerData, [
+                    { data: finalStageData, fields: ['contract_qty', 'contract_expr', 'contract_tp', 'qc_qty', 'qc_tp', 'postil'], prefix: 'final_', relaId: 'lid' },
+                    { data: prefinalStageData, fields: ['contract_qty', 'contract_tp', 'qc_qty', 'qc_tp', 'used'], prefix: 'pre_final_', relaId: 'lid' },
+                ]);
+            }
+            return ledgerData;
+        }
+
+        /**
+         * 获取本期下台账计量和计划数据(Ajax)
+         *
+         * @param ctx
+         * @return {Promise<void>}
+         */
+        async _getStageAndPlanData(ctx) {
+            const tender = ctx.tender;
+            const scheduleMonth = await ctx.service.scheduleMonth.getAllDataByCondition({ where: { tid: tender.id }, orders: [['yearmonth', 'asc']] });
+            const stageOrderList = await ctx.service.stage.getAllDataByCondition({ columns: ['id', 's_time', 'order'], where: { tid: tender.id } });
+            const scheduleStage = await ctx.service.scheduleStage.getAllDataByCondition({ where: { tid: tender.id }, orders: [['yearmonth', 'desc']] });
+            let curScheduleStage = scheduleStage.length > 0 ? _.maxBy(scheduleStage, 'yearmonth') : null;
+            if (ctx.params.order && scheduleStage.length > 0) {
+                curScheduleStage = _.find(scheduleStage, { order: parseInt(ctx.params.order) });
+            }
+            let slmList = [];
+            let nextSlmList = [];
+            let endSlmList = [];
+            let yearSlmList = [];
+            let curYearStageData = [];
+            if (curScheduleStage) {
+                const newSM = _.sortBy(scheduleMonth, 'yearmonth');
+                const nowScheduleStage = _.findIndex(newSM, { yearmonth: curScheduleStage.yearmonth });
+                slmList = await ctx.service.scheduleLedgerMonth.getAllDataByCondition({ where: { tid: tender.id, yearmonth: curScheduleStage.yearmonth } });
+                const nextScheduleStage = nowScheduleStage >= 0 && nowScheduleStage + 1 <= newSM.length - 1 ? newSM[nowScheduleStage + 1] : null;
+                if (nextScheduleStage) nextSlmList = await ctx.service.scheduleLedgerMonth.getAllDataByCondition({ where: { tid: tender.id, yearmonth: nextScheduleStage.yearmonth } });
+                if (nowScheduleStage === 0) {
+                    endSlmList = slmList;
+                } else if (nowScheduleStage > 0) {
+                    const endYearmonthCollection = _.map(_.take(newSM, nowScheduleStage + 1), 'yearmonth');
+                    endSlmList = await ctx.service.scheduleLedgerMonth.getConllectionList(tender.id, endYearmonthCollection);
+                }
+                const yearConllection = _.map(_.filter(newSM, function(item) {
+                    return item.yearmonth.indexOf(curScheduleStage.yearmonth.split('-')[0]) !== -1;
+                }), 'yearmonth');
+                yearSlmList = await ctx.service.scheduleLedgerMonth.getConllectionList(tender.id, yearConllection);
+                // 获取本年完成计量数据
+                const curStage = _.find(stageOrderList, { order: curScheduleStage.order });
+                const stageList = _.filter(stageOrderList, function(item) {
+                    return item.s_time.indexOf(curStage.s_time.split('-')[0]) !== -1;
+                });
+                // const newSS = _.sortBy(scheduleStage, 'yearmonth');
+                // const stageIdList = _.map(_.filter(stageList, function(item) {
+                //     return _.find(newSS, { order: item.order });
+                // }), 'id');
+                const stageIdList = _.map(stageList, 'id');
+                curYearStageData = await ctx.service.stageBills.getStagesData(tender.id, stageIdList.join(','));
+            }
+            return { slmList, nextSlmList, endSlmList, yearSlmList, curYearStageData, scheduleMonth, stageOrderList, scheduleStage, curScheduleStage };
+        }
+
+        /**
+         * 获取工程量模式汇总下台账的计划数据(Ajax)
+         *
+         * @param ctx
+         * @return {Promise<void>}
+         */
+        async _getGclAndPlanData(ctx) {
+            const tender = ctx.tender;
+            const scheduleMonth = await ctx.service.scheduleMonth.getAllDataByCondition({ where: { tid: tender.id }, orders: [['yearmonth', 'asc']] });
+            let curScheduleMonth = scheduleMonth.length > 0 ? _.maxBy(scheduleMonth, 'order') : null;
+            const gclScheduleMonth = _.filter(scheduleMonth, { stage_gcl_used: 1 });
+            if (ctx.params.order && gclScheduleMonth.length > 0) {
+                curScheduleMonth = _.find(gclScheduleMonth, { id: parseInt(ctx.params.order) });
+            }
+            let slmList = [];
+            let nextSlmList = [];
+            let endSlmList = [];
+            let yearSlmList = [];
+            if (curScheduleMonth) {
+                const newSM = _.sortBy(scheduleMonth, 'yearmonth');
+                const nowScheduleMonth = _.findIndex(newSM, { yearmonth: curScheduleMonth.yearmonth });
+                slmList = await ctx.service.scheduleLedgerMonth.getAllDataByCondition({ where: { tid: tender.id, yearmonth: curScheduleMonth.yearmonth } });
+                const nextScheduleMonth = nowScheduleMonth >= 0 && nowScheduleMonth + 1 <= newSM.length - 1 ? newSM[nowScheduleMonth + 1] : null;
+                if (nextScheduleMonth) nextSlmList = await ctx.service.scheduleLedgerMonth.getAllDataByCondition({ where: { tid: tender.id, yearmonth: nextScheduleMonth.yearmonth } });
+                if (nowScheduleMonth === 0) {
+                    endSlmList = slmList;
+                } else if (nowScheduleMonth > 0) {
+                    const endYearmonthCollection = _.map(_.take(newSM, nowScheduleMonth + 1), 'yearmonth');
+                    endSlmList = await ctx.service.scheduleLedgerMonth.getConllectionList(tender.id, endYearmonthCollection);
+                }
+                const yearConllection = _.map(_.filter(newSM, function(item) {
+                    return item.yearmonth.indexOf(curScheduleMonth.yearmonth.split('-')[0]) !== -1;
+                }), 'yearmonth');
+                yearSlmList = await ctx.service.scheduleLedgerMonth.getConllectionList(tender.id, yearConllection);
+            }
+            return { slmList, nextSlmList, endSlmList, yearSlmList, scheduleMonth, curScheduleMonth };
+        }
+
         /**
          * 获取台账数据(Ajax)
          *
@@ -221,14 +418,16 @@ module.exports = app => {
          */
         async loadLedgerData(ctx) {
             try {
-                const ledgerData = await ctx.service.ledger.getData(ctx.tender.id);
+                // const ledgerData = await ctx.service.ledger.getData(ctx.tender.id);
+                const ledgerData = await this._getAllStageLedgerData(ctx);
                 // 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 } };
+                const scheduleLedgerMonthData = await ctx.service.scheduleLedgerMonth.getAllDataByCondition({ where: { tid: ctx.tender.id } });
+                const scheduleLedgerHistoryData = await ctx.service.scheduleLedgerHistory.getAllDataByCondition({ where: { tid: ctx.tender.id } });
+                ctx.body = { err: 0, msg: '', data: { bills: ledgerData, slm: scheduleLedgerMonthData, slh: scheduleLedgerHistoryData } };
             } catch (err) {
                 this.log(err);
                 ctx.body = { err: 1, msg: err.toString(), data: [] };
@@ -243,6 +442,7 @@ module.exports = app => {
          */
         async saveLedger(ctx) {
             try {
+                await this._checkScheduleCanModify(ctx);
                 const data = JSON.parse(ctx.request.body.data);
                 const result = await ctx.service.scheduleLedger.saveLedger(data);
                 ctx.body = { err: 0, msg: '', data: result };
@@ -260,6 +460,7 @@ module.exports = app => {
          */
         async savePlan(ctx) {
             try {
+                await this._checkScheduleCanModify(ctx);
                 const data = JSON.parse(ctx.request.body.data);
                 const responseData = {
                     err: 0,
@@ -279,6 +480,9 @@ module.exports = app => {
                     case 'ledger_edit':
                         responseData.data = await ctx.service.scheduleLedgerMonth.save(data.postData);
                         break;
+                    case 'ledger_paste':
+                        responseData.data = await ctx.service.scheduleLedgerMonth.saveDatas(data.postData);
+                        break;
                     default: throw '参数有误';
                 }
                 ctx.body = responseData;
@@ -296,6 +500,7 @@ module.exports = app => {
          */
         async saveStageTp(ctx) {
             try {
+                await this._checkScheduleCanModify(ctx);
                 const data = JSON.parse(ctx.request.body.data);
                 const responseData = {
                     err: 0,
@@ -312,6 +517,9 @@ module.exports = app => {
                     case 'reload_stage':
                         responseData.data = await ctx.service.scheduleStage.changeOrder(data.postData);
                         break;
+                    case 'update_tp':
+                        responseData.data = await ctx.service.scheduleStage.updateOneTp(data.postData);
+                        break;
                     default: throw '参数有误';
                 }
                 ctx.body = responseData;
@@ -329,6 +537,7 @@ module.exports = app => {
          */
         async saveStageGcl(ctx) {
             try {
+                await this._checkScheduleCanModify(ctx);
                 const data = JSON.parse(ctx.request.body.data);
                 const responseData = {
                     err: 0,
@@ -345,6 +554,9 @@ module.exports = app => {
                     case 'ledger_edit':
                         responseData.data = await ctx.service.scheduleLedgerMonth.saveSj(data.postData);
                         break;
+                    case 'ledger_paste':
+                        responseData.data = await ctx.service.scheduleLedgerMonth.saveSjDatas(data.postData);
+                        break;
                     default: throw '参数有误';
                 }
                 ctx.body = responseData;
@@ -353,6 +565,40 @@ module.exports = app => {
                 ctx.body = { err: 1, msg: err.toString(), data: null };
             }
         }
+
+        async saveAudit(ctx) {
+            try {
+                const data = JSON.parse(ctx.request.body.data);
+                if (!data) {
+                    throw '提交数据错误';
+                }
+                // 判断修改权限
+                if (ctx.session.sessionUser.is_admin === 0) {
+                    throw '你没有权限修改形象进度';
+                }
+                let info = '';
+                switch (data.type) {
+                    case 'add':
+                        const result = await ctx.service.scheduleAudit.addAudit(data);
+                        if (!result) {
+                            throw '添加审批人失败';
+                        }
+                        info = result;
+                        break;
+                    case 'del':
+                        await ctx.service.scheduleAudit.removeAudit(data);
+                        break;
+                    case 'edit':
+                        await ctx.service.scheduleAudit.editAudit(data);
+                        break;
+                    default:break;
+                }
+                ctx.body = { err: 0, msg: '', data: info };
+            } catch (err) {
+                this.log(err);
+                ctx.body = this.ajaxErrorBody(err, '保存审批流程设置失败');
+            }
+        }
     }
 
     return ScheduleController;

+ 10 - 4
app/controller/stage_extra_controller.js

@@ -91,7 +91,8 @@ module.exports = app => {
         async bonus (ctx) {
             try {
                 const renderData = {
-                    jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.stageExtra.bonus)
+                    jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.stageExtra.bonus),
+                    auditConst: auditConst,
                 };
                 await this.layout('stage_extra/bonus.ejs', renderData, 'stage_extra/bonus_modal.ejs');
             } catch (err) {
@@ -151,7 +152,7 @@ module.exports = app => {
             try {
                 const renderData = {
                     jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.stageExtra.other)
-                }
+                };
                 await this.layout('stage_extra/other.ejs', renderData);
             } catch (err) {
                 ctx.helper.log(err);
@@ -205,7 +206,7 @@ module.exports = app => {
                 const create_time = Date.parse(new Date()) / 1000;
                 let stream = await parts();
                 const bonus = await ctx.service.stageBonus.getStageDataById(parts.field.bonus_id);
-                if (!bonus || bonus.sid !== ctx.stage.id) throw '该奖罚金,当前不允许上传附件';
+                //if (!bonus || bonus.sid !== ctx.stage.id) throw '该奖罚金,当前不允许上传附件';
                 while (stream !== undefined) {
                     if (!stream.filename) {
                         throw '未发现上传文件!';
@@ -227,6 +228,7 @@ module.exports = app => {
                         filepath: path.join(dirName, fileName),
                         uid: ctx.session.sessionUser.accountId,
                         in_time: moment(create_time * 1000).format('YYYY-MM-DD'),
+                        renew: bonus.sid === ctx.stage.id ? ctx.stage.status === auditConst.status.checked : true,
                     });
                     ++index;
                     if (Array.isArray(parts.field.size) && index < parts.field.size.length) {
@@ -295,9 +297,13 @@ module.exports = app => {
                 const data = JSON.parse(ctx.request.body.data);
 
                 const bonus = await ctx.service.stageBonus.getStageDataById(data.b_id);
-                if (!bonus || !bonus.proof_file || !bonus.proof_file[data.index]) throw '删除的文件不存在'
+                if (!bonus || !bonus.proof_file || !bonus.proof_file[data.index]) throw '删除的文件不存在';
 
                 const fileInfo = bonus.proof_file[data.index];
+                if (fileInfo.uid !== ctx.session.sessionUser.accountId) throw '您无权删除该文件';
+
+                if (ctx.stage.status === auditConst.status.checked && !fileInfo.renew) throw '不可删除该文件';
+
                 // 先删除文件
                 await fs.unlinkSync(path.join(this.app.baseDir, fileInfo.filepath));
                 // 再删除数据库

+ 17 - 0
app/controller/tender_controller.js

@@ -20,6 +20,7 @@ const measureType = require('../const/tender').measureType;
 const billsPosConvert = require('../lib/bills_pos_convert');
 const path = require('path');
 const sendToWormhole = require('stream-wormhole');
+const scheduleConst = require('../const/schedule');
 
 module.exports = app => {
 
@@ -452,6 +453,22 @@ module.exports = app => {
                     audit: auditConst,
                     jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.tender.tenderInfo),
                 };
+                if (ctx.session.sessionProject.page_show !== null && parseInt(ctx.session.sessionProject.page_show.xxjd) === 1 && ctx.session.sessionUser.is_admin) {
+                    // 形象进度内容
+                    // 获取所有项目参与者
+                    const accountList = await ctx.service.projectAccount.getAllDataByCondition({
+                        where: { project_id: ctx.session.sessionProject.id, enable: 1 },
+                        columns: ['id', 'name', 'company', 'role', 'enable', 'is_admin', 'account_group', 'mobile'],
+                    });
+                    const accountGroupList = accountGroup.map((item, idx) => {
+                        const groupList = accountList.filter(item => item.account_group === idx);
+                        return { groupName: item, groupList };
+                    });
+                    renderData.scheduleAuditList = await ctx.service.scheduleAudit.getAllDataByCondition({ where: { tid: tender.id } });
+                    renderData.accountList = accountList;
+                    renderData.accountGroup = accountGroupList;
+                    renderData.scPermission = scheduleConst.permission;
+                }
                 await this.layout('tender/detail.ejs', renderData, 'tender/detail_modal.ejs');
             } catch (error) {
                 this.log(error);

+ 43 - 0
app/middleware/schedule_check.js

@@ -0,0 +1,43 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author EllisRan
+ * @date
+ * @version
+ */
+const scPermission = require('../const/schedule').permission;
+module.exports = options => {
+    /**
+     * 形象进度校验 中间件
+     *
+     * @param {function} next - 中间件继续执行的方法
+     * @return {void}
+     */
+    return function* scheduleCheck(next) {
+        try {
+            if (this.tender.schedule_permission === scPermission.no) {
+                throw '您无权查看该内容';
+            }
+            yield next;
+        } catch (err) {
+            // 输出错误到日志
+            if (err.stack) {
+                this.logger.error(err);
+            } else {
+                this.getLogger('fail').info(JSON.stringify({
+                    error: err,
+                    project: this.session.sessionProject,
+                    user: this.session.sessionUser,
+                    body: this.session.body,
+                }));
+            }
+            if (this.helper.isWap(this.request)) {
+                this.redirect('/wap/list');
+            } else {
+                err === '您无权查看该内容' ? this.redirect(this.request.headers.referer) : this.redirect('/list');
+            }
+        }
+    };
+};

+ 12 - 0
app/middleware/tender_check.js

@@ -10,6 +10,7 @@
 
 const auditConst = require('../const/audit').ledger;
 const messageType = require('../const/message_type');
+const scPermission = require('../const/schedule').permission;
 
 module.exports = options => {
     /**
@@ -74,6 +75,17 @@ module.exports = options => {
             tender.ledgerUsers = tender.ledger_status === auditConst.status.uncheck ? [tender.data.user_id] : [tender.data.user_id, ...auditorsId];
             this.tender = tender;
             this.session.sessionProject.page_show = yield this.service.project.getPageshow(this.session.sessionProject.id);
+            // 形象进度权限获取
+            let schedule_permission = scPermission.no;
+            if (this.session.sessionUser.accountId === tender.data.user_id) {
+                schedule_permission = scPermission.edit;
+            } else {
+                const scheduleUser = yield this.service.scheduleAudit.getDataByCondition({ tid: tender.id, audit_id: this.session.sessionUser.accountId });
+                if (scheduleUser) {
+                    schedule_permission = scheduleUser.permission;
+                }
+            }
+            tender.schedule_permission = schedule_permission;
             yield next;
         } catch (err) {
             // 输出错误到日志

+ 11 - 0
app/public/js/change_information_set.js

@@ -256,6 +256,7 @@ $(document).ready(() => {
                     select.spamount = ZhCalc.round(select.camount, findDecimal(select.unit)) || 0;
                 }
                 console.log(select);
+                delete select.waitingLoading;
 
                 // 更新至服务器
                 postData(window.location.pathname + '/save', { type:'update', updateData: select }, function (result) {
@@ -731,6 +732,12 @@ $(document).ready(() => {
             toastr.error('工程名称不能为空!');
             return;
         }
+        // 换行更改并提交
+        changeInfo.content = changeInfo.content.replace(/<br><br>/g, '\r\n');
+        changeInfo.basis = changeInfo.basis.replace(/<br><br>/g, '\r\n');
+        changeInfo.expr = changeInfo.expr.replace(/<br><br>/g, '\r\n');
+        changeInfo.memo = changeInfo.memo.replace(/<br><br>/g, '\r\n');
+        // 后改为br
         // 更新至服务器
         postData(window.location.pathname + '/save', { type:'info', updateData: changeInfo }, function (result) {
             $('.reduction-code').attr('data-code', $('input[name="code"]').val());
@@ -738,6 +745,10 @@ $(document).ready(() => {
             $('#show-save-btn').hide();
             $('#sp-btn').show();
             $('.title-main').removeClass('bg-warning');
+            changeInfo.content = changeInfo.content.replace(/[\r\n]/g, '<br>');
+            changeInfo.basis = changeInfo.basis.replace(/[\r\n]/g, '<br>');
+            changeInfo.expr = changeInfo.expr.replace(/[\r\n]/g, '<br>');
+            changeInfo.memo = changeInfo.memo.replace(/[\r\n]/g, '<br>');
             back_changeInfo = Object.assign({}, changeInfo);
         });
         return false;

+ 7 - 0
app/public/js/global.js

@@ -103,6 +103,13 @@ $(function(){
     });
 });
 
+function checkShowLast (count) {
+    if (count > 12500) {
+        $('a[name=showLevel][tag=last]').hide();
+    } else {
+        $('a[name=showLevel][tag=last]').show();
+    }
+}
 
 /**
  * 提示框

+ 49 - 23
app/public/js/ledger.js

@@ -59,6 +59,7 @@ $(document).ready(function() {
     autoFlashHeight();
     // 初始化台账
     const ledgerSpread = SpreadJsObj.createNewSpread($('#ledger-spread')[0]);
+    removeLocalCache('bills-fold');
     const treeSetting = {
         id: 'ledger_id',
         pid: 'ledger_pid',
@@ -68,8 +69,11 @@ $(document).ready(function() {
         keys: ['id', 'tender_id', 'ledger_id'],
         preUrl: '/tender/' + getTenderId() + '/ledger',
         //treeCacheKey: 'ledger_bills_fold' + '_' + getTenderId(),
-        markFoldKey: 'bills-fold',
-        markFoldSubKey: window.location.pathname.split('/')[2],
+        autoExpand: 3,
+        // markFoldKey: 'bills-fold',
+        // markFoldSubKey: window.location.pathname.split('/')[2],
+        markExpandKey: 'bills-expand',
+        markExpandSubKey: window.location.pathname.split('/')[2],
     };
     if (checkTzMeasureType()) {
         treeSetting.calcFields = ['sgfh_tp', 'sjcl_tp', 'qtcl_tp', 'total_price'];
@@ -1410,8 +1414,8 @@ $(document).ready(function() {
                 return !readOnly;
             }
         };
-        billsContextMenuOptions.items.sprBlock = '----';
     }
+    billsContextMenuOptions.items.sprBlock = '----';
     if (!readOnly) {
         billsContextMenuOptions.items.sortChildren = {
             name: '顺序重排子项编号',
@@ -1542,6 +1546,7 @@ $(document).ready(function() {
                             SpreadJsObj.loadSheetData(ledgerSpread.getActiveSheet(), 'tree', ledgerTree);
                             pos.loadDatas(result.pos);
                             posOperationObj.loadCurPosData();
+                            checkShowLast(result.bills.length);
                         }, null);
                     },
                     // callback: function (file, select) {
@@ -1617,6 +1622,21 @@ $(document).ready(function() {
             return !node;
         }
     };
+    billsContextMenuOptions.items.sprTag = '----';
+    billsContextMenuOptions.items.showLast = {
+        name: '显示至最底层',
+        callback: function (key, opt, menu, e) {
+            const node = SpreadJsObj.getSelectObject(ledgerSpread.getActiveSheet());
+            setTimeout(() => {
+                showWaitingView();
+                ledgerTree.expandByCustom(x => {
+                    return x.expanded ||(x.id === node.id) || (x.full_path.indexOf(node.ledger_id + '-') >= 0);
+                });
+                SpreadJsObj.refreshTreeRowVisible(ledgerSpread.getActiveSheet());
+                closeWaitingView();
+            }, 100);
+        },
+    };
 
     $.contextMenu(billsContextMenuOptions);
 
@@ -2179,6 +2199,8 @@ $(document).ready(function() {
         SpreadJsObj.loadSheetData(ledgerSpread.getActiveSheet(), 'tree', ledgerTree);
         SpreadJsObj.loadTopAndSelect(ledgerSpread.getActiveSheet(), ckBillsSpread);
 
+        checkShowLast(data.bills.length);
+
         pos.loadDatas(data.pos);
         posOperationObj.loadCurPosData();
         SpreadJsObj.resetTopAndSelect(posSpread.getActiveSheet());
@@ -3126,7 +3148,7 @@ $(document).ready(function() {
                 }
             }
         }, 400);
-    })
+    });
 
     // 添加审批流程按钮逻辑
     $('.book-list').on('click', 'dt', function () {
@@ -3145,7 +3167,7 @@ $(document).ready(function() {
             })
         }
         return false
-    })
+    });
 
     // 添加到审批流程中
     $('dl').on('click', 'dd', function () {
@@ -3232,24 +3254,28 @@ $(document).ready(function() {
             const tag = $(this).attr('tag');
             const tree = sheet.zh_tree;
             if (!tree) return;
-            switch (tag) {
-                case "1":
-                case "2":
-                case "3":
-                case "4":
-                case "5":
-                    tree.expandByLevel(parseInt(tag));
-                    SpreadJsObj.refreshTreeRowVisible(sheet);
-                    break;
-                case "last":
-                    tree.expandByCustom(() => { return true; });
-                    SpreadJsObj.refreshTreeRowVisible(sheet);
-                    break;
-                case "leafXmj":
-                    tree.expandToLeafXmj();
-                    SpreadJsObj.refreshTreeRowVisible(sheet);
-                    break;
-            }
+            setTimeout(() => {
+                showWaitingView();
+                switch (tag) {
+                    case "1":
+                    case "2":
+                    case "3":
+                    case "4":
+                    case "5":
+                        tree.expandByLevel(parseInt(tag));
+                        SpreadJsObj.refreshTreeRowVisible(sheet);
+                        break;
+                    case "last":
+                        tree.expandByCustom(() => { return true; });
+                        SpreadJsObj.refreshTreeRowVisible(sheet);
+                        break;
+                    case "leafXmj":
+                        tree.expandToLeafXmj();
+                        SpreadJsObj.refreshTreeRowVisible(sheet);
+                        break;
+                }
+                closeWaitingView();
+            }, 100);
         });
     })('a[name=showLevel]', ledgerSpread.getActiveSheet());
 

+ 53 - 20
app/public/js/ledger_audit.js

@@ -28,8 +28,11 @@ $(document).ready(() => {
         rootId: -1,
         keys: ['id', 'tender_id', 'ledger_id'],
         preUrl: '/ledger',
-        markFoldKey: 'bills-fold',
-        markFoldSubKey: window.location.pathname.split('/')[2],
+        autoExpand: 3,
+        // markFoldKey: 'bills-fold',
+        // markFoldSubKey: window.location.pathname.split('/')[2],
+        markExpandKey: 'bills-expand',
+        markExpandSubKey: window.location.pathname.split('/')[2],
     };
     if (checkTzMeasureType()) {
         treeSetting.calcFields = ['sgfh_tp', 'sjcl_tp', 'qtcl_tp', 'total_price'];
@@ -92,12 +95,38 @@ $(document).ready(() => {
         SpreadJsObj.initSheet(posSpread.getActiveSheet(), posSpreadSetting);
     }
 
+    $.contextMenu({
+        selector: '#ledger-spread',
+        build: function ($trigger, e) {
+            const target = SpreadJsObj.safeRightClickSelection($trigger, e, ledgerSpread);
+            return target.hitTestType === spreadNS.SheetArea.viewport || target.hitTestType === spreadNS.SheetArea.rowHeader;
+        },
+        items: {
+            showLast: {
+                name: '显示至最底层',
+                callback: function (key, opt, menu, e) {
+                    const node = SpreadJsObj.getSelectObject(ledgerSpread.getActiveSheet());
+                    setTimeout(() => {
+                        showWaitingView();
+                        ledgerTree.expandByCustom(x => {
+                            return x.expanded || (x.id === node.id) || (x.full_path.indexOf(node.ledger_id + '-') >= 0);
+                        });
+                        SpreadJsObj.refreshTreeRowVisible(ledgerSpread.getActiveSheet());
+                        closeWaitingView();
+                    }, 100);
+                },
+            },
+        }
+    });
+
     postData('/tender/' + getTenderId() + '/ledger/load', null, function (data) {
         ledgerTree.loadDatas(data.bills);
         treeCalc.calculateAll(ledgerTree);
         SpreadJsObj.loadSheetData(ledgerSpread.getActiveSheet(), 'tree', ledgerTree);
         SpreadJsObj.loadTopAndSelect(ledgerSpread.getActiveSheet(), ckBillsSpread);
 
+        checkShowLast(data.bills.length);
+
         pos.loadDatas(data.pos);
         loadCurPosData();
         SpreadJsObj.resetTopAndSelect(posSpread.getActiveSheet());
@@ -238,24 +267,28 @@ $(document).ready(() => {
             if (!sheet.zh_tree) return;
             const tag = $(this).attr('tag');
             const tree = sheet.zh_tree;
-            switch (tag) {
-                case "1":
-                case "2":
-                case "3":
-                case "4":
-                case "5":
-                    tree.expandByLevel(parseInt(tag));
-                    SpreadJsObj.refreshTreeRowVisible(sheet);
-                    break;
-                case "last":
-                    tree.expandByCustom(() => { return true; });
-                    SpreadJsObj.refreshTreeRowVisible(sheet);
-                    break;
-                case "leafXmj":
-                    tree.expandToLeafXmj();
-                    SpreadJsObj.refreshTreeRowVisible(sheet);
-                    break;
-            }
+            setTimeout(() => {
+                showWaitingView();
+                switch (tag) {
+                    case "1":
+                    case "2":
+                    case "3":
+                    case "4":
+                    case "5":
+                        tree.expandByLevel(parseInt(tag));
+                        SpreadJsObj.refreshTreeRowVisible(sheet);
+                        break;
+                    case "last":
+                        tree.expandByCustom(() => { return true; });
+                        SpreadJsObj.refreshTreeRowVisible(sheet);
+                        break;
+                    case "leafXmj":
+                        tree.expandToLeafXmj();
+                        SpreadJsObj.refreshTreeRowVisible(sheet);
+                        break;
+                }
+                closeWaitingView();
+            }, 100);
         });
     })('a[name=showLevel]', ledgerSpread.getActiveSheet());
 });

+ 2 - 0
app/public/js/material.js

@@ -358,6 +358,7 @@ $(document).ready(() => {
                 select.msg_spread = materialCol.getValue.msg_spread(select);
                 select.m_spread = materialCol.getValue.m_spread(select);
                 select.m_tp = materialCol.getValue.m_tp(select);
+                delete select.waitingLoading;
 
                 // console.log(select);
 
@@ -389,6 +390,7 @@ $(document).ready(() => {
                         info.sheet.endEdit(true);
                     }
                     select.is_summary = info.sheet.getValue(info.row, info.col) ? 1 : 0;
+                    delete select.waitingLoading;
                     // 更新至服务器
                     postData(window.location.pathname + '/save', { type:'update', updateData: select }, function (result) {
                         m_tp = result.m_tp;

+ 2 - 0
app/public/js/material_exponent.js

@@ -187,6 +187,7 @@ $(document).ready(() => {
                 }
                 select[col.field] = validText;
                 select.calc_num = materialExponentCol.getValue.calc_num(select);
+                delete select.waitingLoading;
                 // console.log(select);
 
                 // 更新至服务器
@@ -220,6 +221,7 @@ $(document).ready(() => {
                         info.sheet.endEdit(true);
                     }
                     select.is_summary = info.sheet.getValue(info.row, info.col) ? 1 : 0;
+                    delete select.waitingLoading;
                     // 更新至服务器
                     postData(window.location.pathname + '/save', { type:'update', updateData: select }, function (result) {
                         ex_tp = result.ex_tp;

+ 54 - 21
app/public/js/path_tree.js

@@ -275,11 +275,18 @@ const createNewPathTree = function (type, setting) {
             // 树设置
             this.setting = setting;
 
-            if (this.setting.markFoldKey) {
+            this.hasMark = false;
+            if (this.setting.markExpandKey) {
+                const markStr = getLocalCache(this.setting.markExpandKey);
+                const markData = markStr ? markStr.split('|') : ['', ''];
+                this.hasMark = markData[0] === this.setting.markExpandSubKey;
+                this.markExpand = this.hasMark && markData[1] ? _.map(markData[1].split(','), _.toInteger) : [];
+
+            } else if (this.setting.markFoldKey) {
                  const markStr = getLocalCache(this.setting.markFoldKey);
                  const markData = markStr ? markStr.split('|') : ['', ''];
-                 this.markFold = markData[0] === this.setting.markFoldSubKey && markData[1]
-                     ? _.map(markData[1].split(','), _.toInteger) : [];
+                 this.hasMark = markData[0] === this.setting.markFoldSubKey;
+                 this.markFold = this.hasMark && markData[1] ? _.map(markData[1].split(','), _.toInteger) : [];
             }
             // if (this.setting.treeCacheKey) {
             //     localforage.getItem(this.setting.treeCacheKey).then(function (v) {
@@ -355,10 +362,25 @@ const createNewPathTree = function (type, setting) {
                 return a.order - b.order;
             });
             this.sortTreeNode(true);
-            if (this.setting.autoExpand >= 0) this.expandByLevel(this.setting.autoExpand);
-            if (this.setting.markFoldKey) this.expandByCustom(function (node) {
-                return self.markFold.indexOf(node[self.setting.id]) === -1;
-            });
+            if (this.hasMark) {
+                if (this.setting.markExpandKey) {
+                    this.expandByCustom(node => {
+                        return self.markExpand.indexOf(node[self.setting.id]) >= 0;
+                    });
+                } else if (this.setting.markFoldKey) {
+                    this.expandByCustom(function (node) {
+                        return self.markFold.indexOf(node[self.setting.id]) === -1;
+                    });
+                }
+            } else {
+                if (this.setting.autoExpand >= 0) {
+                    this.expandByLevel(this.setting.autoExpand);
+                    for (const node of this.nodes) {
+                        this._markExpandFold(node);
+                    }
+                    this._saveMarkExpandFold();
+                }
+            }
         }
 
         getItemsByIndex(index) {
@@ -526,18 +548,29 @@ const createNewPathTree = function (type, setting) {
             return node[this.setting.id];
         };
 
-        _markFold(node) {
-            if (!this.setting.markFoldKey || !this.setting.markFoldSubKey) return;
-            // if (!this.setting.treeCacheKey) return;
-
-            if (!node.expanded) {
-                if (this.markFold.indexOf(node[this.setting.id]) === -1) this.markFold.push(node[this.setting.id]);
-            } else {
-                if (this.markFold.indexOf(node[this.setting.id]) >= 0) this.markFold.splice(this.markFold.indexOf(node[this.setting.id]), 1);
+        _markExpandFold(node) {
+            if (this.setting.markExpandKey && this.setting.markExpandSubKey) {
+                if (node.expanded) {
+                    if (this.markExpand.indexOf(node[this.setting.id]) === -1)
+                        this.markExpand.push(node[this.setting.id]);
+                } else {
+                    if (this.markExpand.indexOf(node[this.setting.id]) >= 0)
+                        this.markExpand.splice(this.markExpand.indexOf(node[this.setting.id]), 1);
+                }
+            } else if (this.setting.markFoldKey && this.setting.markFoldSubKey) {
+                if (!node.expanded) {
+                    if (this.markFold.indexOf(node[this.setting.id]) === -1)
+                        this.markFold.push(node[this.setting.id]);
+                } else {
+                    if (this.markFold.indexOf(node[this.setting.id]) >= 0)
+                        this.markFold.splice(this.markFold.indexOf(node[this.setting.id]), 1);
+                }
             }
         }
-        _saveMarkFold() {
-            if (this.setting.markFoldKey && this.setting.markFoldSubKey) {
+        _saveMarkExpandFold() {
+            if (this.setting.markExpandKey && this.setting.markExpandSubKey) {
+                setLocalCache(this.setting.markExpandKey, this.setting.markExpandSubKey + '|' + this.markExpand.join(','));
+            } else if (this.setting.markFoldKey && this.setting.markFoldSubKey) {
                 setLocalCache(this.setting.markFoldKey, this.setting.markFoldSubKey + '|' + this.markFold.join(','));
             }
             // if (this.setting.treeCacheKey) {
@@ -570,9 +603,9 @@ const createNewPathTree = function (type, setting) {
          */
         setExpanded(node, expanded) {
             node.expanded = expanded;
-            this._markFold(node);
+            this._markExpandFold(node);
             this._refreshChildrenVisible(node);
-            this._saveMarkFold();
+            this._saveMarkExpandFold();
         };
         /**
          * 递归 设置节点展开状态
@@ -586,7 +619,7 @@ const createNewPathTree = function (type, setting) {
                 const expanded = checkFun(node);
                 if (node.expanded !== expanded) {
                     node.expanded = expanded;
-                    this._markFold(node);
+                    this._markExpandFold(node);
                 }
                 node.visible = parent ? (parent.expanded && parent.visible) : true;
                 this._recursiveExpand(node.children, node, checkFun);
@@ -598,7 +631,7 @@ const createNewPathTree = function (type, setting) {
          */
         expandByCustom(checkFun) {
             this._recursiveExpand(this.children, null, checkFun);
-            this._saveMarkFold();
+            this._saveMarkExpandFold();
         }
         /**
          * 展开到第几层

+ 41 - 19
app/public/js/revise.js

@@ -103,6 +103,7 @@ $(document).ready(() => {
         rootId: -1,
         keys: ['id', 'tender_id', 'ledger_id'],
         calcFields: ['sgfh_tp', 'sjcl_tp', 'qtcl_tp', 'total_price'],
+        autoExpand: 3,
     };
     if (!isTz) {
         treeSetting.calcFields.push('deal_tp');
@@ -1257,6 +1258,21 @@ $(document).ready(() => {
             }
         };
     }
+    billsContextMenuOptions.items.sprTag = '----';
+    billsContextMenuOptions.items.showLast = {
+        name: '显示至最底层',
+        callback: function (key, opt, menu, e) {
+            const node = SpreadJsObj.getSelectObject(billsSheet);
+            setTimeout(() => {
+                showWaitingView();
+                billsTree.expandByCustom(x => {
+                    return x.expanded || (x.id === node.id) || (x.full_path.indexOf(node.ledger_id + '-') >= 0);
+                });
+                SpreadJsObj.refreshTreeRowVisible(billsSheet);
+                closeWaitingView();
+            }, 100);
+        },
+    };
     $.contextMenu(billsContextMenuOptions);
 
     // 计量单元 相关方法&绑定spreadjs事件
@@ -1798,7 +1814,9 @@ $(document).ready(() => {
         billsTree.loadDatas(result.bills);
         treeCalc.calculateAll(billsTree);
         SpreadJsObj.loadSheetData(billsSheet, SpreadJsObj.DataType.Tree, billsTree);
-        SpreadJsObj.loadTopAndSelect(billsSheet, ckBillsSpread);        
+        SpreadJsObj.loadTopAndSelect(billsSheet, ckBillsSpread);
+
+        checkShowLast(result.bills.length);
 
         pos.loadDatas(result.pos);
         posSpreadObj.loadCurPosData();
@@ -2543,24 +2561,28 @@ $(document).ready(() => {
             if (!sheet.zh_tree) return;
             const tag = $(this).attr('tag');
             const tree = sheet.zh_tree;
-            switch (tag) {
-                case "1":
-                case "2":
-                case "3":
-                case "4":
-                case "5":
-                    tree.expandByLevel(parseInt(tag));
-                    SpreadJsObj.refreshTreeRowVisible(sheet);
-                    break;
-                case "last":
-                    tree.expandByCustom(() => { return true; });
-                    SpreadJsObj.refreshTreeRowVisible(sheet);
-                    break;
-                case "leafXmj":
-                    tree.expandToLeafXmj();
-                    SpreadJsObj.refreshTreeRowVisible(sheet);
-                    break;
-            }
+            setTimeout(() => {
+                showWaitingView();
+                switch (tag) {
+                    case "1":
+                    case "2":
+                    case "3":
+                    case "4":
+                    case "5":
+                        tree.expandByLevel(parseInt(tag));
+                        SpreadJsObj.refreshTreeRowVisible(sheet);
+                        break;
+                    case "last":
+                        tree.expandByCustom(() => { return true; });
+                        SpreadJsObj.refreshTreeRowVisible(sheet);
+                        break;
+                    case "leafXmj":
+                        tree.expandToLeafXmj();
+                        SpreadJsObj.refreshTreeRowVisible(sheet);
+                        break;
+                }
+                closeWaitingView();
+            }, 100);
         });
     })('a[name=showLevel]', billsSheet);
 

+ 26 - 19
app/public/js/revise_history.js

@@ -53,6 +53,7 @@ $(document).ready(() => {
         rootId: -1,
         keys: ['id', 'tender_id', 'ledger_id'],
         calcFields: ['sgfh_tp', 'sjcl_tp', 'qtcl_tp', 'total_price'],
+        autoExpand: 3,
     };
     if (!isTz) {
         treeSetting.calcFields.push('deal_tp');
@@ -158,7 +159,9 @@ $(document).ready(() => {
         billsTree.loadDatas(result.bills);
         treeCalc.calculateAll(billsTree);
         SpreadJsObj.loadSheetData(billsSheet, SpreadJsObj.DataType.Tree, billsTree);
-        SpreadJsObj.loadTopAndSelect(billsSheet, ckBillsSpread);        
+        SpreadJsObj.loadTopAndSelect(billsSheet, ckBillsSpread);
+
+        checkShowLast(result.bills.length);
 
         pos.loadDatas(result.pos);
         posSpreadObj.loadCurPosData();
@@ -271,24 +274,28 @@ $(document).ready(() => {
             if (!sheet.zh_tree) return;
             const tag = $(this).attr('tag');
             const tree = sheet.zh_tree;
-            switch (tag) {
-                case "1":
-                case "2":
-                case "3":
-                case "4":
-                case "5":
-                    tree.expandByLevel(parseInt(tag));
-                    SpreadJsObj.refreshTreeRowVisible(sheet);
-                    break;
-                case "last":
-                    tree.expandByCustom(() => { return true; });
-                    SpreadJsObj.refreshTreeRowVisible(sheet);
-                    break;
-                case "leafXmj":
-                    tree.expandToLeafXmj();
-                    SpreadJsObj.refreshTreeRowVisible(sheet);
-                    break;
-            }
+            setTimeout(() => {
+                showWaitingView();
+                switch (tag) {
+                    case "1":
+                    case "2":
+                    case "3":
+                    case "4":
+                    case "5":
+                        tree.expandByLevel(parseInt(tag));
+                        SpreadJsObj.refreshTreeRowVisible(sheet);
+                        break;
+                    case "last":
+                        tree.expandByCustom(() => { return true; });
+                        SpreadJsObj.refreshTreeRowVisible(sheet);
+                        break;
+                    case "leafXmj":
+                        tree.expandToLeafXmj();
+                        SpreadJsObj.refreshTreeRowVisible(sheet);
+                        break;
+                }
+                closeWaitingView();
+            }, 100);
         });
     })('a[name=showLevel]', billsSheet);
 

+ 190 - 16
app/public/js/schedule_ledger.js

@@ -9,6 +9,7 @@ function getTenderId() {
     return window.location.pathname.split('/')[2];
 }
 const selects = [];
+let slh = [];
 $(function () {
     autoFlashHeight();
     // 初始化台账
@@ -20,15 +21,13 @@ $(function () {
         level: 'level',
         rootId: -1,
         fullPath: 'full_path',
-        //treeCacheKey: 'ledger_bills_fold' + '_' + getTenderId(),
-        // markFoldKey: 'bills-fold',
-        // markFoldSubKey: window.location.pathname.split('/')[2],
+        // calcFields: ['can_select'],
     };
     const ledgerTree = createNewPathTree('filter', treeSetting);
 
     const ledgerSpreadSetting = {
         cols: [
-            {title: '', colSpan: '1', rowSpan: '2', field: 'is_select', hAlign: 1, width: 40, formatter: '@', cellType: 'checkbox'},
+            {title: '', colSpan: '1', rowSpan: '2', field: 'is_select', hAlign: 1, width: 40, formatter: '@', cellType: 'checkbox', readOnly: 'readOnly.can_select'},
             {title: '编号', colSpan: '1', rowSpan: '2', field: 'code', hAlign: 0, width: 185, formatter: '@', readOnly: true, cellType: 'tree'},
             {title: '名称', colSpan: '1', rowSpan: '2', field: 'name', hAlign: 0, width: 205, formatter: '@', readOnly: true},
             {title: '单位', colSpan: '1', rowSpan: '2', field: 'unit', hAlign: 1, width: 100, formatter: '@', readOnly: true},
@@ -44,17 +43,47 @@ $(function () {
         font: '12px 微软雅黑',
         // readOnly: true,
         localCache: {
-            key: 'ledger-bills',
+            key: 'ledger-schedule',
             colWidth: true,
         }
     };
 
+    const ledgerCol = {
+        readOnly: {
+            can_select: function (data) {
+                return !(data.can_select && !revising);
+            },
+        },
+    };
+
     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(window.location.pathname + '/load', {}, function (data) {
+        const showList = ['is_select', 'can_select', 'ledger_id', 'ledger_pid', 'order', 'level', 'tender_id', 'full_path',
+            'code', 'name', 'unit', 'dgn_qty1', 'dgn_qty2', 'dgn_price', 'quantity', 'total_price',
+            'final_contract_tp', 'final_qc_tp', 'pre_final_contract_tp', 'pre_final_qc_tp', 'pre_final_gather_tp', 'final_gather_tp', 'end_final_gather_tp'
+        ];
+        const calcList = ['total_price',
+            'final_contract_tp', 'final_qc_tp', 'pre_final_contract_tp', 'pre_final_qc_tp', 'pre_final_gather_tp', 'final_gather_tp', 'end_final_gather_tp'
+        ];
+        const fileds = ['final_contract_tp', 'final_qc_tp'];
+        if (scheduleStage.length > 0) {
+            for (const ss of scheduleStage) {
+                // const pushTp = [ss.order + '_gather_tp', ss.order + '_contract_tp', ss.order + '_qc_tp'];
+                showList.push(ss.order + '_gather_tp');
+                showList.push(ss.order + '_contract_tp');
+                showList.push(ss.order + '_qc_tp');
+                calcList.push(ss.order + '_gather_tp');
+                calcList.push(ss.order + '_contract_tp');
+                calcList.push(ss.order + '_qc_tp');
+                fileds.push(ss.order + '_contract_tp');
+                fileds.push(ss.order + '_qc_tp');
+            }
+        }
         const baseLedgerTree = createNewPathTree('base', {
             id: 'ledger_id',
             pid: 'ledger_pid',
@@ -62,20 +91,31 @@ $(function () {
             level: 'level',
             rootId: -1,
             fullPath: 'full_path',
-            calcFields: ['total_price'],
+            updateFields: fileds,
+            calcFields: calcList,
             calcFun: function (node) {
                 node.dgn_price = ZhCalc.round(ZhCalc.div(node.total_price, node.dgn_qty1), 2);
+                if (scheduleStage.length > 0) {
+                    for (const ss of scheduleStage) {
+                        node[ss.order + '_gather_tp'] = ZhCalc.add(node[ss.order + '_contract_tp'], node[ss.order + '_qc_tp']);
+                    }
+                }
+                node.pre_final_gather_tp = ZhCalc.add(node.pre_final_contract_tp, node.pre_final_qc_tp);
+                node.final_gather_tp = ZhCalc.add(node.final_contract_tp, node.final_qc_tp);
+                node.end_final_gather_tp = ZhCalc.add(node.pre_final_gather_tp, node.final_gather_tp);
             }
         });
+        slh = data.slh;
         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.addData(d, showList);
         }
         ledgerTree.sortTreeNode(true);
+        // treeCalc.calculateAll(ledgerTree);
+        console.log(ledgerTree);
 
         SpreadJsObj.loadSheetData(ledgerSpread.getActiveSheet(), SpreadJsObj.DataType.Tree, ledgerTree);
     }, null, true);
@@ -138,13 +178,16 @@ $(function () {
             });
         },
         buttonClicked: function (e, info) {
+            if(revising) {
+                return;
+            }
             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 (col.field === 'is_select' && select.can_select) {
                     if (info.sheet.isEditing()) {
                         info.sheet.endEdit(true);
                     }
@@ -178,19 +221,91 @@ $(function () {
 
     $('#ledger_submit').click(function () {
         const select_ledger = _.filter(ledgerTree.nodes, { 'is_select': 1 });
-        console.log(select_ledger);
         if (select_ledger.length === 0) {
-            toastr.error('请选择清单项');
+            toastr.error('请至少勾选一个节点');
             return;
         }
         $(this).attr('disabled', true);
-        postData(window.location.pathname + '/save',  _.map(select_ledger, 'ledger_id'), function (result) {
+        const [under_ledger, parent_ledger] = getPAndULedger(select_ledger);
+        // 重新计算每一期计量进度金额并提交
+        const stageTpList = calcStageTp(under_ledger);
+        const stageSjTp = calcStageSjTp(under_ledger);
+        console.log(under_ledger, stageSjTp);
+        postData(window.location.pathname + '/save',  { select_ledger: _.map(select_ledger, 'ledger_id'), under_ledger, parent_ledger, stageTpList, stageSjTp, type: 'sz' } , function (result) {
             toastr.success('设置成功');
             setTimeout(function () {
                 window.location.reload();
             },1500);
         });
     });
+    $('#update-ledger').click(function () {
+        // const addLedgerData = _.map(_.differenceBy(ledgerTree.nodes, slh, 'ledger_id'), 'ledger_id');
+        const select_ledger = _.filter(ledgerTree.nodes, { 'is_select': 1 });
+        // 找出addLedgerData父节点并一起加入到select_ledger中
+        // if (addLedgerData.length > 0) {
+        //     for (const a of addLedgerData) {
+        //         const ainfo = _.find(ledgerTree.nodes, { 'ledger_id': a });
+        //         select_ledger.push(ainfo);
+        //         findandpush(ainfo.ledger_pid);
+        //     }
+        // }
+        // function findandpush(ledger) {
+        //     const parent = _.find(ledgerTree.nodes, { ledger_id: ledger });
+        //     if (parent) {
+        //         const hadinfo = _.find(select_ledger, { ledger_id: parent.ledger_id });
+        //         if (!hadinfo) {
+        //             select_ledger.push(parent);
+        //             findandpush(parent);
+        //         }
+        //     }
+        // }
+        $(this).attr('disabled', true);
+        const [under_ledger, parent_ledger] = getPAndULedger(select_ledger);
+        // 重新计算每一期计量进度金额并提交
+        const stageTpList = calcStageTp(under_ledger);
+        const stageSjTp = calcStageSjTp(under_ledger);
+        console.log(stageSjTp);
+        postData(window.location.pathname + '/save',  { select_ledger: _.map(select_ledger, 'ledger_id'), under_ledger, parent_ledger, stageTpList, stageSjTp, type: 'xz' } , function (result) {
+            toastr.success('进度台账数据修正成功');
+            setTimeout(function () {
+                window.location.reload();
+            },1500);
+        });
+    });
+
+    function getPAndULedger(select_ledger) {
+        // 找出所选的最底层,并统计总设计金额用
+        const under_ledger = [];
+        for (const sl of select_ledger) {
+            const hadChild = _.find(select_ledger, { ledger_pid: sl.ledger_id });
+            if (!hadChild) {
+                const pushArray = { ledger_id: sl.ledger_id, ledger_pid: sl.ledger_pid, gcl: sl.dgn_qty1, tp: sl.total_price, end_final_gather_tp: sl.end_final_gather_tp };
+                if (scheduleStage.length > 0) {
+                    for (const ss of scheduleStage) {
+                        pushArray[ss.order + '_gather_tp'] = sl[ss.order + '_gather_tp'];
+                    }
+                }
+                under_ledger.push(pushArray);
+            }
+        }
+        // 从最底层找出之前的所有父节点,删除他们可能存在已填的值
+        const parent_ledger = [];
+        if (under_ledger.length > 0) {
+            for (const ul of under_ledger) {
+                digui(select_ledger, ul.ledger_pid);
+            }
+        }
+        function digui(select_ledger, ul) {
+            const parent = _.find(select_ledger, { ledger_id: ul });
+            if (parent && parent_ledger.indexOf(ul) === -1) {
+                digui(select_ledger, parent.ledger_pid);
+                if (!parent.can_select) {
+                    parent_ledger.push(parent.ledger_id);
+                }
+            }
+        }
+        return [under_ledger, parent_ledger];
+    }
 
     $.subMenu({
         menu: '#sub-menu', miniMenu: '#sub-mini-menu', miniMenuList: '#mini-menu-list',
@@ -238,20 +353,66 @@ $(function () {
     //     });
     // })('a[name=showLevel]', ledgerSpread.getActiveSheet());
 });
+
+function calcStageTp(list) {
+    if (scheduleStage.length > 0) {
+        const stageTp = [];
+        for (const ss of scheduleStage) {
+            let stage_price = 0;
+            for (const l of list) {
+                stage_price = ZhCalc.add(stage_price, l[ss.order + '_gather_tp']);
+            }
+            stageTp.push({ id: ss.id, tp: stage_price });
+        }
+        return stageTp;
+    }
+    return false;
+}
+
+function calcStageSjTp(list) {
+    let stageSjTp = 0;
+    for (const l of list) {
+        stageSjTp = ZhCalc.add(stageSjTp, l.end_final_gather_tp);
+    }
+    return stageSjTp;
+}
+
 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);
+            if (!d.is_leaf) {
+                d.can_select = hadDataLidList.length === 0 ? true : hadDataLidList.indexOf(d.ledger_id) !== -1 ? false : true;
+                // console.log(d.can_select);
+            } else if(d.can_select === undefined) {
+                d.can_select = true;
+            }
+        }
+    }
+    for (const d2 of datas) {
+        if (!d2.b_code && !d2.can_select) {
+            updateParentCanSelect(datas, d2.ledger_pid);
         }
     }
     return datas;
 }
 
+function updateParentCanSelect(datas, ledger_pid) {
+    if (ledger_pid !== 0) {
+        const info = _.find(datas, { ledger_id: ledger_pid});
+        if (info) {
+            info.can_select = false;
+            updateParentCanSelect(datas, info.ledger_pid);
+        }
+    }
+}
+
 function updateChildrenSelect(datas, is_select) {
     for (const data of datas) {
-        data.is_select = is_select;
+        if (data.can_select) {
+            data.is_select = is_select;
+        }
         if(data.children && data.children.length > 0) {
             updateChildrenSelect(data.children, is_select);
         }
@@ -263,7 +424,10 @@ 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 (d.can_select) {
+                d.is_select = is_select;
+            }
+            // d.is_select = is_select;
             if (select_msg.select_children || is_select === 0) {
                 updateChildrenSelect(d.children, is_select);
             }
@@ -282,15 +446,25 @@ function updateOtherSiblingsSelect(tree, pid, is_select, select_msg) {
         if (grandparent) {
             for (const d of grandparent.children) {
                 if (d.ledger_id !== pid) {
+                    let p = false;
                     if(d.children && d.children.length > 0) {
                         for (const dd of d.children) {
-                            dd.is_select = is_select;
+                            if (dd.can_select) {
+                                dd.is_select = is_select;
+                            }
+                            p = true;
                             if (select_msg.select_children || is_select === 0) {
                                 updateChildrenSelect(dd.children, is_select);
                             }
                             selects.push(dd);
                         }
                     }
+                    if(p) {
+                        if(d.can_select && is_select) {
+                            d.is_select = is_select;
+                        }
+                        selects.push(d);
+                    }
                 }
             }
         }

+ 205 - 87
app/public/js/schedule_plan.js

@@ -10,7 +10,7 @@ function getTenderId() {
 }
 $(function () {
     autoFlashHeight();
-    if(schedule && !schedule.mode) {
+    if(schedule && !schedule.mode && !revising && !schedule.revising) {
         $('#mode').modal('show');
     }
     // 初始化台账
@@ -22,11 +22,19 @@ $(function () {
         level: 'level',
         rootId: -1,
         fullPath: 'full_path',
+        calcFun: function (node) {
+            node.dgn_price = ZhCalc.round(ZhCalc.div(node.total_price, node.dgn_qty1), 2);
+            if (node.children && node.children.length > 0) {
+                for (const sm of scheduleMonth) {
+                    node[sm.yearmonth+'_gcl'] = ZhCalc.round(ZhCalc.div(node[sm.yearmonth+'_tp'], node.dgn_price), 3);
+                }
+            }
+        }
         //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'},
@@ -34,7 +42,7 @@ $(function () {
         {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},
+        {title: '|金额(元)', colSpan: '|1', rowSpan: '|1', field: 'total_price', hAlign: 2, width: 70, type: 'Number', readOnly: true},
     ];
 
     const ledgerSpreadSetting = {
@@ -46,21 +54,26 @@ $(function () {
         font: '12px 微软雅黑',
         // readOnly: true,
         localCache: {
-            key: 'ledger-bills',
+            key: 'ledger-plan',
             colWidth: true,
         }
     };
     const monthsCols = [];
+    const calcCols = ['total_price'];
     if(scheduleMonth.length > 0) {
         for (const sm of scheduleMonth) {
-            const readOnly = sm.stage_used !== 0;
+            const readOnly = sm.stage_gcl_used !== 0 || sm.stage_tp_used !== 0;
             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'};
+            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);
+            // calcCols.push(sm.yearmonth+'_gcl');
+            calcCols.push(sm.yearmonth+'_tp');
         }
     }
+    treeSetting.calcFields = calcCols;
+    const ledgerTree = createNewPathTree('filter', treeSetting);
     const spreadHeaderCols = static_cols.concat(monthsCols);
     ledgerSpreadSetting.cols = spreadHeaderCols;
 
@@ -71,14 +84,14 @@ $(function () {
                 if (data.is_leaf) {
                     flag = schedule && schedule.mode === mode.tp;
                 }
-                return !flag;
+                return !(flag && !revising);
             },
             gcl: function (data) {
                 let flag = data.is_leaf;
                 if (data.is_leaf) {
                     flag = schedule && schedule.mode === mode.gcl;
                 }
-                return !flag;
+                return !(flag && !revising);
             },
         },
     };
@@ -88,6 +101,8 @@ $(function () {
     SpreadJsObj.initSpreadSettingEvents(ledgerSpreadSetting, ledgerCol);
     SpreadJsObj.initSheet(ledgerSpread.getActiveSheet(), ledgerSpreadSetting);
     SpreadJsObj.selChangedRefreshBackColor(ledgerSpread.getActiveSheet());
+    ledgerSpread.getActiveSheet().frozenColumnCount(6);
+    ledgerSpread.getActiveSheet().options.frozenlineColor = '#93b5e4';
 
     postData('/tender/' + getTenderId() + '/schedule/ledger/load', {}, function (data) {
         // let treeData = [];
@@ -122,7 +137,6 @@ $(function () {
         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) {
@@ -133,9 +147,9 @@ $(function () {
                 }
             }
         }
-        console.log(ledgerTree);
         ledgerTree.sortTreeNode(true);
-        // console.log(ledgerTree);
+        treeCalc.calculateAll(ledgerTree);
+        console.log(ledgerTree);
         SpreadJsObj.loadSheetData(ledgerSpread.getActiveSheet(), SpreadJsObj.DataType.Tree, ledgerTree);
     }, null, true);
 
@@ -193,7 +207,7 @@ $(function () {
             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);
+                let 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);
@@ -210,23 +224,25 @@ $(function () {
                 let plan_tp = 0;
                 // 判断输入位数,提示
                 if (mode === 'tp') {
-                    const reg = new RegExp('^([-]?)\\d+(\\.\\d{0,'+ parseInt(tenderInfo.decimal.tp) +'})?$');
+                    const reg = new RegExp('^([-]?)\\d+$');
                     if (validText !== null && (!reg.test(validText))) {
-                        toastr.error('输入金额小数位数不能大于' + tenderInfo.decimal.tp + '位');
-                        SpreadJsObj.reLoadRowData(info.sheet, info.row);
-                        return;
+                        // toastr.error('输入金额请为整数');
+                        // SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                        // return;
+                        validText = ZhCalc.round(validText, 0);
                     }
-                    plan_gcl = select.dgn_price && select.dgn_price !== 0 ? ZhCalc.round(ZhCalc.div(validText, select.dgn_price), tenderInfo.decimal.up) : 0;
+                    plan_gcl = select.dgn_price && select.dgn_price !== 0 ? ZhCalc.round(ZhCalc.div(validText, select.dgn_price), 3) : 0;
                     plan_tp = validText;
                 } else {
-                    const reg = new RegExp('^([-]?)\\d+(\\.\\d{0,'+ parseInt(tenderInfo.decimal.up) +'})?$');
+                    const reg = new RegExp('^([-]?)\\d+$');
                     if (validText !== null && (!reg.test(validText))) {
-                        toastr.error('输入工程量小数位数不能大于' + tenderInfo.decimal.up + '位');
-                        SpreadJsObj.reLoadRowData(info.sheet, info.row);
-                        return;
+                        // toastr.error('输入工程量小数位数不能大于3位');
+                        // SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                        // return;
+                        validText = ZhCalc.round(validText, 3);
                     }
                     plan_gcl = validText;
-                    plan_tp = select.dgn_price && select.dgn_price !== 0 ? ZhCalc.round(ZhCalc.mul(validText, select.dgn_price), tenderInfo.decimal.tp) : 0;
+                    plan_tp = select.dgn_price && select.dgn_price !== 0 ? ZhCalc.round(ZhCalc.mul(validText, select.dgn_price), 0) : 0;
                 }
                 select[col.field] = validText;
                 const updateData = {
@@ -235,90 +251,147 @@ $(function () {
                     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);
+                    const nodes = treeCalc.calculateParent(info.sheet.zh_tree, select);
+                    const refreshNode = ledgerTree.loadPostData({update: nodes});
+                    ledgerSpreadObj.refreshTree(info.sheet, refreshNode);
                 },function () {
                     select[col.field] = orgValue;
-                    SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                    const nodes = treeCalc.calculateParent(info.sheet.zh_tree, select);
+                    const refreshNode = ledgerTree.loadPostData({update: nodes});
+                    ledgerSpreadObj.refreshTree(info.sheet, refreshNode);
                 })
             }
         },
         deletePress: function (sheet) {
-            return;
+            if (!sheet.zh_setting) return;
+            if (sheet.zh_setting && sheet.zh_tree) {
+                const sel = sheet.getSelections()[0], datas = [], filterNodes = [];
+                if (!sel) return;
+                for (let iRow = sel.row; iRow < sel.row + sel.rowCount; iRow++) {
+                    let bDel = false;
+                    const node = sheet.zh_tree.nodes[iRow];
+                    for (let iCol = sel.col; iCol < sel.col + sel.colCount; iCol++) {
+                        const col = sheet.zh_setting.cols[iCol];
+                        const orgValue = node[col.field];
+                        if (!orgValue) {
+                            continue;
+                        }
+                        const yearmonth = col.field.split('_')[0];
+                        const modes = col.field.split('_')[1];
+                        if(col.readOnly === true || !node.is_leaf || mode[modes] !== schedule.mode) {
+                            continue;
+                        }
+                        const updateData = {
+                            lid: node.ledger_id,
+                            yearmonth,
+                            plan_gcl: null,
+                            plan_tp: null,
+                        };
+                        datas.push(updateData);
+                        node[yearmonth+'_gcl'] = null;
+                        node[yearmonth+'_tp'] = null;
+                        bDel = true;
+                    }
+                    if (bDel) filterNodes.push(node);
+                }
+                if (datas.length > 0) {
+                    postData(window.location.pathname + '/save', {type: 'ledger_paste', postData: datas}, function (result) {
+                        for (const uul of filterNodes) {
+                            const nodes = treeCalc.calculateParent(sheet.zh_tree, uul);
+                            const refreshNode = ledgerTree.loadPostData({update: nodes});
+                            ledgerSpreadObj.refreshTree(sheet, refreshNode);
+                        }
+                        SpreadJsObj.reLoadSheetData(sheet);
+                    }, function () {
+                        SpreadJsObj.reLoadSheetData(sheet);
+                    });
+                }
+            }
         },
         clipboardPasted(e, info) {
             const hint = {
                 cellError: {type: 'error', msg: '粘贴内容超出了表格范围'},
                 numberExpr: {type: 'error', msg: '不能粘贴其它非数字类型字符'},
             };
-            const range = info.cellRange;
-            if (range.rowCount > 1 || range.colCount > 1) {
-                toastMessageUniq(hint.cellError);
-                SpreadJsObj.reLoadSheetHeader(ledgerSpread.getActiveSheet());
-                SpreadJsObj.reLoadSheetData(ledgerSpread.getActiveSheet());
-                return;
-            }
-            const select = SpreadJsObj.getSelectObject(info.sheet);
-            const col = info.sheet.zh_setting.cols[range.col];
-            const validText = is_numeric(info.pasteData.text) ? parseFloat(info.pasteData.text) : (info.pasteData.text ? trimInvalidChar(info.pasteData.text) : 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.cellRange.row, info.cellRange.rowCount);
-                return;
+            const tree = info.sheet.zh_tree;
+            if (!tree) { return; }
+
+            const sortData = info.sheet.zh_tree.nodes;
+            const datas = [], filterNodes = [];
+
+            for (let iRow = 0; iRow < info.cellRange.rowCount; iRow ++) {
+                let bPaste = false;
+                const curRow = info.cellRange.row + iRow;
+                const node = sortData[curRow];
+                if (node) {
+                    for (let iCol = 0; iCol < info.cellRange.colCount; iCol++) {
+                        const curCol = info.cellRange.col + iCol;
+                        const colSetting = info.sheet.zh_setting.cols[curCol];
+                        let validText = is_numeric(info.sheet.getText(curRow, curCol)) ? parseFloat(info.sheet.getText(curRow, curCol)) : (info.sheet.getText(curRow, curCol) ? trimInvalidChar(info.sheet.getText(curRow, curCol)) : null);
+                        const orgValue = node[colSetting.field];
+                        if (orgValue == validText || ((!orgValue || orgValue === '') && (validText === ''))) {
+                            continue;
+                        }
+                        if (isNaN(validText)) {
+                            toastMessageUniq(hint.numberExpr);
+                            continue;
+                        }
+                        const yearmonth = colSetting.field.split('_')[0];
+                        const mode = colSetting.field.split('_')[1];
+                        let plan_gcl = 0;
+                        let plan_tp = 0;
+                        // 判断输入位数,提示
+                        if (mode === 'tp') {
+                            const reg = new RegExp('^([-]?)\\d+$');
+                            if (validText !== null && (!reg.test(validText))) {
+                                validText = ZhCalc.round(validText, 0);
+                            }
+                            plan_gcl = node.dgn_price && node.dgn_price !== 0 ? ZhCalc.round(ZhCalc.div(validText, node.dgn_price), 3) : 0;
+                            plan_tp = validText;
+                        } else {
+                            const reg = new RegExp('^([-]?)\\d+$');
+                            if (validText !== null && (!reg.test(validText))) {
+                                validText = ZhCalc.round(validText, 3);
+                            }
+                            plan_gcl = validText;
+                            plan_tp = node.dgn_price && node.dgn_price !== 0 ? ZhCalc.round(ZhCalc.mul(validText, node.dgn_price), 0) : 0;
+                        }
+                        const updateData = {
+                            lid: node.ledger_id,
+                            yearmonth,
+                            plan_gcl,
+                            plan_tp,
+                        };
+                        datas.push(updateData);
+                        node[yearmonth+'_gcl'] = plan_gcl;
+                        node[yearmonth+'_tp'] = plan_tp;
+                        bPaste = true;
+                    }
+                    if (bPaste) {
+                        filterNodes.push(node);
+                    }
+                }
             }
-            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 + '位');
+            if (datas.length > 0) {
+                postData(window.location.pathname + '/save', {type: 'ledger_paste', postData: datas}, function (result) {
+                    for (const uul of filterNodes) {
+                        const nodes = treeCalc.calculateParent(info.sheet.zh_tree, uul);
+                        const refreshNode = ledgerTree.loadPostData({update: nodes});
+                        ledgerSpreadObj.refreshTree(info.sheet, refreshNode);
+                    }
+                    SpreadJsObj.reLoadSheetData(info.sheet);
+                }, function () {
                     SpreadJsObj.reLoadRowData(info.sheet, info.cellRange.row, info.cellRange.rowCount);
-                    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.cellRange.row, info.cellRange.rowCount);
-                    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,
-            };
-            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.cellRange.row, info.cellRange.rowCount);
-            },function () {
-                select[col.field] = orgValue;
                 SpreadJsObj.reLoadRowData(info.sheet, info.cellRange.row, info.cellRange.rowCount);
-            })
+            }
         },
     };
 
@@ -329,13 +402,58 @@ $(function () {
     // 进度计算方式选择
     $('.mode-select').on('click', function () {
         const _self = $(this);
-        postData(window.location.pathname + '/save', {type: 'mode', postData: $(this).data('mode')}, function (result) {
+        const this_mode = _self.data('mode');
+        // 重新计算所有未使用的月份数据
+        const under_ledger = _.filter(ledgerTree.nodes, { is_leaf: true});
+        const update_nodes = [];
+        const newMonthList = _.map(_.filter(scheduleMonth, function (item) {
+            return item.stage_gcl_used === 0 && item.stage_tp_used === 0;
+        }), 'yearmonth');
+        for (const ul of under_ledger) {
+            for (const m of newMonthList) {
+                if (ul[m+'_tp'] || ul[m+'_gcl']) {
+                    update_nodes.push(ul);
+                    break;
+                }
+            }
+        }
+        const update_under_ledger = [];
+        if(update_nodes.length > 0) {
+            for (const un of update_nodes) {
+                if (this_mode === mode.gcl) {
+                    for (const m of newMonthList) {
+                        if(un[m+'_tp']) {
+                            un[m+'_tp'] = un.dgn_price && un.dgn_price !== 0 ? ZhCalc.round(ZhCalc.mul(un[m+'_gcl'], un.dgn_price), 0) : 0;
+                            update_under_ledger.push({lid: un.ledger_id, yearmonth: m, plan_tp: un[m+'_tp'], plan_gcl: un[m+'_gcl']});
+                        }
+                    }
+                } else {
+                    for (const m of newMonthList) {
+                        if(un[m+'_gcl']) {
+                            un[m+'_gcl'] = un.dgn_price && un.dgn_price !== 0 ? ZhCalc.round(ZhCalc.div(un[m+'_tp'], un.dgn_price), 3) : 0;
+                            update_under_ledger.push({lid: un.ledger_id, yearmonth: m, plan_tp: un[m+'_tp'], plan_gcl: un[m+'_gcl']});
+                        }
+                    }
+                }
+            }
+        }
+
+        postData(window.location.pathname + '/save', {type: 'mode', postData: {mode: this_mode, update_under_ledger}}, function (result) {
             _self.addClass('disabled').attr('disabled', true);
+            _self.text('当前');
             _self.parents('.col-6').siblings('.col-6').find('button').removeClass('disabled').removeAttr('disabled');
+            _self.parents('.col-6').siblings('.col-6').find('button').text('选择');
             $('#mode-tips').show();
             $('#mode-cancel').show();
             $('#mode').modal('hide');
-            schedule.mode = _self.data('mode');
+            schedule.mode = this_mode;
+            if (update_nodes.length > 0) {
+                for (const uul of update_nodes) {
+                    const nodes = treeCalc.calculateParent(ledgerSpread.getActiveSheet().zh_tree, uul);
+                    const refreshNode = ledgerTree.loadPostData({update: nodes});
+                    ledgerSpreadObj.refreshTree(ledgerSpread.getActiveSheet(), refreshNode);
+                }
+            }
             SpreadJsObj.reLoadSheetData(ledgerSpread.getActiveSheet());
         })
     });
@@ -379,7 +497,7 @@ $(function () {
             const hadmonth = [];
             for (const m of addMonthList) {
                 const one = _.find(scheduleMonth, { yearmonth: m });
-                console.log(one, m);
+
                 if (one) {
                     hadmonth.push(m);
                 }

+ 318 - 149
app/public/js/schedule_stage_gcl.js

@@ -22,11 +22,22 @@ $(function () {
         level: 'level',
         rootId: -1,
         fullPath: 'full_path',
+        calcFun: function (node) {
+            node.dgn_price = ZhCalc.round(ZhCalc.div(node.total_price, node.dgn_qty1), 2);
+            if (node.children && node.children.length > 0) {
+                for (const sm of scheduleMonth) {
+                    if (sm.stage_gcl_used === 1) {
+                        node[sm.yearmonth + '_plan_gcl'] = ZhCalc.round(ZhCalc.div(node[sm.yearmonth + '_plan_tp'], node.dgn_price), 3);
+                        node[sm.yearmonth + '_sj_gcl'] = ZhCalc.round(ZhCalc.div(node[sm.yearmonth + '_sj_tp'], node.dgn_price), 3);
+                    }
+                }
+            }
+        }
         //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'},
@@ -34,7 +45,7 @@ $(function () {
         {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},
+        {title: '|金额(元)', colSpan: '|1', rowSpan: '|1', field: 'total_price', hAlign: 2, width: 70, type: 'Number', readOnly: true},
     ];
 
     const ledgerSpreadSetting = {
@@ -46,33 +57,40 @@ $(function () {
         font: '12px 微软雅黑',
         // readOnly: true,
         localCache: {
-            key: 'ledger-bills',
+            key: 'ledger-gcl',
             colWidth: true,
         }
     };
     const monthsCols = [];
+    const calcCols = ['total_price'];
     if(scheduleMonth.length > 0) {
         for (const sm of scheduleMonth) {
             if (sm.stage_gcl_used === 1) {
                 const yearmonth = sm.yearmonth.split('-')[0] + '年' + parseInt(sm.yearmonth.split('-')[1]) + '月';
                 const cols = {title: yearmonth + '|计划工程量', colSpan: '4|1', rowSpan: '1|1', field: sm.yearmonth+'_plan_gcl', hAlign: 2, width: 90, type: 'Number', readOnly: true};
-                const cols2 = {title: '|计划金额(元)', colSpan: '|1', rowSpan: '|1', field: sm.yearmonth+'_plan_tp', hAlign: 2, width: 90, type: 'Number', readOnly: true};
+                const cols2 = {title: '|计划金额(元)', colSpan: '|1', rowSpan: '|1', field: sm.yearmonth+'_plan_tp', hAlign: 2, width: 90, type: 'Number', readOnly: true};
                 const cols3 = {title: '|计量工程量', colSpan: '|1', rowSpan: '|1', field: sm.yearmonth+'_sj_gcl', hAlign: 2, width: 90, type: 'Number', readOnly: 'readOnly.gcl'};
-                const cols4 = {title: '|计量金额(元)', colSpan: '|1', rowSpan: '|1', field: sm.yearmonth+'_sj_tp', hAlign: 2, width: 90, type: 'Number', readOnly : true};
+                const cols4 = {title: '|计量金额(元)', colSpan: '|1', rowSpan: '|1', field: sm.yearmonth+'_sj_tp', hAlign: 2, width: 90, type: 'Number', readOnly : true};
                 monthsCols.push(cols);
                 monthsCols.push(cols2);
                 monthsCols.push(cols3);
                 monthsCols.push(cols4);
+                // calcCols.push(sm.yearmonth + '_plan_gcl');
+                calcCols.push(sm.yearmonth + '_plan_tp');
+                // calcCols.push(sm.yearmonth + '_sj_gcl');
+                calcCols.push(sm.yearmonth + '_sj_tp');
             }
         }
     }
+    treeSetting.calcFields = calcCols;
+    const ledgerTree = createNewPathTree('filter', treeSetting);
     const spreadHeaderCols = static_cols.concat(monthsCols);
     ledgerSpreadSetting.cols = spreadHeaderCols;
 
     const ledgerCol = {
         readOnly: {
             gcl: function (data) {
-                return !data.is_leaf;
+                return !(data.is_leaf && !revising);
             },
         },
     };
@@ -82,6 +100,8 @@ $(function () {
     SpreadJsObj.initSpreadSettingEvents(ledgerSpreadSetting, ledgerCol);
     SpreadJsObj.initSheet(ledgerSpread.getActiveSheet(), ledgerSpreadSetting);
     SpreadJsObj.selChangedRefreshBackColor(ledgerSpread.getActiveSheet());
+    ledgerSpread.getActiveSheet().frozenColumnCount(6);
+    ledgerSpread.getActiveSheet().options.frozenlineColor = '#93b5e4';
 
     postData('/tender/' + getTenderId() + '/schedule/ledger/load', {}, function (data) {
         // let treeData = [];
@@ -133,6 +153,7 @@ $(function () {
         }
         console.log(ledgerTree);
         ledgerTree.sortTreeNode(true);
+        treeCalc.calculateAll(ledgerTree);
         // console.log(ledgerTree);
         SpreadJsObj.loadSheetData(ledgerSpread.getActiveSheet(), SpreadJsObj.DataType.Tree, ledgerTree);
     }, null, true);
@@ -187,11 +208,33 @@ $(function () {
                 }
             });
         },
+        reCalcHuizong: function(row, yearmonth, orgValue, validText) {
+            // 下方汇总sjs实时更新计量数据
+            // 先判断当前修改月份与汇总sjs月份是否一致或更大,获取修改的对应台账行
+            const huizongSelect = SpreadJsObj.getRowObject(huizongSpread.getActiveSheet(), row);
+            if (yearmonth.split('-')[0] === curScheduleMonth.yearmonth.split('-')[0]) {
+                console.log(huizongSelect, row);
+                huizongSelect.year_sj_gcl = huizongSelect.year_sj_gcl ? ZhCalc.add(ZhCalc.sub(huizongSelect.year_sj_gcl, orgValue), validText) : validText;
+                huizongSelect.year_sj_tp = huizongSelect.dgn_price && huizongSelect.dgn_price !== 0 ? ZhCalc.round(ZhCalc.mul(huizongSelect.year_sj_gcl, huizongSelect.dgn_price), 0) : 0;
+            }
+            if (yearmonth === curScheduleMonth.yearmonth) {
+                // 判断是否同年同月,是则修改本年,截至本月,本月计量数据,否则只修改截至本月计量数据
+                huizongSelect.sj_gcl = validText;
+                huizongSelect.sj_tp = huizongSelect.dgn_price && huizongSelect.dgn_price !== 0 ? ZhCalc.round(ZhCalc.mul(huizongSelect.sj_gcl, huizongSelect.dgn_price), 0) : 0;
+            }
+            if (yearmonth <= curScheduleMonth.yearmonth) {
+                huizongSelect.end_sj_gcl = huizongSelect.end_sj_gcl ? ZhCalc.add(ZhCalc.sub(huizongSelect.end_sj_gcl, orgValue), validText) : validText;
+                huizongSelect.end_sj_tp = huizongSelect.dgn_price && huizongSelect.dgn_price !== 0 ? ZhCalc.round(ZhCalc.mul(huizongSelect.end_sj_gcl, huizongSelect.dgn_price), 0) : 0;
+            }
+            const nodes = treeCalc.calculateParent(huizongSpread.getActiveSheet().zh_tree, huizongSelect);
+            const refreshNode = huizongTree.loadPostData({update: nodes});
+            huizongObj.refreshTree(huizongSpread.getActiveSheet(), refreshNode);
+        },
         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);
+                let 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);
@@ -204,14 +247,15 @@ $(function () {
                 }
                 const yearmonth = col.field.split('_')[0];
                 // 判断输入位数,提示
-                const reg = new RegExp('^([-]?)\\d+(\\.\\d{0,'+ parseInt(tenderInfo.decimal.up) +'})?$');
+                const reg = new RegExp('^([-]?)\\d+$');
                 if (validText !== null && (!reg.test(validText))) {
-                    toastr.error('输入工程量小数位数不能大于' + tenderInfo.decimal.up + '位');
-                    SpreadJsObj.reLoadRowData(info.sheet, info.row);
-                    return;
+                    // toastr.error('输入工程量小数位数不能大于3位');
+                    // SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                    // return;
+                    validText = ZhCalc.round(validText, 3);
                 }
                 const sj_gcl = validText;
-                const sj_tp = select.dgn_price && select.dgn_price !== 0 ? ZhCalc.round(ZhCalc.mul(validText, select.dgn_price), tenderInfo.decimal.tp) : 0;
+                const sj_tp = select.dgn_price && select.dgn_price !== 0 ? ZhCalc.round(ZhCalc.mul(validText, select.dgn_price), 0) : 0;
                 select[col.field] = validText;
                 const updateData = {
                     lid: select.ledger_id,
@@ -222,66 +266,136 @@ $(function () {
                 console.log(updateData);
                 postData(window.location.pathname + '/save', {type: 'ledger_edit', postData: updateData}, function (result) {
                     select[yearmonth + '_sj_tp'] = sj_tp;
-                    SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                    const nodes = treeCalc.calculateParent(info.sheet.zh_tree, select);
+                    const refreshNode = ledgerTree.loadPostData({update: nodes});
+                    ledgerSpreadObj.refreshTree(info.sheet, refreshNode);
+                    ledgerSpreadObj.reCalcHuizong(info.row, yearmonth, orgValue, validText);
                 },function () {
                     select[col.field] = orgValue;
-                    SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                    const nodes = treeCalc.calculateParent(info.sheet.zh_tree, select);
+                    const refreshNode = ledgerTree.loadPostData({update: nodes});
+                    ledgerSpreadObj.refreshTree(info.sheet, refreshNode);
                 })
             }
         },
         deletePress: function (sheet) {
-            return;
+            if (!sheet.zh_setting) return;
+            if (sheet.zh_setting && sheet.zh_tree) {
+                const sel = sheet.getSelections()[0], datas = [], filterNodes = [], huizongNodes = [];
+                if (!sel) return;
+                for (let iRow = sel.row; iRow < sel.row + sel.rowCount; iRow++) {
+                    let bDel = false;
+                    const node = sheet.zh_tree.nodes[iRow];
+                    for (let iCol = sel.col; iCol < sel.col + sel.colCount; iCol++) {
+                        const col = sheet.zh_setting.cols[iCol];
+                        const orgValue = node[col.field];
+                        if (!orgValue) {
+                            continue;
+                        }
+                        const yearmonth = col.field.split('_')[0];
+                        if(col.readOnly === true || !node.is_leaf) {
+                            continue;
+                        }
+                        const updateData = {
+                            lid: node.ledger_id,
+                            yearmonth,
+                            sj_gcl: null,
+                            sj_tp: null,
+                        };
+                        datas.push(updateData);
+                        huizongNodes.push({row: iRow, yearmonth, orgValue, nowValue: null});
+                        node[yearmonth+'_sj_gcl'] = null;
+                        node[yearmonth+'_sj_tp'] = null;
+                        bDel = true;
+                    }
+                    if (bDel) filterNodes.push(node);
+                }
+                if (datas.length > 0) {
+                    postData(window.location.pathname + '/save', {type: 'ledger_paste', postData: datas}, function (result) {
+                        for (const uul of filterNodes) {
+                            const nodes = treeCalc.calculateParent(sheet.zh_tree, uul);
+                            const refreshNode = ledgerTree.loadPostData({update: nodes});
+                            ledgerSpreadObj.refreshTree(sheet, refreshNode);
+                        }
+                        for (const hz of huizongNodes) {
+                            ledgerSpreadObj.reCalcHuizong(hz.row, hz.yearmonth, hz.orgValue, hz.nowValue);
+                        }
+                        SpreadJsObj.reLoadSheetData(sheet);
+                    }, function () {
+                        SpreadJsObj.reLoadSheetData(sheet);
+                    });
+                }
+            }
         },
         clipboardPasted(e, info) {
             const hint = {
                 cellError: {type: 'error', msg: '粘贴内容超出了表格范围'},
                 numberExpr: {type: 'error', msg: '不能粘贴其它非数字类型字符'},
             };
-            const range = info.cellRange;
-            if (range.rowCount > 1 || range.colCount > 1) {
-                toastMessageUniq(hint.cellError);
-                SpreadJsObj.reLoadSheetHeader(ledgerSpread.getActiveSheet());
-                SpreadJsObj.reLoadSheetData(ledgerSpread.getActiveSheet());
-                return;
-            }
-            const select = SpreadJsObj.getSelectObject(info.sheet);
-            const col = info.sheet.zh_setting.cols[range.col];
-            const validText = is_numeric(info.pasteData.text) ? parseFloat(info.pasteData.text) : (info.pasteData.text ? trimInvalidChar(info.pasteData.text) : 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.cellRange.row, info.cellRange.rowCount);
-                return;
+            const tree = info.sheet.zh_tree;
+            if (!tree) { return; }
+            const sortData = info.sheet.zh_tree.nodes;
+            const datas = [], filterNodes = [], huizongNodes = [];
+
+            for (let iRow = 0; iRow < info.cellRange.rowCount; iRow ++) {
+                let bPaste = false;
+                const curRow = info.cellRange.row + iRow;
+                const node = sortData[curRow];
+                if (node) {
+                    for (let iCol = 0; iCol < info.cellRange.colCount; iCol++) {
+                        const curCol = info.cellRange.col + iCol;
+                        const colSetting = info.sheet.zh_setting.cols[curCol];
+                        let validText = is_numeric(info.sheet.getText(curRow, curCol)) ? parseFloat(info.sheet.getText(curRow, curCol)) : (info.sheet.getText(curRow, curCol) ? trimInvalidChar(info.sheet.getText(curRow, curCol)) : null);
+                        const orgValue = node[colSetting.field];
+                        if (orgValue == validText || ((!orgValue || orgValue === '') && (validText === ''))) {
+                            continue;
+                        }
+                        if (isNaN(validText)) {
+                            toastMessageUniq(hint.numberExpr);
+                            continue;
+                        }
+                        const yearmonth = colSetting.field.split('_')[0];
+                        // 判断输入位数,提示
+                        const reg = new RegExp('^([-]?)\\d+$');
+                        if (validText !== null && (!reg.test(validText))) {
+                            validText = ZhCalc.round(validText, 3);
+                        }
+                        const sj_gcl = validText;
+                        const sj_tp = node.dgn_price && node.dgn_price !== 0 ? ZhCalc.round(ZhCalc.mul(validText, node.dgn_price), 0) : 0;
+                        const updateData = {
+                            lid: node.ledger_id,
+                            yearmonth,
+                            sj_gcl,
+                            sj_tp,
+                        };
+                        datas.push(updateData);
+                        huizongNodes.push({row: info.cellRange.row + iRow, yearmonth, orgValue, nowValue: validText});
+                        node[yearmonth+'_sj_gcl'] = sj_gcl;
+                        node[yearmonth+'_sj_tp'] = sj_tp;
+                        bPaste = true;
+                    }
+                    if (bPaste) {
+                        filterNodes.push(node);
+                    }
+                }
             }
-            const yearmonth = col.field.split('_')[0];
-            // 判断输入位数,提示
-            const reg = new RegExp('^([-]?)\\d+(\\.\\d{0,'+ parseInt(tenderInfo.decimal.up) +'})?$');
-            if (validText !== null && (!reg.test(validText))) {
-                toastr.error('粘贴的工程量小数位数不能大于' + tenderInfo.decimal.up + '位');
+            if (datas.length > 0) {
+                postData(window.location.pathname + '/save', {type: 'ledger_paste', postData: datas}, function (result) {
+                    for (const uul of filterNodes) {
+                        const nodes = treeCalc.calculateParent(info.sheet.zh_tree, uul);
+                        const refreshNode = ledgerTree.loadPostData({update: nodes});
+                        ledgerSpreadObj.refreshTree(info.sheet, refreshNode);
+                    }
+                    for (const hz of huizongNodes) {
+                        ledgerSpreadObj.reCalcHuizong(hz.row, hz.yearmonth, hz.orgValue, hz.nowValue);
+                    }
+                    SpreadJsObj.reLoadSheetData(info.sheet);
+                }, function () {
+                    SpreadJsObj.reLoadRowData(info.sheet, info.cellRange.row, info.cellRange.rowCount);
+                });
+            } else {
                 SpreadJsObj.reLoadRowData(info.sheet, info.cellRange.row, info.cellRange.rowCount);
-                return;
             }
-            const sj_gcl = validText;
-            const sj_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,
-                sj_gcl,
-                sj_tp,
-            };
-            console.log(updateData);
-            postData(window.location.pathname + '/save', {type: 'ledger_edit', postData: updateData}, function (result) {
-                select[yearmonth + '_sj_tp'] = sj_tp;
-                SpreadJsObj.reLoadRowData(info.sheet, info.cellRange.row, info.cellRange.rowCount);
-            },function () {
-                select[col.field] = orgValue;
-                SpreadJsObj.reLoadRowData(info.sheet, info.cellRange.row, info.cellRange.rowCount);
-            })
         },
     };
 
@@ -299,7 +413,19 @@ $(function () {
         level: 'level',
         rootId: -1,
         fullPath: 'full_path',
-        calcFields: ['plan_gcl', 'plan_tp', 'next_plan_gcl', 'next_plan_tp', 'end_plan_gcl', 'end_plan_tp', 'year_plan_gcl', 'year_plan_tp'],
+        calcFun: function (node) {
+            node.dgn_price = ZhCalc.round(ZhCalc.div(node.total_price, node.dgn_qty1), 2);
+            if (node.children && node.children.length > 0) {
+                node.end_plan_gcl = ZhCalc.round(ZhCalc.div(node.end_plan_tp, node.dgn_price), 3);
+                node.year_plan_gcl = ZhCalc.round(ZhCalc.div(node.year_plan_tp, node.dgn_price), 3);
+                node.plan_gcl = ZhCalc.round(ZhCalc.div(node.plan_tp, node.dgn_price), 3);
+                node.next_plan_gcl = ZhCalc.round(ZhCalc.div(node.next_plan_tp, node.dgn_price), 3);
+                node.end_sj_gcl = ZhCalc.round(ZhCalc.div(node.end_sj_tp, node.dgn_price), 3);
+                node.year_sj_gcl = ZhCalc.round(ZhCalc.div(node.year_sj_tp, node.dgn_price), 3);
+                node.sj_gcl = ZhCalc.round(ZhCalc.div(node.sj_tp, node.dgn_price), 3);
+            }
+        },
+        calcFields: ['plan_tp', 'next_plan_tp', 'end_plan_tp', 'year_plan_tp', 'total_price', 'end_sj_tp', 'year_sj_tp', 'sj_tp'],
     };
 
     const huizong_static_cols = [
@@ -308,19 +434,21 @@ $(function () {
         {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},
+        {title: '|金额(元)', colSpan: '|1', rowSpan: '|1', field: 'total_price', hAlign: 2, width: 70, type: 'Number', readOnly: true},
         {title: '自开工至本月计划完成|工程量', colSpan: '2|1', rowSpan: '1|1', field: 'end_plan_gcl', hAlign: 2, width: 70, type: 'Number', readOnly: true},
-        {title: '|金额(元)', colSpan: '|1', rowSpan: '|1', field: 'end_plan_tp', hAlign: 2, width: 70, type: 'Number', readOnly: true},
-        {title: '截止本月完成计量|工程量', colSpan: '2|1', rowSpan: '1|1', field: 'end_gather_qty', hAlign: 2, width: 70, type: 'Number', readOnly: true},
-        {title: '|金额(万元)', colSpan: '|1', rowSpan: '|1', field: 'end_gather_tp', hAlign: 2, width: 70, type: 'Number', readOnly: true},
+        {title: '|金额(元)', colSpan: '|1', rowSpan: '|1', field: 'end_plan_tp', hAlign: 2, width: 70, type: 'Number', readOnly: true},
+        {title: '截止本月完成计量|工程量', colSpan: '2|1', rowSpan: '1|1', field: 'end_sj_gcl', hAlign: 2, width: 70, type: 'Number', readOnly: true},
+        {title: '|金额(元)', colSpan: '|1', rowSpan: '|1', field: 'end_sj_tp', hAlign: 2, width: 70, type: 'Number', readOnly: true},
         {title: '本年计划完成|工程量', colSpan: '2|1', rowSpan: '1|1', field: 'year_plan_gcl', hAlign: 2, width: 70, type: 'Number', readOnly: true},
-        {title: '|金额(元)', colSpan: '|1', rowSpan: '|1', field: 'year_plan_tp', hAlign: 2, width: 70, type: 'Number', readOnly: true},
-        {title: '本年累计完成|工程量', colSpan: '2|1', rowSpan: '1|1', field: 'year_contract_qty', hAlign: 2, width: 70, type: 'Number', readOnly: true},
-        {title: '|金额(元)', colSpan: '|1', rowSpan: '|1', field: 'year_gather_tp', hAlign: 2, width: 70, type: 'Number', readOnly: true},
+        {title: '|金额(元)', colSpan: '|1', rowSpan: '|1', field: 'year_plan_tp', hAlign: 2, width: 70, type: 'Number', readOnly: true},
+        {title: '本年累计完成|工程量', colSpan: '2|1', rowSpan: '1|1', field: 'year_sj_gcl', hAlign: 2, width: 70, type: 'Number', readOnly: true},
+        {title: '|金额(元)', colSpan: '|1', rowSpan: '|1', field: 'year_sj_tp', hAlign: 2, width: 70, type: 'Number', readOnly: true},
         {title: '本月计划完成|工程量', colSpan: '2|1', rowSpan: '1|1', field: 'plan_gcl', hAlign: 2, width: 70, type: 'Number', readOnly: true},
-        {title: '|金额(万元)', colSpan: '|1', rowSpan: '|1', field: 'plan_tp', hAlign: 2, width: 70, type: 'Number', readOnly: true},
+        {title: '|金额(元)', colSpan: '|1', rowSpan: '|1', field: 'plan_tp', hAlign: 2, width: 70, type: 'Number', readOnly: true},
+        {title: '本月计量完成|工程量', colSpan: '2|1', rowSpan: '1|1', field: 'sj_gcl', hAlign: 2, width: 70, type: 'Number', readOnly: true},
+        {title: '|金额(元)', colSpan: '|1', rowSpan: '|1', field: 'sj_tp', hAlign: 2, width: 70, type: 'Number', readOnly: true},
         {title: '下月计划|工程量', colSpan: '2|1', rowSpan: '1|1', field: 'next_plan_gcl', hAlign: 2, width: 70, type: 'Number', readOnly: true},
-        {title: '|金额(万元)', colSpan: '|1', rowSpan: '|1', field: 'next_plan_tp', hAlign: 2, width: 70, type: 'Number', readOnly: true},
+        {title: '|金额(元)', colSpan: '|1', rowSpan: '|1', field: 'next_plan_tp', hAlign: 2, width: 70, type: 'Number', readOnly: true},
     ];
 
     const huizongSpreadSetting = {
@@ -332,7 +460,7 @@ $(function () {
         font: '12px 微软雅黑',
         // readOnly: true,
         localCache: {
-            key: 'ledger-bills',
+            key: 'ledger-huizong',
             colWidth: true,
         }
     };
@@ -342,94 +470,136 @@ $(function () {
     if (thousandth) sjsSettingObj.setTpThousandthFormat(huizongSpreadSetting);
     SpreadJsObj.initSheet(huizongSpread.getActiveSheet(), huizongSpreadSetting);
     SpreadJsObj.selChangedRefreshBackColor(huizongSpread.getActiveSheet());
+    huizongSpread.getActiveSheet().frozenColumnCount(6);
+    huizongSpread.getActiveSheet().options.frozenlineColor = '#93b5e4';
+
+    let huizongTree = '';
 
     const huizongObj = {
-        setSjs: function (order) {
-            postData('/tender/' + getTenderId() + '/measure/stage/' + order + '/load', { filter: 'ledger' }, function (data) {
-                postData('/tender/' + getTenderId() + '/schedule/stage/' + order + '/load', {}, function (data2) {
-                    const calcList = ['year_contract_qty', 'year_gather_tp',
-                        'contract_qty', 'end_gather_qty', 'contract_tp', 'qc_qty', 'qc_tp', 'total_price', 'contract_tp', 'qc_tp', 'gather_tp',
-                        'pre_contract_tp', 'pre_qc_tp', 'pre_gather_tp', 'end_contract_tp', 'end_qc_tp', 'end_gather_tp', 'end_correct_tp'];
-                    const showList = ['plan_gcl', 'plan_tp', 'next_plan_gcl', 'next_plan_tp', 'end_plan_gcl', 'end_plan_tp', 'year_plan_gcl', 'year_plan_tp',
-                        'year_contract_qty', 'year_gather_tp',
-                        'contract_qty', 'end_gather_qty', 'contract_tp', 'qc_qty', 'qc_tp', 'ledger_id', 'ledger_pid', 'order', 'level', 'tender_id', 'full_path',
-                        'code', 'name', 'unit', 'dgn_qty1', 'dgn_qty2', 'dgn_price', 'quantity', 'total_price', 'contract_tp', 'qc_tp', 'gather_tp',
-                        'pre_contract_tp', 'pre_qc_tp', 'pre_gather_tp', 'end_contract_tp', 'end_qc_tp', 'end_gather_tp', 'end_correct_tp'];
-                    const baseLedgerTreeSetting = {
-                        id: 'ledger_id',
-                        pid: 'ledger_pid',
-                        order: 'order',
-                        level: 'level',
-                        rootId: -1,
-                        fullPath: 'full_path',
-                        calcFields: calcList,
-                    };
-                    baseLedgerTreeSetting.updateFields = ['year_contract_qty', 'year_gather_tp', 'contract_qty', 'end_gather_qty', 'contract_tp', 'qc_qty', 'qc_tp', 'postil', 'used', 'contract_expr'];
-                    baseLedgerTreeSetting.calcFun = function (node) {
-                        if (!node.children || node.children.length === 0) {
-                            node.pre_gather_qty = ZhCalc.add(node.pre_contract_qty, node.pre_qc_qty);
-                            node.gather_qty = ZhCalc.add(node.contract_qty, node.qc_qty);
-                            node.end_contract_qty = ZhCalc.add(node.pre_contract_qty, node.contract_qty);
-                            node.end_qc_qty = ZhCalc.add(node.pre_qc_qty, node.qc_qty);
-                            node.end_gather_qty = ZhCalc.add(node.pre_gather_qty, node.gather_qty);
+        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);
                         }
-                        node.pre_gather_tp = ZhCalc.add(node.pre_contract_tp, node.pre_qc_tp);
-                        node.gather_tp = ZhCalc.add(node.contract_tp, node.qc_tp);
-                        node.end_contract_tp = ZhCalc.add(node.pre_contract_tp, node.contract_tp);
-                        node.end_qc_tp = ZhCalc.add(node.pre_qc_tp, node.qc_tp);
-                        node.end_gather_tp = ZhCalc.add(node.pre_gather_tp, node.gather_tp);
-                        node.end_final_tp = ZhCalc.add(node.end_qc_tp, node.total_price);
-                        if (!node.children || node.children.length === 0) {
-                            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_tp;
+                    }
+                }
+                // 处理更新
+                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);
                             }
                         }
-                        node.end_gather_percent = ZhCalc.mul(ZhCalc.div(node.end_gather_tp, node.end_final_tp), 100, 2);
-                        node.end_correct_percent = ZhCalc.mul(ZhCalc.div(node.end_correct_tp, node.end_final_tp), 100, 2);
-                        node.final_dgn_price = ZhCalc.round(ZhCalc.div(node.end_gather_tp, ZhCalc.add(node.deal_dgn_qty1, node.c_dgn_qty1)), tenderInfo.decimal.up);
-                        node.dgn_price = ZhCalc.round(ZhCalc.div(node.total_price, node.dgn_qty1), 2);
-                    };
-                    const baseLedgerTree = createNewPathTree('base', baseLedgerTreeSetting);
-                    const newLedgerList = setTpMonthToLedger(data.ledgerData, data2.slmList, data2.nextSlmList, data2.endSlmList, data2.yearSlmList, data2.curYearStageData);
-                    baseLedgerTree.loadDatas(newLedgerList);
-                    treeCalc.calculateAll(baseLedgerTree);
-                    const huizongTree = createNewPathTree('filter', huizongTreeSetting);
-                    for (const d of baseLedgerTree.nodes) {
-                        if (!d.b_code) {
-                            const one = _.find(selectedLedgerList, function (item) {
-                                return item === d.ledger_id;
-                            });
-                            if (one) {
-                                huizongTree.addData(d, showList);
-                            }
+                    }
+                }
+            });
+        },
+        setSjs: function (order) {
+            postData('/tender/' + getTenderId() + '/schedule/stage/gcl/' + order + '/load', {}, function (data) {
+                const calcList = ['total_price'];
+                const showList = ['plan_gcl', 'plan_tp', 'next_plan_gcl', 'next_plan_tp', 'end_plan_gcl', 'end_plan_tp', 'year_plan_gcl', 'year_plan_tp',
+                    'sj_gcl', 'sj_tp', 'year_sj_gcl', 'year_sj_tp', 'end_sj_gcl', 'end_sj_tp',
+                    'ledger_id', 'ledger_pid', 'order', 'level', 'tender_id', 'full_path',
+                    'code', 'name', 'unit', 'dgn_qty1', 'dgn_qty2', 'dgn_price', 'quantity', 'total_price'];
+                const baseLedgerTreeSetting = {
+                    id: 'ledger_id',
+                    pid: 'ledger_pid',
+                    order: 'order',
+                    level: 'level',
+                    rootId: -1,
+                    fullPath: 'full_path',
+                    calcFields: calcList,
+                };
+                // baseLedgerTreeSetting.updateFields = ['year_sj_qty', 'year_gather_tp', 'contract_qty', 'end_gather_qty', 'contract_tp', 'qc_qty', 'qc_tp', 'postil', 'used', 'contract_expr'];
+                baseLedgerTreeSetting.calcFun = function (node) {
+                    node.dgn_price = ZhCalc.round(ZhCalc.div(node.total_price, node.dgn_qty1), 2);
+                };
+                const baseLedgerTree = createNewPathTree('base', baseLedgerTreeSetting);
+                console.log(data.slmList);
+                const newLedgerList = setGclMonthToLedger(data.ledgerData, data.slmList, data.nextSlmList, data.endSlmList, data.yearSlmList);
+                baseLedgerTree.loadDatas(newLedgerList);
+                treeCalc.calculateAll(baseLedgerTree);
+                huizongTree = createNewPathTree('filter', huizongTreeSetting);
+                for (const d of baseLedgerTree.nodes) {
+                    if (!d.b_code) {
+                        const one = _.find(selectedLedgerList, function (item) {
+                            return item === d.ledger_id;
+                        });
+                        if (one) {
+                            huizongTree.addData(d, showList);
                         }
                     }
-                    huizongTree.sortTreeNode(true);
-                    treeCalc.calculateAll(huizongTree);
-                    console.log(huizongTree);
-                    SpreadJsObj.loadSheetData(huizongSpread.getActiveSheet(), SpreadJsObj.DataType.Tree, huizongTree);
-                }, null, true);
+                }
+                huizongTree.sortTreeNode(true);
+                treeCalc.calculateAll(huizongTree);
+                console.log(huizongTree);
+                SpreadJsObj.loadSheetData(huizongSpread.getActiveSheet(), SpreadJsObj.DataType.Tree, huizongTree);
+                // huizongSpread.refresh();
             }, null, true);
+        },
+        dropDownHtml: function (order) {
+            let html = '';
+            for (const ss of gclScheduleMonth) {
+                if (ss.id !== order) {
+                    html += '<a class="dropdown-item change-tp" data-order="'+ ss.id +'" href="javascript:void(0);">'+ ss.yearmonth.split('-')[0] + '年' + parseInt(ss.yearmonth.split('-')[1]) +'月</a>';
+                } else {
+                    $('#stageDropdownMenuButton').text(ss.yearmonth.split('-')[0] + '年' + parseInt(ss.yearmonth.split('-')[1]) +'月');
+                }
+            }
+            $('#stageDropdownMenu').html(html);
         }
     };
 
-    if (curScheduleStage && curScheduleStage.order) {
-        huizongObj.setSjs(curScheduleStage.order);
+    if (curScheduleMonth && curScheduleMonth.id) {
+        let order = parseInt(localStorage.getItem('tender_' + getTenderId() + '_schedule_tp_sjs') ? localStorage.getItem('tender_' + getTenderId() + '_schedule_tp_sjs') : curScheduleMonth.id);
+        const ssinfo = _.find(gclScheduleMonth, { id: order });
+        if (!ssinfo) {
+            order = curScheduleMonth.id;
+        } else {
+            curScheduleMonth = ssinfo;
+        }
+        huizongObj.dropDownHtml(order);
+        huizongObj.setSjs(order);
     }
 
     // 汇总切换
     $('body').on('click', '.change-tp', function () {
         const order = parseInt($(this).data('order'));
-        $('#dropdownMenuButton').text($(this).text());
-        let html = '';
-        for (const ss of scheduleStage) {
-            if (ss.order !== order)
-            html += '<a class="dropdown-item change-tp" data-order="'+ ss.order +'" href="javascript:void(0);">'+ ss.yearmonth.split('-')[0] + '年' + parseInt(ss.yearmonth.split('-')[1]) +'月(第'+ ss.order +'期)</a>';
-        }
-        $('.dropdown-menu').html(html);
+        huizongObj.dropDownHtml(order);
         huizongObj.setSjs(order);
+        localStorage.setItem('tender_' + getTenderId() + '_schedule_tp_sjs', order);
+        curScheduleMonth = _.find(gclScheduleMonth, { id: order });
     });
 
     // 月份添加
@@ -441,6 +611,7 @@ $(function () {
         }
         const _self = $(this);
         postData(window.location.pathname + '/save', {type: 'add_stage', postData: { id: id }}, function (result) {
+            localStorage.setItem('tender_' + getTenderId() + '_schedule_tp_sjs', id);
             _self.addClass('disabled').attr('disabled', true);
             toastr.success('创建成功');
             setTimeout(function () {
@@ -475,6 +646,7 @@ $(function () {
         }
         const _self = $(this);
         postData(window.location.pathname + '/save', {type: 'del_stage', postData: selectedMonth}, function (result) {
+            localStorage.removeItem('tender_' + getTenderId() + '_schedule_tp_sjs');
             _self.addClass('disabled').attr('disabled', true);
             toastr.success('删除成功');
             setTimeout(function () {
@@ -541,13 +713,15 @@ function setMonthToLedger(ledgerList, slm) {
     }
     return ledgerList;
 }
-function setTpMonthToLedger(ledgerList, slm, nextSlm, endSlm, yearSlm, yearLedgerData) {
+function setGclMonthToLedger(ledgerList, slm, nextSlm, endSlm, yearSlm) {
     if (slm.length > 0) {
         for(const s of slm) {
             const index = _.findIndex(ledgerList, { 'ledger_id': s.lid });
             if (index && index !== -1) {
                 ledgerList[index].plan_tp = s.plan_tp;
                 ledgerList[index].plan_gcl = s.plan_gcl;
+                ledgerList[index].sj_tp = s.sj_tp;
+                ledgerList[index].sj_gcl = s.sj_gcl;
             }
         }
     }
@@ -566,6 +740,8 @@ function setTpMonthToLedger(ledgerList, slm, nextSlm, endSlm, yearSlm, yearLedge
             if (index && index !== -1) {
                 ledgerList[index].end_plan_tp = es.plan_tp;
                 ledgerList[index].end_plan_gcl = es.plan_gcl;
+                ledgerList[index].end_sj_tp = es.sj_tp;
+                ledgerList[index].end_sj_gcl = es.sj_gcl;
             }
         }
     }
@@ -575,15 +751,8 @@ function setTpMonthToLedger(ledgerList, slm, nextSlm, endSlm, yearSlm, yearLedge
             if (index && index !== -1) {
                 ledgerList[index].year_plan_tp = ys.plan_tp;
                 ledgerList[index].year_plan_gcl = ys.plan_gcl;
-            }
-        }
-    }
-    if (yearLedgerData.length > 0) {
-        for (const yl of yearLedgerData) {
-            const index = _.findIndex(ledgerList, {'id': yl.lid});
-            if (index && index !== -1) {
-                ledgerList[index].year_contract_qty = ZhCalc.add(yl.contract_qty, yl.qc_qty);
-                ledgerList[index].year_gather_tp = ZhCalc.add(yl.contract_tp, yl.qc_tp);
+                ledgerList[index].year_sj_tp = ys.sj_tp;
+                ledgerList[index].year_sj_gcl = ys.sj_gcl;
             }
         }
     }

+ 66 - 18
app/public/js/schedule_stage_tp.js

@@ -22,7 +22,19 @@ $(function () {
         level: 'level',
         rootId: -1,
         fullPath: 'full_path',
-        calcFields: ['plan_gcl', 'plan_tp', 'next_plan_gcl', 'next_plan_tp', 'end_plan_gcl', 'end_plan_tp', 'year_plan_gcl', 'year_plan_tp'],
+        calcFun: function (node) {
+            node.dgn_price = ZhCalc.round(ZhCalc.div(node.total_price, node.dgn_qty1), 2);
+            if (node.children && node.children.length > 0) {
+                node.end_plan_gcl = ZhCalc.round(ZhCalc.div(node.end_plan_tp, node.dgn_price), 3);
+                node.year_plan_gcl = ZhCalc.round(ZhCalc.div(node.year_plan_tp, node.dgn_price), 3);
+                node.plan_gcl = ZhCalc.round(ZhCalc.div(node.plan_tp, node.dgn_price), 3);
+                node.next_plan_gcl = ZhCalc.round(ZhCalc.div(node.next_plan_tp, node.dgn_price), 3);
+            }
+            node.end_gather_qty = ZhCalc.round(ZhCalc.div(node.end_gather_tp, node.dgn_price), 3);
+            node.year_contract_qty = ZhCalc.round(ZhCalc.div(node.year_gather_tp, node.dgn_price), 3);
+            node.contract_qty = ZhCalc.round(ZhCalc.div(node.gather_tp, node.dgn_price), 3);
+        },
+        calcFields: ['plan_tp', 'next_plan_tp', 'end_plan_tp', 'year_plan_tp', 'total_price', 'end_gather_tp', 'year_gather_tp', 'gather_tp', 'end_final_gather_tp'],
     };
     const ledgerTree = createNewPathTree('filter', treeSetting);
 
@@ -32,21 +44,21 @@ $(function () {
         {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},
+        {title: '|金额(元)', colSpan: '|1', rowSpan: '|1', field: 'total_price', hAlign: 2, width: 70, type: 'Number', readOnly: true},
         {title: '自开工至本月计划完成|工程量', colSpan: '2|1', rowSpan: '1|1', field: 'end_plan_gcl', hAlign: 2, width: 70, type: 'Number', readOnly: true},
-        {title: '|金额(元)', colSpan: '|1', rowSpan: '|1', field: 'end_plan_tp', hAlign: 2, width: 70, type: 'Number', readOnly: true},
+        {title: '|金额(元)', colSpan: '|1', rowSpan: '|1', field: 'end_plan_tp', hAlign: 2, width: 70, type: 'Number', readOnly: true},
         {title: '截止本期完成计量|工程量', colSpan: '2|1', rowSpan: '1|1', field: 'end_gather_qty', hAlign: 2, width: 70, type: 'Number', readOnly: true},
-        {title: '|金额(元)', colSpan: '|1', rowSpan: '|1', field: 'end_gather_tp', hAlign: 2, width: 70, type: 'Number', readOnly: true},
+        {title: '|金额(元)', colSpan: '|1', rowSpan: '|1', field: 'end_gather_tp', hAlign: 2, width: 70, type: 'Number', readOnly: true},
         {title: '本年计划完成|工程量', colSpan: '2|1', rowSpan: '1|1', field: 'year_plan_gcl', hAlign: 2, width: 70, type: 'Number', readOnly: true},
-        {title: '|金额(元)', colSpan: '|1', rowSpan: '|1', field: 'year_plan_tp', hAlign: 2, width: 70, type: 'Number', readOnly: true},
+        {title: '|金额(元)', colSpan: '|1', rowSpan: '|1', field: 'year_plan_tp', hAlign: 2, width: 70, type: 'Number', readOnly: true},
         {title: '本年累计完成|工程量', colSpan: '2|1', rowSpan: '1|1', field: 'year_contract_qty', hAlign: 2, width: 70, type: 'Number', readOnly: true},
-        {title: '|金额(元)', colSpan: '|1', rowSpan: '|1', field: 'year_gather_tp', hAlign: 2, width: 70, type: 'Number', readOnly: true},
+        {title: '|金额(元)', colSpan: '|1', rowSpan: '|1', field: 'year_gather_tp', hAlign: 2, width: 70, type: 'Number', readOnly: true},
         {title: '本月计划完成|工程量', colSpan: '2|1', rowSpan: '1|1', field: 'plan_gcl', hAlign: 2, width: 70, type: 'Number', readOnly: true},
-        {title: '|金额(元)', colSpan: '|1', rowSpan: '|1', field: 'plan_tp', hAlign: 2, width: 70, type: 'Number', readOnly: true},
+        {title: '|金额(元)', colSpan: '|1', rowSpan: '|1', field: 'plan_tp', hAlign: 2, width: 70, type: 'Number', readOnly: true},
         {title: '本期完成计量|工程量', colSpan: '2|1', rowSpan: '1|1', field: 'contract_qty', hAlign: 2, width: 70, type: 'Number', readOnly: true},
-        {title: '|金额(元)', colSpan: '|1', rowSpan: '|1', field: 'gather_tp', hAlign: 2, width: 70, type: 'Number', readOnly: true},
+        {title: '|金额(元)', colSpan: '|1', rowSpan: '|1', field: 'gather_tp', hAlign: 2, width: 70, type: 'Number', readOnly: true},
         {title: '下月计划|工程量', colSpan: '2|1', rowSpan: '1|1', field: 'next_plan_gcl', hAlign: 2, width: 70, type: 'Number', readOnly: true},
-        {title: '|金额(元)', colSpan: '|1', rowSpan: '|1', field: 'next_plan_tp', hAlign: 2, width: 70, type: 'Number', readOnly: true},
+        {title: '|金额(元)', colSpan: '|1', rowSpan: '|1', field: 'next_plan_tp', hAlign: 2, width: 70, type: 'Number', readOnly: true},
     ];
 
     const ledgerSpreadSetting = {
@@ -58,7 +70,7 @@ $(function () {
         font: '12px 微软雅黑',
         // readOnly: true,
         localCache: {
-            key: 'ledger-bills',
+            key: 'ledger-tp',
             colWidth: true,
         }
     };
@@ -68,17 +80,21 @@ $(function () {
     if (thousandth) sjsSettingObj.setTpThousandthFormat(ledgerSpreadSetting);
     SpreadJsObj.initSheet(ledgerSpread.getActiveSheet(), ledgerSpreadSetting);
     SpreadJsObj.selChangedRefreshBackColor(ledgerSpread.getActiveSheet());
+    ledgerSpread.getActiveSheet().frozenColumnCount(6);
+    ledgerSpread.getActiveSheet().options.frozenlineColor = '#93b5e4';
 
     if (curScheduleStage && curScheduleStage.order) {
-        postData('/tender/' + getTenderId() + '/measure/stage/' + curScheduleStage.order + '/load', { filter: 'ledger' }, function (data) {
-            const calcList = ['year_contract_qty', 'year_gather_tp',
-                'contract_qty', 'end_gather_qty', 'contract_tp', 'qc_qty', 'qc_tp', 'total_price', 'contract_tp', 'qc_tp', 'gather_tp',
-                'pre_contract_tp', 'pre_qc_tp', 'pre_gather_tp', 'end_contract_tp', 'end_qc_tp', 'end_gather_tp', 'end_correct_tp'];
+        postData('/tender/' + getTenderId() + '/schedule/stage/' + curScheduleStage.order + '/load', {}, function (data) {
+            const calcList = ['year_gather_tp',
+                'contract_tp', 'qc_qty', 'qc_tp', 'total_price', 'contract_tp', 'qc_tp', 'gather_tp',
+                'pre_contract_tp', 'pre_qc_tp', 'pre_gather_tp', 'end_contract_tp', 'end_qc_tp', 'end_gather_tp', 'end_correct_tp',
+                'final_contract_tp', 'final_qc_tp', 'pre_final_contract_tp', 'pre_final_qc_tp', 'pre_final_gather_tp', 'final_gather_tp', 'end_final_gather_tp'];
             const showList = ['plan_gcl', 'plan_tp', 'next_plan_gcl', 'next_plan_tp', 'end_plan_gcl', 'end_plan_tp', 'year_plan_gcl', 'year_plan_tp',
                 'year_contract_qty', 'year_gather_tp',
                 'contract_qty', 'end_gather_qty', 'contract_tp', 'qc_qty', 'qc_tp', 'ledger_id', 'ledger_pid', 'order', 'level', 'tender_id', 'full_path',
                 'code', 'name', 'unit', 'dgn_qty1', 'dgn_qty2', 'dgn_price', 'quantity', 'total_price', 'contract_tp', 'qc_tp', 'gather_tp',
-                'pre_contract_tp', 'pre_qc_tp', 'pre_gather_tp', 'end_contract_tp', 'end_qc_tp', 'end_gather_tp', 'end_correct_tp'];
+                'pre_contract_tp', 'pre_qc_tp', 'pre_gather_tp', 'end_contract_tp', 'end_qc_tp', 'end_gather_tp', 'end_correct_tp',
+                'final_contract_tp', 'final_qc_tp', 'pre_final_contract_tp', 'pre_final_qc_tp', 'pre_final_gather_tp', 'final_gather_tp', 'end_final_gather_tp'];
             const baseLedgerTreeSetting = {
                 id: 'ledger_id',
                 pid: 'ledger_pid',
@@ -88,7 +104,7 @@ $(function () {
                 fullPath: 'full_path',
                 calcFields: calcList,
             };
-            baseLedgerTreeSetting.updateFields = ['year_contract_qty', 'year_gather_tp', 'contract_qty', 'end_gather_qty', 'contract_tp', 'qc_qty', 'qc_tp', 'postil', 'used', 'contract_expr'];
+            baseLedgerTreeSetting.updateFields = ['year_contract_qty', 'year_gather_tp', 'contract_qty', 'end_gather_qty', 'contract_tp', 'qc_qty', 'qc_tp', 'final_contract_tp', 'final_qc_tp', 'postil', 'used', 'contract_expr'];
             baseLedgerTreeSetting.calcFun = function (node) {
                 if (!node.children || node.children.length === 0) {
                     node.pre_gather_qty = ZhCalc.add(node.pre_contract_qty, node.pre_qc_qty);
@@ -114,6 +130,10 @@ $(function () {
                 node.end_correct_percent = ZhCalc.mul(ZhCalc.div(node.end_correct_tp, node.end_final_tp), 100, 2);
                 node.final_dgn_price = ZhCalc.round(ZhCalc.div(node.end_gather_tp, ZhCalc.add(node.deal_dgn_qty1, node.c_dgn_qty1)), tenderInfo.decimal.up);
                 node.dgn_price = ZhCalc.round(ZhCalc.div(node.total_price, node.dgn_qty1), 2);
+
+                node.pre_final_gather_tp = ZhCalc.add(node.pre_final_contract_tp, node.pre_final_qc_tp);
+                node.final_gather_tp = ZhCalc.add(node.final_contract_tp, node.final_qc_tp);
+                node.end_final_gather_tp = ZhCalc.add(node.pre_final_gather_tp, node.final_gather_tp);
             };
             const baseLedgerTree = createNewPathTree('base', baseLedgerTreeSetting);
             const newLedgerList = setMonthToLedger(data.ledgerData, slmList, nextSlmList, endSlmList, yearSlmList, curYearStageData);
@@ -131,6 +151,8 @@ $(function () {
             }
             ledgerTree.sortTreeNode(true);
             treeCalc.calculateAll(ledgerTree);
+            calcGatherTpAndUpdate(ledgerTree.nodes);
+            console.log(ledgerTree);
             SpreadJsObj.loadSheetData(ledgerSpread.getActiveSheet(), SpreadJsObj.DataType.Tree, ledgerTree);
         }, null, true);
     }
@@ -204,6 +226,30 @@ $(function () {
     });
 });
 
+function calcGatherTpAndUpdate(tree) {
+    if (tree.length > 0) {
+        let total_tp = 0;
+        let final_total_tp = 0;
+        for (const t of tree) {
+            if (t.is_leaf && t.gather_tp) {
+                total_tp = ZhCalc.add(total_tp, t.gather_tp);
+            }
+            if (t.is_leaf && t.end_final_gather_tp) {
+                final_total_tp = ZhCalc.add(final_total_tp, t.end_final_gather_tp);
+            }
+        }
+        console.log(curScheduleStage.tp, total_tp);
+        console.log(schedule.stage_sj_tp, final_total_tp);
+        if (curScheduleStage.tp !== total_tp || schedule.stage_sj_tp !== final_total_tp) {
+            // 更新计量总金额并更新金额模式总金额
+            postData('/tender/' + getTenderId() + '/schedule/stage/save', { type: 'update_tp', postData: { tp: total_tp, stage_sj_tp: final_total_tp, order: curScheduleStage.order } }, function (data) {
+                console.log('总金额已更新');
+            })
+        }
+    }
+
+}
+
 function setMonthToLedger(ledgerList, slm, nextSlm, endSlm, yearSlm, yearLedgerData) {
     if (slm.length > 0) {
         for(const s of slm) {
@@ -243,8 +289,10 @@ function setMonthToLedger(ledgerList, slm, nextSlm, endSlm, yearSlm, yearLedgerD
     }
     if (yearLedgerData.length > 0) {
         for (const yl of yearLedgerData) {
-            const index = _.findIndex(ledgerList, {'id': yl.lid});
-            if (index && index !== -1) {
+            const index = _.findIndex(ledgerList, function (item) {
+                return item.id === yl.lid;
+            });
+            if (index !== undefined && index !== -1) {
                 ledgerList[index].year_contract_qty = ZhCalc.add(yl.contract_qty, yl.qc_qty);
                 ledgerList[index].year_gather_tp = ZhCalc.add(yl.contract_tp, yl.qc_tp);
             }

+ 6 - 6
app/public/js/se_bonus.js

@@ -155,7 +155,7 @@ $(document).ready(() => {
                     // 下载
                     html.push('<a href="download/file?b_id=' + id + '&index=' + i + '" title="下载"><i class="fa fa-download "></i></a>');
                     // 删除
-                    if (!readOnly && !isPre(data)) {
+                    if (f.uid === userID && ((!isPre(data) && !stageChecked) || f.renew)) {
                         html.push('<a class="delete-att text-danger ml-1" href="javascript:void(0);" data-id ="' + id + '"file-index="' + i + '" title="删除"><i class="fa fa-remove "></i></a>');
                     }
                     html.push('</td>');
@@ -206,11 +206,11 @@ $(document).ready(() => {
             });
         },
         canUpload(data) {
-            if (data.sid === stageId) {
-                $('#upload-file-panel').show();
-            } else {
-                $('#upload-file-panel').hide();
-            }
+            // if (data.sid === stageId) {
+            //     $('#upload-file-panel').show();
+            // } else {
+            //     $('#upload-file-panel').hide();
+            // }
         }
     };
 

+ 87 - 6
app/public/js/shenpi.js

@@ -116,15 +116,21 @@ function recursiveGetTenderNodeHtml (node, arr, pid, this_code, this_status, aid
     // html.push('<td>', sp_status_list[node.shenpiInfo[shenpi_type]].name, '</td>');
     html.push('<td>');
     if (!node.cid) {
+        if(cur_tenderid === node.id) {
+            html.push(sp_status_list[this_status].name);
+        } else {
+            html.push(sp_status_list[node.shenpiInfo[this_code]].name);
+        }
+    }
+    html.push('</td>');
+    html.push('<td>');
+    if (!node.cid) {
         let auditList = [];
         let tender_status = 1;
         if(cur_tenderid === node.id) {
-            html.push(sp_status_list[this_status].name);
             auditList = aidList;
             tender_status = this_status;
         } else {
-            console.log(node);
-            html.push(sp_status_list[node.shenpiInfo[this_code]].name);
             auditList = node.shenpiauditList[this_code];
             tender_status = node.shenpiInfo[this_code];
         }
@@ -136,8 +142,9 @@ function recursiveGetTenderNodeHtml (node, arr, pid, this_code, this_status, aid
                     nameList.push(user.name);
                 }
             }
-            html.push('<i class="fa fa-question-circle text-primary" data-container="body" data-toggle="tooltip" data-placement="bottom" ' +
-                'data-original-title="'+ (nameList.length > 0 ? nameList.join('-') : '') +'"></i>');
+            // html.push('<i class="fa fa-question-circle text-primary" data-container="body" data-toggle="tooltip" data-placement="bottom" ' +
+            //     'data-original-title="'+ (nameList.length > 0 ? nameList.join('-') : '') +'"></i>');
+            html.push(nameList.length > 0 ? nameList.join('-') : '');
         }
     }
     html.push('</td>');
@@ -161,7 +168,8 @@ function getTenderTreeHtml (this_code, this_status, aidList = []) {
         html.push('<table class="table table-hover table-bordered">');
         html.push('<thead>', '<tr>');
         html.push('<th>名称</th>');
-        html.push('<th width="100">审批流程</th>');
+        html.push('<th width="80">流程模式</th>');
+        html.push('<th>详细流程</th>');
         html.push('<th width="40">选择</th>');
         html.push('</tr>', '</thead>');
         parentId = 0;
@@ -510,6 +518,10 @@ $(document).ready(function () {
         $('#shenpi_status').val(this_status);
         $('#shenpi_auditors').val(aidList.join(','));
         $('#tender-list').html(html);
+        $('#search-audit').val('');
+        $('#search-result').text('0/0');
+        $('#up-search').attr('disabled', true);
+        $('#down-search').attr('disabled', true);
         setTimeout(function () { $("#tender-list [data-toggle='tooltip']").tooltip(); },800);
     });
 
@@ -1025,6 +1037,75 @@ $(document).ready(function () {
             autoFlashHeight();
         }
     });
+
+    let timer2 = null;
+    let oldSearchVal2 = null;
+    $('body').on('input propertychange', '#batch input[name="audit-name"]', function(e) {
+        oldSearchVal2 = e.target.value;
+        timer2 && clearTimeout(timer2);
+        timer2 = setTimeout(() => {
+            const newVal = $(this).val();
+
+            const resultLength = $('#tender-list').find('.result').length;
+            if (resultLength > 0) {
+                let content = $('#tender-list').html();
+                const replaceStr = $('#tender-list').find('.result').eq(0).html();
+                const regExp2 = new RegExp('<span class="result" style="background: yellow;">' + replaceStr + '</span>', 'g');
+                content = content.replace(regExp2, replaceStr);
+                const regExp3 = new RegExp('<span class="result" style="background: orange;">' + replaceStr + '</span>', 'g');
+                content = content.replace(regExp3, replaceStr);
+                $('#tender-list').html(content);
+            }
+            $('#search-result').text('0/0');
+            $('#up-search').attr('disabled', true);
+            $('#down-search').attr('disabled', true);
+            if (newVal && newVal === oldSearchVal2) {
+                const regExp = new RegExp(newVal, 'g');
+                for (let i = 0; i < $('#tender-list tr').length; i++) {
+                    if (_.includes($('#tender-list tr').eq(i).children('td').eq(2).html(), newVal)) {
+                        $('#tender-list tr').eq(i).children('td').eq(2).html($('#tender-list tr').eq(i).children('td').eq(2).html().replace(regExp, '<span class="result" style="background: yellow;">' + newVal + '</span>'))
+                    }
+                }
+                const resultLength2 = $('#tender-list').find('.result').length;
+                if (resultLength2 > 0) {
+                    $('#tender-list').find('.result').eq(0).css('background', 'orange');
+                    $('#search-result').text('1/' + resultLength2);
+                    $('#up-search').attr('disabled', false);
+                    $('#down-search').attr('disabled', false);
+                }
+            }
+            if($('#tender-list').find('.result').length > 0) {
+                const X = $('#tender-list').find('.result').eq(0).offset().top;
+                $('#tender-list').scrollTop(X - $('#tender-list').offset().top + $('#tender-list').scrollTop() - 30);
+            }
+        }, 400);
+    });
+
+    $('#up-search').on('click', function () {
+        const cur = parseInt($('#search-result').text().split('/')[0]);
+        const total = parseInt($('#search-result').text().split('/')[1]);
+        const now = cur - 1 !== 0 ? cur - 1: total;
+        $('#tender-list').find('.result').eq(cur-1).css('background', 'yellow');
+        $('#tender-list').find('.result').eq(now-1).css('background', 'orange');
+        // $('#tender-list tr').eq(searchUser[cur-1]).children('td').eq(2).html($('#tender-list tr').eq(searchUser[cur-1]).children('td').eq(2).html().replace('<span class="result" style="background:orange;">', '<span class="result" style="background:yellow;">'))
+        // $('#tender-list tr').eq(searchUser[now-1]).children('td').eq(2).html($('#tender-list tr').eq(searchUser[now-1]).children('td').eq(2).html().replace('<span class="result" style="background:yellow;">', '<span class="result" style="background:orange;">'))
+        $('#search-result').text(now + '/' + total);
+        const X = $('#tender-list').find('.result').eq(now-1).offset().top;
+        $('#tender-list').scrollTop(X - $('#tender-list').offset().top + $('#tender-list').scrollTop() - 30);
+    });
+
+    $('#down-search').on('click', function () {
+        const cur = parseInt($('#search-result').text().split('/')[0]);
+        const total = parseInt($('#search-result').text().split('/')[1]);
+        const now = cur + 1 > total ? 1: cur + 1;
+        $('#tender-list').find('.result').eq(cur-1).css('background', 'yellow');
+        $('#tender-list').find('.result').eq(now-1).css('background', 'orange');
+        // $('#tender-list tr').eq(searchUser[cur-1]).children('td').eq(2).html($('#tender-list tr').eq(searchUser[cur-1]).children('td').eq(2).html().replace('<span class="result" style="background:orange;">', '<span class="result" style="background:yellow;">'))
+        // $('#tender-list tr').eq(searchUser[now-1]).children('td').eq(2).html($('#tender-list tr').eq(searchUser[now-1]).children('td').eq(2).html().replace('<span class="result" style="background:yellow;">', '<span class="result" style="background:orange;">'))
+        $('#search-result').text(now + '/' + total);
+        const X = $('#tender-list').find('.result').eq(now-1).offset().top;
+        $('#tender-list').scrollTop(X - $('#tender-list').offset().top + $('#tender-list').scrollTop() -30);
+    });
 });
 
 function setRightData(datas, coolist) {

+ 18 - 1
app/public/js/spreadjs_rela/spreadjs_zh.js

@@ -506,6 +506,10 @@ const SpreadJsObj = {
     _loadRowData: function (sheet, data, row) {
         // 单元格重新写入数据
         if (!data) { return }
+        if (sheet.zh_dataType === SpreadJsObj.DataType.Tree && !data.visible && data.waitingLoading === undefined) {
+            data.waitingLoading = true;
+            return;
+        }
         sheet.zh_setting.cols.forEach(function (col, j) {
             const cell = sheet.getCell(row, j);
             if (col.getValue && Object.prototype.toString.apply(col.getValue) === "[object Function]") {
@@ -550,6 +554,7 @@ const SpreadJsObj = {
             cell.backColor(SpreadJsObj._getBackColor(sheet, data, row, col));
 
             cell.setBorder(sheet.borderLine, {all: true});
+            data.waitingLoading = false;
         });
     },
     _addActivePaintEvents: function (sheet, cellType) {
@@ -1067,8 +1072,14 @@ const SpreadJsObj = {
         for (const iRow in sortData) {
             const node = sortData[iRow];
             if (node.visible !== undefined && node.visible !== null) {
+                if (node.visible && node.waitingLoading) {
+                    SpreadJsObj._loadRowData(sheet, node, parseInt(iRow));
+                }
                 sheet.setRowVisible(iRow, node.visible);
             } else {
+                if (node.waitingLoading) {
+                    this._loadRowData(sheet, node, parseInt(iRow));
+                }
                 sheet.setRowVisible(iRow, true);
             }
         }
@@ -1392,7 +1403,13 @@ const SpreadJsObj = {
                         SpreadJsObj.massOperationSheet(hitinfo.sheet, function () {
                             const posterity = tree.getPosterity(node);
                             for (const child of posterity) {
-                                hitinfo.sheet.setRowVisible(tree.nodes.indexOf(child), child.visible, hitinfo.sheetArea);
+                                const cIndex = tree.getNodeIndex(child);
+                                if (child.visible && child.waitingLoading) {
+                                    SpreadJsObj._loadRowData(hitinfo.sheet, child, cIndex);
+                                }
+                                //if (child.visible !== hitinfo.sheet.getRowVisible(cIndex, hitinfo.sheetArea)) {
+                                    hitinfo.sheet.setRowVisible(cIndex, child.visible, hitinfo.sheetArea);
+                                //}
                             }
                         });
                         hitinfo.sheet.repaint();

+ 163 - 98
app/public/js/stage.js

@@ -172,6 +172,13 @@ function getDaglText(data) {
     return def ? def.name : '';
 }
 
+function getHintMsg () {
+    return {
+        invalidExpr: {type: 'warning', msg: '粘贴了非法表达式,已过滤'},
+        disableChange: {type: 'error', msg: '不可修改此数据'},
+    }
+}
+
 
 $(document).ready(() => {
     const exportExcelSetting = {
@@ -238,6 +245,7 @@ $(document).ready(() => {
     // 界面布局
     autoFlashHeight();
     // 初始化 台账树结构 数据结构
+    removeLocalCache('bills-fold');
     const stageTreeSetting = {
         id: 'ledger_id',
         pid: 'ledger_pid',
@@ -246,8 +254,11 @@ $(document).ready(() => {
         rootId: -1,
         keys: ['id', 'tender_id', 'ledger_id'],
         stageId: 'id',
-        markFoldKey: 'bills-fold',
-        markFoldSubKey: window.location.pathname.split('/')[2],
+        autoExpand: 3,
+        // markFoldKey: 'bills-fold',
+        // markFoldSubKey: window.location.pathname.split('/')[2],
+        markExpandKey: 'stage-bills-expand',
+        markExpandSubKey: window.location.pathname.split('/')[2],
     };
     // 台账树结构计算相关设置
     stageTreeSetting.updateFields = ['contract_qty', 'contract_tp', 'qc_qty', 'qc_tp', 'postil', 'used', 'contract_expr'];
@@ -301,6 +312,7 @@ $(document).ready(() => {
     const reloadCooperationHtml = function () {
         const html = [];
         for (const p of stageTree.pwd) {
+            if (!p.node) continue;
             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>');
@@ -364,7 +376,9 @@ $(document).ready(() => {
             // 切换变更令,加载右侧明细数据
             this.spread.bind(spreadNS.Events.SelectionChanged, function (e, info) {
                 const change = SpreadJsObj.getSelectObject(info.sheet);
-                self._loadChangeDetail(change);
+                if(change) {
+                    self._loadChangeDetail(change);
+                }
             });
             // 填写本期计量
             this.spread.bind(spreadNS.Events.EditEnded, function (e, info) {
@@ -464,9 +478,9 @@ $(document).ready(() => {
                     stageTreeSpreadObj.refreshTreeNodes(slSpread.getActiveSheet(), nodes);
                     stagePosSpreadObj.loadCurPosData();
                     if (detail) {
-                        detail.loadStageChangeUpdateData(result);
+                        detail.loadStageChangeUpdateData(result, nodes);
                     } else {
-                        stageIm.loadUpdateChangeData(result)
+                        stageIm.loadUpdateChangeData(result, nodes)
                     }
                     self.obj.modal('hide');
                 });
@@ -499,7 +513,7 @@ $(document).ready(() => {
                 const textareas = $('textarea', this.obj);
                 for (const ta of textareas) {
                     const field = $(ta).attr('name');
-                    const text = (field && change[field]) ? change[field] : '';
+                    const text = (field && change[field]) ? change[field].replace(/<br><br>/g, '\r\n') : '';
                     ta.textContent = text;
                 }
                 const html = [];
@@ -913,9 +927,9 @@ $(document).ready(() => {
                     const nodes = stageTree.loadPostStageData(data);
                     stageTreeSpreadObj.refreshTreeNodes(slSpread.getActiveSheet(), nodes);
                     if (detail) {
-                        detail.loadStageLedgerUpdateData(data);
+                        detail.loadStageLedgerUpdateData(data, nodes);
                     } else {
-                        stageIm.loadUpdateLedgerData(data);
+                        stageIm.loadUpdateLedgerData(data, nodes);
                     }
                 }, function () {
                     SpreadJsObj.reLoadRowData(info.sheet, info.row);
@@ -960,9 +974,18 @@ $(document).ready(() => {
                             if (sheet.zh_setting.dgnUpFields.indexOf(colSetting.field) !== -1) {
                                 if (node.b_code && node.b_code !== '') continue;
                             } else if (colSetting.field !== 'postil') {
-                                if (node.children && node.children.length > 0) { continue; }
+                                if (colSetting.field === 'contract_qty') {
+                                    if (node.is_tp) continue;
+                                } else if (colSetting.field === 'contract_tp') {
+                                    if (!node.is_tp) continue;
+                                }
+                                if (node.children && node.children.length > 0) {
+                                    continue;
+                                }
                                 const nodePos = stagePos.getLedgerPos(node.id);
-                                if (nodePos && nodePos.length > 0) { continue; }
+                                if (nodePos && nodePos.length > 0) {
+                                    continue;
+                                }
                             }
 
                             if (sheet.zh_setting.dgnUpFields.indexOf(colSetting.field) !== -1) {
@@ -996,9 +1019,9 @@ $(document).ready(() => {
                         const nodes = stageTree.loadPostStageData(result);
                         stageTreeSpreadObj.refreshTreeNodes(slSpread.getActiveSheet(), nodes);
                         if (detail) {
-                            detail.loadStageLedgerUpdateData(result);
+                            detail.loadStageLedgerUpdateData(result, nodes);
                         } else {
-                            stageIm.loadUpdateLedgerData(result);
+                            stageIm.loadUpdateLedgerData(result, nodes);
                         }
                         stageTreeSpreadObj.loadExprToInput(sheet);
                     });
@@ -1022,10 +1045,10 @@ $(document).ready(() => {
             }
         },
         clipboardPasted(e, info) {
+            const pasteHint = getHintMsg();
             if (info.sheet.zh_setting && info.sheet.zh_tree) {
                 const sheet = info.sheet, setting = info.sheet.zh_setting;
                 const filterNodes = [], datas = [], dgnDatas = [], mainDatas = [];
-                let bHint = false;
 
                 for (let iRow = 0; iRow < info.cellRange.rowCount; iRow++) {
                     const curRow = iRow + info.cellRange.row;
@@ -1061,10 +1084,7 @@ $(document).ready(() => {
                                     dgnData[col.field] = math.evaluate(transExpr(text));
                                     filterDgn = false;
                                 } catch(err) {
-                                    if (!bHint) {
-                                        toastr.warning('粘贴了非法表达式,已过滤');
-                                        bHint = true;
-                                    }
+                                    toastMessageUniq(pasteHint.invalidExpr);
                                 }
                             }
                         } else if (col.field === 'memo') {
@@ -1072,24 +1092,26 @@ $(document).ready(() => {
                             filterMain = false;
                         } else {
                             if (col.type === 'Number') {
-                                const num = _.toNumber(text);
-                                if (_.isFinite(num)) {
+                                const exprInfo = getExprInfo(col.field);
+                                if (text) {
+                                    const num = _.toNumber(text);
                                     data[col.field] = num;
-                                    filter = false;
-                                } else {
-                                    try {
-                                        data[col.field] = math.evaluate(transExpr(text));
-                                        const exprInfo = getExprInfo(col.field);
-                                        if (exprInfo) {
-                                            data[exprInfo.expr] = text;
-                                        }
+                                    if (exprInfo) data[exprInfo.expr] = '';
+                                    if (_.isFinite(num)) {
                                         filter = false;
-                                    } catch(err) {
-                                        if (!bHint) {
-                                            toastr.warning('粘贴了非法表达式,已过滤');
-                                            bHint = true;
+                                    } else {
+                                        try {
+                                            data[col.field] = math.evaluate(transExpr(text));
+                                            if (exprInfo) data[exprInfo.expr] = text;
+                                            filter = false;
+                                        } catch(err) {
+                                            toastMessageUniq(pasteHint.invalidExpr);
                                         }
                                     }
+                                } else {
+                                    data[col.field] = null;
+                                    if (exprInfo) data[exprInfo.expr] = '';
+                                    filter = false;
                                 }
                             } else {
                                 data[col.field] = text;
@@ -1113,9 +1135,9 @@ $(document).ready(() => {
                         const nodes = stageTree.loadPostStageData(data);
                         stageTreeSpreadObj.refreshTreeNodes(slSpread.getActiveSheet(), nodes.concat(filterNodes));
                         if (detail) {
-                            detail.loadStageLedgerUpdateData(data);
+                            detail.loadStageLedgerUpdateData(data, nodes);
                         } else {
-                            stageIm.loadUpdateLedgerData(data);
+                            stageIm.loadUpdateLedgerData(data, nodes);
                         }
                         stageTreeSpreadObj.loadExprToInput(sheet);
                     }, function () {
@@ -1165,9 +1187,9 @@ $(document).ready(() => {
                 stageTreeSpreadObj.refreshTreeNodes(slSpread.getActiveSheet(), nodes);
                 stagePosSpreadObj.loadCurPosData();
                 if (detail) {
-                    detail.loadStagePosUpdateData(result);
+                    detail.loadStagePosUpdateData(result, nodes);
                 } else {
-                    stageIm.loadUpdatePosData(result);
+                    stageIm.loadUpdatePosData(result, nodes);
                 }
                 toastr.success('已计量' + data.updateData.length + '条');
             }, function () {
@@ -1221,9 +1243,9 @@ $(document).ready(() => {
                         const nodes = stageTree.loadPostStageData(result);
                         stageTreeSpreadObj.refreshTreeNodes(slSpread.getActiveSheet(), nodes);
                         if (detail) {
-                            detail.loadStageLedgerUpdateData(result);
+                            detail.loadStageLedgerUpdateData(result, nodes);
                         } else {
-                            stageIm.loadUpdateLedgerData(result);
+                            stageIm.loadUpdateLedgerData(result, nodes);
                         }
                     });
                 };
@@ -1287,9 +1309,9 @@ $(document).ready(() => {
                 stageTreeSpreadObj.refreshTreeNodes(slSpread.getActiveSheet(), nodes);
                 stagePosSpreadObj.loadCurPosData();
                 if (detail) {
-                    detail.loadStagePosUpdateData(result);
+                    detail.loadStagePosUpdateData(result, nodes);
                 } else {
-                    stageIm.loadUpdatePosData(result);
+                    stageIm.loadUpdatePosData(result, nodes);
                 }
                 $('#calc-by-ratio').modal('hide');
             }, function () {
@@ -1390,6 +1412,11 @@ $(document).ready(() => {
             postData(window.location.pathname + '/update', {bills: updateData}, function (result) {
                 const nodes = stageTree.loadPostStageData(result);
                 stageTreeSpreadObj.refreshTreeNodes(slSpread.getActiveSheet(), nodes);
+                if (detail) {
+                    detail.loadStageLedgerUpdateData(result, nodes);
+                } else {
+                    stageIm.loadUpdateLedgerData(result, nodes);
+                }
             });
         });
     }
@@ -1446,6 +1473,21 @@ $(document).ready(() => {
                     return !node;
                 }
             },
+            showSpr: '----',
+            showLast: {
+                name: '显示至最底层',
+                callback: function (key, opt, menu, e) {
+                    const node = SpreadJsObj.getSelectObject(slSpread.getActiveSheet());
+                    setTimeout(() => {
+                        showWaitingView();
+                        stageTree.expandByCustom(x => {
+                            return x.expanded || (x.id === node.id) || (x.full_path.indexOf(node.ledger_id + '-') >= 0);
+                        });
+                        SpreadJsObj.refreshTreeRowVisible(slSpread.getActiveSheet());
+                        closeWaitingView();
+                    }, 100);
+                },
+            },
             exportSpr: '----',
             exportSelectNodeXlsx: {
                 name: '导出选中节点至Excel',
@@ -1490,8 +1532,8 @@ $(document).ready(() => {
         loadCurPosData: function () {
             const sheet = slSpread.getActiveSheet();
             const node = SpreadJsObj.getSelectObject(sheet);
-            spSpread.getActiveSheet().zh_setting.readOnly = node.lock;
             if (node) {
+                spSpread.getActiveSheet().zh_setting.readOnly = node.lock;
                 const posData = stagePos.ledgerPos[itemsPre + node.id] || [];
                 SpreadJsObj.loadSheetData(spSpread.getActiveSheet(), 'data', posData);
                 getNodeList(node.id);
@@ -1601,9 +1643,9 @@ $(document).ready(() => {
                     stageTreeSpreadObj.refreshTreeNodes(slSpread.getActiveSheet(), refreshData);
                     stagePosSpreadObj.loadCurPosData();
                     if (detail) {
-                        detail.loadStagePosUpdateData(result);
+                        detail.loadStagePosUpdateData(result, refreshData);
                     } else {
-                        stageIm.loadUpdatePosData(result);
+                        stageIm.loadUpdatePosData(result, refreshData);
                     }
                 }, function () {
                     stagePosSpreadObj.loadCurPosData();
@@ -1684,22 +1726,29 @@ $(document).ready(() => {
 
                             const newValue = trimInvalidChar(info.sheet.getText(curRow, curCol));
                             if (colSetting.type === 'Number') {
-                                const num = _.toNumber(newValue);
-                                if (num) {
-                                    newData[colSetting.field] = num;
-                                } else {
-                                    try {
-                                        newData[colSetting.field] = math.evaluate(transExpr(newValue));
-                                        const exprInfo = getExprInfo(colSetting.field);
+                                const exprInfo = getExprInfo(colSetting.field);
+                                if (newValue) {
+                                    const num = _.toNumber(newValue);
+                                    if (num) {
+                                        newData[colSetting.field] = num;
                                         if (exprInfo) {
-                                            newData[exprInfo.expr] = newValue;
+                                            newData[exprInfo.expr] = '';
+                                        }
+                                    } else {
+                                        try {
+                                            newData[colSetting.field] = math.evaluate(transExpr(newValue));
+                                            if (exprInfo) {
+                                                newData[exprInfo.expr] = newValue;
+                                            }
+                                        } catch(err) {
+                                            toastr.error('输入的表达式非法');
+                                            stagePosSpreadObj.loadCurPosData();
+                                            return;
                                         }
-                                    } catch(err) {
-                                        toastr.error('输入的表达式非法');
-                                        stagePosSpreadObj.loadCurPosData();
-                                        return;
-
                                     }
+                                } else {
+                                    newData[colSetting.field] = null;
+                                    if (exprInfo) newData[exprInfo.expr] = '';
                                 }
                             } else {
                                 newData[colSetting.field] = newValue;
@@ -1719,16 +1768,22 @@ $(document).ready(() => {
                                 const colSetting = info.sheet.zh_setting.cols[curCol];
                                 const newValue = trimInvalidChar(info.sheet.getText(curRow, curCol));
                                 if (colSetting.type === 'Number') {
+                                    const exprInfo = getExprInfo(colSetting.field);
+                                    if (!newValue) {
+                                        newData[colSetting.field] = newValue;
+                                        if (exprInfo) newData[exprInfo.expr] = '';
+                                        continue;
+                                    }
+
                                     const num = _.toNumber(newValue);
                                     if (num) {
                                         newData[colSetting.field] = num;
+                                        if (exprInfo) newData[exprInfo.expr] = '';
                                     } else {
                                         try {
                                             newData[colSetting.field] = math.evaluate(transExpr(newValue));
                                             const exprInfo = getExprInfo(colSetting.field);
-                                            if (exprInfo) {
-                                                newData[exprInfo.expr] = newValue;
-                                            }
+                                            if (exprInfo) newData[exprInfo.expr] = newValue;
                                         } catch(err) {
                                             toastr.error('输入的表达式非法');
                                             stagePosSpreadObj.loadCurPosData();
@@ -1752,9 +1807,9 @@ $(document).ready(() => {
                     stageTreeSpreadObj.refreshTreeNodes(slSpread.getActiveSheet(), nodes);
                     stagePosSpreadObj.loadCurPosData();
                     if (detail) {
-                        detail.loadStagePosUpdateData(result);
+                        detail.loadStagePosUpdateData(result, nodes);
                     } else {
-                        stageIm.loadUpdatePosData(result);
+                        stageIm.loadUpdatePosData(result, nodes);
                     }
                 }, function () {
                     stagePosSpreadObj.loadCurPosData();
@@ -1808,9 +1863,9 @@ $(document).ready(() => {
                         const nodes = stageTree.loadPostStageData(result.ledger);
                         stageTreeSpreadObj.refreshTreeNodes(slSpread.getActiveSheet(), nodes);
                         if (detail) {
-                            detail.loadStagePosUpdateData(result);
+                            detail.loadStagePosUpdateData(result, nodes);
                         } else {
-                            stageIm.loadUpdatePosData(result);
+                            stageIm.loadUpdatePosData(result, nodes);
                         }
                         // todo 只加载改变项
                         stagePosSpreadObj.loadCurPosData();
@@ -1836,9 +1891,9 @@ $(document).ready(() => {
                         stageTreeSpreadObj.refreshTreeNodes(slSpread.getActiveSheet(), refreshData);
                         stagePosSpreadObj.loadCurPosData();
                         if (detail) {
-                            detail.loadStagePosUpdateData(result);
+                            detail.loadStagePosUpdateData(result, refreshData);
                         } else {
-                            stageIm.loadUpdatePosData(result);
+                            stageIm.loadUpdatePosData(result, refreshData);
                         }
                     });
                 }
@@ -1883,6 +1938,7 @@ $(document).ready(() => {
         stageTree.loadDatas(result.ledgerData);
         // stageTree.loadCurStageData(curStageData);
         // stageTree.loadPreStageData(preStageData);
+        checkShowLast(result.ledgerData.length);
         treeCalc.calculateAll(stageTree);
         // 加载解锁相关
         if (result.cooperation) {
@@ -1958,6 +2014,11 @@ $(document).ready(() => {
                 }
                 const refreshData = stageTree.loadPostStageData(result.ledger);
                 stageTreeSpreadObj.refreshTreeNodes(slSpread.getActiveSheet(), refreshData);
+                if (detail) {
+                    detail.loadStagePosUpdateData(result, refreshData);
+                } else {
+                    stageIm.loadUpdatePosData(result, refreshData);
+                }
             });
         });
     }
@@ -3177,18 +3238,18 @@ $(document).ready(() => {
             this.reLoadDetailData();
             this.loadLocateInfo();
         }
-        loadStageLedgerUpdateData(data) {
-            const imData = stageIm.loadUpdateLedgerData(data);
+        loadStageLedgerUpdateData(data, nodes) {
+            const imData = stageIm.loadUpdateLedgerData(data, nodes);
             SpreadJsObj.loadSheetData(this.sheet, SpreadJsObj.DataType.Data, imData);
             this.reLoadDetailData();
         }
-        loadStagePosUpdateData(data) {
-            const imData = stageIm.loadUpdatePosData(data);
+        loadStagePosUpdateData(data, nodes) {
+            const imData = stageIm.loadUpdatePosData(data, nodes);
             SpreadJsObj.loadSheetData(this.sheet, SpreadJsObj.DataType.Data, imData);
             this.reLoadDetailData();
         }
-        loadStageChangeUpdateData(data) {
-            const imData = stageIm.loadUpdateChangeData(data);
+        loadStageChangeUpdateData(data, nodes) {
+            const imData = stageIm.loadUpdateChangeData(data, nodes);
             SpreadJsObj.loadSheetData(this.sheet, SpreadJsObj.DataType.Data, imData);
             this.reLoadDetailData();
         }
@@ -3879,35 +3940,39 @@ $(document).ready(() => {
             if (!sheet.zh_tree) return;
             const tag = $(this).attr('tag');
             const tree = sheet.zh_tree;
-            switch (tag) {
-                case "1":
-                case "2":
-                case "3":
-                case "4":
-                case "5":
-                    tree.expandByLevel(parseInt(tag));
-                    SpreadJsObj.refreshTreeRowVisible(sheet);
-                    break;
-                case "last":
-                    tree.expandByCustom(() => { return true; });
-                    SpreadJsObj.refreshTreeRowVisible(sheet);
-                    break;
-                case "leafXmj":
-                    tree.expandToLeafXmj();
-                    SpreadJsObj.refreshTreeRowVisible(sheet);
-                    break;
-                case "curMeasure":
-                    tree.expandByCustom(function (node) {
-                        for (const field of ['contract_tp', 'qc_tp', 'gather_tp']) {
-                            if (node[field]) {
-                                return true;
+            setTimeout(() => {
+                showWaitingView();
+                switch (tag) {
+                    case "1":
+                    case "2":
+                    case "3":
+                    case "4":
+                    case "5":
+                        tree.expandByLevel(parseInt(tag));
+                        SpreadJsObj.refreshTreeRowVisible(sheet);
+                        break;
+                    case "last":
+                        tree.expandByCustom(() => { return true; });
+                        SpreadJsObj.refreshTreeRowVisible(sheet);
+                        break;
+                    case "leafXmj":
+                        tree.expandToLeafXmj();
+                        SpreadJsObj.refreshTreeRowVisible(sheet);
+                        break;
+                    case "curMeasure":
+                        tree.expandByCustom(function (node) {
+                            for (const field of ['contract_tp', 'qc_tp', 'gather_tp']) {
+                                if (node[field]) {
+                                    return true;
+                                }
                             }
-                        }
-                        return false;
-                    });
-                    SpreadJsObj.refreshTreeRowVisible(sheet);
-                    break;
-            }
+                            return false;
+                        });
+                        SpreadJsObj.refreshTreeRowVisible(sheet);
+                        break;
+                }
+                closeWaitingView();
+            }, 100);
         });
     })('a[name=showLevel]', slSpread.getActiveSheet());
 

+ 52 - 22
app/public/js/stage_compare.js

@@ -134,6 +134,7 @@ $(document).ready(function () {
         masterId: 'id',
         minorId: 'lid',
         calcFields: [],
+        autoExpand: 3,
     };
     const scTree = createNewPathTree('master', scTreeSetting);
     // 加载 部位 数据
@@ -145,6 +146,30 @@ $(document).ready(function () {
     };
     const scPos = new MasterPosData(scPosSetting);
 
+    $.contextMenu({
+        selector: '#ledger-spread',
+        build: function ($trigger, e) {
+            const target = SpreadJsObj.safeRightClickSelection($trigger, e, ledgerSpread);
+            return target.hitTestType === spreadNS.SheetArea.viewport || target.hitTestType === spreadNS.SheetArea.rowHeader;
+        },
+        items: {
+            showLast: {
+                name: '显示至最底层',
+                callback: function (key, opt, menu, e) {
+                    const node = SpreadJsObj.getSelectObject(ledgerSpread.getActiveSheet());
+                    setTimeout(() => {
+                        showWaitingView();
+                        scTree.expandByCustom(x => {
+                            return x.expanded || (x.id === node.id) || (x.full_path.indexOf(node.ledger_id + '-') >= 0);
+                        });
+                        SpreadJsObj.refreshTreeRowVisible(ledgerSpread.getActiveSheet());
+                        closeWaitingView();
+                    }, 100);
+                },
+            },
+        }
+    });
+
     postData(window.location.pathname + '/load', {main: true, roles: scRoles}, function (result) {
         for (const r of scRoles) {
             if (r && r != 0) {
@@ -153,6 +178,7 @@ $(document).ready(function () {
             }
         }
         scTree.loadDatas(result.main.ledger);
+        checkShowLast(result.main.ledger.length);
         scPos.loadDatas(result.main.pos);
         for (const aData of result.roles) {
             calculateStageLedgerData(aData.bills);
@@ -252,28 +278,32 @@ $(document).ready(function () {
             if (!sheet.zh_tree) return;
             const tag = $(this).attr('tag');
             const tree = sheet.zh_tree;
-            switch (tag) {
-                case "1":
-                case "2":
-                case "3":
-                case "4":
-                case "5":
-                    tree.expandByLevel(parseInt(tag));
-                    SpreadJsObj.refreshTreeRowVisible(sheet);
-                    break;
-                case "last":
-                    tree.expandByCustom(() => { return true; });
-                    SpreadJsObj.refreshTreeRowVisible(sheet);
-                    break;
-                case "leafXmj":
-                    tree.expandToLeafXmj();
-                    SpreadJsObj.refreshTreeRowVisible(sheet);
-                    break;
-                case "curMeasure":
-                    tree.expandByCalcFields();
-                    SpreadJsObj.refreshTreeRowVisible(sheet);
-                    break;
-            }
+            setTimeout(() => {
+                showWaitingView();
+                switch (tag) {
+                    case "1":
+                    case "2":
+                    case "3":
+                    case "4":
+                    case "5":
+                        tree.expandByLevel(parseInt(tag));
+                        SpreadJsObj.refreshTreeRowVisible(sheet);
+                        break;
+                    case "last":
+                        tree.expandByCustom(() => { return true; });
+                        SpreadJsObj.refreshTreeRowVisible(sheet);
+                        break;
+                    case "leafXmj":
+                        tree.expandToLeafXmj();
+                        SpreadJsObj.refreshTreeRowVisible(sheet);
+                        break;
+                    case "curMeasure":
+                        tree.expandByCalcFields();
+                        SpreadJsObj.refreshTreeRowVisible(sheet);
+                        break;
+                }
+                closeWaitingView();
+            }, 100);
         });
     })('a[name=showLevel]', ledgerSpread.getActiveSheet());
 

+ 79 - 21
app/public/js/stage_im.js

@@ -13,7 +13,7 @@ const stageIm = (function () {
     const resetFields = ['peg', 'bw', 'xm', 'drawing_code', 'calc_memo', 'position', 'jldy'];
     const splitChar = '-';
     const mergeChar = ';';
-    let stage, imType, decimal, details, changes, ImData, pre;
+    let stage, imType, decimal, details, changes, ImData, pre, orgImData;
     const gsTreeSetting = {
         id: 'ledger_id',
         pid: 'ledger_pid',
@@ -837,25 +837,20 @@ const stageIm = (function () {
         }
     }
 
-    /**
-     * 生成 中间计量表 全部数据
-     * @returns {Array}
-     */
-    function buildImData () {
-        // 初始化
-        ImData = [];
-        pre = (stage.im_pre && stage.im_pre !== '') ? stage.im_pre + splitChar : '';
-        if (stage.im_gather) {
-            initCheck();
-        }
-        // 生成数据
-        recursiveBuildImData(gsTree.children);
+    function _sortImData() {
         if (stage.im_type !== imType.tz.value) {
             ImData.sort(function (x, y) {
                 const iCode = compareCode(x.code, y.code);
                 return iCode === 0 ? x.lIndex - y.lIndex : iCode;
             });
+        } else {
+            ImData.sort(function (x, y) {
+                return x.lIndex - y.lIndex;
+            });
         }
+    }
+
+    function _buildInfoByCache() {
         for (const [i, im] of ImData.entries()) {
             if (im.source.length > 1) {
                 im.source.sort(function (x, y) {
@@ -864,9 +859,72 @@ const stageIm = (function () {
             }
             getCalcMemo(im);
             getChangeInfo(im);
-            im.im_code = pre + getNumberFormat(stage.order, 2) + splitChar + getNumberFormat(i + 1, 3);
             checkCustomDetail(im);
         }
+    }
+
+    function _buildImCode() {
+        pre = (stage.im_pre && stage.im_pre !== '') ? stage.im_pre + splitChar : '';
+        for (const [i, im] of ImData.entries()) {
+            im.im_code = pre + getNumberFormat(stage.order, 2) + splitChar + getNumberFormat(i + 1, 3);
+        }
+    }
+
+    /**
+     * 生成 中间计量表 全部数据
+     * @returns {Array}
+     */
+    function buildImData () {
+        // 初始化
+        ImData = [];
+        if (stage.im_gather) initCheck();
+        // 生成数据
+        recursiveBuildImData(gsTree.children);
+        _buildInfoByCache();
+        _sortImData();
+        _buildImCode();
+        return ImData;
+    }
+
+    /**
+     * 递归 生成中间计量表
+     * @param {Array} nodes
+     */
+    function recursiveReBuildImData (nodes, refreshNodeIds) {
+        if (!nodes || nodes.length === 0) { return; }
+        for (const node of nodes) {
+            if (refreshNodeIds.indexOf(node.id) < 0) continue;
+            if (gsTree.isLeafXmj(node) || ((stage.im_type !== imType.bw.value && stage.im_type !== imType.bb.value) && stage.im_gather && node.check)) {
+                if (stage.im_type === imType.tz.value) {
+                    generateTzImData(node);
+                } else if (stage.im_type === imType.zl.value) {
+                    generateZlImData(node);
+                } else if (stage.im_type === imType.bw.value) {
+                    generateBwImData(node);
+                } else if (stage.im_type === imType.bb.value) {
+                    generateBwBillsImData(node);
+                }
+            } else {
+                recursiveReBuildImData(node.children, refreshNodeIds);
+            }
+        }
+    }
+
+    function refreshImData(refreshNodes) {
+        // 初始化
+        const refreshIds = refreshNodes.map(x => {return x.id});
+        orgImData = ImData ? ImData.filter(x => {
+            return refreshIds.indexOf(x.lid) < 0
+        }) : [];
+        ImData = [];
+        if (stage.im_gather) initCheck();
+        // 生成数据
+        recursiveReBuildImData(gsTree.children, refreshIds);
+        _buildInfoByCache();
+        ImData = ImData.concat(orgImData);
+        _sortImData();
+        _buildImCode();
+        orgImData = [];
         return ImData;
     }
 
@@ -897,7 +955,7 @@ const stageIm = (function () {
         }
     }
 
-    function loadUpdateLedgerData(data) {
+    function loadUpdateLedgerData(data, refreshNodes) {
         gsTree.loadPostStageData(data);
         if (data.change) {
             _.remove(changes, data.change.target);
@@ -905,9 +963,9 @@ const stageIm = (function () {
                 changes.push(c);
             }
         }
-        return buildImData();
+        return refreshImData(refreshNodes);
     }
-    function loadUpdatePosData(data) {
+    function loadUpdatePosData(data, refreshNodes) {
         if (data.pos) {
             if (data.pos.pos && data.pos.pos.length > 0 && typeof data.pos.pos[0] === 'string') {
                 gsPos.removeDatas(data.pos.pos);
@@ -919,9 +977,9 @@ const stageIm = (function () {
             }
         }
         gsTree.loadPostStageData(data.ledger);
-        return buildImData();
+        return refreshImData(refreshNodes);
     }
-    function loadUpdateChangeData(data) {
+    function loadUpdateChangeData(data, refreshNodes) {
         if (data.pos) {
             gsPos.loadCurStageData(data.pos.curStageData);
         }
@@ -932,7 +990,7 @@ const stageIm = (function () {
                 changes.push(c);
             }
         }
-        return buildImData();
+        return refreshImData(refreshNodes);
     }
 
     function getParentCheckNode(node) {

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

@@ -713,4 +713,4 @@ $(document).ready(function() {
             $('#edit-6').parent().show();
         });
     });
-});
+});

+ 13 - 13
app/public/js/xlsx-populate/xlsx-populate.js

@@ -7356,7 +7356,7 @@ var Workbook = function () {
 
         /**
          * Add a new sheet to the workbook.
-         * 
+         *
          * **WARN:** this function has limits:  if you clone a sheet with some images or other things link outside the Sheet object, these things in the cloned sheet will be locked when you open in MS Excel app.
          * @param {Sheet} from - The sheet to be cloned.
          * @param {string} name - The name of the new sheet. Must be unique, less than 31 characters, and may not contain the following characters: \ / * [ ] : ?
@@ -19910,7 +19910,7 @@ $export.P = 8;   // proto
 $export.B = 16;  // bind
 $export.W = 32;  // wrap
 $export.U = 64;  // safe
-$export.R = 128; // real proto method for `library` 
+$export.R = 128; // real proto method for `library`
 module.exports = $export;
 },{"./_core":86,"./_ctx":87,"./_global":92,"./_hide":93}],91:[function(require,module,exports){
 module.exports = function(exec){
@@ -31243,8 +31243,8 @@ exports.prepareContent = function(name, inputData, isBinary, isOptimizedBinarySt
 
     // if inputData is already a promise, this flatten it.
     var promise = external.Promise.resolve(inputData).then(function(data) {
-        
-        
+
+
         var isBlob = support.blob && (data instanceof Blob || ['[object File]', '[object Blob]'].indexOf(Object.prototype.toString.call(data)) !== -1);
 
         if (isBlob && typeof FileReader !== "undefined") {
@@ -33773,7 +33773,7 @@ function race(iterable) {
 
     /*------------------------------------------------------------------------*/
 
-    /**
+    t/**
      * Creates a `lodash` object which wraps `value` to enable implicit method
      * chain sequences. Methods that operate on and return arrays, collections,
      * and functions can be chained together. Methods that retrieve a single value
@@ -62607,13 +62607,13 @@ Script.prototype.runInContext = function (context) {
     if (!(context instanceof Context)) {
         throw new TypeError("needs a 'context' argument.");
     }
-    
+
     var iframe = document.createElement('iframe');
     if (!iframe.style) iframe.style = {};
     iframe.style.display = 'none';
-    
+
     document.body.appendChild(iframe);
-    
+
     var win = iframe.contentWindow;
     var wEval = win.eval, wExecScript = win.execScript;
 
@@ -62622,7 +62622,7 @@ Script.prototype.runInContext = function (context) {
         wExecScript.call(win, 'null');
         wEval = win.eval;
     }
-    
+
     forEach(Object_keys(context), function (key) {
         win[key] = context[key];
     });
@@ -62631,11 +62631,11 @@ Script.prototype.runInContext = function (context) {
             win[key] = context[key];
         }
     });
-    
+
     var winKeys = Object_keys(win);
 
     var res = wEval.call(win, this.code);
-    
+
     forEach(Object_keys(win), function (key) {
         // Avoid copying circular objects like `top` and `window` by only
         // updating existing context properties or new properties in the `win`
@@ -62650,9 +62650,9 @@ Script.prototype.runInContext = function (context) {
             defineProp(context, key, win[key]);
         }
     });
-    
+
     document.body.removeChild(iframe);
-    
+
     return res;
 };
 

+ 10 - 3
app/public/report/js/rpt_custom.js

@@ -432,9 +432,16 @@ const rptCustomObj = (function () {
                 return;
             }
         }
-        if (data[sGatherSelect].tenders.length <= specCol.length) {
-            hintObj.html('请至少选择1个普通汇总项目').show();
-            return;
+        if (gsObj.setting.onlySpec) {
+            if (data[sGatherSelect].tenders.length > specCol.length) {
+                hintObj.html('请勿选择普通汇总项目').show();
+                return;
+            }
+        } else {
+            if (data[sGatherSelect].tenders.length <= specCol.length) {
+                hintObj.html('请至少选择1个普通汇总项目').show();
+                return;
+            }
         }
         if (gsObj.setting.type === 'month') {
             data[sGatherSelect].month = $('#gather-month').val();

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

@@ -687,6 +687,7 @@ let rptControlObj = {
             const signatureRelArr = [];
             if (refRptTplIds.length > 0) {
                 let params = rptControlObj.creatCommonExportParam(refRptTplIds);
+                params.getPicFlag = true; //专门针对草图项,只有此项为true,才需要把草图信息带过来,预览及打印动态加载草图 // 纠结:但这样还是解决不了效率问题,得另外想交互方式
                 await rptCustomObj.getCustomSelect(params);
                 delete params.orientation; // 打印时有勾选的话,不需要提供方向
                 $.bootstrapLoading.start();

+ 59 - 1
app/public/report/js/rpt_preview_common.js

@@ -4,6 +4,59 @@
 
 let G_OFFSET_X = 0, G_OFFSET_Y = 0;
 function printPageLoading() {
+    let params = JSON.parse(sessionStorage.report_params);
+    let _current_stage_status = parseInt(sessionStorage.current_stage_status);
+    let closeWaterMark = parseInt(sessionStorage.closeWaterMark);
+    let scaleFactor = 1;
+    CommonAjax.postXsrfEx("/tender/report_api/getMultiReports", params, 60000, true, getCookie('csrfToken'),
+        function(result){
+            const signatureRelArr = [];
+            // sessionStorage.waterMarkStr = result.waterMarkStr;
+            if (params.needWaterMark) sessionStorage.waterMarkStr = result.waterMarkStr;
+            for (const signatureRel of result.signatureRelInfo) {
+                signatureRelArr.push(JSON.parse(signatureRel.rel_content));
+            }
+            // if (params.needWaterMark) COMMON_WATER_MARK_PIC_DATA = result.waterMarkStr;
+            for (let idx = 0; idx < result.data.length; idx++) {
+                let singleSignatureRelArr = [];
+                for (let rIdx = 0; rIdx < result.signatureRelInfo.length; rIdx++) {
+                    if (result.signatureRelInfo[rIdx].rpt_id === refRptTplIds[idx]) {
+                        singleSignatureRelArr = signatureRelArr[rIdx]; // 有些报表可能没有签名
+                        break;
+                    }
+                }
+                if (_current_stage_status === 3) {
+                    rptSignatureHelper.mergeSignDate(result.data[idx], singleSignatureRelArr, false);
+                    rptSignatureHelper.mergeSignature(result.data[idx], singleSignatureRelArr);
+                    rptSignatureHelper.mergeSignAudit(result.data[idx], singleSignatureRelArr, result.stageAudit);
+                }
+            }
+            let multiRptData = result.data;
+            for (let idx = 0; idx < multiRptData.length; idx++) {
+                let pageData = multiRptData[idx];
+                if (idx === 0) {
+                    $(document).attr("title", pageData[JV.NODE_PAGE_INFO][JV.NODE_MAIN_INFO_RPT_NAME]);
+                }
+                let orgPixelSize = getPixelSize(pageData);
+                let actArea = getActualArea(pageData);
+                let svgArr = rptPrintHelper.buildSvgArr(pageData, actArea, G_OFFSET_X, G_OFFSET_Y, closeWaterMark);
+                let orientation = "纵向";
+                showPreviewData(svgArr, actArea, scaleFactor, sessionStorage.pageSize, orientation, orgPixelSize);
+            }
+            // window.print();
+        },
+        function(failRst){
+            // sessionStorage.currentPageData = null;
+            console.log(failRst);
+        },
+        function(exceptionRst){
+            // sessionStorage.currentPageData = null;
+            console.log(exceptionRst);
+        }
+    );
+}
+
+function printPageLoading_bk() {
     if (sessionStorage.multiRptsData) {
         let multiRptData = JSON.parse(sessionStorage.multiRptsData);
         let scaleFactor = parseInt(sessionStorage.scaleFactor);
@@ -82,7 +135,7 @@ function showPreviewData(svgArr, actAreaArr, scaleFactor, pageSize, orientation,
         }
         // let div = $('<div class="pageBreak"></div>');
         let div = $(`<div class="pageBreak" style="${paddingStr}"></div>`);
-        div.append($(svgArr[i].join("")));
+        div.append($(svgArr[i].join(" ")));
         $(div).find("svg").each(function(cIdx,elementSvg){
             elementSvg.setAttribute('height', pageHeight - offsetHeight);
             elementSvg.setAttribute('width', pageWidth - offsetWidth);
@@ -127,3 +180,8 @@ function printPageClosing() {
     //
 }
 
+function printPage() {
+    $("#print_preview_div")[0].style.display = 'none';
+    window.print();
+}
+

+ 39 - 3
app/public/report/js/rpt_print.js

@@ -18,6 +18,34 @@ let rptPrintHelper = {
             } else {
                 params.needWaterMark = false;
             }
+            delete sessionStorage.currentPageData; //删除
+            sessionStorage.report_params = JSON.stringify(params);
+            sessionStorage.current_stage_status = current_stage_status;
+            sessionStorage.closeWaterMark = getCloseWatermark();
+            sessionStorage.pageSize = rptControlObj.getCurrentPageSize();
+            sessionStorage.waterMarkStr = COMMON_WATER_MARK_PIC_DATA;
+            if (sessionStorage.pageSize === 'A3') {
+                window.open('/printReport/A3');
+            } else {
+                window.open('/printReport/A4');
+            }
+        }
+    },
+    preview_bk: async function () {
+        if (zTreeOprObj.checkedRptTplNodes && zTreeOprObj.checkedRptTplNodes.length > 0) {
+            let refRptTplIds = [];
+            rptControlObj.getTplIdsCommon(refRptTplIds, null);
+            let params = rptControlObj.creatCommonExportParam(refRptTplIds);
+            await rptCustomObj.getCustomSelect(params);
+            let chkNodes = zTreeOprObj.treeObj.getCheckedNodes(true);
+            if (chkNodes.length > 0) {
+                delete params.orientation; // 打印时有勾选的话,不需要提供方向
+            }
+            if (COMMON_WATER_MARK_PIC_DATA === null || COMMON_WATER_MARK_PIC_DATA === '') {
+                params.needWaterMark = true;
+            } else {
+                params.needWaterMark = false;
+            }
             CommonAjax.postXsrfEx("/tender/report_api/getMultiReports", params, 60000, true, getCookie('csrfToken'),
                 function(result){
                     const signatureRelArr = [];
@@ -296,10 +324,10 @@ function buildCellSvg(cell, fonts, styles, controls, pageMergeBorder, rptMergeBo
 
 function buildImage(destRst, cell, control, offsetX, offsetY, adjustY, isHtoV, HtoVStr) {
     let href = '';
-    if (cell.pic) {
-        href = 'href="' + cell.pic +'"';
-    } else {
+    if (cell.path) {
         href = 'xlink:href="' + cell.path +'"';
+    } else {
+        href = 'href="' + cell.pic +'"';
     }
     const area = getProperSignatureArea(cell, control, offsetX, offsetY);
     if (cell.signature_name === JV.SIGNATURE_NAME_DUMMY) {
@@ -352,6 +380,11 @@ function getProperSignatureArea(cell, control, offsetX, offsetY) {
     return rst;
 }
 
+function strReplaceAll(targetStr, FindText, RepText) {
+    let regExp = new RegExp(FindText, "gm");
+    return targetStr.replace(regExp, RepText);
+}
+
 function buildText(destRst, cell, font, control, offsetX, offsetY, adjustY, canvas, isHtoV, HtoVStr) {
     let orgFontHeight = parseInt(font[JV.FONT_PROPS[JV.FONT_PROP_IDX_HEIGHT]]);
     let fontWeight = (font[JV.FONT_PROPS[JV.FONT_PROP_IDX_BOLD]] === 'T')?"bold":"normal";
@@ -367,6 +400,9 @@ function buildText(destRst, cell, font, control, offsetX, offsetY, adjustY, canv
     let value = cell[JV.PROP_VALUE];
     if (value === undefined || value === null) {
         value = "";
+    } else if (typeof value === 'string') {
+        //注意:要把 < 符号转义 -> &lt; 否则svg会有错乱
+        value = strReplaceAll(value, '<', '&lt;');
     }
     let values = null;
     if (typeof value === "string") {

+ 11 - 7
app/router.js

@@ -25,6 +25,8 @@ module.exports = app => {
     const advanceCheck = app.middlewares.advanceCheck();
     // 变更令中间件
     const changeCheck = app.middlewares.changeCheck();
+    // 形象进度中间件
+    const scheduleCheck = app.middlewares.scheduleCheck();
     // 登入登出相关
     app.get('/login', 'loginController.index');
     app.get('/login/port', api2otherCheck, 'loginController.port');
@@ -438,18 +440,20 @@ module.exports = app => {
     app.get('/MP_verify_t3MkWAMqplVxPjmr.txt', 'wechatController.oauthTxt');
 
     // 形象进度
-    app.get('/tender/:id/schedule', sessionAuth, tenderCheck, uncheckTenderCheck, 'scheduleController.index');
-    app.get('/tender/:id/schedule/ledger', sessionAuth, tenderCheck, uncheckTenderCheck, 'scheduleController.ledger');
+    app.get('/tender/:id/schedule', sessionAuth, tenderCheck, uncheckTenderCheck, scheduleCheck, 'scheduleController.index');
+    app.get('/tender/:id/schedule/ledger', sessionAuth, tenderCheck, uncheckTenderCheck, scheduleCheck, '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.get('/tender/:id/schedule/plan', sessionAuth, tenderCheck, uncheckTenderCheck, scheduleCheck, '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/order/:order', sessionAuth, tenderCheck, uncheckTenderCheck, 'scheduleController.stageTp');
+    app.get('/tender/:id/schedule/stage', sessionAuth, tenderCheck, uncheckTenderCheck, scheduleCheck, 'scheduleController.stageTp');
+    app.get('/tender/:id/schedule/stage/order/:order', sessionAuth, tenderCheck, uncheckTenderCheck, scheduleCheck, 'scheduleController.stageTp');
     app.post('/tender/:id/schedule/stage/save', sessionAuth, tenderCheck, uncheckTenderCheck, 'scheduleController.saveStageTp');
-    app.get('/tender/:id/schedule/stage/gcl', sessionAuth, tenderCheck, uncheckTenderCheck, 'scheduleController.stageGcl');
+    app.get('/tender/:id/schedule/stage/gcl', sessionAuth, tenderCheck, uncheckTenderCheck, scheduleCheck, 'scheduleController.stageGcl');
     app.post('/tender/:id/schedule/stage/gcl/save', sessionAuth, tenderCheck, uncheckTenderCheck, 'scheduleController.saveStageGcl');
-    app.post('/tender/:id/schedule/stage/:order/load', sessionAuth, tenderCheck, uncheckTenderCheck, stageCheck, 'scheduleController.loadTpLedgerData');
+    app.post('/tender/:id/schedule/stage/:order/load', sessionAuth, tenderCheck, uncheckTenderCheck, 'scheduleController.loadTpLedgerData');
+    app.post('/tender/:id/schedule/stage/gcl/:order/load', sessionAuth, tenderCheck, uncheckTenderCheck, 'scheduleController.loadGclLedgerData');
+    app.post('/tender/:id/schedule/audit/save', sessionAuth, tenderCheck, uncheckTenderCheck, 'scheduleController.saveAudit');
 
     // 书签
     app.post('/tender/:id/ledger/tag', sessionAuth, tenderCheck, uncheckTenderCheck, 'tenderController.billsTag');

+ 16 - 7
app/service/ledger_revise.js

@@ -205,15 +205,24 @@ module.exports = app => {
          * @returns {Promise<void>}
          */
         async cancelRevise(revise) {
-            const [billsHis, posHis] = await this.backupReviseHistoryFile(revise);
-            const result = await this.db.update(this.tableName, {
-                id: revise.id, valid: false, end_time: new Date(),
-                bills_file: billsHis, pos_file: posHis,
-            });
-            return result.affectedRows === 1;
+            const transaction = await this.db.beginTransaction();
+            try {
+                const [billsHis, posHis] = await this.backupReviseHistoryFile(revise);
+                const result = await transaction.update(this.tableName, {
+                    id: revise.id, valid: false, end_time: new Date(),
+                    bills_file: billsHis, pos_file: posHis,
+                });
+                // 形象进度改变状态
+                await transaction.update(this.ctx.service.schedule.tableName, { revising: 0 }, { where: { tid: this.ctx.tender.id } });
+                await transaction.commit();
+                return result.affectedRows === 1;
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
         }
 
     }
 
     return LedgerRevise;
-};
+};

+ 3 - 0
app/service/revise_audit.js

@@ -258,6 +258,9 @@ module.exports = app => {
                 }
                 await transaction.update(this.ctx.service.ledgerRevise.tableName, reviseData);
 
+                // 形象进度改变状态
+                await transaction.update(this.ctx.service.schedule.tableName, { revising: 1 }, { where: { tid: this.ctx.tender.id } });
+
                 // 添加短信通知-需要审批提醒功能
                 // 下一人
                 // await this.ctx.helper.sendUserSms(audit.audit_id, smsTypeConst.const.XD,

+ 29 - 1
app/service/schedule.js

@@ -16,9 +16,37 @@ module.exports = app => {
                     },
                 };
                 const updateData = {
-                    mode: data,
+                    mode: data.mode,
                 };
                 await transaction.update(this.tableName, updateData, options);
+                if (data.update_under_ledger.length > 0) {
+                    const month_list = [];
+                    const update_options = [];
+                    for (const un of data.update_under_ledger) {
+                        const option = {
+                            row: {
+                                plan_tp: un.plan_tp,
+                                plan_gcl: un.plan_gcl,
+                            },
+                            where: {
+                                tid: this.ctx.tender.id,
+                                lid: un.lid,
+                                yearmonth: un.yearmonth,
+                            },
+                        };
+                        update_options.push(option);
+                        if (!this._.find(month_list, un.yearmonth)) {
+                            month_list.push(un.yearmonth);
+                        }
+                    }
+                    if (update_options.length > 0) {
+                        await transaction.updateRows(this.ctx.service.scheduleLedgerMonth.tableName, update_options);
+                        for (const m of month_list) {
+                            await this.ctx.service.scheduleLedgerMonth.calcMonthPlan(transaction, this.ctx.tender.id, m);
+                        }
+                        await this.ctx.service.scheduleMonth.calcPlan(transaction, this.ctx.tender.id);
+                    }
+                }
                 await transaction.commit();
                 return true;
             } catch (err) {

+ 54 - 0
app/service/schedule_audit.js

@@ -0,0 +1,54 @@
+'use strict';
+const scheduleConst = require('../const/schedule');
+module.exports = app => {
+    class ScheduleAudit extends app.BaseService {
+        constructor(ctx) {
+            super(ctx);
+            this.tableName = 'schedule_audit';
+        }
+
+        async addAudit(data) {
+            const transaction = await this.db.beginTransaction();
+            try {
+                const insertData = {
+                    tid: this.ctx.tender.id,
+                    audit_id: data.audit_id,
+                    in_time: new Date(),
+                };
+                const result = await transaction.insert(this.tableName, insertData);
+                await transaction.commit();
+                return await this.getDataById(result.insertId);
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
+        }
+
+        async removeAudit(data) {
+            const transaction = await this.db.beginTransaction();
+            try {
+                await transaction.delete(this.tableName, { id: data.id });
+                await transaction.commit();
+                return true;
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
+        }
+        async editAudit(data) {
+            const transaction = await this.db.beginTransaction();
+            try {
+                if (!this._.includes(this._.values(scheduleConst.permission), data.permission)) {
+                    throw '修改失败';
+                }
+                await transaction.update(this.tableName, { id: data.id, permission: data.permission });
+                await transaction.commit();
+                return true;
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
+        }
+    }
+    return ScheduleAudit;
+};

+ 86 - 2
app/service/schedule_ledger.js

@@ -1,5 +1,5 @@
 'use strict';
-
+const scheduleConst = require('../const/schedule');
 module.exports = app => {
     class ScheduleLedger extends app.BaseService {
         constructor(ctx) {
@@ -15,7 +15,7 @@ module.exports = app => {
                 });
                 const oldLids = this._.map(oldDatas, 'ledger_id');
                 const insertDatas = [];
-                for (const l of datas) {
+                for (const l of datas.select_ledger) {
                     if (oldLids.indexOf(l) === -1) {
                         const data = {
                             tid: this.ctx.tender.id,
@@ -29,17 +29,101 @@ module.exports = app => {
                 if (oldLids.length > 0) {
                     for (const ol of oldLids) {
                         await transaction.delete(this.tableName, { tid: this.ctx.tender.id, ledger_id: ol });
+                        // 更新已选标段值
+                        await transaction.delete(this.ctx.service.scheduleLedgerMonth.tableName, { tid: this.ctx.tender.id, lid: ol });
                     }
                 }
                 if (insertDatas.length > 0) await transaction.insert(this.tableName, insertDatas);
+                // 更新所有为null
+                await transaction.update(this.tableName, { gcl: null, tp: null }, { where: { tid: this.ctx.tender.id } });
+                const updateOptions = [];
+                let total_tp = 0;
+                // 更新最底层和总设计值并更新最底层已填的计划与实际(金额、工程量)
+                for (const u of datas.under_ledger) {
+                    updateOptions.push({
+                        row: {
+                            gcl: u.gcl,
+                            tp: u.tp,
+                        },
+                        where: {
+                            ledger_id: u.ledger_id,
+                            tid: this.ctx.tender.id,
+                        },
+                    });
+                    total_tp = this.ctx.helper.add(total_tp, u.tp);
+                }
+                if (updateOptions.length > 0) await transaction.updateRows(this.tableName, updateOptions);
                 // 判断是否已创建了形象进度表
                 const scheduleInfo = await this.ctx.service.schedule.getDataByCondition({ tid: this.ctx.tender.id });
                 if (!scheduleInfo) {
                     const newSchedule = {
                         tid: this.ctx.tender.id,
+                        total_tp,
                     };
                     await transaction.insert(this.ctx.service.schedule.tableName, newSchedule);
+                    const ledgerData = await this.ctx.service.ledger.getData(this.ctx.tender.id);
+                    const insertDatas = [];
+                    for (const le of ledgerData) {
+                        insertDatas.push({ tid: this.ctx.tender.id, ledger_id: le.ledger_id });
+                    }
+                    await transaction.insert(this.ctx.service.scheduleLedgerHistory.tableName, insertDatas);
+                } else {
+                    await transaction.update(this.ctx.service.schedule.tableName, { id: scheduleInfo.id, total_tp, revising: 0 });
+                }
+                // 判断是否已存在计划月,并更新计划月统计数据,再更新总的统计数据
+                const smList = await this.ctx.service.scheduleMonth.getAllDataByCondition({ where: { tid: this.ctx.tender.id } });
+                if (scheduleInfo && smList.length > 0) {
+                    // 删除所有父节点已填过的值
+                    for (const p of datas.parent_ledger) {
+                        await transaction.delete(this.ctx.service.scheduleLedgerMonth.tableName, { tid: this.ctx.tender.id, lid: p });
+                    }
+                    if (datas.type === 'xz') {
+                        const slmUpdateOption = [];
+                        const mode = scheduleInfo.mode;
+                        for (const u of datas.under_ledger) {
+                            const dgn_price = this.ctx.helper.round(this.ctx.helper.div(u.tp, u.gcl), 2);
+                            if (dgn_price && dgn_price !== 0) {
+                                const sql = 'SELECT * FROM ?? WHERE tid = ? and lid = ?';
+                                const sqlParam = [this.ctx.service.scheduleLedgerMonth.tableName, this.ctx.tender.id, u.ledger_id];
+                                const lidList = await transaction.query(sql, sqlParam);
+                                if (lidList.length > 0) {
+                                    for (const l of lidList) {
+                                        if (mode === scheduleConst.plan_mode.tp) {
+                                            const new_plan_gcl = l.plan_tp ? this.ctx.helper.round(this.ctx.helper.div(l.plan_tp, dgn_price), 3) : null;
+                                            const new_sj_gcl = l.sj_tp ? this.ctx.helper.round(this.ctx.helper.div(l.sj_tp, dgn_price), 3) : null;
+                                            slmUpdateOption.push({ id: l.id, plan_gcl: new_plan_gcl, sj_gcl: new_sj_gcl });
+                                        } else if (mode === scheduleConst.plan_mode.gcl) {
+                                            const new_plan_tp = l.plan_gcl ? this.ctx.helper.round(this.ctx.helper.mul(l.plan_gcl, dgn_price), 0) : null;
+                                            const new_sj_tp = l.sj_gcl ? this.ctx.helper.round(this.ctx.helper.mul(l.sj_gcl, dgn_price), 0) : null;
+                                            slmUpdateOption.push({ id: l.id, plan_tp: new_plan_tp, sj_tp: new_sj_tp });
+                                        }
+                                    }
+                                }
+                            }
+                        }
+                        if (slmUpdateOption.length > 0) await transaction.updateRows(this.ctx.service.scheduleLedgerMonth.tableName, slmUpdateOption);
+                        // scheduleLedgerHistory更新到最新版台账
+                        const ledgerData = await this.ctx.service.ledger.getData(this.ctx.tender.id);
+                        const insertDatas = [];
+                        for (const le of ledgerData) {
+                            insertDatas.push({ tid: this.ctx.tender.id, ledger_id: le.ledger_id });
+                        }
+                        await transaction.delete(this.ctx.service.scheduleLedgerHistory.tableName, { tid: this.ctx.tender.id });
+                        await transaction.insert(this.ctx.service.scheduleLedgerHistory.tableName, insertDatas);
+                    }
+                    for (const sm of smList) {
+                        await this.ctx.service.scheduleLedgerMonth.calcMonthPlan(transaction, this.ctx.tender.id, sm.yearmonth);
+                        await this.ctx.service.scheduleLedgerMonth.calcMonthSj(transaction, this.ctx.tender.id, sm.yearmonth);
+                    }
+                    await this.ctx.service.scheduleMonth.calcPlan(transaction, this.ctx.tender.id);
+                    await this.ctx.service.scheduleMonth.calcSj(transaction, this.ctx.tender.id);
+                }
+                // 判断是否已存在计量进度,并更新计量统计数据,再更新总的统计数据
+                if (datas.stageTpList) {
+                    await transaction.updateRows(this.ctx.service.scheduleStage.tableName, datas.stageTpList);
+                    // await this.ctx.service.scheduleStage.calcStageSjTp(transaction, this.ctx.tender.id);
                 }
+                await this.ctx.service.scheduleStage.updateStageSjTp(transaction, this.ctx.tender.id, datas.stageSjTp);
                 await transaction.commit();
                 return true;
             } catch (err) {

+ 11 - 0
app/service/schedule_ledger_history.js

@@ -0,0 +1,11 @@
+'use strict';
+
+module.exports = app => {
+    class ScheduleLedgerHistory extends app.BaseService {
+        constructor(ctx) {
+            super(ctx);
+            this.tableName = 'schedule_ledger_history';
+        }
+    }
+    return ScheduleLedgerHistory;
+};

+ 95 - 1
app/service/schedule_ledger_month.js

@@ -44,6 +44,51 @@ module.exports = app => {
             }
         }
 
+        async saveDatas(datas) {
+            // 判断是添加,删除,还是修改
+            const transaction = await this.db.beginTransaction();
+            try {
+                const ymArray = [];
+                for (const data of datas) {
+                    const info = await this.getDataByCondition({ tid: this.ctx.tender.id, lid: data.lid, yearmonth: data.yearmonth });
+                    if (!this._.includes(ymArray, data.yearmonth)) {
+                        ymArray.push(data.yearmonth);
+                    }
+                    if (info) {
+                        if (data.plan_gcl === null && data.plan_tp === null && info.sj_gcl === null && info.sj_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);
+                    }
+                }
+                // 重新计算本月、总 计划金额和计划工程量
+                for (const ym of ymArray) {
+                    await this.calcMonthPlan(transaction, this.ctx.tender.id, ym);
+                }
+                await this.ctx.service.scheduleMonth.calcPlan(transaction, this.ctx.tender.id);
+                await transaction.commit();
+                return true;
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
+        }
+
         async saveSj(data) {
             // 判断是添加,删除,还是修改
             const transaction = await this.db.beginTransaction();
@@ -81,6 +126,51 @@ module.exports = app => {
             }
         }
 
+        async saveSjDatas(datas) {
+            // 判断是添加,删除,还是修改
+            const transaction = await this.db.beginTransaction();
+            try {
+                const ymArray = [];
+                for (const data of datas) {
+                    const info = await this.getDataByCondition({ tid: this.ctx.tender.id, lid: data.lid, yearmonth: data.yearmonth });
+                    if (!this._.includes(ymArray, data.yearmonth)) {
+                        ymArray.push(data.yearmonth);
+                    }
+                    if (info) {
+                        if (data.sj_gcl === null && data.sj_tp === null && info.plan_gcl === null && info.plan_tp === null) {
+                            await transaction.delete(this.tableName, { id: info.id });
+                        } else {
+                            const updateData = {
+                                id: info.id,
+                                sj_gcl: data.sj_gcl,
+                                sj_tp: data.sj_tp,
+                            };
+                            await transaction.update(this.tableName, updateData);
+                        }
+                    } else {
+                        const insertData = {
+                            tid: this.ctx.tender.id,
+                            lid: data.lid,
+                            yearmonth: data.yearmonth,
+                            sj_gcl: data.sj_gcl,
+                            sj_tp: data.sj_tp,
+                        };
+                        await transaction.insert(this.tableName, insertData);
+                    }
+                }
+                // 重新计算本月、总 计划金额和计划工程量
+                for (const ym of ymArray) {
+                    await this.calcMonthSj(transaction, this.ctx.tender.id, ym);
+                }
+                await this.ctx.service.scheduleMonth.calcSj(transaction, this.ctx.tender.id);
+                await transaction.commit();
+                return true;
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
+        }
+
         async calcMonthSj(transaction, tid, yearmonth) {
             const sql = 'SELECT SUM(`sj_gcl`) as total_sj_gcl, SUM(`sj_tp`) as total_sj_tp FROM ?? WHERE tid = ? and yearmonth = ?';
             const sqlParam = [this.tableName, tid, yearmonth];
@@ -116,12 +206,16 @@ module.exports = app => {
         }
 
         async getConllectionList(tid, yearmonthArray) {
-            const sql = 'SELECT tid, lid, SUM(`plan_gcl`) as plan_gcl, SUM(`plan_tp`) as plan_tp' +
+            const sql = 'SELECT tid, lid, SUM(`plan_gcl`) as plan_gcl, SUM(`plan_tp`) as plan_tp, SUM(`sj_gcl`) as sj_gcl, SUM(`sj_tp`) as sj_tp' +
                 ' FROM ?? WHERE `tid` = ? AND `yearmonth` in (?) GROUP BY `lid`';
             const sqlParam = [this.tableName, tid, yearmonthArray];
             const result = await this.db.query(sql, sqlParam);
             return result;
         }
+
+        // async getHadDataList(tid) {
+        //     const sql = 'SELECT lid FROM ?? WHERE tid = ? '
+        // }
     }
     return ScheduleLedgerMonth;
 };

+ 52 - 0
app/service/schedule_stage.js

@@ -57,6 +57,8 @@ module.exports = app => {
                     },
                 };
                 await transaction.update(this.ctx.service.scheduleMonth.tableName, updateData, option);
+                // 重算计量总金额
+                await this.calcStageSjTp(transaction, this.ctx.tender.id);
                 await transaction.commit();
                 return true;
             } catch (err) {
@@ -80,6 +82,56 @@ module.exports = app => {
                 throw err;
             }
         }
+
+        async updateOneTp(data) {
+            const transaction = await this.db.beginTransaction();
+            try {
+                const updateData = {
+                    tp: data.tp,
+                };
+                const option = {
+                    where: {
+                        tid: this.ctx.tender.id,
+                        order: data.order,
+                    },
+                };
+                await transaction.update(this.tableName, updateData, option);
+                await this.updateStageSjTp(transaction, this.ctx.tender.id, data.stage_sj_tp);
+                await transaction.commit();
+                return true;
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
+        }
+
+        async calcStageSjTp(transaction, tid) {
+            const sql = 'SELECT SUM(`tp`) as stage_sj_tp FROM ?? WHERE tid = ?';
+            const sqlParam = [this.tableName, tid];
+            const result = await transaction.queryOne(sql, sqlParam);
+            const updateData = {
+                stage_sj_tp: result.stage_sj_tp,
+            };
+            const option = {
+                where: {
+                    tid,
+                },
+            };
+            return await transaction.update(this.ctx.service.schedule.tableName, updateData, option);
+        }
+
+        async updateStageSjTp(transaction, tid, sj_tp) {
+            // 更新计量总金额
+            const stageData = {
+                stage_sj_tp: sj_tp,
+            };
+            const stageOption = {
+                where: {
+                    tid,
+                },
+            };
+            await transaction.update(this.ctx.service.schedule.tableName, stageData, stageOption);
+        }
     }
     return ScheduleStage;
 };

+ 4 - 0
app/service/stage.js

@@ -547,6 +547,10 @@ module.exports = app => {
                 }
                 await transaction.delete(this.ctx.service.stageBonus.tableName, { sid: id });
                 await transaction.delete(this.ctx.service.stageOther.tableName, { sid: id });
+                // 同步删除进度里所选的期
+                await transaction.delete(this.ctx.service.scheduleStage.tableName, { tid: stageInfo.tid, order: stageInfo.order });
+                // 重算进度计量总金额
+                await this.ctx.service.scheduleStage.calcStageSjTp(transaction, stageInfo.tid);
                 // 记录删除日志
                 await this.ctx.service.projectLog.addProjectLog(transaction, projectLogConst.type.stage, projectLogConst.status.delete, '第' + stageInfo.order + '期');
                 await transaction.commit();

+ 0 - 1
app/service/stage_bills.js

@@ -251,7 +251,6 @@ module.exports = app => {
                         await this._insertStageBillsData(transaction, d, stageBills, ledgerBills);
                     } else {
                         d.id = stageBills.id;
-                        console.log(d);
                         this._calcStageBillsData(d, stageBills, ledgerBills);
                         await transaction.update(this.tableName, d);
                     }

+ 1 - 1
app/service/stage_bonus.js

@@ -122,7 +122,7 @@ module.exports = app => {
 
             const datas = data instanceof Array ? data : [data];
             const orgDatas = await this.getAllDataByCondition({
-                where: { sid: this.ctx.stage.id, id: this.ctx.helper._.map(datas, 'id') }
+                where: { id: this.ctx.helper._.map(datas, 'id') }
             });
 
             const uDatas = [];

+ 9 - 0
app/service/stage_pay.js

@@ -296,6 +296,15 @@ module.exports = app => {
             return await transaction.query(sql, sqlParam);
         }
 
+        async getLastestPayId(id) {
+            const info = await this.getDataById(id);
+            const sql = 'SELECT SP.* FROM ?? As SP WHERE SP.`sid` = ? AND SP.`pid` = ? ORDER BY SP.`stimes` DESC, SP.`sorder` DESC';
+            const sqlParam = [this.tableName, info.sid, info.pid];
+            const lst = await this.db.queryOne(sql, sqlParam);
+            if (!lst || !lst.id) throw '数据错误';
+            return lst.id;
+        }
+
         /**
          * 拷贝上一操作人数据 为 下一操作人数据
          * @param stage - 期数据

+ 8 - 8
app/view/change/information.ejs

@@ -128,19 +128,19 @@
                                 </div>
                                 <div class="form-group">
                                     <label><b class="text-danger">*&nbsp;</b>工程变更理由及内容</label>
-                                    <textarea class="form-control form-control-sm" name="content" rows="6"><%- change.content %></textarea>
+                                    <textarea class="form-control form-control-sm" name="content" rows="6"><%- ctx.helper.replaceStr(change.content, /<br><br>/g, '\r\n') %></textarea>
                                 </div>
                                 <div class="form-group">
                                     <label>工程变更合同依据</label>
-                                    <textarea class="form-control form-control-sm" name="basis" rows="6"><%- change.basis %></textarea>
+                                    <textarea class="form-control form-control-sm" name="basis" rows="6"><%- ctx.helper.replaceStr(change.basis, /<br><br>/g, '\r\n') %></textarea>
                                 </div>
                                 <div class="form-group">
                                     <label>变更工程量数量计算式</label>
-                                    <textarea class="form-control form-control-sm" name="expr" rows="2"><%- change.expr %></textarea>
+                                    <textarea class="form-control form-control-sm" name="expr" rows="2"><%- ctx.helper.replaceStr(change.expr, /<br><br>/g, '\r\n') %></textarea>
                                 </div>
                                 <div class="form-group">
                                     <label>备注</label>
-                                    <textarea class="form-control form-control-sm" name="memo" rows="3"><%- change.memo %></textarea>
+                                    <textarea class="form-control form-control-sm" name="memo" rows="3"><%- ctx.helper.replaceStr(change.memo, /<br><br>/g, '\r\n') %></textarea>
                                 </div>
                                 <div class="form-group">
                                     <label>变更类型</label>
@@ -236,19 +236,19 @@
                                 </div>
                                 <div class="form-group">
                                     <label>工程变更理由及内容</label>
-                                    <textarea class="form-control form-control-sm" rows="6" readonly><%- change.content %></textarea>
+                                    <textarea class="form-control form-control-sm" rows="6" readonly><%- ctx.helper.replaceStr(change.content, /<br><br>/g, '\r\n') %></textarea>
                                 </div>
                                 <div class="form-group">
                                     <label>工程变更合同依据</label>
-                                    <textarea class="form-control form-control-sm" rows="6" readonly><%- change.basis %></textarea>
+                                    <textarea class="form-control form-control-sm" rows="6" readonly><%- ctx.helper.replaceStr(change.basis, /<br><br>/g, '\r\n') %></textarea>
                                 </div>
                                 <div class="form-group">
                                     <label>变更工程量数量计算式</label>
-                                    <textarea class="form-control form-control-sm" rows="2" readonly><%- change.expr %></textarea>
+                                    <textarea class="form-control form-control-sm" rows="2" readonly><%- ctx.helper.replaceStr(change.expr, /<br><br>/g, '\r\n') %></textarea>
                                 </div>
                                 <div class="form-group">
                                     <label>备注</label>
-                                    <textarea class="form-control form-control-sm" rows="3" readonly><%- change.memo %></textarea>
+                                    <textarea class="form-control form-control-sm" rows="3" readonly><%- ctx.helper.replaceStr(change.memo, /<br><br>/g, '\r\n') %></textarea>
                                 </div>
                                 <div class="form-group">
                                     <label>变更类型</label>

+ 12 - 0
app/view/report/rpt_print.ejs

@@ -4,10 +4,16 @@
     <meta charset="UTF-8">
     <title></title>
     <script src="/public/js/jquery/jquery-3.2.1.min.js"></script>
+    <script type="text/javascript" src="/public/js/common_ajax.js"></script>
     <script type="text/javascript" src="/public/report/js/jpc_output_value_define.js"></script>
     <script type="text/javascript" src="/public/report/js/jpc_output.js"></script>
     <script type="text/javascript" src="/public/report/js/rpt_print.js"></script>
+    <script type="text/javascript" src="/public/report/js/rpt_signature.js"></script>
     <script type="text/javascript" src="/public/report/js/rpt_preview_common.js"></script>
+    <!--
+    <link rel="stylesheet" href="/public/css/bootstrap/bootstrap.min.css">
+    -->
+
     <link rel="shortcut icon" href="/public/images/favicon.ico">
 </head>
 <style type="text/css">
@@ -51,6 +57,12 @@
 -->
 <body onload="printPageLoading()" onbeforeunload="printPageClosing()">
     <canvas id="chkCanvas" style="display:none"></canvas>
+    <div class="panel-body" id="print_preview_div" style="display:block">
+        <button class="btn btn-outline-primary btn-sm" type="button" onclick="printPage()">
+            <i class="fa fa-print"></i>
+            打印 <span id="checkCountPrint" class="badge badge-primary"></span>
+        </button>
+    </div>
 </body>
 <SCRIPT type="text/javascript">
 </SCRIPT>

+ 8 - 0
app/view/report/rpt_printA3.ejs

@@ -4,9 +4,11 @@
     <meta charset="UTF-8">
     <title></title>
     <script src="/public/js/jquery/jquery-3.2.1.min.js"></script>
+    <script type="text/javascript" src="/public/js/common_ajax.js"></script>
     <script type="text/javascript" src="/public/report/js/jpc_output_value_define.js"></script>
     <script type="text/javascript" src="/public/report/js/jpc_output.js"></script>
     <script type="text/javascript" src="/public/report/js/rpt_print.js"></script>
+    <script type="text/javascript" src="/public/report/js/rpt_signature.js"></script>
     <script type="text/javascript" src="/public/report/js/rpt_preview_common.js"></script>
     <link rel="shortcut icon" href="/public/images/favicon.ico">
 </head>
@@ -35,6 +37,12 @@
 </style>
 <body onload="printPageLoading()" onbeforeunload="printPageClosing()">
     <canvas id="chkCanvas" style="display:none"></canvas>
+    <div class="panel-body" id="print_preview_div" style="display:block">
+        <button class="btn btn-outline-primary btn-sm" type="button" onclick="printPage()">
+            <i class="fa fa-print"></i>
+            打印 <span id="checkCountPrint" class="badge badge-primary"></span>
+        </button>
+    </div>
 </body>
 <SCRIPT type="text/javascript">
 </SCRIPT>

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

@@ -12,13 +12,13 @@
                             <i class="fa fa-list-ol"></i> 显示层级
                         </button>
                         <div class="dropdown-menu" aria-labelledby="dropdownMenuButton">
-                            <a class="dropdown-item" name="showLevel" tag="1" href="javascirpt: void(0);">第一层</a>
-                            <a class="dropdown-item" name="showLevel" tag="2" href="javascirpt: void(0);">第二层</a>
-                            <a class="dropdown-item" name="showLevel" tag="3" href="javascirpt: void(0);">第三层</a>
-                            <a class="dropdown-item" name="showLevel" tag="4" href="javascirpt: void(0);">第四层</a>
-                            <a class="dropdown-item" name="showLevel" tag="5" href="javascirpt: void(0);">第五层</a>
-                            <a class="dropdown-item" name="showLevel" tag="last" href="javascirpt: void(0);">最底层</a>
-                            <a class="dropdown-item" name="showLevel" tag="leafXmj" href="javascirpt: void(0);">只显示项目节</a>
+                            <a class="dropdown-item" name="showLevel" tag="1" href="javascript: void(0);">第一层</a>
+                            <a class="dropdown-item" name="showLevel" tag="2" href="javascript: void(0);">第二层</a>
+                            <a class="dropdown-item" name="showLevel" tag="3" href="javascript: void(0);">第三层</a>
+                            <a class="dropdown-item" name="showLevel" tag="4" href="javascript: void(0);">第四层</a>
+                            <a class="dropdown-item" name="showLevel" tag="5" href="javascript: void(0);">第五层</a>
+                            <a class="dropdown-item" name="showLevel" tag="last" href="javascript: void(0);">最底层</a>
+                            <a class="dropdown-item" name="showLevel" tag="leafXmj" href="javascript: void(0);">只显示项目节</a>
                         </div>
                     </div>
                 </div>
@@ -118,7 +118,7 @@
                     <div id="xd-content" class="tab-pane">
                         <% if ((revise.status === audit.status.uncheck || revise.status === audit.status.checkNo) && !readOnly) { %>
                         <div class="sjs-bar">
-                            <div class="d-flex"><a href="javascirpt: void(0);" class="btn btn-sm btn-outline-success mb-1 ml-auto" id="save">保存</a></div>
+                            <div class="d-flex"><a href="javascript: void(0);" class="btn btn-sm btn-outline-success mb-1 ml-auto" id="save">保存</a></div>
                         </div>
                         <% } %>
                         <div class="sjs-sh" style="overflow:auto">

File diff suppressed because it is too large
+ 158 - 132
app/view/schedule/index.ejs


+ 4 - 3
app/view/schedule/ledger.ejs

@@ -15,7 +15,7 @@
                 <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_other_siblings">
+                            <input type="checkbox" class="custom-control-input" id="select_other_siblings" checked>
                             <label class="custom-control-label text-primary" for="select_other_siblings">跨级选择同级项</label>
                         </div>
                     </a>
@@ -29,7 +29,7 @@
                     </a>
                 </div>
             </div>
-            <% if (tender.user_id === ctx.session.sessionUser.accountId) { %>
+            <% if (ctx.tender.schedule_permission === scPermission.edit && !revising) { %>
             <div class="ml-auto">
                 <button type="button" id="ledger_submit" class="btn btn-primary btn-sm pull-right">确认提交</button>
             </div>
@@ -51,5 +51,6 @@
     const tenderInfo = JSON.parse(unescape('<%- escape(JSON.stringify(tenderInfo)) %>'));
     const thousandth = <%- ctx.tender.info.display.thousandth %>;
     const measureType = JSON.parse('<%- JSON.stringify(measureType) %>');
-    const selectedLedgerList = JSON.parse('<%- JSON.stringify(scheduleLedgerList) %>');
+    const hadDataLidList = JSON.parse('<%- JSON.stringify(hadDataLidList) %>');
+    const scheduleStage = JSON.parse('<%- JSON.stringify(scheduleStage) %>');
 </script>

+ 64 - 1
app/view/schedule/modal.ejs

@@ -1,3 +1,4 @@
+<% if (scheduleLedgerList.length === 0 && ctx.url !== '/tender/' + ctx.tender.id + '/schedule/ledger') { %>
 <!--首次使用提示-->
 <div class="modal fade" id="first" data-backdrop="static">
     <div class="modal-dialog" role="document">
@@ -23,13 +24,75 @@
         </div>
     </div>
 </div>
+<% } %>
+<% if (revising) { %>
+    <!--正在修订提示-->
+    <div class="modal fade" id="unedit" 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-secondary btn-sm" data-dismiss="modal">好的</button>
+                </div>
+            </div>
+        </div>
+    </div>
+    <script type="text/javascript">$('#unedit').modal('show');</script>
+<% } %>
+<% if (!revising && schedule && schedule.revising === 1) { %>
+    <!--正在修订提示-->
+    <div class="modal fade" id="okedit" 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">
+                    <% if (ctx.url === '/tender/' + ctx.tender.id + '/schedule/ledger') { %>
+                        <h5>正在更新台帐,请等待...</h5>
+                        <div class="progress">
+                            <div id="schedule-progress" class="progress-bar" role="progressbar" style="width: 0%;" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100">0%</div>
+                        </div>
+                        <div style="display: none"><button type="text" class="btn btn-primary" id="update-ledger">更新进度台账</button></div>
+                    <% } else { %>
+                        <h5>台账已完成修订,进入进度台账并更新最新台账数据。</h5>
+                    <% } %>
+                </div>
+                <% if (ctx.url !== '/tender/' + ctx.tender.id + '/schedule/ledger') { %>
+                <div class="modal-footer">
+                    <a href="/tender/<%- ctx.tender.id %>/schedule/ledger" class="btn btn-primary btn-sm">进入进度台账</a>
+                </div>
+                <% } %>
+            </div>
+        </div>
+    </div>
+    <script type="text/javascript">$('#okedit').modal('show');</script>
+<% } %>
 <script>
     const selectedLedgerList = JSON.parse('<%- JSON.stringify(scheduleLedgerList) %>');
+    const revising = <%- revising %>;
 </script>
 <script type="text/javascript">
     $(function () {
-        if (selectedLedgerList.length === 0) {
+        if (selectedLedgerList.length === 0 && !revising) {
             $('#first').modal('show');
         }
+        <% if (!revising && schedule && schedule.revising === 1 && ctx.url === '/tender/' + ctx.tender.id + '/schedule/ledger') { %>
+        let value = 0;
+        setInterval(function(e){
+            if (value < 100) {
+                value = parseInt(value) + 1;
+                $("#schedule-progress").css("width", value + "%").text(value + "%");
+            } else if (value === 100) {
+                value = parseInt(value) + 1;
+                $('#update-ledger').click();
+            }
+        }, 50);
+        <% } %>
     })
 </script>

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

@@ -6,11 +6,13 @@
             <h2>
                 <% if (planMonth) { %>计划至 <%- planMonth.split('-')[0] %>年<%- parseInt(planMonth.split('-')[1]) %>月 <% } %>
             </h2>
+            <% if (!revising && ctx.tender.schedule_permission === scPermission.edit) { %>
             <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">

+ 11 - 9
app/view/schedule/stage_gcl.ejs

@@ -15,10 +15,12 @@
                     </div>
                 </div>
             </div>
+            <% if (!revising && ctx.tender.schedule_permission === scPermission.edit) { %>
             <div class="ml-auto">
                 <a href="#edit-stage" data-toggle="modal" data-target="#edit-stage" class="btn btn-sm btn-outline-primary">管理计量</a>
                 <a href="#add" class="btn btn-sm btn-primary" data-toggle="modal" data-target="#add">创建实际计量</a>
             </div>
+            <% } %>
         </div>
     </div>
     <div class="content-wrap">
@@ -33,16 +35,16 @@
                             <a class="nav-link active" data-toggle="tab" href="#huizong" role="tab">汇总</a>
                         </li>
                         <li class="nav-item">
-                            <% if (scheduleStage.length > 0) { %>
+                            <% if (gclScheduleMonth.length > 0) { %>
                                 <div class="d-inline-block ml-2">
                                     <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">
-                                            <%- curScheduleStage.yearmonth.split('-')[0] %>年<%- parseInt(curScheduleStage.yearmonth.split('-')[1]) %>月(第<%- curScheduleStage.order %>期)
+                                        <button class="btn btn-sm btn-light dropdown-toggle text-primary" type="button" id="stageDropdownMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
+                                            <%- curScheduleMonth.yearmonth.split('-')[0] %>年<%- parseInt(curScheduleMonth.yearmonth.split('-')[1]) %>月
                                         </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;">
-                                            <% for (const s of scheduleStage) { %>
-                                                <% if (s.id !== curScheduleStage.id) { %>
-                                                    <a class="dropdown-item change-tp" data-order="<%- s.order %>" href="javascript:void(0);"><%- s.yearmonth.split('-')[0] %>年<%- parseInt(s.yearmonth.split('-')[1]) %>月(第<%- s.order %>期)</a>
+                                        <div class="dropdown-menu" id="stageDropdownMenu" aria-labelledby="stageDropdownMenuButton" x-placement="bottom-start" style="position: absolute; transform: translate3d(0px, 26px, 0px); top: 0px; left: 0px; will-change: transform;">
+                                            <% for (const s of gclScheduleMonth) { %>
+                                                <% if (s.id !== curScheduleMonth.id) { %>
+                                                    <a class="dropdown-item change-tp" data-order="<%- s.id %>" href="javascript:void(0);"><%- s.yearmonth.split('-')[0] %>年<%- parseInt(s.yearmonth.split('-')[1]) %>月</a>
                                                 <% } %>
                                             <% } %>
                                         </div>
@@ -65,7 +67,7 @@
     const measureType = JSON.parse('<%- JSON.stringify(measureType) %>');
     const schedule = JSON.parse('<%- JSON.stringify(schedule) %>');
     const scheduleMonth = JSON.parse('<%- JSON.stringify(scheduleMonth) %>');
+    const gclScheduleMonth = JSON.parse('<%- JSON.stringify(gclScheduleMonth) %>');
     const monthList = _.map(scheduleMonth, 'yearmonth');
-    const scheduleStage = JSON.parse('<%- JSON.stringify(scheduleStage) %>');
-    const curScheduleStage = JSON.parse('<%- JSON.stringify(curScheduleStage) %>');
+    let curScheduleMonth = gclScheduleMonth.length > 0 ? JSON.parse('<%- JSON.stringify(curScheduleMonth) %>') : null;
 </script>

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

@@ -29,17 +29,21 @@
                         </div>
                     </div>
                 </div>
+                <% if (!revising && ctx.tender.schedule_permission === scPermission.edit) { %>
                 <div class="d-inline-flex">
                     <a href="#delete" data-toggle="modal" data-target="#delete" class="btn btn-sm btn-outline-danger">删除本期进度</a>
                 </div>
                 <% } %>
+                <% } %>
             </div>
+            <% if (!revising && ctx.tender.schedule_permission === scPermission.edit) { %>
             <div class="ml-auto">
                 <a href="" class="btn btn-sm btn-primary" data-toggle="modal" data-target="#add">创建新计量进度</a>
                 <% if (scheduleStage.length > 0) { %>
                 <a href="" class="btn btn-sm btn-warning" data-toggle="modal" data-target="#re-build">重新生成本月进度</a>
                 <% } %>
             </div>
+            <% } %>
         </div>
     </div>
     <div class="content-wrap">

+ 1 - 0
app/view/stage_extra/bonus.ejs

@@ -45,4 +45,5 @@
     const readOnly = <%- ctx.stage.readOnly %>;
     const whiteList = JSON.parse('<%- JSON.stringify(ctx.app.config.multipart.whitelist) %>');
     const thousandth = <%- ctx.tender.info.display.thousandth %>;
+    const stageChecked = <%- ctx.stage.status === auditConst.status.checked %>;
 </script>

+ 0 - 2
app/view/stage_extra/bonus_modal.ejs

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

+ 3 - 0
app/view/tender/detail.ejs

@@ -101,6 +101,9 @@
                                 <a href="#bd-set-6" data-toggle="modal" data-target="#bd-set-6" class="btn btn-sm btn-outline-primary">章节设置</a>
                                 <a href="#bd-set-7" data-toggle="modal" data-target="#bd-set-7" class="btn btn-sm btn-outline-primary">付款账号</a>
                                 <i class="mx-2">|</i>
+                                <% if (ctx.session.sessionProject.page_show !== null && parseInt(ctx.session.sessionProject.page_show.xxjd) === 1 && ctx.session.sessionUser.is_admin) { %>
+                                <a href="#xxjd-set" data-toggle="modal" data-target="#xxjd-set" class="btn btn-sm btn-outline-primary">形象进度</a>
+                                <% } %>
                                 <a href="javascript: void(0);" class="btn btn-sm btn-outline-primary" id="copyBtn">拷贝设置</a>
                                 <% if (ctx.session.sessionUser.is_admin) { %>
                                 <a href="/tender/<%- tender.id %>/shenpi" class="btn btn-sm btn-outline-primary">审批流程</a>

+ 224 - 0
app/view/tender/detail_modal.ejs

@@ -1505,3 +1505,227 @@
         });
     }
 </script>
+
+<% if (ctx.session.sessionProject.page_show !== null && parseInt(ctx.session.sessionProject.page_show.xxjd) === 1 && ctx.session.sessionUser.is_admin) { %>
+<!--标段设置-形象进度-->
+<div class="modal fade" id="xxjd-set" 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="alert alert-warning">设置可使用「形象进度」用户</div>
+                <div class="dropdown">
+                    <button class="btn btn-outline-primary btn-sm dropdown-toggle" type="button" id="xxjd_dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
+                        添加用户
+                    </button>
+                    <div class="dropdown-menu" id="xxjd_dropdownMenu" aria-labelledby="xxjd_dropdownMenuButton" style="width:220px">
+                        <div class="mb-2 p-2"><input class="form-control form-control-sm gr-search"
+                                                     placeholder="姓名/手机 检索" autocomplete="off"></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>
+                                <div class="dd-content" data-toggleid="<%- idx %>">
+                                    <% group.groupList.forEach(item => { %>
+                                        <% if (item.id !== ctx.tender.data.user_id) { %>
+                                            <dd class="border-bottom p-2 mb-0 " data-id="<%- item.id %>" >
+                                                <p class="mb-0 d-flex"><span class="text-primary"><%- item.name %></span><span
+                                                            class="ml-auto"><%- item.mobile %></span></p>
+                                                <span class="text-muted"><%- item.role %></span>
+                                            </dd>
+                                        <% } %>
+                                    <% });%>
+                                </div>
+                            <% }) %>
+                        </dl>
+                    </div>
+                </div>
+                <div class="mt-1" style="max-height: 300px;overflow: auto">
+                    <table class="table table-bordered">
+                        <tr><th>用户</th><th>查看</th><th>修改</th><th>移除</th></tr>
+                        <tbody id="schedule-users">
+                        <% for (const sa of scheduleAuditList) { %>
+                        <% const audit = ctx.helper._.find(accountList, { id : sa.audit_id }); %>
+                        <% if (audit) { %>
+                        <tr data-id="<%- audit.id %>"><td><p class="mb-0 d-flex"><b class="col-5 pl-0"><%- audit.name %></b> <span class="text-muted"><%- audit.mobile %></span><p class="text-muted mb-0"><%- audit.role %></p></td><td>
+                                <div class="custom-control custom-checkbox mb-2">
+                                    <input type="checkbox" data-zhi="<%- scPermission.show %>" data-id="<%- sa.id %>" id="<%- sa.id %>_customRadio41" name="customCheckbox" class="custom-control-input" <% if (sa.permission !== scPermission.no) { %>checked<% } %>>
+                                    <label class="custom-control-label" for="<%- sa.id %>_customRadio41"></label>
+                                </div>
+                            </td><td>
+                                <div class="custom-control custom-checkbox mb-2">
+                                    <input type="checkbox" data-zhi="<%- scPermission.edit %>" data-id="<%- sa.id %>" id="<%- sa.id %>_customRadio42" name="customCheckbox" class="custom-control-input" <% if (sa.permission === scPermission.edit) { %>checked<% } %>>
+                                    <label class="custom-control-label" for="<%- sa.id %>_customRadio42"></label>
+                                </div>
+                            </td>
+                            <td><a href="javascript:void(0);" class="text-danger remove-schedule-user" data-id="<%- sa.id %>">移除</a></td>
+                        </tr>
+                        <% } %>
+                        <% } %>
+                        </tbody>
+                    </table>
+                </div>
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-sm btn-secondary" data-dismiss="modal">关闭</button>
+            </div>
+        </div>
+    </div>
+</div>
+<script>
+    $(function () {
+        const accountGroup = JSON.parse(unescape('<%- escape(JSON.stringify(accountGroup)) %>'));
+        const accountList = JSON.parse(unescape('<%- escape(JSON.stringify(accountList)) %>'));
+        const cur_uid = parseInt('<%- ctx.tender.data.user_id %>');
+        const cur_tenderid = parseInt('<%- ctx.tender.id %>');
+        const scPermission = JSON.parse(unescape('<%- escape(JSON.stringify(scPermission)) %>'));
+        // 形象进度
+        let timer = null;
+        let oldSearchVal = null;
+        $('body').on('input propertychange', '.gr-search', function(e) {
+            oldSearchVal = e.target.value;
+            timer && clearTimeout(timer);
+            timer = setTimeout(() => {
+                const newVal = $(this).val();
+                let html = '';
+                if (newVal && newVal === oldSearchVal) {
+                    accountList.filter(item => item && item.id !== cur_uid && (item.name.indexOf(newVal) !== -1 || (item.mobile && item.mobile.indexOf(newVal) !== -1))).forEach(item => {
+                        html += `<dd class="border-bottom p-2 mb-0 " data-id="${item.id}" >
+                        <p class="mb-0 d-flex"><span class="text-primary">${item.name}</span><span
+                                class="ml-auto">${item.mobile || ''}</span></p>
+                        <span class="text-muted">${item.role || ''}</span>
+                    </dd>`
+                    });
+                    $('#xxjd_dropdownMenu .book-list').empty();
+                    $('#xxjd_dropdownMenu .book-list').append(html);
+                } else {
+                    if (!$('#xxjd_dropdownMenu .acc-btn').length) {
+                        accountGroup.forEach((group, idx) => {
+                            if (!group) return;
+                            html += `<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>
+                        <div class="dd-content" data-toggleid="${idx}">`;
+                            group.groupList.forEach(item => {
+                                if (item.id !== cur_uid) {
+                                    html += `<dd class="border-bottom p-2 mb-0 " data-id="${item.id}" >
+                                    <p class="mb-0 d-flex"><span class="text-primary">${item.name}</span><span
+                                            class="ml-auto">${item.mobile || ''}</span></p>
+                                    <span class="text-muted">${item.role || ''}</span>
+                                </dd>`;
+                                }
+                            });
+                            html += '</div>';
+                        });
+                        $('#xxjd_dropdownMenu .book-list').empty();
+                        $('#xxjd_dropdownMenu .book-list').append(html);
+                    }
+                }
+            }, 400);
+        });
+
+        // 添加审批流程按钮逻辑
+        $('body').on('click', '.book-list dt', function () {
+            const idx = $(this).find('.acc-btn').attr('data-groupid');
+            const type = $(this).find('.acc-btn').attr('data-type');
+            if (type === 'hide') {
+                $(this).parent().find(`div[data-toggleid="${idx}"]`).show(() => {
+                    $(this).children().find('i').removeClass('fa-plus-square').addClass('fa-minus-square-o');
+                    $(this).find('.acc-btn').attr('data-type', 'show');
+
+                })
+            } else {
+                $(this).parent().find(`div[data-toggleid="${idx}"]`).hide(() => {
+                    $(this).children().find('i').removeClass('fa-minus-square-o').addClass('fa-plus-square');
+                    $(this).find('.acc-btn').attr('data-type', 'hide');
+                })
+            }
+            return false;
+        });
+
+        // 选中用户
+        $('body').on('click', 'dl dd', function () {
+            const id = parseInt($(this).data('id'));
+            if (id) {
+                const user = _.find(accountList, function (item) {
+                    return item.id === id;
+                });
+                const saIdList = [];
+                for (let i = 0; i < $('#schedule-users tr').length; i++) {
+                    saIdList.push(parseInt($('#schedule-users tr').eq(i).data('id')));
+                }
+                if (_.includes(saIdList, id)) {
+                    toastr.error('该用户已存在列表中,无需重复添加');
+                    return;
+                }
+
+                const prop = {
+                    audit_id: id,
+                    type: 'add',
+                };
+                postData('/tender/' + cur_tenderid + '/schedule/audit/save', prop, function (data) {
+                    const html = '<tr data-id="'+ user.id + '"><td><p class="mb-0 d-flex"><b class="col-5 pl-0">'+ user.name + '</b> <span class="text-muted">'+ user.mobile + '</span><p class="text-muted mb-0">'+ user.role + '</p></td><td>\n' +
+                        '                                <div class="custom-control custom-checkbox mb-2">\n' +
+                        '                                    <input type="checkbox" data-zhi="'+ scPermission.show +'" data-id="'+ data.id + '" id="'+ data.id + '_customRadio41" name="customCheckbox" class="custom-control-input" checked>\n' +
+                        '                                    <label class="custom-control-label" for="'+ data.id + '_customRadio41"></label>\n' +
+                        '                                </div>\n' +
+                        '                            </td><td>\n' +
+                        '                                <div class="custom-control custom-checkbox mb-2">\n' +
+                        '                                    <input type="checkbox" data-zhi="'+ scPermission.edit +'" data-id="'+ data.id + '" id="'+ data.id + '_customRadio42" name="customCheckbox" class="custom-control-input">\n' +
+                        '                                    <label class="custom-control-label" for="'+ data.id + '_customRadio42"></label>\n' +
+                        '                                </div>\n' +
+                        '                            </td>\n' +
+                        '                            <td><a href="javascript:void(0);" class="text-danger remove-schedule-user" data-id="'+ data.id + '">移除</a></td>\n' +
+                        '                        </tr>';
+                    $('#schedule-users').append(html);
+                });
+            }
+        });
+
+        // 移除用户
+        $('body').on('click', '.remove-schedule-user', function () {
+            const id = parseInt($(this).data('id'));
+            if (id) {
+                const prop = {
+                    id,
+                    type: 'del',
+                };
+                const _self = $(this);
+                postData('/tender/' + cur_tenderid + '/schedule/audit/save', prop, function (data) {
+                    _self.parents('tr').remove();
+                });
+            }
+        });
+
+        // 权限更改
+        $('body').on('click', '#schedule-users input[type="checkbox"]', function () {
+            let permission = scPermission.no;
+            const value = parseInt($(this).data('zhi'));
+            if ($(this).is(':checked')) {
+                if (value === scPermission.edit) {
+                    permission = scPermission.edit;
+                    $(this).parents('td').siblings().find('input').prop('checked', true);
+                } else if (value === scPermission.show) {
+                    permission = scPermission.show;
+                }
+            } else {
+                if (value === scPermission.edit) {
+                    permission = scPermission.show;
+                } else if (value === scPermission.show) {
+                    permission = scPermission.no;
+                    $(this).parents('td').siblings().find('input').prop('checked', false);
+                }
+            }
+            const id = parseInt($(this).data('id'));
+            const prop = {
+                id,
+                permission,
+                type: 'edit',
+            };
+            const _self = $(this);
+            postData('/tender/' + cur_tenderid + '/schedule/audit/save', prop, function (data) {
+            });
+        });
+    })
+</script>
+<% } %>

+ 9 - 1
app/view/tender/shenpi_modal.ejs

@@ -1,12 +1,20 @@
 <!--设置其他标段-->
 <div class="modal fade" id="batch" data-backdrop="static">
-    <div class="modal-dialog" role="document">
+    <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="alert alert-warning">将「<span id="shenpi-name"></span>」设置至其他标段</div>
+                <div class="input-group input-group-sm mb-2">
+                    <input class="form-control" placeholder="输入审批人名称搜索" type="text" name="audit-name" id="search-audit">
+                    <div class="input-group-append">
+                        <span class="input-group-text" id="search-result">0/0</span>
+                        <button class="btn btn-outline-secondary" id="up-search" disabled><i class="fa fa-arrow-up" aria-hidden="true"></i></button>
+                        <button class="btn btn-outline-secondary" id="down-search" disabled><i class="fa fa-arrow-down" aria-hidden="true"></i></button>
+                    </div>
+                </div>
                 <div class="modal-height-300" id="tender-list">
                 </div>
             </div>

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

@@ -43,14 +43,16 @@
                 <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.indexOf('/tender/' + ctx.tender.id + '/schedule/stage') !== -1) { %>class="active"<% } %>><a href="/tender/<%- ctx.tender.id %>/schedule/stage"><span>计量进度</span></a></li>-->
-            <!--</ul>-->
-        <!--</div>-->
+        <% if (ctx.session.sessionProject.page_show !== null && parseInt(ctx.session.sessionProject.page_show.xxjd) === 1 && ctx.tender.schedule_permission !== 0) { %>
+        <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.indexOf('/tender/' + ctx.tender.id + '/schedule/stage') !== -1) { %>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>

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

@@ -41,14 +41,16 @@
                 <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>-->
+        <% if (ctx.session.sessionProject.page_show !== null && parseInt(ctx.session.sessionProject.page_show.xxjd) === 1 && ctx.tender.schedule_permission !== 0) { %>
+        <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>

File diff suppressed because it is too large
+ 2957 - 2984
package-lock.json


+ 1 - 1
package.json

@@ -78,7 +78,7 @@
         "test-local": "set EGG_SERVER_ENV=local&& egg-bin test",
         "test-qa": "set EGG_SERVER_ENV=qa&&egg-bin test",
         "test-auto": "set EGG_SERVER_ENV=autotest&&egg-bin test",
-        "dev-docker": "egg-scripts start --port 7005",
+        "dev-docker": "egg-scripts start --env=local --port 7005",
         "cov": "egg-bin cov",
         "lint": "eslint .",
         "ci": "npm run lint && npm run cov",

+ 90 - 31
sql/update.sql

@@ -1,44 +1,100 @@
-ALTER TABLE `zh_project` ADD `rpt_authority` VARCHAR(1000) NOT NULL DEFAULT '' COMMENT '主管部门(报表显示)' AFTER `page_path`;
-ALTER TABLE `zh_project` ADD `rpt_items` VARCHAR(1000) NOT NULL DEFAULT '' COMMENT '建设项目类别(报表显示)' AFTER `rpt_authority`;
-ALTER TABLE `zh_project` ADD `rpt_level` VARCHAR(1000) NOT NULL DEFAULT '' COMMENT '级别(报表显示)' AFTER `rpt_items`;
-ALTER TABLE `zh_project` ADD `rpt_nature` VARCHAR(1000) NOT NULL DEFAULT '' COMMENT '建设性质(报表显示)' AFTER `rpt_level`;
+--
+-- 表的结构 `zh_schedule`
+--
 
-ALTER TABLE `zh_ledger`
-ADD COLUMN `dagl_url` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '档案管理-查看链接'AFTER `dagl_status`;
+CREATE TABLE `zh_schedule` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `tid` int(11) NOT NULL,
+  `total_tp` decimal(30,8) DEFAULT NULL COMMENT '已选台账总金额',
+  `plan_gcl` decimal(30,8) DEFAULT NULL COMMENT '计划总工程量',
+  `sj_gcl` decimal(30,8) DEFAULT NULL COMMENT '实际完成工程量',
+  `plan_tp` decimal(30,8) DEFAULT NULL COMMENT '计划总金额',
+  `sj_tp` decimal(30,8) DEFAULT NULL COMMENT '实际完成金额',
+  `stage_sj_tp` decimal(30,8) DEFAULT NULL COMMENT '计量期实际计量总额',
+  `mode` tinyint(1) DEFAULT '2' COMMENT '计划进度计算方式(默认为工程量)',
+  `revising` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否修订后并未更新数据',
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci COMMENT='形象进度表';
 
-ALTER TABLE `zh_pos`
-ADD COLUMN `dagl_url` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '档案管理-查看链接'AFTER `dagl_status`;
+--
+-- 表的结构 `zh_schedule_audit`
+--
 
-ALTER TABLE `zh_revise_bills`
-ADD COLUMN `dagl_url` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '档案管理-查看链接'AFTER `dagl_status`;
+CREATE TABLE `zh_schedule_audit` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `tid` int(11) NOT NULL COMMENT '标段id',
+  `audit_id` int(11) NOT NULL COMMENT '用户id',
+  `permission` tinyint(1) NOT NULL DEFAULT '1' COMMENT '权限',
+  `in_time` datetime DEFAULT NULL COMMENT '入库时间',
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci COMMENT='形象进度用户权限表';
 
-ALTER TABLE `zh_revise_pos`
-ADD COLUMN `dagl_url` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '档案管理-查看链接'AFTER `dagl_status`;
+--
+-- 表的结构 `zh_schedule_ledger`
+--
 
-ALTER TABLE `zh_advance_file`
-ADD COLUMN `extra_upload` TINYINT(1) NOT NULL DEFAULT '0' COMMENT '是否为审核通过后再次上传的文件,0为否' AFTER `fileext`;
+CREATE TABLE `zh_schedule_ledger` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `tid` int(11) NOT NULL COMMENT '标段id',
+  `ledger_id` int(11) NOT NULL COMMENT '台账id',
+  `gcl` decimal(30,8) DEFAULT NULL COMMENT '只记录已选最底层树工程量',
+  `tp` decimal(30,8) DEFAULT NULL COMMENT '只记录已选最底层树金额',
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci COMMENT='进度已选台账';
+
+--
+-- 表的结构 `zh_schedule_ledger_history`
+--
 
-ALTER TABLE `zh_ledger`
-ADD COLUMN `check_calc`  tinyint(1) NULL DEFAULT 1 COMMENT '是否检查计算' AFTER `dagl_url`;
+CREATE TABLE `zh_schedule_ledger_history` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `tid` int(11) NOT NULL,
+  `ledger_id` int(11) NOT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci COMMENT='旧台账(用于修订后对比新旧台账区别然后调整进度台账)';
+
+--
+-- 表的结构 `zh_schedule_ledger_month`
+--
+
+CREATE TABLE `zh_schedule_ledger_month` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `tid` int(11) NOT NULL COMMENT '标段id',
+  `lid` int(11) NOT NULL COMMENT '台账id',
+  `yearmonth` varchar(255) COLLATE utf8_unicode_ci NOT NULL COMMENT '计划年月',
+  `plan_gcl` decimal(30,8) DEFAULT NULL COMMENT '计划工程量',
+  `sj_gcl` decimal(30,8) DEFAULT NULL COMMENT '实际工程量',
+  `plan_tp` decimal(30,8) DEFAULT NULL COMMENT '计划金额',
+  `sj_tp` decimal(30,8) DEFAULT NULL COMMENT '实际计量金额',
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci COMMENT='台账年月记录进度表';
+
+--
+-- 表的结构 `zh_schedule_month`
+--
+
+CREATE TABLE `zh_schedule_month` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `tid` int(11) NOT NULL COMMENT '标段id',
+  `yearmonth` varchar(255) COLLATE utf8_unicode_ci NOT NULL COMMENT '计划年月',
+  `plan_gcl` decimal(30,8) DEFAULT NULL COMMENT '计划工程量',
+  `sj_gcl` decimal(30,8) DEFAULT NULL COMMENT '实际工程量',
+  `plan_tp` decimal(30,8) DEFAULT NULL COMMENT '计划金额',
+  `sj_tp` decimal(30,8) DEFAULT NULL COMMENT '实际计量金额',
+  `stage_gcl_used` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否已被工程量计量',
+  `stage_tp_used` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否已被金额模式计量',
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci COMMENT='进度年月记录汇总台账表';
 
-ALTER TABLE `zh_revise_bills`
-ADD COLUMN `check_calc`  tinyint(1) NULL DEFAULT 1 COMMENT '是否检查计算' AFTER `dagl_url`;
+--
+-- 表的结构 `zh_schedule_stage`
+--
 
-CREATE TABLE `zh_project_log` (
+CREATE TABLE `zh_schedule_stage` (
   `id` int(11) NOT NULL AUTO_INCREMENT,
-  `pid` int(11) NOT NULL COMMENT '操作项目',
-  `tid` int(11) NOT NULL COMMENT '操作标段',
-  `uid` int(11) NOT NULL COMMENT '操作人',
-  `type` tinyint(1) NOT NULL COMMENT '操作模块',
-  `status` tinyint(1) NOT NULL COMMENT '操作状态',
-  `msg` varchar(255) COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '操作名称',
-  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '操作时间',
-  `os` varchar(255) COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '系统信息',
-  `browser` varchar(255) COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '浏览器信息',
-  `ip` varchar(45) COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '操作人IP地址',
-  `address` varchar(255) COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '操作人地址(接口获取)',
+  `tid` int(11) NOT NULL COMMENT '标段id',
+  `yearmonth` varchar(255) COLLATE utf8_unicode_ci NOT NULL COMMENT '计划月份',
+  `order` int(11) NOT NULL COMMENT '计量期数',
+  `tp` decimal(30,8) DEFAULT NULL COMMENT '本期计量完成金额',
   PRIMARY KEY (`id`)
-) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci COMMENT='项目操作日志';
+) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci COMMENT='计量进度-计划月份,计量期选中表';

+ 44 - 0
sql/update20210129.sql

@@ -0,0 +1,44 @@
+ALTER TABLE `zh_project` ADD `rpt_authority` VARCHAR(1000) NOT NULL DEFAULT '' COMMENT '主管部门(报表显示)' AFTER `page_path`;
+ALTER TABLE `zh_project` ADD `rpt_items` VARCHAR(1000) NOT NULL DEFAULT '' COMMENT '建设项目类别(报表显示)' AFTER `rpt_authority`;
+ALTER TABLE `zh_project` ADD `rpt_level` VARCHAR(1000) NOT NULL DEFAULT '' COMMENT '级别(报表显示)' AFTER `rpt_items`;
+ALTER TABLE `zh_project` ADD `rpt_nature` VARCHAR(1000) NOT NULL DEFAULT '' COMMENT '建设性质(报表显示)' AFTER `rpt_level`;
+
+ALTER TABLE `zh_ledger`
+ADD COLUMN `dagl_url` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '档案管理-查看链接'AFTER `dagl_status`;
+
+ALTER TABLE `zh_pos`
+ADD COLUMN `dagl_url` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '档案管理-查看链接'AFTER `dagl_status`;
+
+ALTER TABLE `zh_revise_bills`
+ADD COLUMN `dagl_url` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '档案管理-查看链接'AFTER `dagl_status`;
+
+ALTER TABLE `zh_revise_pos`
+ADD COLUMN `dagl_url` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '档案管理-查看链接'AFTER `dagl_status`;
+
+ALTER TABLE `zh_advance_file`
+ADD COLUMN `extra_upload` TINYINT(1) NOT NULL DEFAULT '0' COMMENT '是否为审核通过后再次上传的文件,0为否' AFTER `fileext`;
+
+ALTER TABLE `zh_ledger`
+ADD COLUMN `check_calc`  tinyint(1) NULL DEFAULT 1 COMMENT '是否检查计算' AFTER `dagl_url`;
+
+ALTER TABLE `zh_revise_bills`
+ADD COLUMN `check_calc`  tinyint(1) NULL DEFAULT 1 COMMENT '是否检查计算' AFTER `dagl_url`;
+
+-- ----------------------------
+-- Table structure for zh_project_log
+-- ----------------------------
+CREATE TABLE `zh_project_log` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `pid` int(11) NOT NULL COMMENT '操作项目',
+  `tid` int(11) NOT NULL COMMENT '操作标段',
+  `uid` int(11) NOT NULL COMMENT '操作人',
+  `type` tinyint(1) NOT NULL COMMENT '操作模块',
+  `status` tinyint(1) NOT NULL COMMENT '操作状态',
+  `msg` varchar(255) COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '操作名称',
+  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '操作时间',
+  `os` varchar(255) COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '系统信息',
+  `browser` varchar(255) COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '浏览器信息',
+  `ip` varchar(45) COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '操作人IP地址',
+  `address` varchar(255) COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '操作人地址(接口获取)',
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci COMMENT='项目操作日志';