فهرست منبع

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

# Conflicts:
#	app/view/report/index.ejs
TonyKang 4 سال پیش
والد
کامیت
8270ebe448
76فایلهای تغییر یافته به همراه5156 افزوده شده و 239 حذف شده
  1. 2 1
      app/base/base_bills_service.js
  2. 11 0
      app/const/sms_type.js
  3. 1 1
      app/const/wechat_template.js
  4. 2 1
      app/controller/advance_controller.js
  5. 359 2
      app/controller/change_controller.js
  6. 2 1
      app/controller/ledger_audit_controller.js
  7. 1 1
      app/controller/ledger_controller.js
  8. 2 1
      app/controller/material_controller.js
  9. 3 2
      app/controller/revise_controller.js
  10. 2 1
      app/controller/stage_controller.js
  11. 15 0
      app/controller/tender_controller.js
  12. 5 1
      app/lib/analysis_excel.js
  13. 111 0
      app/lib/bills_utils.js
  14. 1 1
      app/lib/ledger.js
  15. 25 0
      app/lib/wechat.js
  16. 83 0
      app/middleware/change_check.js
  17. 9 0
      app/public/js/category.js
  18. 1 1
      app/public/js/change.js
  19. 200 0
      app/public/js/change_audit.js
  20. 273 0
      app/public/js/change_information.js
  21. 390 0
      app/public/js/change_information_approval.js
  22. 921 0
      app/public/js/change_information_set.js
  23. 204 0
      app/public/js/change_information_show.js
  24. 22 15
      app/public/js/change_set.js
  25. 5 4
      app/public/js/ledger.js
  26. 4 4
      app/public/js/ledger_gather.js
  27. 12 12
      app/public/js/revise.js
  28. 1 0
      app/public/js/shenpi.js
  29. 45 6
      app/public/js/spreadjs_rela/spreadjs_zh.js
  30. 12 44
      app/public/js/stage.js
  31. 32 4
      app/public/js/tender_copy_setting.js
  32. 4 0
      app/public/js/tender_showhide.js
  33. 2 1
      app/public/js/zh_calc.js
  34. 12 0
      app/router.js
  35. 51 1
      app/service/advance_audit.js
  36. 37 16
      app/service/change.js
  37. 2 2
      app/service/change_att.js
  38. 175 10
      app/service/change_audit.js
  39. 203 0
      app/service/change_audit_list.js
  40. 2 2
      app/service/ledger.js
  41. 1 0
      app/service/ledger_audit.js
  42. 49 44
      app/service/report_memory.js
  43. 1 0
      app/service/revise_audit.js
  44. 1 0
      app/service/stage_audit.js
  45. 8 7
      app/service/tender.js
  46. 30 0
      app/service/tender_info.js
  47. 59 0
      app/service/tender_tag.js
  48. 2 2
      app/view/change/index.ejs
  49. 5 0
      app/view/change/info.ejs
  50. 4 4
      app/view/change/info_modal.ejs
  51. 434 0
      app/view/change/information.ejs
  52. 1100 0
      app/view/change/information_modal.ejs
  53. 2 2
      app/view/dashboard/index.ejs
  54. 11 3
      app/view/ledger/explode_modal.ejs
  55. 1 1
      app/view/ledger/gather.ejs
  56. 2 2
      app/view/material/audit_modal.ejs
  57. 1 1
      app/view/material/file.ejs
  58. 2 2
      app/view/profile/wechat.ejs
  59. 2 2
      app/view/revise/info_modal.ejs
  60. 1 1
      app/view/setting/category.ejs
  61. 1 1
      app/view/setting/user.ejs
  62. 17 4
      app/view/shares/import_excel_modal.ejs
  63. 2 2
      app/view/stage/audit_modal.ejs
  64. 2 2
      app/view/stage/compare.ejs
  65. 1 1
      app/view/stage/gather.ejs
  66. 4 4
      app/view/stage/index.ejs
  67. 3 2
      app/view/tender/detail.ejs
  68. 5 5
      app/view/tender/detail_modal.ejs
  69. 2 2
      app/view/tender/index.ejs
  70. 2 2
      app/view/tender/info.ejs
  71. 2 2
      app/view/tender/manage.ejs
  72. 2 2
      app/view/tender/progress.ejs
  73. 6 4
      app/view/tender/shenpi.ejs
  74. 21 0
      config/web.js
  75. 66 0
      sql/update.sql
  76. 60 0
      test/app/lib/bills_utils.test.js

