Procházet zdrojové kódy

Merge branch 'master' of http://192.168.1.41:3000/maixinrong/Calculation

TonyKang před 5 roky
rodič
revize
d0bd351937
61 změnil soubory, kde provedl 3295 přidání a 361 odebrání
  1. 13 1
      app/const/audit.js
  2. 104 28
      app/controller/report_controller.js
  3. 48 9
      app/controller/revise_controller.js
  4. 165 20
      app/controller/spss_controller.js
  5. 4 0
      app/controller/stage_controller.js
  6. 9 4
      app/controller/stage_extra_controller.js
  7. 53 0
      app/lib/rpt_data_analysis.js
  8. 2 2
      app/lib/stage_im.js
  9. binární
      app/public/deal_bills/签约清单导入格式.xls
  10. binární
      app/public/files/template/ledger/导入工程量清单EXCEL格式.xls
  11. 1 1
      app/public/js/change_set.js
  12. 237 0
      app/public/js/compare_stage.js
  13. 5 5
      app/public/js/compare_tz.js
  14. 283 0
      app/public/js/gather_stage.js
  15. 269 0
      app/public/js/gather_tz.js
  16. 88 0
      app/public/js/path_tree.js
  17. 75 26
      app/public/js/se_bonus.js
  18. 77 27
      app/public/js/se_jgcl.js
  19. 429 51
      app/public/js/se_other.js
  20. 1 1
      app/public/js/shares/export_excel.js
  21. 36 11
      app/public/js/spreadjs_rela/spreadjs_zh.js
  22. 4 4
      app/public/js/stage_im.js
  23. 130 0
      app/public/report/js/rpt_custom.js
  24. 1 0
      app/public/report/js/rpt_main.js
  25. 7 7
      app/reports/rpt_component/jpc_ex.js
  26. 3 0
      app/reports/rpt_component/jpc_value_define.js
  27. 7 4
      app/reports/util/rpt_calculation_data_util.js
  28. 6 0
      app/router.js
  29. 33 3
      app/service/change.js
  30. 22 17
      app/service/report.js
  31. 142 10
      app/service/report_memory.js
  32. 76 2
      app/service/revise_audit.js
  33. 29 13
      app/service/revise_pos.js
  34. 34 0
      app/service/rpt_custom_define.js
  35. 2 0
      app/service/stage.js
  36. 24 1
      app/service/stage_audit.js
  37. 20 8
      app/service/stage_bonus.js
  38. 40 14
      app/service/stage_jgcl.js
  39. 40 17
      app/service/stage_other.js
  40. 33 18
      app/service/tender.js
  41. 1 1
      app/view/change/info.ejs
  42. 2 1
      app/view/change/info_modal.ejs
  43. 2 2
      app/view/login/login.ejs
  44. 9 0
      app/view/report/index.ejs
  45. 24 0
      app/view/report/rpt_all_popup.ejs
  46. 10 4
      app/view/revise/index.ejs
  47. 164 2
      app/view/revise/modal.ejs
  48. 0 16
      app/view/spss/compare_stage.ejs
  49. 38 0
      app/view/spss/compare_stage_modal.ejs
  50. 3 1
      app/view/spss/compare_tz.ejs
  51. 2 4
      app/view/spss/compare_tz_modal.ejs
  52. 35 0
      app/view/spss/gather_stage_modal.ejs
  53. 34 0
      app/view/spss/gather_tz_modal.ejs
  54. 6 0
      app/view/stage_extra/bonus.ejs
  55. 13 2
      app/view/stage_extra/jgcl.ejs
  56. 12 17
      app/view/stage_extra/other.ejs
  57. 1 1
      app/view/stage_extra/sub_menu_list.ejs
  58. 277 0
      builder_report_index_define.js
  59. 52 0
      config/web.js
  60. 15 6
      test/app/service/report_memory.test.js
  61. 43 0
      test/app/service/report_memory_temp.test.js

+ 13 - 1
app/const/audit.js

@@ -77,7 +77,19 @@ const revise = (function () {
     auditStringClass[status.checking] = 'text-warning';
     auditStringClass[status.checked] = 'text-success';
     auditStringClass[status.checkNo] = 'text-warning';
-    return { status, statusString, statusClass, auditString, auditStringClass }
+    // 描述文本
+    const auditProgress = [];
+    auditProgress[status.uncheck] = '草稿';
+    auditProgress[status.checking] = '审批中';
+    auditProgress[status.checked] = '审批通过';
+    auditProgress[status.checkNo] = '审批退回';
+    // 样式
+    const auditProgressClass = [];
+    auditProgressClass[status.uncheck] = '';
+    auditProgressClass[status.checking] = 'text-warning';
+    auditProgressClass[status.checked] = 'text-success';
+    auditProgressClass[status.checkNo] = 'text-warning';
+    return { status, statusString, statusClass, auditString, auditStringClass, auditProgress, auditProgressClass };
 })();
 
 // 期审批流程

+ 104 - 28
app/controller/report_controller.js

@@ -156,6 +156,40 @@ module.exports = app => {
             }
         }
 
+        async _getReport(ctx, params) {
+            // console.log('in getReport');
+            // console.log(params);
+            let rptTpl = await ctx.service.rptTpl.getTplById(params.rpt_tpl_id);
+            if (!rptTpl || rptTpl.length !== 1) {
+                throw '获取模板失败';
+            }
+            rptTpl = JSON.parse(rptTpl[0].rpt_content);
+            // console.log('get the template!');
+            const customSelect = await ctx.service.rptCustomDefine.getCustomDefine(params.tender_id, params.stage_id, params.rpt_tpl_id);
+            console.log(customSelect);
+            const pageRst = await getAllPagesCommon(ctx, rptTpl, params, JV.PAGING_OPTION_NORMAL, JV.OUTPUT_TYPE_NORMAL, this.app.baseDir, customSelect);
+            // console.log(pageRst);
+            // const roleRel = (params.stage_status === 3) ? (await ctx.service.roleRptRel.getRoleRptRelByDetailIds(params.tender_id, params.rpt_tpl_id)) : [];
+            const roleRel = await ctx.service.roleRptRel.getRoleRptRelByDetailIds(params.tender_id, params.rpt_tpl_id); // 新需求中,允许在非审核状态下设置签名
+            const stgAudit = await ctx.service.stageAudit.getStageAudit(params.stage_id, params.stage_times);
+            // console.log('after role stage!');
+            // console.log(roleRel);
+            await encodeSignatureDataUri(roleRel, this.app.baseDir);
+            await encodeDummySignatureDataUri(pageRst, this.app.baseDir);
+            const stageFlow = await ctx.service.stageAudit.getAuditGroupByListWithOwner(params.stage_id, params.stage_times);
+
+            // console.log('encodeSignatureDataUri!');
+            return {
+                data: pageRst,
+                signatureRelInfo: roleRel,
+                stageAudit: stgAudit,
+                debugInfo: ctx.app.config.is_debug ? ctx.debugInfo : null,
+                customDefine: rptTpl[JV.NODE_CUSTOM_DEFINE],
+                stageFlow,
+                customSelect,
+            };
+        }
+
         /**
          * 获取报表数据
          *
@@ -166,25 +200,39 @@ module.exports = app => {
             try {
                 // console.log('in getReport');
                 const params = JSON.parse(ctx.request.body.params);
-                // console.log(params);
-                let rptTpl = await ctx.service.rptTpl.getTplById(params.rpt_tpl_id);
-                if (!rptTpl || rptTpl.length !== 1) {
-                    throw '获取模板失败';
-                }
-                rptTpl = JSON.parse(rptTpl[0].rpt_content);
-                // console.log('get the template!');
-                const pageRst = await getAllPagesCommon(ctx, rptTpl, params, JV.PAGING_OPTION_NORMAL, JV.OUTPUT_TYPE_NORMAL, this.app.baseDir);
-                // console.log(pageRst);
-                // const roleRel = (params.stage_status === 3) ? (await ctx.service.roleRptRel.getRoleRptRelByDetailIds(params.tender_id, params.rpt_tpl_id)) : [];
-                const roleRel = await ctx.service.roleRptRel.getRoleRptRelByDetailIds(params.tender_id, params.rpt_tpl_id); // 新需求中,允许在非审核状态下设置签名
-                const stgAudit = await ctx.service.stageAudit.getStageAudit(params.stage_id, params.stage_times);
-                // console.log('after role stage!');
-                // console.log(roleRel);
-                await encodeSignatureDataUri(roleRel, this.app.baseDir);
-                await encodeDummySignatureDataUri(pageRst, this.app.baseDir);
-                // console.log('encodeSignatureDataUri!');
-                ctx.body = { data: pageRst, signatureRelInfo: roleRel, stageAudit: stgAudit, debugInfo: ctx.app.config.is_debug ? ctx.debugInfo : null };
-                // ctx.body = { data: { msg: 'test the network' } };
+                ctx.body = await this._getReport(ctx, params);
+                // // console.log(params);
+                // let rptTpl = await ctx.service.rptTpl.getTplById(params.rpt_tpl_id);
+                // if (!rptTpl || rptTpl.length !== 1) {
+                //     throw '获取模板失败';
+                // }
+                // rptTpl = JSON.parse(rptTpl[0].rpt_content);
+                // // console.log('get the template!');
+                // const pageRst = await getAllPagesCommon(ctx, rptTpl, params, JV.PAGING_OPTION_NORMAL, JV.OUTPUT_TYPE_NORMAL, this.app.baseDir);
+                // // console.log(pageRst);
+                // // const roleRel = (params.stage_status === 3) ? (await ctx.service.roleRptRel.getRoleRptRelByDetailIds(params.tender_id, params.rpt_tpl_id)) : [];
+                // const roleRel = await ctx.service.roleRptRel.getRoleRptRelByDetailIds(params.tender_id, params.rpt_tpl_id); // 新需求中,允许在非审核状态下设置签名
+                // const stgAudit = await ctx.service.stageAudit.getStageAudit(params.stage_id, params.stage_times);
+                // // console.log('after role stage!');
+                // // console.log(roleRel);
+                // await encodeSignatureDataUri(roleRel, this.app.baseDir);
+                // await encodeDummySignatureDataUri(pageRst, this.app.baseDir);
+                // const stageFlow = await ctx.service.stageAudit.getAuditGroupByListWithOwner(params.stage_id, params.stage_times);
+                // const customSelect = await ctx.service.rptCustomDefine.getDataByCondition({
+                //     tid: params.tender_id, sid: params.stage_id, rid: params.rpt_tpl_id
+                // });
+                //
+                // // console.log('encodeSignatureDataUri!');
+                // ctx.body = {
+                //     data: pageRst,
+                //     signatureRelInfo: roleRel,
+                //     stageAudit: stgAudit,
+                //     debugInfo: ctx.app.config.is_debug ? ctx.debugInfo : null,
+                //     customDefine: rptTpl[JV.NODE_CUSTOM_DEFINE],
+                //     stageFlow,
+                //     customSelect,
+                // };
+                // // ctx.body = { data: { msg: 'test the network' } };
                 ctx.status = 201;
             } catch (ex) {
                 console.log(ex);
@@ -346,6 +394,30 @@ module.exports = app => {
                 console.log(e);
             }
         }
+
+        async setCustomDefine(ctx) {
+            try {
+                const data = JSON.parse(ctx.request.body.data);
+                console.log(data);
+                const filter = {tid: data.tender_id, sid: data.stage_id, rid: data.rpt_tpl_id};
+                const count = await ctx.service.rptCustomDefine.count(filter);
+                const updateData = {};
+                if (data.audit_select) updateData.audit_select = JSON.stringify(data.audit_select);
+                if (count > 0) {
+                    await ctx.service.rptCustomDefine.update(updateData, filter);
+                } else {
+                    updateData.tid = data.tender_id;
+                    updateData.sid = data.stage_id;
+                    updateData.rid = data.rpt_tpl_id;
+                    await ctx.service.rptCustomDefine.db.insert(ctx.service.rptCustomDefine.tableName, updateData);
+                }
+                const result = await this._getReport(ctx, data);
+                ctx.body = {err: 0, msg: '', data: result};
+            } catch (err) {
+                ctx.helper.log(err);
+                ctx.body = this.ajaxErrorBody(err, '保存数据出错');
+            }
+        }
     }
     return ReportController;
 };
@@ -358,7 +430,7 @@ async function checkStg(ctx, params) {
         }
     }
 }
-async function getReportData(ctx, params, filters, memFieldKeys) {
+/*async function getReportData(ctx, params, filters, memFieldKeys) {
     const rst = {};
     const runnableRst = [];
     const runnableKey = []; // 这个配合runnableRst用,未来考虑并行查询优化
@@ -459,9 +531,9 @@ async function getReportData(ctx, params, filters, memFieldKeys) {
         }
     }
     return rst;
-}
+}*/
 
