Преглед изворни кода

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

ellisran пре 2 недеља
родитељ
комит
f4dd1d19a0

+ 1 - 0
app/const/quality_inspection.js

@@ -6,4 +6,5 @@ inspection.stage = { legacy: 0, report: 1, rectification: 2, reply: 3 };
 inspection.stageString = ['旧流程', '上报', '整改', '批复'];
 inspection.filter.status.reply = 13;
 inspection.filter.statusString[13] = '批复中';
+inspection.statusButtonClass[inspection.status.checkNoPre] = 'btn-success';
 module.exports = inspection;

+ 6 - 1
app/lib/budget_final.js

@@ -95,7 +95,7 @@ class BudgetFinal {
     constructor (ctx) {
         this.ctx = ctx;
         this.budgetSetting = { id: 'tree_id', pid: 'tree_pid', order: 'order', level: 'level', rootId: -1, calcFields: ['total_price'] };
-        this.tenderSetting = { id: 'ledger_id', pid: 'ledger_pid', order: 'order', level: 'level', rootId: -1, calcFields: ['total_price', 'end_gather_tp', 'end_contract_tp', 'end_qc_tp', 'tz_qc_tp'] };
+        this.tenderSetting = { id: 'ledger_id', pid: 'ledger_pid', order: 'order', level: 'level', rootId: -1, calcFields: ['total_price', 'end_gather_tp', 'end_contract_tp', 'end_qc_tp', 'tz_qc_tp', 'un_tz_qc_tp'] };
         this.finalTree = new FinalTree(this.ctx, { id: 'id', pid: 'pid', order: 'order', level: 'level', fullPath: 'full_path', rootId: -1 });
     }
 
@@ -295,6 +295,8 @@ class BudgetFinal {
         const tenderInfo = await this.ctx.service.tenderInfo.getTenderInfo(id);
         const reCalcChange = await this.ctx.service.change.getReCalcChangeData(id, tenderInfo);
         TreeUtils.loadChangeData(tree, null, reCalcChange, tenderInfo, helper);
+        const reCalcUnChange = await this.ctx.service.change.getReCalcChangeData(id, tenderInfo, false);
+        TreeUtils.loadChangeData(tree, null, reCalcUnChange, tenderInfo, helper, 'un_');
 
         tree.calculateAll();
         this.finalTree.loadTree(tree, function (cur, source) {
@@ -313,6 +315,8 @@ class BudgetFinal {
 
             cur.tz_qc_qty = helper.add(cur.tz_qc_qty, source.tz_qc_qty);
             cur.tz_qc_tp = helper.add(cur.tz_qc_tp, source.tz_qc_tp);
+            cur.un_tz_qc_qty = helper.add(cur.un_tz_qc_qty, source.un_tz_qc_qty);
+            cur.un_tz_qc_tp = helper.add(cur.un_tz_qc_tp, source.un_tz_qc_tp);
         });
     }
 
@@ -444,6 +448,7 @@ class BudgetFinal {
                 final_contract_tp: x.final_contract_tp || 0, final_qc_tp: x.final_qc_tp || 0,
                 final_dgn_price: x.final_dgn_price || 0, final_dgn_qty: x.final_dgn_qty || '',
                 tz_qc_qty: x.tz_qc_qty || 0, tz_qc_tp: x.tz_qc_tp || 0,
+                un_tz_qc_qty: x.un_tz_qc_qty || 0, un_tz_qc_tp: x.un_tz_qc_tp || 0,
                 grow_dgn_qty1: x.grow_dgn_qty1 || 0, grow_dgn_qty2: x.grow_dgn_qty2 || 0, grow_dgn_qty: x.grow_dgn_qty || '', grow_tp: x.grow_tp || 0,
             })
         });

+ 11 - 10
app/lib/ledger.js