+ 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 = {

+ 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 '添加审核人失败';

+ 1 - 1
app/controller/ledger_controller.js

@@ -538,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);

+ 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 '添加审核人失败';

+ 3 - 2
app/controller/revise_controller.js

@@ -630,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);
@@ -799,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 '添加审核人失败';
 

+ 2 - 1
app/controller/stage_controller.js

@@ -979,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 '添加审核人失败';

+ 15 - 0
app/controller/tender_controller.js

@@ -878,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;

+ 5 - 1
app/lib/analysis_excel.js

@@ -547,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);
     }
     /**
@@ -610,7 +613,8 @@ class AnalysisExcelTree {
      * @param {Array} tempData - 新建项目使用的清单模板
      * @returns {ImportBaseTree}
      */
-    analysisData(sheet, tempData) {
+    analysisData(sheet, tempData, filter) {
+        this.filter = filter;
         this.colsDef = null;
         this.cacheTree = this._getNewCacheTree(tempData);
         this.errorData = [];

+ 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) {

+ 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) {

+ 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);
+        }
+    };
+};

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

@@ -66,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) {
@@ -177,8 +182,10 @@ function bindCategoryControl() {
                 editCate.value.push(newValue);
                 $(this).before(getValueHtml([newValue]));
                 bindCategoryValueControl();
+                makeIconColor();
             });
             bindCategoryValueControl();
+            makeIconColor();
             $('#add').modal('show');
         }
     });
@@ -303,6 +310,7 @@ $(document).ready(() => {
         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');
         // }
@@ -315,5 +323,6 @@ $(document).ready(() => {
         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('*;*'));
     }
 

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

@@ -1320,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;
             }
         };
@@ -1436,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);

+ 4 - 4
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,8 +168,8 @@ $(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);
@@ -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: '@'},

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

@@ -692,23 +692,23 @@ $(document).ready(() => {
             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) {
-                        data[col.field] = null;
-                        const exprInfo = getExprInfo(col.field);
-                        if (exprInfo) {
-                            data[exprInfo.expr] = '';
-                        }
-                        bDel = true;
+                    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;
                 }
                 if (bDel) datas.push(data);
             }

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

@@ -122,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];

+ 45 - 6
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') {
@@ -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();
         }
     },
 

+ 12 - 44
app/public/js/stage.js

@@ -200,11 +200,16 @@ $(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);
-        node.end_correct_tp = ZhCalc.add(node.end_qc_tp, ZhCalc.mul(node.end_contract_qty, node.unit_price, tenderInfo.decimal.tp));
+        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);
-        if (node.b_code === '209-2-3') console.log(node);
     };
     const stageTree = createNewPathTree('stage', stageTreeSetting);
     // 初始化 计量单元 数据结构
