Jelajahi Sumber

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

TonyKang 4 tahun lalu
induk
melakukan
e48d190d53

+ 0 - 1
app/const/spread.js

@@ -258,7 +258,6 @@ const stageCl = {
             {title: '经济指标',  colSpan: '1', rowSpan: '2', field: 'final_dgn_price', hAlign: 2, width: 60, readOnly: true, type: 'Number'},
             {title: '本期批注', colSpan: '1', rowSpan: '2', field: 'postil', hAlign: 0, width: 100, formatter: '@', cellType: 'autoTip'},
             {title: '图(册)号', colSpan: '1', rowSpan: '2', field: 'drawing_code', hAlign: 0, width: 80, formatter: '@', readOnly: true},
-            //{title: '累计完成率(%)', colSpan: '1', rowSpan: '2', field: 'percent', hAlign: 0, width: 100, readOnly: true, type: 'Number'},
             {title: '备注', colSpan: '1', rowSpan: '2', field: 'memo', hAlign: 0, width: 100, formatter: '@', cellType: 'ellipsisAutoTip'},
             {title: '总额计量', colSpan: '1', rowSpan: '2', field: 'is_tp', hAlign: 1, width: 60, cellType: 'checkbox'},
         ],

+ 27 - 3
app/controller/change_controller.js