@@ -1447,7 +1447,7 @@ class reviseTree extends billsTree {
 }
 
 const treeUtils = {
-    loadChangeData: function(tree, pos, changes, tenderInfo, helper) {
+    loadChangeData: function(tree, pos, changes, tenderInfo, helper, prefix = '') {
         const findEmptyBills = function(tree, change, changeBills) {
             const cb = {
                 b_code: changeBills.code || '',
@@ -1475,26 +1475,27 @@ const treeUtils = {
                     : findEmptyBills(tree, change, changeBills);
 
                 if (!node) continue;
-                if (!node.changeBills) node.changeBills = [];
-                node.tz_qc_qty = helper.add(node.tz_qc_qty, changeBills.checked_amount);
+                if (!node[prefix + 'changeBills']) node[prefix + 'changeBills'] = [];
+                node[prefix + 'tz_qc_qty'] = helper.add(node[prefix + 'tz_qc_qty'], changeBills.checked_amount);
                 if (tenderInfo.calc_type === 'up') {
-                    node.tz_qc_tp = helper.add(node.tz_qc_tp, changeBills.checked_price);
+                    node[prefix + 'tz_qc_tp'] = helper.add(node[prefix + 'tz_qc_tp'], changeBills.checked_price);
                 } else {
-                    node.tz_qc_tp = helper.add(node.tz_qc_tp, changeBills.tp);
+                    node[prefix + 'tz_qc_tp'] = helper.add(node[prefix + 'tz_qc_tp'], changeBills.tp);
                 }
+                console.log(prefix, changeBills, node);
 
                 const posData = pos ? pos.getLedgerPos(node.id) : undefined;
                 if (posData && posData.length > 0) {
                     for (const p of changeBills.pos) {
                         const changePos = p.bwmx ? posData.find(x => { return x.name === p.bwmx; }) : posData[0];
                         if (changePos) {
-                            changePos.tz_qc_qty = helper.add(changePos.tz_qc_qty, p.checked_amount);
-                            if (!changePos.changeBills) changePos.changeBills = [];
-                            changePos.changeBills.push({
+                            changePos[prefix + 'tz_qc_qty'] = helper.add(changePos[prefix + 'tz_qc_qty'], p.checked_amount);
+                            if (!changePos[prefix + 'changeBills']) changePos[prefix + 'changeBills'] = [];
+                            changePos[prefix + 'changeBills'].push({
                                 code: change.code || '', name: change.name || '', is_valuation:  p.is_valuation,
                                 qty: p.checked_amount || 0, tp: p.checked_price || 0, pos: changePos.name,
                             });
-                            node.changeBills.push({
+                            node[prefix + 'changeBills'].push({
                                 code: change.code || '', name: change.name || '', is_valuation: p.is_valuation,
                                 qty: p.checked_amount || 0, tp: p.checked_price || 0, pos: changePos.name,
                             });
@@ -1502,7 +1503,7 @@ const treeUtils = {
                     }
                 } else {
                     for (const p of changeBills.pos) {
-                        node.changeBills.push({
+                        node[prefix + 'changeBills'].push({
                             code: change.code || '', name: change.name || '', is_valuation: p.is_valuation,
                             qty: p.checked_amount || 0, tp: p.checked_price || 0, pos: '',
                         });

+ 52 - 0
app/public/css/main.css

@@ -3245,6 +3245,12 @@ button.gy-stat:focus-visible{outline:2px solid var(--accent);outline-offset:3px}
   }
 }
 
+.safe-inspection-need-pending .safe-inspection-need-options {
+    visibility: hidden;
+}
+.safe-inspection-need-pending .safe-inspection-flow {
+    display: none;
+}
 .safe-inspection-flow .flow-picker > .dropdown-menu {
     width: 220px;
 }
@@ -3305,6 +3311,18 @@ button.gy-stat:focus-visible{outline:2px solid var(--accent);outline-offset:3px}
 .safe-inspection-flow .flow-phase-person {
     padding: 12px 18px;
 }
+.safe-inspection-flow .flow-progress-layout .flow-phase-title,
+.safe-inspection-flow .flow-stage-progress,
+.safe-inspection-flow .flow-timeline-empty {
+    margin-left: 70px;
+}
+.safe-inspection-flow .flow-stage-progress {
+    margin-bottom: 8px;
+}
+.safe-inspection-flow .flow-rectification-result {
+    white-space: pre-wrap;
+    overflow-wrap: break-word;
+}
 .safe-inspection-flow .flow-person-names {
     min-width: 0;
     overflow-wrap: break-word;
@@ -3401,6 +3419,12 @@ button.gy-stat:focus-visible{outline:2px solid var(--accent);outline-offset:3px}
 }
 
 /* 质量巡检三阶段流程及概况 */
+.quality-inspection-need-pending .quality-inspection-need-options {
+    visibility: hidden;
+}
+.quality-inspection-need-pending .quality-inspection-flow {
+    display: none;
+}
 .quality-inspection-flow .flow-picker > .dropdown-menu {
     width: 220px;
 }
@@ -3461,6 +3485,18 @@ button.gy-stat:focus-visible{outline:2px solid var(--accent);outline-offset:3px}
 .quality-inspection-flow .flow-phase-person {
     padding: 12px 18px;
 }
+.quality-inspection-flow .flow-progress-layout .flow-phase-title,
+.quality-inspection-flow .flow-stage-progress,
+.quality-inspection-flow .flow-timeline-empty {
+    margin-left: 70px;
+}
+.quality-inspection-flow .flow-stage-progress {
+    margin-bottom: 8px;
+}
+.quality-inspection-flow .flow-rectification-result {
+    white-space: pre-wrap;
+    overflow-wrap: break-word;
+}
 .quality-inspection-flow .flow-person-names {
     min-width: 0;
     overflow-wrap: break-word;
@@ -3555,3 +3591,19 @@ button.gy-stat:focus-visible{outline:2px solid var(--accent);outline-offset:3px}
     gap: 8px;
     flex-wrap: wrap;
 }
+
+.quality-inspection-attachments .modal-dialog {
+    width: max-content;
+    max-width: calc(100% - 32px);
+    margin-right: auto;
+    margin-left: auto;
+}
+.quality-inspection-attachments table {
+    width: auto;
+    max-width: 100%;
+    table-layout: auto;
+}
+.quality-inspection-attachments th,
+.quality-inspection-attachments td {
+    white-space: nowrap;
+}

+ 39 - 12
app/public/js/quality_inspection_information.js

@@ -754,6 +754,7 @@ function postQualityInspection(url, data, success, failure) {
 function bindQualityInspectionFlow() {
     const $flow = $('.quality-inspection-flow');
     const $radios = $('[name="quality-need-rectification"]');
+    const isWap = window.location.pathname.indexOf('/wap/') === 0;
     let needRectification = qualityInspectionNeedDraft();
     showQualityInspectionNeed(needRectification);
     let pending = null;
@@ -765,6 +766,7 @@ function bindQualityInspectionFlow() {
         postQualityInspection(preUrl + '/save', data, function () {
             busy = false;
             $button.prop('disabled', false);
+            if (!isWap && data.type === 'start-inspection') qualityInspectionNeedDraft(null);
             if (reload) {
                 if (!inspection.readOnly && ['flow-add-audit', 'flow-del-audit'].includes(data.type)) qualityInspectionNeedDraft(needRectification);
                 window.location.reload();
@@ -828,6 +830,7 @@ function bindQualityInspectionFlow() {
             return;
         }
         needRectification = value;
+        if (!isWap) qualityInspectionNeedDraft(needRectification);
         showQualityInspectionNeed(needRectification);
     });
     $('.flow-save-rectification').on('click', function () {
@@ -858,40 +861,63 @@ function bindQualityInspectionFlow() {
         } else if (type === 'flow-check') {
             message = pending.checkType === 3 ? '确认本次审批通过?' : pending.checkType === 4 ? '确认关闭本次巡检?' : inspection.flow_stage === 3 ? '确认退回重新整改?所有整改人将同时开始新一轮整改。' : pending.checkType === 5 ? '确认退回上报人重新上报?' : '确认退回上一个上报审批节点?';
         }
+        const isReturn = type === 'flow-return';
         $('#quality-flow-title').text($(this).text());
-        $('#quality-flow-message').text(message);
+        $('#quality-flow-message').text(message).toggle(!isReturn);
+        $('#quality-flow-return-group').toggleClass('d-none', !isReturn);
+        $('[name="quality-flow-return-type"]').prop('checked', false);
         $('#quality-flow-opinion').val('').css('height', '');
-        $('#quality-flow-opinion-group').toggle(type === 'flow-check');
+        $('#quality-flow-opinion-group').toggle(type === 'flow-check' || isReturn).find('label').text(isReturn ? '退回意见' : '处理意见');
+        $('#quality-flow-cancel').text(isReturn ? '关闭' : '取消');
+        $('#quality-flow-submit').text(isReturn ? '确认退回' : '确认').toggleClass('btn-warning', isReturn).toggleClass('btn-primary', !isReturn);
         $('#quality-flow-confirm').modal('show');
     });
     $('#quality-flow-submit').on('click', function () {
         if (!pending || busy) return;
-        if (pending.type === 'flow-check') {
-            pending.opinion = $('#quality-flow-opinion').val().trim();
-            if (pending.checkType !== 3 && !pending.opinion) {
-                toastr.error('请填写退回或关闭原因');
+        const data = Object.assign({}, pending);
+        if (data.type === 'flow-return') {
+            const checkType = Number($('[name="quality-flow-return-type"]:checked').val());
+            if (![5, 6].includes(checkType) || (checkType === 6 && !inspection.flowCanReturnPrevious)) {
+                toastr.error('请选择退回位置');
                 return;
             }
+            data.type = 'flow-check';
+            data.checkType = checkType;
         }
-        request(pending, $(this), true);
+        if (data.type === 'flow-check') {
+            data.opinion = $('#quality-flow-opinion').val().trim();
+            if (data.checkType !== 3 && !data.opinion) {
+                toastr.error(pending.type === 'flow-return' ? '请填写退回意见' : '请填写退回或关闭原因');
+                return;
+            }
+        }
+        request(data, $(this), true);
     });
 }
 
-// 仅在添加、移除审批人引起的刷新之间暂存选择,读取后即清除。
+// 电脑版按用户、巡检单和轮次保留草稿选择;手机端沿用一次性暂存。
 function qualityInspectionNeedDraft(value) {
     const current = inspection.need_rectification === 0 ? 0 : 1;
     const key = 'quality-inspection-need:' + cur_uid + ':' + inspection.id + ':' + inspection.times + ':' + inspection.stage_round;
+    const isWap = window.location.pathname.indexOf('/wap/') === 0;
     try {
+        if (!isWap && (value === null || inspection.readOnly)) {
+            removeLocalCache(key);
+            return current;
+        }
         if (value !== undefined) {
-            sessionStorage.setItem(key, JSON.stringify({ value, savedValue: current }));
+            const saved = JSON.stringify({ value, savedValue: current });
+            if (isWap) sessionStorage.setItem(key, saved);
+            else setLocalCache(key, saved);
         } else {
-            const saved = sessionStorage.getItem(key);
-            sessionStorage.removeItem(key);
+            const saved = isWap ? sessionStorage.getItem(key) : getLocalCache(key);
+            if (isWap) sessionStorage.removeItem(key);
             const draft = saved ? JSON.parse(saved) : null;
             if (!inspection.readOnly && draft && draft.savedValue === current && [0, 1].includes(draft.value)) return draft.value;
+            if (!isWap && saved) removeLocalCache(key);
         }
     } catch (error) {
-        console.warn('无法暂存是否整改选项', error);
+        console.warn('无法保存或读取是否整改选项', error);
     }
     return current;
 }
@@ -900,4 +926,5 @@ function showQualityInspectionNeed(value) {
     $('[name="quality-need-rectification"]').filter('[value="' + value + '"]').prop('checked', true);
     $('.quality-inspection-flow [data-flow-stage="2"], .quality-inspection-flow [data-flow-stage="3"]').toggleClass('d-none', value === 0);
     $('.quality-inspection-flow .flow-no-rectification').toggleClass('d-none', value !== 0);
+    $('.quality-inspection-need-pending').removeClass('quality-inspection-need-pending');
 }

+ 39 - 12
app/public/js/safe_inspection_information.js

@@ -754,6 +754,7 @@ function postSafeInspection(url, data, success, failure) {
 function bindSafeInspectionFlow() {
     const $flow = $('.safe-inspection-flow');
     const $radios = $('[name="safe-need-rectification"]');
+    const isWap = window.location.pathname.indexOf('/wap/') === 0;
     let needRectification = safeInspectionNeedDraft();
     showSafeInspectionNeed(needRectification);
     let pending = null;
@@ -765,6 +766,7 @@ function bindSafeInspectionFlow() {
         postSafeInspection(preUrl + '/save', data, function () {
             busy = false;
             $button.prop('disabled', false);
+            if (!isWap && data.type === 'start-inspection') safeInspectionNeedDraft(null);
             if (reload) {
                 if (!inspection.readOnly && ['flow-add-audit', 'flow-del-audit'].includes(data.type)) safeInspectionNeedDraft(needRectification);
                 window.location.reload();
@@ -828,6 +830,7 @@ function bindSafeInspectionFlow() {
             return;
         }
         needRectification = value;
+        if (!isWap) safeInspectionNeedDraft(needRectification);
         showSafeInspectionNeed(needRectification);
     });
     $('.flow-save-rectification').on('click', function () {
@@ -858,40 +861,63 @@ function bindSafeInspectionFlow() {
         } else if (type === 'flow-check') {
             message = pending.checkType === 3 ? '确认本次审批通过?' : pending.checkType === 4 ? '确认关闭本次巡检?' : inspection.flow_stage === 3 ? '确认退回重新整改?所有整改人将同时开始新一轮整改。' : pending.checkType === 5 ? '确认退回上报人重新上报?' : '确认退回上一个上报审批节点?';
         }
+        const isReturn = type === 'flow-return';
         $('#safe-flow-title').text($(this).text());
-        $('#safe-flow-message').text(message);
+        $('#safe-flow-message').text(message).toggle(!isReturn);
+        $('#safe-flow-return-group').toggleClass('d-none', !isReturn);
+        $('[name="safe-flow-return-type"]').prop('checked', false);
         $('#safe-flow-opinion').val('').css('height', '');
-        $('#safe-flow-opinion-group').toggle(type === 'flow-check');
+        $('#safe-flow-opinion-group').toggle(type === 'flow-check' || isReturn).find('label').text(isReturn ? '退回意见' : '处理意见');
+        $('#safe-flow-cancel').text(isReturn ? '关闭' : '取消');
+        $('#safe-flow-submit').text(isReturn ? '确认退回' : '确认').toggleClass('btn-warning', isReturn).toggleClass('btn-primary', !isReturn);
         $('#safe-flow-confirm').modal('show');
     });
     $('#safe-flow-submit').on('click', function () {
         if (!pending || busy) return;
-        if (pending.type === 'flow-check') {
-            pending.opinion = $('#safe-flow-opinion').val().trim();
-            if (pending.checkType !== 3 && !pending.opinion) {
-                toastr.error('请填写退回或关闭原因');
+        const data = Object.assign({}, pending);
+        if (data.type === 'flow-return') {
+            const checkType = Number($('[name="safe-flow-return-type"]:checked').val());
+            if (![5, 6].includes(checkType) || (checkType === 6 && !inspection.flowCanReturnPrevious)) {
+                toastr.error('请选择退回位置');
                 return;
             }
+            data.type = 'flow-check';
+            data.checkType = checkType;
         }
-        request(pending, $(this), true);
+        if (data.type === 'flow-check') {
+            data.opinion = $('#safe-flow-opinion').val().trim();
+            if (data.checkType !== 3 && !data.opinion) {
+                toastr.error(pending.type === 'flow-return' ? '请填写退回意见' : '请填写退回或关闭原因');
+                return;
+            }
+        }
+        request(data, $(this), true);
     });
 }
 
-// 仅在添加、移除审批人引起的刷新之间暂存选择,读取后即清除。
+// 电脑版按用户、巡检单和轮次保留草稿选择;手机端沿用一次性暂存。
 function safeInspectionNeedDraft(value) {
     const current = inspection.need_rectification === 0 ? 0 : 1;
     const key = 'safe-inspection-need:' + cur_uid + ':' + inspection.id + ':' + inspection.times + ':' + inspection.stage_round;
+    const isWap = window.location.pathname.indexOf('/wap/') === 0;
     try {
+        if (!isWap && (value === null || inspection.readOnly)) {
+            removeLocalCache(key);
+            return current;
+        }
         if (value !== undefined) {
-            sessionStorage.setItem(key, JSON.stringify({ value, savedValue: current }));
+            const saved = JSON.stringify({ value, savedValue: current });
+            if (isWap) sessionStorage.setItem(key, saved);
+            else setLocalCache(key, saved);
         } else {
-            const saved = sessionStorage.getItem(key);
-            sessionStorage.removeItem(key);
+            const saved = isWap ? sessionStorage.getItem(key) : getLocalCache(key);
+            if (isWap) sessionStorage.removeItem(key);
             const draft = saved ? JSON.parse(saved) : null;
             if (!inspection.readOnly && draft && draft.savedValue === current && [0, 1].includes(draft.value)) return draft.value;
+            if (!isWap && saved) removeLocalCache(key);
         }
     } catch (error) {
-        console.warn('无法暂存是否整改选项', error);
+        console.warn('无法保存或读取是否整改选项', error);
     }
     return current;
 }
@@ -900,4 +926,5 @@ function showSafeInspectionNeed(value) {
     $('[name="safe-need-rectification"]').filter('[value="' + value + '"]').prop('checked', true);
     $('.safe-inspection-flow [data-flow-stage="2"], .safe-inspection-flow [data-flow-stage="3"]').toggleClass('d-none', value === 0);
     $('.safe-inspection-flow .flow-no-rectification').toggleClass('d-none', value !== 0);
+    $('.safe-inspection-need-pending').removeClass('safe-inspection-need-pending');
 }

+ 15 - 10
app/service/change.js

@@ -2230,18 +2230,24 @@ module.exports = app => {
             return returnList;
         }
 
-        async getReCalcChangeData(tenderId, tenderInfo) {
-            const change = await this.ctx.service.change.getAllDataByCondition({
-                columns: [ 'cid', 'code', 'name', 'w_code', 'tp_decimal', 'up_decimal' ],
-                where: { tid: tenderId, valid: 1, status: audit.flow.status.checked },
+        async getReCalcChangeData(tenderId, tenderInfo, checked = true) {
+            let change = await this.ctx.service.change.getAllDataByCondition({
+                columns: [ 'cid', 'code', 'name', 'w_code', 'tp_decimal', 'up_decimal', 'status' ],
+                where: { tid: tenderId, valid: 1 },
                 orders: [['code', 'asc']],
             });
+            if (checked) {
+                change = change.filter(x => { return x.status === audit.flow.status.checked; });
+            } else {
+                change = change.filter(x => { return x.status !== audit.flow.status.checked; });
+            }
             const changeBills = await this.ctx.service.changeAuditList.getAllDataByCondition({
-                columns: [ 'id', 'cid', 'code', 'name', 'unit', 'unit_price', 'oamount2', 'checked_amount', 'checked_price', 'is_valuation', 'gcl_id', 'bwmx', 'mx_id'],
+                columns: [ 'id', 'cid', 'code', 'name', 'unit', 'unit_price', 'oamount2', 'checked_amount', 'checked_price', 'is_valuation', 'gcl_id', 'bwmx', 'mx_id', 'camount'],
                 where: { tid: tenderId }
             });
             const changeIndex = {};
             change.forEach(x => { changeIndex[x.cid] = x; x.bills = [] });
+            const field = checked ? 'checked_amount' : 'camount';
             changeBills.forEach(cb => {
                 const c = changeIndex[cb.cid];
                 if (!c) return;
@@ -2251,19 +2257,18 @@ module.exports = app => {
                         b = { code: cb.code, name: cb.name, unit: cb.unit, unit_price: cb.unit_price, pos: [], gcl_id: cb.gcl_id };
                         c.bills.push(b);
                     }
-                    b.checked_amount = this.ctx.helper.add(b.checked_amount, cb.checked_amount);
+                    b.checked_amount = this.ctx.helper.add(b.checked_amount, cb[field]);
                     if (cb.is_valuation) {
-                        b.qty = this.ctx.helper.add(b.qty, cb.checked_amount);
+                        b.qty = this.ctx.helper.add(b.qty, cb[field]);
                     } else {
-                        b.minus_qty = this.ctx.helper.add(b.minus_qty, cb.checked_amount);
+                        b.minus_qty = this.ctx.helper.add(b.minus_qty, cb[field]);
                     }
                     b.pos.push(cb);
                 } else {
                     c.bills.push({
                         code: cb.code, name: cb.name, unit: cb.unit, unit_price: cb.unit_price,
                         gcl_id: '', checked_amount: cb.checked_amount, checked_price: cb.checked_price,
-                        qty: cb.is_valuation ? cb.checked_amount : 0, tp: cb.is_valuation ? cb.checked_price : 0,
-                        minus_qty: !cb.is_valuation ? cb.checked_amount : 0, minus_tp: !cb.is_valuation ? cb.checked_price : 0,
+                        qty: cb.is_valuation ? cb[field] : 0, minus_qty: !cb.is_valuation ? cb[field] : 0,
                         pos: [cb],
                     });
                 }

+ 2 - 1
app/service/quality_inspection.js

@@ -275,7 +275,8 @@ module.exports = app => {
 
         setFlowLabel(inspection) {
             const status = inspection.status === undefined ? inspection.mstatus : inspection.status;
-            inspection.flowLabel = inspection.flow_version === 1 && [2, 6, 12].includes(status) ? auditConst.stageString[inspection.flow_stage] + '中' : auditConst.statusString[status];
+            const stageLabel = inspection.flow_stage === auditConst.stage.report ? '审批中' : auditConst.stageString[inspection.flow_stage] + '中';
+            inspection.flowLabel = inspection.flow_version === 1 && [2, 6, 12].includes(status) ? stageLabel : auditConst.statusString[status];
             inspection.actionLabel = status === 1 || status === 5 ? '上报' : status === 12 ? '整改' : inspection.flow_version === 1 && inspection.flow_stage === 3 ? '批复' : '审批';
         }
 

+ 30 - 13
app/service/stage_audit.js

@@ -326,6 +326,13 @@ module.exports = app => {
 
             const transaction = await this.db.beginTransaction();
             try {
+                const sql = 'SELECT * FROM ?? WHERE id = ? FOR UPDATE';
+                const sqlParam = [this.ctx.service.stage.tableName, stageId];
+                const currentStage = await transaction.queryOne(sql, sqlParam);
+                if (!currentStage || currentStage.times !== times ||
+                    [auditConst.status.uncheck, auditConst.status.checkNo].indexOf(currentStage.status) < 0) {
+                    throw '计量期状态已变化,请刷新后重试';
+                }
                 const ledgerTp = await this._updateTender(transaction);
                 const begin_time = new Date();
                 const updateData = audits.map(x => { return { id: x.id, status: auditConst.status.checking, begin_time, audit_locked: 0 } });
@@ -427,22 +434,32 @@ module.exports = app => {
         async _checked(pid, stageId, checkData, times) {
             const accountId = this.ctx.session.sessionUser.accountId;
             const time = new Date();
-            // 整理当前流程审核人状态更新
-            const audits = await this.getAllDataByCondition({ where: { sid: stageId, times, status: auditConst.status.checking } });
-            if (audits.length === 0) throw '审核数据错误';
-            const selfAudit = audits.find(x => { return x.aid === accountId; });
-            if (!selfAudit) throw '当前标段您无权审批';
+            const transaction = await this.db.beginTransaction();
+            try {
+                const sql = 'SELECT * FROM ?? WHERE id = ? FOR UPDATE';
+                const sqlParam = [this.ctx.service.stage.tableName, stageId];
+                const currentStage = await transaction.queryOne(sql, sqlParam);
+                if (!currentStage || currentStage.times !== times || currentStage.status !== auditConst.status.checking) {
+                    throw '计量期状态已变化,请刷新后重试';
+                }
+                this.ctx.stage.tp_history = currentStage.tp_history ? JSON.parse(currentStage.tp_history) : [];
+                // 锁定计量期后重新读取审批状态,避免使用并发请求开始时的旧数据。
+                const audits = await this.getAllDataByCondition({ where: { sid: stageId, times, status: auditConst.status.checking } });
+                if (audits.length === 0) throw '审核数据错误';
+                const selfAudit = audits.find(x => { return x.aid === accountId; });
+                if (!selfAudit) throw '当前标段您无权审批';
+                if (!this.ctx.stage.curAuditors.some(audit => audit.id === selfAudit.id)) {
+                    throw '审批流程已变化,请刷新后重试';
+                }
 
-            const flowAudits = await this.getAllDataByCondition({ where: { sid: stageId, times, order: selfAudit.order } });
-            const nextAudits = await this.getAllDataByCondition({ where: { sid: stageId, times, order: selfAudit.order + 1 } });
-            const tpData = await this.ctx.service.stageBills.getSumTotalPrice(this.ctx.stage);
-            const stageInfo = await this.ctx.service.stage.getDataById(stageId);
+                const flowAudits = await this.getAllDataByCondition({ where: { sid: stageId, times, order: selfAudit.order } });
+                const nextAudits = await this.getAllDataByCondition({ where: { sid: stageId, times, order: selfAudit.order + 1 } });
+                const tpData = await this.ctx.service.stageBills.getSumTotalPrice(this.ctx.stage);
+                const stageInfo = await this.ctx.service.stage.getDataById(stageId);
 
-            // 计算并合同支付最终数据
-            const [yfPay, sfPay, stagePays] = await this.ctx.service.stagePay.calcAllStagePays(this.ctx.stage);
+                // 计算并合同支付最终数据
+                const [yfPay, sfPay, stagePays] = await this.ctx.service.stagePay.calcAllStagePays(this.ctx.stage, transaction);
 
-            const transaction = await this.db.beginTransaction();
-            try {
                 const ledgerTp = await this._updateTender(transaction);
                 // 添加通知
                 const noticeContent = await this.getNoticeContent(pid, stageInfo.tid, stageId, accountId, checkData.opinion);

+ 2 - 0
app/service/stage_pay.js

@@ -334,6 +334,8 @@ module.exports = app => {
             if (!stage || !transaction || !times || order === undefined) {
                 throw '数据错误';
             }
+            const existed = await transaction.get(this.tableName, { sid: stage.id, stimes: times, sorder: order });
+            if (existed) return { affectedRows: 0 };
             const sql = 'INSERT INTO ?? (`tid`, `sid`, `pid`, `stimes`, `sorder`, `name`, `tp`, `expr`, `pause`,' +
                         '    `pre_tp`, `end_tp`, `pre_used`, `pre_finish`, `start_stage_order`, `postil`) ' +
                         '  SELECT SP.`tid`, SP.`sid`, SP.`pid`, ?, ?, SP.name, SP.`tp`, SP.`expr`, SP.`pause`,' +

+ 19 - 33
app/view/quality/inspection_flow.ejs

@@ -21,8 +21,7 @@
     <% } else { %>
         <div class="flow-title text-center mb-1">审批流程</div>
     <% } %>
-    <% if (!showTimeline) { %>
-    <div class="row flow-layout">
+    <div class="row flow-layout<%= showTimeline ? ' flow-progress-layout' : '' %>">
         <div class="col-md-4 flow-summary mb-3">
             <table class="table table-bordered table-sm flow-summary-table mb-0" aria-label="审批流程人员">
                 <colgroup><col class="flow-phase-label"><col></colgroup>
@@ -49,7 +48,6 @@
             </table>
         </div>
         <div class="col-md-8 flow-details">
-    <% } %>
             <% inspection.flowStages.forEach(phase => { %>
                 <% if (phase.stage === 1 || inspection.need_rectification || !inspection.readOnly) { %>
                     <% const reportMode = ctx.tender.info.shenpi.inspection;
@@ -58,27 +56,6 @@
                     <% if (showTimeline) { %>
                     <section class="flow-phase-section" data-flow-stage="<%= phase.stage %>">
                         <div class="flow-phase-title text-center text-muted">巡检<%= phase.name %></div>
-                        <div class="row flow-layout">
-                            <div class="col-md-4 flow-summary mb-3">
-                                <div class="card">
-                                    <ul class="list-group list-group-flush">
-                                        <% phase.nodes.forEach(node => { %>
-                                            <li class="list-group-item d-flex align-items-center flow-phase-person">
-                                                <i class="fa <%= node.origin ? 'fa-play-circle fa-rotate-90' : node.label === '终审' ? 'fa-stop-circle' : 'fa-chevron-circle-down' %> mr-2" aria-hidden="true"></i>
-                                                <div class="flow-person-names">
-                                                    <% node.auditors.forEach(audit => { %><div><%= audit.name %></div><% }); %>
-                                                </div>
-                                                <div class="ml-auto pl-2 text-right">
-                                                    <span class="badge badge-light badge-pill"><%= node.label %></span>
-                                                    <% if (phase.stage === 2) { %><small class="d-block text-muted"><%= flowStatusText[node.status] %></small><% } %>
-                                                </div>
-                                            </li>
-                                        <% }); %>
-                                        <% if (!phase.nodes.length) { %><li class="list-group-item text-muted"><%= phase.stage === 2 ? '整改人由上报审批人指派' : '尚未设置审批人' %></li><% } %>
-                                    </ul>
-                                </div>
-                            </div>
-                            <div class="col-md-8 flow-details">
                     <% } else { %>
                     <div class="mb-3<%= phase.stage !== 1 && !inspection.need_rectification ? ' d-none' : '' %>" data-flow-stage="<%= phase.stage %>">
                     <% } %>
@@ -109,7 +86,12 @@
                                 </div>
                             </div>
                         <% } %>
-                        <% if (showTimeline && phase.stage !== 2) { %>
+                        <% if (showTimeline) { %>
+                            <% if (phase.stage === 2 && phase.auditors.length) { %>
+                                <div class="flow-stage-progress d-flex justify-content-between flex-wrap text-muted">
+                                    <span>同时整改</span><span>已完成 <%= inspection.rectificationDone %>/<%= inspection.rectificationTotal %></span>
+                                </div>
+                            <% } %>
                             <ul class="timeline-list list-unstyled flow-timeline">
                                 <% phase.nodes.forEach((node, index) => { %>
                                     <% const tone = node.status === 3 ? 'success' : [2, 5, 6, 12].includes(node.status) ? 'warning' : node.status === 4 ? 'danger' : 'secondary';
@@ -123,7 +105,7 @@
                                         <div class="timeline-item-content">
                                             <div class="d-flex justify-content-between py-1">
                                                 <span class="text-black-50"><%= node.label %></span>
-                                                <span class="text-<%= tone %>"><%= node.origin ? '上报审批' : node.status === 3 ? '审批通过' : flowStatusText[node.status] %></span>
+                                                <span class="text-<%= tone %>"><%= node.origin ? '上报审批' : phase.stage !== 2 && node.status === 3 ? '审批通过' : flowStatusText[node.status] %></span>
                                             </div>
                                             <div class="card">
                                                 <div class="card-body px-3 py-2">
@@ -132,9 +114,17 @@
                                                             <div class="d-flex align-items-start">
                                                                 <div class="flow-person-names"><span class="h6 mr-1"><%= audit.name %></span><small class="text-muted"><%= audit.company || '' %><%= audit.company && audit.role ? '-' : '' %><%= audit.role || '' %></small></div>
                                                                 <% const personTone = node.origin || audit.status === 3 ? 'success' : [2, 5, 6, 12].includes(audit.status) ? 'warning' : audit.status === 4 ? 'danger' : 'secondary'; %>
-                                                                <span class="ml-auto pl-2 text-<%= personTone %>"><% if (!node.origin && node.auditors.length > 1) { %><small class="mr-1"><%= flowStatusText[audit.status] %></small><% } %><i class="fa <%= node.origin || audit.status === 3 ? 'fa-check-circle' : [2, 12].includes(audit.status) ? 'fa-commenting' : [5, 6].includes(audit.status) ? 'fa-reply' : audit.status === 4 ? 'fa-stop-circle' : 'fa-circle-o' %>" aria-hidden="true"></i></span>
+                                                                <% if (phase.stage === 2 && editable) { %>
+                                                                    <button class="btn btn-link btn-sm text-danger flow-remove-user p-0 ml-auto" data-stage="<%= phase.stage %>" data-id="<%= audit.aid %>" type="button">移除</button>
+                                                                <% } else { %>
+                                                                    <span class="ml-auto pl-2 text-<%= personTone %>"><% if (!node.origin && node.auditors.length > 1) { %><small class="mr-1"><%= flowStatusText[audit.status] %></small><% } %><i class="fa <%= node.origin || audit.status === 3 ? 'fa-check-circle' : [2, 12].includes(audit.status) ? 'fa-commenting' : [5, 6].includes(audit.status) ? 'fa-reply' : audit.status === 4 ? 'fa-stop-circle' : 'fa-circle-o' %>" aria-hidden="true"></i></span>
+                                                                <% } %>
                                                             </div>
-                                                            <div class="bg-light flow-opinion mt-1" aria-label="处理意见"><i class="fa fa-commenting-o mr-1" aria-hidden="true"></i><%= audit.opinion || '' %></div>
+                                                            <% if (phase.stage === 2) { %>
+                                                                <% if (audit.rectification_item) { %><div class="flow-rectification-result mt-2"><%= audit.rectification_item %></div><% } %>
+                                                                <% if (audit.rectification_date) { %><small class="d-block text-muted mt-1">整改日期:<%= moment(audit.rectification_date).format('YYYY-MM-DD') %></small><% } %>
+                                                            <% } %>
+                                                            <% if (phase.stage !== 2 || audit.opinion) { %><div class="bg-light flow-opinion mt-1" aria-label="<%= phase.stage === 2 ? '说明' : '处理意见' %>"><i class="fa fa-commenting-o mr-1" aria-hidden="true"></i><%= audit.opinion || '' %></div><% } %>
                                                         </div>
                                                     <% }); %>
                                                 </div>
@@ -142,7 +132,7 @@
                                         </div>
                                     </li>
                                 <% }); %>
-                                <% if (!phase.nodes.length) { %><li class="text-muted py-2">尚未设置审批人</li><% } %>
+                                <% if (!phase.nodes.length) { %><li class="flow-timeline-empty text-muted py-2"><%= phase.stage === 2 ? '整改人由上报审批人指派,可设置多人同时处理。' : '尚未设置审批人' %></li><% } %>
                             </ul>
                         <% } else { %>
                         <div class="card">
@@ -196,8 +186,6 @@
                         </div>
                         <% } %>
                     <% if (showTimeline) { %>
-                            </div>
-                        </div>
                     </section>
                     <% } else { %>
                     </div>
@@ -205,10 +193,8 @@
                 <% } %>
             <% }); %>
             <div class="alert alert-warning flow-no-rectification<%= inspection.need_rectification ? ' d-none' : '' %>">本次巡检无整改/批复流程</div>
-    <% if (!showTimeline) { %>
         </div>
     </div>
-    <% } %>
     <% if (inspection.flowHistory.length) { %>
         <details class="card mb-3 d-none">
             <summary class="card-header">查看处理记录</summary>

+ 6 - 5
app/view/quality/inspection_information.ejs

@@ -1,5 +1,5 @@
 <% include ./sub_menu.ejs %>
-<div class="panel-content">
+<div class="panel-content<%= inspection.flow_version === 1 && !inspection.readOnly ? ' quality-inspection-need-pending' : '' %>">
     <div class="panel-title">
         <div class="title-main d-flex justify-content-between">
             <% include ./sub_mini_menu.ejs %>
@@ -20,9 +20,8 @@
                         <button type="button" class="btn btn-success btn-sm flow-action" data-action="start-inspection">提交上报</button>
                     <% } else if (inspection.flowCurrentAudit && inspection.shenpiPower) { %>
                         <button type="button" class="btn btn-success btn-sm flow-action" data-action="flow-check" data-result="3" data-id="<%= inspection.flowCurrentAudit.id %>"><%= inspection.flow_stage === 1 ? '审批' : '批复' %>通过</button>
-                        <button type="button" class="btn btn-warning btn-sm flow-action" data-action="flow-check" data-result="5" data-id="<%= inspection.flowCurrentAudit.id %>"><%= inspection.flow_stage === 1 ? '退回上报人' : '退回重新整改' %></button>
-                        <% if (inspection.flowCanReturnPrevious) { %><button type="button" class="btn btn-outline-warning btn-sm flow-action" data-action="flow-check" data-result="6" data-id="<%= inspection.flowCurrentAudit.id %>">退回上一审批节点</button><% } %>
-                        <button type="button" class="btn btn-outline-danger btn-sm flow-action" data-action="flow-check" data-result="4" data-id="<%= inspection.flowCurrentAudit.id %>">关闭巡检</button>
+                        <button type="button" class="btn btn-warning btn-sm flow-action" data-action="<%= inspection.flow_stage === 1 ? 'flow-return' : 'flow-check' %>" data-result="5" data-id="<%= inspection.flowCurrentAudit.id %>"><%= inspection.flow_stage === 1 ? '审批退回' : '退回重新整改' %></button>
+                        <button type="button" class="btn btn-danger btn-sm flow-action" data-action="flow-check" data-result="4" data-id="<%= inspection.flowCurrentAudit.id %>">关闭</button>
                     <% } else if (inspection.flowCurrentAudit && inspection.rectificationPower) { %>
                         <button type="button" class="btn btn-outline-primary btn-sm flow-save-rectification" data-id="<%= inspection.flowCurrentAudit.id %>">保存整改</button>
                         <button type="button" class="btn btn-success btn-sm flow-action" data-action="flow-complete-rectification" data-id="<%= inspection.flowCurrentAudit.id %>">提交整改完成</button>
@@ -82,7 +81,7 @@
                         <% if (inspection.flow_version === 1) { %>
                         <tr>
                             <th class="text-center" width="150">是否整改</th>
-                            <td>
+                            <td class="quality-inspection-need-options">
                                 <% [1, 0].forEach(value => { %>
                                     <label class="mr-3 mb-0"><input type="radio" name="quality-need-rectification" value="<%= value %>" <%= inspection.need_rectification === value ? 'checked' : '' %> <%= inspection.readOnly ? 'disabled' : '' %>> <%= value ? '是' : '否' %></label>
                                 <% }); %>
@@ -519,6 +518,8 @@
     const fileList = JSON.parse(unescape('<%- escape(JSON.stringify(fileList)) %>')) || [];
     const whiteList = JSON.parse('<%- JSON.stringify(whiteList) %>');
     const deleteFilePermission = <%- deleteFilePermission %>;
+    // 在页面解析完成前恢复草稿,避免先展示数据库默认选项及其流程。
+    if (inspection.flow_version === 1) showQualityInspectionNeed(qualityInspectionNeedDraft());
 </script>
 <% if(!inspection.readOnly) { %>
 <script>

+ 20 - 1
app/view/quality/inspection_information_modal.ejs

@@ -257,10 +257,29 @@
             <div class="modal-header"><h5 class="modal-title" id="quality-flow-title">确认操作</h5></div>
             <div class="modal-body">
                 <p id="quality-flow-message"></p>
+                <% if (inspection.shenpiPower && inspection.flow_stage === 1 && inspection.flowCurrentAudit) { %>
+                <div class="alert alert-warning d-none" id="quality-flow-return-group">
+                    <div class="form-check form-check-inline">
+                        <input class="form-check-input" type="radio" name="quality-flow-return-type" id="quality-flow-return-origin" value="5">
+                        <label class="form-check-label" for="quality-flow-return-origin">退回原报</label>
+                    </div>
+                    <% if (inspection.flowCanReturnPrevious) {
+                        const report = inspection.flowStages.find(phase => phase.stage === 1).auditors;
+                        const earlier = report.filter(audit => audit.audit_order < inspection.flowCurrentAudit.audit_order);
+                        const previousOrder = Math.max(...earlier.map(audit => audit.audit_order));
+                        const previousNames = report.filter(audit => audit.audit_order === previousOrder).map(audit => audit.name).join('、');
+                    %>
+                    <div class="form-check form-check-inline">
+                        <input class="form-check-input" type="radio" name="quality-flow-return-type" id="quality-flow-return-previous" value="6">
+                        <label class="form-check-label" for="quality-flow-return-previous">退回上一审批人:<%= previousNames %></label>
+                    </div>
+                    <% } %>
+                </div>
+                <% } %>
                 <div id="quality-flow-opinion-group"><label for="quality-flow-opinion">处理意见</label><textarea id="quality-flow-opinion" class="form-control" rows="3" maxlength="1000"></textarea></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-secondary btn-sm" id="quality-flow-cancel" data-dismiss="modal">取消</button>
                 <button type="button" class="btn btn-primary btn-sm" id="quality-flow-submit">确认</button>
             </div>
         </div>

+ 1 - 1
app/view/quality/inspection_modal.ejs

@@ -24,7 +24,7 @@
     </div>
 </div>
 <!--附件-->
-<div class="modal fade" id="file" data-backdrop="static">
+<div class="modal fade quality-inspection-attachments" id="file" data-backdrop="static">
     <div class="modal-dialog" role="document">
         <div class="modal-content">
             <div class="modal-header">

+ 1 - 1
app/view/quality/sub_memu_list.ejs

@@ -5,7 +5,7 @@
 <!--<nav-menu title="试验报告" url="/sp/<%- ctx.subProject.id %>/quality/tender/<%= ctx.tender.id %>/lab%>" ml="3" active="<%= ctx.url.indexOf('/lab') %>"></nav-menu>-->
 <% } else { %>
 <nav-menu title="巡检概况" url="/sp/<%- ctx.subProject.id %>/quality/tender/<%= ctx.tender.id %>/inspection/overview" ml="1" active="<%= ctx.url.indexOf('/inspection/overview') >= 0 ? 1 : -1 %>" icon="fa-pie-chart"></nav-menu>
-<nav-menu title="质量巡检" url="/sp/<%- ctx.subProject.id %>/quality/tender/<%= ctx.tender.id %>/inspection" ml="3" active="<%= ctx.url.includes('/inspection') && !ctx.url.includes('/overview') && !ctx.url.includes('/rectification') && !ctx.url.includes('/approval') && !ctx.url.includes('/information') ? 1 : -1 %>" ></nav-menu>
+<nav-menu title="质量巡检" url="/sp/<%- ctx.subProject.id %>/quality/tender/<%= ctx.tender.id %>/inspection" ml="3" active="<%= ctx.url.includes('/inspection') && !ctx.url.includes('/overview') && !ctx.url.includes('/rectification') && !ctx.url.includes('/approval') ? 1 : -1 %>" ></nav-menu>
 <nav-menu title="巡检整改" url="/sp/<%- ctx.subProject.id %>/quality/tender/<%= ctx.tender.id %>/inspection/rectification" ml="3" active="<%= ctx.url.indexOf('/inspection/rectification') >= 0 ? 1 : -1 %>" ></nav-menu>
 <nav-menu title="巡检批复" url="/sp/<%- ctx.subProject.id %>/quality/tender/<%= ctx.tender.id %>/inspection/approval" ml="3" active="<%= ctx.url.indexOf('/inspection/approval') >= 0 ? 1 : -1 %>" ></nav-menu>
 <% } %>

+ 19 - 33
app/view/safe/inspection_flow.ejs

@@ -21,8 +21,7 @@
     <% } else { %>
         <div class="flow-title text-center mb-1">审批流程</div>
     <% } %>
-    <% if (!showTimeline) { %>
-    <div class="row flow-layout">
+    <div class="row flow-layout<%= showTimeline ? ' flow-progress-layout' : '' %>">
         <div class="col-md-4 flow-summary mb-3">
             <table class="table table-bordered table-sm flow-summary-table mb-0" aria-label="审批流程人员">
                 <colgroup><col class="flow-phase-label"><col></colgroup>
@@ -49,7 +48,6 @@
             </table>
         </div>
         <div class="col-md-8 flow-details">
-    <% } %>
             <% inspection.flowStages.forEach(phase => { %>
                 <% if (phase.stage === 1 || inspection.need_rectification || !inspection.readOnly) { %>
                     <% const reportMode = ctx.tender.info.shenpi.safe_inspection;
@@ -58,27 +56,6 @@
                     <% if (showTimeline) { %>
                     <section class="flow-phase-section" data-flow-stage="<%= phase.stage %>">
                         <div class="flow-phase-title text-center text-muted">巡检<%= phase.name %></div>
-                        <div class="row flow-layout">
-                            <div class="col-md-4 flow-summary mb-3">
-                                <div class="card">
-                                    <ul class="list-group list-group-flush">
-                                        <% phase.nodes.forEach(node => { %>
-                                            <li class="list-group-item d-flex align-items-center flow-phase-person">
-                                                <i class="fa <%= node.origin ? 'fa-play-circle fa-rotate-90' : node.label === '终审' ? 'fa-stop-circle' : 'fa-chevron-circle-down' %> mr-2" aria-hidden="true"></i>
-                                                <div class="flow-person-names">
-                                                    <% node.auditors.forEach(audit => { %><div><%= audit.name %></div><% }); %>
-                                                </div>
-                                                <div class="ml-auto pl-2 text-right">
-                                                    <span class="badge badge-light badge-pill"><%= node.label %></span>
-                                                    <% if (phase.stage === 2) { %><small class="d-block text-muted"><%= flowStatusText[node.status] %></small><% } %>
-                                                </div>
-                                            </li>
-                                        <% }); %>
-                                        <% if (!phase.nodes.length) { %><li class="list-group-item text-muted"><%= phase.stage === 2 ? '整改人由上报审批人指派' : '尚未设置审批人' %></li><% } %>
-                                    </ul>
-                                </div>
-                            </div>
-                            <div class="col-md-8 flow-details">
                     <% } else { %>
                     <div class="mb-3<%= phase.stage !== 1 && !inspection.need_rectification ? ' d-none' : '' %>" data-flow-stage="<%= phase.stage %>">
                     <% } %>
@@ -109,7 +86,12 @@
                                 </div>
                             </div>
                         <% } %>
-                        <% if (showTimeline && phase.stage !== 2) { %>
+                        <% if (showTimeline) { %>
+                            <% if (phase.stage === 2 && phase.auditors.length) { %>
+                                <div class="flow-stage-progress d-flex justify-content-between flex-wrap text-muted">
+                                    <span>同时整改</span><span>已完成 <%= inspection.rectificationDone %>/<%= inspection.rectificationTotal %></span>
+                                </div>
+                            <% } %>
                             <ul class="timeline-list list-unstyled flow-timeline">
                                 <% phase.nodes.forEach((node, index) => { %>
                                     <% const tone = node.status === 3 ? 'success' : [2, 5, 6, 12].includes(node.status) ? 'warning' : node.status === 4 ? 'danger' : 'secondary';
@@ -123,7 +105,7 @@
                                         <div class="timeline-item-content">
                                             <div class="d-flex justify-content-between py-1">
                                                 <span class="text-black-50"><%= node.label %></span>
-                                                <span class="text-<%= tone %>"><%= node.origin ? '上报审批' : node.status === 3 ? '审批通过' : flowStatusText[node.status] %></span>
+                                                <span class="text-<%= tone %>"><%= node.origin ? '上报审批' : phase.stage !== 2 && node.status === 3 ? '审批通过' : flowStatusText[node.status] %></span>
                                             </div>
                                             <div class="card">
                                                 <div class="card-body px-3 py-2">
@@ -132,9 +114,17 @@
                                                             <div class="d-flex align-items-start">
                                                                 <div class="flow-person-names"><span class="h6 mr-1"><%= audit.name %></span><small class="text-muted"><%= audit.company || '' %><%= audit.company && audit.role ? '-' : '' %><%= audit.role || '' %></small></div>
                                                                 <% const personTone = node.origin || audit.status === 3 ? 'success' : [2, 5, 6, 12].includes(audit.status) ? 'warning' : audit.status === 4 ? 'danger' : 'secondary'; %>
-                                                                <span class="ml-auto pl-2 text-<%= personTone %>"><% if (!node.origin && node.auditors.length > 1) { %><small class="mr-1"><%= flowStatusText[audit.status] %></small><% } %><i class="fa <%= node.origin || audit.status === 3 ? 'fa-check-circle' : [2, 12].includes(audit.status) ? 'fa-commenting' : [5, 6].includes(audit.status) ? 'fa-reply' : audit.status === 4 ? 'fa-stop-circle' : 'fa-circle-o' %>" aria-hidden="true"></i></span>
+                                                                <% if (phase.stage === 2 && editable) { %>
+                                                                    <button class="btn btn-link btn-sm text-danger flow-remove-user p-0 ml-auto" data-stage="<%= phase.stage %>" data-id="<%= audit.aid %>" type="button">移除</button>
+                                                                <% } else { %>
+                                                                    <span class="ml-auto pl-2 text-<%= personTone %>"><% if (!node.origin && node.auditors.length > 1) { %><small class="mr-1"><%= flowStatusText[audit.status] %></small><% } %><i class="fa <%= node.origin || audit.status === 3 ? 'fa-check-circle' : [2, 12].includes(audit.status) ? 'fa-commenting' : [5, 6].includes(audit.status) ? 'fa-reply' : audit.status === 4 ? 'fa-stop-circle' : 'fa-circle-o' %>" aria-hidden="true"></i></span>
+                                                                <% } %>
                                                             </div>
-                                                            <div class="bg-light flow-opinion mt-1" aria-label="处理意见"><i class="fa fa-commenting-o mr-1" aria-hidden="true"></i><%= audit.opinion || '' %></div>
+                                                            <% if (phase.stage === 2) { %>
+                                                                <% if (audit.rectification_item) { %><div class="flow-rectification-result mt-2"><%= audit.rectification_item %></div><% } %>
+                                                                <% if (audit.rectification_date) { %><small class="d-block text-muted mt-1">整改日期:<%= moment(audit.rectification_date).format('YYYY-MM-DD') %></small><% } %>
+                                                            <% } %>
+                                                            <% if (phase.stage !== 2 || audit.opinion) { %><div class="bg-light flow-opinion mt-1" aria-label="<%= phase.stage === 2 ? '说明' : '处理意见' %>"><i class="fa fa-commenting-o mr-1" aria-hidden="true"></i><%= audit.opinion || '' %></div><% } %>
                                                         </div>
                                                     <% }); %>
                                                 </div>
@@ -142,7 +132,7 @@
                                         </div>
                                     </li>
                                 <% }); %>
-                                <% if (!phase.nodes.length) { %><li class="text-muted py-2">尚未设置审批人</li><% } %>
+                                <% if (!phase.nodes.length) { %><li class="flow-timeline-empty text-muted py-2"><%= phase.stage === 2 ? '整改人由上报审批人指派,可设置多人同时处理。' : '尚未设置审批人' %></li><% } %>
                             </ul>
                         <% } else { %>
                         <div class="card">
@@ -196,8 +186,6 @@
                         </div>
                         <% } %>
                     <% if (showTimeline) { %>
-                            </div>
-                        </div>
                     </section>
                     <% } else { %>
                     </div>
@@ -205,10 +193,8 @@
                 <% } %>
             <% }); %>
             <div class="alert alert-warning flow-no-rectification<%= inspection.need_rectification ? ' d-none' : '' %>">本次巡检无整改/批复流程</div>
-    <% if (!showTimeline) { %>
         </div>
     </div>
-    <% } %>
     <% if (inspection.flowHistory.length) { %>
         <details class="card mb-3 d-none">
             <summary class="card-header">查看处理记录</summary>

+ 6 - 5
app/view/safe/inspection_information.ejs

@@ -1,5 +1,5 @@
 <% include ./sub_menu.ejs %>
-<div class="panel-content">
+<div class="panel-content<%= inspection.flow_version === 1 && !inspection.readOnly ? ' safe-inspection-need-pending' : '' %>">
     <div class="panel-title">
         <div class="title-main d-flex justify-content-between">
             <% include ./sub_mini_menu.ejs %>
@@ -20,9 +20,8 @@
                         <button type="button" class="btn btn-success btn-sm flow-action" data-action="start-inspection">提交上报</button>
                     <% } else if (inspection.flowCurrentAudit && inspection.shenpiPower) { %>
                         <button type="button" class="btn btn-success btn-sm flow-action" data-action="flow-check" data-result="3" data-id="<%= inspection.flowCurrentAudit.id %>"><%= inspection.flow_stage === 1 ? '审批' : '批复' %>通过</button>
-                        <button type="button" class="btn btn-warning btn-sm flow-action" data-action="flow-check" data-result="5" data-id="<%= inspection.flowCurrentAudit.id %>"><%= inspection.flow_stage === 1 ? '退回上报人' : '退回重新整改' %></button>
-                        <% if (inspection.flowCanReturnPrevious) { %><button type="button" class="btn btn-outline-warning btn-sm flow-action" data-action="flow-check" data-result="6" data-id="<%= inspection.flowCurrentAudit.id %>">退回上一审批节点</button><% } %>
-                        <button type="button" class="btn btn-outline-danger btn-sm flow-action" data-action="flow-check" data-result="4" data-id="<%= inspection.flowCurrentAudit.id %>">关闭巡检</button>
+                        <button type="button" class="btn btn-warning btn-sm flow-action" data-action="<%= inspection.flow_stage === 1 ? 'flow-return' : 'flow-check' %>" data-result="5" data-id="<%= inspection.flowCurrentAudit.id %>"><%= inspection.flow_stage === 1 ? '审批退回' : '退回重新整改' %></button>
+                        <button type="button" class="btn btn-danger btn-sm flow-action" data-action="flow-check" data-result="4" data-id="<%= inspection.flowCurrentAudit.id %>">关闭</button>
                     <% } else if (inspection.flowCurrentAudit && inspection.rectificationPower) { %>
                         <button type="button" class="btn btn-outline-primary btn-sm flow-save-rectification" data-id="<%= inspection.flowCurrentAudit.id %>">保存整改</button>
                         <button type="button" class="btn btn-success btn-sm flow-action" data-action="flow-complete-rectification" data-id="<%= inspection.flowCurrentAudit.id %>">提交整改完成</button>
@@ -82,7 +81,7 @@
                         <% if (inspection.flow_version === 1) { %>
                         <tr>
                             <th class="text-center" width="150">是否整改</th>
-                            <td>
+                            <td class="safe-inspection-need-options">
                                 <% [1, 0].forEach(value => { %>
                                     <label class="mr-3 mb-0"><input type="radio" name="safe-need-rectification" value="<%= value %>" <%= inspection.need_rectification === value ? 'checked' : '' %> <%= inspection.readOnly ? 'disabled' : '' %>> <%= value ? '是' : '否' %></label>
                                 <% }); %>
@@ -519,6 +518,8 @@
     const fileList = JSON.parse(unescape('<%- escape(JSON.stringify(fileList)) %>')) || [];
     const whiteList = JSON.parse('<%- JSON.stringify(whiteList) %>');
     const deleteFilePermission = <%- deleteFilePermission %>;
+    // 在页面解析完成前恢复草稿,避免先展示数据库默认选项及其流程。
+    if (inspection.flow_version === 1) showSafeInspectionNeed(safeInspectionNeedDraft());
 </script>
 <% if(!inspection.readOnly) { %>
 <script>

+ 20 - 1
app/view/safe/inspection_information_modal.ejs

@@ -255,10 +255,29 @@
             <div class="modal-header"><h5 class="modal-title" id="safe-flow-title">确认操作</h5></div>
             <div class="modal-body">
                 <p id="safe-flow-message"></p>
+                <% if (inspection.shenpiPower && inspection.flow_stage === 1 && inspection.flowCurrentAudit) { %>
+                <div class="alert alert-warning d-none" id="safe-flow-return-group">
+                    <div class="form-check form-check-inline">
+                        <input class="form-check-input" type="radio" name="safe-flow-return-type" id="safe-flow-return-origin" value="5">
+                        <label class="form-check-label" for="safe-flow-return-origin">退回原报</label>
+                    </div>
+                    <% if (inspection.flowCanReturnPrevious) {
+                        const report = inspection.flowStages.find(phase => phase.stage === 1).auditors;
+                        const earlier = report.filter(audit => audit.audit_order < inspection.flowCurrentAudit.audit_order);
+                        const previousOrder = Math.max(...earlier.map(audit => audit.audit_order));
+                        const previousNames = report.filter(audit => audit.audit_order === previousOrder).map(audit => audit.name).join('、');
+                    %>
+                    <div class="form-check form-check-inline">
+                        <input class="form-check-input" type="radio" name="safe-flow-return-type" id="safe-flow-return-previous" value="6">
+                        <label class="form-check-label" for="safe-flow-return-previous">退回上一步骤:<%= previousNames %></label>
+                    </div>
+                    <% } %>
+                </div>
+                <% } %>
                 <div id="safe-flow-opinion-group"><label for="safe-flow-opinion">处理意见</label><textarea id="safe-flow-opinion" class="form-control" rows="3" maxlength="1000"></textarea></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-secondary btn-sm" id="safe-flow-cancel" data-dismiss="modal">取消</button>
                 <button type="button" class="btn btn-primary btn-sm" id="safe-flow-submit">确认</button>
             </div>
         </div>

+ 1 - 1
app/view/safe/sub_memu_list.ejs

@@ -2,7 +2,7 @@
 <% if (!ctx.url.includes('/inspection')) { %>
 <% } else { %>
 <nav-menu title="巡检概况" url="/sp/<%- ctx.subProject.id %>/safe/tender/<%= ctx.tender.id %>/inspection/overview" ml="1" active="<%= ctx.url.indexOf('/inspection/overview') >= 0 ? 1 : -1 %>" icon="fa-pie-chart"></nav-menu>
-<nav-menu title="安全巡检" url="/sp/<%- ctx.subProject.id %>/safe/tender/<%= ctx.tender.id %>/inspection" ml="3" active="<%= ctx.url.includes('/inspection') && !ctx.url.includes('/overview') && !ctx.url.includes('/rectification') && !ctx.url.includes('/approval') && !ctx.url.includes('/information') ? 1 : -1 %>" ></nav-menu>
+<nav-menu title="安全巡检" url="/sp/<%- ctx.subProject.id %>/safe/tender/<%= ctx.tender.id %>/inspection" ml="3" active="<%= ctx.url.includes('/inspection') && !ctx.url.includes('/overview') && !ctx.url.includes('/rectification') && !ctx.url.includes('/approval') ? 1 : -1 %>" ></nav-menu>
 <nav-menu title="巡检整改" url="/sp/<%- ctx.subProject.id %>/safe/tender/<%= ctx.tender.id %>/inspection/rectification" ml="3" active="<%= ctx.url.indexOf('/inspection/rectification') >= 0 ? 1 : -1 %>" ></nav-menu>
 <nav-menu title="巡检批复" url="/sp/<%- ctx.subProject.id %>/safe/tender/<%= ctx.tender.id %>/inspection/approval" ml="3" active="<%= ctx.url.indexOf('/inspection/approval') >= 0 ? 1 : -1 %>" ></nav-menu>
 <% } %>

+ 7 - 0
sql/update.sql

@@ -122,6 +122,13 @@ ALTER TABLE `zh_ledger_attachment`
 ALTER TABLE `zh_advance_audit`
 ADD COLUMN `audit_type` tinyint(4) UNSIGNED NOT NULL DEFAULT 1 COMMENT '审批类型(1个人,2会签,3或签)' AFTER `audit_id`,
 ADD COLUMN `audit_order` tinyint(4) UNSIGNED NOT NULL DEFAULT 0 COMMENT '审批顺序' AFTER `audit_type`;
+
+ALTER TABLE `calculation`.`zh_budget_final`
+MODIFY COLUMN `tz_qc_qty` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '决算-变更令数量' AFTER `grow_dgn_qty`,
+MODIFY COLUMN `tz_qc_tp` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '决算-变更令金额' AFTER `tz_qc_qty`,
+ADD COLUMN `un_tz_qc_qty` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '未批复变更令数量' AFTER `tz_qc_tp`,
+ADD COLUMN `un_tz_qc_tp` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '未批复变更令金额' AFTER `un_tz_qc_qty`;
+
 ------------------------------------
 -- 表数据
 ------------------------------------