@@ -3051,50 +3056,12 @@ $(document).ready(() => {
                         name: '定位至台账',
                         icon: 'fa-sign-in',
                         callback: function (key, opt) {
-                            const checkPosMatch = function (bills, posName) {
-                                const posRange = stagePos.getLedgerPos(p.id);
-                                for (const pr of posRange) {
-                                    if (pr.name === posName) return true;
-                                }
-                                return false;
-                            };
-                            const checkBillsMatch = function (bills, checkData, posName) {
-                                if (bills.children && bills.children.length > 0) return false;
-                                const billsCheckData = {
-                                    b_code: bills.b_code || '',
-                                    name: bills.name || '',
-                                    unit: bills.unit || '',
-                                    unit_price: bills.unit_price || 0,
-                                };
-                                if (!_.isMatch(checkData, billsCheckData)) return false;
-                                return !posName || checkPosMatch(bills, posName);
-                            };
                             const changeBills = SpreadJsObj.getSelectObject(self.changeBillsSheet);
-                            const cb = {
-                                b_code: changeBills.code || '',
-                                name: changeBills.name || '',
-                                unit: changeBills.unit || '',
-                                unit_price: changeBills.unit_price || 0,
-                            };
-                            if (changeBills.lid && changeBills.lid !== '0') {
-                                const node = stageTree.nodes.find(x => {return x.id === changeBills.lid});
-                                const posterity = stageTree.getPosterity(node) || [];
-                                posterity.unshift(node);
-                                for (const p of posterity) {
-                                    if (checkBillsMatch(p, cb, changeBills.detail)) {
-                                        SpreadJsObj.locateTreeNode(slSpread.getActiveSheet(), p.ledger_id);
-                                        stagePosSpreadObj.loadCurPosData();
-                                        return;
-                                    }
-                                }
+                            if (changeBills.gcl_id) {
+                                const node = stageTree.nodes.find(x => {return x.id === changeBills.gcl_id});
+                                SpreadJsObj.locateTreeNode(slSpread.getActiveSheet(), node.ledger_id);
                             } else {
-                                for (const node of stageTree.nodes) {
-                                    if (checkBillsMatch(node, cb, changeBills.detail)) {
-                                        SpreadJsObj.locateTreeNode(slSpread.getActiveSheet(), node.ledger_id);
-                                        stagePosSpreadObj.loadCurPosData();
-                                        return;
-                                    }
-                                }
+                                toastr.warning('该清单无法定位');
                             }
                         },
                         disabled: function (key, opt) {
@@ -3124,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();
             });
         }

+ 32 - 4
app/public/js/tender_copy_setting.js

@@ -95,6 +95,7 @@ function initTenderTree () {
     }
 }
 function recursiveGetTenderNodeHtml (node, arr, pid) {
+    if (node.id === tender.id) return ''
     const html = [];
     html.push('<tr pid="' + pid + '">');
     // 名称
@@ -112,7 +113,8 @@ function recursiveGetTenderNodeHtml (node, arr, pid) {
     // 创建人
     html.push('<td>');
     if (!node.cid) {
-        html.push('<input data-tid="'+ node.id +'" type="radio">');
+        // 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>');
@@ -145,9 +147,35 @@ function getTenderTreeHtml () {
 $(document).ready(function () {
     initTenderTree()
     $('#copyBtn').click(() => {
-        console.log('1111')
         const html = getTenderTreeHtml();
-        $('#copyModalContent').html(html)
-        $('#bd-set-8').modal('show')
+        $('#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()
+            })
+        }
+    });
 })

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

@@ -45,6 +45,10 @@ function sortTenderTree(teTree = tenderTree) {
                     return 0;
                 }
             });
+        }
+    }
+    for (const tender of teTree) {
+        if (tender.children) {
             sortTenderTree(tender.children);
         }
     }

+ 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}
-})();
+})();

+ 12 - 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');