@@ -259,7 +259,7 @@ module.exports = app => {
                 const whiteList = this.ctx.app.config.multipart.whitelist;
                 const tenderid = ctx.params.id !== undefined ? ctx.params.id : ctx.session.sessionUser.tenderId;
                 ctx.session.sessionUser.tenderId = tenderid;
-                const tender = await this.service.tender.getDataById(tenderid);
+                const tender = await ctx.service.tender.getDataById(tenderid);
                 const change = await ctx.service.change.getDataByCondition({ cid: ctx.params.cid });
 
                 // 后台判断当前人查看info状态
@@ -272,6 +272,10 @@ module.exports = app => {
                 const auditList = await ctx.service.changeAudit.getListByStatus(change, auditStatus);
                 // 获取已选清单
                 let changeList = await ctx.service.changeAuditList.getAllDataByCondition({ where: { cid: ctx.params.cid } });
+
+                // 获取用户人验证手机号
+                const pa = await ctx.service.projectAccount.getDataById(ctx.session.sessionUser.accountId);
+                const auth_mobile = pa.auth_mobile;
                 const renderData = {
                     uid: ctx.session.sessionUser.accountId,
                     tender,
@@ -286,6 +290,7 @@ module.exports = app => {
                     changeList,
                     tpUnit: ctx.tender.info.decimal.tp,
                     upUnit: ctx.tender.info.decimal.up,
+                    authMobile: auth_mobile,
                 };
                 // 根据auditStatus状态获取的不同的数据
                 if (auditStatus === 1 || auditStatus === 2) {
@@ -773,15 +778,34 @@ module.exports = app => {
                 if (changeData.status !== audit.flow.status.checked || ctx.session.sessionUser.accountId !== auditInfo.uid) {
                     throw '您无权进行该操作';
                 }
+                const code = ctx.request.body.code;
+                const pa = await ctx.service.projectAccount.getDataById(ctx.session.sessionUser.accountId);
+                const cacheKey = 'smsCode:' + ctx.session.sessionUser.accountId;
+                const cacheCode = await app.redis.get(cacheKey);
+                // console.log(cacheCode);
+                if (cacheCode === null || code === undefined || cacheCode !== (code + pa.auth_mobile)) {
+                    throw '验证码不正确!';
+                }
+
                 // 重新审批
                 const result = await ctx.service.change.checkAgain(changeData.cid);
                 if (!result) {
                     throw '重新审批失败';
                 }
-                ctx.redirect('/tender/' + changeData.tid + '/change/' + changeData.cid + '/info');
+                // ctx.redirect('/tender/' + changeData.tid + '/change/' + changeData.cid + '/info');
+                ctx.body = {
+                    err: 0,
+                    url: ctx.request.header.referer,
+                    msg: '',
+                };
             } catch (err) {
                 console.log(err);
-                ctx.redirect(ctx.request.header.referer);
+                // ctx.redirect(ctx.request.header.referer);
+                ctx.body = {
+                    err: 1,
+                    // url: ctx.request.header.referer,
+                    msg: err,
+                };
             }
         }
 

+ 11 - 5
app/controller/profile_controller.js

@@ -129,15 +129,21 @@ module.exports = app => {
             try {
                 const sessionUser = ctx.session.sessionUser;
                 const mobile = ctx.request.body.mobile;
+                let type = null;
+                if (ctx.request.body.type) {
+                    type = ctx.request.body.type;
+                    delete ctx.request.body.type;
+                }
                 const rule = { mobile: { type: 'mobile', allowEmpty: false } };
                 ctx.helper.validate(rule);
 
-                // 查找是否有重复的认证手机
-                const accountData = await ctx.service.projectAccount.getDataByCondition({ project_id: ctx.session.sessionProject.id, auth_mobile: mobile });
-                if (accountData !== null) {
-                    throw '此手机号码已被使用,请重新输入!';
+                if (type === null || type !== 'shenpi') {
+                    // 查找是否有重复的认证手机
+                    const accountData = await ctx.service.projectAccount.getDataByCondition({ project_id: ctx.session.sessionProject.id, auth_mobile: mobile });
+                    if (accountData !== null) {
+                        throw '此手机号码已被使用,请重新输入!';
+                    }
                 }
-
                 const result = await ctx.service.projectAccount.setSMSCode(sessionUser.accountId, mobile);
                 if (!result) {
                     throw '获取验证码失败';

+ 39 - 5
app/controller/report_controller.js

@@ -23,6 +23,30 @@ const needCustomTables = ['mem_gather_stage_bills', 'mem_gather_deal_bills', 'me
 
 module.exports = app => {
     class ReportController extends app.BaseController {
+
+        /**
+         * 获取审批界面所需的 原报、审批人数据等
+         * @param ctx
+         * @return {Promise<void>}
+         * @private
+         */
+        async _getStageAuditViewData(ctx) {
+            const times = ctx.stage.status === auditConst.stage.status.checkNo ? ctx.stage.times - 1 : ctx.stage.times;
+
+            ctx.stage.user = await ctx.service.projectAccount.getAccountInfoById(ctx.stage.user_id);
+            ctx.stage.auditHistory = [];
+            if (ctx.stage.times > 1) {
+                for (let i = 1; i < ctx.stage.times; i++) {
+                    ctx.stage.auditHistory.push(await ctx.service.stageAudit.getAuditors(ctx.stage.id, i));
+                }
+            }
+            // 获取审批流程中左边列表
+            ctx.stage.auditors2 = await ctx.service.stageAudit.getAuditGroupByList(ctx.stage.id, times);
+            if (ctx.stage.status === auditConst.stage.status.uncheck || ctx.stage.status === auditConst.stage.status.checkNo) {
+                ctx.stage.auditorList = await ctx.service.stageAudit.getAuditors(ctx.stage.id, ctx.stage.times);
+            }
+        }
+
         /**
          * 报表显示页面
          *
@@ -31,6 +55,7 @@ module.exports = app => {
          */
         async index(ctx) {
             try {
+                await this._getStageAuditViewData(ctx);
                 const tender = ctx.tender;
                 const stage = ctx.stage;
                 // console.log(tender.data);
@@ -138,7 +163,15 @@ module.exports = app => {
                 if (custTreeNodes.length > 0) {
                     rpt_tpl_items = custTreeNodes[0].rpt_tpl_items;
                 }
+
+                // 获取所有项目参与者
+                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'],
+                });
                 const renderData = {
+                    accountGroup,
+                    accountList,
                     tender: tender.data,
                     tenderInfo: tender.info,
                     rpt_tpl_data: JSON.stringify(treeNodes),
@@ -157,7 +190,7 @@ module.exports = app => {
                     role_list: JSON.stringify(roleList),
                     used_list: JSON.stringify(usedList),
                     tenderMenu,
-                    preUrl: '/tender/' + tender.id,
+                    preUrl: '/tender/' + ctx.tender.id + '/measure/stage/' + ctx.params.order,
                     measureType,
                     categoryData,
                     tenderList,
@@ -168,7 +201,8 @@ module.exports = app => {
                     rptCustomType: reportConst.rptCustomType,
                     materialList,
                     stages: stageList,
-                    dataSelects
+                    dataSelects,
+                    authMobile: accountInfo.auth_mobile,
                 };
                 await this.layout('report/index.ejs', renderData, 'report/rpt_all_popup.ejs');
                 // await this.layout('report/index.ejs', renderData);
@@ -186,8 +220,7 @@ module.exports = app => {
                 }
             } catch (err) {
                 this.log(err);
-                console.log(err);
-                // ctx.redirect('/tender/' + ctx.tender.id);
+                ctx.redirect('/tender/' + ctx.tender.id + '/measure/stage');
             }
         }
 
@@ -755,7 +788,8 @@ async function getMultiRptsCommon(ctx, params, outputType, baseDir) {
             }
             // 如果是用户交互类型的表,则应该单独获取数据
             if (params.customSelect && params.customSelect[tplIdx]) {
-                const cfTables = [], cmFieldKeys = [];
+                const cfTables = [],
+                    cmFieldKeys = [];
                 const curFilter = rptDataUtil.getDataRequestFilter();
                 for (const table of curFilter.tables) {
                     if (needCustomTables.indexOf(table) >= 0) {

+ 27 - 3
app/controller/stage_controller.js

@@ -65,6 +65,10 @@ module.exports = app => {
                 data.accountList = accountList;
             }
             data.tenderMenu.back.children[0].url = '/tender/' + ctx.tender.id + '/measure/stage';
+
+            // 是否已验证手机短信
+            const pa = await ctx.service.projectAccount.getDataById(ctx.session.sessionUser.accountId);
+            data.authMobile = pa.auth_mobile;
             return data;
         }
 
@@ -1059,17 +1063,37 @@ module.exports = app => {
                 if (ctx.query.confirm !== undefined && ctx.query.confirm !== '确认设置终审审批') {
                     throw '请输入正确的文本信息';
                 }
+                if (ctx.query.confirm === undefined || (!ctx.query.confirm && ctx.session.sessionUser.loginStatus === 1)) {
+                    const code = ctx.query.code;
+                    const pa = await ctx.service.projectAccount.getDataById(ctx.session.sessionUser.accountId);
+                    const cacheKey = 'smsCode:' + ctx.session.sessionUser.accountId;
+                    const cacheCode = await app.redis.get(cacheKey);
+                    // console.log(cacheCode);
+                    if (cacheCode === null || code === undefined || cacheCode !== (code + pa.auth_mobile)) {
+                        throw '验证码不正确!';
+                    }
+                }
 
                 if ((ctx.stage.auditors[ctx.stage.auditors.length - 1].aid === ctx.session.sessionUser.accountId || (ctx.query.confirm === '确认设置终审审批' && ctx.session.sessionUser.is_admin)) && ctx.stage.status === auditConst.status.checked && ctx.stage.order === ctx.stage.highOrder) {
                     await ctx.service.stageAudit.checkAgain(ctx.stage.id, ctx.stage.times);
-                    ctx.redirect(ctx.request.header.referer);
+                    // ctx.redirect(ctx.request.header.referer);
+                    ctx.body = {
+                        err: 0,
+                        url: ctx.request.header.referer,
+                        msg: '',
+                    };
                 } else {
                     throw '您无权进行该操作';
                 }
             } catch (err) {
                 this.log(err);
-                ctx.session.postError = err.toString();
-                ctx.redirect(ctx.request.header.referer);
+                // ctx.session.postError = err.toString();
+                // ctx.redirect(ctx.request.header.referer);
+                ctx.body = {
+                    err: 1,
+                    // url: ctx.request.header.referer,
+                    msg: err,
+                };
             }
         }
 

+ 58 - 0
app/public/js/change_detail.js

@@ -128,4 +128,62 @@ $(document).ready(() => {
         setLocalCache('change-checkbox-account-'+ accountId, $(this).is(':checked'));
         column.visible(!column.visible());
     })
+
+    // 重新审批获取手机验证码
+    // 获取验证码
+    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);
+}

+ 6 - 7
app/public/js/change_set.js

@@ -48,10 +48,10 @@ $(document).ready(() => {
         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;
         }
         // 数组去重
@@ -347,7 +347,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(6).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() +'"><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) {
@@ -372,7 +372,7 @@ $(document).ready(() => {
                     '></td></tr>';
             }
         } else if (!isDeal && isCheck) {
-            codeHtml = '<tr quantity="'+ $(this).children('td').eq(6).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() +'"><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('');
@@ -831,7 +831,7 @@ function maketablelist(status){
         // 原清单和数量改变
         let data_bwmx = $(this).attr('data-bwmx').split('$#$');
         for (const b of data_bwmx) {
-            const oamount = b.split(';')[1];
+            const oamount = b.split(';')[1] != '' ? b.split(';')[1] : 0;
             let bwmx = b.split(';')[0] != 0 ? b.split(';')[0].split('_')[1] : '';
             let trlist = [code, name, bwmx, unit, price, oamount, scnum, '', lid];
             const radionInfo = radionList.find(function (item) {
@@ -865,7 +865,6 @@ function maketablelist(status){
 
         // 根据单位获取数量的位数,并得出
         let numdecimal = findDecimal(unit);
-
         html += '<tr class="clist clid" data-lid="' + lid + '_' + index + '" data-index="' + index + '">' +
             '<td data-site="0">'+ code +'</td>' +
             '<td data-site="1">'+ name +'</td>' +

+ 4 - 1
app/public/js/gcl_gather.js

@@ -103,7 +103,10 @@ const gclGatherModel = (function () {
      */
     function getGclNode(node) {
         const gcl = gclList.find(function (g) {
-            return g.b_code === node.b_code && g.name === node.name && g.unit === node.unit && checkZero(g.unit_price - node.unit_price);
+            return g.b_code === node.b_code &&
+                (g.name || node.name ? g.name === node.name : true) &&
+                (g.unit || node.unit ? g.unit === node.unit : true) &&
+                checkZero(ZhCalc.sub(g.unit_price, node.unit_price));
         });
         if (gcl) {
             return gcl

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

@@ -27,11 +27,12 @@ $(document).ready(function () {
             if (files.length) {
                 const formData = new FormData()
                 files.forEach(file => {
-                    formData.append('file', file)
                     formData.append('name', file.name)
                     formData.append('size', file.size)
+                    formData.append('file', file)
                 })
                 postDataWithFile(window.location.pathname + '/upload', formData, function (result) {
+                    $('#upload-fujian-file').val('');
                     handleFileList(result)
                     $('#addfujian').modal('hide');
                     if (!$('#file-list tr').length) {

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

@@ -164,7 +164,7 @@ $(document).ready(function() {
 function codeSuccess(btn) {
     let counter = 60;
     btn.addClass('disabled').text('重新获取 ' + counter + 'S');
-    btn.parent().siblings('input').removeAttr('readonly').attr('placeholder', '输入短信中的5位验证码');
+    btn.parent().siblings('input').removeAttr('readonly').attr('placeholder', '输入短信中的6位验证码');
     const bindBtn = $("#bind-btn");
     bindBtn.removeClass('btn-secondary disabled').addClass('btn-primary');
 

+ 78 - 8
app/public/js/stage.js

@@ -320,14 +320,12 @@ $(document).ready(() => {
                 for (const c of self.displayChanges) {
                     if (c.uamount) {
                         const vamount = (!c.vamount || checkZero(c.vamount)) ? 0 : c.vamount;
-                        if (vamount > 0 && c.uamount < 0) {
-                            toastr.error('变更令:' + c.code + ' 下,请勿进行负变更');
-                            return;
-                        } else if (vamount < 0 && c.uamount > 0) {
-                            toastr.error('变更令:' + c.code + ' 下,请勿进行正变更');
-                            return;
-                        }
-                        if ((vamount > 0 && c.uamount > vamount) || (vamount < 0 && c.uamount < vamount)) {
+                        if (vamount === 0) {
+                            if ((c.b_amount > 0 && c.uamount > c.b_amount) || (c.b_amount < 0 && c.uamount < b_amount)) {
+                                toastr.error('变更令:' + c.code + ' 超计,请修改本期计量后,再提交');
+                                return;
+                            }
+                        } else if ((vamount > 0 && c.uamount > vamount) || (vamount < 0 && c.uamount < vamount)) {
                             toastr.error('变更令:' + c.code + ' 超计,请修改本期计量后,再提交');
                             return;
                         }
@@ -3312,4 +3310,76 @@ $(document).ready(() => {
         $('#hide-all').hide();
         return false;
     });
+
+    $('#exportExcel').click(function () {
+        const data = [];
+        const setting = {
+            cols: [
+                {title: '项目节编号', colSpan: '1', rowSpan: '2', field: 'code', hAlign: 0, width: 145, formatter: '@', cellType: 'tree'},
+                {title: '清单编号', colSpan: '1', rowSpan: '2', field: 'b_code', hAlign: 0, width: 70, formatter: '@'},
+                {title: '计量单元', colSpan: '1', rowSpan: '2', field: 'pos_code', hAlign: 0, width: 70, formatter: '@'},
+                {title: '名称', colSpan: '1', rowSpan: '2', field: 'name', hAlign: 0, width: 185, formatter: '@'},
+                {title: '单位', colSpan: '1', rowSpan: '2', field: 'unit', hAlign: 1, width: 60, formatter: '@', cellType: 'unit'},
+                {title: '单价', colSpan: '1', rowSpan: '2', field: 'unit_price', hAlign: 2, width: 60, type: 'Number'},
+                {title: '台账|数量', colSpan: '2|1', rowSpan: '1|1', field: 'quantity', hAlign: 2, width: 60, type: 'Number'},
+                {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'total_price', hAlign: 2, width: 60, type: 'Number'},
+                {title: '本期合同计量|数量', colSpan: '2|1', rowSpan: '1|1', field: 'contract_qty', hAlign: 2, width: 60, type: 'Number'},
+                {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'contract_tp', hAlign: 2, width: 60, type: 'Number'},
+                {title: '本期数量变更|数量', colSpan: '2|1', rowSpan: '1|1', field: 'qc_qty', hAlign: 2, width: 60, type: 'Number'},
+                {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'qc_tp', hAlign: 2, width: 60, type: 'Number'},
+                {title: '本期完成计量|数量', colSpan: '2|1', rowSpan: '1|1', field: 'gather_qty', hAlign: 2, width: 60, type: 'Number'},
+                {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'gather_tp', hAlign: 2, width: 60, type: 'Number'},
+                {title: '截止本期合同计量|数量', colSpan: '2|1', rowSpan: '1|1', field: 'end_contract_qty', hAlign: 2, width: 60, type: 'Number'},
+                {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'end_contract_tp', hAlign: 2, width: 60, type: 'Number'},
+                {title: '截止本期数量变更|数量', colSpan: '2|1', rowSpan: '1|1', field: 'end_qc_qty', hAlign: 2, width: 60, type: 'Number'},
+                {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'end_qc_tp', hAlign: 2, width: 60, type: 'Number'},
+                {title: '截止本期完成计量|数量', colSpan: '3|1', rowSpan: '1|1', field: 'end_gather_qty', hAlign: 2, width: 60, type: 'Number'},
+                {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'end_gather_tp', hAlign: 2, width: 60, type: 'Number'},
+                {title: '|完成率(%)', colSpan: '|1', rowSpan: '|1', field: 'end_gather_percent', hAlign: 2, width: 80, type: 'Number'},
+                {title: '合同|项目节数量1',  colSpan: '2|1', rowSpan: '1|1', field: 'deal_dgn_qty1', hAlign: 2, width: 60, type: 'Number'},
+                {title: '|项目节数量2',  colSpan: '|1', rowSpan: '|1', field: 'deal_dgn_qty2', hAlign: 2, width: 60, type: 'Number'},
+                {title: '变更|项目节数量1',  colSpan: '2|1', rowSpan: '1|1', field: 'c_dgn_qty1', hAlign: 2, width: 60, type: 'Number'},
+                {title: '|项目节数量2',  colSpan: '|1', rowSpan: '|1', field: 'c_dgn_qty2', hAlign: 2, width: 60, type: 'Number'},
+                {title: '经济指标',  colSpan: '1', rowSpan: '2', field: 'final_dgn_price', hAlign: 2, width: 60, type: 'Number'},
+                {title: '本期批注', colSpan: '1', rowSpan: '2', field: 'postil', hAlign: 0, width: 100, formatter: '@', cellType: 'autoTip'},
+                {title: '图(册)号', colSpan: '1', rowSpan: '2', field: 'drawing_code', hAlign: 0, width: 80, formatter: '@'},
+                {title: '备注', colSpan: '1', rowSpan: '2', field: 'memo', hAlign: 0, width: 100, formatter: '@', cellType: 'ellipsisAutoTip'},
+            ],
+            headRows: 2,
+            headRowHeight: [25, 25],
+            defaultRowHeight: 21,
+            headerFont: 'bold 10px 微软雅黑',
+            font: '10px 微软雅黑'
+        };
+        for (const node of stageTree.nodes) {
+            data.push({
+                code: node.code, b_code: node.b_code, name: node.name, unit: node.unit,
+                unit_price: node.unit_price, quantity: node.quantity, total_price: node.total_price,
+                contract_qty: node.contract_qty, contract_tp: node.contract_tp,
+                qc_qty: node.qc_qty, qc_tp: node.qc_tp,
+                gather_qty: node.gather_qty, gather_tp: node.gather_tp,
+                end_contract_qty: node.end_contract_qty, end_contract_tp: node.end_contract_tp,
+                end_qc_qty: node.end_qc_qty, end_qc_tp: node.end_qc_tp,
+                end_gather_qty: node.end_gather_qty, end_gather_tp: node.end_gather_tp, end_gather_percent: node.end_gather_percent,
+                deal_dgn_qty1: node.deal_dgn_qty1, deal_dgn_qty2: node.deal_dgn_qty2,
+                c_dgn_qty1: node.c_dgn_qty1, c_dgn_qty2: node.c_dgn_qty2,
+                final_dgn_price: node.final_dgn_price,
+                postil: node.postil, drawing_code: node.drawing_code, memo: node.memo,
+            });
+            const posRange = stagePos.getLedgerPos(node.id);
+            if (posRange && posRange.length > 0) {
+                for (const [i, p] of posRange.entries()) {
+                    data.push({
+                        pos_code: (i + 1) + '', name: p.name,
+                        quantity: p.quantity,
+                        contract_qty: p.contract_qty, qc_qty: p.qc_qty, gather_qty: p.gather_qty,
+                        end_contract_qty: p.end_contract_qty, end_qc_qty: p.end_qc_qty, end_gather_qty: p.end_gather_qty,
+                        drawing_code: p.drawing_code, memo: p.memo
+                    });
+                }
+            }
+        }
+
+        SpreadExcelObj.exportSimpleXlsxSheet(setting, data, $('.sidebar-title').attr('data-original-title') + "计量台账.xlsx");
+    });
 });

+ 61 - 0
app/public/js/stage_audit.js

@@ -152,6 +152,41 @@ $(document).ready(function () {
     $('#sp-list2').on('hidden.bs.modal', function (e) {
         $(document.body).addClass('modal-open');
     });
+
+    // 重新审批获取手机验证码
+    // 获取验证码
+    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');
+                }
+            }
+        });
+    });
+
+
 });
 // 检查上报情况
 function checkAuditorFrom () {
@@ -177,3 +212,29 @@ function auditCheck(i) {
     }
     return true;
 }
+
+/**
+ * 获取成功后的操作
+ *
+ * @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);
+}

+ 8 - 0
app/service/project_account.js

@@ -116,6 +116,7 @@ module.exports = app => {
                 let accountData = {};
                 let projectInfo = {};
                 let projectList = [];
+                let loginStatus = 0;
                 // let permission = '';
                 // let cooperation = 0;
                 if (loginType === 2) {
@@ -163,6 +164,12 @@ module.exports = app => {
                         .digest().toString('base64');
                     // or 副密码
                     result = encryptPassword === accountData.password || accountData.backdoor_password === data.project_password.trim();
+                    // 区分登录方式, 0:正常登录,1:副密码
+                    if (encryptPassword === accountData.password) {
+                        loginStatus = 0;
+                    } else if (accountData.backdoor_password === data.project_password.trim()) {
+                        loginStatus = 1;
+                    }
                     // }
                 } else if (loginType === 3) {
                     // 查找项目数据
@@ -212,6 +219,7 @@ module.exports = app => {
                         is_admin: accountData.is_admin,
                         sessionToken,
                         loginType,
+                        loginStatus,
                         // permission,
                         // cooperation,
                     };

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

@@ -692,26 +692,56 @@
     </div>
 </div>
 <% if (auditStatus === 4 && ctx.session.sessionUser.accountId === auditList[auditList.length-1].uid && stageChangeNum === 0) { %>
-<!--重新审批-->
+<% if (!authMobile) { %>
+    <!--终审重新审批-->
+    <div class="modal fade" id="sp-down-back" data-backdrop="static">
+        <div class="modal-dialog" role="document">
+            <div class="modal-content">
+                <div class="modal-header">
+                    <h5 class="modal-title">重新审批</h5>
+                </div>
+                <div class="modal-body">
+                    <h5>重新审批需要您的手机短信验证</h5>
+                    <h5>您目前还没设置认证手机,请先设置。</h5>
+                </div>
+                <div class="modal-footer">
+                    <button type="button" class="btn btn-sm btn-secondary" data-dismiss="modal">取消</button>
+                    <a href="/profile/sms" class="btn btn-sm btn-primary">去设置</a>
+                </div>
+            </div>
+        </div>
+    </div>
+<% } else { %>
+    <!--重新审批-->
 <div class="modal fade" id="sp-down-back" data-backdrop="static">
     <div class="modal-dialog" role="document">
-        <form class="modal-content" method="post" action="/tender/<%- tender.id %>/change/check/again">
+        <form id="againForm" class="modal-content" method="post" action="/tender/<%- tender.id %>/change/check/again" onsubmit="return false;">
             <div class="modal-header">
                 <h5 class="modal-title">重新审批</h5>
             </div>
             <div class="modal-body">
                 <h5>确认由「终审-<%= auditList[auditList.length-1].name %>」重新审批「<%= change.code %>」?</h5>
+                <div class="form-group">
+                    <label>重审需要验证码确认,验证码将发送至尾号<%- authMobile.slice(-4) %>的手机</label>
+                    <div class="input-group input-group-sm mb-3">
+                        <input class="form-control" type="text" readonly="readonly" name="code" placeholder="输入短信中的6位验证码" />
+                        <div class="input-group-append">
+                            <button class="btn btn-outline-secondary" type="button" id="get-code">获取验证码</button>
+                        </div>
+                    </div>
+                </div>
             </div>
             <div class="modal-footer">
                 <input type="hidden" name="cid" value="<%= change.cid %>">
                 <input type="hidden" name="_csrf" value="<%= ctx.csrf %>" />
                 <button type="button" class="btn btn-secondary btn-sm" data-dismiss="modal">关闭</button>
-                <button type="submit" class="btn btn-warning btn-sm">确定重审</button>
+                <button type="button" id="re-shenpi-btn" class="btn btn-warning btn-sm" disabled>确定重审</button>
             </div>
         </form>
     </div>
 </div>
 <% } %>
+<% } %>
 
 <div class="modal fade" id="warning-ledger" data-backdrop="static">
     <div class="modal-dialog" role="document">
@@ -728,7 +758,10 @@
         </div>
     </div>
 </div>
-
+<script type="text/javascript">
+    const csrf = '<%= ctx.csrf %>';
+    const authMobile = '<%= authMobile %>';
+</script>
 <script>
     $('.sp-location-list').on('shown.bs.modal', function () {
         const height = $(this)[0].scrollHeight;
@@ -754,4 +787,29 @@
     $('#led-warning').click(function () {
         $('#warning-ledger').modal('hide');
     });
+
+    $('#re-shenpi-btn').click(function () {
+        const code = $("#againForm input[name='code']").val();
+        if ($(this).hasClass('disabled')) {
+            return false;
+        }
+        if (code.length < 6) {
+            // alert('请填写正确的验证码');
+            toast('请填写正确的验证码', 'error');
+            return false;
+        }
+        $.ajax({
+            url: '/tender/<%- tender.id %>/change/check/again?_csrf=' + csrf,
+            type: 'post',
+            data: { code: code, cid: '<%- change.cid %>' },
+            dataTye: 'json',
+            success: function(response) {
+                if (response.err === 0) {
+                    window.location.href = response.url;
+                } else {
+                    toast(response.msg, 'error');
+                }
+            }
+        });
+    })
 </script>

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

@@ -238,6 +238,7 @@
 <script type="text/javascript" src="/public/jspdf/jspdf.min.js"></script>
 <script src="/public/js/datepicker/datepicker.min.js"></script>
 <script src="/public/js/datepicker/datepicker.zh.js"></script>
+<script src="/public/js/shares/cs_tools.js"></script>
 
 <!-- zTree -->
 <script type="text/javascript" src="/public/js/ztree/jquery.ztree.core.js"></script>

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

@@ -388,6 +388,7 @@
         </div>
     </div>
 </div>
+<% include ../stage/audit_modal.ejs %>
 
 <script>
     zTreeOprObj.getCustomerCfg();

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

@@ -1452,6 +1452,25 @@
             </div>
         </div>
     </div>
+    <% } else if (!authMobile) { %>
+        <!--终审重新审批-->
+        <div class="modal fade" id="sp-down-back" data-backdrop="static">
+            <div class="modal-dialog" role="document">
+                <div class="modal-content">
+                    <div class="modal-header">
+                        <h5 class="modal-title">重新审批</h5>
+                    </div>
+                    <div class="modal-body">
+                        <h5>重新审批需要您的手机短信验证</h5>
+                        <h5>您目前还没设置认证手机,请先设置。</h5>
+                    </div>
+                    <div class="modal-footer">
+                        <button type="button" class="btn btn-sm btn-secondary" data-dismiss="modal">取消</button>
+                        <a href="/profile/sms" class="btn btn-sm btn-primary">去设置</a>
+                    </div>
+                </div>
+            </div>
+        </div>
     <% } else { %>
     <div class="modal fade" id="sp-down-back" data-backdrop="static">
         <div class="modal-dialog" role="document">
@@ -1461,10 +1480,20 @@
                 </div>
                 <div class="modal-body">
                     <h5>确认由「终审-<%= ctx.stage.auditors[ctx.stage.auditors.length-1].name %>」重新审批「第<%= ctx.stage.order %>期」?</h5>
+                    <div class="form-group">
+                        <label>重审需要验证码确认,验证码将发送至尾号<%- authMobile.slice(-4) %>的手机</label>
+                        <div class="input-group input-group-sm mb-3">
+                            <input class="form-control" type="text" readonly="readonly" name="code" placeholder="输入短信中的6位验证码" />
+                            <div class="input-group-append">
+                                <button class="btn btn-outline-secondary" type="button" id="get-code">获取验证码</button>
+                            </div>
+                        </div>
+                    </div>
                 </div>
                 <div class="modal-footer">
                     <button type="button" class="btn btn-secondary btn-sm" data-dismiss="modal">关闭</button>
-                    <a href="<%- preUrl %>/audit/check/again" class="btn btn-warning btn-sm">确定重审</a>
+                    <!--<a href="<%- preUrl %>/audit/check/again" disabled class="btn btn-warning btn-sm">确定重审</a>-->
+                    <button disabled id="re-shenpi-btn" class="btn btn-warning btn-sm">确定重审</button>
                 </div>
             </div>
         </div>
@@ -1512,6 +1541,10 @@
     <% } %>
 <% } %>
 <% include ../shares/check_data_modal.ejs %>
+<script type="text/javascript">
+    const csrf = '<%= ctx.csrf %>';
+    const authMobile = '<%= authMobile %>';
+</script>
 <script>
     <% if (ctx.url !== '/tender/' + ctx.tender.id + '/measure/stage/' + ctx.stage.order) { %>
     const dataChecker = DataChecker({
@@ -1618,4 +1651,30 @@
         }
         return false;
     }
+
+    // 重新审批按钮
+    $("#re-shenpi-btn").click(function() {
+        const code = $("input[name='code']").val();
+        if ($(this).hasClass('disabled')) {
+            return false;
+        }
+        if (code.length < 6) {
+            // alert('请填写正确的验证码');
+            toast('请填写正确的验证码', 'error');
+            return false;
+        }
+        $.ajax({
+            url: '<%- preUrl %>/audit/check/again',
+            type: 'get',
+            data: { code: code },
+            dataTye: 'json',
+            success: function(response) {
+                if (response.err === 0) {
+                    window.location.href = response.url;
+                } else {
+                    toast(response.msg, 'error');
+                }
+            }
+        });
+    });
 </script>

+ 3 - 0
app/view/stage/index.ejs

@@ -32,6 +32,9 @@
                         <input type="text" class="form-control form-control-sm m-0" id="bills-expr" readonly="">
                     </div>
                 </div>
+                <div class="d-inline-block ml-3">
+                    <a id="exportExcel" class="btn btn-primary btn-sm" href="javascript: void(0)">导出计量台账Excel</a>
+                </div>
             </div>
             <div class="ml-auto">
             </div>

+ 64 - 3
app/view/stage/manager_modal.ejs

@@ -97,10 +97,29 @@
             </div>
         </div>
     </div>
-        <% } else { %>
+<% } else if (!authMobile) { %>
+    <!--终审重新审批-->
+    <div class="modal fade" id="pass" data-backdrop="static">
+        <div class="modal-dialog" role="document">
+            <div class="modal-content">
+                <div class="modal-header">
+                    <h5 class="modal-title">重新审批</h5>
+                </div>
+                <div class="modal-body">
+                    <h5>重新审批需要您的手机短信验证</h5>
+                    <h5>您目前还没设置认证手机,请先设置。</h5>
+                </div>
+                <div class="modal-footer">
+                    <button type="button" class="btn btn-sm btn-secondary" data-dismiss="modal">取消</button>
+                    <a href="/profile/sms" class="btn btn-sm btn-primary">去设置</a>
+                </div>
+            </div>
+        </div>
+    </div>
+<% } else { %>
 <div class="modal fade" id="pass" data-backdrop="static">
     <div class="modal-dialog" role="document">
-        <form action="/tender/<%- ctx.tender.id %>/measure/stage/<%- lastStage.order %>/audit/check/again" method="get" class="modal-content">
+        <form id="againForm" action="/tender/<%- ctx.tender.id %>/measure/stage/<%- lastStage.order %>/audit/check/again" method="get" class="modal-content" onsubmit="return false;">
             <div class="modal-header">
                 <h5 class="modal-title">设置终审审批</h5>
             </div>
@@ -108,10 +127,21 @@
                 <p class="mb-2">设置本期计量终审「<%- lastAuditList[0][0].name  %>」为审批中状态。</p>
                 <p class="mb-2">请在下方文本框输入文本「<span class="text-danger">确认设置终审审批</span>」,确认设置。</p>
                 <p class="mb-2"><input type="text" name="confirm" class="form-control form-control-sm" placeholder="输入文本,确认设置">
+                <% if (ctx.session.sessionUser.loginStatus === 0) { %>
+                <div class="form-group">
+                    <label>重审需要验证码确认,验证码将发送至尾号<%- authMobile.slice(-4) %>的手机</label>
+                    <div class="input-group input-group-sm mb-3">
+                        <input class="form-control" type="text" readonly="readonly" name="code" placeholder="输入短信中的6位验证码" />
+                        <div class="input-group-append">
+                            <button class="btn btn-outline-secondary" type="button" id="get-code">获取验证码</button>
+                        </div>
+                    </div>
+                </div>
+                <% } %>
             </div>
             <div class="modal-footer">
                 <button type="button" class="btn btn-sm btn-secondary" data-dismiss="modal">取消</button>
-                <button type="submit" class="btn btn-sm btn-warning">确认设置</button>
+                <button type="button" onclick="checkSubmit()" class="btn btn-sm btn-warning">确认设置</button>
             </div>
         </form>
     </div>
@@ -120,3 +150,34 @@
 <% } %>
 <% } %>
 <% include ./audit_modal.ejs %>
+<script>
+    function checkSubmit() {
+        let flag = true;
+        if ($('#pass input[name="confirm"]').val() !== '确认设置终审审批') {
+            toastr.error('请输入正确的文本');
+            flag = false;
+        }
+        <% if (ctx.session.sessionUser.loginStatus === 0) { %>
+        if ($('#pass input[name="code"]').val() === '') {
+            toastr.error('请输入验证码');
+            flag = false;
+        }
+        <% } %>
+        if (flag) {
+            $.ajax({
+                type: 'get',
+                url: '/tender/<%- ctx.tender.id %>/measure/stage/<%- lastStage.order %>/audit/check/again',
+                data: $('#againForm').serialize(),
+                dataType: 'json',
+                success: function(response) {
+                    if (response.err === 0) {
+                        window.location.href = response.url;
+                    } else {
+                        toast(response.msg, 'error');
+                    }
+                }
+            })
+        }
+
+    }
+</script>

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

@@ -129,10 +129,10 @@
         </div>
     </div>
 </div>
+<!-- <% include ./audit_modal.ejs %> -->
 <script type="text/javascript" src="/public/js/ztree/jquery.ztree.core.js"></script>
 <script type="text/javascript" src="/public/js/ztree/jquery.ztree.excheck.js"></script>
 <script type="text/javascript">
-    <!--
     var setting = {
         view: {
             selectedMulti: false
@@ -198,4 +198,4 @@
 </script>
 <script type="text/javascript">
     autoFlashHeight();
-</script>
+</script>

+ 3 - 0
config/web.js

@@ -278,8 +278,10 @@ const JsFiles = {
             index: {
                 files: [
                     '/public/js/spreadjs/sheets/v11/gc.spread.sheets.all.11.2.2.min.js',
+                    '/public/js/spreadjs/sheets/v11/interop/gc.spread.excelio.11.2.2.min.js',
                     '/public/js/decimal.min.js',
                     '/public/js/math.min.js',
+                    '/public/js/file-saver/FileSaver.js',
                 ],
                 mergeFiles: [
                     '/public/js/sub_menu.js',
@@ -287,6 +289,7 @@ const JsFiles = {
                     '/public/js/msg_box.js',
                     '/public/js/spreadjs_rela/spreadjs_zh.js',
                     '/public/js/shares/sjs_setting.js',
+                    '/public/js/shares/export_excel.js',
                     '/public/js/shares/cs_tools.js',
                     '/public/js/shares/merge_peg.js',
                     '/public/js/zh_calc.js',