소스 검색

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

MaiXinRong 4 년 전
부모
커밋
d19661a2c4
100개의 변경된 파일7514개의 추가작업 그리고 1457개의 파일을 삭제
  1. 2 0
      .gitignore
  2. 2 13
      app.js
  3. 2 1
      app/base/base_bills_service.js
  4. 11 0
      app/const/sms_type.js
  5. 11 0
      app/const/standard.js
  6. 1 1
      app/const/wechat_template.js
  7. 2 1
      app/controller/advance_controller.js
  8. 359 2
      app/controller/change_controller.js
  9. 2 1
      app/controller/ledger_audit_controller.js
  10. 3 1
      app/controller/ledger_controller.js
  11. 2 1
      app/controller/material_controller.js
  12. 28 22
      app/controller/report_controller.js
  13. 5 2
      app/controller/revise_controller.js
  14. 65 0
      app/controller/schedule_controller.js
  15. 4 1
      app/controller/stage_controller.js
  16. 24 5
      app/controller/tender_controller.js
  17. 203 18
      app/lib/analysis_excel.js
  18. 111 0
      app/lib/bills_utils.js
  19. 1 1
      app/lib/ledger.js
  20. 7 3
      app/lib/rpt_data_analysis.js
  21. 25 0
      app/lib/wechat.js
  22. 1 1
      app/middleware/api2other_check.js
  23. 83 0
      app/middleware/change_check.js
  24. 32 0
      app/public/js/category.js
  25. 1 1
      app/public/js/change.js
  26. 200 0
      app/public/js/change_audit.js
  27. 273 0
      app/public/js/change_information.js
  28. 390 0
      app/public/js/change_information_approval.js
  29. 921 0
      app/public/js/change_information_set.js
  30. 204 0
      app/public/js/change_information_show.js
  31. 22 15
      app/public/js/change_set.js
  32. 37 7
      app/public/js/gcl_gather.js
  33. 1 1
      app/public/js/global.js
  34. 49 4
      app/public/js/ledger.js
  35. 2 1
      app/public/js/ledger_bwtz.js
  36. 5 5
      app/public/js/ledger_gather.js
  37. 54 5
      app/public/js/revise.js
  38. 1 1
      app/public/js/revise_gcl_compare.js
  39. 92 0
      app/public/js/schedule_ledger.js
  40. 0 3
      app/public/js/shares/cs_tools.js
  41. 36 6
      app/public/js/shares/gcl_gather_compare.js
  42. 6 3
      app/public/js/shares/tenders2tree.js
  43. 3 0
      app/public/js/shenpi.js
  44. 48 9
      app/public/js/spreadjs_rela/spreadjs_zh.js
  45. 61 2
      app/public/js/stage.js
  46. 1 1
      app/public/js/stage_gather.js
  47. 181 0
      app/public/js/tender_copy_setting.js
  48. 6 3
      app/public/js/tender_list.js
  49. 4 0
      app/public/js/tender_list_info.js
  50. 2 0
      app/public/js/tender_list_manage.js
  51. 2 0
      app/public/js/tender_list_progress.js
  52. 24 1
      app/public/js/tender_showhide.js
  53. 2 0
      app/public/js/wap/list.js
  54. 2 1
      app/public/js/zh_calc.js
  55. 1 1
      app/public/report/js/rpt_jspdf.js
  56. 24 29
      app/public/report/js/rpt_print.js
  57. 2 2
      app/reports/public/stringUtil.js
  58. 2 2
      app/reports/rpt_component/helper/jpc_helper_field.js
  59. 17 0
      app/router.js
  60. 51 1
      app/service/advance_audit.js
  61. 8 3
      app/service/category.js
  62. 6 2
      app/service/category_value.js
  63. 37 16
      app/service/change.js
  64. 2 2
      app/service/change_att.js
  65. 175 10
      app/service/change_audit.js
  66. 203 0
      app/service/change_audit_list.js
  67. 2 2
      app/service/ledger.js
  68. 1 0
      app/service/ledger_audit.js
  69. 794 786
      app/service/project_account.js
  70. 451 390
      app/service/report_memory.js
  71. 1 0
      app/service/revise_audit.js
  72. 1 0
      app/service/stage.js
  73. 1 0
      app/service/stage_audit.js
  74. 8 7
      app/service/tender.js
  75. 30 0
      app/service/tender_info.js
  76. 59 0
      app/service/tender_tag.js
  77. 2 2
      app/view/change/index.ejs
  78. 5 0
      app/view/change/info.ejs
  79. 4 4
      app/view/change/info_modal.ejs
  80. 434 0
      app/view/change/information.ejs
  81. 1100 0
      app/view/change/information_modal.ejs
  82. 2 2
      app/view/dashboard/index.ejs
  83. 6 6
      app/view/ledger/bwtz.ejs
  84. 7 7
      app/view/ledger/explode.ejs
  85. 11 3
      app/view/ledger/explode_modal.ejs
  86. 2 1
      app/view/ledger/gather.ejs
  87. 2 2
      app/view/material/audit_modal.ejs
  88. 1 1
      app/view/material/file.ejs
  89. 1 1
      app/view/profile/wechat.ejs
  90. 2 2
      app/view/report/index.ejs
  91. 30 25
      app/view/report/rpt_all_popup.ejs
  92. 1 0
      app/view/revise/gcl_compare.ejs
  93. 2 2
      app/view/revise/info_modal.ejs
  94. 323 0
      app/view/schedule/index.ejs
  95. 63 0
      app/view/schedule/ledger.ejs
  96. 21 0
      app/view/schedule/ledger_modal.ejs
  97. 1 1
      app/view/setting/category.ejs
  98. 1 1
      app/view/setting/category_modal.ejs
  99. 1 1
      app/view/setting/user.ejs
  100. 0 0
      app/view/shares/import_excel_modal.ejs

+ 2 - 0
.gitignore

@@ -12,3 +12,5 @@ app/public/upload/
 package-lock.json
 app/public/js/web
 .vscode/
+/report_temp
+/file

+ 2 - 13
app.js

@@ -12,8 +12,6 @@ const fs = require('fs');
 const moment = require('moment');
 const uuid = require('node-uuid');
 const _ = require('lodash');
-const crypto = require('crypto');
-//const calc = require('number-precision');
 
 const BaseService = require('./app/base/base_service');
 const BaseTreeService = require('./app/base/base_tree_service');
@@ -97,18 +95,9 @@ module.exports = app => {
             }
         }
     }
-    if (app.config.min) {
-        app.minify = (file) => {
-            const files = file instanceof Array ? file : [file];
-            for (const f of files) {
-                const fileName = app.baseDir + '/app/public/js/' + f;
-                const code = fs.readFileSync(fileName, 'utf8');
-                fs.writeFileSync(fileName.replace('.js', '.min.js'), Uglyfy.minify(code, { mangle: true }).code);
-            }
-        };
-        app.minify(['spreadjs_rela/spreadjs_zh.js', 'path_tree.js']);
-    }
+
     // 设置Date对象Format函数
+    // -- 若无必要理由,请保留这段,报表用 ------------------------------------------------
     Date.prototype.Format = function(fmt) {
         const o = {
             'M+': this.getMonth() + 1, // 月份

+ 2 - 1
app/base/base_bills_service.js

@@ -18,6 +18,7 @@ const qtyFields = ['sgfh_qty', 'sjcl_qty', 'qtcl_qty', 'quantity', 'deal_qty', '
 const tpFields = ['sgfh_tp', 'sjcl_tp', 'qtcl_tp', 'total_price', 'deal_tp'];
 const measureType = require('../const/tender').measureType;
 const math = require('mathjs');
+const billsUtils = require('../lib/bills_utils');
 
 class BaseBillsSerivce extends TreeService {
 
@@ -212,7 +213,7 @@ class BaseBillsSerivce extends TreeService {
         const findPreData = function(list, a) {
             if (!list || list.length === 0) { return null; }
             for (let i = 0, iLen = list.length; i < iLen; i++) {
-                if (self.ctx.helper.compareCode(list[i].code, a.code) > 0) {
+                if (billsUtils.compareCode(list[i].code, a.code) > 0) {
                     return i > 0 ? list[i - 1] : null;
                 }
             }

+ 11 - 0
app/const/sms_type.js

@@ -13,6 +13,7 @@ const smsConst = {
     BG: 'BG',
     XD: 'XD',
     TC: 'TC',
+    YFK: 'YFK',
 };
 const judgeConst = {
     approval: 1,
@@ -69,6 +70,16 @@ const smsType = {
             { title: '审批结果', value: 2 },
         ],
     },
+    YFK: {
+        name: '预付款',
+        path: '',
+        wechat: true,
+        sms: false,
+        children: [
+            { title: '需要我审批', value: 1 },
+            { title: '审批结果', value: 2 },
+        ],
+    },
 };
 
 module.exports = {

+ 11 - 0
app/const/standard.js

@@ -26,6 +26,17 @@ const nodeType = [
     {text: '其他建安工程', value: 14},
 ];
 
+const chapterFilter = [];
+const jrg = nodeType.find(x => {
+    return x.text === '计日工';
+});
+if (jrg) {
+    chapterFilter.push({node_type: jrg.value});
+    chapterFilter.push({field: 'name', part: jrg.text});
+}
+
+
 module.exports = {
     nodeType,
+    chapterFilter,
 };

+ 1 - 1
app/const/wechat_template.js

@@ -21,7 +21,7 @@ const templateId = {
     ledger: 'ia3ObPVe_0A1GLVpU-jcDe1P6zVriJ36eAijeQvbpFM', // 台账和台账修订共用模板
     revise: 'ia3ObPVe_0A1GLVpU-jcDe1P6zVriJ36eAijeQvbpFM',
     material: 'Y9ov80rev3LHcoM2hOQE6jrK_5xZsqE-PZ0Z6HS9VGA',
-    advance: '',
+    advance: 'rgZHkyiLzrqaSGnQ2nSCCrOdUz2RJJZ_JA34L_MnQik',
 };
 const status = {
     check: '待审批',

+ 2 - 1
app/controller/advance_controller.js

@@ -241,7 +241,8 @@ module.exports = app => {
                 if (exist) {
                     throw '该审核人已存在,请勿重复添加';
                 }
-                const is_gdzs = ctx.tender.info.shenpi.advance === shenpiConst.sp_status.gdzs ? 1 : 0;
+                const shenpiInfo = await ctx.service.shenpiAudit.getDataByCondition({ tid: ctx.tender.id, sp_type: shenpiConst.sp_type.advance, sp_status: shenpiConst.sp_status.gdzs });
+                const is_gdzs = shenpiInfo && ctx.tender.info.shenpi.advance === shenpiConst.sp_status.gdzs ? 1 : 0;
                 const result = await ctx.service.advanceAudit.addAuditor(ctx.tender.id, ctx.advance.id, audit_id, ctx.advance.times, is_gdzs);
                 if (!result) {
                     throw '添加审核人失败';

+ 359 - 2
app/controller/change_controller.js

@@ -355,6 +355,7 @@ module.exports = app => {
                             cl.lid,
                             cl.xmj_code,
                             cl.xmj_jldy,
+                            cl.gcl_id,
                         ];
                         ototalCost += cl.unit_price === null ? 0 : ctx.helper.mul(cl.unit_price, cl.oamount, ctx.tender.info.decimal.tp);
                         ctotalCost += cl.unit_price === null ? 0 : ctx.helper.mul(cl.unit_price, cl.camount, ctx.tender.info.decimal.tp);
@@ -521,6 +522,355 @@ module.exports = app => {
             }
         }
 
+        /**
+         * 变更信息 新版本页面 (Get)
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        async information(ctx) {
+            try {
+                const whiteList = this.ctx.app.config.multipart.whitelist;
+                const tender = await ctx.service.tender.getDataById(ctx.tender.id);
+                const change = ctx.change;
+                // 后台判断当前人查看info状态
+                const auditStatus = await ctx.service.changeAudit.getStatusByChange(change);
+
+                // 获取附件列表
+                const attList = await ctx.service.changeAtt.getChangeAttachment(change.cid, 'desc');
+
+                // 获取其他变更令数据
+                const othersChange = await ctx.service.change.getOthersChange(ctx.tender.id, change.cid);
+
+                // 根据auditStatus获取审批人列表
+                const auditList = await ctx.service.changeAudit.getListByStatus(change, auditStatus);
+
+                // 获取用户人验证手机号
+                const pa = await ctx.service.projectAccount.getDataById(ctx.session.sessionUser.accountId);
+                const auth_mobile = pa.auth_mobile;
+                const renderData = {
+                    tender,
+                    change,
+                    othersChange,
+                    changeConst,
+                    auditStatus,
+                    auditConst: audit.flow,
+                    ledgerConsts: audit.ledger.status,
+                    attList,
+                    whiteList,
+                    auditList,
+                    tpUnit: ctx.tender.info.decimal.tp,
+                    upUnit: ctx.tender.info.decimal.up,
+                    authMobile: auth_mobile,
+                    shenpiConst,
+                    jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.change.information),
+                    preUrl: '/tender/' + ctx.tender.id + '/change/' + ctx.change.cid + '/information',
+                    precision: ctx.tender.info.precision,
+                };
+                // 根据auditStatus状态获取的不同的数据
+                if (auditStatus === 1 || auditStatus === 2) {
+                    renderData.changeUnits = changeConst.units;
+                    // renderData.accountGroup = accountGroup;
+                    // 获取所有项目参与者
+                    const accountList = await ctx.service.projectAccount.getAllDataByCondition({
+                        where: { project_id: ctx.session.sessionProject.id, enable: 1 },
+                        columns: ['id', 'name', 'company', 'role', 'enable', 'is_admin', 'account_group'],
+                    });
+                    renderData.accountList = accountList;
+                    renderData.accountGroup = accountGroup.map((item, idx) => {
+                        const groupList = accountList.filter(item => item.account_group === idx);
+                        return { groupName: item, groupList };
+                    });
+                    // 重新上报获取审批流程
+                    if (auditStatus === 2) {
+                        const auditList2 = await ctx.service.changeAudit.getListByBack(change.cid, change.times);
+                        // 展示页右侧审批流程列表
+                        const auditList3 = [];
+                        for (let time = 1; time <= change.times - 1; time++) {
+                            const auditTimeList = [];
+                            let max_sort = 1;
+                            for (const al of auditList2) {
+                                if (al.times === time) {
+                                    auditTimeList.push(al);
+                                    if (al.usite > max_sort) {
+                                        max_sort = al.usite;
+                                    }
+                                }
+                            }
+                            for (const i in auditTimeList) {
+                                auditTimeList[i].max_sort = max_sort;
+                            }
+                            auditList3.push(auditTimeList);
+                        }
+                        renderData.auditList3 = auditList3;
+                    }
+                    // 获取公司列表
+                    const companyList = await ctx.service.changeCompany.getAllDataByCondition({ where: { tid: ctx.tender.id } });
+                    renderData.companyList = companyList;
+                    // 获取已选清单
+                    const changeList = await ctx.service.changeAuditList.getList(change.cid);
+                    renderData.changeList = changeList;
+                } else if (auditStatus === 3 || auditStatus === 4 || auditStatus === 5 || auditStatus === 7) {
+                    // 展示页左侧审批流程列表和清单审批列表数据
+                    const times = change.status === audit.flow.status.back ?
+                        change.times - 1 : change.times;
+                    const auditList2 = await ctx.service.changeAudit.getListGroupByTimes(change.cid, times);
+
+                    // 展示页右侧审批流程列表
+                    const auditList3 = [];
+                    for (let time = 1; time <= times; time++) {
+                        const auditTimeList = [];
+                        let max_sort = 1;
+                        for (const al of auditList) {
+                            if (al.times === time) {
+                                auditTimeList.push(al);
+                                if (al.usite > max_sort) {
+                                    max_sort = al.usite;
+                                }
+                            }
+                        }
+                        for (const i in auditTimeList) {
+                            auditTimeList[i].max_sort = max_sort;
+                        }
+                        auditList3.push(auditTimeList);
+                    }
+                    renderData.auditList3 = auditList3;
+
+                    // 获取已选清单
+                    let changeList = await ctx.service.changeAuditList.getAllDataByCondition({ where: { cid: ctx.params.cid } });
+
+                    changeList = JSON.parse(JSON.stringify(changeList.sort())).sort().sort();
+                    for (const cl of changeList) {
+                        const audit_amount = cl.audit_amount !== null && cl.audit_amount !== '' ? cl.audit_amount.split(',') : '';
+                        // 清单表页赋值
+                        for (const [index, au] of auditList2.entries()) {
+                            if (au.usite !== 0) {
+                                cl['audit_amount_' + au.uid] = audit_amount[index - 1] ? audit_amount[index - 1] : null;
+                            }
+                        }
+                    }
+                    renderData.changeList = changeList;
+                    renderData.auditList2 = auditList2;
+                } else if (auditStatus === 6) {
+                    // 展示页左侧审批流程列表和清单审批列表数据
+                    const auditList2 = await ctx.service.changeAudit.getListGroupByTimes(change.cid, change.times);
+                    renderData.auditList2 = auditList2;
+                    const auditList3 = await ctx.service.changeAudit.getListOrderByTimes(change.cid, change.times);
+                    for (const i in auditList3) {
+                        auditList3[i].max_sort = auditList2.length - 1;
+                    }
+                    renderData.auditList3 = auditList3;
+
+                    // 展示页右侧审批流程列表
+                    const auditList5 = await ctx.service.changeAudit.getListByBack(change.cid, change.times);
+                    const auditList4 = [];
+                    for (let time = 1; time <= change.times; time++) {
+                        const auditTimeList = [];
+                        let max_sort = 1;
+                        for (const al of auditList5) {
+                            if (al.times === time) {
+                                auditTimeList.push(al);
+                                if (al.usite > max_sort) {
+                                    max_sort = al.usite;
+                                }
+                            }
+                        }
+                        for (const i in auditTimeList) {
+                            auditTimeList[i].max_sort = max_sort;
+                        }
+                        if (auditTimeList.length > 0) {
+                            auditList4.push(auditTimeList);
+                        }
+                    }
+                    renderData.auditList4 = auditList4;
+
+                    // 获取已选清单
+                    let changeList = await ctx.service.changeAuditList.getAllDataByCondition({ where: { cid: ctx.params.cid } });
+
+                    changeList = JSON.parse(JSON.stringify(changeList.sort())).sort().sort();
+                    for (const cl of changeList) {
+                        const audit_amount = cl.audit_amount !== null && cl.audit_amount !== '' ? cl.audit_amount.split(',') : '';
+                        // 清单表页赋值
+                        for (const [index, au] of auditList2.entries()) {
+                            if (au.usite !== 0) {
+                                cl['audit_amount_' + au.uid] = au.uid === ctx.session.sessionUser.accountId ? cl.spamount : (audit_amount[index - 1] ? audit_amount[index - 1] : null);
+                            }
+                        }
+                    }
+                    renderData.changeList = changeList;
+                }
+                renderData.auditList = auditList;
+
+                // 获取是否已存在调用变更令
+                const changeUsedData = await ctx.service.stageChange.getFinalUsedData(ctx.tender.id, change.cid);
+                renderData.stageChangeNum = this.ctx.helper.sum(changeUsedData.map(x => { return Math.abs(x.used_qty); }));
+                await this.layout('change/information.ejs', renderData, 'change/information_modal.ejs');
+            } catch (err) {
+                this.log(err);
+                ctx.redirect('/tender/' + ctx.params.id + '/change');
+            }
+        }
+
+        /**
+         * 变更清单 - 操作 (Ajax)
+         * @param ctx
+         * @return {Promise<void>}
+         */
+        async saveListsData(ctx) {
+            try {
+                const data = JSON.parse(ctx.request.body.data);
+                const responseData = {
+                    err: 0,
+                    msg: '',
+                    data: {},
+                };
+                switch (data.type) {
+                    case 'add':
+                        responseData.data = await ctx.service.changeAuditList.add(data.postData);
+                        break;
+                    case 'del':
+                        await ctx.service.changeAuditList.del(data.id);
+                        break;
+                    case 'update':
+                        await ctx.service.changeAuditList.save(data.updateData);
+                        break;
+                    case 'paste':
+                        await ctx.service.changeAuditList.saveDatas(data.updateData);
+                        // 取所有工料表
+                        responseData.data = await ctx.service.changeAuditList.getList(ctx.change.cid);
+                        break;
+                    case 'ledger_list':
+                        await ctx.service.changeAuditList.saveLedgerListDatas(data.updateData);
+                        // 取所有工料表
+                        responseData.data = await ctx.service.changeAuditList.getList(ctx.change.cid);
+                        break;
+                    case 'info':
+                        await ctx.service.change.saveInfo(data.updateData);
+                        // 取所有工料表
+                        responseData.data = '保存成功';
+                        break;
+                    case 'paste_amount_rows':
+                        await ctx.service.changeAuditList.saveDatas(data.updateData);
+                        let changeList = await ctx.service.changeAuditList.getList(ctx.change.cid);
+                        const auditList2 = await ctx.service.changeAudit.getListGroupByTimes(ctx.change.cid, ctx.change.times);
+                        changeList = JSON.parse(JSON.stringify(changeList.sort())).sort().sort();
+                        for (const cl of changeList) {
+                            const audit_amount = cl.audit_amount !== null && cl.audit_amount !== '' ? cl.audit_amount.split(',') : '';
+                            // 清单表页赋值
+                            for (const [index, au] of auditList2.entries()) {
+                                if (au.usite !== 0) {
+                                    cl['audit_amount_' + au.uid] = au.uid === ctx.session.sessionUser.accountId ? cl.spamount : (audit_amount[index - 1] ? audit_amount[index - 1] : null);
+                                }
+                            }
+                        }
+                        // 取所有工料表
+                        responseData.data = changeList;
+                        break;
+                    default: throw '参数有误';
+                }
+
+                ctx.body = responseData;
+            } catch (err) {
+                this.log(err);
+                ctx.body = { err: 1, msg: err.toString(), data: null };
+            }
+        }
+
+        /**
+         * 上报和重新上报
+         * @param ctx
+         * @return {Promise<void>}
+         */
+        async startAudit(ctx) {
+            try {
+                // 检查权限等
+                if (!ctx.change) {
+                    throw '数据错误';
+                }
+                if (ctx.change.uid !== ctx.session.sessionUser.accountId) {
+                    throw '您无权上报该变更令数据';
+                }
+                if (!(ctx.change.status === audit.flow.status.uncheck || ctx.change.status === audit.flow.status.back)) {
+                    throw '该变更令当前无法上报';
+                }
+
+                await ctx.service.changeAudit.start(ctx.change.cid, ctx.change.times);
+
+                ctx.redirect(ctx.request.header.referer);
+            } catch (err) {
+                this.log(err);
+                ctx.session.postError = err.toString();
+                ctx.redirect(ctx.request.header.referer);
+            }
+        }
+
+        // 审批相关
+        /**
+         * 添加审批人
+         * @param ctx
+         * @return {Promise<void>}
+         */
+        async addAudit(ctx) {
+            try {
+                const data = JSON.parse(ctx.request.body.data);
+                const id = this.app._.toInteger(data.auditorId);
+                if (isNaN(id) || id <= 0) {
+                    throw '参数错误';
+                }
+                // 检查权限等
+                if (ctx.change.uid !== ctx.session.sessionUser.accountId) {
+                    throw '您无权添加审核人';
+                }
+                if (ctx.change.status === audit.flow.status.checking || ctx.change.status === audit.flow.status.checked) {
+                    throw '当前不允许添加审核人';
+                }
+
+                const auditorList = await ctx.service.changeAudit.getAuditors(ctx.change.cid, ctx.change.times);
+                // 检查审核人是否已存在
+                const exist = this.app._.find(auditorList, { uid: id });
+                if (exist) {
+                    throw '该审核人已存在,请勿重复添加';
+                }
+                const shenpiInfo = await ctx.service.shenpiAudit.getDataByCondition({ tid: ctx.tender.id, sp_type: shenpiConst.sp_type.change, sp_status: shenpiConst.sp_status.gdzs });
+                const is_gdzs = shenpiInfo && ctx.tender.info.shenpi.change === shenpiConst.sp_status.gdzs ? 1 : 0;
+                const result = await ctx.service.changeAudit.addAuditor(ctx.change.cid, id, ctx.change.times, is_gdzs);
+                if (!result) {
+                    throw '添加审核人失败';
+                }
+
+                // const auditors = await ctx.service.changeAudit.getAuditorsWithOwner(ctx.change.id, ctx.change.times);
+                const auditors = null;
+                ctx.body = { err: 0, msg: '', data: auditors };
+            } catch (err) {
+                this.log(err);
+                ctx.body = { err: 1, msg: err.toString(), data: null };
+            }
+        }
+        /**
+         * 移除审批人
+         * @param ctx
+         * @return {Promise<void>}
+         */
+        async deleteAudit(ctx) {
+            try {
+                const data = JSON.parse(ctx.request.body.data);
+                const id = data.auditorId instanceof Number ? data.auditorId : this.app._.toNumber(data.auditorId);
+                if (isNaN(id) || id <= 0) {
+                    throw '参数错误';
+                }
+
+                const result = await ctx.service.changeAudit.deleteAuditor(ctx.change.cid, id, ctx.change.times);
+                if (!result) {
+                    throw '移除审核人失败';
+                }
+
+                const auditors = null;
+                ctx.body = { err: 0, msg: '', data: auditors };
+            } catch (err) {
+                ctx.body = { err: 1, msg: err.toString(), data: null };
+            }
+        }
+
         async defaultBills(ctx) {
             try {
                 const ledgerData = await ctx.service.ledger.getData(ctx.tender.id);
@@ -677,8 +1027,9 @@ module.exports = app => {
                     if (!result) {
                         throw '导入数据库保存失败';
                     }
-                    fileData.in_time = moment(create_time * 1000).format('YYYY-MM-DD');
-                    fileData.filesize = await ctx.helper.bytesToSize(fileData.filesize);
+                    // fileData.in_time = moment(create_time * 1000).format('YYYY-MM-DD');
+                    // fileData.filesize = await ctx.helper.bytesToSize(fileData.filesize);
+                    fileData.uid = ctx.session.sessionUser.accountId;
                     fileData.id = result.insertId;
                     delete fileData.filepath;
                     files.push(fileData);
@@ -901,6 +1252,12 @@ module.exports = app => {
                     }
                 }
 
+                // 获取是否已存在调用变更令
+                const changeUsedData = await ctx.service.stageChange.getFinalUsedData(ctx.tender.id, changeData.cid);
+                const stageChangeNum = this.ctx.helper.sum(changeUsedData.map(x => { return Math.abs(x.used_qty); }));
+                if (stageChangeNum !== 0) {
+                    throw '该变更令已被调用,无法重新审批';
+                }
                 // 重新审批
                 const result = await ctx.service.change.checkAgain(changeData.cid);
                 if (!result) {

+ 2 - 1
app/controller/ledger_audit_controller.js

@@ -138,7 +138,8 @@ module.exports = app => {
                 if (exist) {
                     throw '该审核人已存在,请勿重复添加';
                 }
-                const is_gdzs = ctx.tender.info.shenpi.ledger === shenpiConst.sp_status.gdzs ? 1 : 0;
+                const shenpiInfo = await ctx.service.shenpiAudit.getDataByCondition({ tid: ctx.tender.id, sp_type: shenpiConst.sp_type.ledger, sp_status: shenpiConst.sp_status.gdzs });
+                const is_gdzs = shenpiInfo && ctx.tender.info.shenpi.ledger === shenpiConst.sp_status.gdzs ? 1 : 0;
                 const result = await ctx.service.ledgerAudit.addAuditor(ctx.tender.id, id, ctx.tender.data.ledger_times, is_gdzs);
                 if (!result) {
                     throw '添加审核人失败';

+ 3 - 1
app/controller/ledger_controller.js

@@ -27,6 +27,7 @@ const path = require('path');
 const exportExcel = require('../lib/export_excel');
 const billsPosConvert = require('../lib/bills_pos_convert');
 const xlsx = require('js-xlsx');
+const stdConst = require('../const/standard');
 
 module.exports = app => {
 
@@ -537,7 +538,7 @@ module.exports = app => {
                     case 'tz':
                         const templateId = await this.ctx.service.valuation.getValuationTemplate(
                             this.ctx.tender.data.valuation, this.ctx.tender.data.measure_type);
-                        responseData.data = await ctx.service.ledger.importExcel(templateId, data);
+                        responseData.data = await ctx.service.ledger.importExcel(templateId, data.sheet, data.filter);
                         break;
                     case 'gcl2xmj':
                         responseData.data = await ctx.service.ledger.importGclExcel(data.id, data.sheet);
@@ -697,6 +698,7 @@ module.exports = app => {
                 const renderData = {
                     tender: ctx.tender.data,
                     jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.ledger.gather),
+                    chapterFilter: stdConst.chapterFilter,
                 };
 
                 await this.layout('ledger/gather.ejs', renderData);

+ 2 - 1
app/controller/material_controller.js

@@ -743,7 +743,8 @@ module.exports = app => {
                 if (exist) {
                     throw '该审核人已存在,请勿重复添加';
                 }
-                const is_gdzs = ctx.tender.info.shenpi.material === shenpiConst.sp_status.gdzs ? 1 : 0;
+                const shenpiInfo = await ctx.service.shenpiAudit.getDataByCondition({ tid: ctx.tender.id, sp_type: shenpiConst.sp_type.material, sp_status: shenpiConst.sp_status.gdzs });
+                const is_gdzs = shenpiInfo && ctx.tender.info.shenpi.material === shenpiConst.sp_status.gdzs ? 1 : 0;
                 const result = await ctx.service.materialAudit.addAuditor(ctx.material.id, id, ctx.material.times, is_gdzs);
                 if (!result) {
                     throw '添加审核人失败';

+ 28 - 22
app/controller/report_controller.js

@@ -1249,20 +1249,24 @@ function mergeSignAudit(pageData, currRoleRelList, currAuditList) {
         if (page.signature_audit_cells) {
             for (const sCell of page.signature_audit_cells) {
                 sCell.Value = ''; // 这里要先清除原有信息
-                for (const role_rel of currRoleRelList) {
-                    if (sCell.signature_name === role_rel.signature_name + '_审核意见') {
-                        let preDate = '';
-                        sCell.Value = '同意'; // 只有选择了签名的,才需要初始化一个默认的意见(之前的逻辑在有多个签名,哪怕只选择了一个,其他的意见都会有默认意见)
-                        for (const audit_rel of currAuditList) {
-                            if (role_rel.acc_id === audit_rel.aid) {
-                                if (audit_rel.end_time > preDate && audit_rel.status === 3) {
-                                    sCell.Value = audit_rel.opinion;
-                                    preDate = audit_rel.end_time;
+                if (currRoleRelList && currRoleRelList.length > 0) {
+                    for (const role_rel of currRoleRelList) {
+                        if (sCell.signature_name === role_rel.signature_name + '_审核意见') {
+                            let preDate = '';
+                            sCell.Value = '同意'; // 只有选择了签名的,才需要初始化一个默认的意见(之前的逻辑在有多个签名,哪怕只选择了一个,其他的意见都会有默认意见)
+                            if (currAuditList && currAuditList.length > 0) {
+                                for (const audit_rel of currAuditList) {
+                                    if (role_rel.acc_id === audit_rel.aid) {
+                                        if (audit_rel.end_time > preDate && audit_rel.status === 3) {
+                                            sCell.Value = audit_rel.opinion;
+                                            preDate = audit_rel.end_time;
+                                        }
+                                        // 不能break,实际会有多个审核意见,以最后一个为准
+                                    }
                                 }
-                                // 不能break,实际会有多个审核意见,以最后一个为准
                             }
+                            break;
                         }
-                        break;
                     }
                 }
             }
@@ -1280,19 +1284,21 @@ function mergeSignDate(pageData, current_stage_id, currRoleRelList, STAGE_AUDIT,
             }
         }
     }
-    for (const page of pageData.items) {
-        if (page.signature_date_cells) {
-            for (const sCell of page.signature_date_cells) {
-                sCell.Value = _getSignDateDftName();
-                for (const role_rel of currRoleRelList) {
-                    if (sCell.signature_name === role_rel.signature_name + '_签字日期') {
-                        if (role_rel.sign_date !== '') {
-                            if (typeof role_rel.sign_date === 'string') {
-                                role_rel.sign_date = new Date(role_rel.sign_date);
+    if (currRoleRelList && currRoleRelList.length > 0) {
+        for (const page of pageData.items) {
+            if (page.signature_date_cells) {
+                for (const sCell of page.signature_date_cells) {
+                    sCell.Value = _getSignDateDftName();
+                    for (const role_rel of currRoleRelList) {
+                        if (sCell.signature_name === role_rel.signature_name + '_签字日期') {
+                            if (role_rel.sign_date !== '') {
+                                if (typeof role_rel.sign_date === 'string') {
+                                    role_rel.sign_date = new Date(role_rel.sign_date);
+                                }
+                                sCell.Value = role_rel.sign_date.Format(role_rel.sign_date_format);
                             }
-                            sCell.Value = role_rel.sign_date.Format(role_rel.sign_date_format);
+                            break;
                         }
-                        break;
                     }
                 }
             }

+ 5 - 2
app/controller/revise_controller.js

@@ -21,6 +21,7 @@ const spreadConst = require('../const/spread');
 const shenpiConst = require('../const/shenpi');
 const fs = require('fs');
 const LzString = require('lz-string');
+const stdConst = require('../const/standard');
 
 module.exports = app => {
     class ReviseController extends app.BaseController {
@@ -629,6 +630,7 @@ module.exports = app => {
             const stdData = await stdLib.getDataByDataId(data.stdLibId, data.stdNode);
             switch (addType) {
                 case stdDataAddType.child:
+                    return await this.ctx.service.reviseBills.addStdNodeAsChild(revise.tid, data.id, stdData, revise.id);
                     break;
                 case stdDataAddType.next:
                     return await this.ctx.service.reviseBills.addStdNode(revise.tid, data.id, stdData, revise.id);
@@ -798,8 +800,8 @@ module.exports = app => {
                 // 检查审核人是否已存在
                 const exist = await ctx.service.reviseAudit.getAuditor(revise.id, id, revise.times);
                 if (exist) throw '该审核人已存在,请勿重复添加';
-
-                const is_gdzs = ctx.tender.info.shenpi.revise === shenpiConst.sp_status.gdzs ? 1 : 0;
+                const shenpiInfo = await ctx.service.shenpiAudit.getDataByCondition({ tid: ctx.tender.id, sp_type: shenpiConst.sp_type.revise, sp_status: shenpiConst.sp_status.gdzs });
+                const is_gdzs = shenpiInfo && ctx.tender.info.shenpi.revise === shenpiConst.sp_status.gdzs ? 1 : 0;
                 const result = await ctx.service.reviseAudit.addAuditor(revise, id, is_gdzs);
                 if (!result) throw '添加审核人失败';
 
@@ -1019,6 +1021,7 @@ module.exports = app => {
             const renderData = {
                 revise,
                 jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.revise.gclCompare),
+                chapterFilter: stdConst.chapterFilter,
             };
             await this.layout('revise/gcl_compare.ejs', renderData);
         }

+ 65 - 0
app/controller/schedule_controller.js

@@ -0,0 +1,65 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Ellisran
+ * @date 2020/7/2
+ * @version
+ */
+
+const moment = require('moment');
+const measureType = require('../const/tender').measureType;
+const billsPosConvert = require('../lib/bills_pos_convert');
+
+module.exports = app => {
+    class ScheduleController extends app.BaseController {
+        async index(ctx) {
+            try {
+                const renderData = {
+                    tender: ctx.tender.data,
+                    tenderMenu: this.menu.tenderMenu,
+                    preUrl: '/tender/' + ctx.tender.id,
+                };
+                await this.layout('schedule/index.ejs', renderData);
+            } catch (err) {
+                this.log(err);
+                ctx.redirect(this.menu.menu.dashboard.url);
+            }
+        }
+
+        async ledger(ctx) {
+            const tender = ctx.tender;
+            const renderData = {
+                tender: tender.data,
+                tenderInfo: tender.info,
+                measureType,
+                jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.schedule.ledger),
+            };
+            await this.layout('schedule/ledger.ejs', renderData, 'schedule/ledger_modal.ejs');
+        }
+
+        /**
+         * 获取部位明细数据(Ajax)
+         *
+         * @param ctx
+         * @return {Promise<void>}
+         */
+        async loadLedgerData(ctx) {
+            try {
+                const ledgerData = await ctx.service.ledger.getData(ctx.tender.id);
+                const posData = this.ctx.tender.data.measure_type === measureType.tz.value
+                    ? await ctx.service.pos.getPosData({ tid: ctx.tender.id }) : [];
+                const convert = new billsPosConvert(ctx);
+                convert.loadData(ledgerData, posData, []);
+                const result = await convert.convert();
+                ctx.body = { err: 0, msg: '', data: result };
+            } catch (err) {
+                this.log(err);
+                ctx.body = { err: 1, msg: err.toString(), data: [] };
+            }
+        }
+    }
+
+    return ScheduleController;
+};

+ 4 - 1
app/controller/stage_controller.js

@@ -24,6 +24,7 @@ const accountGroup = require('../const/account_group').group;
 const sendToWormhole = require('stream-wormhole');
 const billsPosConvert = require('../lib/bills_pos_convert');
 const fs = require('fs');
+const stdConst = require('../const/standard');
 
 module.exports = app => {
     class StageController extends app.BaseController {
@@ -978,7 +979,8 @@ module.exports = app => {
                 if (exist) {
                     throw '该审核人已存在,请勿重复添加';
                 }
-                const is_gdzs = ctx.tender.info.shenpi.stage === shenpiConst.sp_status.gdzs ? 1 : 0;
+                const shenpiInfo = await ctx.service.shenpiAudit.getDataByCondition({ tid: ctx.tender.id, sp_type: shenpiConst.sp_type.stage, sp_status: shenpiConst.sp_status.gdzs });
+                const is_gdzs = shenpiInfo && ctx.tender.info.shenpi.stage === shenpiConst.sp_status.gdzs ? 1 : 0;
                 const result = await ctx.service.stageAudit.addAuditor(ctx.stage.id, id, ctx.stage.times, is_gdzs);
                 if (!result) {
                     throw '添加审核人失败';
@@ -1171,6 +1173,7 @@ module.exports = app => {
                 [renderData.gclSpread, renderData.leafXmjSpread] = this._getGatherSpreadSetting();
 
                 renderData.jsFiles = this.app.jsFiles.common.concat(this.app.jsFiles.stage.gather);
+                renderData.chapterFilter = stdConst.chapterFilter;
                 await this.layout('stage/gather.ejs', renderData, 'stage/gather_modal.ejs');
             } catch (err) {
                 this.log(err);

+ 24 - 5
app/controller/tender_controller.js

@@ -33,7 +33,7 @@ module.exports = app => {
             ctx.showTitle = true;
         }
 
-        async _getLedgerAuditInfo (tender) {
+        async _getLedgerAuditInfo(tender) {
             tender.cur_flow = {
                 title: '台账',
                 status: auditConst.ledger.tiStatusString[tender.ledger_status],
@@ -59,7 +59,7 @@ module.exports = app => {
             }
         }
 
-        async _getStageAuditInfo (tender, stage) {
+        async _getStageAuditInfo(tender, stage) {
             tender.cur_flow = {
                 title: '第' + stage.order + '期',
                 status: auditConst.stage.tiStatusString[stage.status],
@@ -73,11 +73,11 @@ module.exports = app => {
                     tender.cur_flow.name = user.name;
                 }
                 if (stage.order > 1) {
-                    const preStage = await this.ctx.service.stage.getDataByCondition({ tid: tender.id, order: stage.order - 1});
+                    const preStage = await this.ctx.service.stage.getDataByCondition({ tid: tender.id, order: stage.order - 1 });
                     if (!preStage) return;
 
                     const pre = await this.ctx.service.stageAudit.getLastestAuditor(preStage.id, preStage.times, auditConst.stage.status.checked);
-                    if (pre) tender.pre_flow = { name: pre.name, time: pre.end_time};
+                    if (pre) tender.pre_flow = { name: pre.name, time: pre.end_time };
                 }
             } else {
                 let cur;
@@ -120,7 +120,7 @@ module.exports = app => {
 
                 for (const t of tenderList) {
                     if (t.user_id === this.ctx.session.sessionUser.accountId && (
-                            t.ledger_status === auditConst.ledger.status.checkNo || t.ledger_status === auditConst.ledger.status.uncheck)) {
+                        t.ledger_status === auditConst.ledger.status.checkNo || t.ledger_status === auditConst.ledger.status.uncheck)) {
                         const sum = await this.ctx.service.ledger.addUp({ tender_id: t.id/* , is_leaf: true*/ });
                         t.total_price = sum.total_price;
                         t.deal_tp = sum.deal_tp;
@@ -426,7 +426,11 @@ module.exports = app => {
                     p.end_ratio = ctx.helper.mul(ctx.helper.div(p.end_tp, tender.sum, 4), 100);
                 }
                 const revise = await ctx.service.ledgerRevise.getLastestRevise(tender.id);
+                const tenders = await ctx.service.tender.getList('', null, 1);
+                const categoryData = await ctx.service.category.getAllCategory(ctx.session.sessionProject.id);
                 const renderData = {
+                    tenders,
+                    categoryData,
                     tender,
                     revise,
                     tenderInfo: ctx.tender.info,
@@ -874,6 +878,21 @@ module.exports = app => {
                 ctx.body = this.ajaxErrorBody(err, '保存审批流程设置失败');
             }
         }
+        /**
+         * 拷贝设置
+         * @param {object} ctx - 上下文
+         */
+        async copyTender(ctx) {
+            try {
+                const id = ctx.tender.data.id;
+                const { id: copy_id = '', type: typeArr = [] } = JSON.parse(ctx.request.body.data);
+                await ctx.service.tenderInfo.copyTenderHandler(id, copy_id, typeArr);
+                ctx.body = { err: 0, msg: '' };
+            } catch (error) {
+                this.log(error);
+                ctx.body = this.ajaxErrorBody(error, '保存审批流程设置失败');
+            }
+        }
     }
 
     return TenderController;

+ 203 - 18
app/lib/analysis_excel.js

@@ -45,6 +45,18 @@ const aeUtils = {
     }
 };
 
+const mainReg = /^(GD*)?G?(\d\d)*\d$/i;
+const subReg = /^(GD*)?G?[A-Z]{2}(\d\d)+$/i;
+const gdXmjPartReg = /^(GD*)?G?/;
+const specCode106 = {
+    code: ['102', '103', '104'],
+    reg: /^(GD*)?G?106/i
+};
+const specCode109 = {
+    code: ['102', '103', '104', '105', '106', '107', '108'],
+    reg: /^(GD*)?G?109/i
+};
+
 class ImportBaseTree {
     /**
      * 构造函数
@@ -61,6 +73,8 @@ class ImportBaseTree {
         this.roots = [];
         this.pos = [];
         this.tempData = [];
+        // 以id为索引
+        this.nodes = {};
 
         // 缓存
         this.finalNode = null;
@@ -97,6 +111,7 @@ class ImportBaseTree {
                 this.keyNodeId = node.ledger_id + 1;
             }
             this.tempData.push(node);
+            this.nodes[node.ledger_id] = node;
         }
         for (const node of this.items) {
             node.tender_id = this.ctx.tender.id;
@@ -182,6 +197,7 @@ class ImportBaseTree {
      * @param {Object} node - 当前添加的节点
      */
     defineCacheData(node) {
+        this.nodes[node.ledger_id] = node;
         this.finalNode = node;
         this.finalPrecision = this.ctx.helper.findPrecision(this.ctx.tender.info.precision, node.unit);
         if (node.code) {
@@ -297,6 +313,133 @@ class ImportBaseTree {
     }
 }
 
+class ImportStd18Tree extends ImportBaseTree {
+
+    /**
+     * 检查是否是父项
+     * @param parent
+     * @param code
+     * @returns {boolean}
+     * @private
+     */
+    _checkParent(parent, code) {
+        if (code === 'LJ0703') console.log(parent.code, code);
+        const codeNumberPart = code.replace(gdXmjPartReg, '');
+        if (!parent.code) return false;
+        const numberPart = parent.code.replace(gdXmjPartReg, '');
+        if (code === 'LJ0703') console.log(numberPart, codeNumberPart);
+        if (!numberPart || !codeNumberPart || numberPart.length >= codeNumberPart.length) return false;
+        return code.indexOf(numberPart) === 0 ||
+            code.indexOf('G' + numberPart) === 0 ||
+            code.indexOf('GD' + numberPart) === 0;
+    }
+
+    /**
+     * 查找主表项目节父项
+     * @param code
+     * @returns {*}
+     */
+    findMainXmjParent(code) {
+        const numberPart = code.replace(gdXmjPartReg, '');
+        if (numberPart.length <= 1) throw '首层项目节模板中未定义,不支持导入';
+
+        let parent = this.cacheMainXmjNode;
+        while (parent) {
+            if (this._checkParent(parent, code)) return parent;
+            parent = this.nodes[parent.ledger_pid];
+        }
+        return null;
+    }
+
+    /**
+     * 查找分表项目节父项
+     * @param code
+     * @returns {*}
+     */
+    findSubXmjParent(code) {
+        let parent = this.cacheSubXmjNode;
+        while (parent && parent.is_sub && parent.code.match(subReg)) {
+            if (this._checkParent(parent, code)) return parent;
+            parent = this.nodes[parent.ledger_pid];
+        }
+        return this.cacheMainXmjNode;
+    }
+
+    /**
+     * 根据 编号 查找 父项项目节
+     * @param {String} code - 子项编号
+     * @returns {*}
+     */
+    findXmjParent(code) {
+        if (code.match(mainReg)) {
+            if (!this.cacheMainXmjNode) throw '主表项目节找不到父项';
+            return this.findMainXmjParent(code);
+        } else if (code.match(subReg)) {
+            if (!this.cacheMainXmjNode) throw '分表项目节找不到所属主表项目节';
+            return this.findSubXmjParent(code);
+        }
+    }
+
+    /**
+     * 定义缓存节点(添加工程量清单、部位明细需使用缓存定位)
+     * @param {Object} node - 当前添加的节点
+     */
+    defineCacheData(node) {
+        super.defineCacheData(node);
+        if (node.code) {
+            if (node.code.match(mainReg)) {
+                node.is_main = true;
+                this.cacheMainXmjNode = node;
+                this.cacheSubXmjNode = null;
+            } else if (node.code.match(subReg)) {
+                node.is_sub = true;
+                this.cacheSubXmjNode = node;
+            }
+            if (node.code.match(specCode106.reg)) {
+                if (this.cacheSpecMainXmj1 && this.cacheSpecMainXmj1.code.match(specCode109.reg)) {
+                    this.cacheSpecMainXmj2 = node;
+                } else {
+                    this.cacheSpecMainXmj1 = node;
+                }
+            } else if (node.code.match(specCode109.reg)) {
+                this.cacheSpecMainXmj1 = node;
+                this.cacheSpecMainXmj2 = null;
+            }
+        }
+    }
+
+    /**
+     * 添加 项目节
+     * @param {Object} node - 项目节
+     * @returns {*}
+     */
+    addXmjNode(node) {
+        if (!node.code || (!node.code.match(mainReg) && !node.code.match(subReg))) return null;
+
+        node.id = this.ctx.app.uuid.v4();
+        node.tender_id = this.ctx.tender.id;
+        node.children = [];
+        if ((specCode106.code.indexOf(node.code) >= 0)) {
+            if (this.cacheSpecMainXmj2 && this.cacheSpecMainXmj2.code.match(specCode106.reg))
+                return this.addNodeWithParent(node, this.cacheSpecMainXmj2);
+            if (this.cacheSpecMainXmj1 && this.cacheSpecMainXmj1.code.match(specCode106.reg))
+                return this.addNodeWithParent(node, this.cacheSpecMainXmj1);
+        }
+        if ((specCode109.code.indexOf(node.code) >= 0) &&
+            (this.cacheSpecMainXmj1 && this.cacheSpecMainXmj1.code.match(specCode109.reg))) {
+            return this.addNodeWithParent(node, this.cacheSpecMainXmj1)
+        }
+        const temp = this.findTempData(node);
+        if (temp) {
+            this.defineCacheData(temp);
+            return temp;
+        } else {
+            const parent = this.findXmjParent(node.code);
+            return this.addNodeWithParent(node, parent);
+        }
+    }
+}
+
 class AnalysisExcelTree {
     /**
      * 构造函数
@@ -319,6 +462,29 @@ class AnalysisExcelTree {
         };
     }
 
+    _isMatch11(tempData) {
+        return _.find(tempData, x => {
+            return x.code.indexOf('-') > 0;
+        })
+    }
+
+    _isMatch18(tempData) {
+        return _.every(tempData, x => {
+            return !x.code || !!x.code.match(mainReg);
+        });
+    }
+
+    _getNewCacheTree(tempData) {
+        // 模板符合11编办规则,使用11编办树
+        if (this._isMatch18(tempData)) {
+            return new ImportStd18Tree(tempData, this.ctx);
+        // 反之使用11编办(未校验模板是否符合,替换注释部分即可实现)
+        // } else if (this._isMatch11(tempData)){
+        } else {
+            return new ImportBaseTree(tempData, this.ctx);
+        }
+    }
+
     /**
      * 读取项目节节点
      * @param {Array} row - excel行数据
@@ -326,23 +492,38 @@ class AnalysisExcelTree {
      * @private
      */
     _loadXmjNode(row) {
-        const node = {};
-        node.code = this.ctx.helper.replaceReturn(row[this.colsDef.code]);
-        node.name = this.ctx.helper.replaceReturn(row[this.colsDef.name]);
-        node.unit = this.ctx.helper.replaceReturn(row[this.colsDef.unit]);
-        const precision = this.ctx.helper.findPrecision(this.ctx.tender.info.precision, node.unit);
-        node.quantity = this.ctx.helper.round(aeUtils.toNumber(row[this.colsDef.quantity]), precision.value);
-        node.dgn_qty1 = aeUtils.toNumber(row[this.colsDef.dgn_qty1]);
-        node.dgn_qty2 = aeUtils.toNumber(row[this.colsDef.dgn_qty2]);
-        node.unit_price = aeUtils.toNumber(row[this.colsDef.unit_price]);
-        node.drawing_code = this.ctx.helper.replaceReturn(row[this.colsDef.drawing_code]);
-        node.memo = this.ctx.helper.replaceReturn(row[this.colsDef.memo]);
-        if (node.quantity && node.unit_price) {
-            node.total_price = this.ctx.helper.mul(node.quantity, node.unit_price, this.ctx.tender.info.decimal.tp);
-        } else {
-            node.total_price = null;
+        try {
+            const node = {};
+            node.code = this.ctx.helper.replaceReturn(row[this.colsDef.code]);
+            node.name = this.ctx.helper.replaceReturn(row[this.colsDef.name]);
+            node.unit = this.ctx.helper.replaceReturn(row[this.colsDef.unit]);
+            const precision = this.ctx.helper.findPrecision(this.ctx.tender.info.precision, node.unit);
+            node.quantity = this.ctx.helper.round(aeUtils.toNumber(row[this.colsDef.quantity]), precision.value);
+            node.dgn_qty1 = aeUtils.toNumber(row[this.colsDef.dgn_qty1]);
+            node.dgn_qty2 = aeUtils.toNumber(row[this.colsDef.dgn_qty2]);
+            node.unit_price = aeUtils.toNumber(row[this.colsDef.unit_price]);
+            node.drawing_code = this.ctx.helper.replaceReturn(row[this.colsDef.drawing_code]);
+            node.memo = this.ctx.helper.replaceReturn(row[this.colsDef.memo]);
+            if (node.quantity && node.unit_price) {
+                node.total_price = this.ctx.helper.mul(node.quantity, node.unit_price, this.ctx.tender.info.decimal.tp);
+            } else {
+                node.total_price = null;
+            }
+            return this.cacheTree.addXmjNode(node);
+        } catch (error) {
+            console.log(error);
+            if (error.stack) {
+                this.ctx.logger.error(error);
+            } else {
+                this.ctx.getLogger('fail').info(JSON.stringify({
+                    error,
+                    project: this.ctx.session.sessionProject,
+                    user: this.ctx.session.sessionUser,
+                    body: row,
+                }));
+            }
+            return null;
         }
-        return this.cacheTree.addXmjNode(node);
     }
     /**
      * 读取工程量清单数据
@@ -366,6 +547,9 @@ class AnalysisExcelTree {
         } else {
             node.total_price = null;
         }
+        if (this.filter && !node.quantity && !node.total_price) {
+            return this.filter;
+        }
         return this.cacheTree.addGclNode(node);
     }
     /**
@@ -429,9 +613,10 @@ class AnalysisExcelTree {
      * @param {Array} tempData - 新建项目使用的清单模板
      * @returns {ImportBaseTree}
      */
-    analysisData(sheet, tempData) {
+    analysisData(sheet, tempData, filter) {
+        this.filter = filter;
         this.colsDef = null;
-        this.cacheTree = new ImportBaseTree(tempData, this.ctx);
+        this.cacheTree = this._getNewCacheTree(tempData);
         this.errorData = [];
         this.loadEnd = false;
 

+ 111 - 0
app/lib/bills_utils.js

@@ -0,0 +1,111 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date
+ * @version
+ */
+
+const allNumReg = /^[0-9]+$/;
+const suffixNumReg = /[0-9]+$/;
+const mainReg = /^((GD*)|G)?(\d\d)*\d$/i;
+const subReg = /^((GD*)|G)?[A-Z]{2}(\d\d)+$/i;
+const gdXmjPartReg = /^((GD*)|G)/;
+// 路基、路面、涵洞、、桥梁、隧道、交通安全、隧道机电、绿化、其他、房建、专项评估
+const subXmjPrefixReplace = [
+    { str: 'LJ', num: '110', },
+    { str: 'LM', num: '120', },
+    { str: 'HD', num: '130', },
+    { str: 'TD', num: '140', },
+    { str: 'QL', num: '150', },
+    { str: 'SD', num: '160', },
+    { str: 'JA', num: '170', },
+    { str: 'SJ', num: '180', },
+    { str: 'LH', num: '190', },
+    { str: 'QT', num: '210', },
+    { str: 'FJ', num: '211', },
+    { str: 'ZP', num: '212', },
+];
+
+const findSubXmjPrefixNum = function (str) {
+    for (const p of subXmjPrefixReplace) {
+        if (str.indexOf(p.str) >= 0)
+            return p.num;
+    }
+    return '999';
+};
+
+const subXmjToNumStr = function (str) {
+    const num = str.match(suffixNumReg);
+    return findSubXmjPrefixNum(str) + (num ? num[0] : '') + (gdXmjPartReg.test(str) ? 1 : 0);
+};
+
+module.exports = {
+    reg: {
+        mainXmj: mainReg,
+        subXmj: subReg,
+        gdXmjPartReg: gdXmjPartReg,
+    },
+    /**
+     * 用于比较1-1类型项目节编号,或者202-1-a/202-2-3类型工程量清单
+     *
+     * @param str1
+     * @param str2
+     * @param symbol
+     */
+    defaultCompareCode: function (str1, str2, symbol = '-') {
+        if (!str1) {
+            return 1;
+        } else if (!str2) {
+            return -1;
+        }
+
+        function compareSubCode(code1, code2) {
+            if (allNumReg.test(code1)) {
+                if (allNumReg.test(code2)) {
+                    return parseInt(code1) - parseInt(code2);
+                }
+                return -1;
+
+            }
+            if (allNumReg.test(code2)) {
+                return 1;
+            }
+            return code1 === code2 ? 0 : (code1 < code2 ? -1 : 1); // code1.localeCompare(code2);
+        }
+        const aCodes = str1.split(symbol),
+            bCodes = str2.split(symbol);
+        for (let i = 0, iLength = Math.min(aCodes.length, bCodes.length); i < iLength; ++i) {
+            const iCompare = compareSubCode(aCodes[i], bCodes[i]);
+            if (iCompare !== 0) {
+                return iCompare;
+            }
+        }
+        return aCodes.length - bCodes.length;
+    },
+    compare18MainXmj(str1, str2) {
+        if (str1.length < str2.length) {
+            return -1;
+        } else if (str1.length > str2.length) {
+            return 1;
+        } else {
+            const num1 = str1.match(suffixNumReg)[0], num2 = str2.match(suffixNumReg)[0];
+            return num1.localeCompare(num2);
+        }
+    },
+    compare18SubXmj(str1, str2) {
+        const numStr1 = subXmjToNumStr(str1), numStr2 = subXmjToNumStr(str2);
+        return numStr1.localeCompare(numStr2);
+    },
+    compareCode(str1, str2) {
+        if (mainReg.test(str1)) {
+            return this.compare18MainXmj(str1, str2);
+        }
+        if (subReg.test(str2)) {
+            return this.compare18SubXmj(str1, str2);
+        }
+        return this.defaultCompareCode(str1, str2);
+    }
+};

+ 1 - 1
app/lib/ledger.js

@@ -260,7 +260,7 @@ class baseTree {
         if (fun) {
             fun(node);
         } else if (this.setting.calc) {
-            this.setting.calc(node);
+            this.setting.calc(node, this.ctx.helper);
         }
     }
     calculateAll(fun) {

+ 7 - 3
app/lib/rpt_data_analysis.js

@@ -299,7 +299,8 @@ const gatherChapter = {
         sum: {
             name: '合计',
             order: 4,
-        }
+        },
+        filter: [{node_type: standard.nodeType.find(function (x) {return x.text === '计日工'}).value}, {field: 'name', part: '计日工'}],
     },
     customSetting1: {
         count: 7,
@@ -541,8 +542,9 @@ const gatherChapter = {
             this._completeStageSumInfo(otherChapter, sourceData[0]);
             this._completeStageSumInfo(customChapter, sourceData[0]);
         }
-        const filter = [];
+        const filter = [], defaultFilter = [];
         for (const d of sourceData) {
+            if (this._checkMatch(this.defaultSetting.filter, d)) defaultFilter.push(d.full_path);
             for (const c of customChapter) {
                 if (c.match && this._checkMatch(c.match, d)) {
                     gatherData(c, d);
@@ -562,7 +564,9 @@ const gatherChapter = {
                 }
             }
             if (d.b_code) {
-                const c = this._getGclChapter(gclChapter, d, 'b_code');
+                const c = this._checkFilter(d.full_path, defaultFilter)
+                    ? gclChapter.find(x => {return x.cType === 21})
+                    : this._getGclChapter(gclChapter, d, 'b_code');
                 if (c) {
                     gatherData(c, d);
                 }

+ 25 - 0
app/lib/wechat.js

@@ -162,6 +162,31 @@ class WX {
                             },
                         };
                         break;
+                    case wxConst.template.advance:
+                        templateId = wxConst.templateId.advance;
+                        remark = data.status === wxConst.status.check ? '微信暂无法在线审批' :
+                            (data.status === wxConst.status.success ? '审批已通过,查看审批结果' : '审批被退回,查看退回结果');
+                        msgData = {
+                            first: {
+                                value: '您好,预付款申请' + data.tips,
+                            },
+                            keyword1: {
+                                value: data.projectName,
+                            },
+                            keyword2: {
+                                value: data.tenderName,
+                            },
+                            keyword3: {
+                                value: data.qi,
+                            },
+                            keyword4: {
+                                value: data.tp ? data.tp.toString() : null,
+                            },
+                            remark: {
+                                value: remark + ',请登录PC端系统。',
+                            },
+                        };
+                        break;
                     default:break;
                 }
                 if (msgData !== '' && templateId) {

+ 1 - 1
app/middleware/api2other_check.js

@@ -17,7 +17,7 @@ module.exports = options => {
             if (maintainData.status === maintainConst.status.ongoing) {
                 throw '系统维护中~';
             }
-            const code = this.query.code || this.request.body.code;
+            const code = this.query.projectCode || this.request.body.projectCode;
             const sign = this.query.sign || this.request.body.sign;
             const time = this.query.time || this.request.body.time;
             if (!code || !sign || !time) {

+ 83 - 0
app/middleware/change_check.js

@@ -0,0 +1,83 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Ellisran
+ * @date 2020/10/15
+ * @version
+ */
+
+const status = require('../const/audit').flow.status;
+const shenpiConst = require('../const/shenpi');
+const _ = require('lodash');
+
+module.exports = options => {
+    /**
+     * 标段校验 中间件
+     * 1. 读取标段数据(包括属性)
+     * 2. 检验用户是否可见标段(不校验具体权限)
+     *
+     * @param {function} next - 中间件继续执行的方法
+     * @return {void}
+     */
+    return function* changeCheck(next) {
+        try {
+            // 获取revise
+            const cid = this.params.cid || this.request.body.cid;
+            if (!cid) {
+                throw '您访问的变更令不存在';
+            }
+            const change = yield this.service.change.getDataByCondition({ cid });
+            // 读取原报、审核人数据
+            change.auditors = yield this.service.changeAudit.getListGroupByTimes(change.cid, change.times);
+            change.curAuditor = yield this.service.changeAudit.getCurAuditor(change.cid, change.times);
+            console.log(change.curAuditor);
+
+            if (!change) throw '变更令数据有误';
+            // 权限相关
+            // todo 校验权限 (变更参与人)
+            const accountId = this.session.sessionUser.accountId,
+                auditorIds = _.map(change.auditors, 'uid'),
+                shareIds = [];
+            const permission = this.session.sessionUser.permission;
+            if (accountId === change.uid) { // 原报
+                if (change.curAuditor) {
+                    change.readOnly = change.curAuditor.uid !== accountId;
+                } else {
+                    change.readOnly = change.status !== status.uncheck && change.status !== status.back;
+                }
+            } else if (auditorIds.indexOf(accountId) !== -1) { // 审批人
+                if (change.status === status.uncheck) {
+                    throw '您无权查看该数据';
+                }
+                change.readOnly = true;
+            } else if (shareIds.indexOf(accountId) !== -1 || (permission !== null && permission.tender !== undefined && permission.tender.indexOf('2') !== -1)) { // 分享人
+                if (change.status === status.uncheck) {
+                    throw '您无权查看该数据';
+                }
+                change.readOnly = true;
+            } else { // 其他不可见
+                throw '您无权查看该数据';
+            }
+
+            this.change = change;
+            yield next;
+        } catch (err) {
+            console.log(err);
+            // 输出错误到日志
+            if (err.stack) {
+                this.logger.error(err);
+            } else {
+                this.getLogger('fail').info(JSON.stringify({
+                    error: err,
+                    project: this.session.sessionProject,
+                    user: this.session.sessionUser,
+                    body: this.session.body,
+                }));
+            }
+            // 重定向值标段管理
+            this.redirect(this.request.headers.referer);
+        }
+    };
+};

+ 32 - 0
app/public/js/category.js

@@ -58,6 +58,7 @@ function getValueHtml(value) {
     const html = [];
     for (const v of value) {
         html.push('<tr name="value" vid="' + v.id + '">');
+        html.push('<td><a href="javascript:void(0);" class="up-btn mr-2" title="上移"><i class="fa fa-caret-up"></i></a><a href="javascript:void(0);" class="down-btn" title="下移"><i class="fa fa-caret-down "></i></a></td>');
         html.push('<td><input class="form-control form-control-sm" name="value" placeholder="请输入值" value="' + v.value + '" vid ="' + v.id +  '"></td>');
         html.push('<td>', v.relaTenders.length + v.newTenders.length ,'</td>');
         html.push('<td><a href="javascript: void(0);" class="text-danger" name="del-value" vid="' + v.id + '">删除</a></td>');
@@ -65,6 +66,11 @@ function getValueHtml(value) {
     }
     return html.join('');
 }
+function makeIconColor() {
+    $('#value-list a').removeClass('text-secondary');
+    $('#value-list').children('tr').eq(0).find('.up-btn').addClass('text-secondary');
+    $('#value-list').children('tr').eq(-2).find('.down-btn').addClass('text-secondary');
+}
 // 根据分类id查找分类
 function findCategory(cid) {
     for (const c of cData) {
@@ -113,11 +119,14 @@ function bindCategoryValueControl() {
         const value = _.find(editCate.value, function (v) {
             return v.id == vid;
         });
+        const delHtml = $(this).parents('td').html();
+        const _self = $(this).parents('td');
         $(this).remove();
         if (value.delete) { return; }
 
         if (value.relaTenders.length > 0) {
             // 提示用户转移标段
+            _self.html(delHtml);
             $('#tender-count').text(value.relaTenders.length).attr('vid', vid);
             $('#tender-target').html(getValidValueHtml(vid));
             $('input[type=radio]', '#tender-target')[0].checked = true;
@@ -173,8 +182,10 @@ function bindCategoryControl() {
                 editCate.value.push(newValue);
                 $(this).before(getValueHtml([newValue]));
                 bindCategoryValueControl();
+                makeIconColor();
             });
             bindCategoryValueControl();
+            makeIconColor();
             $('#add').modal('show');
         }
     });
@@ -293,4 +304,25 @@ $(document).ready(() => {
             $('#del-cate').modal('hide');
         });
     });
+
+    // 上移值
+    $('body').on('click', '.up-btn', function () {
+        const prev = $(this).parents("tr").prev();
+        const prevIndex = parseInt($(prev).index('#value-list tr'));
+        $(this).parents("tr").insertBefore(prev);
+        makeIconColor();
+        // if(prevIndex === 1){
+        //     console.log('hello');
+        // }
+    });
+    // 下一值
+    $('body').on('click', '.down-btn', function () {
+        const next = $(this).parents("tr").next();
+        const nextIndex = parseInt($(next).index('#value-list tr'));
+        const nowlength = $('#value-list tr').length - 1;
+        if (nextIndex < nowlength) {
+            $(this).parents("tr").insertAfter(next);
+        }
+        makeIconColor();
+    });
 });

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

@@ -258,7 +258,7 @@ $(document).ready(() => {
                 $('#bj-code').removeClass('is-invalid');
                 $('#mj-add').modal('hide');
                 $(this).attr('disabled', false);
-                window.location.href = '/tender/'+ $('#tenderId').val() +'/change/' + rst.cid + '/info';
+                window.location.href = '/tender/'+ $('#tenderId').val() +'/change/' + rst.cid + '/information';
             }, function () {
                 $('#mj-code').addClass('is-invalid');
                 $('#mj-Hint').show();

+ 200 - 0
app/public/js/change_audit.js

@@ -0,0 +1,200 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date 2019/2/27
+ * @version
+ */
+
+$(document).ready(function () {
+    let timer = null;
+    let oldSearchVal = null;
+    // 获取审核相关url
+    function getUrlPre () {
+        const path = window.location.pathname.split('/');
+        return _.take(path, 6).join('/');
+    }
+
+    $('#gr-search').bind('input propertychange', function(e) {
+        oldSearchVal = e.target.value;
+        timer && clearTimeout(timer);
+        timer = setTimeout(() => {
+            const newVal = $('#gr-search').val();
+            let html = '';
+            if (newVal && newVal === oldSearchVal) {
+                accountList.filter(item => item && (item.name.indexOf(newVal) !== -1 || (item.mobile && item.mobile.indexOf(newVal) !== -1))).forEach(item => {
+                    html += `<dd class="border-bottom p-2 mb-0 " data-id="${item.id}" >
+                        <p class="mb-0 d-flex"><span class="text-primary">${item.name}</span><span
+                                class="ml-auto">${item.mobile || ''}</span></p>
+                        <span class="text-muted">${item.role || ''}</span>
+                    </dd>`;
+                });
+                $('.book-list').empty();
+                $('.book-list').append(html);
+            } else {
+                if (!$('.acc-btn').length) {
+                    accountGroup.forEach((group, idx) => {
+                        if (!group) return;
+                        html += `<dt><a href="javascript: void(0);" class="acc-btn" data-groupid="${idx}" data-type="hide"><i class="fa fa-plus-square"></i>
+                        </a> ${group.groupName}</dt>
+                        <div class="dd-content" data-toggleid="${idx}">`;
+                        group.groupList.forEach(item => {
+                            // if (item.id !== changesUid) {
+                            html += `<dd class="border-bottom p-2 mb-0 " data-id="${item.id}" >
+                                    <p class="mb-0 d-flex"><span class="text-primary">${item.name}</span><span
+                                            class="ml-auto">${item.mobile || ''}</span></p>
+                                    <span class="text-muted">${item.role || ''}</span>
+                                </dd>`;
+                            // }
+                        });
+                        html += '</div>';
+                    });
+                    $('.book-list').empty();
+                    $('.book-list').append(html);
+                }
+            }
+        }, 400);
+    });
+
+    // 添加审批流程按钮逻辑
+    $('.book-list').on('click', 'dt', function () {
+        const idx = $(this).find('.acc-btn').attr('data-groupid');
+        const type = $(this).find('.acc-btn').attr('data-type');
+        if (type === 'hide') {
+            $(this).parent().find(`div[data-toggleid="${idx}"]`).show(() => {
+                $(this).children().find('i').removeClass('fa-plus-square').addClass('fa-minus-square-o')
+                $(this).find('.acc-btn').attr('data-type', 'show')
+
+            })
+        } else {
+            $(this).parent().find(`div[data-toggleid="${idx}"]`).hide(() => {
+                $(this).children().find('i').removeClass('fa-minus-square-o').addClass('fa-plus-square')
+                $(this).find('.acc-btn').attr('data-type', 'hide')
+            })
+        }
+        return false
+    });
+
+    $('#hideSp').click(function () {
+        $('#sub-sp2').modal('hide');
+    });
+
+    // 添加到审批流程中
+    $('dl').on('click', 'dd', function () {
+        const id = parseInt($(this).data('id'));
+        if (id) {
+            const auditListIdData = [];
+            $('#auditList li').each(function () {
+                const aid = $(this).data('auditid');
+                auditListIdData.push(aid);
+            });
+            if (!in_array(auditListIdData, id)) {
+                postData(getUrlPre() + '/audit/add', { auditorId: id }, (datas) => {
+                    if (shenpi_status === shenpiConst.sp_status.gdzs) {
+                        auditListIdData.splice(-1,0,id);
+                    } else {
+                        auditListIdData.push(id);
+                    }
+                    const html = [];
+                    const auditorshtml = [];
+                    auditListIdData.unshift(changesUid);
+                    for (const [index,ids] of auditListIdData.entries()) {
+                        const accountInfo = _.find(accountList, { 'id': ids });
+                        if (index !== 0) {
+                            const user = accountInfo.id + '/%/' + accountInfo.name + '/%/' + accountInfo.role + '/%/' + accountInfo.company;
+                            html.push('<li class="list-group-item" data-auditmsg="' + user + '" data-auditid="'+ ids +'">');
+                            if (shenpi_status === shenpiConst.sp_status.sqspr || (shenpi_status === shenpiConst.sp_status.gdzs && index+1 !== auditListIdData.length)) {
+                                html.push('<a href="javascript:void(0);" class="text-danger pull-right remove_audit_btn">移除</a>');
+                            }
+                            html.push('<span>');
+                            html.push(index + ' ');
+                            html.push('</span> ');
+                            html.push(accountInfo.name + ' ');
+                            html.push('<small class="text-muted">');
+                            html.push(accountInfo.role);
+                            html.push('</small>');
+                            html.push('<p class="m-0 ml-2"><small class="text-muted">' + accountInfo.company + '</small></p>');
+                            html.push('</li>');
+                        }
+                        // 添加新审批人流程修改
+                        auditorshtml.push('<li class="list-group-item" ' + (index !== 0 ? 'data-auditid="' + accountInfo.id + '"' : '') + '>');
+                        auditorshtml.push('<i class="fa ' + (index+1 === auditListIdData.length ? 'fa-stop-circle' : 'fa-chevron-circle-down') + '"></i> ');
+                        auditorshtml.push(accountInfo.name + ' <small class="text-muted">' + accountInfo.role + '</small>');
+                        if (index === 0) {
+                            auditorshtml.push('<span class="pull-right">原报</span>');
+                        } else if (index+1 === auditListIdData.length) {
+                            auditorshtml.push('<span class="pull-right">终审</span>');
+                        } else {
+                            auditorshtml.push('<span class="pull-right">'+ transFormToChinese(index) +'审</span>');
+                        }
+                        auditorshtml.push('</li>');
+                    }
+                    $('#auditList').html(html.join(''));
+                    $('#shenpi-audit-list').html(auditorshtml.join(''));
+                });
+            } else {
+                toastr.error('审批流程中已存在该用户!');
+            }
+        }
+    });
+
+    // 移除审批流程的审批人
+    $('body').on('click', '.remove_audit_btn', function () {
+        const uid = $(this).parents('li').attr('data-auditid');
+        const li = $(this).parent();
+        const data = {
+            auditorId: uid,
+        };
+        postData(getUrlPre() + '/audit/delete', data, (result) => {
+            li.remove();
+            let index = 1;
+            $('#auditList li').each(function () {
+                $(this).children('span').text(index);
+                index++;
+            });
+            if (index === 1) {
+                $('#account_list').val(0);
+            }
+
+            // 重新上报时。移除审批流程
+            // 令最后一个图标转换
+            $('#shenpi-audit-list li[data-auditid="' + uid + '"]').remove();
+            if ($('#shenpi-audit-list li').length !== 0 && !$('#shenpi-audit-list li i').hasClass('fa-stop-circle')) {
+                $('#shenpi-audit-list li').eq($('#shenpi-audit-list li').length-1).children('i')
+                    .removeClass('fa-chevron-circle-down').addClass('fa-stop-circle');
+            }
+            for (let i = 0; i < $('#shenpi-audit-list li').length; i++) {
+                $('#shenpi-audit-list li').eq(i).find('.pull-right').text(i === 0 ? '原报' : (i+1 === $('#shenpi-audit-list li').length ? '终' : transFormToChinese(i)) + '审');
+            }
+            $('#shenpi-audit-list li i').eq(0).removeClass('fa-chevron-circle-down').addClass('fa-play-circle');
+        });
+    });
+
+    $('a[f-target]').click(function () {
+        $($(this).attr('f-target')).modal('show');
+    });
+
+    // 多层modal关闭后的滚动bug修复
+    $('#sp-list').on('hidden.bs.modal', function (e) {
+        $(document.body).addClass('modal-open');
+    });
+});
+// texterea换行
+function auditCheck(i) {
+    const inlineRadio1 = $('#inlineRadio1:checked').val()
+    const inlineRadio2 = $('#inlineRadio2:checked').val()
+    const opinion = $('textarea[name="opinion"]').eq(i).val().replace(/\r\n/g, '<br/>').replace(/\n/g, '<br/>').replace(/\s/g, ' ');
+    $('textarea[name="opinion"]').eq(i).val(opinion);
+    if (i === 1) {
+        if (!inlineRadio1 && !inlineRadio2) {
+            if (!$('#warning-text').length) {
+                $('#reject-process').prepend('<p id="warning-text" style="color: red; margin: 0;">请选择退回流程</p>');
+            }
+            return false;
+        }
+        if ($('#warning-text').length) $('#warning-text').remove()
+    }
+    return true;
+}

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

@@ -0,0 +1,273 @@
+'use strict';
+
+/**
+ * 变更令详细页js
+ *
+ * @author EllisRan.
+ * @date 2018/11/22
+ * @version
+ */
+const is_numeric = (value) => {
+    if (typeof(value) === 'object') {
+        return false;
+    } else {
+        return !Number.isNaN(Number(value)) && value.toString().trim() !== '';
+    }
+};
+$(document).ready(() => {
+    //初始化所有附件列表
+    getAllList();
+    const cca = getLocalCache('change-checkbox-account-' + accountId);
+    if (cca !== null && cca !== undefined) {
+        $('#customCheck1').prop('checked', cca !== 'false');
+    }
+    changeSpreadSheet.setColumnVisible(3,$('#customCheck1').is(':checked'), GC.Spread.Sheets.SheetArea.viewport);
+    // 变更详情展示和隐藏
+    $('.change-detail-checkbox').on('click', function (e) {
+        if($(e.target).is('label')){
+            return;
+        }
+        // // 设置用户项目本地记录展示和隐藏情况
+        setLocalCache('change-checkbox-account-'+ accountId, $(this).is(':checked'));
+        changeSpreadSheet.setColumnVisible(3,$(this).is(':checked'), GC.Spread.Sheets.SheetArea.viewport);
+    });
+
+    //tab change
+    $('a[data-toggle="tab"]').on('shown.bs.tab', function () {
+        const tab = $(this).data('tab');
+        if (tab === 'bgfujian') {
+            $('#fujian_btn').show();
+        } else {
+            $('#fujian_btn').hide();
+        }
+    });
+
+    $('#add-bj').on('click', 'input[type="checkbox"]', function () {
+        const isCheck = $(this).prop('checked');
+        if (isCheck) {
+            $('#add-bj input[type="checkbox"]').each(function () {
+                $(this).prop('checked', false)
+            });
+            $(this).prop('checked', true)
+        }
+    });
+    $('#bg-copy').click(function() {
+        const cid = $('#add-bj input:checked').data('id');
+        postData(window.location.pathname + '/copy', cid, function () {
+            window.location.reload();
+        })
+    });
+    // 上传附件
+    $('#upload-file-btn').click(function () {
+        const files = $('#upload-file')[0].files;
+        const formData = new FormData();
+        formData.append('cid', $('#changeId').val());
+        formData.append('tid', $('#tenderId').val());
+        for (const file of files) {
+            if (file === undefined) {
+                toastr.error('未选择上传文件!');
+                return false;
+            }
+            const filesize = file.size;
+            if (filesize > 30 * 1024 * 1024) {
+                toastr.error('文件大小过大!');
+                return false;
+            }
+            const fileext = '.' + file.name.toLowerCase().split('.').splice(-1)[0];
+            if (whiteList.indexOf(fileext) === -1) {
+                toastr.error('只能上传指定格式的附件!');
+                return false;
+            }
+            formData.append('size', filesize);
+            formData.append('file[]', file);
+        }
+        if (auditList.findIndex(item => item.uid === parseInt(accountId)) === -1) {
+            return toastr.error('暂无权限上传!');
+        }
+        postDataWithFile(window.location.pathname + '/file/upload', formData, function (data) {
+            attData = data.concat(attData);
+            // 重新生成List
+            getAllList();
+            $('#addfujian').modal('hide');
+            // let html = '';
+            // let index = $('#attList tr').length + 1;
+            // for (const fileInfo of data) {
+            //     html += '<tr> ' +
+            //         `<td width="20"><input type="checkbox" class="check-file" file-id=${fileInfo.id}></td>` +
+            //         '<td>' + index + '</td> ' +
+            //         `<td><a href="javascript: void(0);" class="file-atn" f-id="${fileInfo.id}">${fileInfo.filename}${fileInfo.fileext}</a></td>`+
+            //         '<td>' + fileInfo.in_time + '<br>' + fileInfo.filesize + '</td> ' +
+            //         `<td><a href="/change/download/file/${fileInfo.id}" class="mr-2" title="下载"><span class="fa fa-download text-primary"></span></a>`+
+            //         ( auditStatus === 4 ?
+            //             fileInfo.extra_upload ? `<a class="mr-2 delete-file" data-attid="${fileInfo.id}"  title="删除附件"><span class="fa fa-trash text-danger"></span></a>` : ''
+            //             : ` <a href="javascript:void(0);" class="mr-2 delete-file" data-attid="${fileInfo.id}"  title="删除附件"><span class="fa fa-trash text-danger"></span></a>`)+
+            //         `</td>`+
+            //         // '<td> <a class="btn btn-light btn-sm delete-file" data-attid="' + fileInfo.id + '"  title="删除附件"><span class="fa fa-trash text-danger"></span></a> </td> ' +
+            //         '</tr>';
+            //     ++index;
+            // }
+            // $('#attList').append(html);
+        }, function () {
+        });
+        $('#upload-file').val('');
+
+    });
+
+    // 删除附件
+    $('body').on('click', '.delete-file', function () {
+        let attid = $(this).data('attid');
+        let self = $(this);
+        const data = {id: attid};
+        postData(window.location.pathname + '/file/delete', data, function (result) {
+            // self.parents('tr').remove();
+            // // 重新排序
+            // let newsort = 1;
+            // $('#attList tr').each(function(){
+            //     $(this).children('td').eq(1).text(newsort);
+            //     newsort++;
+            // });
+            // 删除到attData中
+            const att_index = attData.findIndex(function (item) {
+                return item.id === parseInt(attid);
+            });
+            attData.splice(att_index, 1);
+            // 重新生成List
+
+            if ($('#attList tr').length === 1) {
+                getAllList(parseInt($('#currentPage').text()) - 1);
+
+            } else {
+                getAllList(parseInt($('#currentPage').text()));
+            }
+        });
+    });
+    // /change/download/file/
+    $('#attList').on('click', '.file-atn', function() {
+        const id = $(this).attr('f-id');
+        postData(`/change/download/file/${id}`, {}, (data) => {
+            const { filepath } = data;
+            $('#file-upload').attr('href', filepath);
+            $('#file-upload')[0].click();
+        })
+    });
+
+    $('#attList').on('click', '.check-file', function() {
+        const checkedList = $('#attList').find('input:checked');
+        const childs = $('#attList').children().length;
+        const checkBox = $('#check-all-file');
+        if (checkedList.length === childs) {
+            checkBox.prop("checked", true);
+        } else {
+            checkBox.prop("checked", false);
+        }
+    });
+    $('#check-all-file').click(function() {
+        const isCheck = $(this).is(':checked');
+        $('#attList').children().each(function() {
+            $(this).find('input:checkbox').prop("checked", isCheck);
+        })
+    });
+
+    $('#bach-download').click(function() {
+        const fileIds = [];
+        $( '#attList .check-file:checked').each(function() {
+            const fileId = $(this).attr('file-id');
+            fileId && fileIds.push(fileId);
+        });
+
+        if (fileIds.length) {
+            const tid = $('#tenderId').val();
+            const cid = $('#changeId').val();
+            $('#downloadZip').attr('href', `/tender/${tid}/change/${cid}/download/compresse-file?fileIds=${JSON.stringify(fileIds)}`);
+            $('#downloadZip')[0].click();
+        }
+    });
+
+    $.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();
+            changeSpread.refresh();
+        }
+    });
+
+    // 切换页数
+    $('.page-select').on('click', function () {
+        const totalPageNum = parseInt($('#totalPage').text());
+        const lastPageNum = parseInt($('#currentPage').text());
+        const status = $(this).attr('content');
+        if (status === 'pre' && lastPageNum > 1) {
+            getAllList(lastPageNum-1);
+            $('#showAttachment').hide();
+            $('#syfujian .check-all-file').prop('checked', false)
+        } else if (status === 'next' && lastPageNum < totalPageNum) {
+            getAllList(lastPageNum+1);
+            $('#showAttachment').hide();
+            $('#syfujian .check-all-file').prop('checked', false)
+        }
+    });
+});
+function findDecimal(unit) {
+    let value = precision.other.value;
+    const changeUnits = precision;
+    for (const d in changeUnits) {
+        if (changeUnits[d].unit !== undefined && changeUnits[d].unit === unit) {
+            value = changeUnits[d].value;
+            break;
+        }
+    }
+    return value;
+}
+// 生成附件列表
+function getAllList(currPageNum = 1) {
+    // 每页最多几个附件
+    const pageCount = 15;
+    // 附件总数
+    const total = attData.length;
+    // 总页数
+    const pageNum = Math.ceil(total/pageCount);
+    $('#totalPage').text(pageNum);
+    $('#currentPage').text(total === 0 ? 0 : currPageNum);
+    // 当前页附件内容
+    const currPageAttData = attData.slice((currPageNum-1)*pageCount, currPageNum*pageCount);
+    let html = '';
+    // '/tender/' + tender.id + '/measure/stage/' + stage.order + '/download/file/' + att.id
+    for(const [index,att] of currPageAttData.entries()) {
+        console.log(att.uid, accountId, auditStatus, Boolean(att.extra_upload));
+        html += `<tr>
+        <td width="25"><input type="checkbox" class="check-file" file-id=${att.id}></td>
+        <td>${index+1}</td>
+        <td><a href="javascript:void(0)" class="pl-0 col-11 att-file-name" file-id=${att.id}>${att.filename}${att.fileext}</a></td>
+        <td>${moment(att.in_time * 1000).format('YYYY-MM-DD')}<br>${bytesToSize(att.filesize)}</td>
+        <td>
+            <a href="/change/download/file/${att.id}" class="mr-2" title="下载"><span class="fa fa-download text-primary"></span></a>`
+        html += (att.uid === accountId && (auditStatus === 4 ? Boolean(att.extra_upload) : true)) ?
+            `<a href="javascript:void(0)" class="mr-2 delete-file" data-attid="${att.id}" title="删除附件"><span class="fa fa-trash text-danger"></span></a>` : '';
+        html += `</td>`;
+    }
+    $('#attList').html(html);
+    $('#attList').on('click', 'tr', function() {
+        $('#attList tr').removeClass('bg-light');
+        $(this).addClass('bg-light');
+    })
+}
+
+function bytesToSize(bytes) {
+    if (parseInt(bytes) === 0) return '0 B';
+    const k = 1024;
+    const sizes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
+    const i = Math.floor(Math.log(bytes) / Math.log(k));
+    // return (bytes / Math.pow(k, i)) + ' ' + sizes[i];
+    return (bytes / Math.pow(k, i)).toPrecision(3) + ' ' + sizes[i];
+}
+

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

@@ -0,0 +1,390 @@
+'use strict';
+
+/**
+ * 变更令详细页js
+ *
+ * @author EllisRan.
+ * @date 2018/11/22
+ * @version
+ */
+$(document).ready(() => {
+    const changeSpreadSetting = {
+        cols: [
+            {title: '清单编号', colSpan: '1', rowSpan: '2', field: 'code', hAlign: 0, width: 80, formatter: '@', readOnly: true},
+            {title: '名称', colSpan: '1', rowSpan: '2', field: 'name', hAlign: 0, width: 120, formatter: '@', readOnly: true},
+            {title: '变更部位', colSpan: '1', rowSpan: '2', field: 'bwmx', hAlign: 0, width: 120, formatter: '@', readOnly: true},
+            {title: '变更详情', colSpan: '1', rowSpan: '2', field: 'detail', hAlign: 0, width: 120, formatter: '@', readOnly: true},
+            {title: '单位', colSpan: '1', rowSpan: '2', field: 'unit', hAlign: 1, width: 60, formatter: '@', readOnly: true},
+            {title: '单价', colSpan: '1', rowSpan: '2', field: 'unit_price', hAlign: 2, width: 60, type: 'Number', getValue: 'getValue.unit_price', readOnly: true},
+            {title: '原设计|数量', colSpan: '2|1', rowSpan: '1|1', field: 'oamount', hAlign: 2, width: 60, type: 'Number', getValue: 'getValue.oamount', readOnly: true},
+            {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'oa_tp', hAlign: 2, width: 80, formatter: '@', type: 'Number', getValue: 'getValue.oa_tp', readOnly: true},
+            {title: '申请变更增(+)减(-)|数量', colSpan: '2|1', rowSpan: '1|1', field: 'camount', hAlign: 2, width: 60, type: 'Number', getValue: 'getValue.camount', readOnly: true},
+            {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'ca_tp', hAlign: 2, width: 80, formatter: '@', type: 'Number', getValue: 'getValue.ca_tp', readOnly: true},
+        ],
+        emptyRows: 0,
+        headRows: 2,
+        headRowHeight: [25, 25],
+        defaultRowHeight: 21,
+        headerFont: '12px 微软雅黑',
+        font: '12px 微软雅黑',
+        // readOnly: true,
+    };
+    for (const aid of aidList) {
+        const userinfo = _.find(auditList2, { 'uid': aid });
+        const newColcount = {
+            title: userinfo.name + ' 审批|数量',
+            colSpan: '2|1', rowSpan: '1|1',
+            field: 'audit_amount_' + aid,
+            hAlign: 2, width: 60, type: 'Number',
+            readOnly: aid !== parseInt(accountId)
+        };
+        const newColTp = {
+            title: '|金额',
+            colSpan: '|1', rowSpan: '|1',
+            field: 'sa_tp',
+            hAlign: 2, width: 80, formatter: '@', type: 'Number',
+            readOnly: true
+        };
+        changeSpreadSetting.cols.push(newColcount);
+        changeSpreadSetting.cols.push(newColTp);
+    }
+
+    const changeCol = {
+        getValue: {
+            unit_price: function(data) {
+                return ZhCalc.round(data.unit_price, unitPriceUnit);
+            },
+            oa_tp: function (data) {
+                return ZhCalc.round(ZhCalc.mul(data.unit_price, data.oamount), totalPriceUnit);
+            },
+            ca_tp: function (data) {
+                return ZhCalc.round(ZhCalc.mul(data.unit_price, data.camount), totalPriceUnit);
+            },
+            oamount: function (data) {
+                return ZhCalc.round(data.oamount, findDecimal(data.unit));
+            },
+            camount: function (data) {
+                return ZhCalc.round(data.camount, findDecimal(data.unit));
+            },
+        },
+    };
+
+    const changeSpreadObj = {
+        makeSjsFooter: function () {
+            // 增加汇总行并设为锁定禁止编辑状态
+            changeSpreadSheet.addRows(changeSpreadSheet.getRowCount(), 1);
+            changeSpreadSheet.setValue(changeSpreadSheet.getRowCount() - 1, 0, '合计');
+            changeSpreadSheet.setStyle(changeSpreadSheet.getRowCount() - 1, -1, style1);
+            changeSpreadObj.countSum();
+        },
+        setAuditValue: function () {
+            const rowCount = changeSpreadSheet.getRowCount();
+            // 用户的数据合计
+            for (const j in aidList) {
+                for(let i = 0; i <= rowCount - 1; i++){
+                    const data = {
+                        unit_price: changeSpreadSheet.getValue(i, 5),
+                        amount: parseFloat(changeSpreadSheet.getValue(i, 10 + parseInt(j)*2)),
+                    };
+                    const sum = ZhCalc.round(ZhCalc.mul(data.unit_price, data.amount), totalPriceUnit);
+                    changeSpreadSheet.setValue(i, 11 + j*2, sum !== 0 ? sum : null);
+                }
+            }
+        },
+        setRowValueAndSum: function (data, row, col) {
+            for (const j in aidList) {
+                const sum = ZhCalc.round(ZhCalc.mul(data.unit_price, parseFloat(changeSpreadSheet.getValue(row, 10 + parseInt(j)*2))), totalPriceUnit);
+                changeSpreadSheet.setValue(row, 11 + j*2, sum !== 0 ? sum : null);
+            }
+            // const sum = ZhCalc.round(ZhCalc.mul(data.unit_price, data.spamount), totalPriceUnit);
+            // changeSpreadSheet.setValue(row, col+1, sum !== 0 ? sum : null);
+            const rowCount = changeSpreadSheet.getRowCount();
+            // 用户的数据合计
+            let audit_sum = 0;
+            for(let i = 0; i < rowCount - 1; i++){
+                audit_sum = ZhCalc.add(audit_sum, changeSpreadSheet.getValue(i, col+1));
+            }
+            changeSpreadSheet.setValue(changeSpreadSheet.getRowCount() - 1, col+1, audit_sum !== 0 ? audit_sum : null);
+        },
+        countSum: function() {
+            const rowCount = changeSpreadSheet.getRowCount();
+            let oSum = 0,
+                cSum = 0;
+            for(let i = 0; i < rowCount - 1; i++){
+                oSum = ZhCalc.add(oSum, changeSpreadSheet.getValue(i, 7));
+                cSum = ZhCalc.add(cSum, changeSpreadSheet.getValue(i, 9));
+            }
+            changeSpreadSheet.setValue(changeSpreadSheet.getRowCount() - 1, 7, oSum !== 0 ? oSum : null);
+            changeSpreadSheet.setValue(changeSpreadSheet.getRowCount() - 1, 9, cSum !== 0 ? cSum : null);
+            // 用户的数据合计
+            for (const j in aidList) {
+                let audit_sum = 0;
+                for(let i = 0; i < rowCount - 1; i++){
+                    audit_sum = ZhCalc.add(audit_sum, changeSpreadSheet.getValue(i, 11 + j*2));
+                }
+                changeSpreadSheet.setValue(changeSpreadSheet.getRowCount() - 1, 11 + j*2, audit_sum !== 0 ? audit_sum : null);
+            }
+        },
+        deletePress: function (sheet) {
+            return;
+        },
+        editEnded: function (e, info) {
+            if (info.sheet.zh_setting) {
+                const select = SpreadJsObj.getSelectObject(info.sheet);
+                const col = info.sheet.zh_setting.cols[info.col];
+                // 未改变值则不提交
+                let validText = is_numeric(info.editingText) ? parseFloat(info.editingText) : (info.editingText ? trimInvalidChar(info.editingText) : '');
+                const orgValue = select[col.field];
+                if (orgValue == validText || ((!orgValue || orgValue === '') && (validText === ''))) {
+                    SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                    changeSpreadObj.setRowValueAndSum(select, info.row, info.col);
+                    return;
+                }
+                // 判断部分值是否输入的是数字判断和数据计算
+                if (col.type === 'Number') {
+                    if (isNaN(validText)) {
+                        toastr.error('不能输入其它非数字类型字符');
+                        SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                        changeSpreadObj.setRowValueAndSum(select, info.row, info.col);
+                        return;
+                    }
+                    validText = ZhCalc.round(validText, findDecimal(select.unit)) || 0;
+                }
+                select[col.field] = validText;
+                select.spamount = ZhCalc.round(validText, findDecimal(select.unit)) || 0;
+                console.log(select);
+
+                const data = {
+                    id: select.id,
+                    spamount: select.spamount,
+                };
+                console.log(data);
+
+                // 更新至服务器
+                postData(window.location.pathname + '/save', { type:'update', updateData: data }, function (result) {
+                    changeList.splice(info.row, 1, select);
+                    SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                    changeSpreadObj.setRowValueAndSum(select, info.row, info.col);
+                }, function () {
+                    select[col.field] = orgValue;
+                    select.spamount = orgValue;
+                    SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                    changeSpreadObj.setRowValueAndSum(select, info.row, info.col);
+                });
+            }
+        },
+        clipboardPasted(e, info) {
+            // const hint = {
+            //     cellError: {type: 'error', msg: '粘贴内容超出了表格范围'},
+            //     numberExpr: {type: 'error', msg: '不能粘贴其它非数字类型字符'},
+            // };
+            const range = info.cellRange;
+            const sortData = info.sheet.zh_data || [];
+            // console.log(info, range);
+            // if (info.cellRange.row + info.cellRange.rowCount > sortData.length) {
+            //     toastMessageUniq(hint.cellError);
+            //     // SpreadJsObj.loadSheetData(materialSpread.getActiveSheet(), SpreadJsObj.DataType.Data, materialBillsData);
+            //     SpreadJsObj.reLoadSheetHeader(changeSpreadSheet);
+            //     SpreadJsObj.reLoadSheetData(changeSpreadSheet);
+            //     changeSpreadObj.setAuditValue();
+            //     changeSpreadObj.makeSjsFooter();
+            //     return;
+            // }
+            // if (sortData.length > 0 && range.col + range.colCount > 10) {
+            //     toastMessageUniq(hint.cellError);
+            //     SpreadJsObj.reLoadSheetHeader(changeSpreadSheet);
+            //     SpreadJsObj.reLoadSheetData(changeSpreadSheet);
+            //     changeSpreadObj.setAuditValue();
+            //     changeSpreadObj.makeSjsFooter();
+            //     return;
+            // }
+            const data = [];
+            // const rowData = [];
+            for (let iRow = 0; iRow < range.rowCount; iRow++) {
+                let bPaste = true;
+                const curRow = range.row + iRow;
+                // const materialData = JSON.parse(JSON.stringify(sortData[curRow]));
+                const cLData = { id: sortData[curRow].id };
+                const hintRow = range.rowCount > 1 ? curRow : '';
+                let sameCol = 0;
+                for (let iCol = 0; iCol < range.colCount; iCol++) {
+                    const curCol = range.col + iCol;
+                    const colSetting = info.sheet.zh_setting.cols[curCol];
+                    if (!colSetting) continue;
+
+                    let validText = info.sheet.getText(curRow, curCol);
+                    validText = is_numeric(validText) ? parseFloat(validText) : (validText ? trimInvalidChar(validText) : null);
+                    const orgValue = sortData[curRow][colSetting.field];
+                    if (orgValue == validText || ((!orgValue || orgValue === '') && (validText === ''))) {
+                        sameCol++;
+                        if (range.colCount === sameCol)  {
+                            bPaste = false;
+                        }
+                        continue;
+                    }
+                    if (colSetting.type === 'Number') {
+                        if (isNaN(validText)) {
+                            toastMessageUniq(getPasteHint(hint.numberExpr, hintRow));
+                            bPaste = false;
+                            continue;
+                        }
+                        validText = ZhCalc.round(validText, findDecimal(sortData[curRow].unit)) || 0;
+                    }
+                    // cLData[colSetting.field] = validText;
+                    sortData[curRow][colSetting.field] = validText;
+                    cLData.spamount = validText;
+                }
+                if (bPaste) {
+                    data.push(cLData);
+                    // rowData.push(curRow);
+                } else {
+                    SpreadJsObj.reLoadRowData(info.sheet, curRow);
+                }
+            }
+            if (data.length === 0) {
+                SpreadJsObj.reLoadRowData(info.sheet, info.cellRange.row, info.cellRange.rowCount);
+                return;
+            }
+            console.log(data);
+            // 更新至服务器
+            postData(window.location.pathname + '/save', { type:'paste_amount_rows', updateData: data }, function (result) {
+                changeList = result;
+                SpreadJsObj.loadSheetData(changeSpreadSheet, SpreadJsObj.DataType.Data, changeList);
+                changeSpreadObj.setAuditValue();
+                changeSpreadObj.makeSjsFooter();
+            }, function () {
+                SpreadJsObj.reLoadRowData(info.sheet, info.cellRange.row, info.cellRange.rowCount);
+                changeSpreadObj.setAuditValue();
+                return;
+            });
+        },
+    };
+
+    SpreadJsObj.initSpreadSettingEvents(changeSpreadSetting, changeCol);
+    SpreadJsObj.initSheet(changeSpreadSheet, changeSpreadSetting);
+    SpreadJsObj.loadSheetData(changeSpreadSheet, SpreadJsObj.DataType.Data, changeList);
+    changeSpreadObj.setAuditValue();
+    changeSpreadObj.makeSjsFooter();
+    const userIndex = aidList.indexOf(parseInt(accountId));
+
+    changeSpread.bind(spreadNS.Events.EditEnded, changeSpreadObj.editEnded);
+    changeSpread.bind(spreadNS.Events.ClipboardPasted, changeSpreadObj.clipboardPasted);
+    SpreadJsObj.addDeleteBind(changeSpread, changeSpreadObj.deletePress);
+
+
+    // 审批提交与判断
+    $('.approval-btn').on('click', function () {
+        // 判断审批状态
+        let returnflag = true;
+        if ($(this).hasClass('btn-success')) {
+            const sdesc = $('#success-approval').find('textarea').val();
+            if (sdesc === '') {
+                toastr.error('审批意见不能为空!');
+                returnflag = false;
+            }
+            if ($('input[name="p_code"]').val() === '') {
+                toastr.error('变更令号(批复编号)不能为空!');
+                returnflag = false;
+            } else if ($('input[name="p_code"]').val() !== undefined) {
+                $('input[name="p_code"]').val($.trim($('input[name="p_code"]').val()));
+                const postData = {
+                    p_code: $('input[name="p_code"]').val(),
+                };
+                postDataWithAsync('/tender/' + $('#tenderId').val() + '/change/' + $('#changeId').val() + '/check/codeRepeat',postData, function (result) {
+                }, function (data) {
+                    returnflag = false;
+                });
+            }
+            // 判断并提交变更清单表格数据到表单中
+            const clist = [];
+            for(const [i,cl] of changeList.entries()) {
+                if (cl['audit_amount_' + accountId] === null || cl['audit_amount_' + accountId] === '') {
+                    toastr.error('清单第' + (i+1) + '行审批变更数量不能为空');
+                    returnflag = false;
+                } else {
+                    clist.push(cl.id + '_' + cl['audit_amount_' + accountId]);
+                }
+            }
+            $('#change-list-approval').val(clist.join(','));
+
+            if(returnflag) {
+                $('input[name="w_code"]').val($.trim($('#w_code').val()));
+
+                $('#success-approval').find('textarea').val(sdesc.replace(/\r\n/g, '<br/>').replace(/\n/g, '<br/>').replace(/\s/g, ' '));
+                if ($('#warning-text').length) $('#warning-text').remove()
+                $('#success-approval').submit();
+            }
+        } else {
+            const sdesc = $('#fail-approval').find('textarea').val();
+            if (sdesc === '') {
+                toastr.error('审批意见不能为空!');
+                returnflag = false;
+            }
+            const type = $('#fail-approval').find('input[name="status"]:checked').val();
+            if (type === undefined) {
+                // toastr.error('请选择退回类型!');
+                if (!$('#warning-text').length) {
+                    $('#change-back-content').prepend('<p id="warning-text" style="color: red; margin: 0;">请选择退回流程</p>');
+                }
+                returnflag = false;
+            }
+            if(returnflag) {
+                $('#fail-approval').find('textarea').val(sdesc.replace(/\r\n/g, '<br/>').replace(/\n/g, '<br/>').replace(/\s/g, ' '));
+                $('input[name="w_code"]').val($.trim($('#w_code').val()));
+                $('#fail-approval').submit();
+            }
+        }
+    })
+});
+const postDataWithAsync = function (url, data, successCallback, errorCallBack, showWaiting = true) {
+    if (showWaiting) showWaitingView();
+    $.ajax({
+        type:"POST",
+        url: url,
+        data: {'data': JSON.stringify(data)},
+        dataType: 'json',
+        cache: false,
+        async: false,
+        timeout: 60000,
+        beforeSend: function(xhr) {
+            let csrfToken = Cookies.get('csrfToken');
+            xhr.setRequestHeader('x-csrf-token', csrfToken);
+        },
+        success: function(result){
+            if (result.err === 0) {
+                if (successCallback) {
+                    successCallback(result.data);
+                }
+            } else {
+                toastr.error(result.msg);
+                if (errorCallBack) {
+                    errorCallBack(result.msg);
+                }
+            }
+            if (showWaiting) closeWaitingView();
+        },
+        error: function(jqXHR, textStatus, errorThrown){
+            toastr.error('error: ' + textStatus + " " + errorThrown);
+            if (errorCallBack) {
+                errorCallBack();
+            }
+            if (showWaiting) closeWaitingView();
+        }
+    });
+};
+function auditCheck(i) {
+    const inlineRadio1 = $('#change-back:checked').val()
+    const inlineRadio2 = $('#chagne-backnew:checked').val()
+    const opinion = $('textarea[name="sdesc"]').eq(i).val().replace(/\r\n/g, '<br/>').replace(/\n/g, '<br/>').replace(/\s/g, ' ');
+    $('textarea[name="sdesc"]').eq(i).val(opinion);
+    if (i === 1) {
+        if (!inlineRadio1 && !inlineRadio2) {
+            if (!$('#warning-text').length) {
+                $('#reject-process').prepend('<p id="warning-text" style="color: red; margin: 0;">请选择退回流程</p>');
+            }
+            return false;
+        }
+        if ($('#warning-text').length) $('#warning-text').remove()
+    }
+    return true;
+}

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

@@ -0,0 +1,921 @@
+'use strict';
+
+/**
+ * 变更令详细页js
+ *
+ * @author EllisRan.
+ * @date 2018/11/22
+ * @version
+ */
+// 编号排序,多重判断
+function sortByCode(a, b) {
+    let code1 = a.code.split('-');
+    let code2 = b.code.split('-');
+    let code1length = code1.length;
+    let code2length = code2.length;
+    for (let i = 0; i < code1length; i ++) {
+        if (i+1 <= code2length) {
+            if (code1[i] != code2[i]) {
+                if (!/^\d+$/.test(code1[i])) {
+                    return code1[i].charCodeAt() - code2[i].charCodeAt();
+                } else {
+                    return parseInt(code1[i]) - parseInt(code2[i]);
+                }
+            } else if (i+1 == code1length && code1[i] == code2[i]) {
+                if (code1length == code2length) {
+                    return 0;
+                } else {
+                    return code1length - code2length;
+                }
+            }
+        } else {
+            if (i+1 >= code1length) {
+                return 1;
+            } else {
+                return -1;
+            }
+        }
+    }
+}
+$.event.special.valuechange = {
+    teardown: function (namespaces) {
+        $(this).unbind('.valuechange');
+    },
+
+    handler: function (e) {
+        $.event.special.valuechange.triggerChanged($(this));
+    },
+
+    add: function (obj) {
+        $(this).on('keyup.valuechange cut.valuechange paste.valuechange input.valuechange', obj.selector, $.event.special.valuechange.handler)
+    },
+
+    triggerChanged: function (element) {
+        var current = element[0].contentEditable === 'true' ? element.html() : element.val()
+            , previous = typeof element.data('previous') === 'undefined' ? element[0].defaultValue : element.data('previous');
+        if (current !== previous) {
+            element.trigger('valuechange', [element.data('previous')]);
+            element.data('previous', current);
+        }
+    }
+};
+$(document).ready(() => {
+    const changeSpreadSetting = {
+        cols: [
+            {title: '清单编号', colSpan: '1', rowSpan: '2', field: 'code', hAlign: 0, width: 80, formatter: '@', readOnly: 'readOnly.isEdit'},
+            {title: '名称', colSpan: '1', rowSpan: '2', field: 'name', hAlign: 0, width: 120, formatter: '@', readOnly: 'readOnly.isEdit'},
+            {title: '变更部位', colSpan: '1', rowSpan: '2', field: 'bwmx', hAlign: 0, width: 120, formatter: '@', readOnly: 'readOnly.isEdit'},
+            {title: '变更详情', colSpan: '1', rowSpan: '2', field: 'detail', hAlign: 0, width: 120, formatter: '@', readOnly: false},
+            {title: '单位', colSpan: '1', rowSpan: '2', field: 'unit', hAlign: 1, width: 60, formatter: '@', readOnly: 'readOnly.isEdit', cellType: 'unit', comboItems: changeUnits},
+            {title: '单价', colSpan: '1', rowSpan: '2', field: 'unit_price', hAlign: 2, width: 60, type: 'Number', readOnly: 'readOnly.isEdit', getValue: 'getValue.unit_price'},
+            {title: '原设计|数量', colSpan: '2|1', rowSpan: '1|1', field: 'oamount', hAlign: 2, width: 60, type: 'Number', readOnly: 'readOnly.isEdit', getValue: 'getValue.oamount'},
+            {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'oa_tp', hAlign: 2, width: 80, formatter: '@', type: 'Number', readOnly: true, getValue: 'getValue.oa_tp'},
+            {title: '申请变更增(+)减(-)|数量', colSpan: '2|1', rowSpan: '1|1', field: 'camount', hAlign: 2, width: 60, type: 'Number', readOnly: false, getValue: 'getValue.camount'},
+            {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'ca_tp', hAlign: 2, width: 80, formatter: '@', type: 'Number', readOnly: true, getValue: 'getValue.ca_tp'},
+            {title: '操作', colSpan: '1', rowSpan: '2', field: 'del_list', hAlign: 1, width: 40, readOnly: true, cellType: 'mouseTouch', getValue: 'getValue.del_list'},
+        ],
+        emptyRows: 0,
+        headRows: 2,
+        headRowHeight: [25, 25],
+        defaultRowHeight: 21,
+        headerFont: '12px 微软雅黑',
+        font: '12px 微软雅黑',
+        readOnly: readOnly,
+    };
+
+    const changeCol = {
+        getValue: {
+            unit_price: function(data) {
+                return ZhCalc.round(data.unit_price, unitPriceUnit);
+            },
+            oa_tp: function (data) {
+                return ZhCalc.round(ZhCalc.mul(data.unit_price, data.oamount), totalPriceUnit);
+            },
+            ca_tp: function (data) {
+                return ZhCalc.round(ZhCalc.mul(data.unit_price, data.camount), totalPriceUnit);
+            },
+            oamount: function (data) {
+                return ZhCalc.round(data.oamount, findDecimal(data.unit));
+            },
+            camount: function (data) {
+                return ZhCalc.round(data.camount, findDecimal(data.unit));
+            },
+            del_list: function (data) {
+                return '移除';
+            }
+        },
+        readOnly: {
+            isEdit: function (data) {
+                return !readOnly && data.lid != 0;
+            },
+        },
+    };
+
+    const changeSpreadObj = {
+        makeSjsFooter: function () {
+            // 增加汇总行并设为锁定禁止编辑状态
+            changeSpreadSheet.addRows(changeSpreadSheet.getRowCount(), 1);
+            changeSpreadSheet.setValue(changeSpreadSheet.getRowCount() - 1, 0, '合计');
+            changeSpreadSheet.setStyle(changeSpreadSheet.getRowCount() - 1, -1, style1);
+            changeSpreadObj.countSum();
+        },
+        countSum: function() {
+            const rowCount = changeSpreadSheet.getRowCount();
+            let oSum = 0,
+                cSum = 0;
+            for(var i = 0; i < rowCount - 1; i++){
+                oSum = ZhCalc.add(oSum, changeSpreadSheet.getValue(i, 7));
+                cSum = ZhCalc.add(cSum, changeSpreadSheet.getValue(i, 9));
+            }
+            changeSpreadSheet.setValue(changeSpreadSheet.getRowCount() - 1, 7, oSum !== 0 ? oSum : null);
+            changeSpreadSheet.setValue(changeSpreadSheet.getRowCount() - 1, 9, cSum !== 0 ? cSum : null);
+        },
+        add: function () {
+            postData(window.location.pathname + '/save', {type: 'add'}, function (result) {
+                if (result) {
+                    changeList.push(result);
+                    changeSpreadSheet.addRows(changeList.length - 1, 1);
+                    SpreadJsObj.reLoadRowData(changeSpreadSheet, changeList.length - 1);
+                    changeSpreadSheet.setStyle(changeSpreadSheet.getRowCount() - 1, -1, style1);
+                    changeSpreadSheet.setSelection(changeList.length - 1, 0, 1, 1);
+                }
+            });
+        },
+        del: function () {
+            const select = SpreadJsObj.getSelectObject(changeSpreadSheet);
+            const index = changeList.indexOf(select);
+            if (index > -1) {
+                postData(window.location.pathname + '/save', {type: 'del', id: select.id}, function (result) {
+                    changeList.splice(index, 1);
+                    changeSpreadSheet.deleteRows(index, 1);
+                    const sel = changeSpreadSheet.getSelections();
+                    changeSpreadSheet.setSelection(0, 0, 1, 1);
+                    if (select.lid != 0) {
+                        tableDataRemake(changeListData);
+                    }
+                    changeSpreadObj.countSum();
+                });
+            }
+        },
+        selectionChanged: function (e, info) {
+            const sel = info.sheet.getSelections()[0];
+            const col = info.sheet.zh_setting.cols[sel.col];
+            const data = SpreadJsObj.getSelectObject(info.sheet);
+            if (col.field === 'del_list') {
+                changeSpreadObj.del();
+            }
+        },
+        deletePress: function (sheet) {
+            return;
+        },
+        editEnded: function (e, info) {
+            if (info.sheet.zh_setting) {
+                const select = SpreadJsObj.getSelectObject(info.sheet);
+                const col = info.sheet.zh_setting.cols[info.col];
+                if (col.field === 'del_list') {
+                    return;
+                }
+                // 未改变值则不提交
+                let validText = is_numeric(info.editingText) ? parseFloat(info.editingText) : (info.editingText ? trimInvalidChar(info.editingText) : '');
+                const orgValue = select[col.field];
+                if (orgValue == validText || ((!orgValue || orgValue === '') && (validText === ''))) {
+                    SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                    return;
+                }
+                // 判断部分值是否输入的是数字判断和数据计算
+                if (col.type === 'Number') {
+                    if (isNaN(validText)) {
+                        toastr.error('不能输入其它非数字类型字符');
+                        SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                        return;
+                    }
+                    if (col.field === 'unit_price') {
+                        validText = ZhCalc.round(validText, unitPriceUnit);
+                    } else {
+                        validText = ZhCalc.round(validText, findDecimal(select.unit)) || 0;
+                    }
+                }
+                if (col.field === 'unit') {
+                    select.camount = ZhCalc.round(select.camount, findDecimal(validText)) || 0;
+                    select.oamount = ZhCalc.round(select.oamount, findDecimal(validText)) || 0;
+                }
+                select[col.field] = validText;
+                if(col.field === 'camount') {
+                    select.spamount = ZhCalc.round(select.camount, findDecimal(select.unit)) || 0;
+                }
+                console.log(select);
+
+                // 更新至服务器
+                postData(window.location.pathname + '/save', { type:'update', updateData: select }, function (result) {
+                    changeList.splice(info.row, 1, select);
+                    SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                    changeSpreadObj.countSum();
+                }, function () {
+                    select[col.field] = orgValue;
+                    if(col.field === 'camount') {
+                        select.spamount = orgValue;
+                    }
+                    SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                });
+            }
+        },
+        clipboardPasted(e, info) {
+            const hint = {
+                cellError: {type: 'error', msg: '粘贴内容超出了表格范围'},
+                numberExpr: {type: 'error', msg: '不能粘贴其它非数字类型字符'},
+            };
+            const range = info.cellRange;
+            const sortData = info.sheet.zh_data || [];
+            if (info.cellRange.row + info.cellRange.rowCount > sortData.length) {
+                toastMessageUniq(hint.cellError);
+                // SpreadJsObj.loadSheetData(materialSpread.getActiveSheet(), SpreadJsObj.DataType.Data, materialBillsData);
+                SpreadJsObj.reLoadSheetHeader(changeSpreadSheet);
+                SpreadJsObj.reLoadSheetData(changeSpreadSheet);
+                changeSpreadObj.makeSjsFooter();
+                return;
+            }
+            if (sortData.length > 0 && range.col + range.colCount > 10) {
+                toastMessageUniq(hint.cellError);
+                SpreadJsObj.reLoadSheetHeader(changeSpreadSheet);
+                SpreadJsObj.reLoadSheetData(changeSpreadSheet);
+                changeSpreadObj.makeSjsFooter();
+                return;
+            }
+            const data = [];
+            // const rowData = [];
+            for (let iRow = 0; iRow < range.rowCount; iRow++) {
+                let bPaste = true;
+                const curRow = range.row + iRow;
+                // const materialData = JSON.parse(JSON.stringify(sortData[curRow]));
+                const cLData = { id: sortData[curRow].id };
+                const hintRow = range.rowCount > 1 ? curRow : '';
+                let sameCol = 0;
+                for (let iCol = 0; iCol < range.colCount; iCol++) {
+                    const curCol = range.col + iCol;
+                    const colSetting = info.sheet.zh_setting.cols[curCol];
+                    if (!colSetting) continue;
+
+                    let validText = info.sheet.getText(curRow, curCol);
+                    validText = is_numeric(validText) ? parseFloat(validText) : (validText ? trimInvalidChar(validText) : '');
+                    const orgValue = sortData[curRow][colSetting.field];
+                    if (orgValue == validText || ((!orgValue || orgValue === '') && (validText === ''))) {
+                        sameCol++;
+                        if (range.colCount === sameCol)  {
+                            bPaste = false;
+                        }
+                        continue;
+                    }
+                    if (colSetting.type === 'Number') {
+                        if (isNaN(validText)) {
+                            toastMessageUniq(getPasteHint(hint.numberExpr, hintRow));
+                            bPaste = false;
+                            continue;
+                        }
+                        if (colSetting.field === 'unit_price') {
+                            validText = ZhCalc.round(validText, unitPriceUnit);
+                        } else {
+                            validText = ZhCalc.round(validText, findDecimal(sortData[curRow].unit)) || 0;
+                        }
+                    }
+                    if (colSetting.field === 'unit') {
+                        //粘贴内容要为下拉列表里所有的单位,不然为空
+                        if (changeUnits.indexOf(validText) === -1) {
+                            validText = '';
+                        }
+                        cLData.camount = ZhCalc.round(sortData[curRow].camount, findDecimal(validText)) || 0;
+                        cLData.oamount = ZhCalc.round(sortData[curRow].oamount, findDecimal(validText)) || 0;
+                    }
+                    cLData[colSetting.field] = validText;
+                    sortData[curRow][colSetting.field] = validText;
+                    cLData.spamount = ZhCalc.round(sortData[curRow].camount, findDecimal(validText)) || 0;
+                }
+                if (bPaste) {
+                    data.push(cLData);
+                    // rowData.push(curRow);
+                } else {
+                    SpreadJsObj.reLoadRowData(info.sheet, curRow);
+                }
+            }
+            if (data.length === 0) {
+                SpreadJsObj.reLoadRowData(info.sheet, info.cellRange.row, info.cellRange.rowCount);
+                return;
+            }
+            console.log(data);
+            // 更新至服务器
+            postData(window.location.pathname + '/save', { type:'paste', updateData: data }, function (result) {
+                changeList = result;
+                SpreadJsObj.loadSheetData(changeSpreadSheet, SpreadJsObj.DataType.Data, changeList);
+                changeSpreadObj.makeSjsFooter();
+            }, function () {
+                SpreadJsObj.reLoadRowData(info.sheet, info.cellRange.row, info.cellRange.rowCount);
+                return;
+            });
+        },
+    };
+
+    const preUrl = window.location.pathname.split('/').slice(0, 4).join('/');
+    let changeListData;
+    let gclGatherData;
+    postData(preUrl + '/defaultBills', {}, function (result) {
+        gclGatherModel.loadLedgerData(result.bills);
+        gclGatherModel.loadPosData(result.pos);
+
+        gclGatherData = gclGatherModel.gatherGclData();
+        gclGatherData = _.filter(gclGatherData, function (item) {
+            return item.leafXmjs && item.leafXmjs.length !== 0;
+        });
+        for (const ggd in gclGatherData) {
+            if (gclGatherData[ggd].leafXmjs && gclGatherData[ggd].leafXmjs.length === 0) {
+                gclGatherData.splice(ggd, 1);
+            }
+            gclGatherData[ggd].code = gclGatherData[ggd].b_code;
+        }
+        // 数组去重
+        const dealBillList = result.dealBills;
+        for (const db of gclGatherData) {
+            const exist_index = dealBillList.findIndex(function (item) {
+                return item.code === db.code && item.name === db.name && item.unit === db.unit && item.unit_price === db.unit_price;
+            });
+            if (exist_index !== -1) {
+                dealBillList.splice(exist_index, 1);
+            }
+        }
+        changeListData = gclGatherData.concat(dealBillList).sort(sortByCode);
+        // 先加载台账数据
+        let listHtml = '';
+        let list_index = 1;
+        let gcl_index = 0;
+        for (const gcl of changeListData) {
+            const unit = gcl.unit !== undefined && gcl.unit !== null ? gcl.unit : '';
+            const quantity = gcl.quantity !== null && gcl.quantity !== undefined ? (unit !== '' ? ZhCalc.round(gcl.quantity, findDecimal(gcl.unit)) : gcl.quantity) : 0;
+            const unit_price = gcl.unit_price !== null && gcl.unit_price !== undefined ? gcl.unit_price : 0;
+            let gclhtml = gcl.leafXmjs !== undefined && gcl.leafXmjs !== null ? ' data-gcl="' + gcl_index + '"' : '';
+            gcl_index = gclhtml !== '' ? ++gcl_index : gcl_index;
+            const lid = gcl.leafXmjs !== undefined && gcl.leafXmjs !== null ? (gcl.leafXmjs.length !== 0 ? gcl.leafXmjs[0].gcl_id : false) : gcl.id;
+            if (lid) {
+                listHtml += '<tr data-lid="' + lid + '"' + gclhtml + ' data-index="' + list_index + '" data-bwmx="">' +
+                    '<td class="text-center">' + list_index + '</td>' +
+                    '<td>' + gcl.code + '</td>' +
+                    '<td class="text-left">' + gcl.name + '</td>' +
+                    '<td class="text-center">' + unit + '</td>' +
+                    '<td class="text-right">' + (ZhCalc.round(unit_price, unitPriceUnit) ? ZhCalc.round(unit_price, unitPriceUnit) : 0) + '</td>' +
+                    '<td class="text-right">' + quantity + '</td>' +
+                    '</tr>';
+                list_index++;
+            }
+        }
+        $('#table-list-select').html(listHtml);
+        tableDataRemake(changeListData);
+
+        SpreadJsObj.initSpreadSettingEvents(changeSpreadSetting, changeCol);
+        SpreadJsObj.initSheet(changeSpreadSheet, changeSpreadSetting);
+        SpreadJsObj.loadSheetData(changeSpreadSheet, SpreadJsObj.DataType.Data, changeList);
+        changeSpreadObj.makeSjsFooter();
+    });
+
+    if (!readOnly) {
+        $('#add-white-btn').click(changeSpreadObj.add);
+        changeSpread.bind(spreadNS.Events.EditEnded, changeSpreadObj.editEnded);
+        changeSpread.bind(spreadNS.Events.SelectionChanged, changeSpreadObj.selectionChanged);
+        changeSpread.bind(spreadNS.Events.ClipboardPasted, changeSpreadObj.clipboardPasted);
+        SpreadJsObj.addDeleteBind(changeSpread, changeSpreadObj.deletePress);
+        changeSpreadSheet.getCell(-1, 10).foreColor('#dc3545');
+
+        // 右键菜单
+        $.contextMenu({
+            selector: '#change-spread',
+            build: function ($trigger, e) {
+                const target = SpreadJsObj.safeRightClickSelection($trigger, e, changeSpread);
+                return target.hitTestType === GC.Spread.Sheets.SheetArea.viewport || target.hitTestType === GC.Spread.Sheets.SheetArea.rowHeader;
+            },
+            items: {
+                'createList': {
+                    name: '添加台账清单',
+                    icon: 'fa-sign-in',
+                    callback: function (key, opt) {
+                        $('#addlist').modal('show');
+                    },
+                },
+                'createAdd': {
+                    name: '添加空白清单',
+                    icon: 'fa-sign-in',
+                    callback: function (key, opt) {
+                        changeSpreadObj.add(changeSpreadSheet);
+                    },
+                },
+                'delete': {
+                    name: '删除',
+                    icon: 'fa-remove',
+                    callback: function (key, opt) {
+                        changeSpreadObj.del(changeSpreadSheet);
+                    },
+                    disabled: function (key, opt) {
+                        const select = SpreadJsObj.getSelectObject(changeSpreadSheet);
+                        const sel = changeSpreadSheet.getSelections()[0];
+                        console.log(select, sel);
+                        if (!readOnly && select && sel.row !== changeSpreadSheet.getRowCount() - 1) {
+                            return false;
+                        } else {
+                            return true;
+                        }
+                    }
+                },
+            }
+        });
+    }
+
+    // 清单选中和移除
+    $('body').on('click', '#table-list-select tr', function () {
+        $('#table-list-select tr').removeClass('table-warning');
+        $(this).addClass('table-warning');
+        const isCheck = $(this).hasClass('table-success') ? true : false;
+        const data_bwmx = $(this).attr('data-bwmx').split('$#$');
+        const isDeal = $(this).data('gcl') !== undefined ? true : false;
+        let codeHtml = '<tr quantity="'+ $(this).children('td').eq(5).text() +'" gcl_id=""><td colspan="7" class="colspan_1">&nbsp;</td><td class="colspan_2"><input type="checkbox"></td></tr>';
+        if (isDeal) {
+            const lid = $(this).data('lid');
+            let gcl = _.find(gclGatherData, function (item) {
+                return item.leafXmjs && item.leafXmjs[0].gcl_id === lid;
+            });
+            if (!gcl) {
+                gcl = gclGatherData[$(this).data('gcl')];
+            }
+            codeHtml = '';
+            for (const leaf of gcl.leafXmjs) {
+                const quantity = leaf.quantity !== undefined && leaf.quantity !== null ? leaf.quantity : 0;
+                const gcl_id = leaf.gcl_id ? leaf.gcl_id : '';
+                const bwmx = leaf.bwmx !== undefined ? leaf.bwmx : '';
+                const isChecked = data_bwmx.indexOf(leaf.code + '!_!' + (leaf.jldy ? leaf.jldy : '') + '!_!' + (leaf.gcl_id ? leaf.gcl_id : '0') + '!_!' + (bwmx !== '' ? bwmx : leaf.jldy ? leaf.jldy : '') + '*;*' + quantity) !== -1 && isCheck ? 'checked' : '';
+                codeHtml += '<tr quantity="' + quantity + '" gcl_id="' + gcl_id + '"><td>' + leaf.code + '</td>' +
+                    '<td>' + (leaf.jldy ? leaf.jldy: '') + '</td>' +
+                    '<td>' + (leaf.dwgc ? leaf.dwgc : '') + '</td>' +
+                    '<td>' + (leaf.fbgc ? leaf.fbgc : '') + '</td>' +
+                    '<td>' + (leaf.fxgc ? leaf.fxgc : '') + '</td>' +
+                    '<td>' + bwmx + '</td>' +
+                    '<td class="text-right">' + (ZhCalc.round(quantity, findDecimal(gcl.unit)) ? ZhCalc.round(quantity, findDecimal(gcl.unit)) : 0) + '</td>' +
+                    '<td class="text-center"><input type="checkbox"' + isChecked +
+                    '></td></tr>';
+            }
+        } else if (!isDeal && isCheck) {
+            codeHtml = '<tr quantity="'+ $(this).children('td').eq(5).text() +'" gcl_id=""><td colspan="7" class="colspan_1">&nbsp;</td><td class="colspan_2"><input type="checkbox" checked></td></tr>';
+        }
+        $('#code-list').attr('data-index', $(this).children('td').eq(0).text());
+        $('#code-input').val('');
+        $('#code-input').siblings('a').hide();
+        $('#code-list').html(codeHtml);
+    });
+
+    // 右边项目节选择
+    $('body').on('click', '#code-list input', function () {
+        let index = $('#code-list').attr('data-index');
+        if ($(this).is(':checked')) {
+            // 去除其它可能已选的checked
+            // $('#code-list input').prop('checked', false);
+            $(this).prop('checked', true);
+            // 左边表单传值并添加class
+            $('#table-list-select tr[data-index="' + index + '"]').addClass('table-success');
+            // 去除部分data-detail值
+            let data_bwmx = [];
+            $('#code-list input:checked').each(function () {
+                const tr = $(this).parents('tr');
+                const length = tr.children('td').length;
+                const gcl_id = tr.attr('gcl_id');
+                const bwmx = length === 8 ? tr.children('td').eq(0).text() + '!_!' + tr.children('td').eq(1).text() + '!_!' + gcl_id + '!_!' + (tr.children('td').eq(5).text() !== '' ? tr.children('td').eq(5).text() : tr.children('td').eq(1).text()) : '0';
+                const quantity = tr.attr('quantity');
+                const de_qu = bwmx + '*;*' + quantity;
+                data_bwmx.push(de_qu);
+            });
+            data_bwmx = data_bwmx.join('$#$');
+            $('#table-list-select tr[data-index="' + index + '"]').attr('data-bwmx', data_bwmx);
+        } else {
+            // 判断还有无选中项目节编号
+            if ($('#code-list input').is(':checked')) {
+                // 去除部分data-detail值
+                let data_bwmx = [];
+                $('#code-list input:checked').each(function () {
+                    const tr = $(this).parents('tr');
+                    const length = tr.children('td').length;
+                    const gcl_id = tr.attr('gcl_id');
+                    const bwmx = length === 8 ? tr.children('td').eq(0).text() + '!_!'+ tr.children('td').eq(1).text() + '!_!' + gcl_id + '!_!' + (tr.children('td').eq(5).text() !== '' ? tr.children('td').eq(5).text() : tr.children('td').eq(1).text()) : '0';
+                    const quantity = tr.attr('quantity');
+                    const de_qu = bwmx + '*;*' + quantity;
+                    data_bwmx.push(de_qu);
+                });
+                data_bwmx = data_bwmx.join('$#$');
+                $('#table-list-select tr[data-index="' + index + '"]').attr('data-bwmx', data_bwmx);
+            } else {
+                $('#table-list-select tr[data-index="' + index + '"]').removeClass('table-success');
+                $('#table-list-select tr[data-index="' + index + '"]').attr('data-bwmx', '');
+            }
+        }
+    });
+
+    // 添加空白清单or签约清单
+    $('.add-list-btn').on('click', function () {
+        const newLedgerList = remakeChangeSpread();
+        // 更新至服务器
+        postData(window.location.pathname + '/save', { type:'ledger_list', updateData: newLedgerList }, function (result) {
+            changeList = result;
+            SpreadJsObj.loadSheetData(changeSpreadSheet, SpreadJsObj.DataType.Data, changeList);
+            changeSpreadObj.makeSjsFooter();
+            $('#addlist').modal('hide');
+        }, function () {
+            $('#addlist').modal('hide');
+        });
+
+    });
+
+    // 选中input所有值
+    $('body').on('focus', ".clist input", function() {
+        $(this).select();
+    });
+
+    // 取消选中清单
+    $('#cancel-list-btn').click(function () {
+        // $('#table-list-select tr').removeClass('table-success');
+        // $('#table-list-select tr').attr('data-bwmx', '');
+        // $('#code-list').html('');
+        tableDataRemake(changeListData);
+    });
+
+    // 自动编号
+    $('.reduction-code').click(function () {
+        const code = $(this).attr('data-code');
+        $('input[name="code"]').val(code);
+    });
+
+    $('#list-input').on('valuechange', function (e, previous) {
+        const value = $(this).val();
+        let showListData = changeListData;
+        if (value !== '') {
+            $(this).siblings('a').show();
+            showListData = _.filter(changeListData, function (c) {
+                return (c.code && c.code.indexOf(value) !== -1) || (c.name && c.name.indexOf(value) !== -1);
+            });
+        } else {
+            $(this).siblings('a').hide();
+        }
+        makeListTable(changeListData, showListData);
+        $('#table-list-select tr').removeClass('table-warning');
+        $('#code-input').val('');
+        $('#code-input').siblings('a').hide();
+        $('#code-list').html('');
+    });
+
+    $('#code-input').on('valuechange', function (e, previous) {
+        const value = $(this).val();
+        if (value !== '') {
+            $(this).siblings('a').show();
+        } else {
+            $(this).siblings('a').hide();
+        }
+        makeCodeTable($(this).val());
+    });
+
+    $('.remove-btn').on('click', function () {
+        $(this).hide();
+        $(this).siblings('input').val('');
+        if ($(this).data('btn') === 'list') {
+            makeListTable(changeListData);
+            $('#table-list-select tr').removeClass('table-warning');
+            $('#code-list').html('');
+        } else {
+            makeCodeTable();
+        }
+    });
+
+    // 记录变更信息操作
+    $('body').on('valuechange', '#change_form input[type="text"]', function (e, previous) {
+        changeInfo[$(this).attr('name')] = $(this).val();
+        judgeChange();
+    });
+    $('body').on('valuechange', '#change_form textarea', function (e, previous) {
+        changeInfo[$(this).attr('name')] = $(this).val().replace(/[\r\n]/g, '<br><br>');
+        judgeChange();
+    });
+    $('body').on('change', '#change_form select', function (e, previous) {
+        changeInfo[$(this).attr('name')] = $(this).val();
+        judgeChange();
+    });
+    $('body').on('click', '#change_form input[type="radio"]', function (e, previous) {
+        changeInfo[$(this).attr('name')] = $(this).val();
+        judgeChange();
+    });
+    $('body').on('click', '#change_form input[type="checkbox"]', function (e, previous) {
+        const typecheck = [];
+        $.each($('#change_form input[name="type[]"]:checked'), function () {
+            typecheck.push($(this).val());
+        });
+        changeInfo.type = typecheck.join(',');
+        judgeChange();
+    });
+
+    // 保存修改ajax提交(不刷新页面)
+    $('.save_change_btn').on('click', function () {
+        // 保存修改modal
+        if ($('input[name="code"]').val() === '') {
+            toastr.error('申请编号不能为空!');
+            return;
+        }
+        if ($('input[name="name"]').val() === '') {
+            toastr.error('工程名称不能为空!');
+            return;
+        }
+        // 更新至服务器
+        postData(window.location.pathname + '/save', { type:'info', updateData: changeInfo }, function (result) {
+            $('.reduction-code').attr('data-code', $('input[name="code"]').val());
+            toastr.success(result);
+            $('#show-save-btn').hide();
+            $('#sp-btn').show();
+            $('.title-main').removeClass('bg-warning');
+            back_changeInfo = Object.assign({}, changeInfo);
+        });
+        return false;
+    });
+
+    $('#cancel_change').on('click', function () {
+        $('#show-save-btn').hide();
+        $('#sp-btn').show();
+        $('.title-main').removeClass('bg-warning');
+        if (!isObjEqual(changeInfo, back_changeInfo)) {
+            changeFormRemake();
+        }
+        toastr.success('已还原到上次保存状态');
+    });
+});
+function checkChangeFrom() {
+    let returnFlag = false;
+    // 表单判断
+    if ($('input[name="code"]').val() === '') {
+        toastr.error('申请编号不能为空!');
+        returnFlag = true;
+    }
+    if ($('input[name="name"]').val() === '') {
+        toastr.error('工程名称不能为空!');
+        returnFlag = true;
+    }
+    if ($('textarea[name="content"]').val() === '') {
+        toastr.error('工程变更理由及内容不能为空!');
+        returnFlag = true;
+    }
+    if (changeList.length === 0) {
+        toastr.error('请添加变更清单!');
+        returnFlag = true;
+    } else {
+        for (const [i,cl] of changeList.entries()) {
+            if (cl.code === '' || cl.name === '' || cl.oamount === '') {
+                toastr.error('变更清单第' + (i+1) + '行未完整填写数据(变更部位、变更详情、单位、单价可空)');
+                returnFlag = true;
+            }
+        }
+    }
+    if(!checkAuditorFrom ()) {
+        returnFlag = true;
+    }
+    if (returnFlag) {
+        return false;
+    }
+}
+// 检查上报情况
+function checkAuditorFrom () {
+    if ($('#auditList li').length === 0) {
+        if(shenpi_status === shenpiConst.sp_status.gdspl) {
+            toastr.error('请联系管理员添加审批人');
+        } else {
+            toastr.error('请先选择审批人,再上报数据');
+        }
+        return false;
+    }
+    return true;
+}
+
+function tableDataRemake(changeListData) {
+    $('#table-list-select tr').removeClass('table-warning');
+    $('#table-list-select tr').removeClass('table-success');
+    $('#table-list-select tr').attr('data-bwmx', '');
+    $('#code-list').html('');
+    // 根据已添加的清单显示
+    if (changeList.length > 0 && changeList[0]) {
+        for (const [index,clinfo] of changeList.entries()) {
+            if (clinfo.lid != 0) {
+                let listinfo = changeListData.find(function (item) {
+                    return (item.id !== undefined && item.id == clinfo.lid) || (item.id === undefined && item.leafXmjs !== undefined && item.leafXmjs.length !== 0 && item.leafXmjs[0].gcl_id == clinfo.lid);
+                });
+                if (listinfo === undefined) {
+                    // 针对旧数据获取清单信息
+                    listinfo = changeListData[clinfo.lid - 1];
+                    if (listinfo === undefined) {
+                        toastr.warning('台账清单列表已不存在'+ clinfo.code +',已更新变更清单列表');
+                        changeList.splice(index, 1);
+                        continue;
+                    }
+                    $('#table-list-select tr[data-index="'+ clinfo.lid +'"]').addClass('table-success');
+                    let pushbwmx = '0*;*0';
+                    if (listinfo.leafXmjs !== undefined) {
+                        const leafInfo = listinfo.leafXmjs.find(function (item) {
+                            return (item.bwmx === undefined || item.bwmx === clinfo.bwmx) && (item.quantity !== null ? item.quantity === parseFloat(clinfo.oamount) : 0 === parseFloat(clinfo.oamount));
+                        });
+                        console.log(leafInfo);
+                        if (leafInfo) {
+                            pushbwmx = leafInfo.code + '!_!' + (leafInfo.jldy !== undefined ? leafInfo.jldy : '') + '!_!' + (leafInfo.gcl_id ? leafInfo.gcl_id : '') + '!_!' + (leafInfo.bwmx !== undefined ? leafInfo.bwmx : '') + '*;*' + (leafInfo.quantity !== null ? leafInfo.quantity : 0);
+                        } else {
+                            toastr.warning('台账清单列表已不存在'+ clinfo.code +',已更新变更清单列表');
+                            changeList.splice(index, 1);
+                            continue;
+                        }
+                    } else {
+                        pushbwmx = '0*;*' + (listinfo.quantity !== null ? listinfo.quantity : 0);
+                    }
+                    const bwmx = $('#table-list-select tr[data-index="'+ clinfo.lid +'"]').attr('data-bwmx');
+                    if (bwmx) {
+                        const bwmxArray = bwmx.split('$#$');
+                        bwmxArray.push(pushbwmx);
+                        $('#table-list-select tr[data-index="'+ clinfo.lid +'"]').attr('data-bwmx', bwmxArray.join('$#$'));
+                    } else {
+                        $('#table-list-select tr[data-index="'+ clinfo.lid +'"]').attr('data-bwmx', pushbwmx);
+                    }
+                } else {
+                    $('#table-list-select tr[data-lid="'+ clinfo.lid +'"]').addClass('table-success');
+                    let pushbwmx = '0*;*0';
+                    if (listinfo.leafXmjs !== undefined) {
+                        const leafInfo = listinfo.leafXmjs.find(function (item) {
+                            return (item.bwmx === undefined || item.bwmx === clinfo.bwmx || item.jldy === clinfo.bwmx) && (item.quantity !== null ? item.quantity === parseFloat(clinfo.oamount) : 0 === parseFloat(clinfo.oamount));
+                        });
+                        if (leafInfo) {
+                            pushbwmx = leafInfo.code + '!_!' + (leafInfo.jldy !== undefined ? leafInfo.jldy : '') + '!_!' + (leafInfo.gcl_id ? leafInfo.gcl_id : '') + '!_!' + (leafInfo.bwmx !== undefined ? leafInfo.bwmx : (leafInfo.jldy ? leafInfo.jldy : '')) + '*;*' + (leafInfo.quantity !== null ? leafInfo.quantity : 0);
+                        } else {
+                            toastr.warning('台账清单列表已不存在'+ clinfo.code +',已更新变更清单列表');
+                            changeList.splice(index, 1);
+                            continue;
+                        }
+                    } else {
+                        pushbwmx = '0*;*' + (listinfo.quantity !== null ? listinfo.quantity : 0);
+                    }
+                    const bwmx = $('#table-list-select tr[data-lid="'+ clinfo.lid +'"]').attr('data-bwmx');
+                    if (bwmx) {
+                        const bwmxArray = bwmx.split('$#$');
+                        bwmxArray.push(pushbwmx);
+                        $('#table-list-select tr[data-lid="'+ clinfo.lid +'"]').attr('data-bwmx', bwmxArray.join('$#$'));
+                    } else {
+                        $('#table-list-select tr[data-lid="'+ clinfo.lid +'"]').attr('data-bwmx', pushbwmx);
+                    }
+                }
+            }
+        }
+    }
+}
+
+// 清单搜索隐藏清单table部分值
+function makeListTable(changeListData, showListData = changeListData) {
+    // 先加载台账数据
+    let listHtml = '';
+    let list_index = 1;
+    let gcl_index = 0;
+    for (const [index,gcl] of changeListData.entries()) {
+        const isShow = _.find(showListData, gcl);
+        $('#table-list-select tr').eq(index).css('display', (isShow ? 'table-row' : 'none'));
+    }
+}
+// 项目节搜索隐藏code-table部分值
+function makeCodeTable(search = '') {
+    if (search === '') {
+        $('#code-list tr').css('display', 'table-row');
+        return;
+    }
+    for(let i = 0; i < $('#code-list tr').length; i++) {
+        const length = $('#code-list tr').eq(i).children('td').length;
+        if (length === 8) {
+            const code = $('#code-list tr').eq(i).children('td').eq(0).text();
+            const name = $('#code-list tr').eq(i).children('td').eq(1).text();
+            const jldy = $('#code-list tr').eq(i).children('td').eq(5).text();
+            const isShow = code.indexOf(search) !== -1 || name.indexOf(search) !== -1 || jldy.indexOf(search) !== -1;
+            $('#code-list tr').eq(i).css('display', (isShow ? 'table-row' : 'none'));
+        } else {
+            return;
+        }
+    }
+}
+
+function remakeChangeSpread() {
+    const newTableList = [];
+    // 获取选中的签约清单判断并插入到原有清单中
+    $('#table-list-select .table-success').each(function(){
+        let code = $(this).children('td').eq(1).text();
+        let name = $(this).children('td').eq(2).text();
+        let unit = $(this).children('td').eq(3).text();
+        let price = $(this).children('td').eq(4).text();
+        // let oamount = $(this).children('td').eq(5).text();
+
+        // 根据单位获取数量的位数,并得出
+        // let numdecimal = findDecimal(unit);
+
+        // let scnum = makedecimalzero(numdecimal);
+        let scnum = 0;
+        // let detail = $(this).attr('data-detail') != 0 ? $(this).attr('data-detail').split('_')[1] : '';
+        let lid = $(this).data('lid');
+        let lindex = $(this).data('index');
+        // 原清单和数量改变
+        let data_bwmx = $(this).attr('data-bwmx').split('$#$');
+
+        for (const b of data_bwmx) {
+            const oamount = b.split('*;*')[1] != '' ? b.split('*;*')[1] : 0;
+            let bwmx = b.split('*;*')[0] != 0 ? b.split('*;*')[0].split('!_!')[3] : '';
+            let xmj_code = b.split('*;*')[0] != 0 ? b.split('*;*')[0].split('!_!')[0] : '';
+            let xmj_jldy = b.split('*;*')[0] != 0 ? b.split('*;*')[0].split('!_!')[1] : '';
+            let gcl_id = b.split('*;*')[0] != 0 ? b.split('*;*')[0].split('!_!')[2] : '';
+            let trlist = {
+                code,
+                name,
+                bwmx,
+                unit,
+                unit_price: price,
+                oamount,
+                camount: scnum,
+                detail: '',
+                lid,
+                xmj_code,
+                xmj_jldy,
+                gcl_id,
+            };
+            const radionInfo = changeList.find(function (info) {
+                return info.code === code && (info.lid == lid || parseInt(info.lid) === parseInt(lindex)) && gcl_id == info.gcl_id && info.bwmx === bwmx;
+            });
+            if (radionInfo) {
+                trlist.camount = radionInfo.camount;
+                trlist.detail = radionInfo.detail;
+            }
+            newTableList.push(trlist);
+        }
+    });
+    // const changeWhiteList = _.filter(changeList, function (item) {
+    //     return item.lid == 0;
+    // });
+    // console.log(newTableList);
+    // changeList = newTableList.concat(changeWhiteList);
+    return newTableList;
+}
+//判断元素是否在数组中,相当于php的in_array();
+function in_array(arr, obj) {
+    let i = arr.length;
+    while (i--) {
+        if (arr[i] == obj) {
+            return true;
+        }
+    }
+    return false;
+}
+function isObjEqual(o1,o2){
+    var props1 = Object.getOwnPropertyNames(o1);
+    var props2 = Object.getOwnPropertyNames(o2);
+    if (props1.length != props2.length) {
+        return false;
+    }
+    for (var i = 0,max = props1.length; i < max; i++) {
+        var propName = props1[i];
+        if (o1[propName] !== o2[propName]) {
+            return false;
+        }
+    }
+    return true;
+}
+// 判断是否有更改过
+function judgeChange() {
+    let change = false;
+    if (!isObjEqual(changeInfo, back_changeInfo)) {
+        change = true;
+    }
+    if (change) {
+        $('#show-save-btn').show();
+        $('#sp-btn').hide();
+        $('.title-main').addClass('bg-warning');
+    } else {
+        $('#show-save-btn').hide();
+        $('#sp-btn').show();
+        $('.title-main').removeClass('bg-warning');
+    }
+}
+
+function changeFormRemake() {
+    changeInfo = Object.assign({}, back_changeInfo);
+    $('#change_form input[name="code"]').val(changeInfo.code);
+    $('#change_form input[name="name"]').val(changeInfo.name);
+    $('#change_form input[name="peg"]').val(changeInfo.peg);
+    $('#change_form input[name="org_name"]').val(changeInfo.org_name);
+    $('#change_form input[name="org_code"]').val(changeInfo.org_code);
+    $('#change_form input[name="new_name"]').val(changeInfo.new_name);
+    $('#change_form input[name="new_code"]').val(changeInfo.new_code);
+    $('#change_form textarea[name="content"]').val(changeInfo.content.replace(/<br><br>/g, '\r\n'));
+    $('#change_form textarea[name="basis"]').val(changeInfo.basis.replace(/<br><br>/g, '\r\n'));
+    $('#change_form textarea[name="expr"]').val(changeInfo.expr.replace(/<br><br>/g, '\r\n'));
+    $('#change_form textarea[name="memo"]').val(changeInfo.memo.replace(/<br><br>/g, '\r\n'));
+    $('#change_form select[name="type"]').val(changeInfo.type);
+    $('#change_form select[name="class"]').val(changeInfo.class);
+    $('#change_form select[name="quality"]').val(changeInfo.quality);
+    $('#change_form select[name="company"]').val(changeInfo.company);
+    $('#change_form input[name="charge"][value="'+ changeInfo.charge +'"]').prop('checked', true);
+    $('#change_form input[name="type[]"]').prop('checked', false);
+    const typecheck = changeInfo.type.split(',');
+    for (const type of typecheck) {
+        $('#change_form input[name="type[]"][value="'+ type +'"]').prop('checked', true);
+    }
+}

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

@@ -0,0 +1,204 @@
+'use strict';
+
+/**
+ * 变更令详细页js
+ *
+ * @author EllisRan.
+ * @date 2018/11/22
+ * @version
+ */
+$(document).ready(() => {
+    const changeSpreadSetting = {
+        cols: [
+            {title: '清单编号', colSpan: '1', rowSpan: '2', field: 'code', hAlign: 0, width: 80, formatter: '@'},
+            {title: '名称', colSpan: '1', rowSpan: '2', field: 'name', hAlign: 0, width: 120, formatter: '@'},
+            {title: '变更部位', colSpan: '1', rowSpan: '2', field: 'bwmx', hAlign: 0, width: 120, formatter: '@'},
+            {title: '变更详情', colSpan: '1', rowSpan: '2', field: 'detail', hAlign: 0, width: 120, formatter: '@'},
+            {title: '单位', colSpan: '1', rowSpan: '2', field: 'unit', hAlign: 1, width: 60, formatter: '@'},
+            {title: '单价', colSpan: '1', rowSpan: '2', field: 'unit_price', hAlign: 2, width: 60, type: 'Number', getValue: 'getValue.unit_price'},
+            {title: '原设计|数量', colSpan: '2|1', rowSpan: '1|1', field: 'oamount', hAlign: 2, width: 60, type: 'Number', getValue: 'getValue.oamount'},
+            {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'oa_tp', hAlign: 2, width: 80, formatter: '@', type: 'Number', getValue: 'getValue.oa_tp'},
+            {title: '申请变更增(+)减(-)|数量', colSpan: '2|1', rowSpan: '1|1', field: 'camount', hAlign: 2, width: 60, type: 'Number', getValue: 'getValue.camount'},
+            {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'ca_tp', hAlign: 2, width: 80, formatter: '@', type: 'Number', getValue: 'getValue.ca_tp'},
+            {title: '审批后变更|数量', colSpan: '2|1', rowSpan: '1|1', field: 'samount', hAlign: 2, width: 60, type: 'Number', getValue: 'getValue.samount'},
+            {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'sa_tp', hAlign: 2, width: 80, formatter: '@', type: 'Number', getValue: 'getValue.sa_tp'},
+        ],
+        emptyRows: 0,
+        headRows: 2,
+        headRowHeight: [25, 25],
+        defaultRowHeight: 21,
+        headerFont: '12px 微软雅黑',
+        font: '12px 微软雅黑',
+        readOnly: true,
+    };
+    for (const aid of aidList) {
+        const userinfo = _.find(auditList2, { 'uid': aid });
+        const newColcount = {
+            title: userinfo.name + ' 审批|数量',
+            colSpan: '2|1', rowSpan: '1|1',
+            field: 'audit_amount_' + aid,
+            hAlign: 2, width: 60, type: 'Number',
+        };
+        const newColTp = {
+            title: '|金额',
+            colSpan: '|1', rowSpan: '|1',
+            field: 'sa_tp',
+            hAlign: 2, width: 80, formatter: '@', type: 'Number',
+        };
+        changeSpreadSetting.cols.push(newColcount);
+        changeSpreadSetting.cols.push(newColTp);
+    }
+
+    const changeCol = {
+        getValue: {
+            unit_price: function(data) {
+                return ZhCalc.round(data.unit_price, unitPriceUnit);
+            },
+            oa_tp: function (data) {
+                return ZhCalc.round(ZhCalc.mul(data.unit_price, data.oamount), totalPriceUnit);
+            },
+            ca_tp: function (data) {
+                return ZhCalc.round(ZhCalc.mul(data.unit_price, data.camount), totalPriceUnit);
+            },
+            oamount: function (data) {
+                return ZhCalc.round(data.oamount, findDecimal(data.unit));
+            },
+            camount: function (data) {
+                return ZhCalc.round(data.camount, findDecimal(data.unit));
+            },
+            samount: function (data) {
+                return ZhCalc.round(data.samount, findDecimal(data.unit));
+            },
+            sa_tp: function (data) {
+                return ZhCalc.round(ZhCalc.mul(data.unit_price, data.samount), totalPriceUnit);
+            },
+        },
+    };
+
+    const changeSpreadObj = {
+        makeSjsFooter: function () {
+            // 增加汇总行并设为锁定禁止编辑状态
+            changeSpreadSheet.addRows(changeSpreadSheet.getRowCount(), 1);
+            changeSpreadSheet.setValue(changeSpreadSheet.getRowCount() - 1, 0, '合计');
+            changeSpreadSheet.setStyle(changeSpreadSheet.getRowCount() - 1, -1, style1);
+            changeSpreadObj.countSum();
+        },
+        setAuditValue: function () {
+            const rowCount = changeSpreadSheet.getRowCount();
+            // 用户的数据合计
+            for (const j in aidList) {
+                for(let i = 0; i <= rowCount - 1; i++){
+                    const data = {
+                        unit_price: changeSpreadSheet.getValue(i, 5),
+                        amount: parseFloat(changeSpreadSheet.getValue(i, 12 + parseInt(j)*2)),
+                    };
+                    const sum = ZhCalc.round(ZhCalc.mul(data.unit_price, data.amount), totalPriceUnit);
+                    changeSpreadSheet.setValue(i, 13 + j*2, sum !== 0 ? sum : null);
+                }
+            }
+        },
+        countSum: function() {
+            const rowCount = changeSpreadSheet.getRowCount();
+            let oSum = 0,
+                cSum = 0,
+                sSum = 0;
+            for(let i = 0; i < rowCount - 1; i++){
+                oSum = ZhCalc.add(oSum, changeSpreadSheet.getValue(i, 7));
+                cSum = ZhCalc.add(cSum, changeSpreadSheet.getValue(i, 9));
+                sSum = ZhCalc.add(sSum, changeSpreadSheet.getValue(i, 11));
+            }
+            changeSpreadSheet.setValue(changeSpreadSheet.getRowCount() - 1, 7, oSum !== 0 ? oSum : null);
+            changeSpreadSheet.setValue(changeSpreadSheet.getRowCount() - 1, 9, cSum !== 0 ? cSum : null);
+            changeSpreadSheet.setValue(changeSpreadSheet.getRowCount() - 1, 11, sSum !== 0 ? sSum : null);
+            // 用户的数据合计
+            for (const j in aidList) {
+                let audit_sum = 0;
+                for(let i = 0; i < rowCount - 1; i++){
+                    audit_sum = ZhCalc.add(audit_sum, changeSpreadSheet.getValue(i, 13 + j*2));
+                }
+                changeSpreadSheet.setValue(changeSpreadSheet.getRowCount() - 1, 13 + j*2, audit_sum !== 0 ? audit_sum : null);
+            }
+        },
+        showHideAudit: function (show = false) {
+            const count = changeSpreadSetting.cols.length;
+            for (let i = 12; i < count; i++) {
+                changeSpreadSheet.setColumnVisible(i, show, GC.Spread.Sheets.SheetArea.viewport);
+            }
+            changeSpreadSheet.setColumnVisible(10, !show, GC.Spread.Sheets.SheetArea.viewport);
+            changeSpreadSheet.setColumnVisible(11, !show, GC.Spread.Sheets.SheetArea.viewport);
+        }
+    };
+
+    SpreadJsObj.initSpreadSettingEvents(changeSpreadSetting, changeCol);
+    SpreadJsObj.initSheet(changeSpreadSheet, changeSpreadSetting);
+    SpreadJsObj.loadSheetData(changeSpreadSheet, SpreadJsObj.DataType.Data, changeList);
+    changeSpreadObj.setAuditValue();
+    changeSpreadObj.makeSjsFooter();
+    changeSpreadObj.showHideAudit();
+
+    // 审批流程展示与隐藏
+    $('#show-table-detail').on('click', function (e) {
+        if($(e.target).is('label')){
+            return;
+        }
+        changeSpreadObj.showHideAudit($(this).is(':checked'));
+    });
+
+    // 重新审批获取手机验证码
+    // 获取验证码
+    let isPosting = false;
+    $("#get-code").click(function() {
+        if (isPosting) {
+            return false;
+        }
+        const btn = $(this);
+
+        $.ajax({
+            url: '/profile/code?_csrf=' + csrf,
+            type: 'post',
+            data: { mobile: authMobile, type: 'shenpi' },
+            dataTye: 'json',
+            error: function() {
+                isPosting = false;
+            },
+            beforeSend: function() {
+                isPosting = true;
+            },
+            success: function(response) {
+                isPosting = false;
+                if (response.err === 0) {
+                    codeSuccess(btn);
+                    $("input[name='code']").removeAttr('readonly');
+                    $("#re-shenpi-btn").removeAttr('disabled');
+                } else {
+                    toast(response.msg, 'error');
+                }
+            }
+        });
+    });
+});
+/**
+ * 获取成功后的操作
+ *
+ * @param {Object} btn - 点击的按钮
+ * @return {void}
+ */
+function codeSuccess(btn) {
+    let counter = 60;
+    btn.addClass('disabled').text('重新获取 ' + counter + 'S');
+    btn.parent().siblings('input').removeAttr('readonly').attr('placeholder', '输入短信中的6位验证码');
+    const bindBtn = $("#bind-btn");
+    bindBtn.removeClass('btn-secondary disabled').addClass('btn-primary');
+
+    const countDown = setInterval(function() {
+        const countString = counter - 1 <= 0 ? '' : ' ' + (counter - 1) + 'S';
+        // 倒数结束后
+        if (countString === '') {
+            clearInterval(countDown);
+            btn.removeClass('disabled');
+        }
+        const text = '重新获取' + countString;
+        btn.text(text);
+        counter -= 1;
+    }, 1000);
+}

+ 22 - 15
app/public/js/change_set.js

@@ -404,7 +404,7 @@ $(document).ready(() => {
         const isCheck = $(this).hasClass('table-success') ? true : false;
         const data_bwmx = $(this).attr('data-bwmx').split('$#$');
         const isDeal = $(this).data('gcl') !== undefined ? true : false;
-        let codeHtml = '<tr quantity="'+ $(this).children('td').eq(5).text() +'"><td colspan="7" class="colspan_1">&nbsp;</td><td class="colspan_2"><input type="checkbox"></td></tr>';
+        let codeHtml = '<tr quantity="'+ $(this).children('td').eq(5).text() +'" gcl_id=""><td colspan="7" class="colspan_1">&nbsp;</td><td class="colspan_2"><input type="checkbox"></td></tr>';
         if (isDeal) {
             const lid = $(this).data('lid');
             let gcl = _.find(gclGatherData, function (item) {
@@ -416,9 +416,10 @@ $(document).ready(() => {
             codeHtml = '';
             for (const leaf of gcl.leafXmjs) {
                 const quantity = leaf.quantity !== undefined && leaf.quantity !== null ? leaf.quantity : 0;
+                const gcl_id = leaf.gcl_id ? leaf.gcl_id : '';
                 const bwmx = leaf.bwmx !== undefined ? leaf.bwmx : '';
-                const isChecked = data_bwmx.indexOf(leaf.code + '!_!' + (leaf.jldy ? leaf.jldy : '') + '!_!' + (bwmx !== '' ? bwmx : leaf.jldy ? leaf.jldy : '') + '*;*' + quantity) !== -1 && isCheck ? 'checked' : '';
-                codeHtml += '<tr quantity="' + quantity + '"><td>' + leaf.code + '</td>' +
+                const isChecked = data_bwmx.indexOf(leaf.code + '!_!' + (leaf.jldy ? leaf.jldy : '') + '!_!' + (leaf.gcl_id ? leaf.gcl_id : '') + '!_!' + (bwmx !== '' ? bwmx : leaf.jldy ? leaf.jldy : '') + '*;*' + quantity) !== -1 && isCheck ? 'checked' : '';
+                codeHtml += '<tr quantity="' + quantity + '" gcl_id="' + gcl_id + '"><td>' + leaf.code + '</td>' +
                     '<td>' + (leaf.jldy ? leaf.jldy: '') + '</td>' +
                     '<td>' + (leaf.dwgc ? leaf.dwgc : '') + '</td>' +
                     '<td>' + (leaf.fbgc ? leaf.fbgc : '') + '</td>' +
@@ -429,7 +430,7 @@ $(document).ready(() => {
                     '></td></tr>';
             }
         } else if (!isDeal && isCheck) {
-            codeHtml = '<tr quantity="'+ $(this).children('td').eq(5).text() +'"><td colspan="7" class="colspan_1">&nbsp;</td><td class="colspan_2"><input type="checkbox" checked></td></tr>';
+            codeHtml = '<tr quantity="'+ $(this).children('td').eq(5).text() +'" gcl_id=""><td colspan="7" class="colspan_1">&nbsp;</td><td class="colspan_2"><input type="checkbox" checked></td></tr>';
         }
         $('#code-list').attr('data-index', $(this).children('td').eq(0).text());
         $('#code-input').val('');
@@ -451,7 +452,8 @@ $(document).ready(() => {
             $('#code-list input:checked').each(function () {
                 const tr = $(this).parents('tr');
                 const length = tr.children('td').length;
-                const bwmx = length === 8 ? tr.children('td').eq(0).text() + '!_!' + tr.children('td').eq(1).text() + '!_!' + (tr.children('td').eq(5).text() !== '' ? tr.children('td').eq(5).text() : tr.children('td').eq(1).text()) : '0';
+                const gcl_id = tr.attr('gcl_id');
+                const bwmx = length === 8 ? tr.children('td').eq(0).text() + '!_!' + tr.children('td').eq(1).text() + '!_!' + gcl_id + '!_!' + (tr.children('td').eq(5).text() !== '' ? tr.children('td').eq(5).text() : tr.children('td').eq(1).text()) : '0';
                 const quantity = tr.attr('quantity');
                 const de_qu = bwmx + '*;*' + quantity;
                 data_bwmx.push(de_qu);
@@ -466,7 +468,8 @@ $(document).ready(() => {
                 $('#code-list input:checked').each(function () {
                     const tr = $(this).parents('tr');
                     const length = tr.children('td').length;
-                    const bwmx = length === 8 ? tr.children('td').eq(0).text() + '!_!'+ tr.children('td').eq(1).text() + '!_!' + (tr.children('td').eq(5).text() !== '' ? tr.children('td').eq(5).text() : tr.children('td').eq(1).text()) : '0';
+                    const gcl_id = tr.attr('gcl_id');
+                    const bwmx = length === 8 ? tr.children('td').eq(0).text() + '!_!'+ tr.children('td').eq(1).text() + '!_!'+ gcl_id + '!_!' + (tr.children('td').eq(5).text() !== '' ? tr.children('td').eq(5).text() : tr.children('td').eq(1).text()) : '0';
                     const quantity = tr.attr('quantity');
                     const de_qu = bwmx + '*;*' + quantity;
                     data_bwmx.push(de_qu);
@@ -536,6 +539,7 @@ $(document).ready(() => {
         const lid = $(this).parents('tr').data('lid');
         const xmj_code = $(this).parents('tr').data('xmjcode') || '';
         const xmj_jldy = $(this).parents('tr').data('xmjjldy') || '';
+        const gcl_id = $(this).parents('tr').data('gclid') || '';
         const isWhite = !isNaN(lid) ? true : false;
         const tr = $('#list tr[data-lid="' + lid + '"]').eq(0);
         const site = parseInt($(this).parents('td').data('site'));
@@ -566,6 +570,7 @@ $(document).ready(() => {
             trlist.push(0);
             trlist.push(xmj_code);
             trlist.push(xmj_jldy);
+            trlist.push(gcl_id);
             changelist.splice(index, 1, trlist.join('*;*'));
             $('#change-whitelist').val(changelist.join('^_^'));
         } else {
@@ -573,6 +578,7 @@ $(document).ready(() => {
             trlist.push(lid.split('_')[0]);
             trlist.push(xmj_code);
             trlist.push(xmj_jldy);
+            trlist.push(gcl_id);
             changelist.splice(index, 1, trlist.join('*;*'));
             $('#change-list').val(changelist.join('^_^'));
         }
@@ -603,7 +609,7 @@ $(document).ready(() => {
         tr.children('td[data-site="6"]').children('input').attr('onkeyup','RegNum(this,event,'+ numdecimal +')');
         tr.children('td[data-site="8"]').children('input').val(scnum);
         tr.children('td[data-site="8"]').children('input').attr('onkeyup','RegNum(this,event,'+ numdecimal +')');
-        const trlist = [code,name,bwmx,unit,price,oamount,scnum,detail,0,'',''];
+        const trlist = [code,name,bwmx,unit,price,oamount,scnum,detail,0,'','',''];
         let changelist = $('#change-whitelist').val().split('^_^');
         changelist.splice(index, 1, trlist.join('*;*'));
         $('#change-whitelist').val(changelist.join('^_^'));
@@ -720,7 +726,7 @@ function tableDataRemake(changeListData) {
                     });
                     console.log(leafInfo);
                     if (leafInfo) {
-                        pushbwmx = leafInfo.code + '!_!' + (leafInfo.jldy !== undefined ? leafInfo.jldy : '') + '!_!' + (leafInfo.bwmx !== undefined ? leafInfo.bwmx : '') + '*;*' + (leafInfo.quantity !== null ? leafInfo.quantity : 0);
+                        pushbwmx = leafInfo.code + '!_!' + (leafInfo.jldy !== undefined ? leafInfo.jldy : '') + '!_!' + (leafInfo.gcl_id ? leafInfo.gcl_id : '') + '!_!' + (leafInfo.bwmx !== undefined ? leafInfo.bwmx : '') + '*;*' + (leafInfo.quantity !== null ? leafInfo.quantity : 0);
                     } else {
                         toastr.warning('台账清单列表已不存在'+ clinfo[0] +',已更新变更清单列表');
                         changeList.splice(index, 1);
@@ -745,7 +751,7 @@ function tableDataRemake(changeListData) {
                         return (item.bwmx === undefined || item.bwmx === clinfo[2] || item.jldy === clinfo[2]) && (item.quantity !== null ? item.quantity === parseFloat(clinfo[5]) : 0 === parseFloat(clinfo[5]));
                     });
                     if (leafInfo) {
-                        pushbwmx = leafInfo.code + '!_!' + (leafInfo.jldy !== undefined ? leafInfo.jldy : '') + '!_!' + (leafInfo.bwmx !== undefined ? leafInfo.bwmx : (leafInfo.jldy ? leafInfo.jldy : '')) + '*;*' + (leafInfo.quantity !== null ? leafInfo.quantity : 0);
+                        pushbwmx = leafInfo.code + '!_!' + (leafInfo.jldy !== undefined ? leafInfo.jldy : '') + '!_!' + (leafInfo.gcl_id ? leafInfo.gcl_id : '') + '!_!' + (leafInfo.bwmx !== undefined ? leafInfo.bwmx : (leafInfo.jldy ? leafInfo.jldy : '')) + '*;*' + (leafInfo.quantity !== null ? leafInfo.quantity : 0);
                     } else {
                         toastr.warning('台账清单列表已不存在'+ clinfo[0] +',已更新变更清单列表');
                         changeList.splice(index, 1);
@@ -840,7 +846,6 @@ function makeCodeTable(search = '') {
             const code = $('#code-list tr').eq(i).children('td').eq(0).text();
             const name = $('#code-list tr').eq(i).children('td').eq(1).text();
             const jldy = $('#code-list tr').eq(i).children('td').eq(5).text();
-            console.log(code, name, jldy, search, code.indexOf(search) !== -1 || name.indexOf(search) !== -1 || jldy.indexOf(search) !== -1);
             const isShow = code.indexOf(search) !== -1 || name.indexOf(search) !== -1 || jldy.indexOf(search) !== -1;
             $('#code-list tr').eq(i).css('display', (isShow ? 'table-row' : 'none'));
         } else {
@@ -895,13 +900,14 @@ function maketablelist(status){
         let data_bwmx = $(this).attr('data-bwmx').split('$#$');
         for (const b of data_bwmx) {
             const oamount = b.split('*;*')[1] != '' ? b.split('*;*')[1] : 0;
-            let bwmx = b.split('*;*')[0] != 0 ? b.split('*;*')[0].split('!_!')[2] : '';
+            let bwmx = b.split('*;*')[0] != 0 ? b.split('*;*')[0].split('!_!')[3] : '';
             let xmj_code = b.split('*;*')[0] != 0 ? b.split('*;*')[0].split('!_!')[0] : '';
             let xmj_jldy = b.split('*;*')[0] != 0 ? b.split('*;*')[0].split('!_!')[1] : '';
-            let trlist = [code, name, bwmx, unit, price, oamount, scnum, '', lid, xmj_code, xmj_jldy];
+            let gcl_id = b.split('*;*')[0] != 0 ? b.split('*;*')[0].split('!_!')[2] : '';
+            let trlist = [code, name, bwmx, unit, price, oamount, scnum, '', lid, xmj_code, xmj_jldy, gcl_id];
             const radionInfo = radionList.find(function (item) {
                 const info = item.split('*;*');
-                return info[0] === code && (info[8] == lid || parseInt(info[8]) === parseInt(lindex)) && info[2] === bwmx;
+                return info[0] === code && (info[8] == lid || parseInt(info[8]) === parseInt(lindex)) && gcl_id == info[11] && info[2] === bwmx;
             });
             if (radionInfo) {
                 trlist[6] = radionInfo.split('*;*')[6];
@@ -928,11 +934,12 @@ function maketablelist(status){
         let lid = radionArray[8];
         let xmj_code = radionArray[9];
         let xmj_jldy = radionArray[10];
+        let gcl_id = radionArray[11];
         let sctotal = scnum !== '' && scnum !== '-' ? roundnum(parseFloat(price).mul(parseFloat(scnum)),decimal) : '';
 
         // 根据单位获取数量的位数,并得出
         let numdecimal = findDecimal(unit);
-        html += '<tr class="clist clid" data-lid="' + lid + '_' + index + '" data-index="' + index + '" data-xmjcode="'+ xmj_code +'" data-xmjjldy="'+ xmj_jldy +'">' +
+        html += '<tr class="clist clid" data-lid="' + lid + '_' + index + '" data-index="' + index + '" data-xmjcode="'+ xmj_code +'" data-xmjjldy="'+ xmj_jldy +'" data-gclid="'+ gcl_id +'">' +
             '<td data-site="0">'+ code +'</td>' +
             '<td data-site="1">'+ name +'</td>' +
             '<td data-site="2">'+ bwmx +'</td>' +
@@ -952,7 +959,7 @@ function maketablelist(status){
     let radionWhiteList = $('#change-whitelist').val() !== '' ? $('#change-whitelist').val().split('^_^') : [];
     //判断是否添加空白清单
     if(status == 'addwhite'){
-        let trlist = ['','','','','',makedecimalzero(findDecimal(3)),makedecimalzero(findDecimal(3)),'',0, '', ''];
+        let trlist = ['','','','','',makedecimalzero(findDecimal(3)),makedecimalzero(findDecimal(3)),'',0, '', '', ''];
         radionWhiteList.push(trlist.join('*;*'));
     }
 

+ 37 - 7
app/public/js/gcl_gather.js

@@ -336,8 +336,11 @@ const gclGatherModel = (function () {
             gcl.end_qc_tp = ZhCalc.add(gcl.pre_qc_tp, gcl.qc_tp);
             gcl.end_gather_tp = ZhCalc.add(gcl.pre_gather_tp, gcl.gather_tp);
             gcl.dgn_price = ZhCalc.round(ZhCalc.div(gcl.total_price, gcl.dgn_qty1), 2);
+            gcl.end_final_qty = ZhCalc.add(gcl.end_qc_qty, gcl.quantity);
             gcl.end_final_tp = ZhCalc.add(gcl.end_qc_tp, gcl.total_price);
-            gcl.end_gather_percent = ZhCalc.mul(ZhCalc.div(gcl.end_gather_tp, gcl.end_final_tp), 100, 2);
+            gcl.end_gather_percent = gcl.end_final_qty && gcl.end_gather_qty
+                ? ZhCalc.mul(ZhCalc.div(gcl.end_gather_qty, gcl.end_final_qty), 100, 2)
+                : ZhCalc.mul(ZhCalc.div(gcl.end_gather_tp, gcl.end_final_tp), 100, 2);
             for (const xmj of gcl.leafXmjs) {
                 xmj.pre_gather_qty = ZhCalc.add(xmj.pre_contract_qty, xmj.pre_qc_qty);
                 xmj.gather_qty = ZhCalc.add(xmj.contract_qty, xmj.qc_qty);
@@ -452,22 +455,49 @@ const gclGatherModel = (function () {
         }
     }
 
-    function gatherChapterData(chapter, fields) {
-        const [gclChapter, otherChapter] = _getCalcChapter(chapter);
-        for (const d of gsTree.datas) {
+    function _checkFilter(d, filter) {
+        for (const f of filter) {
+            if (f.node_type && f.node_type === d.node_type) return true;
+            if (f.field) {
+                if (f.part && d[f.field] && d[f.field].indexOf(f.part) >= 0) return true;
+                if (f.all && d[f.all] && d[f.all] === f.all) return true;
+            }
+        }
+        return false;
+    }
+
+    function gatherChapterData(chapter, fields, filter = []) {
+        const filterPath = [];
+        const checkFilterPath = function (data) {
+            for (const fp of filterPath) {
+                if (data.full_path.indexOf(fp + '-') === 0 || data.full_path === fp) return true;
+            }
+            return false;
+        };
+
+        const [gclChapter, otherChapter] = _getCalcChapter(chapter, filter);
+        for (const d of gsTree.nodes) {
+            if (_checkFilter(d, filter)) {
+                filterPath.push(d.full_path);
+            }
             if (d.children && d.children.length > 0) continue;
 
             for (const c of otherChapter) {
                 if (c.cType === 41) {
                     gatherfields(c, d, fields);
-                } else if (c.cType === 31 && (!d.b_code || d.b_code === '')) {
+                }
+                if (c.cType === 11 && (d.b_code)) {
                     gatherfields(c, d, fields);
-                } else if (c.cType === 11 && (d.b_code)) {
+                }
+                if (c.cType === 31 && (!d.b_code || d.b_code === '')) {
                     gatherfields(c, d, fields);
                 }
             }
+
             if (d.b_code) {
-                const c = _getGclChapter(gclChapter, d);
+                const c = checkFilterPath(d)
+                    ? gclChapter.find(x => { return x.cType === 21})
+                    : _getGclChapter(gclChapter, d);
                 gatherfields(c, d, fields);
             }
         }

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

@@ -33,7 +33,7 @@ function autoFlashHeight(){
     $(".sjs-sh-4").height($(window).height()-cHeader-sBar4-92+55);
     $(".sjs-sh-5").height($(window).height()-cHeader-sBar5-92+55);
     for (const sh of $('.sjs-sh')) {
-        $(sh).height($(window).height()-cHeader-getObjHeight($('.sjs-bar', sh.parentNode))-92+55);
+        $(sh).height($(window).height()-cHeader-getObjHeight($('.sjs-bar', sh.parentNode))-getObjHeight($('.sjs-bottom', sh.parentNode))-92+55);
     }
     /*工程变更添加清单高度*/
     $(".sjs-biangeng-height").height($(window).height()/3.3);

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

@@ -1246,6 +1246,50 @@ $(document).ready(function() {
             return select && select.level <= 1;
         }
     };
+    billsContextMenuOptions.items.copyBlockXmj = {
+        name: '复制整块(只复制项目节)',
+        icon: 'fa-files-o',
+        callback: function (key, opt) {
+            treeOperationObj.block = [];
+            const copyBlockList = [];
+            const sheet = ledgerSpread.getActiveSheet();
+            const sel = sheet.getSelections()[0];
+            let iRow = sel.row;
+            const pid = sheet.zh_tree.nodes[iRow].ledger_pid;
+            while (iRow < sel.row + sel.rowCount) {
+                const node = sheet.zh_tree.nodes[iRow];
+                if (node.ledger_pid !== pid) {
+                    toastr.error('仅可同时选中同层节点');
+                    return;
+                }
+                const posterity = sheet.zh_tree.getPosterity(node);
+                iRow += posterity.length + 1;
+                const copyPosterity = posterity.filter(x => { return !x.b_code; });
+                copyPosterity.unshift(node);
+                const copyData = sheet.zh_tree.getDefaultData(copyPosterity);
+                for (const p of copyData) {
+                    const children = copyData.filter(y => {return y.ledger_pid === p.ledger_id}) || [];
+                    p.is_leaf = children.length === 0;
+                }
+                copyBlockList.push(copyData);
+            }
+            setLocalCache(copyBlockTag, JSON.stringify({block: copyBlockList}));
+        },
+        visible: function (key, opt) {
+            const sheet = ledgerSpread.getActiveSheet();
+            const selection = sheet.getSelections();
+            const row = selection[0].row;
+            const select = ledgerTree.nodes[row];
+            return select;
+        },
+        disabled: function (key, opt) {
+            const sheet = ledgerSpread.getActiveSheet();
+            const selection = sheet.getSelections();
+            const row = selection[0].row;
+            const select = ledgerTree.nodes[row];
+            return select && select.level <= 1;
+        }
+    };
     if (!readOnly) {
         billsContextMenuOptions.items.pasteBlock = {
             name: '粘贴整块',
@@ -1276,12 +1320,12 @@ $(document).ready(function() {
             icon: 'fa-sort-numeric-asc',
             disabled: function (key, opt) {
                 const node = SpreadJsObj.getSelectObject(ledgerSpread.getActiveSheet());
-                return !node || !node.code || !node.children || node.children === 0;
+                return !node || !node.code || !node.children || node.children === 0 || node.code.indexOf('-') < 0;
             },
             callback: function (key, opt) {
                 treeOperationObj.sortCode(ledgerSpread.getActiveSheet());
             },
-            visible: function (key, opt) {
+            visible: function (key, opt) {;
                 return !readOnly;
             }
         };
@@ -1392,8 +1436,9 @@ $(document).ready(function() {
                         hint: '0号台账',
                         url: '/template/导入分项清单EXCEL格式.xls',
                     },
-                    callback: function (sheet) {
-                        postDataCompress(window.location.pathname + '/upload-excel/tz', sheet, function (result) {
+                    filter: true,
+                    callback: function (sheet, filter) {
+                        postDataCompress(window.location.pathname + '/upload-excel/tz', {sheet, filter}, function (result) {
                             ledgerTree.loadDatas(result.bills);
                             treeCalc.calculateAll(ledgerTree);
                             SpreadJsObj.loadSheetData(ledgerSpread.getActiveSheet(), 'tree', ledgerTree);

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

@@ -55,6 +55,7 @@ $(document).ready(() => {
                 n.unitTree.loadDatas(n.unitTreeData);
             }
         }
+        console.log(xmjTree);
         SpreadJsObj.loadSheetData(xmjSheet, SpreadJsObj.DataType.Tree, xmjTree);
         unitTreeObj.loadCurUnitData();
     });
@@ -234,4 +235,4 @@ $(document).ready(() => {
 
         SpreadExcelObj.exportSimpleXlsxSheet(setting, data, $('.sidebar-title').attr('data-original-title') + "-部位台账.xlsx");
     });
-});
+});

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

@@ -47,7 +47,7 @@ $(document).ready(() => {
             {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'deal_bills_tp', hAlign: 2, width: 80, type: 'Number'},
             {title: '台账|数量', colSpan: '2|1', rowSpan: '1|1', field: 'quantity', hAlign: 2, width: 80, type: 'Number'},
             {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'total_price', hAlign: 2, width: 80, type: 'Number'},
-            {title: '签约-台账|数量', colSpan: '2|1', rowSpan: '1|1', field: 'compare_qty', hAlign: 2, width: 80, type: 'Number'},
+            {title: '台账-签约|数量', colSpan: '2|1', rowSpan: '1|1', field: 'compare_qty', hAlign: 2, width: 80, type: 'Number'},
             {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'compare_tp', hAlign: 2, width: 80, type: 'Number'},
         ],
         emptyRows: 0,
@@ -168,15 +168,15 @@ $(document).ready(() => {
         gclGatherData = gclGatherModel.gatherGclData();
         gclGatherModel.checkDiffer(gclGatherData);
         for (const gcl of gclGatherData) {
-            gcl.compare_qty = ZhCalc.sub(gcl.deal_bills_qty, gcl.quantity);
-            gcl.compare_tp = ZhCalc.sub(gcl.deal_bills_tp, gcl.total_price);
+            gcl.compare_qty = ZhCalc.sub(gcl.quantity, gcl.deal_bills_qty);
+            gcl.compare_tp = ZhCalc.sub(gcl.total_price, gcl.deal_bills_tp);
             gcl.compare_differ = !checkZero(gcl.compare_qty) || !checkZero(gcl.compare_tp);
         }
         SpreadJsObj.loadSheetData(gclSheet, SpreadJsObj.DataType.Data, gclGatherData);
         checkCompareData();
         loadLeafXmjData(0);
 
-        const chapterData = gclGatherModel.gatherChapterData(chapter, ['total_price']);
+        const chapterData = gclGatherModel.gatherChapterData(chapter, ['total_price'], filter);
         for (const c of chapterData) {
             c.compare_tp = ZhCalc.sub(c.deal_bills_tp, c.total_price);
         }
@@ -256,7 +256,7 @@ $(document).ready(() => {
                 {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'deal_bills_tp', hAlign: 2, width: 80, type: 'Number'},
                 {title: '台账|数量', colSpan: '2|1', rowSpan: '1|1', field: 'quantity', hAlign: 2, width: 80, type: 'Number'},
                 {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'total_price', hAlign: 2, width: 80, type: 'Number'},
-                {title: '签约-台账|数量', colSpan: '2|1', rowSpan: '1|1', field: 'compare_qty', hAlign: 2, width: 80, type: 'Number'},
+                {title: '台账-签约|数量', colSpan: '2|1', rowSpan: '1|1', field: 'compare_qty', hAlign: 2, width: 80, type: 'Number'},
                 {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'compare_tp', hAlign: 2, width: 80, type: 'Number'},
                 {title: '单位工程', colSpan: '1', rowSpan: '2', field: 'dwgc', hAlign: 0, width: 80, formatter: '@'},
                 {title: '分部工程', colSpan: '1', rowSpan: '2', field: 'fbgc', hAlign: 0, width: 80, formatter: '@'},

+ 54 - 5
app/public/js/revise.js

@@ -690,21 +690,27 @@ $(document).ready(() => {
             if (!sheet.zh_setting) return;
             const sel = sheet.getSelections()[0], datas = [];
             for (let iRow = sel.row; iRow < sel.row + sel.rowCount; iRow++) {
+                let bDel = false;
                 const node = sheet.zh_tree.nodes[iRow];
-                if (sheet.zh_tree.checkNodeUsed(node, pos)) {
-                    toastr.warning('"' + node.code + node.b_code + ' ' + node.name +'"已计量,请勿修改');
-                    return;
-                }
                 const data = sheet.zh_tree.getNodeKeyData(node);
                 for (let iCol = sel.col; iCol < sel.col + sel.colCount; iCol++) {
                     const col = sheet.zh_setting.cols[iCol];
+                    const style = sheet.getStyle(iRow, iCol);
+                    if (style.locked || (['dgn_qty1', 'dgn_qty2'].indexOf(col.field) >= 0 && node.b_code)) continue;
+
+                    if (['dgn_qty1', 'dgn_qty2'].indexOf(col.field) < 0 && sheet.zh_tree.checkNodeUsed(node, pos)) {
+                        toastr.warning('"' + (node.code || '') + (node.b_code || '') + ' ' + node.name +'"已计量,请勿修改');
+                        return;
+                    }
+
                     data[col.field] = null;
                     const exprInfo = getExprInfo(col.field);
                     if (exprInfo) {
                         data[exprInfo.expr] = '';
                     }
+                    bDel = true;
                 }
-                datas.push(data);
+                if (bDel) datas.push(data);
             }
             if (datas.length > 0) {
                 postData(window.location.pathname + '/update', {postType: 'update', postData: datas}, function (result) {
@@ -1069,6 +1075,49 @@ $(document).ready(() => {
             return select && select.level <= 1;
         }
     };
+    billsContextMenuOptions.items.copyBlockXmj = {
+        name: '复制整块(只复制项目节)',
+        icon: 'fa-files-o',
+        callback: function (key, opt) {
+            const copyBlockList = [];
+            const sheet = billsSheet;
+            const sel = sheet.getSelections()[0];
+            let iRow = sel.row;
+            const pid = sheet.zh_tree.nodes[iRow].ledger_pid;
+            while (iRow < sel.row + sel.rowCount) {
+                const node = sheet.zh_tree.nodes[iRow];
+                if (node.ledger_pid !== pid) {
+                    toastr.error('仅可同时选中同层节点');
+                    return;
+                }
+                const posterity = sheet.zh_tree.getPosterity(node);
+                iRow += posterity.length + 1;
+                const copyPosterity = posterity.filter(x => { return !x.b_code; });
+                copyPosterity.unshift(node);
+                const copyData = sheet.zh_tree.getDefaultData(copyPosterity);
+                for (const p of copyData) {
+                    const children = copyData.filter(y => {return y.ledger_pid === p.ledger_id}) || [];
+                    p.is_leaf = children.length === 0;
+                }
+                copyBlockList.push(copyData);
+            }
+            setLocalCache(copyBlockTag, JSON.stringify({ block: copyBlockList }));
+        },
+        visible: function (key, opt) {
+            const sheet = billsSheet;
+            const selection = sheet.getSelections();
+            const row = selection[0].row;
+            const select = billsTree.nodes[row];
+            return select;
+        },
+        disabled: function (key, opt) {
+            const sheet = billsSheet;
+            const selection = sheet.getSelections();
+            const row = selection[0].row;
+            const select = billsTree.nodes[row];
+            return select && select.level <= 1;
+        }
+    };
     if (!readOnly) {
         billsContextMenuOptions.items.pasteBlock = {
             name: '粘贴整块',

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

@@ -165,7 +165,7 @@ $(document).ready(() => {
             posFields: ['quantity'],
             chapterFields: ['total_price'],
         };
-        gclCompareModel.init(gclData, chapter);
+        gclCompareModel.init(gclData, chapter, filter);
         setting.prefix = 'new_';
         gclCompareModel.gatherLedgerData(data.reviseBills, data.revisePos, setting);
         setting.prefix = 'org_';

+ 92 - 0
app/public/js/schedule_ledger.js

@@ -0,0 +1,92 @@
+/**
+ * 进度台账相关js
+ *
+ * @author Ellisran
+ * @date 2020/11/6
+ * @version
+ */
+function getTenderId() {
+    return window.location.pathname.split('/')[2];
+}
+$(function () {
+    autoFlashHeight();
+    // 初始化台账
+    const ledgerSpread = SpreadJsObj.createNewSpread($('#ledger-spread')[0]);
+    const treeSetting = {
+        id: 'ledger_id',
+        pid: 'ledger_pid',
+        order: 'order',
+        level: 'level',
+        rootId: -1,
+        fullPath: 'full_path',
+        calcFields: ['total_price']
+        //treeCacheKey: 'ledger_bills_fold' + '_' + getTenderId(),
+        // markFoldKey: 'bills-fold',
+        // markFoldSubKey: window.location.pathname.split('/')[2],
+    };
+    treeSetting.calcFun = function (node) {
+        node.dgn_price = ZhCalc.round(ZhCalc.div(node.total_price, node.dgn_qty1), 2);
+    };
+    const ledgerTree = createNewPathTree('base', treeSetting);
+
+    const ledgerSpreadSetting = {
+        cols: [
+            {title: '', colSpan: '1', rowSpan: '2', field: 'is_select', hAlign: 1, width: 40, formatter: '@', cellType: 'checkbox'},
+            {title: '编号', colSpan: '1', rowSpan: '2', field: 'code', hAlign: 0, width: 185, formatter: '@', readOnly: true, cellType: 'tree'},
+            {title: '名称', colSpan: '1', rowSpan: '2', field: 'name', hAlign: 0, width: 205, formatter: '@', readOnly: true},
+            {title: '单位', colSpan: '1', rowSpan: '2', field: 'unit', hAlign: 1, width: 100, formatter: '@', readOnly: true},
+            {title: '经济指标', colSpan: '1', rowSpan: '2', field: 'dgn_price', hAlign: 2, width: 100, type: 'Number', readOnly: true},
+            {title: '工程量', colSpan: '1', rowSpan: '2', field: 'dgn_qty1', hAlign: 2, width: 100, type: 'Number', readOnly: true},
+            {title: '金额', colSpan: '1', rowSpan: '2', field: 'total_price', hAlign: 2, width: 100, type: 'Number', readOnly: true},
+        ],
+        emptyRows: 0,
+        headRows: 1,
+        headRowHeight: [25, 25],
+        defaultRowHeight: 21,
+        headerFont: '12px 微软雅黑',
+        font: '12px 微软雅黑',
+        readOnly: true,
+        localCache: {
+            key: 'ledger-bills',
+            colWidth: true,
+        }
+    };
+
+    sjsSettingObj.setFxTreeStyle(ledgerSpreadSetting, sjsSettingObj.FxTreeStyle.jz);
+    if (thousandth) sjsSettingObj.setTpThousandthFormat(ledgerSpreadSetting);
+    SpreadJsObj.initSheet(ledgerSpread.getActiveSheet(), ledgerSpreadSetting);
+    SpreadJsObj.selChangedRefreshBackColor(ledgerSpread.getActiveSheet());
+
+    postData(window.location.pathname + '/load', {}, function (data) {
+        ledgerTree.loadDatas(data);
+        treeCalc.calculateAll(ledgerTree);
+        SpreadJsObj.loadSheetData(ledgerSpread.getActiveSheet(), SpreadJsObj.DataType.Tree, ledgerTree);
+    }, null, true);
+
+    // // 显示层次
+    // (function (select, sheet) {
+    //     $(select).click(function () {
+    //         const tag = $(this).attr('tag');
+    //         const tree = sheet.zh_tree;
+    //         if (!tree) return;
+    //         switch (tag) {
+    //             case "1":
+    //             case "2":
+    //             case "3":
+    //             case "4":
+    //             case "5":
+    //                 tree.expandByLevel(parseInt(tag));
+    //                 SpreadJsObj.refreshTreeRowVisible(sheet);
+    //                 break;
+    //             case "last":
+    //                 tree.expandByCustom(() => { return true; });
+    //                 SpreadJsObj.refreshTreeRowVisible(sheet);
+    //                 break;
+    //             case "leafXmj":
+    //                 tree.expandToLeafXmj();
+    //                 SpreadJsObj.refreshTreeRowVisible(sheet);
+    //                 break;
+    //         }
+    //     });
+    // })('a[name=showLevel]', ledgerSpread.getActiveSheet());
+});

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

@@ -104,7 +104,6 @@ const showSideTools = function (show) {
             const spread = SpreadJsObj.createNewSpread($('#' + resultId)[0]);
             const sheet = spread.getActiveSheet();
             SpreadJsObj.initSheet(sheet, setting.spreadSetting);
-            SpreadJsObj.forbiddenSpreadContextMenu('#' + resultId, spread);
 
             spread.getActiveSheet().bind(spreadNS.Events.CellDoubleClick, function (e, info) {
                 const sheet = info.sheet;
@@ -258,7 +257,6 @@ const showSideTools = function (show) {
             const spread = SpreadJsObj.createNewSpread($('#' + resultId)[0]);
             const sheet = spread.getActiveSheet();
             SpreadJsObj.initSheet(sheet, setting.spreadSetting);
-            SpreadJsObj.forbiddenSpreadContextMenu('#' + resultId, spread);
 
             spread.getActiveSheet().bind(spreadNS.Events.CellDoubleClick, function (e, info) {
                 const sheet = info.sheet;
@@ -505,7 +503,6 @@ const showSideTools = function (show) {
         autoFlashHeight();
         const resultSpread = SpreadJsObj.createNewSpread($('#' + resultId)[0]);
         SpreadJsObj.initSheet(resultSpread.getActiveSheet(), setting.resultSpreadSetting);
-        SpreadJsObj.forbiddenSpreadContextMenu('#' + resultId, resultSpread);
         const searchSheet = setting.searchSpread.getActiveSheet();
         let searchResult = [];
         const search = function () {

+ 36 - 6
app/public/js/shares/gcl_gather_compare.js

@@ -11,7 +11,7 @@
 
 const gclCompareModel = (function () {
     const leafXmjs = [], mergeChar = ';';
-    let gclList, gclChapter, otherChapter;
+    let gclList, gclChapter, otherChapter, chapterfilter;
     let ledgerSetting, gsTree;
 
     function gatherfields(obj, src, fields, prefix = '') {
@@ -292,29 +292,59 @@ const gclCompareModel = (function () {
         }
     }
 
+    function _checkFilter(d, filter) {
+        for (const f of filter) {
+            if (f.node_type && f.node_type === d.node_type) return true;
+            if (f.field) {
+                if (f.part && d[f.field] && d[f.field].indexOf(f.part) >= 0) return true;
+                if (f.all && d[f.all] && d[f.all] === f.all) return true;
+            }
+        }
+        return false;
+    }
+
     function _gatherChapter() {
-        for (const d of gsTree.datas) {
+        const filterPath = [];
+        const checkFilterPath = function (data) {
+            for (const fp of filterPath) {
+                if (data.full_path.indexOf(fp + '-') === 0 || data.full_path === fp) return true;
+            }
+            return false;
+        };
+
+        for (const d of gsTree.nodes) {
+            if (_checkFilter(d, chapterfilter)) {
+                filterPath.push(d.full_path);
+            }
             if (d.children && d.children.length > 0) continue;
 
             for (const c of otherChapter) {
                 if (c.cType === 41) {
                     gatherfields(c, d, ledgerSetting.chapterFields, ledgerSetting.prefix);
-                } else if (c.cType === 31 && (!d.b_code || d.b_code === '')) {
+                }
+                if (c.cType === 11 && (d.b_code)) {
                     gatherfields(c, d, ledgerSetting.chapterFields, ledgerSetting.prefix);
-                } else if (c.cType === 11 && (d.b_code)) {
+                }
+                if (c.cType === 31 && (!d.b_code || d.b_code === '')) {
                     gatherfields(c, d, ledgerSetting.chapterFields, ledgerSetting.prefix);
                 }
             }
+            for (const fp of filterPath) {
+                if (d.full_path.indexOf(fp + '-') === 0 || d.full_path === fp) continue;
+            }
             if (d.b_code) {
-                const c = _getGclChapter(gclChapter, d);
+                const c = checkFilterPath(d)
+                    ? gclChapter.find(x => { return x.cType === 21})
+                    : _getGclChapter(gclChapter, d);
                 gatherfields(c, d, ledgerSetting.chapterFields, ledgerSetting.prefix);
             }
         }
     }
 
-    function init (gclData, chapter) {
+    function init (gclData, chapter, filter) {
         gclList = gclData;
         [gclChapter, otherChapter] = _getCalcChapter(chapter);
+        chapterfilter = filter || [];
     }
 
     /**

+ 6 - 3
app/public/js/shares/tenders2tree.js

@@ -81,12 +81,15 @@ const Tender2Tree = (function () {
 
         for (const t of tenders) {
             const parent = (t.category && levelCategory.length > 0) ? loadCategoryTreeNode(t, levelCategory) : null;
-            tenderTree.addNode({
+            const node = {
                 tid: t.id,
                 name: t.name,
                 phase: t.lastStage ? '第' + t.lastStage.order + '期' : '台账',
-                status: t.lastStage ? stageAuditConst.statusString[t.lastStage.status] : ledgerAuditConst.statusString[t.ledger_status]
-            }, parent);
+            };
+            if (ledgerAuditConst && stageAuditConst) {
+                node.status = t.lastStage ? stageAuditConst.statusString[t.lastStage.status] : ledgerAuditConst.statusString[t.ledger_status];
+            }
+            tenderTree.addNode(node, parent);
         }
         tenderTree.sortTreeNode(false);
         return tenderTree;

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

@@ -42,6 +42,7 @@ function initTenderTree () {
                 children: [],
                 level: i ? i : category.level,
                 sort_id: ++parentId,
+                sort: cateValue.sort,
             };
             array.push(cate);
         }
@@ -93,6 +94,7 @@ function initTenderTree () {
             tenderTree.push(t);
         }
     }
+    sortTenderTree();
 }
 function recursiveGetTenderNodeHtml (node, arr, pid, this_code, this_status, aidList = []) {
     const html = [];
@@ -120,6 +122,7 @@ function recursiveGetTenderNodeHtml (node, arr, pid, this_code, this_status, aid
             auditList = aidList;
             tender_status = this_status;
         } else {
+            console.log(node);
             html.push(sp_status_list[node.shenpiInfo[this_code]].name);
             auditList = node.shenpiauditList[this_code];
             tender_status = node.shenpiInfo[this_code];

+ 48 - 9
app/public/js/spreadjs_rela/spreadjs_zh.js

@@ -637,7 +637,7 @@ const SpreadJsObj = {
         }
         if (colSetting.cellType === 'unit') {
             if (!sheet.extendCellType.unit) {
-                sheet.extendCellType.unit = this.CellType.getUnitCellType();
+                sheet.extendCellType.unit = colSetting.comboItems ? this.CellType.getUnitCellType(colSetting.comboItems) : this.CellType.getUnitCellType();
                 SpreadJsObj._addActivePaintEvents(sheet, sheet.extendCellType.unit);
             }
             sheet.getRange(-1, col, -1, 1).cellType(sheet.extendCellType.unit);
@@ -657,6 +657,13 @@ const SpreadJsObj = {
             }
             sheet.getRange(-1, col, -1, 1).cellType(sheet.extendCellType.datepicker);
         }
+        if (colSetting.cellType === 'mouseTouch') {
+            if (!sheet.extendCellType.mouseTouch) {
+                sheet.extendCellType.mouseTouch = this.CellType.getMouseTouchCellType();
+                SpreadJsObj._addActivePaintEvents(sheet, sheet.extendCellType.mouseTouch);
+            }
+            sheet.getRange(-1, col, -1, 1).cellType(sheet.extendCellType.mouseTouch);
+        }
         if (colSetting.formatter) {
             sheet.getRange(-1, col, -1, 1).formatter(colSetting.formatter);
             if(colSetting.type === 'Number') {
@@ -796,12 +803,12 @@ const SpreadJsObj = {
 
         this.beginMassOperation(sheet);
         try {
-            for (const iCol of cols) {
+            for (const col of cols) {
                 // 清空原单元格数据
-                sheet.clear(-1, iCol, -1, 1, spreadNS.SheetArea.viewport, spreadNS.StorageType.data);
+                sheet.clear(-1, col, -1, 1, spreadNS.SheetArea.viewport, spreadNS.StorageType.data);
 
                 for (const [iRow, data] of sortData.entries()) {
-                    this._loadCellData(sheet, data, iRow, iCol);
+                    this._loadCellData(sheet, data, iRow, col);
                 }
             }
             this.endMassOperation(sheet);
@@ -2079,12 +2086,12 @@ const SpreadJsObj = {
          * 获取 单位的CellType
          * @returns {GC.Spread.Sheets.CellTypes.ComboBox}
          */
-        getUnitCellType: function () {
+        getUnitCellType: function (items = ['m', 'km', 'm2', 'm3', 'dm3', 'kg', 't', 'm3·km',
+            '总额', '月' ,'项', '处' ,'个', '根', '棵', '块', '台', '系统', '延米', '每一试桩',
+            '桥长米', '公路公里', '株', '组', '座', '元', '工日', '套', '台班', '艘班', '亩', '片',
+            'm/处', 'm/道', 'm/座', 'm2/m', 'm3/m', 'm3/处', '根/米', 'm3/m2']) {
             let combo = this.getActiveComboCellType();
-            combo.itemHeight(10).items(['m', 'km', 'm2', 'm3', 'dm3', 'kg', 't', 'm3·km',
-                '总额', '月' ,'项', '处' ,'个', '根', '棵', '块', '台', '系统', '延米', '每一试桩',
-                '桥长米', '公路公里', '株', '组', '座', '元', '工日', '套', '台班', '艘班', '亩', '片',
-                'm/处', 'm/道', 'm/座', 'm2/m', 'm3/m', 'm3/处', '根/米', 'm3/m2']);
+            combo.itemHeight(10).items(items);
             return combo;
         },
         /**
@@ -2143,6 +2150,38 @@ const SpreadJsObj = {
                 }
             };
             return new DatePickerCellType();
+        },
+        /**
+         * 获取 鼠标移入变手势 CellType
+         * @returns {DatePickerCellType}
+         */
+        getMouseTouchCellType: function () {
+            const mouseTouchCellType = function () {};
+            mouseTouchCellType.prototype = new spreadNS.CellTypes.Base();
+            mouseTouchCellType.prototype.getHitInfo = function (x, y, cellStyle, cellRect, context) {
+                var xm = cellRect.x + cellRect.width / 2,
+                    ym = cellRect.y + cellRect.height / 2,
+                    size = 10;
+                var info = { x: x, y: y, row: context.row, col: context.col, cellRect: cellRect, sheetArea: context.sheetArea };
+                if (xm - size <= x && x <= xm + size && ym - size <= y && y <= ym + size) {
+                    info.isReservedLocation = true;
+                }
+                return info;
+            };
+            mouseTouchCellType.prototype.processMouseMove = function (hitInfo) {
+                var sheet = hitInfo.sheet;
+                var div = sheet.getParent().getHost();
+                var canvasId = div.id + "vp_vp";
+                var canvas = $("#"+canvasId)[0];
+                if (sheet && hitInfo.isReservedLocation) {
+                    canvas.style.cursor='pointer';
+                    return true;
+                }else{
+                    canvas.style.cursor='default';
+                }
+                return false;
+            };
+            return new mouseTouchCellType();
         }
     },
 

+ 61 - 2
app/public/js/stage.js

@@ -185,9 +185,9 @@ $(document).ready(() => {
     // 台账树结构计算相关设置
     stageTreeSetting.updateFields = ['contract_qty', 'contract_tp', 'qc_qty', 'qc_tp', 'postil', 'used', 'contract_expr'];
     stageTreeSetting.calcFields = ['deal_tp', 'total_price', 'contract_tp', 'qc_tp', 'gather_tp',
-        'pre_contract_tp', 'pre_qc_tp', 'pre_gather_tp', 'end_contract_tp', 'end_qc_tp', 'end_gather_tp'];
+        'pre_contract_tp', 'pre_qc_tp', 'pre_gather_tp', 'end_contract_tp', 'end_qc_tp', 'end_gather_tp', 'end_correct_tp'];
     stageTreeSetting.calcFun = function (node) {
-        if (node.children && node.children.length === 0) {
+        if (!node.children || node.children.length === 0) {
             node.pre_gather_qty = ZhCalc.add(node.pre_contract_qty, node.pre_qc_qty);
             node.gather_qty = ZhCalc.add(node.contract_qty, node.qc_qty);
             node.end_contract_qty = ZhCalc.add(node.pre_contract_qty, node.contract_qty);
@@ -200,7 +200,15 @@ $(document).ready(() => {
         node.end_qc_tp = ZhCalc.add(node.pre_qc_tp, node.qc_tp);
         node.end_gather_tp = ZhCalc.add(node.pre_gather_tp, node.gather_tp);
         node.end_final_tp = ZhCalc.add(node.end_qc_tp, node.total_price);
+        if (!node.children || node.children.length === 0) {
+            if (node.end_contract_qty) {
+                node.end_correct_tp = ZhCalc.add(node.end_qc_tp, ZhCalc.mul(node.end_contract_qty, node.unit_price, tenderInfo.decimal.tp));
+            } else {
+                node.end_correct_tp = node.end_gather;
+            }
+        }
         node.end_gather_percent = ZhCalc.mul(ZhCalc.div(node.end_gather_tp, node.end_final_tp), 100, 2);
+        node.end_correct_percent = ZhCalc.mul(ZhCalc.div(node.end_correct_tp, node.end_final_tp), 100, 2);
         node.final_dgn_price = ZhCalc.round(ZhCalc.div(node.end_gather_tp, ZhCalc.add(node.deal_dgn_qty1, node.c_dgn_qty1)), tenderInfo.decimal.up);
     };
     const stageTree = createNewPathTree('stage', stageTreeSetting);
@@ -1189,9 +1197,11 @@ $(document).ready(() => {
         });
     }
     stageTreeSpreadObj.loadExprToInput(slSpread.getActiveSheet());
+    //let check_correct = false;
     $.contextMenu({
         selector: '#stage-ledger',
         build: function ($trigger, e) {
+            e.data.items.correct_percent.selected = check_correct;
             const target = SpreadJsObj.safeRightClickSelection($trigger, e, slSpread);
             return target.hitTestType === spreadNS.SheetArea.viewport || target.hitTestType === spreadNS.SheetArea.rowHeader;
         },
@@ -1228,6 +1238,22 @@ $(document).ready(() => {
                     }
                 },
             },
+            // 'correct_percent': {
+            //     name: '使用数量矫正完成率',
+            //     type: 'checkbox',
+            //     events: {
+            //         change: function (e) {
+            //             check_correct = e.data.commands.correct_percent.$input[0].checked;
+            //             const sheet = slSpread.getActiveSheet();
+            //             const col = sheet.zh_setting.cols.find(x => {
+            //                 return x.field === 'end_gather_percent' || x.field === 'end_correct_percent'
+            //             });
+            //             col.field = check_correct ? 'end_correct_percent' : 'end_gather_percent';
+            //             SpreadJsObj.reLoadColsData(sheet, [col]);
+            //             e.data.$menu.hide();
+            //         }
+            //     },
+            // }
         }
     });
 
@@ -3019,6 +3045,32 @@ $(document).ready(() => {
             setting.reloadObj.click(function() {
                 self.reloadChangeData();
             });
+            $.contextMenu({
+                selector: '#' + setting.changeBillsObj.attr('id'),
+                build: function ($trigger, e) {
+                    const target = SpreadJsObj.safeRightClickSelection($trigger, e, self.changeBillsSpread);
+                    return target.hitTestType === spreadNS.SheetArea.viewport || target.hitTestType === spreadNS.SheetArea.rowHeader;
+                },
+                items: {
+                    'locateZjjl': {
+                        name: '定位至台账',
+                        icon: 'fa-sign-in',
+                        callback: function (key, opt) {
+                            const changeBills = SpreadJsObj.getSelectObject(self.changeBillsSheet);
+                            if (changeBills.gcl_id) {
+                                const node = stageTree.nodes.find(x => {return x.id === changeBills.gcl_id});
+                                SpreadJsObj.locateTreeNode(slSpread.getActiveSheet(), node.ledger_id);
+                            } else {
+                                toastr.warning('该清单无法定位');
+                            }
+                        },
+                        disabled: function (key, opt) {
+                            const changeBills = SpreadJsObj.getSelectObject(self.changeBillsSheet);
+                            return !changeBills;
+                        }
+                    },
+                }
+            });
         }
         loadChangeDetailData() {
             const change = SpreadJsObj.getSelectObject(this.changeSheet);
@@ -3039,6 +3091,7 @@ $(document).ready(() => {
                 SpreadJsObj.loadSheetData(self.changeSheet, SpreadJsObj.DataType.Data, self.changes);
                 self.changeSheet.setSelection(0, 0, 1, 1);
                 if (self.changes.length > 0) self.analyzeChange(result.changes[0]);
+                self.curChangeId = result.changes[0].id;
                 self.loadChangeDetailData();
             });
         }
@@ -3712,5 +3765,11 @@ $(document).ready(() => {
             return;
         }
         stageTreeSpreadObj.measureByBatch(posName, ratio, apply2sibling);
+    });
+    $('#correct_percent').click(function () {
+        const sheet = slSpread.getActiveSheet();
+        const col = sheet.zh_setting.cols.find(x => {return x.field === 'end_gather_percent' || x.field === 'end_correct_percent'});
+        col.field = this.checked ? 'end_correct_percent' : 'end_gather_percent';
+        SpreadJsObj.reLoadColsData(sheet, [sheet.zh_setting.cols.indexOf(col)]);
     })
 });

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

@@ -126,7 +126,7 @@ $(document).ready(function () {
         SpreadJsObj.loadSheetData(gclSpread.getActiveSheet(), SpreadJsObj.DataType.Data, gclGatherData);
         loadLeafXmjData(0);
         // 章节合计
-        const chapterData = gclGatherModel.gatherChapterData(chapter, ['total_price', 'contract_tp', 'qc_tp', 'pre_contract_tp', 'pre_qc_tp']);
+        const chapterData = gclGatherModel.gatherChapterData(chapter, ['total_price', 'contract_tp', 'qc_tp', 'pre_contract_tp', 'pre_qc_tp'], filter);
         for (const c of chapterData) {
             c.gather_tp = ZhCalc.add(c.contract_tp, c.qc_tp);
             c.end_contract_tp = ZhCalc.add(c.contract_tp, c.pre_contract_tp);

+ 181 - 0
app/public/js/tender_copy_setting.js

@@ -0,0 +1,181 @@
+'use strict';
+
+/**
+ *
+ * @author LanJianRong
+ * @date 2020/11/13
+ * @version
+ */
+const tenderTree = [];
+let parentId = 0;
+// 查询方法
+function findNode (key, value, arr) {
+    for (const a of arr) {
+        if (a[key] && a[key] === value) {
+            return a;
+        }
+    }
+}
+// 初始化TenderTree数据
+function initTenderTree () {
+    const levelCategory = category.filter(function (c) {
+        return c.level && c.level > 0;
+    });
+    function findCategoryNode(cid, value, array) {
+        for (const a of array) {
+            if (a.cid === cid && a.vid === value) {
+                return a;
+            }
+        }
+    }
+    function getCategoryNode(category, value, parent, i = null) {
+        const array = parent ?  parent.children : tenderTree;
+        let cate = findCategoryNode(category.id, value, array);
+        if (!cate) {
+            const cateValue = findNode('id', value, category.value);
+            if (!cateValue) return null;
+            cate = {
+                cid: category.id,
+                vid: value,
+                name: cateValue.value,
+                children: [],
+                level: i ? i : category.level,
+                sort_id: ++parentId,
+                sort: cateValue.sort,
+            };
+            array.push(cate);
+        }
+        return cate;
+    }
+    function loadTenderCategory (tender) {
+        let tenderCategory = null;
+        for (const [index,lc] of levelCategory.entries()) {
+            const tenderCate = findNode('cid', lc.id, tender.category);
+            if (tenderCate) {
+                tenderCategory = getCategoryNode(lc, tenderCate.value, tenderCategory);
+            } else {
+                if (index === 0 && tender.category) {
+                    for (const [i,c] of tender.category.entries()) {
+                        const cate = findNode('id', c.cid, category);
+                        tenderCategory = getCategoryNode(cate, c.value, tenderCategory, i+1);
+                    }
+                }
+                return tenderCategory;
+            }
+        }
+        return tenderCategory;
+    }
+    function calculateTender(tender) {
+        if (tender.lastStage) {
+            tender.gather_tp = ZhCalc.add(tender.lastStage.contract_tp, tender.lastStage.qc_tp);
+            tender.end_contract_tp = ZhCalc.add(tender.lastStage.pre_contract_tp, tender.lastStage.contract_tp);
+            tender.end_qc_tp = ZhCalc.add(tender.lastStage.pre_qc_tp, tender.lastStage.qc_tp);
+            tender.end_gather_tp = ZhCalc.add(tender.end_contract_tp, tender.end_qc_tp);
+            tender.pre_gather_tp = ZhCalc.add(tender.lastStage.pre_contract_tp, tender.lastStage.pre_qc_tp);
+            tender.yf_tp = ZhCalc.add(tender.lastStage.yf_tp);
+            tender.end_yf_tp = ZhCalc.add(tender.lastStage.pre_yf_tp, tender.yf_tp);
+        }
+    }
+    tenderTree.splice(0, tenderTree.length);
+    for (const t of tenders) {
+        calculateTender(t);
+        t.valid = true;
+        delete t.level;
+        if (t.category && levelCategory.length > 0) {
+            const parent = loadTenderCategory(t);
+            if (parent) {
+                t.level = parent.level + 1;
+                parent.children.push(t);
+            } else {
+                tenderTree.push(t);
+            }
+        } else {
+            tenderTree.push(t);
+        }
+    }
+}
+function recursiveGetTenderNodeHtml (node, arr, pid) {
+    if (node.id === tender.id) return ''
+    const html = [];
+    html.push('<tr pid="' + pid + '">');
+    // 名称
+    html.push('<td class="in-' + node.level + '">');
+    if (node.cid) {
+        html.push('<i class="fa fa-folder-o"></i> ', node.name);
+    } else {
+        html.push('<span class="text-muted mr-2">');
+        html.push(arr.indexOf(node) === arr.length - 1 ? '└' : '├');
+        html.push('</span>');
+        //html.push('<a href="/tender/' + node.id + '">', node[c.field], '</a>');
+        html.push('<a href="javascript: void(0)" id="' + node.id + '">', node.name, '</a>');
+    }
+    html.push('</td>');
+    // 创建人
+    html.push('<td>');
+    if (!node.cid) {
+        // html.push('<input data-tid="'+ node.id +'" type="radio"> '+ (node.copy_id === tender.copy_id ? 'checked' : '') + '/>');
+        html.push(`<input data-tid=${node.id} type="radio" ${node.id === tender.copy_id ? 'checked' : ''}>`);
+    }
+    html.push('</td>');
+    html.push('</tr>');
+    if (node.children) {
+        for (const c of node.children) {
+            html.push(recursiveGetTenderNodeHtml(c, node.children, node.sort_id));
+        }
+    }
+    return html.join('');
+}
+// 根据TenderTree数据获取Html代码
+function getTenderTreeHtml () {
+    if (tenderTree.length > 0) {
+        const html = [];
+        html.push('<table class="table table-hover table-bordered">');
+        html.push('<thead>', '<tr>');
+        html.push('<th>名称</th>');
+        html.push('<th width="40">选择</th>');
+        html.push('</tr>', '</thead>');
+        parentId = 0;
+        for (const t of tenderTree) {
+            html.push(recursiveGetTenderNodeHtml(t, tenderTree, ''));
+        }
+        html.push('</table>');
+        return html.join('');
+    } else {
+        return EmptyTenderHtml.join('');
+    }
+}
+$(document).ready(function () {
+    initTenderTree()
+    $('#copyBtn').click(() => {
+        const html = getTenderTreeHtml();
+        $('#copyModalContent').html(html);
+        $('#bd-set-8').modal('show');
+    });
+
+    $('#copyModalContent').on('click', 'input[type="radio"]', function() {
+        $('#copyModalContent tbody').children().each(function () {
+            $(this).find('input:radio').prop("checked", false);
+        })
+        $(this).prop("checked", true);
+    });
+
+    $('#setting-custom').on('click', '.custom-checkbox', function () {
+        const isChecked = $(this).find('input').prop("checked");
+        $(this).find('input').prop("checked", !isChecked);
+    })
+    $('#copy_comfirm_btn').click(function() {
+        const type = []
+        $('#setting-custom').find('input:checked').each(function() {
+            type.push($(this).data('type'))
+        })
+        if (!type.length) {
+            return toastr.error('请勾选需要拷贝的属性')
+        }
+        const id = $('#copyModalContent').find('input:checked').data('tid');
+        if (id) {
+            postData(window.location.pathname + '/copy-setting', { id, type }, function() {
+                window.location.reload()
+            })
+        }
+    });
+})

+ 6 - 3
app/public/js/tender_list.js

@@ -217,6 +217,7 @@ function initTenderTree () {
                 children: [],
                 level: i ? i : category.level,
                 sort_id: ++parentId,
+                sort: cateValue.sort,
             };
             array.push(cate);
         }
@@ -268,7 +269,9 @@ function initTenderTree () {
             tenderTree.push(t);
         }
     }
+    sortTenderTree();
 }
+
 function recursiveGetTenderNodeHtml (node, arr, pid) {
     const html = [];
     html.push('<tr pid="' + pid + '">');
@@ -363,10 +366,10 @@ $(document).ready(() => {
     $('.modal-body', '#add-bd').append(getCategoryHtml());
     // 初始化标段树结构
     tenderListOrder.reOrderTenders();
-    initTenderTree();
-    $('.c-body').html(getTenderTreeHtml());
+    // initTenderTree();
+    // $('.c-body').html(getTenderTreeHtml());
     bindTenderUrl();
-    localHideList();
+    // localHideList();
 
     // 分类
     $('#cate-set').on('show.bs.modal', function () {

+ 4 - 0
app/public/js/tender_list_info.js

@@ -200,6 +200,7 @@ function calculateParent(node) {
         node.pre_gather_tp = 0;
         node.yf_tp = 0;
         node.end_yf_tp = 0;
+        node.advance_tp = 0;
         for (const c of node.children) {
             calculateParent(c);
             node.total_price = ZhCalc.add(node.total_price, c.total_price);
@@ -210,6 +211,7 @@ function calculateParent(node) {
             node.pre_gather_tp = ZhCalc.add(node.pre_gather_tp, c.pre_gather_tp);
             node.yf_tp = ZhCalc.add(node.yf_tp, c.yf_tp);
             node.end_yf_tp = ZhCalc.add(node.end_yf_tp, c.end_yf_tp);
+            node.advance_tp = ZhCalc.add(node.advance_tp, c.advance_tp);
         }
     }
 }
@@ -237,6 +239,7 @@ function initTenderTree () {
                 children: [],
                 level: i ? i : category.level,
                 sort_id: ++parentId,
+                sort: cateValue.sort,
             };
             array.push(cate);
         }
@@ -288,6 +291,7 @@ function initTenderTree () {
             tenderTree.push(t);
         }
     }
+    sortTenderTree();
     for (const t of tenderTree) {
         calculateParent(t);
     }

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

@@ -215,6 +215,7 @@ function initTenderTree () {
                 children: [],
                 level: i ? i : category.level,
                 sort_id: ++parentId,
+                sort: cateValue.sort,
             };
             array.push(cate);
         }
@@ -255,6 +256,7 @@ function initTenderTree () {
             tenderTree.push(t);
         }
     }
+    sortTenderTree();
 }
 function recursiveGetTenderNodeHtml (node, arr, pid) {
     const html = [];

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

@@ -234,6 +234,7 @@ function initTenderTree () {
                 children: [],
                 level: i ? i : category.level,
                 sort_id: ++parentId,
+                sort: cateValue.sort,
             };
             array.push(cate);
         }
@@ -284,6 +285,7 @@ function initTenderTree () {
             tenderTree.push(t);
         }
     }
+    sortTenderTree();
     for (const t of tenderTree) {
         calculateParent(t);
     }

+ 24 - 1
app/public/js/tender_showhide.js

@@ -29,7 +29,30 @@ function removeValueToCate(cate) {
     }
     return newCate;
 }
-
+// 根据标段类别设置排序
+function sortTenderTree(teTree = tenderTree) {
+    for (const tender of teTree) {
+        if (tender.sort) {
+            // 当前层排序
+            teTree.sort(function (a, b) {
+                if (a.sort && b.sort) {
+                    return a.sort - b.sort;
+                } else if (a.sort && !b.sort) {
+                    return -1;
+                } else if (b.sort && !a.sort) {
+                    return 1;
+                } else {
+                    return 0;
+                }
+            });
+        }
+    }
+    for (const tender of teTree) {
+        if (tender.children) {
+            sortTenderTree(tender.children);
+        }
+    }
+}
 function localHideList(wap = false) {
     const pro_cate = getLocalCache('pro_'+ pid + '_category_list');
     const cate = JSON.stringify(removeValueToCate(category));

+ 2 - 0
app/public/js/wap/list.js

@@ -84,6 +84,7 @@ function initTenderTree () {
                 children: [],
                 level: i ? i : category.level,
                 sort_id: ++parentId,
+                sort: cateValue.sort,
             };
             array.push(cate);
         }
@@ -134,6 +135,7 @@ function initTenderTree () {
             tenderTree.push(t);
         }
     }
+    sortTenderTree();
     for (const t of tenderTree) {
         calculateParent(t);
     }

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

@@ -118,8 +118,9 @@ const ZhCalc = (function () {
      * @returns {*}
      */
     function round(value, decimal) {
+        decimal = decimal ? parseInt(decimal) : 0;
         return value ? new Decimal(value).toDecimalPlaces(decimal).toNumber() : null;
     }
 
     return {add, sub, mul, div, round, isNonZero: zhBaseCalc.isNonZero}
-})();
+})();

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

@@ -261,7 +261,7 @@ let JpcJsPDFHelper = {
         }
 
         function private_drawCellText(doc, ctx, cell, fonts, controls) {
-            if (cell[JV.PROP_VALUE]) {
+            if (cell[JV.PROP_VALUE] !== undefined && cell[JV.PROP_VALUE] !== null) {
                 let values = ("" + cell[JV.PROP_VALUE]).split('|');
                 // let font = fonts[cell[JV.PROP_FONT]];
                 let font = null;

+ 24 - 29
app/public/report/js/rpt_print.js

@@ -357,7 +357,7 @@ function buildText(destRst, cell, font, control, offsetX, offsetY, adjustY, canv
         text_anchor = "start"
     ;
     let value = cell[JV.PROP_VALUE];
-    if (!(value)) {
+    if (value === undefined || value === null) {
         value = "";
     }
     let values = null;
@@ -420,36 +420,31 @@ function buildText(destRst, cell, font, control, offsetX, offsetY, adjustY, canv
             inner_build_text(textValue, area);
         } else {
             while (true) {
-                if (dftFontHeight > 6) {
-                    let lines = Math.floor((area[JV.IDX_BOTTOM] - area[JV.IDX_TOP]) / (dftFontHeight + JV.OUTPUT_OFFSET[JV.IDX_BOTTOM] + JV.OUTPUT_OFFSET[JV.IDX_TOP] + 4));
-                    lines = (lines === 0 || (control.Shrink === 'T' && control.ShrinkFirst === 'T'))?1:lines;
-                    actLines = private_splitString(textValue, (area[JV.IDX_RIGHT] - area[JV.IDX_LEFT] - JV.OUTPUT_OFFSET[JV.IDX_LEFT] - JV.OUTPUT_OFFSET[JV.IDX_RIGHT]), ctx);
-                    if (lines >= actLines.length) {
-                        let aH = dftFontHeight + JV.OUTPUT_OFFSET[JV.IDX_BOTTOM] + JV.OUTPUT_OFFSET[JV.IDX_TOP] + 4;
-                        if ((aH * actLines.length) < (area[JV.IDX_BOTTOM] - area[JV.IDX_TOP]) && (control && control.Vertical !== 'top')) {
-                            if (control.Vertical === 'bottom') {
-                                area[JV.IDX_TOP] = area[JV.IDX_BOTTOM] - (aH * actLines.length);
-                            } else {
-                                area[JV.IDX_TOP] = (area[JV.IDX_TOP] + area[JV.IDX_BOTTOM]) / 2 - (aH * actLines.length) / 2
-                                area[JV.IDX_BOTTOM] = area[JV.IDX_TOP] + (aH * actLines.length);
-                            }
-                        }
-                        let newArea = [], baseTop = area[JV.IDX_TOP];
-                        for (let ai = 0; ai < area.length; ai++) {
-                            newArea[ai] = area[ai];
-                        }
-                        for (let lIdx = 0; lIdx < actLines.length; lIdx++) {
-                            newArea[JV.IDX_TOP] = Math.round(aH * lIdx + baseTop);
-                            newArea[JV.IDX_BOTTOM] = Math.round(aH * (lIdx + 1) + baseTop);
-                            inner_build_text(actLines[lIdx], newArea);
+                let lines = Math.floor((area[JV.IDX_BOTTOM] - area[JV.IDX_TOP]) / (dftFontHeight + JV.OUTPUT_OFFSET[JV.IDX_BOTTOM] + JV.OUTPUT_OFFSET[JV.IDX_TOP] + 4));
+                lines = (lines === 0 || (control.Shrink === 'T' && control.ShrinkFirst === 'T'))?1:lines;
+                actLines = private_splitString(textValue, (area[JV.IDX_RIGHT] - area[JV.IDX_LEFT] - JV.OUTPUT_OFFSET[JV.IDX_LEFT] - JV.OUTPUT_OFFSET[JV.IDX_RIGHT]), ctx);
+                if (actLines.length > lines && dftFontHeight >= 6) {
+                    dftFontHeight--;
+                    ctx.font = ((font[JV.FONT_PROPS[JV.FONT_PROP_IDX_BOLD]] === 'T')?"bold ":"") + ((font[JV.FONT_PROPS[JV.FONT_PROP_IDX_ITALIC]] === 'T')?"italic":"") + dftFontHeight + "px " + font[JV.PROP_NAME];
+                } else {
+                    let aH = dftFontHeight + JV.OUTPUT_OFFSET[JV.IDX_BOTTOM] + JV.OUTPUT_OFFSET[JV.IDX_TOP] + 4;
+                    if ((aH * actLines.length) < (area[JV.IDX_BOTTOM] - area[JV.IDX_TOP]) && (control && control.Vertical !== 'top')) {
+                        if (control.Vertical === 'bottom') {
+                            area[JV.IDX_TOP] = area[JV.IDX_BOTTOM] - (aH * actLines.length);
+                        } else {
+                            area[JV.IDX_TOP] = (area[JV.IDX_TOP] + area[JV.IDX_BOTTOM]) / 2 - (aH * actLines.length) / 2
+                            area[JV.IDX_BOTTOM] = area[JV.IDX_TOP] + (aH * actLines.length);
                         }
-                        break;
-                    } else {
-                        dftFontHeight--;
-                        ctx.font = ((font[JV.FONT_PROPS[JV.FONT_PROP_IDX_BOLD]] === 'T')?"bold ":"") + ((font[JV.FONT_PROPS[JV.FONT_PROP_IDX_ITALIC]] === 'T')?"italic":"") + dftFontHeight + "px " + font[JV.PROP_NAME];
                     }
-                } else {
-                    inner_build_text(textValue, area);
+                    let newArea = [], baseTop = area[JV.IDX_TOP];
+                    for (let ai = 0; ai < area.length; ai++) {
+                        newArea[ai] = area[ai];
+                    }
+                    for (let lIdx = 0; lIdx < actLines.length; lIdx++) {
+                        newArea[JV.IDX_TOP] = Math.round(aH * lIdx + baseTop);
+                        newArea[JV.IDX_BOTTOM] = Math.round(aH * (lIdx + 1) + baseTop);
+                        inner_build_text(actLines[lIdx], newArea);
+                    }
                     break;
                 }
             }

+ 2 - 2
app/reports/public/stringUtil.js

@@ -142,7 +142,7 @@ module.exports = {
     leftTrim: function(str) {
         return str.replace(/(^\s*)/g,"");
     },
-    rightTrim: function(rst) {
+    rightTrim: function(str) {
         return str.replace(/(\s*$)/g,"");
     },
     replaceAll: function (targetStr, FindText, RepText) {
@@ -159,7 +159,7 @@ module.exports = {
             let capChars, unitChars;
             if (isTraditionalCap) {
                 capChars = ["零", "壹", "贰", "叁", "肆", "伍", "陆", "柒", "捌", "玖"];
-                unitChars = ["" , "拾", "佰", "仟", "", "拾", "佰", "仟", "亿", "拾", "佰", "仟", "万"];
+                unitChars = ["" , "拾", "佰", "仟", "", "拾", "佰", "仟", "亿", "拾", "佰", "仟", "万"];
             } else {
                 capChars = ["零", "一", "二", "三", "四", "五", "六", "七", "八", "九"];
                 unitChars = ["" , "十", "百", "千", "万", "十", "百", "千", "亿", "十", "百", "千", "万"];

+ 2 - 2
app/reports/rpt_component/helper/jpc_helper_field.js

@@ -82,10 +82,10 @@ const JpcFieldHelper = {
     },
     decorateValue: function(cell, controls) {
         if (controls) {
-            const val = cell[JV.PROP_VALUE];
             const showZero = controls[cell[JV.PROP_CONTROL]][JV.PROP_SHOW_ZERO];
             if (showZero && showZero === 'F') {
-                if (1.0 * (0 + val) === 0) {
+                const val = parseFloat(cell[JV.PROP_VALUE]);
+                if (val === 0) {
                     cell[JV.PROP_VALUE] = '';
                 }
             }

+ 17 - 0
app/router.js

@@ -23,6 +23,8 @@ module.exports = app => {
     const wechatAuth = app.middlewares.wechatAuth();
     // 预付款中间件
     const advanceCheck = app.middlewares.advanceCheck();
+    // 变更令中间件
+    const changeCheck = app.middlewares.changeCheck();
     // 登入登出相关
     app.get('/login', 'loginController.index');
     app.get('/login/port', api2otherCheck, 'loginController.port');
@@ -113,6 +115,7 @@ module.exports = app => {
     app.get('/tender/:id/shenpi', sessionAuth, tenderCheck, 'tenderController.shenpiSet');
     app.post('/tender/:id/shenpi/save', sessionAuth, tenderCheck, 'tenderController.saveTenderInfoShenpi');
     app.post('/tender/:id/shenpi/audit/save', sessionAuth, tenderCheck, 'tenderController.saveShenpiAudit');
+    app.post('/tender/:id/copy-setting', sessionAuth, tenderCheck, 'tenderController.copyTender');
 
     // 预付款
     app.get('/tender/:id/advance', sessionAuth, tenderCheck, 'advanceController.index');
@@ -329,6 +332,15 @@ module.exports = app => {
     // 变更单位管理
     app.post('/change/update/company', sessionAuth, 'changeController.updateCompany');
 
+    // 变更令 - 新版本
+    app.get('/tender/:id/change/:cid/information', sessionAuth, tenderCheck, uncheckTenderCheck, changeCheck, changeAuditCheck, 'changeController.information');
+    app.post('/tender/:id/change/:cid/information/save', sessionAuth, tenderCheck, uncheckTenderCheck, changeCheck, 'changeController.saveListsData');
+    app.post('/tender/:id/change/:cid/information/audit/start', sessionAuth, tenderCheck, uncheckTenderCheck, changeCheck, changeAuditCheck, 'changeController.startAudit');
+    app.post('/tender/:id/change/:cid/information/file/upload', sessionAuth, 'changeController.uploadFile');
+    app.post('/tender/:id/change/:cid/information/file/delete', sessionAuth, 'changeController.deleteFile');
+    app.post('/tender/:id/change/:cid/information/copy', sessionAuth, tenderCheck, uncheckTenderCheck, 'changeController.copyChange');
+    app.post('/tender/:id/change/:cid/information/audit/add', sessionAuth, tenderCheck, uncheckTenderCheck, changeCheck, 'changeController.addAudit');
+    app.post('/tender/:id/change/:cid/information/audit/delete', sessionAuth, tenderCheck, uncheckTenderCheck, changeCheck, 'changeController.deleteAudit');
     // 材料调差
     app.get('/tender/:id/measure/material', sessionAuth, tenderCheck, uncheckTenderCheck, 'materialController.index');
     app.post('/tender/:id/measure/material/add', sessionAuth, tenderCheck, uncheckTenderCheck, 'materialController.add');
@@ -417,4 +429,9 @@ module.exports = app => {
     app.get('/wx/project', wechatAuth, 'wechatController.project');
     app.get('/wx/test', 'wechatController.testwx');
     app.get('/MP_verify_t3MkWAMqplVxPjmr.txt', 'wechatController.oauthTxt');
+
+    // 形象进度
+    app.get('/tender/:id/schedule', sessionAuth, tenderCheck, uncheckTenderCheck, 'scheduleController.index');
+    app.get('/tender/:id/schedule/ledger', sessionAuth, tenderCheck, uncheckTenderCheck, 'scheduleController.ledger');
+    app.post('/tender/:id/schedule/ledger/load', sessionAuth, tenderCheck, uncheckTenderCheck, 'scheduleController.loadLedgerData');
 };

+ 51 - 1
app/service/advance_audit.js

@@ -3,6 +3,8 @@
 const auditConst = require('../const/audit').advance;
 const shenpiConst = require('../const/shenpi');
 const pushType = require('../const/audit').pushType;
+const smsTypeConst = require('../const/sms_type');
+const wxConst = require('../const/wechat_template.js');
 module.exports = app => {
     class AdvanceAudit extends app.BaseService {
         constructor(ctx) {
@@ -229,6 +231,16 @@ module.exports = app => {
                     id: audit.vid,
                     ...data,
                 });
+
+                // 微信模板通知
+                const advanceInfo = await this.ctx.service.advance.getDataById(vid);
+                const wechatData = {
+                    qi: advanceInfo.type === 0 ? '开工预付款第' + advanceInfo.order + '期' : '材料预付款第' + advanceInfo.order + '期',
+                    status: wxConst.status.check,
+                    tips: wxConst.tips.check,
+                    tp: parseFloat(advanceInfo.cur_amount),
+                };
+                await this.ctx.helper.sendWechat(audit.audit_id, smsTypeConst.const.YFK, smsTypeConst.judge.approval.toString(), wxConst.template.advance, wechatData);
                 await transaction.commit();
             } catch (err) {
                 await transaction.rollback();
@@ -265,6 +277,8 @@ module.exports = app => {
                 });
                 await transaction.insert(this.ctx.service.noticePush.tableName, records);
                 // 无下一审核人表示,审核结束
+
+                const advanceInfo = await this.ctx.service.advance.getDataById(advanceId);
                 if (nextAudit) {
                     // 流程至下一审批人
                     await transaction.update(this.tableName, { id: nextAudit.id, status: auditConst.status.checking, create_time: time });
@@ -274,12 +288,30 @@ module.exports = app => {
                         id: advanceId,
                         status: auditConst.status.checking,
                     });
+                    // 微信模板通知
+                    const wechatData = {
+                        qi: advanceInfo.type === 0 ? '开工预付款第' + advanceInfo.order + '期' : '材料预付款第' + advanceInfo.order + '期',
+                        status: wxConst.status.check,
+                        tips: wxConst.tips.check,
+                        tp: parseFloat(advanceInfo.cur_amount),
+                    };
+                    await this.ctx.helper.sendWechat(nextAudit.audit_id, smsTypeConst.const.YFK, smsTypeConst.judge.approval.toString(), wxConst.template.advance, wechatData);
                 } else {
                     await transaction.update(this.ctx.service.advance.tableName, {
                         id: advanceId,
                         status: checkData.checkType,
                         end_time: time,
                     });
+
+                    // 微信模板通知
+                    const users = this._.uniq(this._.concat(this._.map(auditors, 'audit_id'), advanceInfo.uid));
+                    const wechatData = {
+                        qi: advanceInfo.type === 0 ? '开工预付款第' + advanceInfo.order + '期' : '材料预付款第' + advanceInfo.order + '期',
+                        status: wxConst.status.success,
+                        tips: wxConst.tips.success,
+                        tp: parseFloat(advanceInfo.cur_amount),
+                    };
+                    await this.ctx.helper.sendWechat(users, smsTypeConst.const.YFK, smsTypeConst.judge.result.toString(), wxConst.template.advance, wechatData);
                 }
                 await transaction.commit();
             } catch (err) {
@@ -323,6 +355,16 @@ module.exports = app => {
                     status: checkData.checkType,
                     times: times + 1,
                 });
+                // 微信模板通知
+                const advanceInfo = await this.ctx.service.advance.getDataById(advanceId);
+                const users = this._.uniq(this._.concat(this._.map(auditors, 'audit_id'), advanceInfo.uid));
+                const wechatData = {
+                    qi: advanceInfo.type === 0 ? '开工预付款第' + advanceInfo.order + '期' : '材料预付款第' + advanceInfo.order + '期',
+                    status: wxConst.status.back,
+                    tips: wxConst.tips.back,
+                    tp: parseFloat(advanceInfo.cur_amount),
+                };
+                await this.ctx.helper.sendWechat(users, smsTypeConst.const.YFK, smsTypeConst.judge.result.toString(), wxConst.template.advance, wechatData);
                 // 拷贝新一次审核流程列表
                 await transaction.insert(this.tableName, auditors);
                 await transaction.commit();
@@ -380,7 +422,15 @@ module.exports = app => {
                     order: audit.order + 2,
                     status: auditConst.status.uncheck,
                 });
-
+                // 微信模板通知
+                const advanceInfo = await this.ctx.service.advance.getDataById(advanceId);
+                const wechatData = {
+                    qi: advanceInfo.type === 0 ? '开工预付款第' + advanceInfo.order + '期' : '材料预付款第' + advanceInfo.order + '期',
+                    status: wxConst.status.check,
+                    tips: wxConst.tips.check,
+                    tp: parseFloat(advanceInfo.cur_amount),
+                };
+                await this.ctx.helper.sendWechat(preAuditor.audit_id, smsTypeConst.const.YFK, smsTypeConst.judge.approval.toString(), wxConst.template.advance, wechatData);
                 await transaction.insert(this.tableName, newAuditors);
                 await transaction.commit();
             } catch (error) {

+ 8 - 3
app/service/category.js

@@ -129,7 +129,7 @@ module.exports = app => {
          */
         async getCategory(id) {
             const data = await this.getDataByCondition({id: id});
-            data.value = await this.ctx.service.categoryValue.getAllDataByCondition({ where: {cid: id} });
+            data.value = await this.ctx.service.categoryValue.getAllDataByCondition({ where: { cid: id }, orders: [['sort', 'asc'], ['id', 'asc']] });
             return data;
         }
         /**
@@ -139,8 +139,13 @@ module.exports = app => {
          * @returns {Promise<*>}
          */
         async getAllCategory(pid) {
-            const data = await this.getAllDataByCondition({where: {pid: pid}});
-            const values = await this.ctx.service.categoryValue.getAllDataByCondition({ where: {pid: pid} });
+            const data = await this.getAllDataByCondition({ where: { pid } });
+            const values = await this.ctx.service.categoryValue.getAllDataByCondition({
+                where: { pid },
+                orders: [['sort', 'asc'], ['id', 'asc']],
+            });
+            // values 按名称排序
+            // values.sort((a, b) => a.value.localeCompare(b.value, 'zh-Hans-CN', { sensitivity: 'accent' }));
             for (const d of data) {
                 d.value = values.filter(function (v) {
                     return v.cid === d.id;

+ 6 - 2
app/service/category_value.js

@@ -69,18 +69,22 @@ module.exports = app => {
 
             this.transaction = await this.db.beginTransaction();
             try {
+                let sort = 1;
                 for (const v of value) {
                     if (v.delete) {
                         await this.transaction.delete(this.tableName, {id: v.id});
                     } else if (v.new) {
                         const result = await this.transaction.insert(this.tableName, {
-                            cid: cid,
+                            cid,
                             pid: this.ctx.session.sessionProject.id,
                             value: v.value,
+                            sort,
                         });
                         v.id = result.insertId;
+                        sort++;
                     } else {
-                        await this.transaction.update(this.tableName, {id: v.id, value: v.value});
+                        await this.transaction.update(this.tableName, { id: v.id, value: v.value, sort });
+                        sort++;
                     }
                     if (!v.newTenders) { continue }
                     for (const nt of v.newTenders) {

+ 37 - 16
app/service/change.js

@@ -354,7 +354,6 @@ module.exports = app => {
          * @return {void}
          */
         async save(postData, tenderId) {
-            const tenderInfo = await this.ctx.service.tenderInfo.getTenderInfo(tenderId);
             // 初始化事务
             this.transaction = await this.db.beginTransaction();
             let result = false;
@@ -403,7 +402,6 @@ module.exports = app => {
                         uSite++;
                         uSort++;
                         insertCA.push(caArray);
-                        console.log(change_status, index);
 
                         // 添加短信通知-需要审批提醒功能
                         if (change_status && index === 0) {
@@ -459,12 +457,13 @@ module.exports = app => {
                             spamount: clInfo[6],
                             xmj_code: clInfo[9] !== '' ? clInfo[9] : null,
                             xmj_jldy: clInfo[10] !== '' ? clInfo[10] : null,
+                            gcl_id: clInfo[11] !== '' ? clInfo[11] : '',
                         };
                         if (clInfo[4] === '') {
                             delete clArray.unit_price;
                         }
                         insertCL.push(clArray);
-                        total_price = this.ctx.helper.accAdd(total_price, this.ctx.helper.mul(clArray.unit_price, clArray.spamount, tenderInfo.decimal.tp));
+                        total_price = this.ctx.helper.accAdd(total_price, this.ctx.helper.mul(clArray.unit_price, clArray.spamount, this.ctx.tender.info.decimal.tp));
                     }
                     await this.transaction.insert(this.ctx.service.changeAuditList.tableName, insertCL);
                 }
@@ -486,6 +485,7 @@ module.exports = app => {
                     quality: postData.quality,
                     company: postData.company,
                     charge: postData.charge,
+                    w_code: postData.w_code,
                     total_price,
                 };
                 const options = {
@@ -508,6 +508,32 @@ module.exports = app => {
         }
 
         /**
+         * 保存变更信息
+         * @param {int} postData - 表单提交的数据
+         * @param {int} tenderId - 标段id
+         * @return {void}
+         */
+        async saveInfo(postData) {
+            // 初始化事务
+            const transaction = await this.db.beginTransaction();
+            let result = false;
+            try {
+                const options = {
+                    where: {
+                        cid: this.ctx.change.cid,
+                    },
+                };
+                await transaction.update(this.tableName, postData, options);
+                await transaction.commit();
+                result = true;
+            } catch (error) {
+                await transaction.rollback();
+                result = false;
+            }
+            return result;
+        }
+
+        /**
          * 审批通过
          * @param {Number} pid 项目id
          * @param {int} postData - 表单提交的数据
@@ -515,15 +541,14 @@ module.exports = app => {
          * @return {void}
          */
         async approvalSuccess(pid, postData, changeData) {
-            let tenderInfo;
             // 初始化事务
             this.transaction = await this.db.beginTransaction();
             let result = false;
             try {
                 // 获取所有审核人列表
                 const auditors = await this.ctx.service.changeAudit.getAllAuditors(changeData.tid);
-                console.log('auditors', auditors);
-                console.log('postData', postData);
+                // console.log('auditors', auditors);
+                // console.log('postData', postData);
                 // 添加到消息推送表
                 const noticeContent = await this.getNoticeContent(pid, changeData.tid, changeData.cid, this.ctx.session.sessionUser.accountId);
                 const records = [];
@@ -559,11 +584,8 @@ module.exports = app => {
                     const lid = listInfo[0];
                     const amount = listInfo[1];
                     const changeListInfo = await this.ctx.service.changeAuditList.getDataById(lid);
-                    if (!tenderInfo) {
-                        tenderInfo = await this.ctx.service.tenderInfo.getTenderInfo(changeListInfo.tid);
-                    }
                     if (changeListInfo !== undefined) {
-                        total_price = this.ctx.helper.add(total_price, this.ctx.helper.mul(changeListInfo.unit_price, amount, tenderInfo.decimal.tp));
+                        total_price = this.ctx.helper.add(total_price, this.ctx.helper.mul(changeListInfo.unit_price, amount, this.ctx.tender.info.decimal.tp));
                         const audit_amount = changeListInfo.audit_amount !== null && changeListInfo.audit_amount !== '' ? changeListInfo.audit_amount.split(',') : [];
                         audit_amount.push(amount);
                         const list_update = {
@@ -583,6 +605,8 @@ module.exports = app => {
                     change_update.p_code = postData.p_code;
                     change_update.sin_time = Date.parse(new Date()) / 1000;
 
+                    await this.ctx.service.tenderTag.saveTenderTag(changeData.tid, { bgl_time: new Date() }, this.transaction);
+
                     // 添加短信通知-审批通过提醒功能
                     // const mobile_array = [];
                     const auditList = await this.ctx.service.changeAudit.getListGroupByTimes(changeData.cid, changeData.times);
@@ -719,7 +743,6 @@ module.exports = app => {
                 });
                 await this.transaction.insert('zh_notice', records);
                 const changeInfo = await this.getDataByCondition({ cid: postData.change_id });
-                const tenderInfo = await this.ctx.service.tenderInfo.getTenderInfo(changeInfo.tid);
                 // 设置审批人退回
                 const audit_update = {
                     id: postData.audit_id,
@@ -758,7 +781,7 @@ module.exports = app => {
                 });
                 let total_price = 0;
                 for (const cl of changeList) {
-                    total_price = this.ctx.helper.add(total_price, this.ctx.helper.mul(cl.unit_price, cl.camount, tenderInfo.decimal.tp));
+                    total_price = this.ctx.helper.add(total_price, this.ctx.helper.mul(cl.unit_price, cl.camount, this.ctx.tender.info.decimal.tp));
                 }
                 // 设置变更令退回
                 const change_update = {
@@ -832,7 +855,6 @@ module.exports = app => {
                 await this.transaction.insert('zh_notice', records);
 
                 const changeInfo = await this.getDataByCondition({ cid: postData.change_id });
-                const tenderInfo = await this.ctx.service.tenderInfo.getTenderInfo(changeInfo.tid);
                 // 设置审批人退回
                 const audit_update = {
                     id: postData.audit_id,
@@ -906,7 +928,7 @@ module.exports = app => {
                         audit_amount: audit_amount.join(','),
                         spamount: parseFloat(last_amount),
                     };
-                    total_price = this.ctx.helper.add(total_price, this.ctx.helper.mul(cl.unit_price, parseFloat(last_amount), tenderInfo.decimal.tp));
+                    total_price = this.ctx.helper.add(total_price, this.ctx.helper.mul(cl.unit_price, parseFloat(last_amount), this.ctx.tender.info.decimal.tp));
                     await this.transaction.update(this.ctx.service.changeAuditList.tableName, list_update);
                 }
 
@@ -1163,7 +1185,6 @@ module.exports = app => {
             let result = false;
             try {
                 const changeInfo = await this.getDataByCondition({ cid });
-                const tenderInfo = await this.ctx.service.tenderInfo.getTenderInfo(changeInfo.tid);
 
                 // 获取终审
                 const auditInfo = (
@@ -1222,7 +1243,7 @@ module.exports = app => {
                         audit_amount: audit_amount.join(','),
                         samount: '',
                     };
-                    total_price = this.ctx.helper.add(total_price, this.ctx.helper.mul(cl.unit_price, parseFloat(last_amount), tenderInfo.decimal.tp));
+                    total_price = this.ctx.helper.add(total_price, this.ctx.helper.mul(cl.unit_price, parseFloat(last_amount), this.ctx.tender.info.decimal.tp));
                     await this.transaction.update(this.ctx.service.changeAuditList.tableName, list_update);
                 }
 

+ 2 - 2
app/service/change_att.js

@@ -47,12 +47,12 @@ module.exports = app => {
          * @param {uuid} cid - 变更令id
          * @return {Promise<void>}
          */
-        async getChangeAttachment(cid) {
+        async getChangeAttachment(cid, sort = 'asc') {
             const sql = 'SELECT ca.*, pa.name As u_name, pa.role As u_role ' +
                 '  FROM ?? As ca ' +
                 '  Left Join ?? As pa ' +
                 '  On ca.uid = pa.id ' +
-                '  Where ca.cid = ?';
+                '  Where ca.cid = ? order by id ' + sort;
             const sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, cid];
             return await this.db.query(sql, sqlParam);
         }

+ 175 - 10
app/service/change_audit.js

@@ -8,8 +8,13 @@
  * @version
  */
 
-const audit = require('../const/audit');
+const auditConst = require('../const/audit').flow;
 const pushType = require('../const/audit').pushType;
+const shenpiConst = require('../const/shenpi');
+const smsTypeConst = require('../const/sms_type');
+const SMS = require('../lib/sms');
+const SmsAliConst = require('../const/sms_alitemplate');
+const wxConst = require('../const/wechat_template');
 
 module.exports = app => {
     class ChangeAudit extends app.BaseService {
@@ -96,8 +101,8 @@ module.exports = app => {
          * @return {void}
          */
         async getStatusByChange(change) {
-            const statusConst = audit.flow.status;
-            const auditStatusConst = audit.flow.auditStatus;
+            const statusConst = auditConst.status;
+            const auditStatusConst = auditConst.auditStatus;
             const uid = this.ctx.session.sessionUser.accountId;
             const changeAuditInfo = await this.getAllDataByCondition({ where: { cid: change.cid, times: change.times, uid }, orders: [['id', 'desc']], limit: 1, offset: 0 });
             if (!change.status === statusConst.checked && (changeAuditInfo === null || changeAuditInfo[0] === undefined)) {
@@ -208,7 +213,7 @@ module.exports = app => {
          */
         async getListGroupByTimes(cid, times) {
             const sql = 'SELECT * FROM ?? where id in (SELECT MAX(id) FROM ?? WHERE ' +
-                'cid = ? AND times = ? GROUP BY usite)';
+                'cid = ? AND times = ? GROUP BY usite) ORDER BY usite asc';
             const sqlParam = [this.tableName, this.tableName, cid, times];
             const list = await this.db.query(sql, sqlParam);
             return list;
@@ -271,10 +276,10 @@ module.exports = app => {
                 '  FROM ?? AS ca, ?? AS c, ?? As t ' +
                 '  WHERE ca.`uid` = ? and ca.`status` = ? and c.`status` != ?' +
                 '    and ca.`cid` = c.`cid` and ca.`tid` = t.`id` ORDER BY ca.`sin_time` DESC';
-            const sqlParam = [this.tableName, this.ctx.service.change.tableName, this.ctx.service.tender.tableName, uid, 2, audit.flow.status.uncheck];
+            const sqlParam = [this.tableName, this.ctx.service.change.tableName, this.ctx.service.tender.tableName, uid, 2, auditConst.status.uncheck];
             const changes = await this.db.query(sql, sqlParam);
             for (const c of changes) {
-                if (c.cstatus === audit.flow.status.back) {
+                if (c.cstatus === auditConst.status.back) {
                     const preSql = 'SELECT pa.`id`, pa.`account`, pa.`account_group`, pa.`name`, pa.`company`, pa.`role`, pa.`telephone` FROM ?? As ca, ?? As pa ' +
                         '  WHERE ca.cid = ? and ca.times = ? and ca.status = ? and ca.uid = pa.id';
                     const preSqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, c.cid, c.times - 1, c.cstatus];
@@ -306,7 +311,7 @@ module.exports = app => {
             //             '  WHERE t.`project_id` = ? and `ca`.`sin_time` > ? and `ca`.`usite` != 0 and `ca`.`status` != ?' +
             //             '  ORDER By ca.`sin_time` DESC LIMIT 1000) as new_t GROUP BY new_t.`id`' +
             //             '  ORDER BY new_t.`cu_time`';
-            // const sqlParam = [this.ctx.service.tender.tableName, uid, this.tableName, uid, this.ctx.service.change.tableName, this.tableName, pid, time, audit.flow.status.checking];
+            // const sqlParam = [this.ctx.service.tender.tableName, uid, this.tableName, uid, this.ctx.service.change.tableName, this.tableName, pid, time, auditConst.status.checking];
             // return await this.db.query(sql, sqlParam);
             let notice = await this.db.select('zh_notice', {
                 where: { pid, type: pushType.change, uid },
@@ -345,7 +350,7 @@ module.exports = app => {
                 '  FROM ?? AS ca, ?? AS c, ?? As t, ?? AS ti ' +
                 '  WHERE ca.`uid` = ? and ca.`status` = ? and c.`status` != ? and c.`status` != ?' +
                 '    and ca.`cid` = c.`cid` and ca.`tid` = t.`id` and ti.`tid` = t.`id`';
-            const sqlParam = [this.tableName, this.ctx.service.change.tableName, this.ctx.service.tender.tableName, this.ctx.service.tenderInfo.tableName, uid, audit.flow.auditStatus.checking, audit.flow.status.uncheck, audit.flow.status.back];
+            const sqlParam = [this.tableName, this.ctx.service.change.tableName, this.ctx.service.tender.tableName, this.ctx.service.tenderInfo.tableName, uid, auditConst.auditStatus.checking, auditConst.status.uncheck, auditConst.status.back];
             const changes = await this.db.query(sql, sqlParam);
             for (const c of changes) {
                 const preSql = 'SELECT pa.`id`, pa.`account`, pa.`account_group`, pa.`name`, pa.`company`, pa.`role`, pa.`telephone` FROM ?? As ca, ?? As pa ' +
@@ -372,7 +377,7 @@ module.exports = app => {
                     newAuditors.push({
                         tid: change.tid, cid: change.cid, uid: aid,
                         name: accountInfo.name, jobs: accountInfo.role, company: accountInfo.company,
-                        times: change.times, usite: order, usort: uSort, status: audit.flow.auditStatus.uncheck,
+                        times: change.times, usite: order, usort: uSort, status: auditConst.auditStatus.uncheck,
                     });
                     order++;
                     uSort++;
@@ -408,7 +413,7 @@ module.exports = app => {
                 const newAuditor = {
                     tid: change.tid, cid: change.cid, uid: lastId,
                     name: userInfo.name, jobs: userInfo.role, company: userInfo.company,
-                    times: change.times, usite: order, usort: uSort, status: audit.flow.auditStatus.uncheck,
+                    times: change.times, usite: order, usort: uSort, status: auditConst.auditStatus.uncheck,
                 };
                 await transaction.insert(this.tableName, newAuditor);
                 await transaction.commit();
@@ -455,6 +460,166 @@ module.exports = app => {
             return data;
         }
 
+        /**
+         * 获取 当前审核人
+         *
+         * @param {Number} cid - 期id
+         * @param {Number} times - 第几次审批
+         * @return {Promise<*>}
+         */
+        async getCurAuditor(cid, times = 1) {
+            const sql = 'SELECT * FROM ?? WHERE `cid` = ? and `status` = ? and `times` = ? and usite != 0';
+            const sqlParam = [this.tableName, cid, auditConst.status.checking, times];
+            return await this.db.queryOne(sql, sqlParam);
+        }
+
+        /**
+         * 获取 审核人列表(除去原报)
+         *
+         * @param {Number} cid - 变更id
+         * @param {Number} times - 第几次审批
+         * @return {Promise<*>}
+         */
+        async getAuditors(cid, times = 1) {
+            const sql = 'SELECT * FROM ?? WHERE `cid` = ? and `times` = ? and usite != 0';
+            const sqlParam = [this.tableName, cid, times];
+            return await this.db.query(sql, sqlParam);
+        }
+
+        /**
+         * 新增审核人
+         *
+         * @param {Number} cid - 变更令id
+         * @param {Number} auditorId - 审核人id
+         * @param {Number} times - 第几次审批
+         * @return {Promise<number>}
+         */
+        async addAuditor(cid, auditorId, times = 1, is_gdzs = 0) {
+            const transaction = await this.db.beginTransaction();
+            try {
+                let newOrder = await transaction.count(this.tableName, { cid, times });
+                let uSort = await transaction.count(this.tableName, { cid });
+                // 判断是否存在固定终审,存在则newOrder - 1并使终审order+1
+                newOrder = is_gdzs === 1 ? newOrder - 1 : newOrder;
+                uSort = is_gdzs === 1 ? uSort - 1 : uSort;
+                if (is_gdzs) await this._syncOrderByDelete(transaction, cid, newOrder, uSort, times, '+');
+                const userInfo = await this.ctx.service.projectAccount.getDataById(auditorId);
+                const newAuditor = {
+                    tid: this.ctx.tender.id, cid: cid, uid: auditorId,
+                    name: userInfo.name, jobs: userInfo.role, company: userInfo.company,
+                    times: times, usite: newOrder, usort: uSort, status: auditConst.auditStatus.uncheck,
+                };
+                const result = await transaction.insert(this.tableName, newAuditor);
+                await transaction.commit();
+                return result.effectRows = 1;
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
+            return false;
+        }
+
+        /**
+         * 移除审核人
+         *
+         * @param {Number} cid - 变更令id
+         * @param {Number} auditorId - 审核人id
+         * @param {Number} times - 第几次审批
+         * @return {Promise<boolean>}
+         */
+        async deleteAuditor(cid, auditorId, times = 1) {
+            const transaction = await this.db.beginTransaction();
+            try {
+                const sql = 'SELECT * FROM ?? WHERE `cid` = ? and `times` = ? and `uid` = ? and `usite` != 0 ORDER BY `usort` DESC';
+                const sqlParam = [this.tableName, cid, times, auditorId];
+                const auditor = await transaction.queryOne(sql, sqlParam);
+                if (!auditor) {
+                    throw '该审核人不存在';
+                }
+                await this._syncOrderByDelete(transaction, cid, auditor.usite, auditor.usort, times);
+                await transaction.delete(this.tableName, { id: auditor.id });
+                await transaction.commit();
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
+            return true;
+        }
+
+        /**
+         * 开始审批
+         * @param {Number} cid - 变更令id
+         * @param {Number} times - 第几次审批
+         * @return {Promise<boolean>}
+         */
+        async start(cid, times = 1) {
+            const audit = await this.getDataByCondition({ cid, times, usite: 1 });
+            const yBAudit = await this.getDataByCondition({ cid, times, usite: 0 });
+            if (!audit) {
+                if(this.ctx.tender.info.shenpi.change === shenpiConst.sp_status.gdspl) {
+                    throw '请联系管理员添加审批人';
+                } else {
+                    throw '请先选择审批人,再上报数据';
+                }
+            }
+
+            const transaction = await this.db.beginTransaction();
+            try {
+                await transaction.update(this.tableName, { id: audit.id, status: auditConst.auditStatus.checking, sin_time: new Date() });
+                const options = {
+                    where: {
+                        cid: cid,
+                    },
+                };
+                await transaction.update(this.ctx.service.change.tableName, {
+                    status: auditConst.status.checking,
+                }, options);
+                // 更新原报人审批状态
+                await transaction.update(this.tableName, {
+                    id: yBAudit.id,
+                    status: auditConst.auditStatus.checked,
+                    sin_time: new Date(),
+                });
+                const changeList = await this.ctx.service.changeAuditList.getList(cid);
+                // 更新清单spamount的值
+                const updateListData = [];
+                for (const cl of changeList) {
+                    if(cl.camount !== cl.spamount) {
+                        const uld = {
+                            id: cl.id,
+                            spamount: cl.camount,
+                        };
+                        updateListData.push(uld);
+                    }
+                }
+                if(updateListData.length > 0) await transaction.updateRows(this.ctx.service.changeAuditList.tableName, updateListData);
+
+                // 添加短信通知-需要审批提醒功能
+                const sms = new SMS(this.ctx);
+                const code = await sms.contentChange(this.ctx.change.code);
+                const shenpiUrl = await this.ctx.helper.urlToShort(
+                    this.ctx.protocol + '://' + this.ctx.host + '/wap/tender/' + this.ctx.change.tid + '/change/' + cid + '/info#shenpi'
+                );
+                await this.ctx.helper.sendAliSms(audit.uid, smsTypeConst.const.BG, smsTypeConst.judge.approval.toString(), SmsAliConst.template.change_check, {
+                    biangeng: code,
+                    code: shenpiUrl,
+                });
+                // 微信模板通知
+                const wechatData = {
+                    wap_url: shenpiUrl,
+                    status: wxConst.status.check,
+                    tips: wxConst.tips.check,
+                    code: this.ctx.session.sessionProject.code,
+                    c_name: this.ctx.change.name,
+                };
+                await this.ctx.helper.sendWechat(audit.uid, smsTypeConst.const.BG, smsTypeConst.judge.approval.toString(), wxConst.template.change, wechatData);
+                await transaction.commit();
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
+            return true;
+        }
     }
 
     return ChangeAudit;

+ 203 - 0
app/service/change_audit_list.js

@@ -23,6 +23,209 @@ module.exports = app => {
             this.tableName = 'change_audit_list';
         }
 
+        /**
+         * 取出变更令清单列表,并按台账清单在前,空白清单在后排序
+         * @return {void}
+         */
+        async getList(cid) {
+            const sql = 'SELECT * FROM ?? WHERE `cid` = ? ORDER BY `lid` = "0", `id` asc';
+            const sqlParam = [this.tableName, cid];
+            return await this.db.query(sql, sqlParam);
+        }
+
+        /**
+         * 添加空白变更清单
+         * @return {void}
+         */
+        async add(data) {
+            if (!this.ctx.tender || !this.ctx.change) {
+                throw '数据错误';
+            }
+            const insertData = {
+                tid: this.ctx.tender.id,
+                cid: this.ctx.change.cid,
+                lid: '0',
+                code: '',
+                name: '',
+                bwmx: '',
+                unit: '',
+                unit_price: null,
+                oamount: 0,
+                camount: 0,
+                samount: '',
+                detail: '',
+                spamount: 0,
+                xmj_code: null,
+                xmj_jldy: null,
+                gcl_id: '',
+            };
+            // 新增工料
+            const result = await this.db.insert(this.tableName, insertData);
+            if (result.affectedRows === 0) {
+                throw '新增空白清单数据失败';
+            }
+            return await this.getDataById(result.insertId);
+        }
+
+        /**
+         * 删除变更清单
+         * @param {int} id 清单id
+         * @return {void}
+         */
+        async del(id) {
+            if (!this.ctx.tender || !this.ctx.change) {
+                throw '数据错误';
+            }
+            const transaction = await this.db.beginTransaction();
+            try {
+                // 判断是否可删
+                await transaction.delete(this.tableName, { id });
+                // 重新算变更令总额
+                // await this.calcQuantityByML(transaction, mb_id);
+                await transaction.commit();
+                return true;
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
+        }
+
+        /**
+         * 修改变更清单
+         * @param {Object} data 工料内容
+         * @param {int} order 期数
+         * @return {void}
+         */
+        async save(data, order) {
+            if (!this.ctx.tender || !this.ctx.change) {
+                throw '数据错误';
+            }
+
+            const transaction = await this.db.beginTransaction();
+            try {
+                // const mb_id = data.mb_id;
+                // delete data.mb_id;
+                await transaction.update(this.tableName, data);
+                // await this.calcQuantityByML(transaction, mb_id);
+                await this.calcCamountSum(transaction);
+                await transaction.commit();
+                return true;
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
+        }
+
+        /**
+         * 修改变更清单 复制粘贴
+         * @param {Object} datas 修改内容
+         * @return {void}
+         */
+        async saveDatas(datas) {
+            if (!this.ctx.tender || !this.ctx.change) {
+                throw '数据错误';
+            }
+            // 判断是否可修改
+            // 判断t_type是否为费用
+            const transaction = await this.db.beginTransaction();
+            try {
+                // for (const data of datas) {
+                //     const mb_id = data.mb_id;
+                //     delete data.mb_id;
+                //     await transaction.update(this.tableName, data);
+                //     await this.calcQuantityByML(transaction, mb_id);
+                // }
+                await transaction.updateRows(this.tableName, datas);
+                await this.calcCamountSum(transaction);
+                await transaction.commit();
+                return true;
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
+        }
+
+        /**
+         * 台账数据清单 重新选择
+         * @param {Object} datas 内容
+         * @return {void}
+         */
+        async saveLedgerListDatas(datas) {
+            if (!this.ctx.tender || !this.ctx.change) {
+                throw '数据错误';
+            }
+            // 判断是否可修改
+            // 判断t_type是否为费用
+            const transaction = await this.db.beginTransaction();
+            try {
+                // 先删除原本的台账清单数据
+                const sql = 'DELETE FROM ?? WHERE cid = ? and lid != "0"';
+                const sqlParam = [this.tableName, this.ctx.change.cid];
+                await transaction.query(sql, sqlParam);
+                const insertDatas = [];
+                for (const data of datas) {
+                    data.tid = this.ctx.tender.id;
+                    data.cid = this.ctx.change.cid;
+                    data.spamount = data.camount;
+                    data.samount = '';
+                    insertDatas.push(data);
+                }
+                if (insertDatas.length > 0) await transaction.insert(this.tableName, insertDatas);
+                await this.calcCamountSum(transaction);
+                await transaction.commit();
+                return true;
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
+        }
+
+        async calcCamountSum(transaction) {
+            // const sql = 'SELECT SUM(ROUND(`camount`*`unit_price`, )) as total_price FROM ?? WHERE cid = ?';
+            // const sqlParam = [this.tableName, this.change.cid];
+            // const tp = await transaction.queryOne(sql, sqlParam);
+            // 防止小数位不精确,采用取值计算
+            const sql = 'SELECT unit_price, spamount FROM ?? WHERE cid = ?';
+            const sqlParam = [this.tableName, this.ctx.change.cid];
+            const changeList = await transaction.query(sql, sqlParam);
+            let total_price = 0;
+            for (const cl of changeList) {
+                total_price = this.ctx.helper.accAdd(total_price, this.ctx.helper.mul(cl.unit_price, cl.spamount, this.ctx.tender.info.decimal.tp));
+            }
+            const updateData = {
+                total_price,
+            };
+            const options = {
+                where: {
+                    cid: this.ctx.change.cid,
+                },
+            };
+            await transaction.update(this.ctx.service.change.tableName, updateData, options);
+        }
+
+        /**
+         * 用户数据数量提交
+         * @param {Object} data 内容
+         * @return {void}
+         */
+        async saveAmountData(data) {
+            if (!this.ctx.tender || !this.ctx.change) {
+                throw '数据错误';
+            }
+            // 判断是否可修改
+            // 判断t_type是否为费用
+            const transaction = await this.db.beginTransaction();
+            try {
+                await transaction.update(this.tableName, data);
+                await this.calcCamountSum(transaction);
+                await transaction.commit();
+                return true;
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
+        }
+
         async gatherBgBills(tid) {
             const sql = 'SELECT cb.code, cb.name, cb.unit, cb.unit_price, Round(Sum(cb.samount + 0), 6) as quantity' +
                 '  FROM ' + this.tableName + ' cb' +

+ 2 - 2
app/service/ledger.js

@@ -702,11 +702,11 @@ module.exports = app => {
          * @param excelData
          * @return {Promise<void>}
          */
-        async importExcel(templateId, excelData) {
+        async importExcel(templateId, excelData, filter) {
             const AnalysisExcel = require('../lib/analysis_excel').AnalysisExcelTree;
             const analysisExcel = new AnalysisExcel(this.ctx);
             const tempData = await this.ctx.service.tenderNodeTemplate.getData(templateId, true);
-            const cacheTree = analysisExcel.analysisData(excelData, tempData);
+            const cacheTree = analysisExcel.analysisData(excelData, tempData, filter);
             const cacheKey = keyPre + this.ctx.tender.id;
             const orgMaxId = parseInt(await this.cache.get(cacheKey));
             const transaction = await this.db.beginTransaction();

+ 1 - 0
app/service/ledger_audit.js

@@ -364,6 +364,7 @@ module.exports = app => {
                             id: tenderId,
                             ledger_status: checkType,
                         });
+                        await this.ctx.service.tenderTag.saveTenderTag(tenderId, {ledger_time: new Date()}, transaction);
                         const users = this._.pull(this._.map(auditList, 'audit_id'), audit.id);
                         await this.ctx.helper.sendAliSms(users, smsTypeConst.const.TZ, smsTypeConst.judge.result.toString(), SmsAliConst.template.ledger_result, {
                             status: SmsAliConst.status.success,

파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 794 - 786
app/service/project_account.js


파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 451 - 390
app/service/report_memory.js


+ 1 - 0
app/service/revise_audit.js

@@ -424,6 +424,7 @@ module.exports = app => {
                             status: checkType,
                             end_time: time,
                         });
+                        await this.ctx.service.tenderTag.saveTenderTag(revise.tid, {revise_time: new Date()}, transaction);
                         // 最新一期跟台账相关的缓存数据应过期
                         const lastStage = await this.ctx.service.stage.getLastestStage(revise.tid, true);
                         const cacheTime = new Date();

+ 1 - 0
app/service/stage.js

@@ -133,6 +133,7 @@ module.exports = app => {
         async checkStage(sid) {
             if (!this.ctx.stage) {
                 const stage = await this.ctx.service.stage.getDataById(sid);
+                if (!stage) throw '校验的期数据不存在';
                 await this.doCheckStage(stage);
                 this.ctx.stage = stage;
             }

+ 1 - 0
app/service/stage_audit.js

@@ -444,6 +444,7 @@ module.exports = app => {
                     };
                     await this.ctx.helper.sendWechat(nextAudit.aid, smsTypeConst.const.JL, smsTypeConst.judge.approval.toString(), wxConst.template.stage, wechatData);
                 } else {
+                    await this.ctx.service.tenderTag.saveTenderTag(this.ctx.tender.id, {stage_time: new Date()}, transaction);
                     // 本期结束
                     // 生成截止本期数据 final数据
                     await this.ctx.service.stageBillsFinal.generateFinalData(transaction, this.ctx.tender, this.ctx.stage);

+ 8 - 7
app/service/tender.js

@@ -12,7 +12,7 @@ const tenderConst = require('../const/tender');
 const auditConst = require('../const/audit');
 const fs = require('fs');
 const path = require('path');
-const commonQueryColumns = ['id', 'project_id', 'name', 'status', 'category', 'ledger_times', 'ledger_status', 'measure_type', 'user_id', 'valuation', 'total_price', 'deal_tp'];
+const commonQueryColumns = ['id', 'project_id', 'name', 'status', 'category', 'ledger_times', 'ledger_status', 'measure_type', 'user_id', 'valuation', 'total_price', 'deal_tp', 'copy_id'];
 
 module.exports = app => {
 
@@ -206,6 +206,7 @@ module.exports = app => {
                 if (!result) {
                     throw '新增合同支付数据失败';
                 }
+                await this.ctx.service.tenderTag.addTenderTag(operate.insertId, sessionProject.id, this.transaction);
                 await this.transaction.commit();
                 const sql = 'SELECT t.`id`, t.`project_id`, t.`name`, t.`status`, t.`category`, t.`ledger_times`, t.`ledger_status`, t.`measure_type`, t.`user_id`, t.`create_time`, t.`total_price`, t.`deal_tp`,' +
                     '    pa.`name` As `user_name`, pa.`role` As `user_role`, pa.`company` As `user_company` ' +
@@ -285,16 +286,16 @@ module.exports = app => {
                 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.stageBillsDgn.tableName, {tid: id});
-                await transaction.delete(this.ctx.service.stageBillsFinal.tableName, {tid: id});
-                await transaction.delete(this.ctx.service.stagePosFinal.tableName, {tid: id});
+                await transaction.delete(this.ctx.service.stageBillsDgn.tableName, { tid: id });
+                await transaction.delete(this.ctx.service.stageBillsFinal.tableName, { tid: id });
+                await transaction.delete(this.ctx.service.stagePosFinal.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.stageChange.tableName, { tid: id });
 
-                await transaction.delete(this.ctx.service.stageJgcl.tableName, {tid: id});
-                await transaction.delete(this.ctx.service.stageBonus.tableName, {tid: id});
-                await transaction.delete(this.ctx.service.stageOther.tableName, {tid: id});
+                await transaction.delete(this.ctx.service.stageJgcl.tableName, { tid: id });
+                await transaction.delete(this.ctx.service.stageBonus.tableName, { tid: id });
+                await transaction.delete(this.ctx.service.stageOther.tableName, { tid: id });
 
                 await transaction.delete(this.ctx.service.change.tableName, { tid: id });
                 await transaction.delete(this.ctx.service.changeAudit.tableName, { tid: id });

+ 30 - 0
app/service/tender_info.js

@@ -350,6 +350,36 @@ module.exports = app => {
             const shenpiInfo = !info.shenpi || info.shenpi === null || info.shenpi === '' ? defaultShenpiInfo : JSON.parse(info.shenpi);
             return shenpiInfo;
         }
+
+        /**
+         * 拷贝标段数据至当前标段
+         * @param {number} id - 当前标段id
+         * @param {number} copy_id - 被拷贝的标段id
+         * @param {Array} typeArr - 需要拷贝的类型数组
+         */
+        async copyTenderHandler(id, copy_id, typeArr) {
+            const columns = [];
+            typeArr.forEach(item => {
+                if (item === 'tender') {
+                    columns.push('deal_info', 'construction_unit', 'tech_param');
+                } else if (item === 'chapter') {
+                    columns.push('chapter');
+                } else if (item === 'pay_account') {
+                    columns.push('pay_account');
+                }
+            });
+            if (!columns.length) throw '未选择需要拷贝的设置';
+            const [data] = await this.getAllDataByCondition({
+                where: {
+                    tid: copy_id,
+                },
+                columns,
+            });
+            const isUpdate = await this.update(data, { tid: id });
+            if (isUpdate) {
+                await this.ctx.service.tender.update({ copy_id }, { id });
+            }
+        }
     }
 
     return TenderInfo;

+ 59 - 0
app/service/tender_tag.js

@@ -0,0 +1,59 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date 2018/10/30
+ * @version
+ */
+
+module.exports = app => {
+
+    class TenderTag extends app.BaseService {
+
+        /**
+         * 构造函数
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        constructor(ctx) {
+            super(ctx);
+            this.tableName = 'tender_tag';
+        }
+
+        /**
+         * 新增 标段标记
+         *
+         * @param tenderId - 标段Id
+         * @param projectId - 项目Id
+         * @param transaction - 事务
+         * @return {Promise<void>}
+         */
+        async addTenderTag(tenderId, projectId, transaction) {
+            if (transaction) {
+                await transaction.insert(this.tableName, {tid: tenderId, pid: projectId});
+            } else {
+                await this.db.insert(this.tableName, {tid: tenderId, pid: projectId});
+            }
+        }
+
+        /**
+         * 保存 标段标记
+         *
+         * @param data
+         * @return {Promise<void>}
+         */
+        async saveTenderTag(tenderId, data, transaction) {
+            if (transaction) {
+                await transaction.update(this.tableName, data, { where: { tid: tenderId } });
+            } else {
+                await this.db.update(this.tableName, data, { where: { tid: tenderId } });
+            }
+        }
+
+    }
+
+    return TenderTag;
+};

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

@@ -44,13 +44,13 @@
                     <% } %>
                     <% for (const c of changes) { %>
                     <tr>
-                        <td><a href="/tender/<%- tender.id %>/change/<%- c.cid %>/info"><% if (c.status !== auditConst.status.checked) { %><%- c.code %><% } else { %><%- c.p_code %><% } %></a></td>
+                        <td><a href="/tender/<%- tender.id %>/change/<%- c.cid %>/information"><% if (c.status !== auditConst.status.checked) { %><%- c.code %><% } else { %><%- c.p_code %><% } %></a></td>
                         <td><%- c.name %></td>
                         <td><%- classArray[c.class] %><% c.class %></td>
                         <td style="text-align: right"><%= ctx.helper.roundNum(c.total_price, tpUnit) %></td>
                         <% if (c.auditStatus) { %>
                         <td>
-                            <a href="/tender/<%- tender.id %>/change/<%- c.cid %>/info" class="btn <%- auditConst.statusButtonClass[c.status] %> btn-sm">
+                            <a href="/tender/<%- tender.id %>/change/<%- c.cid %>/information" class="btn <%- auditConst.statusButtonClass[c.status] %> btn-sm">
                                 <%- auditConst.statusButton[c.status] %>
                             </a>
                         </td>

+ 5 - 0
app/view/change/info.ejs

@@ -291,6 +291,10 @@
                                 <% } %>
                             </div>
                         </div>
+                        <div class="form-group">
+                            <label>批复文号</label>
+                            <input class="form-control form-control-sm" id="w_code" name="w_code"  placeholder="" type="text" value="<%- change.w_code %>">
+                        </div>
                     </div>
                 </div>
                 <!--清单、附件、审批人上传数据-->
@@ -812,6 +816,7 @@
         quality: '<%- change.quality %>',
         company: '<%- change.company %>',
         charge: '<%- change.charge %>',
+        w_code: '<%- change.w_code %>',
     };
     let changeInfo = Object.assign({}, back_changeInfo);
 </script>

+ 4 - 4
app/view/change/info_modal.ejs

@@ -626,7 +626,7 @@
                                                                     <% if (auditor.status === auditConst.auditStatus.checking) { %>
                                                                         <label>审批意见<b class="text-danger">*</b></label>
                                                                         <textarea class="form-control form-control-sm"
-                                                                                  name="opinion">同意</textarea>
+                                                                                  name="sdesc">同意</textarea>
                                                                         <input type="hidden" name="audit_id" value="<%= auditor.id %>">
                                                                         <% if (auditor.usort !== 0 && index+1 === auditList3.length) { %>
                                                                             <!--终审填写批复编号-->
@@ -638,7 +638,7 @@
                                                                             <input type="hidden" name="audit_next_id" value="<%= auditList3[index+1].id %>">
                                                                         <% } %>
                                                                     <% } else { %>
-                                                                        <p style="margin: 0;"><%- auditor.opinion %></p>
+                                                                        <p style="margin: 0;"><%- auditor.sdesc %></p>
                                                                     <% } %>
                                                                 </div>
                                                             <% } %>
@@ -863,7 +863,7 @@
                                                                     <% if (auditor.status === auditConst.auditStatus.checking) { %>
                                                                         <label>审批意见<b class="text-danger">*</b></label>
                                                                         <textarea class="form-control form-control-sm"
-                                                                                  name="opinion">不同意</textarea>
+                                                                                  name="sdesc">不同意</textarea>
                                                                         <input type="hidden" name="audit_id" value="<%= auditor.id %>">
                                                                         <div id="change-back-content" class="alert alert-warning"
                                                                              style="margin-top: 15px;">
@@ -883,7 +883,7 @@
                                                                             <% } %>
                                                                         </div>
                                                                     <% } else if(auditor.status === auditConst.auditStatus.checked){ %>
-                                                                        <p style="margin: 0;"><%- auditor.opinion %></p>
+                                                                        <p style="margin: 0;"><%- auditor.sdesc %></p>
                                                                     <% } %>
                                                                 </div>
                                                             <% } %>

+ 434 - 0
app/view/change/information.ejs

@@ -0,0 +1,434 @@
+<% include ../tender/tender_sub_menu.ejs %>
+<div class="panel-content">
+    <div class="panel-title"><!--收起详解目录添加类名 fluid -->
+        <div class="title-main d-flex"><!--工具-->
+            <% include ../tender/tender_sub_mini_menu.ejs %>
+            <div>
+                <% if(auditStatus === auditConst.status.uncheck || auditStatus === auditConst.status.back) { %>
+                    <div class="d-inline-block">
+                        <a class="btn btn-sm btn-primary" href="#add-bj" data-toggle="modal" data-target="#add-bj">拷贝其他变更令数据</a>
+                    </div>
+                <% } %>
+                <% if (auditStatus === 1 || auditStatus === 2) { %>
+                    <div class="d-inline-block">
+                        <a href="#addlist" data-toggle="modal" class="btn btn-sm btn-light text-primary" id="open-list-modal" data-toggle="tooltip" data-placement="bottom" title="" data-original-title="添加清单"><i class="fa fa-plus" aria-hidden="true"></i> 添加台账清单</a>
+                    </div>
+                    <div class="d-inline-block mr-3">
+                        <a href="javascript:void(0);" class="btn btn-sm btn-light text-primary" id="add-white-btn" data-original-title="添加清单"><i class="fa fa-plus" aria-hidden="true"></i> 添加空白清单</a>
+                    </div>
+                <% } %>
+                <div class="d-inline-block">
+                    <div class="custom-control custom-checkbox" style="line-height: normal;">
+                        <input type="checkbox" class="custom-control-input change-detail-checkbox" id="customCheck1">
+                        <label class="custom-control-label" for="customCheck1">变更详情</label>
+                    </div>
+                </div>
+                <% if (auditStatus === 3 || auditStatus === 4 || auditStatus === 5 || auditStatus === 7) { %>
+                    <div class="d-inline-block ml-3">
+                        <div class="custom-control custom-checkbox" style="line-height: normal;">
+                            <input type="checkbox" class="custom-control-input" id="show-table-detail">
+                            <label class="custom-control-label" for="show-table-detail">审批过程</label>
+                        </div>
+                    </div>
+                <% } %>
+            </div>
+            <div class="ml-auto" id="sp-btn">
+                <!--info状态区分-->
+                <% if (auditStatus === 1) { %>
+                    <a href="#sub-ap" data-category="up_change" data-toggle="modal" data-target="#sub-ap" class="btn btn-primary btn-sm">上报审批</a>
+                <% } else if (auditStatus === 2) { %>
+                    <a href="#sub-sp2" data-category="up_change" data-toggle="modal" data-target="#sub-sp2" class="btn btn-primary btn-sm">重新上报</a>
+                <% } else if (auditStatus === 3) { %>
+                    <a href="#sp-list" data-toggle="modal" data-target="#sp-list" class="btn btn-outline-warning btn-sm text-muted">审批退回</a>
+                <% } else if (auditStatus === 4) { %>
+                    <a href="#sp-list" data-toggle="modal" data-target="#sp-list" class="btn btn-outline-success btn-sm">审批完成</a>
+                <% } else if (auditStatus === 5) { %>
+                    <a href="#sp-list" data-toggle="modal" data-target="#sp-list" class="btn btn-outline-danger btn-sm">审批终止</a>
+                <% } else if (auditStatus === 6) { %>
+                    <a href="#sp-done" data-toggle="modal" data-target="#sp-done" class="btn btn-success btn-sm">审批通过</a>
+                    <a href="#sp-back" data-toggle="modal" data-target="#sp-back" class="btn btn-warning btn-sm">审批退回</a>
+                <% } else if (auditStatus === 7) { %>
+                    <a href="#sp-list" data-toggle="modal" data-target="#sp-list" class="btn btn-outline-secondary btn-sm">审批中</a>
+                <% } %>
+                <% if (auditStatus === 4 && ctx.session.sessionUser.accountId === auditList[auditList.length-1].uid) { %>
+                    <% if (stageChangeNum === 0) { %>
+                        <!--重新审批-->
+                        <a href="javascript: void(0);" data-toggle="modal" data-target="#sp-down-back" class="btn btn-warning btn-sm">重新审批</a>
+                    <% } else { %>
+                        <button class="btn btn-outline-secondary btn-sm" data-toggle="tooltip" data-placement="bottom" title="已被调用">重新审批</button>
+                    <% } %>
+                <% } %>
+            </div>
+            <!--info状态区分-->
+            <% 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 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>
+            <% } %>
+        </div>
+    </div>
+    <div class="content-wrap">
+        <div class="c-header p-0"></div>
+        <div class="w-100 sub-content row">
+            <div class="c-body col-4">
+                <div class="sjs-bar-1">
+                    <input type="hidden" id="tenderId" value="<%- ctx.tender.id %>">
+                    <input type="hidden" id="changeId" value="<%- ctx.change.cid %>">
+                    <ul class="nav nav-tabs">
+                        <li class="nav-item">
+                            <a class="nav-link active" data-toggle="tab" data-tab="bgxinxi" href="#bgxinxi" role="tab">变更信息</a>
+                        </li>
+                        <li class="nav-item">
+                            <a class="nav-link" data-toggle="tab" data-tab="bgfujian" href="#bgfujian" role="tab">附件</a>
+                        </li>
+                        <li class="nav-item ml-auto pt-1" id="fujian_btn" style="display:none;">
+                            <!--所有附件 翻页-->
+                            <a href="javascript: void(0);" data-toggle="modal" class="btn btn-sm btn-primary" id="bach-download"><i class="fa fa-download "></i> 批量下载</a>
+                            <a href="javascript:void(0);" class="page-select ml-3" content="pre"><i class="fa fa-chevron-left"></i></a> <span id="currentPage">1</span>/<span id="totalPage">10</span> <a href="javascript:void(0);" class="page-select mr-3" content="next"><i class="fa fa-chevron-right"></i></a>
+                            <a href="#addfujian" data-toggle="modal" class="btn btn-sm btn-light text-primary" data-placement="bottom" title="" data-original-title="上传附件"><i class="fa fa-cloud-upload" aria-hidden="true"></i> 上传附件</a>
+                            <a href="" id="downloadZip" style="display: none;" download></a>
+                        </li>
+                    </ul>
+                </div>
+                <div class="tab-content">
+                    <div class="tab-pane active" id="bgxinxi">
+                        <div class="sjs-sh-1" style="overflow-y: auto;">
+                            <% if (auditStatus === 1 || auditStatus === 2) { %>
+                            <form class="p-2" action="/tender/<%- change.tid %>/change/<%- change.cid %>/information/save?_csrf=<%= ctx.csrf %>" method="post" id="change_form">
+                                <div class="form-group">
+                                    <label><b class="text-danger">*&nbsp;</b>申请编号</label>
+                                    <a href="javascript:void(0);" class="pull-right reduction-code" data-toggle="tooltip" data-placement="bottom" title="" data-code="<%- change.code %>" data-original-title="重置"><i class="fa fa-repeat"></i></a>
+                                    <input class="form-control form-control-sm" name="code" value="<%- change.code %>" type="text">
+                                </div>
+                                <div class="form-group">
+                                    <label><b class="text-danger">*&nbsp;</b>工程名称</label>
+                                    <input class="form-control form-control-sm" name="name" value="<%- change.name %>" type="text">
+                                </div>
+                                <div class="form-group">
+                                    <label>桩号</label>
+                                    <input class="form-control form-control-sm" name="peg" value="<%- change.peg %>" type="text">
+                                </div>
+                                <div class="form-group">
+                                    <label>原设计图名称</label>
+                                    <input class="form-control form-control-sm" name="org_name" placeholder="" type="text" value="<%- change.org_name %>">
+                                </div>
+                                <div class="form-group">
+                                    <label>原图号</label>
+                                    <input class="form-control form-control-sm" name="org_code" placeholder="" type="text" value="<%- change.org_code %>">
+                                </div>
+                                <div class="form-group">
+                                    <label>变更设计图名称</label>
+                                    <input class="form-control form-control-sm" name="new_name" placeholder="" type="text" value="<%- change.new_name %>">
+                                </div>
+                                <div class="form-group">
+                                    <label>变更图号</label>
+                                    <input class="form-control form-control-sm" name="new_code" placeholder="" type="text" value="<%- change.new_code %>">
+                                </div>
+                                <div class="form-group">
+                                    <label><b class="text-danger">*&nbsp;</b>工程变更理由及内容</label>
+                                    <textarea class="form-control form-control-sm" name="content" rows="6"><%- change.content %></textarea>
+                                </div>
+                                <div class="form-group">
+                                    <label>工程变更合同依据</label>
+                                    <textarea class="form-control form-control-sm" name="basis" rows="6"><%- change.basis %></textarea>
+                                </div>
+                                <div class="form-group">
+                                    <label>变更工程量数量计算式</label>
+                                    <textarea class="form-control form-control-sm" name="expr" rows="2"><%- change.expr %></textarea>
+                                </div>
+                                <div class="form-group">
+                                    <label>备注</label>
+                                    <textarea class="form-control form-control-sm" name="memo" rows="3"><%- change.memo %></textarea>
+                                </div>
+                                <div class="form-group">
+                                    <label>变更类型</label>
+                                    <div class="checkbox">
+                                        <% const changeType = change.type !== null && change.type !== '' ? change.type.split(',') : '' %>
+                                        <% for (const t in changeConst.type) { %>
+                                            <% const cType = changeConst.type[t] %>
+                                            <label class="checkbox-inline">
+                                                <input value="<%- cType.value %>" name="type[]"
+                                                       type="checkbox"<% if (changeType.indexOf(cType.value.toString()) !== -1) { %> checked<% } %>><%- cType.name %>
+                                            </label>
+                                        <% } %>
+                                    </div>
+                                </div>
+                                <div class="form-group">
+                                    <label>变更类别 </label>
+                                    <select class="form-control form-control-sm" name="class">
+                                        <% for (const c in changeConst.class) { %>
+                                            <% const cClass = changeConst.class[c] %>
+                                            <option value="<%- cClass.value %>"<% if (cClass.value === change.class) { %> selected<% } %>><%- cClass.name %></option>
+                                        <% } %>
+                                    </select>
+                                </div>
+                                <div class="form-group">
+                                    <label>变更性质 </label>
+                                    <select class="form-control form-control-sm" name="quality">
+                                        <% for (const q in changeConst.quality) { %>
+                                            <% const cQuality = changeConst.quality[q] %>
+                                            <option value="<%- cQuality.value %>"<% if (cQuality.value === change.quality) { %> selected<% } %>><%- cQuality.name %></option>
+                                        <% } %>
+                                    </select>
+                                </div>
+                                <div class="form-group">
+                                    <label>变更提出单位</label>
+                                    <a href="#editcompany" style="float:right;" class="" data-toggle="modal">编辑</a>
+                                    <select class="form-control form-control-sm" id="company" name="company">
+                                        <% for (const company of companyList) { %>
+                                            <option <% if (company.name === change.company) { %>selected<% } %>><%- company.name %></option>
+                                        <% } %>
+                                    </select>
+                                </div>
+                                <div class="form-group">
+                                    <label>费用承担方</label>
+                                    <div class="radio">
+                                        <% for (const c in changeConst.charge) { %>
+                                            <% const cCharge = changeConst.charge[c] %>
+                                            <label class="radio-inline">
+                                                <input value="<%- cCharge.value %>" name="charge" type="radio"<% if (cCharge.value === change.charge) { %> checked<% } %>> <%- cCharge.name %>
+                                            </label>
+                                        <% } %>
+                                    </div>
+                                </div>
+                                <div class="form-group">
+                                    <label>批复文号</label>
+                                    <input class="form-control form-control-sm" id="w_code" name="w_code"  placeholder="" type="text" value="<%- change.w_code %>">
+                                </div>
+                            </form>
+                            <% } else { %>
+                            <form class="p-2">
+                                <div class="form-group">
+                                    <label>申请编号</label>
+                                    <input class="form-control form-control-sm" value="<%- change.code %>" type="text" readonly>
+                                </div>
+                                <% if (auditStatus === 4) { %>
+                                    <div class="form-group">
+                                        <label>变更令号(批复编号)</label>
+                                        <input class="form-control form-control-sm" value="<%- change.p_code %>" type="text" readonly>
+                                    </div>
+                                <% } %>
+                                <div class="form-group">
+                                    <label>工程名称</label>
+                                    <input class="form-control form-control-sm" value="<%- change.name %>" type="text" readonly>
+                                </div>
+                                <div class="form-group">
+                                    <label>桩号</label>
+                                    <input class="form-control form-control-sm" value="<%- change.peg %>" type="text" readonly>
+                                </div>
+                                <div class="form-group">
+                                    <label>原设计图名称</label>
+                                    <input class="form-control form-control-sm" placeholder="" type="text" value="<%- change.org_name %>" readonly>
+                                </div>
+                                <div class="form-group">
+                                    <label>原图号</label>
+                                    <input class="form-control form-control-sm" placeholder="" type="text" value="<%- change.org_code %>" readonly>
+                                </div>
+                                <div class="form-group">
+                                    <label>变更设计图名称</label>
+                                    <input class="form-control form-control-sm" placeholder="" type="text" value="<%- change.new_name %>" readonly>
+                                </div>
+                                <div class="form-group">
+                                    <label>变更图号</label>
+                                    <input class="form-control form-control-sm" placeholder="" type="text" value="<%- change.new_code %>" readonly>
+                                </div>
+                                <div class="form-group">
+                                    <label>工程变更理由及内容</label>
+                                    <textarea class="form-control form-control-sm" rows="6" readonly><%- change.content %></textarea>
+                                </div>
+                                <div class="form-group">
+                                    <label>工程变更合同依据</label>
+                                    <textarea class="form-control form-control-sm" rows="6" readonly><%- change.basis %></textarea>
+                                </div>
+                                <div class="form-group">
+                                    <label>变更工程量数量计算式</label>
+                                    <textarea class="form-control form-control-sm" rows="2" readonly><%- change.expr %></textarea>
+                                </div>
+                                <div class="form-group">
+                                    <label>备注</label>
+                                    <textarea class="form-control form-control-sm" rows="3" readonly><%- change.memo %></textarea>
+                                </div>
+                                <div class="form-group">
+                                    <label>变更类型</label>
+                                    <div class="checkbox">
+                                        <% const changeType = change.type !== null && change.type !== '' ? change.type.split(',') : '' %>
+                                        <% for (const t in changeConst.type) { %>
+                                            <% const cType = changeConst.type[t] %>
+                                            <label class="checkbox-inline">
+                                                <% if (changeType.indexOf(cType.value.toString()) !== -1) { %>
+                                                    <input value="<%- cType.value %>" type="checkbox" disabled checked><%- cType.name %>
+                                                <% } %>
+                                            </label>
+                                        <% } %>
+                                    </div>
+                                </div>
+                                <div class="form-group">
+                                    <label>变更类别 </label>
+                                    <select class="form-control form-control-sm" disabled>
+                                        <% for (const c in changeConst.class) { %>
+                                            <% const cClass = changeConst.class[c] %>
+                                            <% if (cClass.value === change.class) { %>
+                                                <option value="<%- cClass.value %>"><%- cClass.name %></option>
+                                            <% } %>
+                                        <% } %>
+                                    </select>
+                                </div>
+                                <div class="form-group">
+                                    <label>变更性质 </label>
+                                    <select class="form-control form-control-sm" disabled>
+                                        <% for (const q in changeConst.quality) { %>
+                                            <% const cQuality = changeConst.quality[q] %>
+                                            <% if (cQuality.value === change.quality) { %>
+                                                <option value="<%- cQuality.value %>"><%- cQuality.name %></option>
+                                            <% } %>
+                                        <% } %>
+                                    </select>
+                                </div>
+                                <div class="form-group">
+                                    <label>变更提出单位</label>
+                                    <select class="form-control form-control-sm" disabled>
+                                        <option><%- change.company %></option>
+                                    </select>
+                                </div>
+                                <div class="form-group">
+                                    <label>费用承担方</label>
+                                    <div class="radio">
+                                        <% for (const c in changeConst.charge) { %>
+                                            <% const cCharge = changeConst.charge[c] %>
+                                            <label class="radio-inline">
+                                                <% if (cCharge.value === change.charge) { %>
+                                                    <input value="<%- cCharge.value %>" type="radio" disabled checked> <%- cCharge.name %>
+                                                <% } %>
+                                            </label>
+                                        <% } %>
+                                    </div>
+                                </div>
+                                <div class="form-group">
+                                    <label>批复文号</label>
+                                    <input class="form-control form-control-sm" id="w_code" placeholder="" type="text" value="<%- change.w_code %>" <% if (auditStatus !== 6) { %>readonly<% } %>>
+                                </div>
+                            </form>
+                            <% } %>
+                        </div>
+                    </div>
+                    <div class="tab-pane" id="bgfujian">
+                        <div class="sjs-sh-1">
+                            <div class="p-2">
+                                <table class="table table-bordered" style="word-break:break-all; table-layout: fixed">
+                                    <thead>
+                                    <tr>
+                                        <td width="25" style="background-color: #e9ecef;"><input type="checkbox" id="check-all-file" ></td>
+                                        <th width="40">序号</th>
+                                        <th>名称</th>
+                                        <th width="100">上传时间/大小</th>
+                                        <th width="60">操作</th>
+                                    </tr>
+                                    </thead>
+                                    <tbody id="attList">
+                                    <% if (attList !== undefined && attList !== '') { %>
+                                        <% for (const [index,att] of attList.entries()) { %>
+                                            <tr>
+                                                <td width="25"><input type="checkbox" class="check-file" file-id=<%- att.id %>></td>
+                                                <td><%- index+1 %></td>
+                                                <td><a href="javascript: void(0);" class="file-atn" f-id="<%- att.id %>"><%- att.filename %><%- att.fileext %></a></td>
+                                                <td><%- moment(att.in_time * 1000).format('YYYY-MM-DD') %><br><%- ctx.helper.bytesToSize(att.filesize) %></td>
+                                                <td>
+                                                    <a href="/change/download/file/<%- att.id %>" class="mr-2" title="下载"><span class="fa fa-download text-primary"></span></a>
+                                                    <% if (att.uid === ctx.session.sessionUser.accountId && (auditStatus === 4 ? Boolean(att.extra_upload) : true )) { %>
+                                                        <a href="javascript:void(0)" class="mr-2 delete-file" data-attid="<%- att.id %>" title="删除附件"><span class="fa fa-trash text-danger"></span></a>
+                                                    <% } %>
+                                                </td>
+                                            </tr>
+                                        <% } %>
+                                    <% } %>
+                                    </tbody>
+                                </table>
+                                <a href="" id="file-upload" target="_blank" style="display: none;"></a>
+                            </div>
+                        </div>
+                    </div>
+                </div>
+            </div>
+            <div class="c-body col-8">
+                <div class="sjs-height-0" id="change-spread">
+                </div>
+            </div>
+        </div>
+    </div>
+</div>
+<script>
+    const totalPriceUnit = '<%- tpUnit %>';
+    const unitPriceUnit = '<%- upUnit %>';
+    const accountId = parseInt('<%- ctx.session.sessionUser.accountId %>');
+    const ledgeStatus = '<%- tender.ledger_status %>';
+    const ledgerConsts = JSON.parse('<%- JSON.stringify(ledgerConsts) %>');
+    const auditStatus = parseInt('<%- auditStatus %>');
+    const auditList = JSON.parse(unescape('<%- escape(JSON.stringify(auditList)) %>'));
+    const precision = JSON.parse('<%- JSON.stringify(precision) %>');
+    const whiteList = JSON.parse('<%- JSON.stringify(whiteList) %>');
+    let attData = JSON.parse(unescape('<%- escape(JSON.stringify(attList)) %>'));
+    autoFlashHeight();
+    $('a[href="#sub-ap"').click(function() {
+        if (parseInt(ledgeStatus) === ledgerConsts.uncheck) {
+            $('#warning-ledger').modal('show');
+            return false;
+        }
+    });
+    const readOnly = <%- change.readOnly %>;
+    const changeSpread = SpreadJsObj.createNewSpread($('#change-spread')[0]);
+    const changeSpreadSheet = changeSpread.getActiveSheet();
+    let changeList = JSON.parse(unescape('<%- escape(JSON.stringify(changeList)) %>'));
+    const style1 = new GC.Spread.Sheets.Style();
+    style1.locked = true;
+</script>
+<% if (auditStatus === 1 || auditStatus === 2) { %>
+<script>
+    let changeUnits = JSON.parse('<%- JSON.stringify(changeUnits) %>');
+    changeUnits = _.map(changeUnits, 'unit');
+    changeUnits.push('');
+    const accountGroup = JSON.parse(unescape('<%- escape(JSON.stringify(accountGroup)) %>'));
+    const accountList = JSON.parse(unescape('<%- escape(JSON.stringify(accountList)) %>'));
+    const shenpi_status = <%- ctx.tender.info.shenpi.change %>;
+    const shenpiConst = JSON.parse('<%- JSON.stringify(shenpiConst) %>');
+    const changesUid = <%- change.uid %>;
+
+    let back_changeInfo = {
+        code: '<%- change.code %>',
+        name: '<%- change.name %>',
+        peg: '<%- change.peg %>',
+        org_name: '<%- change.org_name %>',
+        org_code: '<%- change.org_code %>',
+        new_name: '<%- change.new_name %>',
+        new_code: '<%- change.new_code %>',
+        content: '<%- ctx.helper.replaceRntoBr(change.content) %>',
+        basis: '<%- ctx.helper.replaceRntoBr(change.basis) %>',
+        expr: '<%- ctx.helper.replaceRntoBr(change.expr) %>',
+        memo: '<%- ctx.helper.replaceRntoBr(change.memo) %>',
+        type: '<%- change.type %>',
+        class: '<%- change.class %>',
+        quality: '<%- change.quality %>',
+        company: '<%- change.company %>',
+        charge: '<%- change.charge %>',
+        w_code: '<%- change.w_code %>',
+    };
+    let changeInfo = Object.assign({}, back_changeInfo);
+</script>
+<script src="/public/js/change_information_set.js"></script>
+<script src="/public/js/change_audit.js"></script>
+<% } else if (auditStatus === 3 || auditStatus === 4 || auditStatus === 5 || auditStatus === 7) { %>
+<script>
+    const auditList2 = JSON.parse(unescape('<%- escape(JSON.stringify(auditList2)) %>'));
+    const aidList = _.map(auditList2, 'uid');
+    aidList.splice(0, 1);
+</script>
+<script src="/public/js/change_information_show.js"></script>
+<% } else if (auditStatus === 6) { %>
+<script>
+    const auditList2 = JSON.parse(unescape('<%- escape(JSON.stringify(auditList2)) %>'));
+    const aidList = _.map(auditList2, 'uid');
+    aidList.splice(0, 1);
+</script>
+<script src="/public/js/change_information_approval.js"></script>
+<% } %>

파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 1100 - 0
app/view/change/information_modal.ejs


+ 2 - 2
app/view/dashboard/index.ejs

@@ -112,7 +112,7 @@
                                                 <div class="row">
                                                     <div class="col-auto"><span class="badge badge-danger">变更审批</span></div>
                                                     <div class="col-6"><a href="/tender/<%- change.tid %>"><%- change.name %></a> 变更令 <%- change.ccode %></div>
-                                                    <div class="col-3 ml-auto text-right pl-0"><a href="/tender/<%- change.tid %>/change/<%- change.cid %>/info" class="btn btn-sm btn-outline-primary"><% if (change.cstatus === acChange.status.checking) { %>审批<% } else if (change.cstatus === acChange.status.backnew) { %>重新审批<% } else { %>重新上报<% } %></a></div>
+                                                    <div class="col-3 ml-auto text-right pl-0"><a href="/tender/<%- change.tid %>/change/<%- change.cid %>/information" class="btn btn-sm btn-outline-primary"><% if (change.cstatus === acChange.status.checking) { %>审批<% } else if (change.cstatus === acChange.status.backnew) { %>重新审批<% } else { %>重新上报<% } %></a></div>
                                                 </div>
                                                 <p class="mt-1 mb-0"><%- change.caname %><small class="ml-1 text-muted"><%- (role ? '- ' + role : '') %></small>
                                                     <span class="pull-right text-muted"><%- (change.sin_time ? ctx.moment(change.sin_time).format('YYYY-MM-DD HH:mm:ss') : '') %></span>
@@ -262,7 +262,7 @@
                                                         <div class="col-auto"><span class="badge badge-danger">变更审批</span></div>
                                                         <div class="col-6">
                                                             <a href="/tender/<%- notice.tid %>"><%- notice.name %></a>
-                                                            <a href="/tender/<%- notice.tid %>/change/<%- notice.cid %>/info"><%- notice.c_code %> </a>
+                                                            <a href="/tender/<%- notice.tid %>/change/<%- notice.cid %>/information"><%- notice.c_code %> </a>
                                                             <%- acChange.statusString[notice.status]%>
                                                         </div>
                                                     </div>

+ 6 - 6
app/view/ledger/bwtz.ejs

@@ -10,12 +10,12 @@
                             <i class="fa fa-list-ol"></i> 显示层级
                         </button>
                         <div class="dropdown-menu" aria-labelledby="dropdownMenuButton">
-                            <a class="dropdown-item" name="showLevel" tag="1" href="javascirpt: void(0);">第一层</a>
-                            <a class="dropdown-item" name="showLevel" tag="2" href="javascirpt: void(0);">第二层</a>
-                            <a class="dropdown-item" name="showLevel" tag="3" href="javascirpt: void(0);">第三层</a>
-                            <a class="dropdown-item" name="showLevel" tag="4" href="javascirpt: void(0);">第四层</a>
-                            <a class="dropdown-item" name="showLevel" tag="5" href="javascirpt: void(0);">第五层</a>
-                            <a class="dropdown-item" name="showLevel" tag="last" href="javascirpt: void(0);">最底层</a>
+                            <a class="dropdown-item" name="showLevel" tag="1" href="javascript:void(0);">第一层</a>
+                            <a class="dropdown-item" name="showLevel" tag="2" href="javascript:void(0);">第二层</a>
+                            <a class="dropdown-item" name="showLevel" tag="3" href="javascript:void(0);">第三层</a>
+                            <a class="dropdown-item" name="showLevel" tag="4" href="javascript:void(0);">第四层</a>
+                            <a class="dropdown-item" name="showLevel" tag="5" href="javascript:void(0);">第五层</a>
+                            <a class="dropdown-item" name="showLevel" tag="last" href="javascript:void(0);">最底层</a>
                         </div>
                     </div>
                 </div>

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

@@ -10,13 +10,13 @@
                             <i class="fa fa-list-ol"></i> 显示层级
                         </button>
                         <div class="dropdown-menu" aria-labelledby="dropdownMenuButton">
-                            <a class="dropdown-item" name="showLevel" tag="1" href="javascirpt: void(0);">第一层</a>
-                            <a class="dropdown-item" name="showLevel" tag="2" href="javascirpt: void(0);">第二层</a>
-                            <a class="dropdown-item" name="showLevel" tag="3" href="javascirpt: void(0);">第三层</a>
-                            <a class="dropdown-item" name="showLevel" tag="4" href="javascirpt: void(0);">第四层</a>
-                            <a class="dropdown-item" name="showLevel" tag="5" href="javascirpt: void(0);">第五层</a>
-                            <a class="dropdown-item" name="showLevel" tag="last" href="javascirpt: void(0);">最底层</a>
-                            <a class="dropdown-item" name="showLevel" tag="leafXmj" href="javascirpt: void(0);">只显示项目节</a>
+                            <a class="dropdown-item" name="showLevel" tag="1" href="javascript:void(0);">第一层</a>
+                            <a class="dropdown-item" name="showLevel" tag="2" href="javascript:void(0);">第二层</a>
+                            <a class="dropdown-item" name="showLevel" tag="3" href="javascript:void(0);">第三层</a>
+                            <a class="dropdown-item" name="showLevel" tag="4" href="javascript:void(0);">第四层</a>
+                            <a class="dropdown-item" name="showLevel" tag="5" href="javascript:void(0);">第五层</a>
+                            <a class="dropdown-item" name="showLevel" tag="last" href="javascript:void(0);">最底层</a>
+                            <a class="dropdown-item" name="showLevel" tag="leafXmj" href="javascript:void(0);">只显示项目节</a>
                         </div>
                     </div>
                 </div>

+ 11 - 3
app/view/ledger/explode_modal.ejs

@@ -16,9 +16,17 @@
                     <h6>选择导入的工作表</h6>
                 </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="upload-ledger-ok">确认上传</button>
+            <div class="modal-footer d-flex justify-content-between">
+                <div class="">
+                    <div class="custom-control custom-checkbox custom-control-inline">
+                        <input type="radio" class="custom-control-input" name="customRadioInline1" id="customCheck3" checked="">
+                        <label class="custom-control-label" for="customCheck3">过滤清单数量和单价为0项</label>
+                    </div>
+                </div>
+                <div>
+                    <button type="button" class="btn btn-secondary btn-sm" data-dismiss="modal">关闭</button>
+                    <button type="button" class="btn btn-primary btn-sm" id="upload-ledger-ok">确认上传</button>
+                </div>
             </div>
         </div>
     </div>

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

@@ -7,7 +7,7 @@
                 <a class="btn btn-sm btn-light">
                     <div class="custom-control custom-checkbox">
                         <input type="checkbox" class="custom-control-input" id="compare-tag" checked="checked">
-                        <label class="custom-control-label text-primary" for="compare-tag">签约-台账≠0</label>
+                        <label class="custom-control-label text-primary" for="compare-tag">台账-签约≠0</label>
                     </div>
                 </a>
             </div>
@@ -68,4 +68,5 @@
 <script>
     const chapter = JSON.parse('<%- JSON.stringify(ctx.tender.info.chapter) %>');
     const thousandth = <%- ctx.tender.info.display.thousandth %>;
+    const filter = JSON.parse('<%- JSON.stringify(chapterFilter) %>');
 </script>

+ 2 - 2
app/view/material/audit_modal.ejs

@@ -682,8 +682,8 @@
 <% } %>
 <% if (ctx.session.sessionUser.accountId === ctx.material.user_id && (ctx.material.status === auditConst.status.uncheck || ctx.material.status === auditConst.status.checkNo)) { %>
     <script>
-        const accountGroup = JSON.parse('<%- JSON.stringify(accountGroup) %>');
-        const accountList = JSON.parse('<%- JSON.stringify(accountList) %>');
+        const accountGroup = JSON.parse(unescape('<%- escape(JSON.stringify(accountGroup)) %>'));
+        const accountList = JSON.parse(unescape('<%- escape(JSON.stringify(accountList)) %>'));
         const shenpi_status = <%- ctx.tender.info.shenpi.material %>;
         const shenpiConst =  JSON.parse('<%- JSON.stringify(shenpiConst) %>');
     </script>

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

@@ -118,6 +118,6 @@
   const tid = '<%- ctx.tender.id %>';
   const mid = '<%- ctx.material.id %>';
   const order = '<%- ctx.material.order %>';
-  const fileList = JSON.parse('<%- JSON.stringify(fileList) %>');
+  const fileList = JSON.parse(unescape('<%- escape(JSON.stringify(fileList)) %>'));
   const whiteList = JSON.parse('<%- JSON.stringify(whiteList) %>');
 </script>

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

@@ -42,7 +42,7 @@
                                 <label class="col-auto col-form-label"><%= smsType[s].name %>
                                     <!--<a href="#sms-view" data-toggle="modal" data-target="#sms-view" class="ml-2"><i class="fa fa-info-circle"></i></a>-->
                                 </label>
-                                <div class="col-5">
+                                <div class="col-3">
                                     <% for (const c of smsType[s].children) { %>
                                     <div class="form-check ">
                                         <input class="form-check-input" id="<%= s %>_<%- c.value %>" type="checkbox" name="<%= s %>[]" value="<%= c.value %>" <% if (user_wxType !== null && user_wxType[s] !== undefined && user_wxType[s].indexOf(c.value.toString()) !== -1) { %>checked<% } %>>

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

@@ -226,8 +226,8 @@
     });
 </script>
 <script>
-    const tenders = JSON.parse('<%- JSON.stringify(tenderList) %>');
-    const category = JSON.parse('<%- JSON.stringify(categoryData) %>');
+    const tenders = JSON.parse(unescape('<%- escape(JSON.stringify(tenderList)) %>'));
+    const category = JSON.parse(unescape('<%- escape(JSON.stringify(categoryData)) %>'));
     const auditConst = JSON.parse('<%- JSON.stringify(auditConst) %>');
     const ledgerAuditConst = JSON.parse('<%- JSON.stringify(ledgerAuditConst) %>');
     $(document).ready(() => {

+ 30 - 25
app/view/report/rpt_all_popup.ejs

@@ -457,21 +457,23 @@
         let _pushRptLine = function (parentItem, rptItem, level, needChk, parentNodeIdStr, thisItemSeq) {
             if (rptItem.nodeType === 1) {
                 let amt = _countAvailableTpls(rptItem);
-                let classStr = '';
-                if (level > 0) {
-                    classStr = 'pl-' + (level + 3);
-                }
-                if (needChk) {
-                    let chkName = parentItem.name + FOLDER_SEPERATER + rptItem.name;
-                    let checkedStr = (checkingArr.indexOf(chkName) >= 0) ? ' checked' : '';
-                    let sIdStr = parentNodeIdStr + '_sub_' + thisItemSeq;
-                    tbDom.append('<tr><td class="' + classStr + '">' + rptItem.name + '</td><td>' + amt + '</td><td><input id="' + sIdStr + '" onchange="changeFolder(this, ' + isCommonStr + ', \'' + parentNodeIdStr + '\' )" hiddenval="' + chkName + '" type="checkbox"' + checkedStr + '></td></tr>');
-                } else {
-                    tbDom.append('<tr><td class="' + classStr + '">' + rptItem.name + '</td><td>' + amt + '</td><td></td></tr>');
-                }
-                if (rptItem.items && rptItem.items.length > 0) {
-                    for (const subItem of rptItem.items) {
-                        _pushRptLine(rptItem, subItem, level + 1, false, '');
+                if (amt > 0) {
+                    let classStr = '';
+                    if (level > 0) {
+                        classStr = 'pl-' + (level + 3);
+                    }
+                    if (needChk) {
+                        let chkName = parentItem.name + FOLDER_SEPERATER + rptItem.name;
+                        let checkedStr = (checkingArr.indexOf(chkName) >= 0) ? ' checked' : '';
+                        let sIdStr = parentNodeIdStr + '_sub_' + thisItemSeq;
+                        tbDom.append('<tr><td class="' + classStr + '">' + rptItem.name + '</td><td>' + amt + '</td><td><input id="' + sIdStr + '" onchange="changeFolder(this, ' + isCommonStr + ', \'' + parentNodeIdStr + '\' )" hiddenval="' + chkName + '" type="checkbox"' + checkedStr + '></td></tr>');
+                    } else {
+                        tbDom.append('<tr><td class="' + classStr + '">' + rptItem.name + '</td><td>' + amt + '</td><td></td></tr>');
+                    }
+                    if (rptItem.items && rptItem.items.length > 0) {
+                        for (const subItem of rptItem.items) {
+                            _pushRptLine(rptItem, subItem, level + 1, false, '');
+                        }
                     }
                 }
             }
@@ -480,18 +482,21 @@
         let parentIdx = 0;
         let subCnt = topTreeNode.items.length;
         for (const topItem of topTreeNode.items) {
-            TplAmts.push(_countAvailableTpls(topItem));
-            let checkedStr = (checkingArr.indexOf(topItem.name) >= 0) ? ' checked' : '';
-            let pIdStr = tbDomId + '_' + parentIdx + '_' + subCnt;
-            tbDom.append('<tr><td>' + topItem.name + '</td><td>' + TplAmts[TplAmts.length - 1] + '</td><td><input id="' + pIdStr + '" onchange="changeFolder(this, ' + isCommonStr + ', null)" hiddenval="' + topItem.name + '" type="checkbox"' + checkedStr + '></td></tr>');
-            if (topItem.items && topItem.items.length > 0) {
-                let subSeq = 0;
-                for (const subItem of topItem.items) {
-                    _pushRptLine(topItem, subItem, 1, true, pIdStr, subSeq);
+            const avRpts = _countAvailableTpls(topItem);
+            if (avRpts > 0) {
+                TplAmts.push(avRpts);
+                let checkedStr = (checkingArr.indexOf(topItem.name) >= 0) ? ' checked' : '';
+                let pIdStr = tbDomId + '_' + parentIdx + '_' + subCnt;
+                tbDom.append('<tr><td>' + topItem.name + '</td><td>' + TplAmts[TplAmts.length - 1] + '</td><td><input id="' + pIdStr + '" onchange="changeFolder(this, ' + isCommonStr + ', null)" hiddenval="' + topItem.name + '" type="checkbox"' + checkedStr + '></td></tr>');
+                if (topItem.items && topItem.items.length > 0) {
+                    let subSeq = 0;
+                    for (const subItem of topItem.items) {
+                        _pushRptLine(topItem, subItem, 1, true, pIdStr, subSeq);
+                    }
+                    subSeq++;
                 }
-                subSeq++;
+                parentIdx++;
             }
-            parentIdx++;
         }
     }
 

+ 1 - 0
app/view/revise/gcl_compare.ejs

@@ -76,4 +76,5 @@
 <script>
     const chapter = JSON.parse('<%- JSON.stringify(ctx.tender.info.chapter) %>');
     const thousandth = <%- ctx.tender.info.display.thousandth %>;
+    const filter = JSON.parse('<%- JSON.stringify(chapterFilter) %>');
 </script>

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

@@ -724,8 +724,8 @@
     <% if(ctx.session.sessionUser.accountId === revise.uid && (revise.status === auditConst.status.uncheck || revise.status === auditConst.status.checkNo)) { %>
     <script>
         const cur_uid = '<%- ctx.session.sessionUser.accountId %>';
-        const accountList = JSON.parse('<%- JSON.stringify(accountList) %>');
-        const accountGroup = JSON.parse('<%- JSON.stringify(accountGroup ) %>');
+        const accountList = JSON.parse(unescape('<%- escape(JSON.stringify(accountList)) %>'));
+        const accountGroup = JSON.parse(unescape('<%- escape(JSON.stringify(accountGroup )) %>'));
         const shenpi_status = <%- ctx.tender.info.shenpi.revise %>;
         const shenpiConst = JSON.parse('<%- JSON.stringify(shenpiConst) %>');
         let timer = null;

+ 323 - 0
app/view/schedule/index.ejs

@@ -0,0 +1,323 @@
+<% include ../tender/tender_sub_menu.ejs %>
+<div class="panel-content">
+    <div class="panel-title">
+        <div class="title-main d-flex">
+            <% include ../tender/tender_sub_mini_menu.ejs %>
+            <h2>
+                计划至至 2020年12月
+            </h2>
+            <div class="ml-auto">
+                <a href="/tender/<%- ctx.tender.id %>/schedule/ledger" class="btn btn-sm btn-outline-primary">进度台帐</a>
+            </div>
+        </div>
+    </div>
+    <div class="content-wrap">
+        <div class="c-body">
+            <div class="sjs-height-0">
+                <div class="m-3">
+                    <!--模式切换-->
+                    <div class="col-12 mb-4">
+                        <ul class="nav nav-tabs justify-content-center">
+                            <li class="nav-item">
+                                <a class="nav-link px-5 py-2 active" href="#">金额模式</a>
+                            </li>
+                            <li class="nav-item">
+                                <a class="nav-link px-5 py-2" href="#">工程量模式</a>
+                            </li>
+                        </ul>
+                    </div>
+                    <!--金额概况-->
+                    <div class="row mx-0 mb-3 justify-content-center">
+                        <div class="col-auto p-0">
+                            <div class="card text-center">
+                                <div class="card-body">
+                                    <h5 class="card-title">163,000.00 <small class="text-danger"  data-toggle="tooltip" data-placement="bottom" title="" data-original-title="占设计比例">20%</small></h5>
+                                    <p class="card-text text-muted">计划完成金额</p>
+                                </div>
+                            </div>
+                        </div>
+                        <div class="col-auto pr-0">
+                            <div class="card text-center">
+                                <div class="card-body">
+                                    <h5 class="card-title">50,000.00 <small class="text-danger"  data-toggle="tooltip" data-placement="bottom" title="" data-original-title="占计划比例">5%</small></h5>
+                                    <p class="card-text text-muted">已完成计划金额</p>
+                                </div>
+                            </div>
+                        </div>
+                        <div class="col-auto pr-0">
+                            <div class="card text-center">
+                                <div class="card-body">
+                                    <h5 class="card-title">50,000.00 <small class="text-danger"  data-toggle="tooltip" data-placement="bottom" title="" data-original-title="占设计比例">15%</small></h5>
+                                    <p class="card-text text-muted">实际完成金额</p>
+                                </div>
+                            </div>
+                        </div>
+                    </div>
+                    <!--进度条-->
+                    <div class="mb-3">
+                        计划工程量进度
+                        <div class="progress">
+                            <div class="progress-bar bg-info" style="width:24%;" data-placement="bottom" data-toggle="tooltip" data-original-title="计划完成工程量:30,000.00">24%</div>
+                            <div class="progress-bar bg-gray" style="width:76%;" data-placement="bottom" data-toggle="tooltip" data-original-title="合同未计划:930,00.00">66%</div>
+                        </div>
+                        <p class="mt-2 mb-0">实际工程量进度</p>
+                        <div class="progress">
+                            <div class="progress-bar bg-info" style="width: 18%;" data-placement="bottom" data-toggle="tooltip" data-original-title="实际完成工程量:40,000.00">18%</div>
+                            <div class="progress-bar bg-gray" style="width:82%;" data-placement="bottom" data-toggle="tooltip" data-original-title="合同未完成:930,00.00">72%</div>
+                        </div>
+                        <p class="mt-2 mb-0">计划金额进度(万元)</p>
+                        <div class="progress">
+                            <div class="progress-bar bg-success" style="width:24%;" data-placement="bottom" data-toggle="tooltip" data-original-title="计划完成金额:¥300.00">24%</div>
+                            <div class="progress-bar bg-gray" style="width:76%;" data-placement="bottom" data-toggle="tooltip" data-original-title="合同未计划:¥930.00">66%</div>
+                        </div>
+                        <p class="mt-2 mb-0">实际金额进度哦(万元)</p>
+                        <div class="progress">
+                            <div class="progress-bar bg-success" style="width: 18%;" data-placement="bottom" data-toggle="tooltip" data-original-title="实际完成金额:¥400.00">18%</div>
+                            <div class="progress-bar bg-gray" style="width:82%;" data-placement="bottom" data-toggle="tooltip" data-original-title="合同未完成:¥930.00">72%</div>
+                        </div>
+                    </div>
+                    <!--图表-->
+                    <div class="card mb-3">
+                        <div class="card-body">
+                            <h5 class="card-title">工程量进度表</h5>
+                            <div id="chartContainer3" style="height: 300px; width: 100%;">
+                            </div>
+                        </div>
+                    </div>
+                    <div class="card mb-3">
+                        <div class="card-body">
+                            <h5 class="card-title">完成金额进度表</h5>
+                            <div id="chartContainer4" style="height: 300px; width: 100%;">
+                            </div>
+                        </div>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+</div>
+<script src="/public/js/echarts/echarts.min.js"></script>
+<script type="text/javascript">
+    //计划进度//
+    // 基于准备好的dom,初始化echarts图表
+    var myChart = echarts.init(document.getElementById('chartContainer3'));
+    var option = {
+        color: ['#d38b70','#8fb7cf','#cd5c5c','#ffa500','#40e0d0',
+            '#17a2b8','#28a745','#e4575a','#959eac','#6699FF',
+            '#1e90ff','#ff6347','#7b68ee','#00fa9a','#ffd700',
+            '#5c616b','#ff6666','#3cb371','#b8860b','#30e0e0'],
+        title : {
+            text: ''
+        },
+        tooltip : {
+            trigger: 'axis'
+        },
+        calculable : true,
+        legend: {
+            data:['计划完成工程量','实际完成工程量','实际占设计比例']
+        },
+        dataZoom: [
+            {show: true,start: 0, end: 100}
+        ],
+        xAxis : [
+            {
+                type : 'category',
+                splitLine : {show : true},
+                data : ['2020年1月','2020年2月','2020年3月','2020年4月','2020年5月','2020年6月','2020年7月']
+            }
+        ],
+        yAxis : [
+            {
+                type : 'value',
+                name : '工程量',
+                position:'left',
+                axisLabel : {
+                    formatter: '{value}'
+                },
+                splitArea : {show : true}
+            },
+            {
+                type : 'value',
+                name:'完成度',
+                axisLabel : {
+                    formatter: '{value} %'
+                },
+                position: 'right',
+                splitArea : {show : true}
+            }
+        ],
+        series : [
+            {
+                name:'计划完成工程量',
+                type:'bar',
+                tooltip : {trigger: 'item',formatter: "{b}<br/>{a}:{c}"},
+                stack: '计划',
+                data:[320, 332, 301, 334, 390, 330, 320]
+            },
+            {
+                name:'实际完成工程量',
+                type:'bar',
+                tooltip : {trigger: 'item',formatter: "{b}<br/>{a}:{c}"},
+                stack: '实际',
+                data:[310, 330, 301, 334, 390, 330, 320]
+            },
+            {
+                name:'实际占设计比例',
+                type:'line',
+                tooltip : {trigger: 'axis',formatter: "{b}占合同比例<br/>{a}:{c} %"},
+                yAxisIndex: 1,
+                data:[10, 15, 20, 13, 11, 9, 5]
+            },
+        ]
+    };
+    // 为echarts对象加载数据
+    myChart.setOption(option);
+    //4 完成金额进度进度//
+    var myChart = echarts.init(document.getElementById('chartContainer4'));
+    var option = {
+        color: ['#e9af68','#57b7b6','#e4575a','#959eac','#6699FF',
+            '#d38b70','#8fb7cf','#cd5c5c','#ffa500','#40e0d0',
+            '#1e90ff','#ff6347','#7b68ee','#00fa9a','#ffd700',
+            '#5c616b','#ff6666','#3cb371','#b8860b','#30e0e0'],
+        title : {
+            text: ''
+        },
+        tooltip : {
+            trigger: 'axis'
+        },
+        calculable : true,
+        legend: {
+            data:['计划完成金额','实际完成金额','实际占设计比例']
+        },
+        dataZoom: [
+            {show: true,start: 0, end: 100}
+        ],
+        xAxis : [
+            {
+                type : 'category',
+                splitLine : {show : true},
+                data : ['2020年1月','2020年2月','2020年3月','2020年4月','2020年5月','2020年6月','2020年7月']
+            }
+        ],
+        yAxis : [
+            {
+                type : 'value',
+                name : '金额',
+                position:'left',
+                axisLabel : {
+                    formatter: '{value} 万元'
+                },
+                splitArea : {show : true}
+            },
+            {
+                type : 'value',
+                name:'完成度',
+                axisLabel : {
+                    formatter: '{value} %'
+                },
+                position: 'right',
+                splitArea : {show : true}
+            }
+        ],
+        series : [
+            {
+                name:'计划完成金额',
+                type:'bar',
+                tooltip : {trigger: 'item',formatter: "{b}<br/>{a}:{c}万元"},
+                stack: '计划',
+                data:[320, 332, 301, 334, 390, 330, 320]
+            },
+            {
+                name:'实际完成金额',
+                type:'bar',
+                tooltip : {trigger: 'item',formatter: "{b}<br/>{a}:{c}万元"},
+                stack: '实际',
+                data:[310, 330, 301, 334, 390, 330, 320]
+            },
+            {
+                name:'实际占合同比例',
+                type:'line',
+                tooltip : {trigger: 'axis',formatter: "{b}占合同比例<br/>{a}:{c} %"},
+                yAxisIndex: 1,
+                data:[10, 15, 20, 13, 11, 9, 5]
+            },
+        ]
+    };
+
+    // 为echarts对象加载数据
+    myChart.setOption(option);
+</script>
+<!--sjs-->
+<script>
+    $(document).ready(function () {
+        const data = [
+            {
+                单位:'稍等',
+                精度:3,
+            },{
+                单位:'km',
+                精度:3,
+            },{
+                单位:'m',
+                精度:3,
+            },{
+                单位:'m2',
+                精度:3,
+            },{
+                单位:'m3',
+                精度:3,
+            },{
+                单位:'kg',
+                精度:3,
+            },{
+                单位:'个',
+                精度:3,
+            },{
+                单位:'台',
+                精度:3,
+            },{
+                单位:'套',
+                精度:3,
+            },{
+                单位:'棵',
+                精度:3,
+            },{
+                单位:'组',
+                精度:3,
+            },{
+                单位:'总额',
+                精度:3,
+            },{
+                单位:'系统',
+                精度:3,
+            },{
+                单位:'其他未列单位',
+                精度:3,
+            }
+        ];
+        const spread = new GC.Spread.Sheets.Workbook($('#option-spread1')[0], {
+            sheetCount: 1
+        });
+        spread.getActiveSheet().setDataSource(data);
+        spread.options.tabStripVisible = false;
+    })
+</script>
+<script src="/public/js/sub_menu.js"></script>
+<script>
+    $.subMenu({
+        menu: '#sub-menu', miniMenu: '#sub-mini-menu', miniMenuList: '#mini-menu-list',
+        toMenu: '#to-menu', toMiniMenu: '#to-mini-menu',
+        key: 'menu.1.0.0',
+        miniHint: '#sub-mini-hint', hintKey: 'menu.hint.1.0.1',
+        callback: function (info) {
+            if (info.mini) {
+                $('.panel-title').addClass('fluid');
+                $('#sub-menu').removeClass('panel-sidebar');
+            } else {
+                $('.panel-title').removeClass('fluid');
+                $('#sub-menu').addClass('panel-sidebar');
+            }
+            autoFlashHeight();
+        }
+    });
+</script>

+ 63 - 0
app/view/schedule/ledger.ejs

@@ -0,0 +1,63 @@
+<% include ../tender/tender_sub_menu.ejs %>
+<div class="panel-content">
+    <div class="panel-title">
+        <div class="title-main d-flex">
+            <% include ../tender/tender_sub_mini_menu.ejs %>
+            <div>
+                <div class="d-inline-block">
+                    <a class="btn btn-sm btn-light">
+                        <div class="custom-control custom-checkbox">
+                            <input type="checkbox" class="custom-control-input" id="customCheckDisabled" checked="">
+                            <label class="custom-control-label text-primary" for="customCheckDisabled">自动选择同级项</label>
+                        </div>
+                    </a>
+                </div>
+                <div class="d-inline-block">
+                    <a class="btn btn-sm btn-light">
+                        <div class="custom-control custom-checkbox">
+                            <input type="checkbox" class="custom-control-input" id="customCheckDisabled2">
+                            <label class="custom-control-label text-primary" for="customCheckDisabled2">跨级选择同级项</label>
+                        </div>
+                    </a>
+                </div>
+            </div>
+            <div class="ml-auto">
+                <a href="#add-qi" data-toggle="modal" data-target="#add-qi" class="btn btn-primary btn-sm pull-right">确认提交<div></div></a>
+            </div>
+        </div>
+    </div>
+    <div class="content-wrap">
+        <div class="c-header p-0"></div>
+        <div class="w-100 sub-content row">
+            <div class="c-body col-12">
+                <div class="sjs-height-0" style="overflow: auto;" id="ledger-spread">
+                </div>
+            </div>
+        </div>
+    </div>
+</div>
+<script src="/public/js/sub_menu.js"></script>
+<script>
+    const tender = JSON.parse('<%- JSON.stringify(tender) %>');
+    const tenderInfo = JSON.parse(unescape('<%- escape(JSON.stringify(tenderInfo)) %>'));
+    const thousandth = <%- ctx.tender.info.display.thousandth %>;
+    const measureType = JSON.parse('<%- JSON.stringify(measureType) %>');
+</script>
+<script>
+    $.subMenu({
+        menu: '#sub-menu', miniMenu: '#sub-mini-menu', miniMenuList: '#mini-menu-list',
+        toMenu: '#to-menu', toMiniMenu: '#to-mini-menu',
+        key: 'menu.1.0.0',
+        miniHint: '#sub-mini-hint', hintKey: 'menu.hint.1.0.1',
+        callback: function (info) {
+            if (info.mini) {
+                $('.panel-title').addClass('fluid');
+                $('#sub-menu').removeClass('panel-sidebar');
+            } else {
+                $('.panel-title').removeClass('fluid');
+                $('#sub-menu').addClass('panel-sidebar');
+            }
+            autoFlashHeight();
+        }
+    });
+</script>

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

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

+ 1 - 1
app/view/setting/category.ejs

@@ -30,7 +30,7 @@
 <script>
     let cData = JSON.parse('<%- JSON.stringify(categoryData) %>');
     const cType = JSON.parse('<%- JSON.stringify(categoryType) %>');
-    let tenders = JSON.parse('<%- JSON.stringify(tenderData) %>');
+    let tenders = JSON.parse(unescape('<%- escape(JSON.stringify(tenderData)) %>'));
 </script>
 <script src="/public/js/category.js"></script>
 <script>autoFlashHeight();</script>

+ 1 - 1
app/view/setting/category_modal.ejs

@@ -58,7 +58,7 @@
             <div class="modal-body">
                 <table class="table table-bordered">
                     <thead>
-                    <tr><th>值</th><th>包含标段</th><th>删除</th></tr>
+                    <tr><th width="40">排序</th><th>值</th><th>包含标段</th><th>删除</th></tr>
                     </thead>
                     <tbody id="value-list">
                     </tbody>

+ 1 - 1
app/view/setting/user.ejs

@@ -72,7 +72,7 @@
     </div>
 </div>
 <script>
-    const accountList = JSON.parse('<%- JSON.stringify(accountData) %>');
+    const accountList = JSON.parse(unescape('<%- escape(JSON.stringify(accountData)) %>'));
 </script>
 <script src="/public/js/setting.js"></script>
 <script>autoFlashHeight();</script>

+ 0 - 0
app/view/shares/import_excel_modal.ejs


이 변경점에서 너무 많은 파일들이 변경되어 몇몇 파일들은 표시되지 않았습니다.