-async function getAllPagesCommon(ctx, rptTpl, params, option, outputType, baseDir) {
+async function getAllPagesCommon(ctx, rptTpl, params, option, outputType, baseDir, customSelect) {
     const rptDataUtil = new rptDataExtractor();
     rptDataUtil.initialize(rptTpl);
     const filter = rptDataUtil.getDataRequestFilter();
@@ -480,7 +552,7 @@ async function getAllPagesCommon(ctx, rptTpl, params, option, outputType, baseDi
         // console.log(defProperties);
 
         // console.log('before assemble');
-        const tplData = rptDataUtil.assembleData(ctx, rawDataObj, baseDir, printCom);
+        const tplData = rptDataUtil.assembleData(ctx, rawDataObj, baseDir, printCom, customSelect);
         // console.log(tplData);
 
         if (params.custCfg) {
@@ -491,12 +563,12 @@ async function getAllPagesCommon(ctx, rptTpl, params, option, outputType, baseDi
         const dftOption = params.option || JV.PAGING_OPTION_NORMAL;
         printCom.initialize(rptTpl);
         // ctx.helper
-        printCom.analyzeData(ctx.helper, rptTpl, tplData, defProperties, dftOption, outputType);
+        printCom.analyzeData(ctx.helper, rptTpl, tplData, defProperties, dftOption, outputType, customSelect);
         // console.log(JSON.stringify(rptTpl));
         const maxPages = printCom.totalPages;
         let pageRst = null;
         if (maxPages > 0) {
-            pageRst = printCom.outputAsSimpleJSONPageArray(ctx.helper, rptTpl, tplData, 1, maxPages, defProperties, params.custCfg);
+            pageRst = printCom.outputAsSimpleJSONPageArray(ctx.helper, rptTpl, tplData, 1, maxPages, defProperties, params.custCfg, customSelect);
         } else {
             pageRst = printCom.outputAsPreviewPage(rptTpl, defProperties);
         }
@@ -570,7 +642,8 @@ async function getMultiRptsCommon(ctx, params, outputType, baseDir) {
         for (let tplIdx = 0; tplIdx < rptTpls.length; tplIdx++) {
             const rptTpl = (rptTpls[tplIdx]._doc) ? rptTpls[tplIdx]._doc : rptTpls[tplIdx];
             rptDataUtil.initialize(rptTpl);
-            const tplData = rptDataUtil.assembleData(ctx, rawDataObj, baseDir);
+            const customSelect = await ctx.service.rptCustomDefine.getCustomDefine(params.tender_id, params.stage_id, rptTpls[tplIdx].id);
+            const tplData = rptDataUtil.assembleData(ctx, rawDataObj, baseDir, null, customSelect);
             const printCom = JpcEx.createNew();
             rptTpl[JV.NODE_MAIN_INFO][JV.NODE_PAGE_INFO][JV.PROP_PAGE_SIZE] = params.pageSize;
 
@@ -580,13 +653,16 @@ async function getMultiRptsCommon(ctx, params, outputType, baseDir) {
             const dftOption = params.option || JV.PAGING_OPTION_NORMAL;
 
             printCom.initialize(rptTpl);
+            const cdDefine = await ctx.service.rptCustomDefine.getDataByCondition({
+                tid: params.tender_id, sid: params.stage_id, rid: params.rpt_tpl_id
+            });
             // console.log(rptTpl);
-            printCom.analyzeData(ctx.helper, rptTpl, tplData, defProperties, dftOption, outputType);
+            printCom.analyzeData(ctx.helper, rptTpl, tplData, defProperties, dftOption, outputType, cdDefine);
             const maxPages = printCom.totalPages;
             let pageRst = null;
             // console.log(maxPages);
             if (maxPages > 0) {
-                pageRst = printCom.outputAsSimpleJSONPageArray(ctx.helper, rptTpl, tplData, 1, maxPages, defProperties, params.custCfg);
+                pageRst = printCom.outputAsSimpleJSONPageArray(ctx.helper, rptTpl, tplData, 1, maxPages, defProperties, params.custCfg, customSelect);
             } else {
                 pageRst = printCom.outputAsPreviewPage(rptTpl, defProperties);
             }

+ 48 - 9
app/controller/revise_controller.js

@@ -66,9 +66,14 @@ module.exports = app => {
                         revise.lastest = true;
                     } else {
                         if (ledgerRevise.length > 1) ledgerRevise[1].lastest = true;
-                        if (revise.status === audit.revise.status.checking) {
-                            revise.curAuditor = await ctx.service.reviseAudit.getCurAuditor(revise.id, revise.times);
-                        }
+                        // if (revise.status === audit.revise.status.checking) {
+                        //     revise.curAuditor = await ctx.service.reviseAudit.getCurAuditor(revise.id, revise.times);
+                        // }
+                    }
+                }
+                for (const lr of ledgerRevise) {
+                    if (lr.valid) {
+                        lr.curAuditor = await ctx.service.reviseAudit.getAuditorByStatus(lr.id, lr.status, lr.times);
                     }
                 }
                 const addValid = await this._getAddReviseValid(ctx);
@@ -86,6 +91,7 @@ module.exports = app => {
                     ledgerRevise,
                     addValid,
                     auditConst: audit.revise,
+                    auditConst2: JSON.stringify(audit.revise),
                     stdBills,
                     stdChapters,
                 };
@@ -97,6 +103,39 @@ module.exports = app => {
         }
 
         /**
+         * 修订审批流程(Get)
+         * @param ctx
+         * @returns {Promise<void>}
+         */
+        async reviseAuditors(ctx) {
+            try {
+                const responseData = {
+                    err: 0, msg: '', data: {},
+                };
+                const rid = JSON.parse(ctx.request.body.data).id;
+                const reviseInfo = await ctx.service.ledgerRevise.getDataById(rid);
+                // 获取审批流程中右边列表
+                const auditHistory = [];
+                const times = reviseInfo.status === audit.revise.status.checkNo ? reviseInfo.times - 1 : reviseInfo.times;
+                if (times >= 1) {
+                    for (let i = 1; i <= times; i++) {
+                        auditHistory.push(await ctx.service.reviseAudit.getAuditors2ReviseList(reviseInfo.id, i));
+                    }
+                }
+                responseData.data.auditHistory = auditHistory;
+                // 获取审批流程中左边列表
+                responseData.data.auditors = await ctx.service.reviseAudit.getAuditGroupByList(reviseInfo.id, times);
+                // 获取原报信息
+                const reviseAuditor = await ctx.service.projectAccount.getAccountInfoById(reviseInfo.uid);
+                responseData.data.reviseAuditor = reviseAuditor;
+                ctx.body = responseData;
+            } catch (error) {
+                this.log(error);
+                ctx.body = { err: 1, msg: error.toString(), data: null };
+            }
+        }
+
+        /**
          * 新增 修订 (Post)
          * @param ctx
          * @returns {Promise<void>}
@@ -230,7 +269,7 @@ module.exports = app => {
             }
             return {
                 revise: revise, tender: ctx.tender.data,
-                //reviseBills, revisePos, 
+                //reviseBills, revisePos,
                 ledgerSpread, posSpread, tenderMenu, measureType,
                 preUrl: '/tender/' + ctx.tender.id,
                 audit: audit.revise,
@@ -361,7 +400,7 @@ module.exports = app => {
                     : await ctx.service.revisePos.getData(ctx.tender.id);
 
                 if (revise.uid === ctx.session.sessionUser.accountId &&
-                    (revise.status === audit.revise.status.uncheck || revise.status === audit.revise.status.checkNo)) {                    
+                    (revise.status === audit.revise.status.uncheck || revise.status === audit.revise.status.checkNo)) {
                     const lastStage = await ctx.service.stage.getLastestStage(ctx.tender.id, true);
                     if (lastStage) {
                         if (lastStage.status === audit.stage.status.checked) {
@@ -385,7 +424,7 @@ module.exports = app => {
                                 p.used = usedPrePos.indexOf(p.id) >= 0 || usedCurPos.indexOf(p.id) >= 0;
                             }
                         }
-                    }  
+                    }
                 }
                 ctx.body = {err: 0, msg: '', data: {bills: reviseBills, pos: revisePos}};
             } catch(err) {
@@ -435,13 +474,13 @@ module.exports = app => {
 
         /**
          * 加载 数据
-         * @param {} ctx 
+         * @param {} ctx
          */
         async loadHistoryData(ctx) {
             try {
                 const revise = await ctx.service.ledgerRevise.getLastestRevise(ctx.tender.id, false);
                 if (!revise) throw '台账修订数据有误';
-    
+
                 const reviseBills = await ctx.service.ledger.getData(ctx.tender.id);
                 const revisePos = await ctx.service.pos.getPosData({tid: ctx.tender.id});
                 ctx.body = {err: 0, msg: '', data: {bills: reviseBills, pos: revisePos}};
@@ -791,4 +830,4 @@ module.exports = app => {
     }
 
     return ReviseController;
-};
+};

+ 165 - 20
app/controller/spss_controller.js

@@ -9,6 +9,7 @@
  */
 
 const measureType = require('../const/tender').measureType;
+const status = require('../const/audit').stage.status;
 
 module.exports = app => {
 
@@ -23,7 +24,81 @@ module.exports = app => {
         constructor(ctx) {
             super(ctx);
             ctx.showProject = true;
-        }        
+        }
+
+        async _getTzData(tid, includePos = false) {
+            console.log(tid);
+            const tender = await this.ctx.service.tender.getTender(tid);
+            if (!tender || tender.project_id !== this.ctx.session.sessionProject.id) {
+                throw '不存在该标段';
+            }
+            const bills = await this.ctx.service.ledger.getData(tid);
+            const pos = tender.measure_type === measureType.tz.value || includePos
+                ? await this.ctx.service.pos.getPosData({tid: tid})
+                : [];
+
+            return { id: tid, name: tender.name, bills: bills, pos: pos };
+        }
+
+        async _checkStage(tid, sorder) {
+            const _ = this.ctx.helper._;
+            const stage = await this.service.stage.getDataByCondition({ tid: tid, order: sorder });
+            if (!stage) throw '期数据错误';
+
+            // 读取原报、审核人数据
+            stage.auditors = await this.service.stageAudit.getAuditors(stage.id, stage.times);
+            stage.curAuditor = await this.service.stageAudit.getCurAuditor(stage.id, stage.times);
+            const accountId = this.ctx.session.sessionUser.accountId, auditorIds = _.map(stage.auditors, 'aid'), shareIds = [];
+            const permission = this.ctx.session.sessionUser.permission;
+
+            if (accountId === stage.user_id) { // 原报
+                stage.curTimes = stage.times;
+                if (stage.status === status.uncheck || stage.status === status.checkNo) {
+                    stage.curOrder = 0;
+                } else if (stage.status === status.checked) {
+                    stage.curOrder = _.max(_.map(stage.auditors, 'order'));
+                } else {
+                    stage.curOrder = stage.curAuditor.aid === accountId ? stage.curAuditor.order : stage.curAuditor.order - 1;
+                }
+            } else if (auditorIds.indexOf(accountId) !== -1) { // 审批人
+                if (stage.status === status.uncheck) {
+                    throw '您无权查看该数据';
+                }
+                stage.curTimes = stage.status === status.checkNo ? stage.times - 1 : stage.times;
+                if (stage.status === status.checked) {
+                    stage.curOrder = _.max(_.map(stage.auditors, 'order'));
+                } else if (stage.status === status.checkNo) {
+                    const audit = await this.service.stageAudit.getDataByCondition({
+                        sid: stage.id, times: stage.times - 1, status: status.checkNo
+                    });
+                    stage.curOrder = audit.order;
+                } else {
+                    stage.curOrder = accountId === stage.curAuditor.aid ? stage.curAuditor.order : stage.curAuditor.order - 1;
+                }
+            } else if (shareIds.indexOf(accountId) !== -1 || (permission !== null && permission.tender !== undefined && permission.tender.indexOf('2') !== -1)) { // 分享人
+                if (stage.status === status.uncheck) {
+                    throw '您无权查看该数据';
+                }
+                stage.curTimes = stage.status === status.checkNo ? stage.times - 1 : stage.times;
+                stage.curOrder = stage.status === status.checked ? _.max(_.map(stage.auditors, 'order')) : stage.curAuditor.order - 1;
+            } else { // 其他不可见
+                throw '您无权查看该数据';
+            }
+            return stage;
+        }
+
+        async _getStageData(tid, sorder) {
+            console.log(tid);
+            const data = await this._getTzData(tid, true);
+            const stage = await this._checkStage(tid, sorder);
+            const bills = await this.ctx.service.stageBills.getAuditorStageData(tid, stage.id, stage.curTimes, stage.curOrder);
+            const pos = await this.ctx.service.stagePos.getAuditorStageData(tid, stage.id, stage.curTimes, stage.curOrder);
+            data.stage = {
+                sid: stage.id, sorder: stage.order, curTimes: stage.curTimes, curOrder: stage.curOrder,
+                bills: bills, pos: pos
+            };
+            return data;
+        }
 
         /**
          * 台账 对比 页面
@@ -35,32 +110,38 @@ module.exports = app => {
             try {
                 const renderData = {
                     jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.compare.tz)
-                }
+                };
                 await this.layout('spss/compare_tz.ejs', renderData, 'spss/compare_tz_modal.ejs');
             } catch (err) {
                 ctx.helper.log(err);
             }
         }
-
+        /**
+         * 获取 台账 对比 数据(Ajax)
+         * @param ctx
+         * @returns {Promise<void>}
+         */
         async loadCompareTz(ctx) {
             try {
                 const data = JSON.parse(ctx.request.body.data);
                 const responseData = {err: 0, msg: '', data: {}};
 
-                const tender1 = await ctx.service.tender.getTender(data.tid1);
-                responseData.data.tender1 = {
-                    name: tender1.name,
-                    bills: await ctx.service.ledger.getData(data.tid1),
-                    pos: tender1.measure_type === measureType.tz.value 
-                        ? await ctx.service.pos.getPosData({tid: data.tid1}) : []             
-                };
-                const tender2 = await ctx.service.tender.getTender(data.tid2);
-                responseData.data.tender2 = {
-                    name: tender2.name,
-                    bills: await ctx.service.ledger.getData(data.tid2),
-                    pos: tender2.measure_type === measureType.tz.value 
-                        ? await ctx.service.pos.getPosData({tid: data.tid2}) : []             
-                };
+                // const tender1 = await ctx.service.tender.getTender(data.tid1);
+                // responseData.data.tender1 = {
+                //     name: tender1.name,
+                //     bills: await ctx.service.ledger.getData(data.tid1),
+                //     pos: tender1.measure_type === measureType.tz.value
+                //         ? await ctx.service.pos.getPosData({tid: data.tid1}) : []
+                // };
+                // const tender2 = await ctx.service.tender.getTender(data.tid2);
+                // responseData.data.tender2 = {
+                //     name: tender2.name,
+                //     bills: await ctx.service.ledger.getData(data.tid2),
+                //     pos: tender2.measure_type === measureType.tz.value
+                //         ? await ctx.service.pos.getPosData({tid: data.tid2}) : []
+                // };
+                responseData.data.tender1 = await this._getTzData(data.tid1);
+                responseData.data.tender2 = await this._getTzData(data.tid2);
                 ctx.body = responseData;
             } catch (err) {
                 ctx.helper.log(err);
@@ -77,11 +158,32 @@ module.exports = app => {
          */
         async compareStage(ctx) {
             try {
-                await this.layout('spss/compare_stage.ejs', {});
+                const renderData = {
+                    jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.compare.stage)
+                };
+                await this.layout('spss/compare_tz.ejs', renderData, 'spss/compare_stage_modal.ejs');
             } catch (err) {
                 ctx.helper.log(err);
             }
         }
+        /**
+         * 获取 期计量 对比 数据(Ajax)
+         * @param ctx
+         * @returns {Promise<void>}
+         */
+        async loadCompareStage(ctx) {
+            try {
+                const data = JSON.parse(ctx.request.body.data);
+                console.log(data);
+                const responseData = {err: 0, msg: '', data: {}};
+                responseData.data.tender1 = await this._getStageData(data.tid1, data.sorder1);
+                responseData.data.tender2 = await this._getStageData(data.tid2, data.sorder2);
+                ctx.body = responseData;
+            } catch (err) {
+                ctx.helper.log(err);
+                ctx.body = this.ajaxErrorBody(err, '查询数据错误');
+            }
+        }
 
 
         /**
@@ -92,12 +194,33 @@ module.exports = app => {
          */
         async gatherTz(ctx) {
             try {
-                await this.layout('spss/compare_stage.ejs', {});
+                const renderData = {
+                    jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.gather.tz)
+                };
+                await this.layout('spss/compare_tz.ejs', renderData, 'spss/gather_tz_modal.ejs');
             } catch (err) {
                 ctx.helper.log(err);
             }
         }
+        /**
+         * 获取 台账 汇总 数据(Ajax)
+         * @param ctx
+         * @returns {Promise<void>}
+         */
+        async loadGatherTz(ctx) {
+            try {
+                const data = JSON.parse(ctx.request.body.data);
+                const responseData = {err: 0, msg: '', data: []};
 
+                for (const t of data.tenders) {
+                    responseData.data.push(await this._getTzData(t.id));
+                }
+                ctx.body = responseData;
+            } catch (err) {
+                ctx.helper.log(err);
+                ctx.body = this.ajaxErrorBody(err, '查询数据错误');
+            }
+        }
 
         /**
          * 期计量 汇总 页面
@@ -107,11 +230,33 @@ module.exports = app => {
          */
         async gatherStage(ctx) {
             try {
-                await this.layout('spss/compare_stage.ejs', {});
+                const renderData = {
+                    jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.gather.stage)
+                };
+                await this.layout('spss/compare_tz.ejs', renderData, 'spss/gather_stage_modal.ejs');
             } catch (err) {
                 ctx.helper.log(err);
             }
         }
+        /**
+         * 获取 期计量 汇总 数据(Ajax)
+         * @param ctx
+         * @returns {Promise<void>}
+         */
+        async loadGatherStage(ctx) {
+            try {
+                const data = JSON.parse(ctx.request.body.data);
+                const responseData = {err: 0, msg: '', data: []};
+
+                for (const t of data.tenders) {
+                    responseData.data.push(await this._getStageData(t.id, t.sorder));
+                }
+                ctx.body = responseData;
+            } catch (err) {
+                ctx.helper.log(err);
+                ctx.body = this.ajaxErrorBody(err, '查询数据错误');
+            }
+        }
     }
 
     return SpssController;

+ 4 - 0
app/controller/stage_controller.js

@@ -1521,6 +1521,10 @@ module.exports = app => {
 
             ctx.body = responseData;
         }
+
+        async differ(ctx) {
+
+        }
     }
 
     return StageController;

+ 9 - 4
app/controller/stage_extra_controller.js

@@ -7,6 +7,8 @@
  * @date 2020/2/24
  * @version
  */
+const auditConst = require('../const/audit').stage;
+
 module.exports = app => {
 
     class StageExtraController extends app.BaseController {
@@ -100,8 +102,11 @@ module.exports = app => {
          */
         async loadBonus (ctx) {
             try {
-                const data = await ctx.service.stageBonus.getEndStageData(ctx.stage.order);
-                ctx.body = {err: 0, msg: '', data: data};
+                const data = await ctx.service.stageBonus.getStageData(ctx.stage.id);
+                console.log(data);
+                const preData = await ctx.service.stageBonus.getPreStageData(ctx.stage.order);
+                console.log(preData);
+                ctx.body = {err: 0, msg: '', data: data.concat(preData)};
             } catch (error) {
                 ctx.helper.log(error);
                 ctx.body = this.ajaxErrorBody(error, '获取甲供材料数据失败,请刷新');
@@ -157,7 +162,7 @@ module.exports = app => {
                 ctx.body = {err: 0, msg: '', data: data};
             } catch (error) {
                 ctx.helper.log(error);
-                ctx.body = this.ajaxErrorBody(error, '获取甲供材料数据失败,请刷新');
+                ctx.body = this.ajaxErrorBody(error, '获取数据失败,请刷新');
             }
 
         }
@@ -174,7 +179,7 @@ module.exports = app => {
                 ctx.body = { err: 0, msg: '', data: result };
             } catch (error) {
                 ctx.helper.log(error);
-                ctx.body = this.ajaxErrorBody(error, '提交甲供材料数据失败,请重试');
+                ctx.body = this.ajaxErrorBody(error, '提交数据失败,请重试');
             }
         }
     }

+ 53 - 0
app/lib/rpt_data_analysis.js

@@ -830,6 +830,58 @@ const addSumChapter = {
         });
     }
 };
+const auditSelect = {
+    name: '审批人选择',
+    hint: '需搭配用户交互--审批人选择一起使用',
+    defaultSetting: {
+        table: ['mem_stage_bills_compare'],
+    },
+    _stageBillsCompare(data, order) {
+        const fields = [];
+        for (const [i, o] of order.entries()) {
+            const sPrefix = 'r' + o + '_';
+            const tPrefix = 'as' + i + '_';
+            fields.push({source: sPrefix + 'contract_qty', target: tPrefix + 'contract_qty'});
+            fields.push({source: sPrefix + 'contract_tp', target: tPrefix + 'contract_tp'});
+            fields.push({source: sPrefix + 'qc_qty', target: tPrefix + 'qc_qty'});
+            fields.push({source: sPrefix + 'qc_tp', target: tPrefix + 'qc_tp'});
+            fields.push({source: sPrefix + 'gather_qty', target: tPrefix + 'gather_qty'});
+            fields.push({source: sPrefix + 'gather_tp', target: tPrefix + 'gather_tp'});
+        }
+        for (const d of data) {
+            for (const f of fields) {
+                d[f.target] = d[f.source];
+            }
+        }
+    },
+    fun: function (ctx, data, fieldsKey, options, csRela) {
+        if (!ctx.tender || !ctx.stage) return;
+        if (!csRela.tplDefine) return;
+        const asDefine = csRela.tplDefine.audit_select;
+        if (!asDefine || !asDefine.enable || !asDefine.setting || asDefine.setting === '') return;
+        const asCustom = csRela.cDefine ? csRela.cDefine.audit_select : null;
+
+        const order = [];
+        if (asCustom) {
+            for (const asc of asCustom) {
+                order.push(asc.order);
+            }
+        } else {
+            const setting = JSON.stringify(asDefine.setting);
+            for (const [i, s] of setting.select.entries) {
+                order.push(i);
+            }
+        }
+
+        for (const t of options.table) {
+            switch (t) {
+                case 'mem_stage_bills_compare':
+                    this._stageBillsCompare(data[t], order);
+                    break;
+            }
+        }
+    }
+};
 
 const analysisObj = {
     changeSort,
@@ -842,6 +894,7 @@ const analysisObj = {
     union,
     gatherStagePay,
     addSumChapter,
+    auditSelect,
 };
 const analysisDefine = (function (obj) {
     const result = [];

+ 2 - 2
app/lib/stage_im.js

@@ -516,9 +516,9 @@ class StageIm {
                 this._checkCustomDetail(im);
                 this.ImData.push(im);
             }
-            if (!this.ctx.stage.im_gather || !node.check) {
+            //if (!this.ctx.stage.im_gather || !node.check) {
                 this._generateZlLeafXmjData(p, im, 'gather_qty');
-            }
+            //}
             this._generateZlChangeData(p, im);
             im.quantity = this.ctx.helper.add(im.quantity, p.quantity);
 

binární
app/public/deal_bills/签约清单导入格式.xls


binární
app/public/files/template/ledger/导入工程量清单EXCEL格式.xls


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

@@ -211,7 +211,7 @@ $(document).ready(() => {
     });
 
     // 保存修改ajax提交(不刷新页面)
-    $('#save_change').on('click', function () {
+    $('.save_change_btn').on('click', function () {
         // 保存修改modal
         $('#changeStatus').val(2);
         // 获取auditlist并填入input中

+ 237 - 0
app/public/js/compare_stage.js

@@ -0,0 +1,237 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date
+ * @version
+ */
+
+$(document).ready(function () {
+    autoFlashHeight();
+    // 根据设置整理Spread设置
+    const ledgerSpreadSetting = {
+        cols: [
+            { title: '项目节编号', colSpan: '1', rowSpan: '2', field: 'code', hAlign: 0, width: 150, formatter: '@', cellType: 'tree' },
+            { title: '清单编号', colSpan: '1', rowSpan: '2', field: 'b_code', hAlign: 0, width: 80, formatter: '@' },
+            { title: '名称', colSpan: '1', rowSpan: '2', field: 'name', hAlign: 0, width: 230, formatter: '@' },
+            { title: '单位', colSpan: '1', rowSpan: '2', field: 'unit', hAlign: 1, width: 50, formatter: '@', cellType: 'unit' },
+            { title: '单价', colSpan: '1', rowSpan: '2', field: 'unit_price', hAlign: 2, width: 60, type: 'Number' },
+            { title: '标段1|数量', colSpan: '2|1', rowSpan: '1|1', field: 'qty_1', hAlign: 2, width: 60, type: 'Number' },
+            { title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'tp_1', hAlign: 2, width: 60, type: 'Number' },
+            { title: '标段2|数量', colSpan: '2|1', rowSpan: '1|1', field: 'qty_2', hAlign: 2, width: 60, type: 'Number' },
+            { title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'tp_2', hAlign: 2, width: 60, type: 'Number' },
+            { title: '标段1-标段2|数量', colSpan: '2|1', rowSpan: '1|1', field: 'qty_differ', hAlign: 2, width: 60, type: 'Number' },
+            { title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'tp_differ', hAlign: 2, width: 60, type: 'Number' },
+        ],
+        emptyRows: 2,
+        headRows: 2,
+        headRowHeight: [35, 25],
+        defaultRowHeight: 21,
+        headerFont: '12px 微软雅黑',
+        font: '12px 微软雅黑',
+        readOnly: true,
+        getColor: function (sheet, data, row, col, defaultColor) {
+            function checkDiffer(data) {
+                return !checkZero(data.qty_differ) && !checkZero(data.tp_differ);
+            }
+            return data && checkDiffer(data) ? '#F2DEDE' : defaultColor;
+        }
+    };
+    const posSpreadSetting = {
+        cols: [
+            { title: '计量单元', colSpan: '1', rowSpan: '2', field: 'name', hAlign: 0, width: 230, formatter: '@' },
+            { title: '数量|标段1', colSpan: '3|1', rowSpan: '1|1', field: 'qty_1', hAlign: 2, width: 80, type: 'Number' },
+            { title: '|标段2', colSpan: '|1', rowSpan: '|1', field: 'qty_2', hAlign: 2, width: 80, type: 'Number' },
+            { title: '|标段1-标段2', colSpan: '|1', rowSpan: '|1', field: 'qty_differ', hAlign: 2, width: 80, type: 'Number' },
+        ],
+        emptyRows: 3,
+        headRows: 2,
+        headRowHeight: [25, 25],
+        defaultRowHeight: 21,
+        headerFont: '12px 微软雅黑',
+        font: '12px 微软雅黑',
+        readOnly: true,
+        getColor: function (sheet, data, row, col, defaultColor) {
+            function checkDiffer(data) {
+                const fieldSufs = sheet.zh_setting.fieldSufs;
+                if (fieldSufs.length <= 1) return false;
+                const base = data['gather_qty' + fieldSufs[0]];
+                for (let i = 1; i < fieldSufs.length; i++) {
+                    const compare = data['gather_qty' + fieldSufs[i]];
+                    if ((base || compare) && (compare !== base)) return true;
+                }
+            }
+            return checkDiffer(data) ? '#F2DEDE' : defaultColor;
+        }
+    };
+    // 初始化台账
+    const billsSpread = SpreadJsObj.createNewSpread($('#bills-spread')[0]);
+    const billsSheet = billsSpread.getActiveSheet();
+    SpreadJsObj.initSheet(billsSheet, ledgerSpreadSetting);
+    // 初始化部位
+    const posSpread = SpreadJsObj.createNewSpread($('#pos-spread')[0]);
+    const posSheet = posSpread.getActiveSheet();
+    SpreadJsObj.initSheet(posSheet, posSpreadSetting);
+
+    const billsTree = createNewPathTree('compare', {
+        id: 'id',
+        pid: 'pid',
+        order: 'order',
+        level: 'level',
+        rootId: -1,
+        loadInfo1: function (node, source) {
+            node.qty_1 = ZhCalc.add(source.contract_qty, source.qc_qty);
+            node.tp_1 = ZhCalc.add(source.contract_tp, source.qc_tp);
+        },
+        loadInfo2: function (node, source) {
+            node.qty_2 = ZhCalc.add(source.contract_qty, source.qc_qty);
+            node.tp_2 = ZhCalc.add(source.contract_tp, source.qc_tp);
+        },
+        calcDiffer: function (node) {
+            node.qty_differ = ZhCalc.sub(node.qty_1, node.qty_2);
+            node.tp_differ = ZhCalc.sub(node.tp_1, node.tp_2);
+        },
+    });
+
+    // // 获取部位明细数据
+    // function loadPosData(iRow) {
+    //     const node = billsSheet.zh_tree.nodes[iRow];
+    //     if (node) {
+    //         SpreadJsObj.loadSheetData(posSheet, SpreadJsObj.DataType.Data, node.pos);
+    //     } else {
+    //         SpreadJsObj.loadSheetData(posSheet, SpreadJsObj.DataType.Data, []);
+    //     }
+    //     SpreadJsObj.resetTopAndSelect(posSheet);
+    // }
+    // // 切换清单行,读取所属项目节数据
+    // billsSheet.bind(spreadNS.Events.SelectionChanged, function (e, info) {
+    //     if (info.newSelections) {
+    //         const iNewRow = info.newSelections[0].row;
+    //         if (info.oldSelections) {
+    //             const iOldRow = info.oldSelections[0].row;
+    //             if (iNewRow !== iOldRow) {
+    //                 loadPosData(iNewRow);
+    //             }
+    //         } else {
+    //             loadPosData(iNewRow);
+    //         }
+    //     }
+    // });
+
+    // 选择
+    $('#select-ok').click(function () {
+        let data = {};
+        try {
+            data.tid1 = parseInt($('input[name=id1]').val());
+            data.sorder1 = parseInt($('input[name=sorder1]').val());
+            data.tid2 = parseInt($('input[name=id2]').val());
+            data.sorder2 = parseInt($('input[name=sorder2]').val());
+
+            if (data.tid1 > 0 && data.sorder1 > 0 && data.tid2 > 0 && data.sorder2 > 0) {
+                postData(window.location.pathname + '/load', data, function (result) {
+                    // 初始化基础数据分析
+                    const tenderTreeSetting = {
+                        id: 'ledger_id',
+                        pid: 'ledger_pid',
+                        order: 'order',
+                        level: 'level',
+                        rootId: -1,
+                        keys: ['id', 'tender_id', 'ledger_id'],
+                        calcFields: ['deal_tp', 'sgfh_tp', 'sjcl_tp', 'qtcl_tp', 'total_price', 'contract_tp', 'qc_tp'],
+                        stageId: 'id',
+                        updateFields: ['contract_qty', 'contract_tp', 'qc_qty', 'qc_tp'],
+                    };
+                    const tender1 = {
+                        billsTree: createNewPathTree('stage', tenderTreeSetting),
+                    };
+                    tender1.billsTree.loadDatas(result.tender1.bills);
+                    tender1.billsTree.loadCurStageData(result.tender1.stage.bills);
+                    treeCalc.calculateAll(tender1.billsTree);
+                    const col1 = ledgerSpreadSetting.cols.find(function (x) {
+                        return x.field === 'qty_1';
+                    });
+                    col1.title = result.tender1.name + '\n' + '第' + result.tender1.stage.sorder + '期' + '|数量';
+
+                    const tender2 = {
+                        billsTree: createNewPathTree('stage', tenderTreeSetting),
+                    };
+                    tender2.billsTree.loadDatas(result.tender2.bills);
+                    tender2.billsTree.loadCurStageData(result.tender2.stage.bills);
+                    treeCalc.calculateAll(tender2.billsTree);
+                    const col2 = ledgerSpreadSetting.cols.find(function (x) {
+                        return x.field === 'qty_2';
+                    });
+                    col2.title = result.tender2.name + '\n' + '第' + result.tender2.stage.sorder + '期' + '|数量';
+
+                    SpreadJsObj.initSheet(billsSheet, ledgerSpreadSetting);
+                    billsTree.loadCompareData(tender1, tender2);
+                    SpreadJsObj.loadSheetData(billsSheet, SpreadJsObj.DataType.Tree, billsTree);
+                    $('#data-select').modal('hide');
+                });
+            }
+        } catch (err) {
+            toastr.error('输入的标段ID非法');
+        }
+    });
+    // 显示层次
+    (function (select, sheet) {
+        $(select).click(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;
+            }
+        });
+    })('a[name=showLevel]', billsSheet);
+    // 导航栏
+    $.subMenu({
+        menu: '#sub-menu', miniMenu: '#sub-mini-menu', miniMenuList: '#mini-menu-list',
+        toMenu: '#to-menu', toMiniMenu: '#to-mini-menu',
+        key: 'menu.1.0.0',
+        miniHint: '#sub-mini-hint', hintKey: 'menu.hint.1.0.1',
+        callback: function (info) {
+            if (info.mini) {
+                $('.panel-title').addClass('fluid');
+                $('#sub-menu').removeClass('panel-sidebar');
+            } else {
+                $('.panel-title').removeClass('fluid');
+                $('#sub-menu').addClass('panel-sidebar');
+            }
+            autoFlashHeight();
+            billsSpread.refresh();
+            posSpread.refresh();
+        }
+    });
+    // 上下窗口resizer
+    $.divResizer({
+        select: '#main-resize',
+        callback: function () {
+            billsSpread.refresh();
+            let bcontent = $(".bcontent-wrap").length > 0 ? $(".bcontent-wrap").height() : 0;
+            $(".sp-wrap").height(bcontent - 30);
+            posSpread.refresh();
+        }
+    });
+});

+ 5 - 5
app/public/js/compare_tz.js

@@ -38,7 +38,7 @@ $(document).ready(function () {
             }
             return data && checkDiffer(data) ? '#F2DEDE' : defaultColor;
         }
-    }
+    };
     const posSpreadSetting = {
         cols: [
             { title: '计量单元', colSpan: '1', rowSpan: '2', field: 'name', hAlign: 0, width: 230, formatter: '@' },
@@ -140,7 +140,7 @@ $(document).ready(function () {
     // });
 
     // 选择
-    $('#compare-ok').click(function () {
+    $('#select-ok').click(function () {
         let id1, id2;
         try {
             id1 = parseInt($('input[name=id1]').val());
@@ -165,7 +165,7 @@ $(document).ready(function () {
                     treeCalc.calculateAll(tender1.billsTree);
                     const col1 = ledgerSpreadSetting.cols.find(function (x) {
                         return x.field === 'qty_1';
-                    })
+                    });
                     col1.title = result.tender1.name + '|数量';
 
                     const tender2 = {
@@ -175,13 +175,13 @@ $(document).ready(function () {
                     treeCalc.calculateAll(tender2.billsTree);
                     const col2 = ledgerSpreadSetting.cols.find(function (x) {
                         return x.field === 'qty_2';
-                    })
+                    });
                     col2.title = result.tender2.name + '|数量';
                     
                     SpreadJsObj.initSheet(billsSheet, ledgerSpreadSetting);
                     billsTree.loadCompareData(tender1, tender2);
                     SpreadJsObj.loadSheetData(billsSheet, SpreadJsObj.DataType.Tree, billsTree);
-                    $('#compare-select').modal('hide');
+                    $('#data-select').modal('hide');
                 });
             }
         } catch (err) {

+ 283 - 0
app/public/js/gather_stage.js

@@ -0,0 +1,283 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date
+ * @version
+ */
+
+function initSpreadSetting(settings, gatherTender) {
+    function setSpreadSettingCols(setting, fieldSufs, Roles) {
+        function addExtraCols(fieldSuf, Role) {
+            for (const ec of setting.extraCols) {
+                const col = JSON.parse(JSON.stringify(ec));
+                col.title = _.replace(col.title, '%s', Role);
+                col.field = _.replace(col.field, '%s', fieldSuf);
+                setting.cols.push(col);
+            }
+        }
+        setting.cols = [];
+        for (const col of setting.baseCols) {
+            setting.cols.push(col);
+        }
+        for (const index in fieldSufs) {
+            addExtraCols(fieldSufs[index], Roles[index]);
+        }
+    }
+    const fieldSufs = [], names = [];
+    for (let [i, t] of gatherTender.entries()) {
+        fieldSufs.push((i + 1) + '');
+        names.push(t.name);
+    }
+    fieldSufs.push('sum');
+    names.push('汇总');
+    for (const s of settings) {
+        s.fieldSufs = fieldSufs;
+        setSpreadSettingCols(s, fieldSufs, names);
+    }
+}
+
+$(document).ready(function () {
+    autoFlashHeight();
+    // 根据设置整理Spread设置
+    const ledgerSpreadSetting = {
+        baseCols: [
+            { title: '项目节编号', colSpan: '1', rowSpan: '2', field: 'code', hAlign: 0, width: 150, formatter: '@', cellType: 'tree' },
+            { title: '清单编号', colSpan: '1', rowSpan: '2', field: 'b_code', hAlign: 0, width: 80, formatter: '@' },
+            { title: '名称', colSpan: '1', rowSpan: '2', field: 'name', hAlign: 0, width: 230, formatter: '@' },
+            { title: '单位', colSpan: '1', rowSpan: '2', field: 'unit', hAlign: 1, width: 50, formatter: '@', cellType: 'unit' },
+            { title: '单价', colSpan: '1', rowSpan: '2', field: 'unit_price', hAlign: 2, width: 60, type: 'Number' },
+        ],
+        extraCols: [
+            { title: '%s|数量', colSpan: '2|1', rowSpan: '1|1', field: 'qty_%s', hAlign: 2, width: 60, type: 'Number' },
+            { title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'tp_%s', hAlign: 2, width: 60, type: 'Number' },
+        ],
+        emptyRows: 2,
+        headRows: 2,
+        headRowHeight: [35, 25],
+        defaultRowHeight: 21,
+        headerFont: '12px 微软雅黑',
+        font: '12px 微软雅黑',
+        readOnly: true,
+        getColor: function (sheet, data, row, col, defaultColor) {
+            function checkDiffer(data) {
+                return !checkZero(data.qty_differ) && !checkZero(data.tp_differ);
+            }
+            return data && checkDiffer(data) ? '#F2DEDE' : defaultColor;
+        }
+    };
+    const posSpreadSetting = {
+        baseCols: [
+            { title: '计量单元', colSpan: '1', rowSpan: '2', field: 'name', hAlign: 0, width: 230, formatter: '@' },
+        ],
+        extraCols: [
+            { title: '%s|数量', colSpan: '1|1', rowSpan: '1|1', field: 'qty_%s', hAlign: 2, width: 80, type: 'Number' },
+        ],
+        emptyRows: 3,
+        headRows: 2,
+        headRowHeight: [25, 25],
+        defaultRowHeight: 21,
+        headerFont: '12px 微软雅黑',
+        font: '12px 微软雅黑',
+        readOnly: true,
+        getColor: function (sheet, data, row, col, defaultColor) {
+            function checkDiffer(data) {
+                const fieldSufs = sheet.zh_setting.fieldSufs;
+                if (fieldSufs.length <= 1) return false;
+                const base = data['gather_qty' + fieldSufs[0]];
+                for (let i = 1; i < fieldSufs.length; i++) {
+                    const compare = data['gather_qty' + fieldSufs[i]];
+                    if ((base || compare) && (compare !== base)) return true;
+                }
+            }
+            return checkDiffer(data) ? '#F2DEDE' : defaultColor;
+        }
+    };
+    initSpreadSetting([ledgerSpreadSetting, posSpreadSetting], []);
+    // 初始化台账
+    const billsSpread = SpreadJsObj.createNewSpread($('#bills-spread')[0]);
+    const billsSheet = billsSpread.getActiveSheet();
+    SpreadJsObj.initSheet(billsSheet, ledgerSpreadSetting);
+    // 初始化部位
+    const posSpread = SpreadJsObj.createNewSpread($('#pos-spread')[0]);
+    const posSheet = posSpread.getActiveSheet();
+    SpreadJsObj.initSheet(posSheet, posSpreadSetting);
+
+    const billsTree = createNewPathTree('tree-gather', {
+        id: 'id',
+        pid: 'pid',
+        order: 'order',
+        level: 'level',
+        rootId: -1,
+        loadInfo: function (node, source, index) {
+            const endfix = '_' + index;
+            node['qty' + endfix] = ZhCalc.add(source.contract_qty, source.qc_qty);
+            node['tp' + endfix] = ZhCalc.add(source.contract_tp, source.qc_tp);
+        },
+        calcSum: function (node, count) {
+            node.qty_sum = 0;
+            node.tp_sum = 0;
+            for (let i = 1; i<= count; i++) {
+                node.qty_sum = ZhCalc.add(node.qty_sum, node['qty_' + i]);
+                node.tp_sum = ZhCalc.add(node.tp_sum, node['tp_' + 1]);
+            }
+        },
+    });
+
+    // // 获取部位明细数据
+    // function loadPosData(iRow) {
+    //     const node = billsSheet.zh_tree.nodes[iRow];
+    //     if (node) {
+    //         SpreadJsObj.loadSheetData(posSheet, SpreadJsObj.DataType.Data, node.pos);
+    //     } else {
+    //         SpreadJsObj.loadSheetData(posSheet, SpreadJsObj.DataType.Data, []);
+    //     }
+    //     SpreadJsObj.resetTopAndSelect(posSheet);
+    // }
+    // // 切换清单行,读取所属项目节数据
+    // billsSheet.bind(spreadNS.Events.SelectionChanged, function (e, info) {
+    //     if (info.newSelections) {
+    //         const iNewRow = info.newSelections[0].row;
+    //         if (info.oldSelections) {
+    //             const iOldRow = info.oldSelections[0].row;
+    //             if (iNewRow !== iOldRow) {
+    //                 loadPosData(iNewRow);
+    //             }
+    //         } else {
+    //             loadPosData(iNewRow);
+    //         }
+    //     }
+    // });
+
+    // 选择
+    $('#select-ok').click(function () {
+        let data = {tenders: []};
+        try {
+            const trs = $('tr[tid]');
+            for (const tr of trs) {
+                const id = parseInt($('td[tid]', tr).attr('tid'));
+                const sorder = parseInt($('td[sorder]', tr).attr('sorder'));
+                data.tenders.push({ id: id, sorder: sorder });
+            }
+            if (data.tenders.length === 0) return;
+
+            postData(window.location.pathname + '/load', data, function (result) {
+                initSpreadSetting([ledgerSpreadSetting, posSpreadSetting], result);
+                SpreadJsObj.initSheet(billsSheet, ledgerSpreadSetting);
+                const tenderDatas = [];
+                const tenderTreeSetting = {
+                    id: 'ledger_id',
+                    pid: 'ledger_pid',
+                    order: 'order',
+                    level: 'level',
+                    rootId: -1,
+                    keys: ['id', 'tender_id', 'ledger_id'],
+                    calcFields: ['deal_tp', 'sgfh_tp', 'sjcl_tp', 'qtcl_tp', 'total_price', 'contract_tp', 'qc_tp'],
+                    stageId: 'id',
+                    updateFields: ['contract_qty', 'contract_tp', 'qc_qty', 'qc_tp'],
+                };
+                for (const [i, tender] of result.entries()) {
+                    const tenderData = {
+                        billsTree: createNewPathTree('stage', tenderTreeSetting),
+                    };
+                    tenderData.billsTree.loadDatas(tender.bills);
+                    tenderData.billsTree.loadCurStageData(tender.stage.bills);
+                    treeCalc.calculateAll(tenderData.billsTree);
+                    tenderDatas.push(tenderData);
+                }
+                billsTree.loadGatherData(tenderDatas);
+                SpreadJsObj.loadSheetData(billsSheet, SpreadJsObj.DataType.Tree, billsTree);
+                $('#data-select').modal('hide');
+            });
+        } catch (err) {
+            toastr.error('输入的标段ID非法');
+        }
+    });
+    // 显示层次
+    (function (select, sheet) {
+        $(select).click(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;
+            }
+        });
+    })('a[name=showLevel]', billsSheet);
+    // 导航栏
+    $.subMenu({
+        menu: '#sub-menu', miniMenu: '#sub-mini-menu', miniMenuList: '#mini-menu-list',
+        toMenu: '#to-menu', toMiniMenu: '#to-mini-menu',
+        key: 'menu.1.0.0',
+        miniHint: '#sub-mini-hint', hintKey: 'menu.hint.1.0.1',
+        callback: function (info) {
+            if (info.mini) {
+                $('.panel-title').addClass('fluid');
+                $('#sub-menu').removeClass('panel-sidebar');
+            } else {
+                $('.panel-title').removeClass('fluid');
+                $('#sub-menu').addClass('panel-sidebar');
+            }
+            autoFlashHeight();
+            billsSpread.refresh();
+            posSpread.refresh();
+        }
+    });
+    // 上下窗口resizer
+    $.divResizer({
+        select: '#main-resize',
+        callback: function () {
+            billsSpread.refresh();
+            let bcontent = $(".bcontent-wrap").length > 0 ? $(".bcontent-wrap").height() : 0;
+            $(".sp-wrap").height(bcontent - 30);
+            posSpread.refresh();
+        }
+    });
+    $('#add-t').click(function () {
+        const id = $('input[name=tid]').val();
+        if (!id || id === '') {
+            toastr.warning('请先输入标段id,再添加');
+            return;
+        }
+        const tr = $('tr[tid=' + id + ']');
+        if (tr && tr.length > 0) {
+            toastr.warning('请勿重复添加同一个标段');
+            return;
+        }
+        const sorder = $('input[name= sorder]').val();
+        if (!sorder || sorder === '') {
+            toastr.warning('请先输入期序号,再添加');
+            return;
+        }
+        const html = '<tr tid="' + id + '">' +
+            '<td tid="' + id + '">' + id + '</td>' +
+            '<td sorder="' + sorder + '">' + sorder + '</td>' +
+            '<td><a href="javascript: void(0);" name="delete-t" class="btn btn-sm" data-toggle="tooltip" data-placement="bottom" title="" data-original-title="删除"><i class="fa fa-remove text-danger" aria-hidden="true"></i></a></td>' +
+            '</tr>';
+        $('tbody').append(html);
+    });
+    $('body').on('click', 'a[name=delete-t]', function () {
+        $(this).parent().parent().remove();
+    });
+});

+ 269 - 0
app/public/js/gather_tz.js

@@ -0,0 +1,269 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date
+ * @version
+ */
+
+function initSpreadSetting(settings, gatherTender) {
+    function setSpreadSettingCols(setting, fieldSufs, Roles) {
+        function addExtraCols(fieldSuf, Role) {
+            for (const ec of setting.extraCols) {
+                const col = JSON.parse(JSON.stringify(ec));
+                col.title = _.replace(col.title, '%s', Role);
+                col.field = _.replace(col.field, '%s', fieldSuf);
+                setting.cols.push(col);
+            }
+        }
+        setting.cols = [];
+        for (const col of setting.baseCols) {
+            setting.cols.push(col);
+        }
+        for (const index in fieldSufs) {
+            addExtraCols(fieldSufs[index], Roles[index]);
+        }
+    }
+    const fieldSufs = [], names = [];
+    for (let [i, t] of gatherTender.entries()) {
+        fieldSufs.push((i + 1) + '');
+        names.push(t.name);
+    }
+    fieldSufs.push('s');
+    names.push('汇总');
+    for (const s of settings) {
+        s.fieldSufs = fieldSufs;
+        setSpreadSettingCols(s, fieldSufs, names);
+    }
+}
+
+$(document).ready(function () {
+    autoFlashHeight();
+    // 根据设置整理Spread设置
+    const ledgerSpreadSetting = {
+        baseCols: [
+            { title: '项目节编号', colSpan: '1', rowSpan: '2', field: 'code', hAlign: 0, width: 150, formatter: '@', cellType: 'tree' },
+            { title: '清单编号', colSpan: '1', rowSpan: '2', field: 'b_code', hAlign: 0, width: 80, formatter: '@' },
+            { title: '名称', colSpan: '1', rowSpan: '2', field: 'name', hAlign: 0, width: 230, formatter: '@' },
+            { title: '单位', colSpan: '1', rowSpan: '2', field: 'unit', hAlign: 1, width: 50, formatter: '@', cellType: 'unit' },
+            { title: '单价', colSpan: '1', rowSpan: '2', field: 'unit_price', hAlign: 2, width: 60, type: 'Number' },
+        ],
+        extraCols: [
+            { title: '%s|数量', colSpan: '2|1', rowSpan: '1|1', field: 'qty_%s', hAlign: 2, width: 60, type: 'Number' },
+            { title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'tp_%s', hAlign: 2, width: 60, type: 'Number' },
+        ],
+        emptyRows: 2,
+        headRows: 2,
+        headRowHeight: [35, 25],
+        defaultRowHeight: 21,
+        headerFont: '12px 微软雅黑',
+        font: '12px 微软雅黑',
+        readOnly: true,
+        getColor: function (sheet, data, row, col, defaultColor) {
+            function checkDiffer(data) {
+                return !checkZero(data.qty_differ) && !checkZero(data.tp_differ);
+            }
+            return data && checkDiffer(data) ? '#F2DEDE' : defaultColor;
+        }
+    };
+    const posSpreadSetting = {
+        baseCols: [
+            { title: '计量单元', colSpan: '1', rowSpan: '2', field: 'name', hAlign: 0, width: 230, formatter: '@' },
+        ],
+        extraCols: [
+            { title: '%s|数量', colSpan: '1|1', rowSpan: '1|1', field: 'qty_%s', hAlign: 2, width: 80, type: 'Number' },
+        ],
+        emptyRows: 3,
+        headRows: 2,
+        headRowHeight: [25, 25],
+        defaultRowHeight: 21,
+        headerFont: '12px 微软雅黑',
+        font: '12px 微软雅黑',
+        readOnly: true,
+        getColor: function (sheet, data, row, col, defaultColor) {
+            function checkDiffer(data) {
+                const fieldSufs = sheet.zh_setting.fieldSufs;
+                if (fieldSufs.length <= 1) return false;
+                const base = data['gather_qty' + fieldSufs[0]];
+                for (let i = 1; i < fieldSufs.length; i++) {
+                    const compare = data['gather_qty' + fieldSufs[i]];
+                    if ((base || compare) && (compare !== base)) return true;
+                }
+            }
+            return checkDiffer(data) ? '#F2DEDE' : defaultColor;
+        }
+    };
+    initSpreadSetting([ledgerSpreadSetting, posSpreadSetting], []);
+    // 初始化台账
+    const billsSpread = SpreadJsObj.createNewSpread($('#bills-spread')[0]);
+    const billsSheet = billsSpread.getActiveSheet();
+    SpreadJsObj.initSheet(billsSheet, ledgerSpreadSetting);
+    // 初始化部位
+    const posSpread = SpreadJsObj.createNewSpread($('#pos-spread')[0]);
+    const posSheet = posSpread.getActiveSheet();
+    SpreadJsObj.initSheet(posSheet, posSpreadSetting);
+
+    const billsTree = createNewPathTree('tree-gather', {
+        id: 'id',
+        pid: 'pid',
+        order: 'order',
+        level: 'level',
+        rootId: -1,
+        loadInfo: function (node, source, index) {
+            const endfix = '_' + index;
+            node['qty' + endfix] = source.quantity;
+            node['tp' + endfix] = source.total_price;
+        },
+        calcGather: function (node, count) {
+            node.qty_differ = 0;
+            for (let i = 1; i<= count; i++) {
+                node.qty_sum = ZhCalc.add(node.qty_sum, node['qty_' + i]);
+                node.tp_sum = ZhCalc.add(node.tp_sum, node['tp_' + 1]);
+            }
+        },
+    });
+
+    // // 获取部位明细数据
+    // function loadPosData(iRow) {
+    //     const node = billsSheet.zh_tree.nodes[iRow];
+    //     if (node) {
+    //         SpreadJsObj.loadSheetData(posSheet, SpreadJsObj.DataType.Data, node.pos);
+    //     } else {
+    //         SpreadJsObj.loadSheetData(posSheet, SpreadJsObj.DataType.Data, []);
+    //     }
+    //     SpreadJsObj.resetTopAndSelect(posSheet);
+    // }
+    // // 切换清单行,读取所属项目节数据
+    // billsSheet.bind(spreadNS.Events.SelectionChanged, function (e, info) {
+    //     if (info.newSelections) {
+    //         const iNewRow = info.newSelections[0].row;
+    //         if (info.oldSelections) {
+    //             const iOldRow = info.oldSelections[0].row;
+    //             if (iNewRow !== iOldRow) {
+    //                 loadPosData(iNewRow);
+    //             }
+    //         } else {
+    //             loadPosData(iNewRow);
+    //         }
+    //     }
+    // });
+
+    // 选择
+    $('#select-ok').click(function () {
+        let data = {tenders: []};
+        try {
+            const trs = $('tr[tid]');
+            for (const tr of trs) {
+                data.tenders.push({
+                    id: parseInt(tr.attributes['tid'].value)
+                });
+            }
+            if (data.tenders.length === 0) return;
+
+            postData(window.location.pathname + '/load', data, function (result) {
+                initSpreadSetting([ledgerSpreadSetting, posSpreadSetting], result);
+                SpreadJsObj.initSheet(billsSheet, ledgerSpreadSetting);
+                const tenderDatas = [];
+                const tenderTreeSetting = {
+                    id: 'ledger_id',
+                    pid: 'ledger_pid',
+                    order: 'order',
+                    level: 'level',
+                    rootId: -1,
+                    keys: ['id', 'tender_id', 'ledger_id'],
+                    calcFields: ['deal_tp', 'sgfh_tp', 'sjcl_tp', 'qtcl_tp', 'total_price'],
+                };
+                for (const [i, tender] of result.entries()) {
+                    const tenderData = {
+                        billsTree: createNewPathTree('ledger', tenderTreeSetting),
+                    };
+                    tenderData.billsTree.loadDatas(tender.bills);
+                    treeCalc.calculateAll(tenderData.billsTree);
+                    tenderDatas.push(tenderData);
+                }
+                billsTree.loadGatherData(tenderDatas);
+                SpreadJsObj.loadSheetData(billsSheet, SpreadJsObj.DataType.Tree, billsTree);
+                $('#data-select').modal('hide');
+            });
+        } catch (err) {
+            toastr.error('输入的标段ID非法');
+        }
+    });
+    // 显示层次
+    (function (select, sheet) {
+        $(select).click(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;
+            }
+        });
+    })('a[name=showLevel]', billsSheet);
+    // 导航栏
+    $.subMenu({
+        menu: '#sub-menu', miniMenu: '#sub-mini-menu', miniMenuList: '#mini-menu-list',
+        toMenu: '#to-menu', toMiniMenu: '#to-mini-menu',
+        key: 'menu.1.0.0',
+        miniHint: '#sub-mini-hint', hintKey: 'menu.hint.1.0.1',
+        callback: function (info) {
+            if (info.mini) {
+                $('.panel-title').addClass('fluid');
+                $('#sub-menu').removeClass('panel-sidebar');
+            } else {
+                $('.panel-title').removeClass('fluid');
+                $('#sub-menu').addClass('panel-sidebar');
+            }
+            autoFlashHeight();
+            billsSpread.refresh();
+            posSpread.refresh();
+        }
+    });
+    // 上下窗口resizer
+    $.divResizer({
+        select: '#main-resize',
+        callback: function () {
+            billsSpread.refresh();
+            let bcontent = $(".bcontent-wrap").length > 0 ? $(".bcontent-wrap").height() : 0;
+            $(".sp-wrap").height(bcontent - 30);
+            posSpread.refresh();
+        }
+    });
+    $('#add-t').click(function () {
+        const id = $('input[name=tid]').val();
+        const tr = $('tr[tid=' + id + ']');
+        if (tr && tr.length > 0) {
+            toastr.warning('请勿重复添加同一个标段');
+            return;
+        }
+        const html = '<tr tid="' + id + '">' +
+            '<td>' + id + '</td>' +
+            '<td><a href="javascript: void(0);" name="delete-t" class="btn btn-sm" data-toggle="tooltip" data-placement="bottom" title="" data-original-title="删除"><i class="fa fa-remove text-danger" aria-hidden="true"></i></a></td>' +
+            '</tr>';
+        $('tbody').append(html);
+    });
+    $('body').on('click', 'a[name=delete-t]', function () {
+        $(this).parent().parent().remove();
+    });
+});

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

@@ -3,6 +3,8 @@
  *
  * 构建pathTree
  * 可动态加载子节点,要求子节点获取接口按/xxx/get-children定义
+ *
+ * 所台账结构数据均用到该文件,请勿随意修改。
  * @param {Object} setting - 设置
  * @returns {PathTree}
  */
@@ -1446,6 +1448,90 @@ const createNewPathTree = function (type, setting) {
         }
     }
 
+    class TreeGatherTree extends FxTree {
+
+        constructor(setting) {
+            super(setting);
+            this._newId = 1;
+        }
+
+        get newId() {
+            return this._newId++;
+        }
+
+        loadGatherNode(node, parent, index, loadFun) {
+            const siblings = parent ? parent.children : this.children;
+            let cur = siblings.find(function (x) {
+                return node.b_code
+                    ? x.b_code === node.b_code && x.name === node.name && x.unit === node.unit && x.unit_price === node.unit_price
+                    : x.code === node.code && x.name === node.name;
+            });
+            if (!cur) {
+                const id = this.newId;
+                cur = {
+                    id: id,
+                    pid: parent ? parent.id : this.setting.rootId,
+                    full_path: parent ? parent.full_path + '-' + id : '' + id,
+                    level: parent ? parent.level + 1 : 1,
+                    order: siblings.length + 1,
+                    children: [],
+                    code: node.code, b_code: node.b_code, name: node.name,
+                    unit: node.unit, unit_price: node.unit_price,
+                };
+                siblings.push(cur);
+                this.datas.push(cur);
+            }
+            loadFun(cur, node, index);
+            for (const c of node.children) {
+                this.loadGatherNode(c, cur, index, loadFun);
+            }
+        }
+
+        generateSortNodes() {
+            const self = this;
+            const addSortNode = function (node) {
+                self.nodes.push(node);
+                for (const c of node.children) {
+                    addSortNode(c);
+                }
+            }
+            this.nodes = [];
+            for (const n of this.children) {
+                addSortNode(n);
+            }
+        }
+
+        loadGatherTree(data, index, loadFun) {
+            for (const c of data.billsTree.children) {
+                this.loadGatherNode(c, null, index, loadFun);
+            }
+            // todo load Pos Data;
+        }
+
+        calculateSum() {
+            if (this.setting.calcSum) {
+                for (const d of this.datas) {
+                    this.setting.calcSum(d, this.count);
+                }
+            }
+        }
+
+        loadGatherData(datas) {
+            this.count = datas.length;
+            for (const [i, data] of datas.entries()) {
+                this.loadGatherTree(data, i+1, this.setting.loadInfo);
+            }
+            for (const d of this.datas) {
+                d.is_leaf = d.children.length === 0;
+                d.expanded = true;
+                d.visible = true;
+                this.items[itemsPre + d[this.setting.id]] = d;
+            }
+            this.generateSortNodes();
+            this.calculateSum();
+        }
+    }
+
     if (type === 'base') {
         return new BaseTree(setting);
     } else if (type === 'fx') {
@@ -1466,6 +1552,8 @@ const createNewPathTree = function (type, setting) {
         return new GatherTree(setting);
     } else if (type === 'compare') {
         return new CompareTree(setting);
+    } else if (type === 'tree-gather') {
+        return new TreeGatherTree(setting);
     }
 };
 

+ 75 - 26
app/public/js/se_bonus.js

@@ -9,32 +9,81 @@
  */
 
 const isPre = function (data) {
-    return data.sid !== stageId;
-};
-const spreadSetting = {
-    cols: [
-        {title: '名称', colSpan: '1', rowSpan: '1', field: 'name', hAlign: 0, width: 235, formatter: '@', readOnly: isPre, },
-        {title: '金额', colSpan: '1', rowSpan: '1', field: 'tp', hAlign: 2, width: 100, type: 'Number', readOnly: isPre, },
-        {title: '时间', colSpan: '1', rowSpan: '1', field: 'real_time', hAlign: 1, width: 150, formatter: 'yyyy-MM-dd', /*cellType: 'datepicker', */readOnly: isPre, },
-        {title: '编号', colSpan: '1', rowSpan: '1', field: 'code', hAlign: 0, width: 150, formatter: '@', readOnly: isPre, },
-        {title: '依据材料证明', colSpan: '1', rowSpan: '1', field: 'proof', hAlign: 0, width: 180, formatter: '@', readOnly: isPre, },
-        {
-            title: '计量期', colSpan: '1', rowSpan: '1', field: 'sorder', hAlign: 1, width: 100, formatter: '@',
-            getValue: function (data) {
-                return '第' + data.sorder + '期';
-            }, readOnly: true,
-        },
-        {title: '备注', colSpan: '1', rowSpan: '1', field: 'memo', hAlign: 0, width: 180, formatter: '@', cellType: 'ellipsisAutoTip', readOnly: isPre, }
-    ],
-    emptyRows: 3,
-    headRows: 1,
-    headRowHeight: [32],
-    defaultRowHeight: 21,
-    headerFont: '12px 微软雅黑',
-    font: '12px 微软雅黑',
+    return data && data.sid !== stageId;
 };
 $(document).ready(() => {
     autoFlashHeight();
+
+    let datepicker;
+    const spreadSetting = {
+        cols: [
+            {title: '名称', colSpan: '1', rowSpan: '1', field: 'name', hAlign: 0, width: 235, formatter: '@', readOnly: isPre, },
+            {title: '金额', colSpan: '1', rowSpan: '1', field: 'tp', hAlign: 2, width: 100, type: 'Number', readOnly: isPre, },
+            {
+                title: '时间', colSpan: '1', rowSpan: '1', field: 'real_time', hAlign: 2, width: 150, readOnly: true,
+                formatter: 'yyyy-MM-dd', cellType: 'activeImageBtn', normalImg: '#ellipsis-icon', indent: 5,
+                showImage: function (data) {
+                    return data !== undefined && data !== null;
+                },
+            },
+            {title: '编号', colSpan: '1', rowSpan: '1', field: 'code', hAlign: 0, width: 150, formatter: '@', readOnly: isPre, },
+            {title: '依据材料证明', colSpan: '1', rowSpan: '1', field: 'proof', hAlign: 0, width: 180, formatter: '@', readOnly: isPre, },
+            {
+                title: '计量期', colSpan: '1', rowSpan: '1', field: 'sorder', hAlign: 1, width: 100, formatter: '@',
+                getValue: function (data) {
+                    return '第' + data.sorder + '期';
+                }, readOnly: true,
+            },
+            {title: '备注', colSpan: '1', rowSpan: '1', field: 'memo', hAlign: 0, width: 180, formatter: '@', cellType: 'ellipsisAutoTip', readOnly: isPre, }
+        ],
+        emptyRows: readOnly ? 0 : 3,
+        headRows: 1,
+        headRowHeight: [32],
+        defaultRowHeight: 21,
+        headerFont: '12px 微软雅黑',
+        font: '12px 微软雅黑',
+        readOnly: readOnly,
+        imageClick: function (data, hitinfo) {
+            if (!data || readOnly) return;
+
+            const setting = hitinfo.sheet.zh_setting;
+            if (!setting) return;
+            const col = setting.cols[hitinfo.col];
+            if (!col || col.field !== 'real_time') return;
+
+            const pos = SpreadJsObj.getObjPos(hitinfo.sheet.getParent().qo);
+            if (!datepicker) {
+                datepicker = $('.datepicker-here').datepicker({
+                    language: 'zh',
+                    dateFormat: 'yyyy-MM-dd',
+                    autoClose: true,
+                    onSelect: function (formattedDate, date, inst) {
+                        if (!inst.visible) return;
+                        const sels = hitinfo.sheet.getSelections();
+                        if (!sels || !sels[0]) return;
+                        const node = SpreadJsObj.getSelectObject(hitinfo.sheet);
+                        const uData = { update: {id: node.id, real_time: date} };
+
+                        postData(window.location.pathname + '/update', uData, function (result) {
+                            bonusObj.loadUpdateData(result);
+                            SpreadJsObj.reLoadRowData(hitinfo.sheet, sels[0].row);
+                        }, function () {
+                            SpreadJsObj.reLoadRowData(hitinfo.sheet, sels[0].row);
+                        });
+                    }
+                }).data('datepicker');
+            }
+            const value = hitinfo.sheet.getValue(hitinfo.row, hitinfo.col);
+            if (value) {
+                datepicker.selectDate(value);
+            } else {
+                datepicker.clear();
+            }
+            datepicker.show();
+            $('#datepickers-container').css('top', hitinfo.cellRect.y + pos.y).css('left', hitinfo.cellRect.x + pos.x);
+        }
+    };
+
     const bonusSpread = SpreadJsObj.createNewSpread($('#bonus-spread')[0]);
     const bonusSheet = bonusSpread.getActiveSheet();
     spreadSetting.readOnly = readOnly;
@@ -163,7 +212,7 @@ $(document).ready(() => {
                     }
                 }
                 if (datas.length > 0) {
-                    postData(window.location.pathname + '/update', {updateType: 'update', updateData: datas}, function (result) {
+                    postData(window.location.pathname + '/update', {update: datas}, function (result) {
                         bonusObj.loadUpdateData(result);
                         SpreadJsObj.reLoadSheetData(bonusSheet);
                     }, function () {
@@ -186,11 +235,11 @@ $(document).ready(() => {
 
                 for (let iRow = sels[0].row, iLen = sels[0].row + sels[0].rowCount; iRow < iLen; iRow++) {
                     const node = sortData[iRow];
-                    if (node.sid !== stageID) {
+                    if (node.sid !== stageId) {
                         toastMessageUniq(hint.isOld);
                         continue;
                     } else {
-                        if (node.uid !== userID || stageUserId !== userID) {
+                        if (node.uid !== userID && stageUserId !== userID) {
                             toastMessageUniq(hint.invalidDel);
                             continue;
                         }

+ 77 - 27
app/public/js/se_jgcl.js

@@ -8,33 +8,47 @@
  * @version
  */
 
-const spreadSetting = {
-    cols: [
-        {title: '名称', colSpan: '1', rowSpan: '2', field: 'name', hAlign: 0, width: 185, formatter: '@'},
-        {title: '单位', colSpan: '1', rowSpan: '2', field: 'unit', hAlign: 1, width: 50, formatter: '@', cellType: 'unit'},
-        {title: '单价', colSpan: '1', rowSpan: '2', field: 'unit_price', hAlign: 2, width: 60, type: 'Number'},
-        {title: '本期到场|数量',  colSpan: '2|1', rowSpan: '1|1', field: 'arrive_qty', hAlign: 2, width: 60, type: 'Number'},
-        {title: '|金额',  colSpan: '|1', rowSpan: '|1', field: 'arrive_tp', hAlign: 2, width: 60, type: 'Number'},
-        {title: '截止本期到场|数量', colSpan: '2|1', rowSpan: '1|1', field: 'end_arrive_qty', hAlign: 2, width: 60, type: 'Number'},
-        {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'end_arrive_tp', hAlign: 2, width: 60, type: 'Number', readOnly: true},
-        {title: '本期扣回|数量', colSpan: '2|1', rowSpan: '1|1', field: 'deduct_qty', hAlign: 2, width: 60, type: 'Number'},
-        {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'deduct_tp', hAlign: 2, width: 60, type: 'Number', readOnly: true},
-        {title: '截止本期扣回|数量', colSpan: '2|1', rowSpan: '1|1', field: 'end_deduct_qty', hAlign: 2, width: 60, type: 'Number'},
-        {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'end_deduct_tp', hAlign: 2, width: 60, type: 'Number', readOnly: true},
-        {title: '材料来源', colSpan: '1', rowSpan: '2', field: 'source', hAlign: 0, width: 80, formatter: '@'},
-        {title: '单据号', colSpan: '1', rowSpan: '2', field: 'bills_code', hAlign: 0, width: 80, formatter: '@'},
-        {title: '检验单编号', colSpan: '1', rowSpan: '2', field: 'check_code', hAlign: 0, width: 80, formatter: '@'},
-        {title: '备注', colSpan: '1', rowSpan: '2', field: 'memo', hAlign: 0, width: 100, formatter: '@', cellType: 'ellipsisAutoTip'}
-    ],
-    emptyRows: 3,
-    headRows: 2,
-    headRowHeight: [25, 25],
-    defaultRowHeight: 21,
-    headerFont: '12px 微软雅黑',
-    font: '12px 微软雅黑',
-};
 $(document).ready(() => {
     autoFlashHeight();
+    const spreadSetting = {
+        cols: [
+            {title: '名称', colSpan: '1', rowSpan: '2', field: 'name', hAlign: 0, width: 185, formatter: '@'},
+            {title: '单位', colSpan: '1', rowSpan: '2', field: 'unit', hAlign: 1, width: 50, formatter: '@', cellType: 'unit'},
+            {title: '单价', colSpan: '1', rowSpan: '2', field: 'unit_price', hAlign: 2, width: 60, type: 'Number'},
+            {title: '本期到场|数量',  colSpan: '2|1', rowSpan: '1|1', field: 'arrive_qty', hAlign: 2, width: 60, type: 'Number'},
+            {title: '|金额',  colSpan: '|1', rowSpan: '|1', field: 'arrive_tp', hAlign: 2, width: 60, type: 'Number', readOnly: true},
+            {title: '截止本期到场|数量', colSpan: '2|1', rowSpan: '1|1', field: 'end_arrive_qty', hAlign: 2, width: 60, type: 'Number', readOnly: true},
+            {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'end_arrive_tp', hAlign: 2, width: 60, type: 'Number', readOnly: true},
+            {title: '本期扣回|数量', colSpan: '2|1', rowSpan: '1|1', field: 'deduct_qty', hAlign: 2, width: 60, type: 'Number'},
+            {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'deduct_tp', hAlign: 2, width: 60, type: 'Number', readOnly: true},
+            {title: '截止本期扣回|数量', colSpan: '2|1', rowSpan: '1|1', field: 'end_deduct_qty', hAlign: 2, width: 60, type: 'Number', readOnly: true},
+            {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'end_deduct_tp', hAlign: 2, width: 60, type: 'Number', readOnly: true},
+            {title: '材料来源', colSpan: '1', rowSpan: '2', field: 'source', hAlign: 0, width: 80, formatter: '@'},
+            {title: '单据号', colSpan: '1', rowSpan: '2', field: 'bills_code', hAlign: 0, width: 80, formatter: '@'},
+            {title: '检验单编号', colSpan: '1', rowSpan: '2', field: 'check_code', hAlign: 0, width: 80, formatter: '@'},
+            {title: '备注', colSpan: '1', rowSpan: '2', field: 'memo', hAlign: 0, width: 100, formatter: '@', cellType: 'ellipsisAutoTip'}
+        ],
+        emptyRows: readOnly ? 0 : 3,
+        headRows: 2,
+        headRowHeight: [25, 25],
+        defaultRowHeight: 21,
+        headerFont: '12px 微软雅黑',
+        font: '12px 微软雅黑',
+        readOnly: readOnly,
+        getColor: function (sheet, data, row, col, defaultColor) {
+            if (data) {
+                if (data.end_deduct_tp) {
+                    return data.end_deduct_qty >= 0
+                        ? data.end_deduct_qty > data.end_arrive_qty ? '#f8d7da' : defaultColor
+                        : data.end_deduct_qty < data.end_arrive_qty ? '#f8d7da' : defaultColor;
+                } else {
+                    return defaultColor;
+                }
+            } else {
+                return defaultColor;
+            }
+        },
+    };
     const jgclSpread = SpreadJsObj.createNewSpread($('#jgcl-spread')[0]);
     const jgclSheet = jgclSpread.getActiveSheet();
     SpreadJsObj.initSheet(jgclSheet, spreadSetting);
@@ -104,12 +118,44 @@ $(document).ready(() => {
             this.calculateAll();
             this.resortData();
         }
+        sum () {
+            const result = {
+                arrive_tp: 0,
+                end_arrive_tp: 0,
+                deduct_tp: 0,
+                end_deduct_tp: 0,
+            };
+            for (const d of this.data) {
+                result.arrive_tp = ZhCalc.add(result.arrive_tp, d.arrive_tp);
+                result.end_arrive_tp = ZhCalc.add(result.end_arrive_tp, d.end_arrive_tp);
+                result.deduct_tp = ZhCalc.add(result.deduct_tp, d.deduct_tp);
+                result.end_deduct_tp = ZhCalc.add(result.end_deduct_tp, d.end_deduct_tp);
+            }
+            return result;
+        }
     }
     const jgclObj = new Jgcl();
+    const refreshSum = function () {
+        const sum = jgclObj.sum();
+        const html = [];
+        const getTrHtml = function (name, value) {
+            return '<tr><td>' + name + '</td><td class="text-right">' + (!checkZero(value) ? value : '') + ' </td></tr>';
+        };
+        html.push(getTrHtml('本期到场', sum.arrive_tp));
+        html.push(getTrHtml('截止本期到场', sum.end_arrive_tp));
+        html.push(getTrHtml('本期扣回', sum.deduct_tp));
+        html.push(getTrHtml('截止本期扣回', sum.end_deduct_tp));
+        /*html.push('本期到场:' + sum.arrive_tp + ';');
+        html.push('截止本期到场:' + sum.end_arrive_tp + ';');
+        html.push('本期扣回:' + sum.deduct_tp + ';');
+        html.push('截止本期扣回:' + sum.end_deduct_tp + ';');*/
+        $('#sum').html(html.join(' '));
+    };
 
     postData(window.location.pathname + '/load', null, function (result) {
         jgclObj.loadDatas(result);
         SpreadJsObj.loadSheetData(jgclSheet, SpreadJsObj.DataType.Data, jgclObj.data);
+        refreshSum();
     });
 
     if (!readOnly) {
@@ -150,9 +196,10 @@ $(document).ready(() => {
                     }
                 }
                 if (datas.length > 0) {
-                    postData(window.location.pathname + '/update', {updateType: 'update', updateData: datas}, function (result) {
+                    postData(window.location.pathname + '/update', {update: datas}, function (result) {
                         jgclObj.loadUpdateData(result);
                         SpreadJsObj.reLoadSheetData(jgclSheet);
+                        refreshSum();
                     }, function () {
                         SpreadJsObj.reLoadSheetData(jgclSheet);
                     });
@@ -176,7 +223,7 @@ $(document).ready(() => {
                         toastMessageUniq(hint.isOld);
                         continue;
                     } else {
-                        if (node.add_uid !== userID || stageUserId !== userID) {
+                        if (node.add_uid !== userID && stageUserId !== userID) {
                             toastMessageUniq(hint.invalidDel);
                             continue;
                         }
@@ -187,6 +234,7 @@ $(document).ready(() => {
                     postData(window.location.pathname + '/update', {del: datas}, function (result) {
                         jgclObj.loadUpdateData(result);
                         SpreadJsObj.reLoadSheetData(jgclSheet);
+                        refreshSum();
                     }, function () {
                         SpreadJsObj.reLoadSheetData(jgclSheet);
                     });
@@ -224,6 +272,7 @@ $(document).ready(() => {
                 postData(window.location.pathname + '/update', data, function (result) {
                     jgclObj.loadUpdateData(result);
                     SpreadJsObj.reLoadSheetData(info.sheet);
+                    refreshSum();
                 }, function () {
                     SpreadJsObj.reLoadRowData(info.sheet, info.row);
                 });
@@ -307,6 +356,7 @@ $(document).ready(() => {
                     postData(window.location.pathname + '/update', updateData, function (result) {
                         jgclObj.loadUpdateData(result);
                         SpreadJsObj.reLoadSheetData(info.sheet);
+                        refreshSum();
                     });
                 } else {
                     SpreadJsObj.reLoadSheetData(info.sheet);

+ 429 - 51
app/public/js/se_other.js

@@ -8,50 +8,90 @@
  * @version
  */
 
-const mainSpreadSetting = {
-    cols: [
-        {title: '名称', colSpan: '1', rowSpan: '1', field: 'name', hAlign: 0, width: 185, formatter: '@'},
-        {title: '金额', colSpan: '1', rowSpan: '1', field: 'e_type', hAlign: 1, width: 80, formatter: '@'},
-        {title: '本期金额', colSpan: '1', rowSpan: '1', field: 'quantity', hAlign: 2, width: 100, type: 'Number'},
-        {title: '截止本期金额', colSpan: '1', rowSpan: '1', field: 'total_price', hAlign: 2, width: 100, type: 'Number', readOnly: true},
-        {title: '时间', colSpan: '1', rowSpan: '1', field: 'total_price', hAlign: 2, width: 100, type: 'Number'},
-        {title: '备注', colSpan: '1', rowSpan: '1', field: 'memo', hAlign: 0, width: 150, formatter: '@', cellType: 'ellipsisAutoTip'}
-    ],
-    emptyRows: 3,
-    headRows: 1,
-    headRowHeight: [32],
-    defaultRowHeight: 21,
-    headerFont: '12px 微软雅黑',
-    font: '12px 微软雅黑',
-};
-
-const subSpreadSetting = {
-    cols: [
-        {title: '名称', colSpan: '1', rowSpan: '1', field: 'name', hAlign: 0, width: 185, formatter: '@'},
-        {title: '金额', colSpan: '1', rowSpan: '1', field: 'e_type', hAlign: 1, width: 80, formatter: '@'},
-        {title: '本期金额', colSpan: '1', rowSpan: '1', field: 'quantity', hAlign: 2, width: 100, type: 'Number'},
-        {title: '截止本期金额', colSpan: '1', rowSpan: '1', field: 'total_price', hAlign: 2, width: 100, type: 'Number', readOnly: true},
-        {title: '时间', colSpan: '1', rowSpan: '1', field: 'total_price', hAlign: 2, width: 100, type: 'Number'},
-        {title: '备注', colSpan: '1', rowSpan: '1', field: 'memo', hAlign: 0, width: 150, formatter: '@', cellType: 'ellipsisAutoTip'}
-    ],
-    emptyRows: 3,
-    headRows: 1,
-    headRowHeight: [32],
-    defaultRowHeight: 21,
-    headerFont: '12px 微软雅黑',
-    font: '12px 微软雅黑',
-};
 
 $(document).ready(() => {
     autoFlashHeight();
+    let datepicker;
 
-    const mainSpread = SpreadJsObj.createNewSpread($('#main-spread')[0]);
-    const mainSheet = mainSpread.getActiveSheet();
-    SpreadJsObj.initSheet(mainSheet, mainSpreadSetting);
+    const otherSpreadSetting = {
+        cols: [
+            {title: '名称', colSpan: '1', rowSpan: '1', field: 'name', hAlign: 0, width: 235, formatter: '@'},
+            {title: '金额', colSpan: '1', rowSpan: '1', field: 'total_price', hAlign: 2, width: 100, type: 'Number'},
+            {title: '本期金额', colSpan: '1', rowSpan: '1', field: 'tp', hAlign: 2, width: 100, type: 'Number'},
+            {title: '截止本期金额', colSpan: '1', rowSpan: '1', field: 'end_tp', hAlign: 2, width: 100, type: 'Number', readOnly: true},
+            {
+                title: '时间', colSpan: '1', rowSpan: '1', field: 'real_time', hAlign: 2, width: 120, readOnly: true,
+                formatter: 'yyyy-MM-dd', cellType: 'activeImageBtn', normalImg: '#ellipsis-icon', indent: 5,
+                showImage: function (data) {
+                    return data !== undefined && data !== null;
+                }
+            },
+            {title: '备注', colSpan: '1', rowSpan: '1', field: 'memo', hAlign: 0, width: 180, formatter: '@', cellType: 'ellipsisAutoTip'}
+        ],
+        emptyRows: readOnly ? 0 : 3,
+        headRows: 1,
+        headRowHeight: [32],
+        defaultRowHeight: 21,
+        headerFont: '12px 微软雅黑',
+        font: '12px 微软雅黑',
+        readOnly: readOnly,
+        getColor: function (sheet, data, row, col, defaultColor) {
+            if (data) {
+                if (data.total_price) {
+                    return data.total_price >= 0
+                        ? data.end_tp > data.total_price ? '#f8d7da' : defaultColor
+                        : data.end_tp < data.total_price ? '#f8d7da' : defaultColor;
+                } else {
+                    return defaultColor;
+                }
+            } else {
+                return defaultColor;
+            }
+        },
+        imageClick: function (data, hitinfo) {
+            if (!data || readOnly) return;
+
+            const setting = hitinfo.sheet.zh_setting;
+            if (!setting) return;
+            const col = setting.cols[hitinfo.col];
+            if (!col || col.field !== 'real_time') return;
+
+            const pos = SpreadJsObj.getObjPos(hitinfo.sheet.getParent().qo);
+            if (!datepicker) {
+                datepicker = $('.datepicker-here').datepicker({
+                    language: 'zh',
+                    dateFormat: 'yyyy-MM-dd',
+                    autoClose: true,
+                    onSelect: function (formattedDate, date, inst) {
+                        if (!inst.visible) return;
+                        const sels = hitinfo.sheet.getSelections();
+                        if (!sels || !sels[0]) return;
+                        const node = SpreadJsObj.getSelectObject(hitinfo.sheet);
+                        const uData = { update: {id: node.id, real_time: date} };
+
+                        postData(window.location.pathname + '/update', uData, function (result) {
+                            seOtherObj.loadUpdateData(result);
+                            SpreadJsObj.reLoadRowData(hitinfo.sheet, sels[0].row);
+                        }, function () {
+                            SpreadJsObj.reLoadRowData(hitinfo.sheet, sels[0].row);
+                        });
+                    }
+                }).data('datepicker');
+            }
+            const value = hitinfo.sheet.getValue(hitinfo.row, hitinfo.col);
+            if (value) {
+                datepicker.selectDate(value);
+            } else {
+                datepicker.clear();
+            }
+            datepicker.show();
+            $('#datepickers-container').css('top', hitinfo.cellRect.y + pos.y).css('left', hitinfo.cellRect.x + pos.x);
+        }
+    };
 
-    const subSpread = SpreadJsObj.createNewSpread($('#sub-spread')[0]);
-    const subSheet = subSpread.getActiveSheet();
-    SpreadJsObj.initSheet(subSheet, subSpreadSetting);
+    const otherSpread = SpreadJsObj.createNewSpread($('#other-spread')[0]);
+    const otherSheet = otherSpread.getActiveSheet();
+    SpreadJsObj.initSheet(otherSheet, otherSpreadSetting);
 
     $.subMenu({
         menu: '#sub-menu', miniMenu: '#sub-mini-menu', miniMenuList: '#mini-menu-list',
@@ -67,18 +107,356 @@ $(document).ready(() => {
                 $('#sub-menu').addClass('panel-sidebar');
             }
             autoFlashHeight();
-            mainSpread.refresh();
-            subSpread.refresh();
+            otherSpread.refresh();
         }
-    });    
-    // 上下窗口resizer
-    $.divResizer({
-        select: '#main-resize',
-        callback: function () {
-            mainSpread.refresh();
-            let bcontent = $(".bcontent-wrap") ? $(".bcontent-wrap").height() : 0;
-            $(".sp-wrap").height(bcontent-30);
-            subSpread.refresh();
+    });
+
+    class SeOther {
+        constructor () {
+            this.data = [];
         }
-    });  
+        resortData() {
+            this.data.sort(function (a, b) {
+                return a.order - b.order;
+            });
+        }
+        calculateAll() {
+            for (const d of this.data) {
+                d.end_tp = ZhCalc.add(d.pre_tp, d.tp);
+            }
+        }
+        loadDatas(datas) {
+            this.data = datas;
+            this.calculateAll();
+            this.resortData();
+        }
+        loadUpdateData(updateData) {
+            if (updateData.add) {
+                for (const a of updateData.add) {
+                    this.data.push(a);
+                }
+            }
+            if (updateData.update) {
+                for (const u of updateData.update) {
+                    const d = this.data.find(function (x) {
+                        return u.id === x.id;
+                    });
+                    if (d) {
+                        _.assign(d, u);
+                    } else {
+                        this.data.push(d);
+                    }
+                }
+            }
+            if (updateData.del) {
+                _.remove(this.data, function (d) {
+                    return updateData.del.indexOf(d.id) >= 0;
+                });
+            }
+            this.calculateAll();
+            this.resortData();
+        }
+    }
+    const seOtherObj = new SeOther();
+
+    postData(window.location.pathname + '/load', null, function (result) {
+        seOtherObj.loadDatas(result);
+        SpreadJsObj.loadSheetData(otherSheet, SpreadJsObj.DataType.Data, seOtherObj.data);
+    });
+
+    if (!readOnly) {
+        const seOtherOprObj = {
+            /**
+             * 删除按钮响应事件
+             * @param sheet
+             */
+            deletePress: function (sheet) {
+                if (!sheet.zh_setting || readOnly) return;
+
+                const sortData = sheet.zh_data;
+                const datas = [];
+                const sels = sheet.getSelections();
+                if (!sels || !sels[0]) return;
+
+                for (let iRow = sels[0].row; iRow < sels[0].row + sels[0].rowCount; iRow++) {
+                    let bDel = false;
+                    const node = sortData[iRow];
+                    if (node) {
+                        const data = {id: node.id};
+                        for (let iCol = sels[0].col; iCol < sels[0].col + sels[0].colCount; iCol++) {
+                            const colSetting = sheet.zh_setting.cols[iCol];
+                            if (colSetting.field === 'name') {
+                                toastr.error('名称不能为空,如需删除请使用右键删除');
+                                return;
+                            }
+                            const style = sheet.getStyle(iRow, iCol);
+                            if (!style.locked) {
+                                const colSetting = sheet.zh_setting.cols[iCol];
+                                data[colSetting.field] = null;
+                                bDel = true;
+                            }
+                        }
+                        if (bDel) {
+                            datas.push(data);
+                        }
+                    }
+                }
+                if (datas.length > 0) {
+                    postData(window.location.pathname + '/update', {update: datas}, function (result) {
+                        seOtherObj.loadUpdateData(result);
+                        SpreadJsObj.reLoadSheetData(otherSheet);
+                    }, function () {
+                        SpreadJsObj.reLoadSheetData(otherSheet);
+                    });
+                }
+            },
+            delete: function (sheet) {
+                if (!sheet.zh_setting || readOnly) return;
+
+                const sortData = sheet.zh_data;
+                const datas = [];
+                const sels = sheet.getSelections();
+                if (!sels || !sels[0]) return;
+                const hint = {
+                    isOld: {type: 'warning', msg: '该数据已计量,不可删除'},
+                    invalidDel: {type: 'warning', msg: '该数据不是您新增的,只有原报和新增人可删除'},
+                };
+
+                for (let iRow = sels[0].row, iLen = sels[0].row + sels[0].rowCount; iRow < iLen; iRow++) {
+                    const node = sortData[iRow];
+                    if (node.pre_used) {
+                        toastMessageUniq(hint.isOld);
+                        continue;
+                    } else {
+                        if (node.add_uid !== userID && stageUserId !== userID) {
+                            toastMessageUniq(hint.invalidDel);
+                            continue;
+                        }
+                        datas.push(node.id);
+                    }
+                }
+                if (datas.length > 0) {
+                    postData(window.location.pathname + '/update', {del: datas}, function (result) {
+                        seOtherObj.loadUpdateData(result);
+                        SpreadJsObj.reLoadSheetData(otherSheet);
+                    }, function () {
+                        SpreadJsObj.reLoadSheetData(otherSheet);
+                    });
+                }
+            },
+            editEnded: function (e, info) {
+                if (!info.sheet.zh_setting || !info.sheet.zh_data) return;
+
+                const node = info.sheet.zh_data[info.row];
+                const col = info.sheet.zh_setting.cols[info.col];
+                const data = {};
+
+                if (node) {
+                    data.update = {};
+                    data.update.id = node.id;
+
+                    const oldValue = node ? node[col.field] : null;
+                    const newValue = trimInvalidChar(info.editingText);
+                    if (oldValue == info.editingText || ((!oldValue || oldValue === '') && (newValue === ''))) {
+                        SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                        return;
+                    }
+                    data.update[col.field] = newValue;
+                } else {
+                    if (col.field !== 'name') {
+                        toastr.warning('请先输入名称');
+                        SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                        return;
+                    }
+                    data.add = {};
+                    data.add.order = info.row + 1;
+                    data.add.name = trimInvalidChar(info.editingText);
+                }
+
+                postData(window.location.pathname + '/update', data, function (result) {
+                    seOtherObj.loadUpdateData(result);
+                    SpreadJsObj.reLoadSheetData(info.sheet);
+                }, function () {
+                    SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                });
+            },
+            editStarting(e, info) {
+                if (!info.sheet.zh_setting || !info.sheet.zh_data) {
+                    info.cancel = true;
+                    return;
+                }
+
+                const col = info.sheet.zh_setting.cols[info.col];
+                const node = info.sheet.zh_data[info.row];
+                if (!node) return;
+
+                switch (col.field) {
+                    case 'name':
+                    case 'total_price':
+                        info.cancel = readOnly || node.pre_used;
+                        break;
+                }
+            },
+            clipboardPasting(e, info) {
+                const setting = info.sheet.zh_setting, sortData = info.sheet.zh_data;
+                info.cancel = true;
+
+                if (!setting || !sortData) return;
+                const pasteData = info.pasteData.html
+                    ? SpreadJsObj.analysisPasteHtml(info.pasteData.html)
+                    : (info.pasteData.text === ''
+                        ? SpreadJsObj.Clipboard.getAnalysisPasteText()
+                        : SpreadJsObj.analysisPasteText(info.pasteData.text));
+                const hint = {
+                    name: {type: 'warning', msg: '名称不可为空,已过滤'},
+                    tp: {type: 'warning', msg: '输入的 金额 非法,已过滤'},
+                };
+
+                const uDatas = [], iDatas = [];
+                for (let iRow = 0; iRow < info.cellRange.rowCount; iRow++) {
+                    const curRow = info.cellRange.row + iRow;
+                    const node = sortData[curRow];
+
+                    let bPaste = false;
+                    const data = {};
+                    for (let iCol = 0; iCol < info.cellRange.colCount; iCol++) {
+                        const curCol = info.cellRange.col + iCol;
+                        const colSetting = setting.cols[curCol];
+                        const value = trimInvalidChar(pasteData[iRow][iCol]);
+
+                        if (colSetting.field === 'name' && (!value || value === '')) {
+                            toastMessageUniq(hint.name);
+                            break;
+                        }
+                        if (colSetting.type === 'Number') {
+                            const num = _.toNumber(value);
+                            if (num) {
+                                data[colSetting.field] = num;
+                                bPaste = true;
+                            }
+                        } else {
+                            data[colSetting.field] = value;
+                            bPaste = true;
+                        }
+                    }
+                    if (bPaste) {
+                        if (node) {
+                            data.id = node.id;
+                            uDatas.push(data);
+                        } else {
+                            data.order = curRow + 1;
+                            iDatas.push(data);
+                        }
+                    }
+                }
+                const updateData = {};
+                if (uDatas.length > 0) updateData.update = uDatas;
+                if (iDatas.length > 0) updateData.add = iDatas;
+                if (uDatas.length > 0 || iDatas.length > 0) {
+                    postData(window.location.pathname + '/update', updateData, function (result) {
+                        seOtherObj.loadUpdateData(result);
+                        SpreadJsObj.reLoadSheetData(info.sheet);
+                    });
+                } else {
+                    SpreadJsObj.reLoadSheetData(info.sheet);
+                }
+            },
+            upMove: function () {
+                const sels = otherSheet.getSelections(), sortData = otherSheet.zh_data;
+                const node = sortData[sels[0].row];
+                const preNode = sortData[sels[0].row - 1];
+                const data = [
+                    {id: node.id, order: preNode.order},
+                    {id: preNode.id, order: node.order}
+                ];
+                postData(window.location.pathname + '/update', {update: data}, function (result) {
+                    seOtherObj.loadUpdateData(result);
+                    SpreadJsObj.reLoadRowsData(otherSheet, [sels[0].row, sels[0].row - 1]);
+                    otherSheet.setSelection(sels[0].row - 1, sels[0].col, sels[0].rowCount, sels[0].colCount);
+                });
+            },
+            downMove: function () {
+                const sels = otherSheet.getSelections(), sortData = otherSheet.zh_data;
+                const node = sortData[sels[0].row];
+                const nextNode = sortData[sels[0].row + 1];
+                const data = [
+                    {id: node.id, order: nextNode.order},
+                    {id: nextNode.id, order: node.order}
+                ];
+                postData(window.location.pathname + '/update', {update: data}, function (result) {
+                    seOtherObj.loadUpdateData(result);
+                    SpreadJsObj.reLoadRowsData(otherSheet, [sels[0].row, sels[0].row + 1]);
+                    otherSheet.setSelection(sels[0].row + 1, sels[0].col, sels[0].rowCount, sels[0].colCount);
+                });
+            }
+        };
+        otherSheet.bind(spreadNS.Events.EditEnded, seOtherOprObj.editEnded);
+        otherSheet.bind(spreadNS.Events.EditStarting, seOtherOprObj.editStarting);
+        otherSheet.bind(spreadNS.Events.ClipboardPasting, seOtherOprObj.clipboardPasting);
+        SpreadJsObj.addDeleteBind(otherSpread, seOtherOprObj.deletePress);
+        $.contextMenu({
+            selector: '#other-spread',
+            build: function ($trigger, e) {
+                const target = SpreadJsObj.safeRightClickSelection($trigger, e, otherSpread);
+                return target.hitTestType === spreadNS.SheetArea.viewport || target.hitTestType === spreadNS.SheetArea.rowHeader;
+            },
+            items: {
+                del: {
+                    name: '删除',
+                    icon: 'fa-remove',
+                    callback: function (key, opt) {
+                        seOtherOprObj.delete(otherSheet);
+                    },
+                    disabled: function (key, opt) {
+                        const sels = otherSheet.getSelections();
+                        if (!sels || !sels[0]) return true;
+
+                        const row = sels[0].row;
+                        const node = seOtherObj.data[row];
+                        return node === undefined || node === null;
+                    },
+                    visible: function (key, opt) {
+                        return !readOnly;
+                    }
+                },
+                sprDel: '------------',
+                upMove: {
+                    name: '上移',
+                    icon: 'fa-arrow-up',
+                    callback: function (key, opt) {
+                        seOtherOprObj.upMove();
+                    },
+                    disabled: function (key, opt) {
+                        const sels = otherSheet.getSelections();
+                        if (!sels || !sels[0] || sels[0].row === 0) return true;
+
+                        const row = sels[0].row;
+                        const node = seOtherObj.data[row];
+                        return node === undefined || node === null;
+                    },
+                    visible: function (key, opt) {
+                        return !readOnly;
+                    }
+                },
+                downMove: {
+                    name: '下移',
+                    icon: 'fa-arrow-down',
+                    callback: function (key, opt) {
+                        seOtherOprObj.downMove();
+                    },
+                    disabled: function (key, opt) {
+                        const sels = otherSheet.getSelections();
+                        if (!sels || !sels[0] || sels[0].row >= seOtherObj.data.length - 1) return true;
+
+                        const row = sels[0].row;
+                        const node = seOtherObj.data[row];
+                        return node === undefined || node === null;
+                    },
+                    visible: function (key, opt) {
+                        return !readOnly;
+                    }
+                }
+            },
+        })
+    }
 });

+ 1 - 1
app/public/js/shares/export_excel.js

@@ -120,7 +120,7 @@ const XLSXObj = (function () {
         };
         const blob = xlsxUtils.format2Blob(xlsxData);
         saveAs(blob, file);
-    }
+    };
 
     return {exportXlsxSheet}
 });

+ 36 - 11
app/public/js/spreadjs_rela/spreadjs_zh.js

@@ -389,6 +389,32 @@ const SpreadJsObj = {
             return backColor;
         }
     },
+    _loadRowStyle: function (sheet, row) {
+        sheet.zh_setting.cols.forEach(function (col, j) {
+            const cell = sheet.getCell(row, j);
+
+            if (col.font) {
+                cell.font(col.font);
+            }
+
+            if (col.foreColor && Object.prototype.toString.apply(col.foreColor) !== "[object Function]") {
+                cell.foreColor(col.foreColor);
+            }
+
+            if (col.readOnly && Object.prototype.toString.apply(col.readOnly) !== "[object Function]") {
+                cell.locked(col.readOnly || sheet.zh_setting.readOnly || false);
+            }
+            cell.vAlign(1).hAlign(col.hAlign);
+
+            if (col.formatter) {
+                cell.formatter(col.formatter);
+            } else if (col.type === 'Number') {
+                cell.formatter(SpreadJsObj.Formatter.getNumberFormatter('0.######'));
+            }
+
+            cell.setBorder(sheet.borderLine, {all: true});
+        });
+    },
     _loadRowData: function (sheet, data, row) {
         // 单元格重新写入数据
         if (!data) { return }
@@ -433,7 +459,6 @@ const SpreadJsObj = {
 
             cell.setBorder(sheet.borderLine, {all: true});
         });
-
     },
     _addActivePaintEvents: function (sheet, cellType) {
         if (!sheet.ActiveType) {
@@ -573,6 +598,9 @@ const SpreadJsObj = {
                     self._loadRowData(sheet, data, i);
                     sheet.setRowVisible(i, data.visible);
                 });
+                for (let iRow = sortData.length - 1; iRow < totalRow; iRow++) {
+                    self._loadRowStyle(sheet, iRow);
+                }
             }
             // 设置列单元格格式
             sheet.zh_setting.cols.forEach(function (col, j) {
@@ -1032,7 +1060,7 @@ const SpreadJsObj = {
             };
 
             let TreeNodeCellType = function (){};
-            TreeNodeCellType.prototype = new spreadNS.CellTypes.Base();
+            TreeNodeCellType.prototype = new spreadNS.CellTypes.Text();
             const proto = TreeNodeCellType.prototype;
 
             /**
@@ -1392,7 +1420,7 @@ const SpreadJsObj = {
                         if (imageClick && Object.prototype.toString.apply(imageClick) === "[object Function]") {
                             const sortData = SpreadJsObj.getSortData(hitinfo.sheet);
                             const data = sortData ? sortData[hitinfo.row] : null;
-                            imageClick(data);
+                            imageClick(data, hitinfo);
                         }
                     }
                 }
@@ -1571,7 +1599,7 @@ const SpreadJsObj = {
                     if (imageClick && Object.prototype.toString.apply(imageClick) === "[object Function]") {
                         const sortData = SpreadJsObj.getSortData(hitinfo.sheet);
                         const data = sortData ? sortData[hitinfo.row] : null;
-                        imageClick(data);
+                        imageClick(data, hitinfo);
                         const cell = hitinfo.sheet.getCell(hitinfo.row, hitinfo.col);
                         cell.tag(null);
                         hitinfo.sheet.repaint(hitinfo.cellRect);
@@ -1766,7 +1794,7 @@ const SpreadJsObj = {
                     if (imageClick && Object.prototype.toString.apply(imageClick) === "[object Function]") {
                         const sortData = SpreadJsObj.getSortData(hitinfo.sheet);
                         const data = sortData ? sortData[hitinfo.row] : null;
-                        imageClick(data);
+                        imageClick(data, hitinfo);
                         const cell = hitinfo.sheet.getCell(hitinfo.row, hitinfo.col);
                         cell.tag(hover);
                         hitinfo.sheet.repaint(hitinfo.cellRect);
@@ -1953,6 +1981,7 @@ const SpreadJsObj = {
          */
         getDatePickerCellType: function () {
             let datepicker;
+
             const DatePickerCellType = function () {};
             DatePickerCellType.prototype = new spreadNS.CellTypes.Text();
             const proto = DatePickerCellType.prototype;
@@ -1963,15 +1992,11 @@ const SpreadJsObj = {
                 const self = this;
                 if (editorContext) {
                     const $editor = $(editorContext);
-                    spreadNS.CellTypes.Base.prototype.activateEditor.apply(this, arguments);
+                    //spreadNS.CellTypes.Text.prototype.activateEditor.apply(this, arguments);
                     $editor.css("position", "absolute");
                     datepicker = $editor.datepicker({
                         language: 'zh',
-                        dateFormat: 'yyy-MM-DD',
-                        autoClose: true,
-                        onSelect: function (formattedDate, date, inst) {
-                            this.value = date;
-                        }
+                        dateFormat: 'yyyy-MM-DD'
                     }).data('datepicker');
                     datepicker.show();
                 }

+ 4 - 4
app/public/js/stage_im.js

@@ -421,9 +421,9 @@ const stageIm = (function () {
                 im.xm = node.name;
             }
             checkCustomDetail(im);
-            if (!stage.im_gather || !node.check) {
+            //if (!stage.im_gather || !node.check) {
                 generateTzGclBillsData(node, im);
-            }
+            //}
             ImData.push(im);
             generateTzChangeData(node, im);
         }
@@ -529,9 +529,9 @@ const stageIm = (function () {
                 checkCustomDetail(im);
                 ImData.push(im);
             }
-            if (!stage.im_gather || !node.check) {
+            //if (!stage.im_gather || !node.check) {
                 generateZlLeafXmjData(p, im, 'gather_qty');
-            }
+            //}
             generateZlChangeData(p, im);
             im.jl = ZhCalc.add(im.jl, p.gather_qty);
             im.contract_jl = ZhCalc.add(im.contract_jl, p.contract_qty);

+ 130 - 0
app/public/report/js/rpt_custom.js

@@ -0,0 +1,130 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date
+ * @version
+ */
+
+const rptCustomObj = (function () {
+
+    const sAuditSelect = 'audit_select';
+    let stageFlow = [];
+
+    const getStageFlowSelectHtml = function (select) {
+        const html = [];
+        html.push('<select style="width: 80%">');
+        for (const sf of stageFlow) {
+            html.push('<option' + (select && sf.order === select.order ? ' selected' : '') + '>' + sf.name + '-' + sf.role +'</option>');
+        }
+        html.push('</select>');
+        return html.join('');
+    };
+
+    const initAuditSelect = function (asSetting, asSelect) {
+        const setting = JSON.parse(asSetting), select = asSelect;
+        $('#audit-select-title').html(setting.title);
+        const html = [];
+        for (const [i, s] of setting.select.entries()) {
+            html.push('<tr>');
+            html.push('<td>', s.title, '</td>');
+            html.push('<td>', getStageFlowSelectHtml(select[i]), '</td>');
+            html.push('</tr>');
+        }
+        $('#audit-select-list').html(html.join(''));
+    };
+
+    const init = function (cDefine, sfData, cSelect) {
+        stageFlow = sfData;
+        if (cDefine && cDefine[sAuditSelect] && cDefine[sAuditSelect].enable && cDefine[sAuditSelect].setting) {
+            $('#pnl_audit_select').show();
+            initAuditSelect(cDefine[sAuditSelect].setting, cSelect ? cSelect.audit_select : []);
+        } else {
+            $('#pnl_audit_select').hide();
+        }
+    };
+
+    const reloadReportData = function (result) {
+        // hintBox.unWaitBox();
+        let pageRst = result.data;
+        if (result.signatureRelInfo && result.signatureRelInfo.length > 0) {
+            CURRENT_ROLE_REL_ID = result.signatureRelInfo[0].id;
+            ROLE_REL_LIST = zTreeOprObj._parseRoleRelList(result.signatureRelInfo[0].rel_content);
+            STAGE_AUDIT = result.stageAudit;
+            rptSignatureHelper.originalRoleRelList = zTreeOprObj._parseRoleRelList(result.signatureRelInfo[0].rel_content);
+            if (current_stage_status === 3) {
+                rptSignatureHelper.mergeSignDate(pageRst);
+                rptSignatureHelper.mergeSignature(pageRst);
+            }
+        } else {
+            CURRENT_ROLE_REL_ID = -1;
+            ROLE_REL_LIST = [];
+        }
+        // if (ROLE_REL_LIST)
+        let canvas = zTreeOprObj.canvas;
+        if (pageRst && pageRst.items && pageRst.items.length > 0) {
+            zTreeOprObj.resetAfter(pageRst);
+            zTreeOprObj.currentRptPageRst = pageRst;
+            zTreeOprObj.maxPages = pageRst.items.length;
+            zTreeOprObj.currentPage = 1;
+            zTreeOprObj.displayPageValue();
+            let size = JpcCanvasOutput.getReportSizeInPixel(zTreeOprObj.currentRptPageRst, getScreenDPI());
+            canvas.width = size[0] + 20;
+            if (size[1] > size[0]) {
+                canvas.height = size[1] + 100;
+            } else {
+                canvas.height = size[1] + 50;
+            }
+            // zTreeOprObj.resetESignature(zTreeOprObj.currentRptPageRst);
+            rptSignatureHelper.buildSelectableAccount();
+            rptSignatureHelper.buildSelectableAccountUsed();
+            rptSignatureHelper.buildRoleDom(ROLE_LIST);
+            zTreeOprObj.showPage(1, canvas);
+        } else {
+            //返回了无数据表
+            JpcCanvasOutput.cleanCanvas(canvas);
+            JpcCanvasOutput.drawPageBorder(zTreeOprObj.currentRptPageRst, canvas, getScreenDPI());
+        }
+        rptCustomObj.init(result.customDefine, result.stageFlow, result.customSelect);
+        try {
+            if (is_debug && result.debugInfo) {
+                console.log('含有key的debug信息:');
+                for (const k in result.debugInfo.key) {
+                    console.log(k + ':', ...result.debugInfo.key[k]);
+                }
+                //console.log(result.debugInfo.key);
+                console.log('其他debug信息:');
+                for (const di of result.debugInfo.other) {
+                    console.log(...di);
+                }
+            }
+        } catch(err) {
+        }
+    };
+
+    const resetAuditSelect = function () {
+        const selObj = $('select', '#audit-select-list');
+        const data = { audit_select: [] };
+        data.pageSize = rptControlObj.getCurrentPageSize();
+        data.orientation = rptControlObj.getCurrentOrientation();
+        data.rpt_tpl_id = zTreeOprObj.currentNode.refId;
+        data.custCfg = CUST_CFG;
+        data.project_id = PROJECT_ID;
+        data.tender_id = TENDER_ID;
+        data.stage_id = getStageId();
+        data.stage_status = getStageStatus();
+        data.stage_order = getStageOrder();
+        data.stage_times = getStageTimes();
+        for (const s of selObj) {
+            data.audit_select.push(stageFlow[s.selectedIndex]);
+        }
+        postData('/report/cDefine', data, function (result) {
+            reloadReportData(result);
+            $('#audit-select').modal('hide');
+        });
+    };
+
+    return {init, resetAuditSelect};
+})();

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

@@ -328,6 +328,7 @@ let zTreeOprObj = {
                     JpcCanvasOutput.cleanCanvas(canvas);
                     JpcCanvasOutput.drawPageBorder(me.currentRptPageRst, canvas, getScreenDPI());
                 }
+                rptCustomObj.init(result.customDefine, result.stageFlow, result.customSelect);
                 try {
                     if (is_debug && result.debugInfo) {
                         console.log('含有key的debug信息:');

+ 7 - 7
app/reports/rpt_component/jpc_ex.js

@@ -166,16 +166,16 @@ JpcExSrv.prototype.createNew = function() {
         me.events = JpcEvent.createNew(rptTpl);
     };
 
-    JpcResult.analyzeData = function($CTX_HELPER, rptTpl, dataObj, defProperties, option, outputType) {
+    JpcResult.analyzeData = function($CTX_HELPER, rptTpl, dataObj, defProperties, option, outputType, $CUSTOM_DEFINE) {
         const me = this;
         const dftPagingOption = option || JV.PAGING_OPTION_NORMAL;
         // 1. data object
         let dataHelper = JpcData.createNew();
         // console.log(JSON.stringify(rptTpl));
-        me.executeFormulas($CTX_HELPER, JV.RUN_TYPE_BEFORE_ANALYZING, rptTpl, dataObj, me); // 在分析前运行,主要是增加灵活性,比如:重新编排数据的主从关系
+        me.executeFormulas($CTX_HELPER, JV.RUN_TYPE_BEFORE_ANALYZING, rptTpl, dataObj, me, $CUSTOM_DEFINE); // 在分析前运行,主要是增加灵活性,比如:重新编排数据的主从关系
         // console.log(JSON.stringify(rptTpl));
         if (me.crossTab) {
-            me.executeFormulas($CTX_HELPER, JV.RUN_TYPE_BEFORE_PAGING, rptTpl, dataObj, me);
+            me.executeFormulas($CTX_HELPER, JV.RUN_TYPE_BEFORE_PAGING, rptTpl, dataObj, me, $CUSTOM_DEFINE);
             dataHelper.analyzeData(rptTpl, dataObj);
             me.crossTab.sorting(rptTpl, dataObj, dataHelper.dataSeq.slice(0), me);
         } else {
@@ -195,7 +195,7 @@ JpcExSrv.prototype.createNew = function() {
             // let dt2 = new Date();
             // alert(dt2 - dt1);
             // 3. formulas
-            me.executeFormulas($CTX_HELPER, JV.RUN_TYPE_BEFORE_PAGING, rptTpl, dataObj, me);
+            me.executeFormulas($CTX_HELPER, JV.RUN_TYPE_BEFORE_PAGING, rptTpl, dataObj, me, $CUSTOM_DEFINE);
         }
         // 4. paging
         me.paging(rptTpl, dataObj, defProperties, dftPagingOption, outputType);
@@ -224,7 +224,7 @@ JpcExSrv.prototype.createNew = function() {
             me.totalPages = me.billTab.paging(rptTpl, dataObj);
         }
     };
-    JpcResult.executeFormulas = function($CTX_HELPER, runType, $CURRENT_TEMPLATE, $CURRENT_DATA, $CURRENT_RPT) {
+    JpcResult.executeFormulas = function($CTX_HELPER, runType, $CURRENT_TEMPLATE, $CURRENT_DATA, $CURRENT_RPT, $CUSTOM_DEFINE) {
         const execFmlMe = this;
         for (let execFmlIdx = 0; execFmlIdx < execFmlMe.formulas.length; execFmlIdx++) {
             // remark: 搞这么复杂的变量名是为了防止与表达式起冲突(如循环变量i,j,k,容易造成变量冲突且不容易看出问题)
@@ -305,7 +305,7 @@ JpcExSrv.prototype.createNew = function() {
         }
         return rst;
     };
-    JpcResult.outputAsSimpleJSONPageArray = function($CTX_HELPER, rptTpl, dataObj, startPage, endPage, defProperties, customizeCfg) {
+    JpcResult.outputAsSimpleJSONPageArray = function($CTX_HELPER, rptTpl, dataObj, startPage, endPage, defProperties, customizeCfg, $CUSTOM_DEFINE) {
         const me = this;
         const rst = {};
         if ((startPage > 0) && (startPage <= endPage) && (endPage <= me.totalPages)) {
@@ -323,7 +323,7 @@ JpcExSrv.prototype.createNew = function() {
             try {
                 for (let page = startPage; page <= endPage; page++) {
                     me.runTimePageData.currentPage = page;
-                    me.executeFormulas($CTX_HELPER, JV.RUN_TYPE_BEFORE_OUTPUT, rptTpl, dataObj, me);
+                    me.executeFormulas($CTX_HELPER, JV.RUN_TYPE_BEFORE_OUTPUT, rptTpl, dataObj, me, $CUSTOM_DEFINE);
                     rst.items.push(me.outputAsSimpleJSONPage(rptTpl, dataObj, bands, page, rst[JV.NODE_CONTROL_COLLECTION], customizeCfg));
                 }
                 if (bands[JV.BAND_PROP_MERGE_BAND]) {

+ 3 - 0
app/reports/rpt_component/jpc_value_define.js

@@ -48,6 +48,9 @@ module.exports = {
     NODE_BIZ_TYPE_SUM: '汇总类型',
     NODE_BIZ_TYPE_DETAIL: '明细类型',
 
+    NODE_CUSTOM_DEFINE: '用户交互',
+    NODE_CUS_AUDIT_SELECT: 'audit_select',
+
     NODE_MAP_DATA_HANDLE_INFO: '映射数据预处理',
     PROP_DATA_KEY: '映射数据对象',
     PROP_PARENT_DATA_KEY: '父映射数据对象',

+ 7 - 4
app/reports/util/rpt_calculation_data_util.js

@@ -213,7 +213,7 @@ class Rpt_Data_Extractor {
     }
 
     // 装配数据(把收集到的数据,依据报表模板的指示,预处理(如:排序、过滤、合计)及装配到相关指标)
-    assembleData(ctx, rawDataObj, baseDir, $CURRENT_RPT) {
+    assembleData(ctx, rawDataObj, baseDir, $CURRENT_RPT, customSelect) {
         const $PROJECT = { REPORT: {} };
         const tpl = this.rptTpl;
         this.COMMON.initialize(tpl, rawDataObj);
@@ -233,7 +233,7 @@ class Rpt_Data_Extractor {
                         // filterData(srcData, preHandle, rawDataObj.prjData);
                         break;
                     case JV.PROP_HANDLE_TYPE_PRE_DEFINED:
-                        preDefineProcess(ctx, tpl, preHandle, rawDataObj, $CURRENT_RPT);
+                        preDefineProcess(ctx, tpl, preHandle, rawDataObj, $CURRENT_RPT, customSelect);
                         break;
                     default:
                         break;
@@ -537,7 +537,7 @@ function getOrgFieldDefine(fieldId, tpl) {
     return rst;
 }
 
-function preDefineProcess(ctx, tpl, preDefineCfg, rawDataObj, $CURRENT_RPT) {
+function preDefineProcess(ctx, tpl, preDefineCfg, rawDataObj, $CURRENT_RPT, customSelect) {
     // 依据约定,需要提供如右所示格式的数据:[{field: 'b_code', table: 'mem_stage_bills'}, {field: 'id', table: 'mem_stage_bills'}]
     // 指标对象的mapExpression 格式类似于: "$PROJECT.REPORT.getProperty('mem_stage_im_zl', 'calc_memo')"
     const fields = [];
@@ -572,6 +572,7 @@ function preDefineProcess(ctx, tpl, preDefineCfg, rawDataObj, $CURRENT_RPT) {
             try {
                 // 在预定义方式中,小麦处理原始数据,不需要
                 let preSetup = preDefineCfg[JV.PROP_HANDLE_SELF_SETUP];
+                console.log(preSetup);
                 try {
                     if (preSetup) {
                         preSetup = JSON.parse(preSetup);
@@ -580,7 +581,9 @@ function preDefineProcess(ctx, tpl, preDefineCfg, rawDataObj, $CURRENT_RPT) {
                     console.log(analysisKey);
                     ctx.helper.log(ex);
                 }
-                data_analyze_util[analysisKey].fun(ctx, rawDataObj, fields, preSetup);
+                data_analyze_util[analysisKey].fun(ctx, rawDataObj, fields, preSetup, {
+                    tplDefine: tpl[JV.NODE_CUS_AUDIT_SELECT], cDefine: customSelect
+                });
             } catch (err) {
                 ctx.helper.log(err);
                 throw '报表预处理数据出错';

+ 6 - 0
app/router.js

@@ -119,6 +119,7 @@ module.exports = app => {
     //app.post('/tender/:id/revise/deal2sgfh', sessionAuth, tenderCheck, 'reviseController.deal2sgfh');
     // 台账修订页面
     app.get('/tender/:id/revise/info', sessionAuth, tenderCheck, 'reviseController.info');
+    app.post('/tender/:id/revise/auditors', sessionAuth, tenderCheck, 'reviseController.reviseAuditors');
     app.post('/tender/:id/revise/info/load', sessionAuth, tenderCheck, 'reviseController.loadInfoData');
     app.post('/tender/:id/revise/info/update', sessionAuth, tenderCheck, 'reviseController.update');
     app.post('/tender/:id/revise/info/upload-excel/:ueType', sessionAuth, tenderCheck, 'reviseController.uploadExcel');
@@ -220,6 +221,8 @@ module.exports = app => {
     app.post('/tender/report_api/updateSignatureUsed', sessionAuth, datetimeFill, 'signatureController.updateSignatureUsed');
     app.post('/tender/report_api/updateRoleRelationship', sessionAuth, 'signatureController.updateRoleRel');
     app.post('/tender/report_api/createRoleRelationship', sessionAuth, 'signatureController.createRoleRel');
+    app.post('/report/cDefine', sessionAuth, 'reportController.setCustomDefine');
+
     // 变更管理
     app.get('/tender/:id/change', sessionAuth, tenderCheck, 'changeController.index');
     app.get('/tender/:id/change/status/:status', sessionAuth, tenderCheck, 'changeController.status');
@@ -287,6 +290,9 @@ module.exports = app => {
     app.get('/compare/tz', sessionAuth, 'spssController.compareTz');
     app.post('/compare/tz/load', sessionAuth, 'spssController.loadCompareTz');
     app.get('/compare/stage', sessionAuth, 'spssController.compareStage');
+    app.post('/compare/stage/load', sessionAuth, 'spssController.loadCompareStage');
     app.get('/gather/tz', sessionAuth, 'spssController.gatherTz');
+    app.post('/gather/tz/load', sessionAuth, 'spssController.loadGatherTz');
     app.get('/gather/stage', sessionAuth, 'spssController.gatherStage');
+    app.post('/gather/stage/load', sessionAuth, 'spssController.loadGatherStage');
 };

+ 33 - 3
app/service/change.js

@@ -109,7 +109,7 @@ module.exports = app => {
 
                 // 把提交人信息添加到zh_change_audit
                 const userInfo = await this.ctx.service.projectAccount.getDataById(userId);
-                const changeaudit = {
+                const changeaudit = [{
                     tid: tenderId,
                     cid,
                     uid: userId,
@@ -120,8 +120,32 @@ module.exports = app => {
                     usite: 0,
                     usort: 0,
                     status: 2,
-                };
-
+                }];
+                // 并把之前存在的变更令审批人添加到zh_change_audit
+                // 先找出标段最近存在的变更令审批人的变更令info
+                const changeInfo = await this.ctx.service.change.getHaveAuditLastInfo(tenderId);
+                if (changeInfo) {
+                    // 再获取非原报审批人
+                    const auditList = await this.ctx.service.changeAudit.getListGroupByTimes(changeInfo.cid, changeInfo.times);
+                    let sort = 1;
+                    for (const audit of auditList) {
+                        if (audit.usite !== 0) {
+                            const oneaudit = {
+                                tid: tenderId,
+                                cid,
+                                uid: audit.uid,
+                                name: audit.name,
+                                jobs: audit.jobs,
+                                company: audit.company,
+                                times: 1,
+                                usite: audit.usite,
+                                usort: sort++,
+                                status: 1,
+                            };
+                            changeaudit.push(oneaudit);
+                        }
+                    }
+                }
                 await this.transaction.insert(this.ctx.service.changeAudit.tableName, changeaudit);
 
                 result = change;
@@ -135,6 +159,12 @@ module.exports = app => {
             return result;
         }
 
+        async getHaveAuditLastInfo(tenderId) {
+            const sql = 'SELECT * FROM ?? as a LEFT JOIN ?? as b ON a.`cid` = b.`cid` WHERE a.`tid` = ? AND b.`usite` > 0 ORDER BY a.`in_time` DESC';
+            const sqlParam = [this.tableName, this.ctx.service.changeAudit.tableName, tenderId];
+            return await this.db.queryOne(sql, sqlParam);
+        }
+
         async pendingDatas(tenderId, userId) {
             return await this.getAllDataByCondition({
                 tid: tenderId,

+ 22 - 17
app/service/report.js

@@ -95,10 +95,10 @@ module.exports = app => {
                         case 'mem_stage_pay':
                             runnableRst.push(service.reportMemory.getStagePayData(params.tender_id, params.stage_id, memFieldKeys[filter]));
                             runnableKey.push('mem_stage_pay');
-                        case 'mem_change_bills':
-                            runnableRst.push(service.reportMemory.getChangeBillsData(params.tender_id, params.stage_id, memFieldKeys[filter]));
-                            runnableKey.push('mem_change_bills');
-                            break;
+                        // case 'mem_change_bills':
+                        //     runnableRst.push(service.reportMemory.getChangeBillsData(params.tender_id, params.stage_id, memFieldKeys[filter]));
+                        //     runnableKey.push('mem_change_bills');
+                        //     break;
                         case 'change':
                             runnableRst.push(service.change.getListByStatus(params.tender_id, 3)); // 获取所有审核通过的变更主信息
                             runnableKey.push(filter);
@@ -107,6 +107,18 @@ module.exports = app => {
                             runnableRst.push(service.changeAuditList.getChangeAuditBills(params.tender_id)); // 获取所有审核通过的变更清单
                             runnableKey.push(filter);
                             break;
+                        case 'stage_jgcl':
+                            runnableRst.push(service.reportMemory.getStageJgcl(params.tender_id, param.stage_id, memFieldKeys[filter]));
+                            runnableKey.push(filter);
+                            break;
+                        case 'stage_bonus':
+                            runnableRst.push(service.reportMemory.getStageBonus(params.tender_id, param.stage_id, memFieldKeys[filter]));
+                            runnableKey.push(filter);
+                            break;
+                        case 'stage_other':
+                            runnableRst.push(service.reportMemory.getStageOther(params.tender_id, param.stage_id, memFieldKeys[filter]));
+                            runnableKey.push(filter);
+                            break;
                         default:
                             break;
                     }
@@ -127,6 +139,12 @@ module.exports = app => {
                     case 'mem_union_data':
                         rst[filter] = [];
                         break;
+                    case 'mem_change':
+                        rst[filter] = await service.reportMemory.getChangeData(params.tender_id, params.stage_id, memFieldKeys[filter]);
+                        break;
+                    case 'mem_change_bills':
+                        rst[filter] = await service.reportMemory.getChangeBillsData(params.tender_id, params.stage_id, memFieldKeys[filter]);
+                        break;
                     default:
                         break;
                 }
@@ -137,16 +155,3 @@ module.exports = app => {
 
     return Report;
 };
-
-async function checkStg(ctx, params) {
-    if (ctx.stage === null || ctx.stage === undefined || parseInt(ctx.stage.id) !== parseInt(params.stage_id)) {
-        await ctx.service.stage.checkStage(params.stage_id);
-        if (ctx.stage) {
-            // params.stage_order = ctx.stage.curOrder;
-            // console.log('ctx.stage.curOrder: ' + ctx.stage.curOrder);
-            // console.log('ctx.stage.order: ' + ctx.stage.order);
-            params.stage_order = ctx.stage.order; // 经过check stage后,取新的order
-            params.stage_times = ctx.stage.times; // 经过check stage后,取新的times
-        }
-    }
-}

+ 142 - 10
app/service/report_memory.js

@@ -13,6 +13,7 @@ const _ = require('lodash');
 const StageIm = require('../lib/stage_im');
 const imType = require('../const/tender').imType;
 const audit = require('../const/audit');
+const changeConst = require('../const/change');
 // const path = require('path');
 // const fs = require('fs');
 
@@ -55,7 +56,7 @@ module.exports = app => {
                 rootId: -1,
                 keys: ['id', 'tender_id', 'ledger_id'],
                 stageId: 'id',
-                calcFields: ['deal_tp', 'total_price', 'contract_tp', 'qc_tp', 'gather_tp'],
+                calcFields: ['deal_tp', 'total_price', 'contract_tp', 'qc_tp', 'gather_tp', 'pre_contract_tp', 'pre_qc_tp', 'pre_gather_tp'],
                 calc: function (node) {
                     if (node.children && node.children.length === 0) {
                         node.pre_gather_qty = self.ctx.helper.add(node.pre_contract_qty, node.pre_qc_qty);
@@ -87,6 +88,7 @@ module.exports = app => {
             });
             // 需要缓存的数据
             this.stageImData = null;
+            this.changeData = null;
         }
 
         _checkFieldsExist(source, check) {
@@ -562,19 +564,149 @@ module.exports = app => {
             return dealPay;
         }
 
+        _getChangeConstName(define, value) {
+            for (const prop in define) {
+                if (define[prop].value === value) {
+                    return define[prop].name;
+                }
+            }
+            return '';
+        }
+
+        async _generateChange(tid) {
+            if (this.changeData !== null) return;
+            const self = this;
+            try {
+                const decimal = this.ctx.tender.info.decimal, ctx = this.ctx;
+                const change = await this.ctx.service.change.getListByStatus(tid, 3);
+                for (const c of change) {
+                    const types = ctx.helper._.map(c.type.split(','), function (t) {
+                        return self._getChangeConstName(changeConst.type, ctx.helper._.toInteger(t));
+                    });
+                    c.type = types.join(';');
+                    c.class = this._getChangeConstName(changeConst.class, c.class);
+                    c.quality = this._getChangeConstName(changeConst.quality, c.quality);
+                    c.charge = this._getChangeConstName(changeConst.charge, c.charge);
+                    c.attachments = await ctx.service.changeAtt.getChangeAttachment(c.cid);
+                    const names = ctx.helper._.map(c.attachments, function (x) {
+                        return x.filename + x.fileext;
+                    });
+                    c.attNames = names.join('\n');
+                }
+                const changeBills = await this.ctx.service.changeAuditList.getChangeAuditBills(tid);
+                for (const d of changeBills) {
+                    //const precision = this.ctx.helper.findPrecision(this.ctx.tender.info.precision, d.unit);
+                    d.o_qty = d.oamount;
+                    d.o_tp = this.ctx.helper.mul(d.o_qty, d.unit_price, decimal.tp);
+                    d.c_qty = d.camount;
+                    d.c_tp = this.ctx.helper.mul(d.c_qty, d.unit_price, decimal.tp);
+                    d.s_qty = d.samount ? parseFloat(d.samount) : 0;
+                    d.s_tp = this.ctx.helper.mul(d.s_qty, d.unit_price, decimal.tp);
+
+                    const auditAmount = d.audit_amount.split(',');
+                    const relaChange = ctx.helper._.find(change, {cid: d.cid});
+                    for (const [i, aa] of auditAmount.entries()) {
+                        const amountField = 'qty_' + (i+1), tpField = 'tp_' + (i+1);
+                        d[amountField] = aa ? parseFloat(aa) : 0;
+                        d[tpField] = ctx.helper.mul(d[amountField], d.unit_price, decimal.tp);
+                        if (relaChange) {
+                            relaChange[tpField] = ctx.helper.add(relaChange[tpField], d[tpField]);
+                        }
+                    }
+                }
+
+                change.sort(function (a, b) {
+                    return a.code.localeCompare(b.code);
+                });
+                changeBills.sort(function (a, b) {
+                    const aCIndex = change.findIndex(function (c) {
+                        return c.cid === a.cid;
+                    });
+                    const bCIndex = change.findIndex(function (c) {
+                        return c.cid === b.cid;
+                    });
+                    return aCIndex === bCIndex
+                        ? ctx.helper.compareCode(a.code, b.code)
+                        : aCIndex - bCIndex;
+                });
+                this.changeData = {change: change, bills: changeBills};
+            } catch(err) {
+                this.ctx.helper.log(err);
+                throw err;
+                this.changeData = {change: [], bills: []};
+            }
+
+        }
+
+        async getChangeData(tid, sid, fields) {
+            await this.ctx.service.tender.checkTender(tid);
+
+            await this._generateChange(tid);
+            return this.changeData.change;
+        }
+
         async getChangeBillsData(tid, sid, fields) {
             await this.ctx.service.tender.checkTender(tid);
 
-            const data = await this.ctx.service.changeAuditList.getChangeAuditBills(tid);
-            const decimal = this.ctx.tender.info.decimal;
+            await this._generateChange(tid);
+            return this.changeData.bills;
+            // const data = await this.ctx.service.changeAuditList.getChangeAuditBills(tid);
+            // for (const d of data) {
+            //     //const precision = this.ctx.helper.findPrecision(this.ctx.tender.info.precision, d.unit);
+            //     d.o_qty = d.oamount;
+            //     d.o_tp = this.ctx.helper.mul(d.o_qty, d.unit_price, decimal.tp);
+            //     d.c_qty = d.camount;
+            //     d.c_tp = this.ctx.helper.mul(d.c_qty, d.unit_price, decimal.tp);
+            //     d.s_qty = d.samount ? parseFloat(d.samount) : 0;
+            //     d.s_tp = this.ctx.helper.mul(d.s_qty, d.unit_price, decimal.tp);
+            //
+            //     const auditAmount = d.audit_amount.split(',');
+            //     for (const [i, aa] of auditAmount.entries()) {
+            //         d['amount_' + (i + 1)] = aa ? parseFloat(aa) : 0;
+            //         d['tp_' + (i + 1)] = this.ctx.helper.mul(d['amount_' + i], d.unit_price, decimal.tp);
+            //     }
+            // }
+            //return data;
+        }
+
+        async getStageJgcl(tid, sid, fields) {
+            await this.ctx.service.tender.checkTender(tid);
+            await this.ctx.service.stage.checkStage(sid);
+
+            const data = await this.ctx.service.stageJgcl.getStageData(this.ctx.stage.id);
+            const preData = await this.ctx.service.stageJgcl.getPreStageData(this.ctx.stage.order);
+            for (const d of data) {
+                const pd = this.ctx.helper._.find(preData, {uuid: d.uuid});
+                if (pd) {
+                    d.pre_arrive_qty = pd.arrive_qty;
+                    d.pre_arrive_tp = pd.arrive_tp;
+                    d.pre_deduct_qty = pd.deduct_qty;
+                    d.pre_deduct_tp = pd.deduct_tp;
+                }
+            }
+            return data;
+        }
+
+        async getStageBonus(tid, sid, fields) {
+            await this.ctx.service.tender.checkTender(tid);
+            await this.ctx.service.stage.checkStage(sid);
+
+            const data = await ctx.service.stageBonus.getEndStageData(this.ctx.stage.order);
+            return data;
+        }
+
+        async getStageOther(tid, sid, fields) {
+            await this.ctx.service.tender.checkTender(tid);
+            await this.ctx.service.stage.checkStage(sid);
+
+
+            const data = await this.ctx.service.stageOther.getStageData(this.ctx.stage.id);
+            const preData = await this.ctx.service.stageOther.getPreStageData(this.ctx.stage.order);
             for (const d of data) {
-                //const precision = this.ctx.helper.findPrecision(this.ctx.tender.info.precision, d.unit);
-                d.o_qty = d.oamount;
-                d.o_tp = this.ctx.helper.mul(d.o_qty, d.unit_price, decimal.tp);
-                d.c_qty = d.camount;
-                d.c_tp = this.ctx.helper.mul(d.c_qty, d.unit_price, decimal.tp);
-                d.s_qty = d.samount ? parseFloat(d.samount) : 0;
-                d.s_tp = this.ctx.helper.mul(d.s_qty, d.unit_price, decimal.tp);
+                const pd = this.ctx.helper._.find(preData, {uuid: d.uuid});
+                if (pd) {
+                    d.pre_tp = pd.tp;
+                }
             }
             return data;
         }

+ 76 - 2
app/service/revise_audit.js

@@ -59,6 +59,28 @@ module.exports = app => {
         }
 
         /**
+         * 获取 审核列表信息(修订列表页审批流程用)
+         *
+         * @param {Number} rid - 修订id
+         * @param {Number} times - 第几次审批
+         * @returns {Promise<*>}
+         */
+        async getAuditors2ReviseList(rid, times = 1) {
+            const sql = 'SELECT la.`audit_id`, pa.`name`, pa.`company`, pa.`role`, pa.`mobile`, pa.`telephone`, la.`times`, la.`audit_order`, la.`status`, la.`opinion`, la.`begin_time`, la.`end_time`, g.`sort` ' +
+                'FROM ?? AS la, ?? AS pa, (SELECT `audit_id`,(@i:=@i+1) as `sort` FROM ??, (select @i:=0) as it WHERE `rid` = ? AND `times` = ? GROUP BY `audit_id`) as g ' +
+                'WHERE la.`rid` = ? and la.`times` = ? and la.`audit_id` = pa.`id` and g.`audit_id` = la.`audit_id` order by la.`audit_order`';
+            const sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, this.tableName, rid, times, rid, times];
+            const result = await this.db.query(sql, sqlParam);
+            const sql2 = 'SELECT COUNT(a.`audit_id`) as num FROM (SELECT `audit_id` FROM ?? WHERE `rid` = ? AND `times` = ? GROUP BY `audit_id`) as a';
+            const sqlParam2 = [this.tableName, rid, times];
+            const count = await this.db.queryOne(sql2, sqlParam2);
+            for (const i in result) {
+                result[i].max_sort = count.num;
+            }
+            return result;
+        }
+
+        /**
          * 获取标段当前审核人
          *
          * @param {Number} reviseId - 修订id
@@ -353,7 +375,7 @@ module.exports = app => {
                 '  Left Join ' + this.ctx.service.ledgerRevise.tableName + ' As r On ra.rid = r.id' +
                 '  Left Join '+ this.ctx.service.tender.tableName +' AS t On r.tid = t.id' +
                 '  Left Join ' + this.ctx.service.projectAccount.tableName + ' As p On ra.audit_id = p.id' +
-                '  WHERE ((ra.`audit_id` = ? and ra.`status` = ?) OR' +
+                '  WHERE r.`valid` != 0 and ((ra.`audit_id` = ? and ra.`status` = ?) OR' +
                 '    (r.`uid` = ? and r.`status` = ? and ra.`status` = ? and ra.`times` = (r.`times`-1)))';
             const sqlParam = [auditorId, auditConst.status.checking, auditorId, auditConst.status.checkNo, auditConst.status.checkNo];
             return await this.db.query(sql, sqlParam);
@@ -381,7 +403,59 @@ module.exports = app => {
             const sqlParam = [auditorId, noticeTime, projectId];
             return await this.db.query(sql, sqlParam);
         }
+
+        /**
+         * 获取最新的审批人状态
+         *
+         * @param {Number} rid - 修订id
+         * @param {Number} status - 修订状态
+         * @param {Number} times - 修订次数
+         * @return {Promise<boolean>}
+         */
+        async getAuditorByStatus(rid, status, times = 1) {
+            let auditor = null;
+            let sql = '';
+            let sqlParam = '';
+            switch (status) {
+                case auditConst.status.checking :
+                case auditConst.status.checked :
+                case auditConst.status.checkNoPre :
+                    sql = 'SELECT la.`audit_id`, pa.`name`, pa.`company`, pa.`role`, la.`times`, la.`rid`, la.`audit_order` ' +
+                        'FROM ?? AS la, ?? AS pa ' +
+                        'WHERE la.`rid` = ? and la.`status` = ? and la.`audit_id` = pa.`id` order by la.`times` desc, la.`id` desc';
+                    sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, rid, status];
+                    auditor = await this.db.queryOne(sql, sqlParam);
+                    break;
+                case auditConst.status.checkNo :
+                    sql = 'SELECT la.`audit_id`, pa.`name`, pa.`company`, pa.`role`, la.`times`, la.`rid`, la.`audit_order` ' +
+                        'FROM ?? AS la, ?? AS pa ' +
+                        'WHERE la.`rid` = ? and la.`status` = ? and la.`times` = ? and la.`audit_id` = pa.`id` order by la.`times` desc, la.`id` desc';
+                    sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, rid, auditConst.status.checkNo, parseInt(times) - 1];
+                    auditor = await this.db.queryOne(sql, sqlParam);
+                    break;
+                case auditConst.status.uncheck :
+                default:break;
+            }
+            return auditor;
+        }
+
+        /**
+         * 获取审核人流程列表
+         *
+         * @param auditorId
+         * @returns {Promise<*>}
+         */
+        async getAuditGroupByList(rid, times) {
+            const sql = 'SELECT la.`audit_id`, pa.`name`, pa.`company`, pa.`role`, la.`times`, la.`rid`, la.`audit_order` ' +
+                'FROM ?? AS la, ?? AS pa ' +
+                'WHERE la.`rid` = ? and la.`times` = ? and la.`audit_id` = pa.`id` GROUP BY la.`audit_id` ORDER BY la.`audit_order`';
+            const sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, rid, times];
+            return await this.db.query(sql, sqlParam);
+            // const sql = 'SELECT `tid`, `sid`, `aid`, `order` FROM ?? WHERE `sid` = ? and `times` = ? GROUP BY `aid`';
+            // const sqlParam = [this.tableName, stageId, times];
+            // return await this.db.query(sql, sqlParam);
+        }
     }
 
     return ReviseAudit;
-};
+};

+ 29 - 13
app/service/revise_pos.js

@@ -282,34 +282,48 @@ module.exports = app => {
             if (!(data instanceof Array)) throw '提交数据错误';
 
             const transaction = await this.db.beginTransaction();
-            const result = { ledger: {}, pos: null }, updateLid = [];
+            const result = { ledger: {}, pos: null };
             const orgPos = await this.getPosData({tid: tid, id: this._.map(data, 'id')});
-            let bills = null, precision = null;
+
+            const bills = await this.ctx.service.reviseBills.getDataById(data[0].lid);
+            const precision = this.ctx.helper.findPrecision(this.ctx.tender.info.precision, bills.unit);
+            const updateBills = {id: bills.id};
+            const billsPos = await this.getAllDataByCondition({where: {tid: tid, lid: bills.id} });
+            for (const bp of billsPos) {
+                const d = data.find(function (x) {
+                    return bp.id ? x.id === bp.id : false;
+                });
+                if (d) continue;
+                updateBills.sgfh_qty = this.ctx.helper.add(updateBills.sgfh_qty, bp.sgfh_qty);
+                updateBills.sjcl_qty = this.ctx.helper.add(updateBills.sjcl_qty, bp.sjcl_qty);
+                updateBills.qtcl_qty = this.ctx.helper.add(updateBills.qtcl_qty, bp.qtcl_qty);
+                updateBills.quantity = this.ctx.helper.add(updateBills.quantity, bp.quantity);
+            }
+
             try {
                 for (const d of data) {
                     const op = d.id ? this._.find(orgPos, {id: d.id}) : null;
                     if (d.sgfh_qty || d.sjcl_qty || d.qtcl_qty) {
-                        if (!bills || bills.id !== d.lid) {
-                            bills = await this.ctx.service.reviseBills.getDataById(d.lid);
-                            precision = this.ctx.helper.findPrecision(this.ctx.tender.info.precision, bills.unit);
-                            updateLid.push(d.lid);
-                        }
                         if (d.sgfh_qty !== undefined) {
                             d.sgfh_qty = this.round(d.sgfh_qty, precision.value);
                         } else if (op) {
                             d.sgfh_qty = op.sgfh_qty;
                         }
+                        updateBills.sgfh_qty = this.ctx.helper.add(updateBills.sgfh_qty, d.sgfh_qty);
                         if (d.sjcl_qty !== undefined) {
                             d.sjcl_qty = this.round(d.sjcl_qty, precision.value);
                         } else if (op) {
                             d.sjcl_qty = op.sjcl_qty;
                         }
+                        updateBills.sjcl_qty = this.ctx.helper.add(updateBills.sjcl_qty, d.sjcl_qty);
                         if (d.qtcl_qty) {
                             d.qtcl_qty = this.round(d.qtcl_qty, precision.value);
                         } else if (op) {
                             d.qtcl_qty = op.qtcl_qty;
                         }
+                        updateBills.qtcl_qty = this.ctx.helper.add(updateBills.qtcl_qty, d.qtcl_qty);
                         d.quantity = this.ctx.helper.sum([d.sgfh_qty, d.qtcl_qty, d.sjcl_qty]);
+                        updateBills.quantity = this.ctx.helper.add(updateBills.quantity, d.quantity);
                     }
                     if (d.id) {
                         await transaction.update(this.tableName, d);
@@ -317,18 +331,20 @@ module.exports = app => {
                         this._insertPosData(transaction, d, tid, rid);
                     }
                 }
-                for (const lid of updateLid) {
-                    await this.ctx.service.reviseBills.calc(tid, lid, transaction);
-                }
+                const info = this.ctx.tender.info;
+                updateBills.sgfh_tp = this.ctx.helper.mul(updateBills.sgfh_qty, bills.unit_price, info.decimal.tp);
+                updateBills.sjcl_tp = this.ctx.helper.mul(updateBills.sjcl_qty, bills.unit_price, info.decimal.tp);
+                updateBills.qtcl_tp = this.ctx.helper.mul(updateBills.qtcl_qty, bills.unit_price, info.decimal.tp);
+                updateBills.total_price = this.ctx.helper.mul(updateBills.quantity, bills.unit_price, info.decimal.tp);
+                await transaction.update(this.ctx.service.reviseBills.tableName, updateBills);
+                updateBills.ledger_id = bills.ledger_id;
                 await transaction.commit();
             } catch (err) {
                 await transaction.rollback();
                 throw err;
             }
             result.pos = data;
-            if (updateLid.length > 0) {
-                result.ledger.update = await this.ctx.service.reviseBills.getDataById(updateLid);
-            }
+            result.ledger.update = [updateBills];
             return result;
         }
     }

+ 34 - 0
app/service/rpt_custom_define.js

@@ -0,0 +1,34 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date
+ * @version
+ */
+
+module.exports = app => {
+    class RptCustomDefine extends app.BaseService {
+        /**
+         * 构造函数
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        constructor(ctx) {
+            super(ctx);
+            this.tableName = 'rpt_custom_define';
+        }
+
+        async getCustomDefine(tid, sid, rid) {
+            const data = await this.getDataByCondition({ tid: tid, sid: sid, rid: rid });
+            if (data && data.audit_select) {
+                data.audit_select = JSON.parse(data.audit_select);
+            }
+            return data;
+        }
+    }
+
+    return RptCustomDefine;
+};

+ 2 - 0
app/service/stage.js

@@ -272,6 +272,8 @@ module.exports = app => {
                 if (preStage) {
                     const jgclResult = await this.ctx.service.stageJgcl.addInitialStageData(newStage, preStage, transaction);
                     if (!jgclResult) throw '初始化甲供材料数据失败';
+                    const otherResult = await this.ctx.service.stageOther.addInitialStageData(newStage, preStage, transaction);
+                    if (!otherResult) throw '初始化其他台账数据失败';
                 }
 
                 await transaction.commit();

+ 24 - 1
app/service/stage_audit.js

@@ -214,6 +214,7 @@ module.exports = app => {
                 await this.ctx.service.stagePay.copyAuditStagePays(this.ctx.stage, this.ctx.stage.times, 1, transaction);
                 await this.ctx.service.stageJgcl.updateHistory(this.ctx.stage, transaction);
                 await this.ctx.service.stageBonus.updateHistory(this.ctx.stage, transaction);
+                await this.ctx.service.stageOther.updateHistory(this.ctx.stage, transaction);
                 // 更新期数据
                 const tpData = await this.ctx.service.stageBills.getSumTotalPrice(this.ctx.stage);
                 await transaction.update(this.ctx.service.stage.tableName, {
@@ -268,6 +269,7 @@ module.exports = app => {
                     await this.ctx.service.stagePay.copyAuditStagePays(this.ctx.stage, this.ctx.stage.times, nextAudit.order, transaction);
                     await this.ctx.service.stageJgcl.updateHistory(this.ctx.stage, transaction);
                     await this.ctx.service.stageBonus.updateHistory(this.ctx.stage, transaction);
+                    await this.ctx.service.stageOther.updateHistory(this.ctx.stage, transaction);
                     // 流程至下一审批人
                     await transaction.update(this.tableName, {id: nextAudit.id, status: auditConst.status.checking, begin_time: time});
                     // 同步 期信息
@@ -445,7 +447,7 @@ module.exports = app => {
                     id: stageId,
                     contract_tp: tpData.contract_tp,
                     qc_tp: tpData.qc_tp,
-                    times: times + 1,
+                    times: times,
                     yf_tp: yfPay.tp,
                     cache_time_r: this.ctx.stage.cache_time_l,
                 });
@@ -475,6 +477,7 @@ module.exports = app => {
                 await this.ctx.service.stagePay.copyAuditStagePays(this.ctx.stage, this.ctx.stage.times, audit.order + 1, transaction);
                 await this.ctx.service.stageJgcl.updateHistory(this.ctx.stage, transaction);
                 await this.ctx.service.stageBonus.updateHistory(this.ctx.stage, transaction);
+                await this.ctx.service.stageOther.updateHistory(this.ctx.stage, transaction);
 
                 // 同步 期信息
                 await transaction.update(this.ctx.service.stage.tableName, {
@@ -684,6 +687,7 @@ module.exports = app => {
                 await this.ctx.service.stagePay.copyAuditStagePays(this.ctx.stage, this.ctx.stage.times, audit.order + 2, transaction);
                 await this.ctx.service.stageJgcl.updateHistory(this.ctx.stage, transaction);
                 await this.ctx.service.stageBonus.updateHistory(this.ctx.stage, transaction);
+                await this.ctx.service.stageOther.updateHistory(this.ctx.stage, transaction);
 
                 // 本期结束
                 // 生成截止本期数据 final数据
@@ -774,6 +778,25 @@ module.exports = app => {
         }
 
         /**
+         * 获取审核人流程列表
+         *
+         * @param auditorId
+         * @returns {Promise<*>}
+         */
+        async getAuditGroupByListWithOwner(stageId, times) {
+            const result = await this.getAuditGroupByList(stageId, times);
+            const sql = 'SELECT pa.`id` As aid, pa.`name`, pa.`company`, pa.`role`, ? As times, ? As sid, 0 As `order`' +
+                '  FROM ' + this.ctx.service.stage.tableName + ' As s' +
+                '  LEFT JOIN ' + this.ctx.service.projectAccount.tableName + ' As pa' +
+                '  ON s.user_id = pa.id' +
+                '  WHERE s.id = ?';
+            const sqlParam = [times, stageId, stageId];
+            const user = await this.db.queryOne(sql, sqlParam);
+            result.unshift(user);
+            return result;
+        }
+
+        /**
          * 复制上一期的审批人列表给最新一期
          *
          * @param transaction - 新增一期的事务

+ 20 - 8
app/service/stage_bonus.js

@@ -8,6 +8,7 @@
  * @version
  */
 
+const auditConst = require('../const/audit').stage;
 module.exports = app => {
     class StageBonus extends app.BaseService {
         /**
@@ -23,6 +24,15 @@ module.exports = app => {
 
         async getStageData(sid) {
             const data = await this.getAllDataByCondition({where: { sid: sid }});
+            if (this.ctx.stage && this.ctx.stage.readOnly && this.ctx.stage.status !== auditConst.status.checked) {
+                for (const d of data) {
+                    const his = d.shistory ? JSON.parse(d.shistory) : [];
+                    const h = this.ctx.helper._.find(his, {
+                        stimes: this.ctx.stage.curTimes, sorder: this.ctx.stage.curOrder
+                    });
+                    d.tp = h ? h.tp : null;
+                }
+            }
             return data;
         }
 
@@ -68,8 +78,10 @@ module.exports = app => {
 
         async _delDatas (data) {
             const datas = data instanceof Array ? data : [data];
-            const orgDatas = await this.getAllDataByCondition({sid: this.ctx.stage.id, id: this.ctx.helper._.map(datas, 'id')});
+            const orgDatas = await this.getAllDataByCondition({where: {sid: this.ctx.stage.id, id: this.ctx.helper._.map(datas, 'id')}});
             for (const od of orgDatas) {
+                console.log(od);
+                console.log(this.ctx.stage.id);
                 if (od.sid !== this.ctx.stage.id) throw '非本期新增数据,不可删除';
             }
             await this.db.delete(this.tableName, {id: datas});
@@ -86,13 +98,13 @@ module.exports = app => {
                 if (!od) continue;
 
                 const nd = {id: od.id};
-                if (d.name) nd.name = d.name;
-                if (d.tp) nd.tp = this.ctx.helper.round(d.tp, this.ctx.tender.info.decimal.tp);
-                if (d.code) nd.code = d.code;
-                if (d.proof) nd.proof = d.proof;
-                if (d.real_time) nd.real_time = d.real_time;
-                if (d.memo) nd.memo = d.memo;
-                if (d.order) nd.order = d.order;
+                if (d.name !== undefined) nd.name = d.name;
+                if (d.tp !== undefined) nd.tp = this.ctx.helper.round(d.tp, this.ctx.tender.info.decimal.tp);
+                if (d.code !== undefined) nd.code = d.code;
+                if (d.proof !== undefined) nd.proof = d.proof;
+                if (d.real_time !== undefined) nd.real_time = new Date(d.real_time);
+                if (d.memo !== undefined) nd.memo = d.memo;
+                if (d.order !== undefined) nd.order = d.order;
                 uDatas.push(nd);
                 console.log(nd);
             }

+ 40 - 14
app/service/stage_jgcl.js

@@ -8,6 +8,7 @@
  * @version
  */
 
+const auditConst = require('../const/audit').stage;
 module.exports = app => {
     class StageJgcl extends app.BaseService {
         /**
@@ -23,11 +24,32 @@ module.exports = app => {
 
         async getStageData(sid) {
             const data = await this.getAllDataByCondition({where: { sid: sid }});
+            if (this.ctx.stage && this.ctx.stage.readOnly && this.ctx.stage.status !== auditConst.status.checked) {
+                for (const d of data) {
+                    const his = d.shistory ? JSON.parse(d.shistory) : [];
+                    const h = this.ctx.helper._.find(his, {
+                        stimes: this.ctx.stage.curTimes, sorder: this.ctx.stage.curOrder
+                    });
+                    if (h) {
+                        d.arrive_qty = h.arrive_qty;
+                        d.arrive_tp = h.arrive_tp;
+                        d.deduct_qty = h.deduct_qty;
+                        d.deduct_tp = h.deduct_tp;
+                    } else {
+                        d.arrive_qty = null;
+                        d.arrive_tp = null;
+                        d.deduct_qty = null;
+                        d.deduct_tp = null;
+                    }
+                }
+            }
             return data;
         }
 
         async getPreStageData(sorder) {
-            const sql = 'SELECT c.uuid, Sum(c.arrive_qty) as arrive_qty, Sum(c.arrive_tp) as arrive_tp, Sum(c.deduct_qty) as deduct_qty, Sum(c.deduct_tp) as deduct_tp From ' + this.tableName + ' c' +
+            const sql = 'SELECT c.uuid, Sum(c.arrive_qty) as arrive_qty, Sum(c.arrive_tp) as arrive_tp,' +
+                '    Sum(c.deduct_qty) as deduct_qty, Sum(c.deduct_tp) as deduct_tp' +
+                '  From ' + this.tableName + ' c' +
                 '  LEFT JOIN ' + this.ctx.service.stage.tableName + ' s ON s.id = c.sid' +
                 '  WHERE s.`order` < ? And s.`tid` = ?' +
                 '  GROUP By uuid';
@@ -37,7 +59,9 @@ module.exports = app => {
         }
 
         async getEndStageData(sorder) {
-            const sql = 'SELECT c.uuid, Sum(c.arrive_qty) as arrive_qty, Sum(c.arrive_tp) as arrive_tp, Sum(c.deduct_qty) as deduct_qty, Sum(c.deduct_tp) as deduct_tp From ' + this.tableName + ' c' +
+            const sql = 'SELECT c.uuid, Sum(c.arrive_qty) as arrive_qty, Sum(c.arrive_tp) as arrive_tp,' +
+                '    Sum(c.deduct_qty) as deduct_qty, Sum(c.deduct_tp) as deduct_tp' +
+                '  From ' + this.tableName + ' c' +
                 '  LEFT JOIN ' + this.ctx.service.stage.tableName + ' s ON s.id = c.sid' +
                 '  WHERE s.`order` <= ? And s.`tid` = ?' +
                 '  GROUP By uuid';
@@ -84,7 +108,7 @@ module.exports = app => {
 
         async _delDatas (data) {
             const datas = data instanceof Array ? data : [data];
-            const orgDatas = await this.getAllDataByCondition({sid: this.ctx.stage.id, id: this.ctx.helper._.map(datas, 'id')});
+            const orgDatas = await this.getAllDataByCondition({where: {sid: this.ctx.stage.id, id: this.ctx.helper._.map(datas, 'id')} });
             for (const od of orgDatas) {
                 if (od.pre_used) throw '甲供材料往期已经计量,不可删除';
             }
@@ -105,27 +129,29 @@ module.exports = app => {
                 const nd = {id: od.id};
                 if (d.name) nd.name = d.name;
                 if (od.pre_used === null || od.pre_used === undefined || od.pre_used === 0) {
-                    if (d.unit) nd.unit = d.unit;
-                    nd.unit_price = d.unit_price ? this.ctx.helper.round(d.unit_price, info.decimal.up) : od.unit_price;
+                    if (d.unit !== undefined) nd.unit = d.unit;
+                    nd.unit_price = d.unit_price !== undefined ? this.ctx.helper.round(d.unit_price, info.decimal.up) : od.unit_price;
+                } else {
+                    nd.unit_price = od.unit_price;
                 }
                 const precision = this.ctx.helper.findPrecision(info.precision, d.unit_price);
-                if (d.arrive_qty) {
+                if (d.arrive_qty !== undefined) {
                     nd.arrive_qty = this.ctx.helper.round(d.arrive_qty, precision.value);
                     nd.arrive_tp = this.ctx.helper.mul(nd.unit_price, nd.arrive_qty, info.decimal.tp);
-                } else if (d.unit_price) {
+                } else if (d.unit_price !== undefined) {
                     nd.arrive_tp = this.ctx.helper.mul(nd.unit_price, od.arrive_qty, info.decimal.tp);
                 }
-                if (d.deduct_qty) {
+                if (d.deduct_qty !== undefined) {
                     nd.deduct_qty = this.ctx.helper.round(d.deduct_qty, precision.value);
                     nd.deduct_tp = this.ctx.helper.mul(nd.unit_price, nd.deduct_qty, info.decimal.tp);
-                } else if (d.unit_price) {
+                } else if (d.unit_price !== undefined) {
                     nd.deduct_tp = this.ctx.helper.mul(nd.unit_price, od.deduct_qty, info.decimal.tp);
                 }
-                if (d.source) nd.source = d.source;
-                if (d.bills_code) nd.bills_code = d.bills_code;
-                if (d.check_code) nd.check_code = d.check_code;
-                if (d.memo) nd.memo = d.memo;
-                if (d.order) nd.order = d.order;
+                if (d.source !== undefined) nd.source = d.source;
+                if (d.bills_code !== undefined) nd.bills_code = d.bills_code;
+                if (d.check_code !== undefined) nd.check_code = d.check_code;
+                if (d.memo !== undefined) nd.memo = d.memo;
+                if (d.order !== undefined) nd.order = d.order;
                 uDatas.push(nd);
             }
             if (uDatas.length > 0) {

+ 40 - 17
app/service/stage_other.js

@@ -8,6 +8,7 @@
  * @version
  */
 
+const auditConst = require('../const/audit').stage;
 module.exports = app => {
     class StageOther extends app.BaseService {
         /**
@@ -23,22 +24,27 @@ module.exports = app => {
 
         async getStageData(sid) {
             const data = await this.getAllDataByCondition({where: { sid: sid }});
+            if (this.ctx.stage && this.ctx.stage.readOnly && this.ctx.stage.status !== auditConst.status.checked) {
+                for (const d of data) {
+                    const his = d.shistory ? JSON.parse(d.shistory) : [];
+                    const h = this.ctx.helper._.find(his, {
+                        stimes: this.ctx.stage.curTimes, sorder: this.ctx.stage.curOrder
+                    });
+                    d.tp = h ? h.tp : null;
+                }
+            }
             return data;
         }
 
         async getPreStageData(sorder) {
-            const sql = 'SELECT c.uuid, Sum(c.tp) as arrive_tp From ' + this.tableName +
-                '  WHERE s.`sorder` < ? And s.`tid` = ?' +
-                '  GROUP By uuid';
+            const sql = 'SELECT uuid, Sum(tp) as tp From ' + this.tableName + ' WHERE sorder < ? And tid = ? GROUP By uuid';
             const sqlParam = [sorder, this.ctx.tender.id];
             const data = await this.db.query(sql, sqlParam);
             return data;
         }
 
         async getEndStageData(sorder) {
-            const sql = 'SELECT c.uuid, Sum(c.tp) as tp ' + this.tableName +
-                '  WHERE s.`order` <= ? And s.`tid` = ?' +
-                '  GROUP By uuid';
+            const sql = 'SELECT uuid, Sum(tp) as tp From' + this.tableName + ' WHERE sorder <= ? And tid = ? GROUP By uuid';
             const sqlParam = [sorder, this.ctx.tender.id];
             const data = await this.db.query(sql, sqlParam);
             return data;
@@ -53,13 +59,16 @@ module.exports = app => {
                     uuid: this.uuid.v4(),
                     add_sid: this.ctx.stage.id,
                     add_uid: this.ctx.session.sessionUser.accountId,
+                    add_time: new Date(),
                     sid: this.ctx.stage.id,
                     sorder: this.ctx.stage.order,
                     tid: this.ctx.tender.id,
-                    create_time: new Date(),
                 };
                 nd.name = d.name;
                 nd.order = d.order;
+
+                if (d.total_price) nd.total_price = this.ctx.helper.round(d.total_price, this.ctx.tender.info.decimal.tp);
+                if (d.tp) nd.tp = this.ctx.helper.round(d.tp, this.ctx.tender.info.decimal.tp);
                 if (d.real_time) nd.real_time = d.real_time;
                 if (d.memo) nd.memo = d.memo;
                 insertData.push(nd);
@@ -72,7 +81,7 @@ module.exports = app => {
 
         async _delDatas (data) {
             const datas = data instanceof Array ? data : [data];
-            const orgDatas = await this.getAllDataByCondition({sid: this.ctx.stage.id, id: this.ctx.helper._.map(datas, 'id')});
+            const orgDatas = await this.getAllDataByCondition({where: {sid: this.ctx.stage.id, id: this.ctx.helper._.map(datas, 'id')} });
             for (const od of orgDatas) {
                 if (od.pre_used) throw '往期已经计量,不可删除';
             }
@@ -90,11 +99,18 @@ module.exports = app => {
                 if (!od) continue;
 
                 const nd = {id: od.id};
-                if (d.name) nd.name = d.name;
-                if (d.order) nd.order = d.order;
-                if (d.tp) nd.tp = this.ctx.helper.round(d.tp, this.ctx.tender.info.decimal.tp);
-                if (d.real_time) nd.real_time = d.real_time;
-                if (d.memo) nd.memo = d.memo;
+                if (d.name !== undefined) {
+                    if (od.pre_used) throw '往期已使用,不可修改名称';
+                    nd.name = d.name;
+                }
+                if (d.order !== undefined) nd.order = d.order;
+                if (d.total_price !== undefined) {
+                    if (od.pre_used) throw '往期已使用,不可修改金额';
+                    nd.total_price = this.ctx.helper.round(d.total_price, this.ctx.tender.info.decimal.tp);
+                }
+                if (d.tp !== undefined) nd.tp = this.ctx.helper.round(d.tp, this.ctx.tender.info.decimal.tp);
+                if (d.real_time !== undefined) nd.real_time = new Date(d.real_time);
+                if (d.memo !== undefined) nd.memo = d.memo;
                 uDatas.push(nd);
             }
             if (uDatas.length > 0) {
@@ -119,8 +135,12 @@ module.exports = app => {
                 }
                 return result;
             } catch (err) {
-                if (err) result.err = err;
-                return result;
+                if (err.stack) {
+                    throw err;
+                } else {
+                    result.err = err.toString();
+                    return result;
+                }
             }
         }
 
@@ -135,8 +155,11 @@ module.exports = app => {
                 const his = this.ctx.helper._.find(datas, filter);
                 if (his) {
                     his.tp = d.tp;
+                    if (d.sid === d.add_sid) his.total_price = d.total_price;
                 } else {
-                    history.push({ stimes: this.ctx.stage.curTimes, sorder: this.ctx.stage.curOrder, tp: d.tp });
+                    const nHis = { stimes: this.ctx.stage.curTimes, sorder: this.ctx.stage.curOrder, tp: d.tp };
+                    if (d.sid === d.add_sid) nHis.total_price = d.total_price;
+                    history.push(nHis);
                 }
                 updateDatas.push({ id: d.id, shistory: JSON.stringify(history) });
             }
@@ -165,4 +188,4 @@ module.exports = app => {
     }
 
     return StageOther;
-};
+};

+ 33 - 18
app/service/tender.js

@@ -123,12 +123,17 @@ module.exports = app => {
                     // 参与审批 变更令 的标段
                     '    OR (t.`ledger_status` = ' + auditConst.ledger.status.checked + ' AND ' +
                     '        t.id IN ( SELECT ca.`tid` FROM ?? AS ca WHERE ca.`uid` = ? GROUP BY ca.`tid`))' +
+                    // 参与审批 台账修订 的标段
+                    '    OR (t.`ledger_status` = ' + auditConst.ledger.status.checked + ' AND ' +
+                    '        t.id IN ( SELECT ra.`tender_id` FROM ?? AS ra WHERE ra.`audit_id` = ? GROUP BY ra.`tender_id`))' +
                     // 未参与,但可见的标段
                     ')';
                 sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, session.sessionProject.id, session.sessionUser.accountId,
                     this.ctx.service.ledgerAudit.tableName, session.sessionUser.accountId,
                     this.ctx.service.stageAudit.tableName, session.sessionUser.accountId,
-                    this.ctx.service.changeAudit.tableName, session.sessionUser.accountId];
+                    this.ctx.service.changeAudit.tableName, session.sessionUser.accountId,
+                    this.ctx.service.reviseAudit.tableName, session.sessionUser.accountId,
+                ];
             }
             const list = await this.db.query(sql, sqlParam);
             for (const l of list) {
@@ -254,22 +259,32 @@ module.exports = app => {
         async deleteTenderNoBackup(id) {
             const transaction = await this.db.beginTransaction();
             try {
-                await transaction.delete(this.tableName, {id: id});
-                await transaction.delete(this.ctx.service.tenderInfo.tableName, {tid: id});
-                await transaction.delete(this.ctx.service.ledger.tableName, {tender_id: id});
-                await transaction.delete(this.ctx.service.ledgerAudit.tableName, {tender_id: id});
-                await transaction.delete(this.ctx.service.pos.tableName, {tid: id});
-                await transaction.delete(this.ctx.service.pay.tableName, {tid: id});
-                await transaction.delete(this.ctx.service.stage.tableName, {tid: id});
-                await transaction.delete(this.ctx.service.stageAudit.tableName, {tid: id});
-                await transaction.delete(this.ctx.service.stageBills.tableName, {tid: id});
-                await transaction.delete(this.ctx.service.stagePos.tableName, {tid: id});
-                await transaction.delete(this.ctx.service.stageDetail.tableName, {tid: id});
-                await transaction.delete(this.ctx.service.stagePay.tableName, {tid: id});
-                await transaction.delete(this.ctx.service.change.tableName, {tid: id});
-                await transaction.delete(this.ctx.service.changeAudit.tableName, {tid: id});
-                await transaction.delete(this.ctx.service.changeAuditList.tableName, {tid: id});
-                await transaction.delete(this.ctx.service.changeCompany.tableName, {tid: id});
+                await transaction.delete(this.tableName, { id });
+                await transaction.delete(this.ctx.service.tenderInfo.tableName, { tid: id });
+                await transaction.delete(this.ctx.service.ledger.tableName, { tender_id: id });
+                await transaction.delete(this.ctx.service.ledgerAudit.tableName, { tender_id: id });
+                await transaction.delete(this.ctx.service.pos.tableName, { tid: id });
+                await transaction.delete(this.ctx.service.pay.tableName, { tid: id });
+
+                await transaction.delete(this.ctx.service.stage.tableName, { tid: id });
+                await transaction.delete(this.ctx.service.stageAudit.tableName, { tid: id });
+                await transaction.delete(this.ctx.service.stageBills.tableName, { tid: id });
+                await transaction.delete(this.ctx.service.stagePos.tableName, { tid: id });
+                await transaction.delete(this.ctx.service.stageDetail.tableName, { tid: id });
+                await transaction.delete(this.ctx.service.stagePay.tableName, { tid: id });
+
+                await transaction.delete(this.ctx.service.change.tableName, { tid: id });
+                await transaction.delete(this.ctx.service.changeAudit.tableName, { tid: id });
+                await transaction.delete(this.ctx.service.changeAuditList.tableName, { tid: id });
+                await transaction.delete(this.ctx.service.changeCompany.tableName, { tid: id });
+
+                await transaction.delete(this.ctx.service.ledgerRevise.tableName, { tid: id });
+                await transaction.delete(this.ctx.service.reviseAudit.tableName, { tender_id: id });
+                await transaction.delete(this.ctx.service.reviseBills.tableName, { tender_id: id });
+                await transaction.delete(this.ctx.service.revisePos.tableName, { tid: id });
+
+                await transaction.delete(this.ctx.service.signatureUsed.tableName, { tender_id: id });
+                await transaction.delete(this.ctx.service.signatureRole.tableName, { tender_id: id });
                 // 先删除附件文件
                 const attList = await this.ctx.service.changeAtt.getAllDataByCondition({ where: { tid: id } });
                 if (attList.length !== 0) {
@@ -279,7 +294,7 @@ module.exports = app => {
                         }
                     }
                 }
-                await transaction.delete(this.ctx.service.changeAtt.tableName, {tid: id});
+                await transaction.delete(this.ctx.service.changeAtt.tableName, { tid: id });
                 await transaction.commit();
                 return true;
             } catch (err) {

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

@@ -163,7 +163,7 @@
             <% if (auditStatus === 1 || auditStatus === 2) { %>
                 <div class="ml-auto px-4" id="show-save-btn" style="display: none">
                     <span>您修改了变更内容,记得保存修改。</span>
-                    <button class="btn btn-sm btn-primary" id="save_change"><i class="fa fa-save"></i> 保存修改</button>
+                    <button class="btn btn-sm btn-primary save_change_btn" id="save_change"><i class="fa fa-save"></i> 保存修改</button>
                     <button class="btn btn-sm btn-light" id="cancel_change">取消</button>
                 </div>
             <% } %>

+ 2 - 1
app/view/change/info_modal.ejs

@@ -84,9 +84,10 @@
                     </div>
                 </div>
                 <div class="modal-footer">
+                    <button type="button" class="btn btn-success btn-sm save_change_btn">保存审批流程</button>
                     <button type="button" class="btn btn-secondary btn-sm" data-dismiss="modal">关闭</button>
                     <button type="button" data-sumbit="sumbit_change" data-category="up_change" class="btn btn-primary btn-sm up-change">确认上报</button>
-                    <button type="button" data-sumbit="sumbit_change" data-category="save_change" class="btn btn-success btn-sm save-change">保存修改</button>
+                    <!--<button type="button" data-sumbit="sumbit_change" data-category="save_change" class="btn btn-success btn-sm save-change">保存修改</button>-->
                 </div>
             </form>
         </div>

+ 2 - 2
app/view/login/login.ejs

@@ -14,8 +14,8 @@
 <div class="container">
     <!--演示版-->
     <form class="form-signin" method="post" action="/login">
-        <h4 class="text-center mb-0">纵横云计量</h4>
-        <p class="text-center mb-4 text-muted" id="project_name"></p>
+        <h4 class="text-center mb-2">纵横云计量</h4>
+        <h5 class="text-center mb-4 text-muted" id="project_name"></h5>
         <!--<nav class="nav nav-tabs nav-justified mb-3" role="tablist" id="login-tab">-->
             <!--<a class="nav-item nav-link" data-toggle="tab" data-type="1" href="#preview" role="tab">演示版登录</a>-->
             <!--<a class="nav-item nav-link active" data-toggle="tab" data-type="2" href="#paid" role="tab">项目版登录</a>-->

+ 9 - 0
app/view/report/index.ejs

@@ -125,6 +125,14 @@
                                     翻页
                                 </div>
                             </div>
+                            <div class="panel" id="pnl_audit_select" style="display: none;">
+                                <div class="panel-body">
+                                    <button class="btn btn-outline-primary btn-sm" type="button" data-toggle="modal" data-target="#audit-select">
+                                        <i class="fa fa-pencil"></i><br>
+                                        审批人选择
+                                    </button>
+                                </div>
+                            </div>
                         </div>
                     </div>
                     <div class="sjs-height-4">
@@ -191,6 +199,7 @@
 <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_custom.js"></script>
 <script type="text/javascript" src="/public/report/js/rpt_jspdf.js"></script>
 
 <script type="text/javascript">

+ 24 - 0
app/view/report/rpt_all_popup.ejs

@@ -243,6 +243,30 @@
         </div>
     </div>
 </div>
+<!--选择审批人-->
+<div class="modal fade" id="audit-select" data-backdrop="static">
+    <div class="modal-dialog" role="document">
+        <div class="modal-content">
+            <div class="modal-header">
+                <h5 class="modal-title" id="audit-select-title">重置</h5>
+                <button type="button" class="close" data-dismiss="modal" aria-label="Close">
+                    <span aria-hidden="true">&times;</span>
+                </button>
+            </div>
+            <div class="modal-body">
+                <table class="table table-bordered">
+                    <tr><th style="width: 50%">报表数据</th><th>审批人</th></tr>
+                    <tbody id="audit-select-list">
+                    </tbody>
+                </table>
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-secondary btn-sm" data-dismiss="modal">关闭</button>
+                <button id="audit-select-ok" class="btn btn-primary btn-sm" onclick="rptCustomObj.resetAuditSelect(this)">确定</button>
+            </div>
+        </div>
+    </div>
+</div>
 
 <script>
     zTreeOprObj.getCustomerCfg();

+ 10 - 4
app/view/revise/index.ejs

@@ -42,13 +42,18 @@
                         <td><%- lr.corder %></td>
                         <td><%- lr.in_time ? lr.in_time.toLocaleDateString() : '' %></td>
                         <td><%- lr.user_name %></td>
-                        <td <% if (!lr.valid) {%>class="text-danger"<% } %>>
-                            <% if (lr.valid) { %>
-                            <%- auditConst.statusString[lr.status] %>
-                            <% } else { %>
+                        <% if (!lr.valid) {%>
+                        <td class="text-danger">
                             作废
+                        </td>
+                        <% } else { %>
+                        <td class="<%- auditConst.auditProgressClass[lr.status] %>">
+                            <% if (lr.curAuditor) { %>
+                                <a href="#sp-list" data-toggle="modal" data-target="#sp-list" lr-id="<%- lr.id %>"><%- lr.curAuditor.name %><%if (lr.curAuditor.role !== '' && lr.curAuditor.role !== null) { %>-<%- lr.curAuditor.role %><% } %></a>
                             <% } %>
+                            <%- auditConst.auditProgress[lr.status] %>
                         </td>
+                        <% } %>
                         <td><%- lr.end_time ? lr.end_time.toLocaleDateString() : '' %></td>
                         <td>
                             <% if (lr.valid) { %>
@@ -76,6 +81,7 @@
     </div>
 </div>
 <script src="/public/js/sub_menu.js"></script>
+<script src="/public/js/moment/moment.min.js"></script>
 <script>
     $.subMenu({
         menu: '#sub-menu', miniMenu: '#sub-mini-menu', miniMenuList: '#mini-menu-list',

+ 164 - 2
app/view/revise/modal.ejs

@@ -16,7 +16,88 @@
         </div>
     </form>
 </div>
-
+<!--审批流程/结果-->
+<div class="modal fade" id="sp-list" data-backdrop="static">
+    <div class="modal-dialog modal-lg" role="document">
+        <div class="modal-content">
+            <div class="modal-header">
+                <h5 class="modal-title">审批流程</h5>
+            </div>
+            <div class="modal-body">
+                <div class="row">
+                    <div class="col-4">
+                        <div class="card mt-3">
+                            <ul class="list-group list-group-flush" id="auditor-list">
+                                <li class="list-group-item"><i class="fa fa fa-play-circle fa-rotate-90"></i> 布尔  <small class="text-muted">施工</small></li>
+                                <li class="list-group-item"><i class="fa fa-chevron-circle-down"></i> 张三  <small class="text-muted">监理</small></li>
+                                <li class="list-group-item"><i class="fa fa-chevron-circle-down"></i> 王五 <small class="text-muted">监理</small></li>
+                                <li class="list-group-item"><i class="fa fa fa-stop-circle"></i> 李四 <small class="text-muted">监理</small></li>
+                            </ul>
+                        </div>
+                    </div>
+                    <div class="col-8 modal-height-500" style="overflow: auto" id="auditor-list2">
+                        <div class="card mt-3">
+                            <ul class="list-group list-group-flush">
+                                <li class="list-group-item">
+                                    <span class="text-success pull-right">上报</span>
+                                    <h5 class="card-title"><i class="fa fa-play-circle fa-rotate-90 text-success"></i> 布尔 <small class="text-muted">施工</small></h5>
+                                    <p class="card-text">2017-11-25</p>
+                                </li>
+                                <li class="list-group-item">
+                                    <span class="text-success pull-right">审批通过</span>
+                                    <h5 class="card-title"><i class="fa fa-chevron-circle-down text-success"></i> 张三 <small class="text-muted">监理</small></h5>
+                                    <p class="card-text">审批意见。2017-11-25</p>
+                                </li>
+                                <li class="list-group-item">
+                                    <span class="text-success pull-right">审批通过</span>
+                                    <h5 class="card-title"><i class="fa fa-chevron-circle-down text-success"></i> 王五 <small class="text-muted">监理</small></h5>
+                                    <p class="card-text">审批通过。2017-11-26</p>
+                                </li>
+                                <li class="list-group-item">
+                                    <span class="text-warning pull-right">审批退回 布尔</span>
+                                    <h5 class="card-title"><i class="fa fa-stop-circle text-warning"></i> 李四 <small class="text-muted">监理</small></h5>
+                                    <p class="card-text">审批退回,审批意见文本。2017-11-27</p>
+                                </li>
+                            </ul>
+                        </div>
+                        <!--退回原报重新上报-->
+                        <div class="card mt-3">
+                            <ul class="list-group list-group-flush">
+                                <li class="list-group-item">
+                                    <span class="text-success pull-right">重新上报</span>
+                                    <h5 class="card-title"><i class="fa fa-play-circle fa-rotate-90 text-success"></i> 布尔 <small class="text-muted">施工</small></h5>
+                                    <p class="card-text">2017-12-01</p>
+                                </li>
+                                <li class="list-group-item">
+                                    <span class="text-success pull-right">审批通过</span>
+                                    <h5 class="card-title"><i class="fa fa-chevron-circle-down text-success"></i> 张三 <small class="text-muted">监理</small></h5>
+                                    <p class="card-text">审批通过 2017-12-02</p>
+                                </li>
+                                <li class="list-group-item">
+                                    <span class="text-warning pull-right">审批退回 张三</span>
+                                    <h5 class="card-title"><i class="fa fa-chevron-circle-down text-warning"></i> 王五 <small class="text-muted">监理</small></h5>
+                                    <p class="card-text">审批退回 2017-12-02</p>
+                                </li>
+                                <!--王五退回上一审批人 张三,张三重新审批-->
+                                <li class="list-group-item">
+                                    <span class="pull-right">审批中</span>
+                                    <h5 class="card-title"><i class="fa fa-chevron-circle-down"></i> 张三 <small class="text-muted">监理</small></h5>
+                                    <p class="card-text"></p>
+                                </li>
+                                <li class="list-group-item">
+                                    <h5 class="card-title"><i class="fa fa-stop-circle"></i> 李四 <small class="text-muted">监理</small></h5>
+                                </li>
+                            </ul>
+                        </div>
+                    </div>
+                </div>
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-secondary btn-sm" data-dismiss="modal">关闭</button>
+            </div>
+        </div>
+    </div>
+</div>
 <% if (ledgerRevise.length > 0 && (ledgerRevise[0].status === auditConst.status.uncheck || ledgerRevise[0].status === auditConst.status.checkNo) && ctx.session.sessionUser.accountId === ledgerRevise[0].uid) { %>
 <!--弹出作废-->
 <div class="modal fade" id="remove" data-backdrop="static">
@@ -54,4 +135,85 @@
         }
         return dbClickFlag;
     }
-</script>
+    const auditConst = JSON.parse('<%- auditConst2 %>');
+
+    $(function () {
+        // 获取审批流程
+        $('a[data-target="#sp-list" ]').on('click', function () {
+            const data = {
+                id: $(this).attr('lr-id'),
+            };
+            postData('<%- preUrl + "/revise/auditors" %>', data, function (result) {
+                const reviseAuditor = result.reviseAuditor;
+                const auditors = result.auditors;
+                const auditHistory = result.auditHistory;
+                // 生成左边列表流程
+                const lefthtml = [];
+                lefthtml.push('<li class="list-group-item"><i class="fa fa fa-play-circle fa-rotate-90"></i> '+ reviseAuditor.name +'  <small class="text-muted">'+ reviseAuditor.role +'</small><span class="pull-right">原报</span></li>');
+                for (const [index,a] of auditors.entries()) {
+                    if (index+1 === auditors.length) {
+                        lefthtml.push('<li class="list-group-item"><i class="fa fa-stop-circle"></i> '+ a.name +'  <small class="text-muted">'+ a.role +'</small><span class="pull-right">终审</span></li>');
+                    } else {
+                        lefthtml.push('<li class="list-group-item"><i class="fa fa-chevron-circle-down"></i> '+ a.name +'  <small class="text-muted">'+ a.role +'</small><span class="pull-right">' + transFormToChinese(index+1) + '审</span></li>');
+                    }
+                }
+                $('#auditor-list').html(lefthtml.join(''));
+
+                // 生成右边列表流程
+                const righthtml = [];
+                for(const ah of auditHistory) {
+                    righthtml.push('<div class="card mt-3"><ul class="list-group list-group-flush">');
+                    for (let iA = 0; iA < ah.length; iA++) {
+                        if (iA === 0) {
+                            righthtml.push('<li class="list-group-item">');
+                            righthtml.push('<h5 class="card-title">');
+                            righthtml.push('<i class="fa fa-play-circle fa-rotate-90 text-success"></i> '+ reviseAuditor.name +' <small class="text-muted">'+ reviseAuditor.role +'</small><span class="pull-right">原报</span></h5>');
+                            righthtml.push('<div class="ml-3">');
+                            righthtml.push('<span class="text-success"><small>' + (ah[iA].begin_time ? moment(ah[iA].begin_time).format('YYYY-MM-DD') : '') + '</small> '+ (auditHistory.indexOf(ah) > 0 ? '重新' : '') + '上报</span></div></li>');
+                            righthtml.push('<li class="list-group-item">');
+                            righthtml.push('<h5 class="card-title"><i class="fa '+ (iA === ah.length - 1 ? 'fa-stop-circle ' : 'fa-chevron-circle-down ') + auditConst.statusClass[ah[iA].status] +'"></i> '+ ah[iA].name +' <small class="text-muted">'+ ah[iA].role +'</small><span class="pull-right">' + (ah[iA].sort === ah[iA].max_sort ? '终' : transFormToChinese(ah[iA].sort)) + '审</span></h5>');
+                            righthtml.push('<div class="ml-3">');
+                            if (ah[iA].status !== auditConst.status.uncheck) {
+                                let timeHtml = '';
+                                if (ah[iA].status === auditConst.status.checked || ah[iA].status === auditConst.status.checkNo) {
+                                    timeHtml = '<small>'+ (ah[iA].end_time ? moment(ah[iA].end_time).format('YYYY-MM-DD') : '') +'</small> ';
+                                }
+                                righthtml.push('<span class="' + auditConst.statusClass[ah[iA].status] +'">'+ timeHtml + auditConst.statusString[ah[iA].status] + (ah[iA].status === auditConst.status.checkNo ? ' ' + reviseAuditor.name : '') + '</span>');
+                            }
+                            righthtml.push('<p class="card-text">'+ (ah[iA].opinion !== null ? ah[iA].opinion : '') +'</p></div>');
+                            righthtml.push('</li>');
+                        } else if (iA === ah.length - 1) {
+                            righthtml.push('<li class="list-group-item">');
+                            righthtml.push('<h5 class="card-title"><i class="fa fa-stop-circle '+ auditConst.statusClass[ah[iA].status] +'"></i> '+ ah[iA].name +' <small class="text-muted">'+ ah[iA].role +'</small><span class="pull-right">终审</span></h5>');
+                            righthtml.push('<div class="ml-3">');
+                            if (ah[iA].status !== auditConst.status.uncheck) {
+                                let timeHtml = '';
+                                if (ah[iA].status === auditConst.status.checked || ah[iA].status === auditConst.status.checkNo) {
+                                    timeHtml = '<small>'+ (ah[iA].end_time ? moment(ah[iA].end_time).format('YYYY-MM-DD') : '') +'</small> ';
+                                }
+                                righthtml.push('<span class="' + auditConst.statusClass[ah[iA].status] +'">' + timeHtml + auditConst.statusString[ah[iA].status] + (ah[iA].status === auditConst.status.checkNo ? ' ' + reviseAuditor.name : '') + '</span>');
+                            }
+                            righthtml.push('<p class="card-text">'+ (ah[iA].opinion !== null ? ah[iA].opinion : '') +'</p></div>');
+                            righthtml.push('</li>');
+                        } else {
+                            righthtml.push('<li class="list-group-item">');
+                            righthtml.push('<h5 class="card-title"><i class="fa '+ (iA === ah.length - 1 ? 'fa-stop-circle ' : 'fa-chevron-circle-down ') + auditConst.statusClass[ah[iA].status] +'"></i> '+ ah[iA].name +' <small class="text-muted">'+ ah[iA].role +'</small><span class="pull-right">' + (ah[iA].sort === ah[iA].max_sort ? '终' : transFormToChinese(ah[iA].sort)) + '审</span></h5>');
+                            righthtml.push('<div class="ml-3">');
+                            if (ah[iA].status !== auditConst.status.uncheck) {
+                                let timeHtml = '';
+                                if (ah[iA].status === auditConst.status.checked || ah[iA].status === auditConst.status.checkNo) {
+                                    timeHtml = '<small>'+ (ah[iA].end_time ? moment(ah[iA].end_time).format('YYYY-MM-DD') : '') +'</small> ';
+                                }
+                                righthtml.push('<span class="' + auditConst.statusClass[ah[iA].status] +'">'+ timeHtml + auditConst.statusString[ah[iA].status] + (ah[iA].status === auditConst.status.checkNo ? ' ' + reviseAuditor.name : '') + '</span>');
+                            }
+                            righthtml.push('<p class="card-text">'+ (ah[iA].opinion !== null ? ah[iA].opinion : '') +'</p></div>');
+                            righthtml.push('</li>');
+                        }
+                    }
+                    righthtml.push('</ul></div>');
+                }
+                $('#auditor-list2').html(righthtml.join(''));
+            })
+        });
+    })
+</script>

+ 0 - 16
app/view/spss/compare_stage.ejs

@@ -1,16 +0,0 @@
-<% include ./sub_menu.ejs %>
-<div class="panel-content">
-    <div class="panel-title">
-        <div class="title-main d-flex">
-            <% include ./sub_mini_menu.ejs %>
-        </div>
-    </div>
-    <div class="content-wrap">
-        <div class="c-header p-0"></div>
-        <div class="c-body">
-            暂无该功能...<br>
-            会不会做,什么时候做...<br>
-            小编太忙了,如急需,请联系...<br>
-        </div>
-    </div>
-</div>

+ 38 - 0
app/view/spss/compare_stage_modal.ejs

@@ -0,0 +1,38 @@
+<!--弹出对比标段-->
+<div class="modal fade" id="data-select" data-backdrop="static">
+    <div class="modal-dialog" role="document">
+        <div class="modal-content">
+            <div class="modal-header">
+                <h5 class="modal-title">选择对比标段</h5>
+            </div>
+            <div class="modal-body">
+                <div class="form-group">
+                    <label>对比标段1<b class="text-danger">*</b></label>
+                    <div class="row ml-1 mr-1">
+                        <div class="col-6">
+                            <input class="form-control form-control-sm"  placeholder="输入标段1的ID" type="text" name="id1">
+                        </div>
+                        <div class="col-6">
+                            <input class="form-control form-control-sm"  placeholder="输入标段1的对比期序号" type="text" name="sorder1">
+                        </div>
+                    </div>
+                </div>
+                <div class="form-group">
+                    <label>对比标段2<b class="text-danger">*</b></label>
+                    <div class="row ml-1 mr-1">
+                        <div class="col-6">
+                            <input class="form-control form-control-sm"  placeholder="输入标段2的ID" type="text" name="id2">
+                        </div>
+                        <div class="col-6">
+                            <input class="form-control form-control-sm"  placeholder="输入标段2的对比期序号" type="text" name="sorder2">
+                        </div>
+                    </div>
+                </div>
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-secondary btn-sm" data-dismiss="modal">关闭</button>
+                <button type="button" class="btn btn-primary btn-sm" id="select-ok">确定</button>
+            </div>
+        </div>
+    </div>
+</div>

+ 3 - 1
app/view/spss/compare_tz.ejs

@@ -21,7 +21,9 @@
                     </div>
                 </div>
                 <div class="d-inline-block">
-                    <button href="#compare-select" class="btn btn-sm btn-light text-primary" data-toggle="modal" data-target="#compare-select"><i class="fa fa-clone"></i> 选择比较标段</button>
+                    <button href="#data-select" class="btn btn-sm btn-light text-primary" data-toggle="modal" data-target="#data-select"><i class="fa fa-clone"></i>
+                        选择<%- ctx.url.indexOf('compare') > 0 ? '对比' : '汇总' %>标段
+                    </button>
                 </div>
             </div>
             <div class="ml-auto"></div>

+ 2 - 4
app/view/spss/compare_tz_modal.ejs

@@ -1,5 +1,5 @@
 <!--弹出对比标段-->
-<div class="modal fade" id="compare-select" data-backdrop="static">
+<div class="modal fade" id="data-select" data-backdrop="static">
     <div class="modal-dialog" role="document">
         <div class="modal-content">
             <div class="modal-header">
@@ -9,17 +9,15 @@
                 <div class="form-group">
                     <label>对比标段1<b class="text-danger">*</b></label>
                     <input class="form-control form-control-sm"  placeholder="输入标段1的ID" type="text" name="id1">
-                    <input class="form-control form-control-sm"  placeholder="输入标段1的对比期序号" type="text" name="stage1" style="display: none;">
                 </div>               
                 <div class="form-group">
                     <label>对比标段2<b class="text-danger">*</b></label>
                     <input class="form-control form-control-sm"  placeholder="输入标段2的ID" type="text" name="id2">
-                    <input class="form-control form-control-sm"  placeholder="输入标段2的对比期序号" type="text" name="stage1" style="display: none;">
                 </div>               
             </div>
             <div class="modal-footer">
                 <button type="button" class="btn btn-secondary btn-sm" data-dismiss="modal">关闭</button>
-                <button type="button" class="btn btn-primary btn-sm" id="compare-ok">确定</button>
+                <button type="button" class="btn btn-primary btn-sm" id="select-ok">确定</button>
             </div>
         </div>
     </div>

+ 35 - 0
app/view/spss/gather_stage_modal.ejs

@@ -0,0 +1,35 @@
+<!--弹出汇总标段-->
+<div class="modal fade" id="data-select" data-backdrop="static">
+    <div class="modal-dialog" role="document">
+        <div class="modal-content">
+            <div class="modal-header">
+                <h5 class="modal-title">选择汇总标段</h5>
+            </div>
+            <div class="modal-body">
+                <div class="form-group">
+                    <div class="row">
+                        <div class="col-10 d-inline-flex">
+                            <input class="form-control form-control-sm"  placeholder="输入需要汇总的标段ID" type="text" name="tid">
+                            <input class="form-control form-control-sm ml-1"  placeholder="输入需要汇总的标段的期序号" type="text" name="sorder">
+                            <a href="javascript: void(0);" id="add-t" class="btn btn-sm" data-toggle="tooltip" data-placement="bottom" title="" data-original-title="新增"><i class="fa fa-plus text-primary" aria-hidden="true"></i></a>
+                        </div>
+                    </div>
+                </div>
+                <div class="form-group">
+                    <label>汇总标段列表<b class="text-danger">*</b></label>
+                    <table class="table table-bordered table-sm mt-1">
+                        <thead>
+                        <th style="width: 40%">标段id</th><th style="width: 40%">第几期</th><th></th>
+                        </thead>
+                        <tbody>
+                        </tbody>
+                    </table>
+                </div>
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-secondary btn-sm" data-dismiss="modal">关闭</button>
+                <button type="button" class="btn btn-primary btn-sm" id="select-ok">确定</button>
+            </div>
+        </div>
+    </div>
+</div>

+ 34 - 0
app/view/spss/gather_tz_modal.ejs

@@ -0,0 +1,34 @@
+<!--弹出汇总标段-->
+<div class="modal fade" id="data-select" data-backdrop="static">
+    <div class="modal-dialog" role="document">
+        <div class="modal-content">
+            <div class="modal-header">
+                <h5 class="modal-title">选择汇总标段</h5>
+            </div>
+            <div class="modal-body">
+                <div class="form-group">
+                    <div class="row">
+                        <div class="col-6 d-inline-flex">
+                            <input class="form-control form-control-sm"  placeholder="输入需要汇总的标段ID" type="text" name="tid">
+                            <a href="javascript: void(0);" id="add-t" class="btn btn-sm" data-toggle="tooltip" data-placement="bottom" title="" data-original-title="新增"><i class="fa fa-plus text-primary" aria-hidden="true"></i></a>
+                        </div>
+                    </div>
+                </div>
+                <div class="form-group">
+                    <label>汇总标段列表<b class="text-danger">*</b></label>
+                    <table class="table table-bordered table-sm mt-1">
+                        <thead>
+                        <th style="width: 80%">标段id</th><th></th>
+                        </thead>
+                        <tbody>
+                        </tbody>
+                    </table>
+                </div>
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-secondary btn-sm" data-dismiss="modal">关闭</button>
+                <button type="button" class="btn btn-primary btn-sm" id="select-ok">确定</button>
+            </div>
+        </div>
+    </div>
+</div>

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

@@ -14,8 +14,14 @@
         <div class="c-body">
             <div class="sjs-height-0" id="bonus-spread">
             </div>
+            <div z-index="0" style="display: none;">
+                <input class="datepicker-here form-control form-control-sm" data-date-format="yyyy-MM-DD" data-language="zh" type="text" autocomplete="off" id="dp-input">
+            </div>
         </div>
     </div>
+    <div style="display: none">
+        <img src="/public/images/ellipsis_horizontal.png" id="ellipsis-icon" />
+    </div>
 </div>
 <script>
     const stageId = <%- ctx.stage.id %>;

+ 13 - 2
app/view/stage_extra/jgcl.ejs

@@ -6,13 +6,24 @@
             <div>
                 甲供材料
             </div>
+            <!--<div id="sum" class="ml-3">
+            </div>-->
             <div class="ml-auto"></div>
         </div>
     </div>
     <div class="content-wrap">
         <div class="c-header p-0"></div>
-        <div class="c-body">
-            <div class="sjs-height-0" id="jgcl-spread">
+        <div class="w-100 sub-content row">
+            <div class="c-body col-9">
+                <div class="sjs-height-0" id="jgcl-spread">
+                </div>
+            </div>
+            <div class="c-body col-3">
+                <table class="table table-bordered" style="width: 99%">
+                    <tr><th class="text-center" width="66.6%">名称</th><th class="text-center">金额</th></tr>
+                    <tbody id="sum">
+                    </tbody>
+                </table>
             </div>
         </div>
     </div>

+ 12 - 17
app/view/stage_extra/other.ejs

@@ -12,24 +12,19 @@
     <div class="content-wrap">
         <div class="c-header p-0"></div>
         <div class="c-body">
-            <div class="sjs-height-1" id="main-spread">
+            <div class="sjs-height-1" id="other-spread" z-index="1">
             </div>
-            <div class="bcontent-wrap" id="main-bottom">
-                <div id="main-resize" class="resize-y"  r-Type="height" div1="#bills-spread" div2="#main-bottom" store-id="compare-main" store-version="1.0.0" min="100"></div>
-                <div class="bc-bar mb-1">
-                    <ul class="nav nav-tabs">
-                        <li class="nav-item">
-                            <a class="nav-link active" data-toggle="tab" href="#detail" role="tab">明细</a>
-                        </li>
-                    </ul>
-                </div>
-                <div class="tab-content">
-                    <div class="tab-pane active" id="detail">
-                        <div class="sp-wrap" id="sub-spread">
-                        </div>
-                    </div>
-                </div>
+            <div z-index="0" style="display: none;">
+                <input class="datepicker-here form-control form-control-sm" data-date-format="yyyy-MM-DD" data-language="zh" type="text" autocomplete="off" id="dp-input">
             </div>
         </div>
     </div>
-</div>
+    <div style="display: none">
+        <img src="/public/images/ellipsis_horizontal.png" id="ellipsis-icon" />
+    </div>
+</div>
+<script>
+    const stageId = <%- ctx.stage.id %>;
+    const stageUserId = <%- ctx.stage.user_id %>;
+    const readOnly = <%- ctx.stage.readOnly %>;
+</script>

+ 1 - 1
app/view/stage_extra/sub_menu_list.ejs

@@ -1,7 +1,7 @@
 <div class="nav-box">
     <ul class="nav-list list-unstyled">
         <li>
-            <a href="/tender/<%= ctx.tender.id %>/measure/stage/<%= ctx.stage.order %>"><span class="ml-3"><i class="fa fa-chevron-left "></i> 返回</span></a>
+            <a class="text-primary" href="/tender/<%= ctx.tender.id %>/measure/stage/<%= ctx.stage.order %>"><i class="fa fa-chevron-left"></i><span class="ml-1">返回</span></a>
         </li>
     </ul>
 </div>

+ 277 - 0
builder_report_index_define.js

@@ -0,0 +1,277 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date
+ * @version
+ */
+
+const fs = require('fs');
+const path = require('path');
+const savePath = path.join(__dirname, 'report_temp');
+const dataType = {
+    int: 'int',
+    str: 'string',
+    double: 'double',
+    currency: 'currency',
+    time: 'string',
+};
+const tag = {
+    tp: {type: 'tp'},
+    up: {type: 'up'},
+};
+const stage_jgcl = {
+    name: '期-甲供材料(mem_stage_jgcl)',
+    remark: '',
+    id: 30,
+    key: 'stage_jgcl',
+    prefix: '期-甲供材料',
+    cols: [
+        {name: 'id', field: 'id', type: dataType.int},
+        {name: 'uuid', field: 'uuid', type: dataType.str},
+        {name: '名称', field: 'name', type: dataType.str},
+        {name: '单位', field: 'unit', type: dataType.str},
+        {name: '单价', field: 'unit_price', type: dataType.currency, tag: {type: 'up'}},
+        {name: '本期到场-数量', field: 'arrive_qty', type: dataType.currency, tag: {type: 'qty', unitKey: 4}},
+        {name: '本期到场-金额', field: 'arrive_tp', type: dataType.currency, tag: {type: 'tp'}},
+        {name: '本期扣回-数量', field: 'deduct_qty', type: dataType.currency, tag: {type: 'qty', unitKey: 4}},
+        {name: '本期扣回-金额', field: 'deduct_tp', type: dataType.currency, tag: {type: 'tp'}},
+        {name: '材料来源', field: 'source', type: dataType.str},
+        {name: '单据号', field: 'bills_code', type: dataType.str},
+        {name: '检验单编号', field: 'check_code', type: dataType.str},
+        {name: '新增人id', field: 'add_uid', type: dataType.int},
+        {name: '新增期id', field: 'add_sid', type: dataType.int},
+        {name: '所属期id', field: 'sid', type: dataType.int},
+        {name: '期历史记录', field: 'shistory', type: dataType.str},
+        {name: '排序', field: 'order', type: dataType.int},
+        {name: '往期是否已用', field: 'pre_used', type: dataType.int},
+        {name: '截止上期到场-数量', field: 'arrive_qty', type: dataType.currency, tag: {type: 'qty', unitKey: 4}},
+        {name: '截止上期到场-金额', field: 'arrive_tp', type: dataType.currency, tag: {type: 'tp'}},
+        {name: '截止上期扣回-数量', field: 'deduct_qty', type: dataType.currency, tag: {type: 'qty', unitKey: 4}},
+        {name: '截止上期扣回-金额', field: 'deduct_tp', type: dataType.currency, tag: {type: 'tp'}},
+    ]
+};
+const stage_bonus = {
+    name: '期-奖罚金(mem_stage_bonus)',
+    remark: '',
+    id: 31,
+    key: 'stage_bonus',
+    prefix: '期-奖罚金',
+    cols: [
+        {name: 'id', field: 'id', type: dataType.str},
+        {name: '所属标段id', field: 'tid', type: dataType.int},
+        {name: '所属期id', field: 'sid', type: dataType.int},
+        {name: '所属第几期', field: 'sorder', type: dataType.int},
+        {name: '新增人id', field: 'uid', type: dataType.int},
+        {name: '名称', field: 'name', type: dataType.str},
+        {name: '金额', field: 'tp', type: dataType.currency, tag: {type: 'tp'}},
+        {name: '证明材料', field: 'proof', type: dataType.str},
+        {name: '创建时间', field: 'create_time', type: dataType.time},
+        {name: '时间', field: 'real_time', type: dataType.time},
+        {name: '备注', field: 'memo', type: dataType.str},
+        {name: '期历史记录', field: 'shistory', type: dataType.str},
+        {name: '排序', field: 'order', type: dataType.int},
+    ]
+};
+const stage_other = {
+    name: '期-其他(mem_stage_other)',
+    remark: '',
+    id: 32,
+    key: 'stage_other',
+    prefix: '期-其他',
+    cols: [
+        {name: 'id', field: 'id', type: dataType.int},
+        {name: 'uuid', field: 'uuid', type: dataType.str},
+        {name: '所属标段id', field: 'tid', type: dataType.int},
+        {name: '所属期id', field: 'sid', type: dataType.int},
+        {name: '所属第几期', field: 'sorder', type: dataType.int},
+        {name: '新增人id', field: 'add_uid', type: dataType.int},
+        {name: '新增期id', field: 'add_sid', type: dataType.int},
+        {name: '创建时间', field: 'add_time', type: dataType.time},
+        {name: '名称', field: 'name', type: dataType.str},
+        {name: '金额', field: 'total_price', type: dataType.currency, tag: {type: 'tp'}},
+        {name: '本期金额', field: 'tp', type: dataType.currency, tag: {type: 'tp'}},
+        {name: '时间', field: 'real_time', type: dataType.time},
+        {name: '备注', field: 'memo', type: dataType.str},
+        {name: '期历史记录', field: 'shistory', type: dataType.str},
+        {name: '排序', field: 'order', type: dataType.int},
+        {name: '往期是否已用', field: 'pre_used', type: dataType.int},
+        {name: '截止上期-金额', field: 'pre_tp', type: dataType.currency, tag: {type: 'tp'}},
+    ]
+};
+const change = {
+    name: '变更令(mem_change)',
+    remark: '',
+    id: 33,
+    key: 'mem_change',
+    prefix: '变更令',
+    cols: [
+        {name: '变更令id', field: 'cid', type: dataType.str},
+        {name: '变更令号', field: 'code', type: dataType.str},
+        {name: '批复文号', field: 'w_code', type: dataType.str},
+        {name: '批复编号', field: 'p_code', type: dataType.str},
+        {name: '变更名称', field: 'name', type: dataType.str},
+        {name: '桩号', field: 'peg', type: dataType.str},
+        {name: '原设计图名称', field: 'org_name', type: dataType.str},
+        {name: '原图号', field: 'org_code', type: dataType.str},
+        {name: '变更设计图名称', field: 'new_name', type: dataType.str},
+        {name: '变更图号', field: 'new_code', type: dataType.str},
+        {name: '工程变更理由及内容', field: 'content', type: dataType.str},
+        {name: '工程变更合同依据', field: 'basis', type: dataType.str},
+        {name: '变更工程量数量计算式', field: 'expr', type: dataType.str},
+        {name: '备注', field: 'memo', type: dataType.str},
+        {name: '变更类型', field: 'type', type: dataType.str},
+        {name: '变更类别', field: 'class', type: dataType.int},
+        {name: '变更性质', field: 'quality', type: dataType.int},
+        {name: '变更提出单位', field: 'company', type: dataType.str},
+        {name: '费用承担方', field: 'charge', type: dataType.int},
+        {name: '金额', field: 'total_price', type: dataType.currency, tag: {type: 'tp'}},
+        {name: '变更状态发生时间(时间戳)', field: 'cin_time', type: dataType.str},
+        {name: '完成审批时间(时间戳)', field: 'sin—_ime', type: dataType.str},
+        {name: '金额_1', field: 'tp_1', type: dataType.currency, tag: {type: 'tp'}},
+        {name: '金额_2', field: 'tp_2', type: dataType.currency, tag: {type: 'tp'}},
+        {name: '金额_3', field: 'tp_3', type: dataType.currency, tag: {type: 'tp'}},
+        {name: '金额_4', field: 'tp_4', type: dataType.currency, tag: {type: 'tp'}},
+        {name: '金额_5', field: 'tp_5', type: dataType.currency, tag: {type: 'tp'}},
+        {name: '金额_6', field: 'tp_6', type: dataType.currency, tag: {type: 'tp'}},
+        {name: '金额_7', field: 'tp_7', type: dataType.currency, tag: {type: 'tp'}},
+        {name: '金额_8', field: 'tp_8', type: dataType.currency, tag: {type: 'tp'}},
+        {name: '金额_9', field: 'tp_9', type: dataType.currency, tag: {type: 'tp'}},
+        {name: '附件名称列表', field: 'attNames', type: dataType.currency},
+    ]
+};
+const changeBills = {
+    name: '变更清单(mem_change_bills)',
+    remark: '',
+    id: 29,
+    key: 'mem_change_bills',
+    prefix: '变更清单',
+    cols: [
+        {name: '变更令id', field: 'cid', type: dataType.str},
+        {name: '签约清单id或台账id', field: 'lid', type: dataType.str},
+        {name: '清单编号', field: 'code', type: dataType.str},
+        {name: '名称', field: 'name', type: dataType.str},
+        {name: '单位', field: 'unit', type: dataType.str},
+        {name: '单价', field: 'unit_price', type: dataType.currency, tag: {type: 'up'}},
+        {name: '原-数量', field: 'o_qty', type: dataType.currency, tag: {type: 'qty', unitKey: 5}},
+        {name: '原-金额', field: 'o_tp', type: dataType.currency, tag: {type: 'tp'}},
+        {name: '变更-数量', field: 'c_qty', type: dataType.currency, tag: {type: 'qty', unitKey: 5}},
+        {name: '变更-金额', field: 'c_tp', type: dataType.currency, tag: {type: 'tp'}},
+        {name: '审批变更后-数量', field: 's_qty', type: dataType.currency, tag: {type: 'qty', unitKey: 5}},
+        {name: '审批变更后-金额', field: 's_tp', type: dataType.currency, tag: {type: 'tp'}},
+        {name: '部位明细', field: 'bwmx', type: dataType.str},
+        {name: '变更详情', field: 'detail', type: dataType.str},
+        {name: '数量_1', field: 'qty_1', type: dataType.currency, tag: {type: 'qty', unitKey: 5}},
+        {name: '金额_1', field: 'tp_1', type: dataType.currency, tag: {type: 'tp'}},
+        {name: '数量_2', field: 'qty_2', type: dataType.currency, tag: {type: 'qty', unitKey: 5}},
+        {name: '金额_2', field: 'tp_2', type: dataType.currency, tag: {type: 'tp'}},
+        {name: '数量_3', field: 'qty_3', type: dataType.currency, tag: {type: 'qty', unitKey: 5}},
+        {name: '金额_3', field: 'tp_3', type: dataType.currency, tag: {type: 'tp'}},
+        {name: '数量_4', field: 'qty_4', type: dataType.currency, tag: {type: 'qty', unitKey: 5}},
+        {name: '金额_4', field: 'tp_4', type: dataType.currency, tag: {type: 'tp'}},
+        {name: '数量_5', field: 'qty_5', type: dataType.currency, tag: {type: 'qty', unitKey: 5}},
+        {name: '金额_5', field: 'tp_5', type: dataType.currency, tag: {type: 'tp'}},
+        {name: '数量_6', field: 'qty_6', type: dataType.currency, tag: {type: 'qty', unitKey: 5}},
+        {name: '金额_6', field: 'tp_6', type: dataType.currency, tag: {type: 'tp'}},
+        {name: '数量_7', field: 'qty_7', type: dataType.currency, tag: {type: 'qty', unitKey: 5}},
+        {name: '金额_7', field: 'tp_7', type: dataType.currency, tag: {type: 'tp'}},
+        {name: '数量_8', field: 'qty_8', type: dataType.currency, tag: {type: 'qty', unitKey: 5}},
+        {name: '金额_8', field: 'tp_8', type: dataType.currency, tag: {type: 'tp'}},
+        {name: '数量_9', field: 'qty_9', type: dataType.currency, tag: {type: 'qty', unitKey: 5}},
+        {name: '金额_9', field: 'tp_9', type: dataType.currency, tag: {type: 'tp'}},
+
+    ]
+};
+
+const recursiveMkdirSync = async function (pathName) {
+    if (!fs.existsSync(pathName)) {
+        const upperPath = path.dirname(pathName);
+        if (!fs.existsSync(upperPath)) {
+            await this.recursiveMkdirSync(upperPath);
+        }
+        await fs.mkdirSync(pathName);
+    }
+};
+const saveBufferFile = async function (buffer, fileName) {
+    // 检查文件夹是否存在,不存在则直接创建文件夹
+    const pathName = path.dirname(fileName);
+    if (!fs.existsSync(pathName)) {
+        await recursiveMkdirSync(pathName);
+    }
+    await fs.writeFileSync(fileName, buffer);
+};
+const addFields = function(table, name, field, type, tag) {
+    const data = {};
+    data.ID = table.ID * 100 + table.items.length + 1;
+    data.Name = name + '(' + field + ')';
+    if (table.prefix && table.prefix !== '') data.Name = table.prefix + '-' + data.Name;
+    data.DataType = type;
+    data.TableName = table.key;
+    data.descr = '';
+    data.mapExpression = "$PROJECT.REPORT.getProperty('" + table.key + "', '" + field + "')";
+    if (tag) {
+        switch (tag.type) {
+            case 'up':
+                data.Precision = { type: "fixed", fixedMapExpression: "$PROJECT.REPORT.getProperty('tender_info', 'decimal.up')" };
+                break;
+            case 'tp':
+                if (tag.subType) {
+                    data.Precision = { type: "fixed", fixedMapExpression: "$PROJECT.REPORT.getProperty('tender_info', 'decimal." + "_" + tag.subType + "_tp')" };
+                } else {
+                    data.Precision = { type: "fixed", fixedMapExpression: "$PROJECT.REPORT.getProperty('tender_info', 'decimal.tp')" };
+                }
+                break;
+            case 'qty':
+                data.Precision = { type: "flexible", flexibleRefFieldID: table.ID * 100 + tag.unitKey, flexibleMapExpression: "$PROJECT.REPORT.getProperty('tender_info', 'precision')" };
+                break;
+        }
+    }
+    table.items.push(data);
+};
+const saveTableDefine = async function (tableDefine, file) {
+    delete tableDefine.ID;
+    delete tableDefine.key;
+    delete tableDefine.prefix;
+    let defineStr = JSON.stringify(tableDefine, "", "\t");
+    const replaceStr = [
+        {match: '"Name":', str: 'Name:'},
+        {match: '"remark":', str: 'remark:'},
+        {match: '"items":', str: 'items:'},
+        {match: '"ID":', str: 'ID:'},
+        {match: '"DataType":', str: 'DataType:'},
+        {match: '"TableName":', str: 'TableName:'},
+        {match: '"descr":', str: 'descr:'},
+        {match: '"mapExpression":', str: 'mapExpression:'},
+        {match: '"Precision":', str: 'Precision:'},
+        {match: '"type":', str: 'type:'},
+        {match: '"flexibleRefFieldID":', str: 'flexibleRefFieldID:'},
+        {match: '"fixedMapExpression":', str: 'fixedMapExpression:'},
+        {match: '"flexibleMapExpression":', str: 'flexibleMapExpression:'},
+    ];
+    for (const rs of replaceStr) {
+        const reg = new RegExp(rs.match, 'gm');
+        defineStr = defineStr.replace(reg, rs.str);
+    }
+    await saveBufferFile(defineStr, file);
+};
+
+const exportTableDefine = async function (define) {
+    const tableDefine = {};
+    tableDefine.Name = define.name;// '期-清单-全参与人数据表(mem_stage_bills_compare)';
+    tableDefine.remark = define.remark;
+    tableDefine.ID = define.id;
+    tableDefine.key = define.key;
+    tableDefine.items = [];
+    tableDefine.prefix = define.prefix;
+    for (const col of define.cols) {
+        addFields(tableDefine, col.name, col.field, col.type, col.tag);
+    }
+    await saveTableDefine(tableDefine, path.join(savePath, define.key + '.json'));
+};
+
+const defines = [change, changeBills];
+for (const d of defines) {
+    exportTableDefine(d);
+}

+ 52 - 0
config/web.js

@@ -396,6 +396,8 @@ const JsFiles = {
                 files: [
                     "/public/js/spreadjs/sheets/v11/gc.spread.sheets.all.11.2.2.min.js",
                     "/public/js/decimal.min.js",
+                    "/public/js/datepicker/datepicker.min.js",
+                    "/public/js/datepicker/datepicker.zh.js",
                 ],
                 mergeFiles: [
                     "/public/js/sub_menu.js",
@@ -480,6 +482,56 @@ const JsFiles = {
                 ],
                 mergeFile: 'compare_tz',
             },
+            stage: {
+                files: [
+                    "/public/js/spreadjs/sheets/v11/gc.spread.sheets.all.11.2.2.min.js",
+                    "/public/js/decimal.min.js",
+                ],
+                mergeFiles: [
+                    "/public/js/sub_menu.js",
+                    "/public/js/div_resizer.js",
+                    "/public/js/spreadjs_rela/spreadjs_zh.js",
+                    "/public/js/shares/sjs_setting.js",
+                    "/public/js/zh_calc.js",
+                    "/public/js/path_tree.js",
+                    "/public/js/compare_stage.js",
+                ],
+                mergeFile: 'compare_stage',
+            },
+        },
+        gather: {
+            tz: {
+                files: [
+                    "/public/js/spreadjs/sheets/v11/gc.spread.sheets.all.11.2.2.min.js",
+                    "/public/js/decimal.min.js",
+                ],
+                mergeFiles: [
+                    "/public/js/sub_menu.js",
+                    "/public/js/div_resizer.js",
+                    "/public/js/spreadjs_rela/spreadjs_zh.js",
+                    "/public/js/shares/sjs_setting.js",
+                    "/public/js/zh_calc.js",
+                    "/public/js/path_tree.js",
+                    "/public/js/gather_tz.js",
+                ],
+                mergeFile: 'gather_tz',
+            },
+            stage: {
+                files: [
+                    "/public/js/spreadjs/sheets/v11/gc.spread.sheets.all.11.2.2.min.js",
+                    "/public/js/decimal.min.js",
+                ],
+                mergeFiles: [
+                    "/public/js/sub_menu.js",
+                    "/public/js/div_resizer.js",
+                    "/public/js/spreadjs_rela/spreadjs_zh.js",
+                    "/public/js/shares/sjs_setting.js",
+                    "/public/js/zh_calc.js",
+                    "/public/js/path_tree.js",
+                    "/public/js/gather_stage.js",
+                ],
+                mergeFile: 'gather_stage',
+            },
         }
     }
 

+ 15 - 6
test/app/service/report_memory.test.js

@@ -77,16 +77,21 @@ describe('test/app/service/report_memory.test.js', () => {
         const ctx = app.mockContext();
         savePath = path.join(ctx.app.baseDir,'report_temp');
         // 模拟登录session
-        const postData = {
-            account: '734406061@qq.com',
-            project: 'T201711273363',
-            project_password: 'mai654321',
-        };
+        // const postData = {
+        //     account: '734406061@qq.com',
+        //     project: 'T201711273363',
+        //     project_password: 'mai654321',
+        // };
         // const postData = {
         //     account: 'chente',
         //     project: 'T201711273363',
         //     project_password: '123456',
         // };
+        const postData = {
+            account: 'zengpeiwen',
+            project: 'P1201',
+            project_password: '123456',
+        };
         ctx.session = {};
         const loginResult = yield ctx.service.projectAccount.accountLogin(postData, 2);
         assert(loginResult);
@@ -397,9 +402,13 @@ describe('test/app/service/report_memory.test.js', () => {
         addFields(tableDefine, prefix + '金额10', 'tp10', dataType.currency);
         yield saveTableDefine(ctx, tableDefine, path.join(savePath, 'mem_union_data.json'));
     });
-    it('test mem_change_bills', function* () {
+    it('test mem_change_bills && mem_change ', function* () {
         const ctx = app.mockContext(mockData);
 
+        const change = yield ctx.service.reportMemory.getChangeData(12);
+        if (change instanceof Array) {
+            yield ctx.helper.saveBufferFile(JSON.stringify(change, '', '\t'), path.join(savePath, 'mem_change.json'));
+        }
         const data = yield ctx.service.reportMemory.getChangeBillsData(12);
         if (data instanceof Array) {
             yield ctx.helper.saveBufferFile(JSON.stringify(data, '', '\t'), path.join(savePath, 'mem_change_bills.json'));

+ 43 - 0
test/app/service/report_memory_temp.test.js

@@ -0,0 +1,43 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date
+ * @version
+ */
+
+const { app, assert } = require('egg-mock/bootstrap');
+const path = require('path');
+let savePath;
+const mockData = {};
+
+describe('test/app/service/report_memory.test.js', () => {
+    // 准备测试数据
+    before(function* () {
+        const ctx = app.mockContext();
+        savePath = path.join(ctx.app.baseDir,'report_temp');
+        const postData = {
+            account: 'zengpeiwen',
+            project: 'P1201',
+            project_password: '123456',
+        };
+        ctx.session = {};
+        const loginResult = yield ctx.service.projectAccount.accountLogin(postData, 2);
+        assert(loginResult);
+        mockData.session = ctx.session;
+    });
+    it('test mem_change_bills && mem_change ', function* () {
+        const ctx = app.mockContext(mockData);
+
+        const change = yield ctx.service.reportMemory.getChangeData(2046);
+        if (change instanceof Array) {
+            yield ctx.helper.saveBufferFile(JSON.stringify(change, '', '\t'), path.join(savePath, 'mem_change.json'));
+        }
+        const data = yield ctx.service.reportMemory.getChangeBillsData(2046);
+        if (data instanceof Array) {
+            yield ctx.helper.saveBufferFile(JSON.stringify(data, '', '\t'), path.join(savePath, 'mem_change_bills.json'));
+        }
+    });
+});