+ 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) {

+ 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,

+ 49 - 44
app/service/report_memory.js

@@ -67,33 +67,6 @@ module.exports = app => {
             this.tableName = 'report_memory';
             // 基础数据类
             // mainData
-            this.billsTree = new Ledger.billsTree(this.ctx, {
-                id: 'ledger_id',
-                pid: 'ledger_pid',
-                order: 'order',
-                level: 'level',
-                rootId: -1,
-                keys: ['id', 'tender_id', 'ledger_id'],
-                stageId: 'id',
-                calcFields: ['deal_tp', 'total_price', 'contract_tp', 'qc_tp', 'gather_tp', 'pre_contract_tp', 'pre_qc_tp', 'pre_gather_tp'],
-                calc: function (node) {
-                    if (node.children && node.children.length === 0) {
-                        node.pre_gather_qty = self.ctx.helper.add(node.pre_contract_qty, node.pre_qc_qty);
-                        node.gather_qty = self.ctx.helper.add(node.contract_qty, node.qc_qty);
-                        node.end_contract_qty = self.ctx.helper.add(node.pre_contract_qty, node.contract_qty);
-                        node.end_qc_qty = self.ctx.helper.add(node.pre_qc_qty, node.qc_qty);
-                        node.end_gather_qty = self.ctx.helper.add(node.pre_gather_qty, node.gather_qty);
-                    }
-                    node.pre_gather_tp = self.ctx.helper.add(node.pre_contract_tp, node.pre_qc_tp);
-                    node.gather_tp = self.ctx.helper.add(node.contract_tp, node.qc_tp);
-                    node.end_contract_tp = self.ctx.helper.add(node.pre_contract_tp, node.contract_tp);
-                    node.end_qc_tp = self.ctx.helper.add(node.pre_qc_tp, node.qc_tp);
-                    node.end_gather_tp = self.ctx.helper.add(node.pre_gather_tp, node.gather_tp);
-
-                    node.final_tp = self.ctx.helper.add(node.total_price, node.end_qc_tp);
-                    node.final_ratio = self.ctx.helper.mul(self.ctx.helper.div(node.end_gather_tp, node.final_tp, 4), 100);
-                }
-            });
             this.pos = new Ledger.pos({
                 id: 'id', ledgerId: 'lid',
                 updateFields: ['contract_qty', 'qc_qty', 'postil'],
@@ -111,6 +84,36 @@ module.exports = app => {
             this.stageValidRole = [];
         }
 
+        _getNewBillsTree(calcFields) {
+            return new Ledger.billsTree(this.ctx, {
+                id: 'ledger_id',
+                pid: 'ledger_pid',
+                order: 'order',
+                level: 'level',
+                rootId: -1,
+                keys: ['id', 'tender_id', 'ledger_id'],
+                stageId: 'id',
+                calcFields: calcFields || ['deal_tp', 'total_price', 'contract_tp', 'qc_tp', 'gather_tp', 'pre_contract_tp', 'pre_qc_tp', 'pre_gather_tp'],
+                calc: function (node, helper) {
+                    if (node.children && node.children.length === 0) {
+                        node.pre_gather_qty = helper.add(node.pre_contract_qty, node.pre_qc_qty);
+                        node.gather_qty = helper.add(node.contract_qty, node.qc_qty);
+                        node.end_contract_qty = helper.add(node.pre_contract_qty, node.contract_qty);
+                        node.end_qc_qty = helper.add(node.pre_qc_qty, node.qc_qty);
+                        node.end_gather_qty = helper.add(node.pre_gather_qty, node.gather_qty);
+                    }
+                    node.pre_gather_tp = helper.add(node.pre_contract_tp, node.pre_qc_tp);
+                    node.gather_tp = helper.add(node.contract_tp, node.qc_tp);
+                    node.end_contract_tp = helper.add(node.pre_contract_tp, node.contract_tp);
+                    node.end_qc_tp = helper.add(node.pre_qc_tp, node.qc_tp);
+                    node.end_gather_tp = helper.add(node.pre_gather_tp, node.gather_tp);
+
+                    node.final_tp = helper.add(node.total_price, node.end_qc_tp);
+                    node.final_ratio = helper.mul(helper.div(node.end_gather_tp, node.final_tp, 4), 100);
+                }
+            });
+        }
+
         _checkFieldsExist(source, check) {
             for (const s of source) {
                 if (check.indexOf(s) >= 0) {
@@ -321,7 +324,7 @@ module.exports = app => {
             return monthProgress;
         }
 
-        async _calcBillsBgl() {
+        async _calcBillsBgl(billsTree) {
             if (!this.ctx.stage) return;
 
             const helper = this.ctx.helper;
@@ -331,7 +334,7 @@ module.exports = app => {
                 ? await this.ctx.service.stageChange.getAuditorAllStageData(tender.id, stage.id, stage.curTimes, stage.curOrder)
                 : await this.ctx.service.stageChange.getLastestAllStageData(tender.id, stage.id);
 
-            for (const node of this.billsTree.nodes) {
+            for (const node of billsTree.nodes) {
                 node.qc_bgl_code = '';
                 if (node.children && node.children.length > 0) continue;
 
@@ -345,10 +348,10 @@ module.exports = app => {
             }
         }
 
-        _calcLeafXmjRela() {
-            for (const node of this.billsTree.nodes) {
+        _calcLeafXmjRela(billsTree) {
+            for (const node of billsTree.nodes) {
                 if (node.b_code) {
-                    const leafXmj = this.billsTree.getLeafXmjParent(node);
+                    const leafXmj = billsTree.getLeafXmjParent(node);
                     node.leaf_xmj_id = leafXmj ? leafXmj.id : -1;
                 }
             }
@@ -389,18 +392,20 @@ module.exports = app => {
                         {data: preStage, fields: ['contract_qty', 'contract_tp', 'qc_qty', 'qc_tp'], prefix: 'pre_', relaId: 'lid'}
                     ]);
                 }
-                this.billsTree.loadDatas(billsData);
-                this.billsTree.calculateAll();
+
+                const billsTree = this._getNewBillsTree();
+                billsTree.loadDatas(billsData);
+                billsTree.calculateAll();
 
                 if (this._checkFieldsExist(fields, billsFields.bgl)) {
-                    await this._calcBillsBgl();
+                    await this._calcBillsBgl(billsTree);
                 }
 
                 if (this._checkFieldsExist(fields, billsFields.leafXmj)) {
-                    this._calcLeafXmjRela();
+                    this._calcLeafXmjRela(billsTree);
                 }
 
-                return this.billsTree.getDatas([
+                return billsTree.getDatas([
                     'id', 'tender_id', 'ledger_id', 'ledger_pid', 'level', 'order', 'full_path', 'is_leaf',
                     'code', 'b_code', 'name', 'unit', 'unit_price',
                     'deal_qty', 'deal_tp',
@@ -552,15 +557,15 @@ module.exports = app => {
                         {data: preStage, fields: ['contract_qty', 'contract_tp', 'qc_qty', 'qc_tp'], prefix: 'pre_', relaId: 'lid'}
                     ]);
                 }
-                this.billsTree.loadDatas(billsData);
+                const billsTree = this._getNewBillsTree();
+                billsTree.loadDatas(billsData);
 
-                this.billsTree.setting.calcFields = ['deal_tp', 'total_price', 'pre_contract_tp', 'pre_qc_tp', 'pre_gather_tp'];
                 for (const role of validRole) {
                     const prefix = 'r' + role.flowOrder + '_';
-                    this.billsTree.setting.calcFields.push(prefix + 'contract_tp', prefix + 'qc_tp', prefix + 'gather_tp');
+                    billsTree.setting.calcFields.push(prefix + 'contract_tp', prefix + 'qc_tp', prefix + 'gather_tp');
                 }
 
-                this.billsTree.calculateAll(function(node) {
+                billsTree.calculateAll(function(node) {
                     let prefix = '';
                     if (node.children && node.children.length === 0) {
                         node.pre_gather_qty = helper.add(node.pre_contract_qty, node.pre_qc_qty);
@@ -577,11 +582,11 @@ module.exports = app => {
                 });
 
                 if (this._checkFieldsExist(fields, billsFields.leafXmj)) {
-                    this._calcLeafXmjRela();
+                    this._calcLeafXmjRela(billsTree);
                 }
 
-                return this.billsTree.getDefaultDatas();
-                // return this.billsTree.getDatas([
+                return billsTree.getDefaultDatas();
+                // return billsTree.getDatas([
                 //     'id', 'tender_id', 'ledger_id', 'ledger_pid', 'level', 'order', 'full_path', 'is_leaf',                 //8
                 //     'code', 'b_code', 'name', 'unit', 'unit_price',                                                         //5
                 //     'deal_qty', 'deal_tp', 'quantity', 'total_price', 'dgn_qty1', 'dgn_qty2',                               //6

+ 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_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>

+ 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>

+ 1 - 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>

+ 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>

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

@@ -39,10 +39,10 @@
                             <% for (const s in smsType) { %>
                             <% if (smsType[s].wechat) { %>
                             <div class="form-group row">
-                                <label class="col-auto col-form-label"><%= smsType[s].name %>
+                                <label class="col-2 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/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;

+ 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/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>

+ 17 - 4
app/view/shares/import_excel_modal.ejs

@@ -17,9 +17,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="import-excel-ok">确认上传</button>
+            <div class="modal-footer d-flex justify-content-between">
+                <div>
+                    <div class="custom-control custom-checkbox custom-control-inline" id="filter-excel">
+                        <input type="radio" class="custom-control-input" name="customRadioInline1" id="filter-qty-price" checked="">
+                        <label class="custom-control-label" for="filter-qty-price">过滤清单数量和单价为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="import-excel-ok">确认上传</button>
+                </div>
             </div>
         </div>
     </div>
@@ -90,7 +98,7 @@
                         rows: xlsxUtils.getSheetByName(sheetName, {header: 1}),
                         merge: xlsxUtils._wb.Sheets[sheetName]["!merges"]
                     };
-                    if (callback) callback(sheet);
+                    if (callback) callback(sheet, $('#filter-qty-price')[0].checked);
                 }
                 $('#import-excel').modal('hide');
             }
@@ -103,6 +111,11 @@
             } else {
                 $('#import-template').hide();
             }
+            if (setting.filter) {
+                $('#filter-excel').show();
+            } else {
+                $('#filter-excel').hide();
+            }
             $('#import-excel').modal('show');
             callback = setting.callback;
             u_type = setting.u_type ? setting.u_type : uploadType.data;

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

@@ -798,8 +798,8 @@
 </script>
 <% if (ctx.stage && (ctx.session.sessionUser.accountId === ctx.stage.user_id && (ctx.stage.status === auditConst.status.uncheck || ctx.stage.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.stage %>;
     const shenpiConst =  JSON.parse('<%- JSON.stringify(shenpiConst) %>');
 </script>

+ 2 - 2
app/view/stage/compare.ejs

@@ -67,7 +67,7 @@
 </div>
 <script>
     //const auditors = JSON.parse(<%- JSON.stringify(ctx.stage.auditors) %>);
-    const stage = JSON.parse('<%- JSON.stringify(ctx.stage) %>');
+    const stage = JSON.parse(unescape('<%- escape(JSON.stringify(ctx.stage)) %>'));
     const ledgerSpreadSetting = JSON.parse('<%- JSON.stringify(ledgerSpread) %>');
     ledgerSpreadSetting.localCache = {
         key: 'stage-compare-bills',
@@ -82,4 +82,4 @@
     let scRoles = getLocalCache(scCacheKey);
     scRoles = scRoles ? scRoles.split(',') : [0];
     const thousandth = <%- ctx.tender.info.display.thousandth %>;
-</script>
+</script>

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

@@ -90,7 +90,7 @@
     </div>
 </div>
 <script>
-    const stage = JSON.parse('<%- JSON.stringify(ctx.stage) %>');
+    const stage = JSON.parse(unescape('<%- escape(JSON.stringify(ctx.stage)) %>'));
     const gclSpreadSetting = JSON.parse('<%- JSON.stringify(gclSpread) %>');
     gclSpreadSetting.localCache = {
         key: 'stage-gather-gcl',

+ 4 - 4
app/view/stage/index.ejs

@@ -272,7 +272,7 @@
                                 <li class="nav-item ml-auto pt-1">
                                     <a href="javascript:void(0);" id="bach-download" class="btn btn-sm btn-primary" type="curr">批量下载</a>
                                     <!--所有附件 翻页-->
-                                    <span id="showPage" style="display: none"><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  ml-3" content="next"><i class="fa fa-chevron-right"></i></a></span>
+                                    <span id="showPage" style="display: none"><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></span>
                                     <a href="#upload" data-toggle="modal" data-target="#upload"  class="btn btn-sm btn-outline-primary ml-3">上传</a>
                                 </li>
                             </ul>
@@ -593,14 +593,14 @@
         key: 'stage-pos',
         colWidth: true,
     }
-    const tender = JSON.parse('<%- JSON.stringify(tender) %>');
+    const tender = JSON.parse(unescape('<%- escape(JSON.stringify(tender)) %>'));
     const tenderInfo = JSON.parse(unescape('<%- escape(JSON.stringify(ctx.tender.info)) %>'));
     const thousandth = <%- ctx.tender.info.display.thousandth %>;
     const measureType = JSON.parse('<%- JSON.stringify(measureType) %>');
-    const stage = JSON.parse('<%- JSON.stringify(ctx.stage) %>');
+    const stage = JSON.parse(unescape('<%- escape(JSON.stringify(ctx.stage)) %>'));
     const imType = JSON.parse('<%- JSON.stringify(imType) %>');
     const whiteList = JSON.parse('<%- JSON.stringify(whiteList) %>');
-    let attData = JSON.parse('<%- JSON.stringify(attData) %>');
+    let attData = JSON.parse(unescape('<%- escape(JSON.stringify(attData)) %>'));
     const ckColSetting = 'stage-col-visible-1.0.3-<%- tender.id %>';
     const auditConst = JSON.parse('<%- JSON.stringify(auditConst) %>');
     // const cur_uid = parseInt('<%- ctx.session.sessionUser.accountId %>');

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

@@ -149,8 +149,9 @@
     });
 </script>
 <script type="text/javascript">
-    const category = JSON.parse('<%- JSON.stringify(categoryData) %>');
-    const tenders = JSON.parse('<%- JSON.stringify(tenders) %>');
+    const tenders = JSON.parse(unescape('<%- escape(JSON.stringify(tenders)) %>'));
+    const category = JSON.parse(unescape('<%- escape(JSON.stringify(categoryData)) %>'));
+    const tender = JSON.parse('<%- JSON.stringify(tender) %>');
     //4 标段期数计量进度//
     var myChart = echarts.init(document.getElementById('chartContainer4'));
     var option = {

+ 5 - 5
app/view/tender/detail_modal.ejs

@@ -623,22 +623,22 @@
             </div>
             <div class="modal-body">
                 <div class="alert alert-warning mb-1">从以下标段拷贝设置</div>
-                <div class=" mb-1">
+                <div class="mb-1" id="setting-custom">
                 <a class="btn btn-sm btn-light">
                     <div class="custom-control custom-checkbox">
-                    <input type="checkbox" class="custom-control-input" id="customCheckDisabld" checked="">
+                    <input type="checkbox" class="custom-control-input" checked data-type="tender">
                     <label class="custom-control-label text-primary" for="customCheckDisabld">标段属性</label>
                     </div>
                 </a>
                 <a class="btn btn-sm btn-light">
                     <div class="custom-control custom-checkbox">
-                    <input type="checkbox" class="custom-control-input" id="customCheckDisabld2" checked="">
+                    <input type="checkbox" class="custom-control-input"  checked data-type="chapter">
                     <label class="custom-control-label text-primary" for="customCheckDisabld2">章节设置</label>
                     </div>
                 </a>
                 <a class="btn btn-sm btn-light">
                     <div class="custom-control custom-checkbox">
-                    <input type="checkbox" class="custom-control-input" id="customCheckDisabld3" checked="">
+                    <input type="checkbox" class="custom-control-input" checked data-type="pay_account">
                     <label class="custom-control-label text-primary" for="customCheckDisabld3">付款账号</label>
                     </div>
                 </a>
@@ -648,7 +648,7 @@
             </div>
             <div class="modal-footer">
                 <button type="button" class="btn btn-sm btn-secondary" data-dismiss="modal">关闭</button>
-                <button type="button" class="btn btn-sm btn-primary">确认</button>
+                <button type="button" class="btn btn-sm btn-primary" id="copy_comfirm_btn">确认</button>
             </div>
         </div>
     </div>

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

@@ -8,9 +8,9 @@
     </div>
 </div>
 <script>
-    const tenders = JSON.parse('<%- JSON.stringify(tenderList) %>');
+    const tenders = JSON.parse(unescape('<%- escape(JSON.stringify(tenderList)) %>'));
     const categoryType = JSON.parse('<%- JSON.stringify(settingConst.cType) %>');
-    const category = JSON.parse('<%- JSON.stringify(categoryData) %>');
+    const category = JSON.parse(unescape('<%- escape(JSON.stringify(categoryData)) %>'));
     const auditConst = JSON.parse('<%- JSON.stringify(auditConst) %>');
     const measureType = JSON.parse('<%- JSON.stringify(measureType) %>');
     const uid = '<%- uid %>';

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

@@ -8,9 +8,9 @@
     </div>
 </div>
 <script>
-    const tenders = JSON.parse('<%- JSON.stringify(tenderList) %>');
+    const tenders = JSON.parse(unescape('<%- escape(JSON.stringify(tenderList)) %>'));
     const categoryType = JSON.parse('<%- JSON.stringify(settingConst.cType) %>');
-    const category = JSON.parse('<%- JSON.stringify(categoryData) %>');
+    const category = JSON.parse(unescape('<%- escape(JSON.stringify(categoryData)) %>'));
     const auditConst = JSON.parse('<%- JSON.stringify(auditConst) %>');
     const measureType = JSON.parse('<%- JSON.stringify(measureType) %>');
     const uid = '<%- uid %>';

+ 2 - 2
app/view/tender/manage.ejs

@@ -8,9 +8,9 @@
     </div>
 </div>
 <script>
-    const tenders = JSON.parse('<%- JSON.stringify(tenderList) %>');
+    const tenders = JSON.parse(unescape('<%- escape(JSON.stringify(tenderList)) %>'));
     const categoryType = JSON.parse('<%- JSON.stringify(settingConst.cType) %>');
-    const category = JSON.parse('<%- JSON.stringify(categoryData) %>');
+    const category = JSON.parse(unescape('<%- escape(JSON.stringify(categoryData)) %>'));
     const auditConst = JSON.parse('<%- JSON.stringify(auditConst) %>');
     const uid = '<%- uid %>';
     const pid = '<%- pid %>';

+ 2 - 2
app/view/tender/progress.ejs

@@ -8,9 +8,9 @@
     </div>
 </div>
 <script>
-    const tenders = JSON.parse('<%- JSON.stringify(tenderList) %>');
+    const tenders = JSON.parse(unescape('<%- escape(JSON.stringify(tenderList)) %>'));
     const categoryType = JSON.parse('<%- JSON.stringify(settingConst.cType) %>');
-    const category = JSON.parse('<%- JSON.stringify(categoryData) %>');
+    const category = JSON.parse(unescape('<%- escape(JSON.stringify(categoryData)) %>'));
     const auditConst = JSON.parse('<%- JSON.stringify(auditConst) %>');
     const uid = '<%- uid %>';
     const pid = '<%- pid %>';

+ 6 - 4
app/view/tender/shenpi.ejs

@@ -145,13 +145,15 @@
     const sp_type = JSON.parse('<%- JSON.stringify(shenpi.sp_type) %>');
     const sp_status = JSON.parse('<%- JSON.stringify(shenpi.sp_status) %>');
     const sp_status_list = JSON.parse('<%- JSON.stringify(shenpi.sp_status_list) %>');
-    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 cur_uid = parseInt('<%- ctx.tender.data.user_id %>');
     const cur_tenderid = parseInt('<%- ctx.tender.id %>');
-    const tenders = JSON.parse('<%- JSON.stringify(tenders) %>');
-    const category = JSON.parse('<%- JSON.stringify(categoryData) %>');
+    const tenders = JSON.parse(unescape('<%- escape(JSON.stringify(tenders)) %>'));
+    const category = JSON.parse(unescape('<%- escape(JSON.stringify(categoryData)) %>'));
 </script>
+<script src="/public/js/decimal.min.js"></script>
+<script src="/public/js/zh_calc.js"></script>
 <script src="/public/js/shenpi.js"></script>
 <script src="/public/js/tender_showhide.js"></script>
 <script>

+ 21 - 0
config/web.js

@@ -676,6 +676,27 @@ const JsFiles = {
                 mergeFile: 'ledger',
             },
         },
+        change: {
+            information: {
+                files: [
+                    '/public/js/spreadjs/sheets/v11/gc.spread.sheets.all.11.2.2.min.js',
+                    '/public/js/decimal.min.js',
+                ],
+                mergeFiles: [
+                    '/public/js/sub_menu.js',
+                    '/public/js/div_resizer.js',
+                    '/public/js/spreadjs_rela/spreadjs_zh.js',
+                    '/public/js/zh_calc.js',
+                    '/public/js/path_tree.js',
+                    '/public/js/gcl_gather.js',
+                    '/public/js/jquery/jquery.form.min.js',
+                    '/public/js/moment/moment.min.js',
+                    // '/public/js/change_calculation.js',
+                    '/public/js/change_information.js',
+                ],
+                mergeFile: 'information',
+            },
+        },
     },
 };
 

+ 66 - 0
sql/update.sql

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

+ 60 - 0
test/app/lib/bills_utils.test.js

@@ -0,0 +1,60 @@
+/**
+ * 清单扩展方法 单元测试
+ *
+ * @author Mai
+ * @date 2017/10/20
+ * @version
+ */
+
+'use strict';
+
+const { app, assert } = require('egg-mock/bootstrap');
+const billsUtils = require('../../../app/lib/bills_utils');
+
+describe('test/app/lib/bills_utils.test.js', () => {
+    it('Test CompareCode', function* () {
+        const testData = [
+            { code1: '202-1-a', code2: '202-1-b', result: -1 },
+            { code1: '202-1-g', code2: '202-1-f', result: 1 },
+            { code1: '1-1-1', code2: '1-1-3', result: -1},
+            { code1: '1-2-3', code2: '1-2-1', result: 1},
+        ];
+        for (const td of testData) {
+            const result = billsUtils.compareCode(td.code1, td.code2);
+            if (td.result > 0) {
+                assert(result > 0);
+            } else if (td.result < 0) {
+                assert(result < 0);
+            }
+        }
+    });
+    it('Test Compare18MainXmj', function* () {
+        const testData = [
+            { code1: '10203', code2: 'GD10204', result: -1 },
+            { code1: '10607', code2: '10605', result: 1 },
+        ];
+        for (const td of testData) {
+            const result = billsUtils.compare18MainXmj(td.code1, td.code2);
+            if (td.result > 0) {
+                assert(result > 0);
+            } else if (td.result < 0) {
+                assert(result < 0);
+            }
+        }
+    });
+    it('Test Compare18SubXmj', function* () {
+        const testData = [
+            { code1: 'LJ0701', code2: 'GLJ0701', result: -1 },
+            { code1: 'LM0604', code2: 'GDLM0608', result: -1 },
+            { code1: 'LM02', code2: 'LJ02', result: 1 },
+        ];
+        for (const td of testData) {
+            const result = billsUtils.compare18SubXmj(td.code1, td.code2);
+            if (td.result > 0) {
+                assert(result > 0);
+            } else if (td.result < 0) {
+                assert(result < 0);
+            }
+        }
+    });
+});