Browse Source

台账分解,数据检查

MaiXinRong 5 years ago
parent
commit
b48d35e7cc

+ 28 - 21
app/public/js/ledger.js

@@ -77,6 +77,21 @@ $(document).ready(function() {
             if (posSpread) posSpread.refresh();
         },
     });
+    const checkList = $.ledger_checkList({
+        id: 'check-list',
+        tabSelector: '#check-list-tab',
+        selector: '#check-list',
+        relaSpread: ledgerSpread,
+        storeKey: 'ledger-check-' + getTenderId(),
+        checkType: ledgerCheckType,
+        afterLocated:  function () {
+            posOperationObj.loadCurPosData();
+        },
+        afterShow: function () {
+            ledgerSpread.refresh();
+            if (posSpread) posSpread.refresh();
+        },
+    });
 
     $.subMenu({
         menu: '#sub-menu', miniMenu: '#sub-mini-menu', miniMenuList: '#mini-menu-list',
@@ -111,6 +126,9 @@ $(document).ready(function() {
             if (errorList) {
                 errorList.spread.refresh();
             }
+            if (checkList) {
+                checkList.spread.refresh();
+            }
         }
     });
 
@@ -1442,27 +1460,6 @@ $(document).ready(function() {
         };
         billsContextMenuOptions.items.sprImport = '-----------';
     }
-    billsContextMenuOptions.items.checkLeafXmj = {
-        name: '检查项目节、工程量清单同层',
-        icon: 'fa-search-plus',
-        callback: function (key, opt) {
-            const error = [];
-            for (const node of ledgerTree.nodes) {
-                if (!node.children || node.children.length === 0) continue;
-                let hasXmj, hasGcl;
-                for (const child of node.children) {
-                    if (child.b_code) hasXmj = true;
-                    if (!child.b_code) hasGcl = true;
-                }
-                if (hasXmj && hasGcl) error.push(node);
-            }
-            if (error.length === 0) toastr.success('不存在项目节、工程量清单同层');
-            if (error.length > 0) {
-                toastr.warning('第' + (ledgerTree.nodes.indexOf(error[0]) + 1) + '行节点,子项中存在项目节与工程量清单同层,请检查。');
-                SpreadJsObj.locateTreeNode(ledgerSpread.getActiveSheet(), error[0].ledger_id);
-            }
-        },
-    };
     $.contextMenu(billsContextMenuOptions);
 
     const posSearch = $.posSearch({selector: '#pos-search', searchSpread: posSpread});
@@ -2026,6 +2023,8 @@ $(document).ready(function() {
 
         treeOperationObj.refreshOperationValid(ledgerSpread.getActiveSheet());
         treeOperationObj.loadExprToInput(ledgerSpread.getActiveSheet());
+
+        checkList.loadHisCheckData();
     }, null, true);
 
     $.divResizer({
@@ -2235,6 +2234,8 @@ $(document).ready(function() {
                 searchLedger.spread.refresh();
             } else if (tab.attr('content') === '#error-list') {
                 errorList.spread.refresh();
+            } else if (tab.attr('content') === '#check-list') {
+                checkList.spread.refresh();
             }
         } else { // 收起工具栏
             tab.removeClass('active');
@@ -2874,6 +2875,12 @@ $(document).ready(function() {
         }
         return false;
     });
+
+    LedgerChecker({
+        ledgerTree: ledgerTree,
+        ledgerPos: pos,
+        checkList: checkList,
+    });
 });
 
 // 检查上报情况

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

@@ -174,6 +174,172 @@ const showSideTools = function (show) {
         }
     };
 
+    $.ledger_checkList = function (setting) {
+        const checkTypeText = [];
+        for (const ct in setting.checkType) {
+            checkTypeText[setting.checkType[ct].value] = setting.checkType[ct].text;
+        }
+        if (!setting.spreadSetting) {
+            setting.spreadSetting = {
+                cols: [
+                    {
+                        title: '类型', field: 'type', width: 150, formatter: '@',
+                        getValue: function (data){
+                            if (setting.checkType) {
+                                return checkTypeText[data.type] || '';
+                            } else {
+                                return '';
+                            }
+                        }
+                    },
+                    {title: '行号', field: 'serialNo', hAlign: 1, width: 40, formatter: '@'},
+                    {title: '项目节编号', field: 'code', width: 80, formatter: '@'},
+                    {title: '清单编号', field: 'b_code', width: 80, formatter: '@'},
+                    {title: '清单名称', field: 'name', width: 150, formatter: '@'},
+                ],
+                emptyRows: 0,
+                headRows: 1,
+                headRowHeight: [32],
+                defaultRowHeight: 21,
+                headerFont: '12px 微软雅黑',
+                font: '12px 微软雅黑',
+                selectedBackColor: '#fffacd',
+                readOnly: true,
+            };
+        }
+
+        const clearCheckData = function () {
+            if (setting.storeKey) removeLocalCache(setting.storeKey);
+        };
+
+        const autoShowHistory = function (show) {
+            if (setting.storeKey) {
+                setLocalCache(setting.storeKey + '-showHis', show.toString());
+            }
+        };
+
+        if (setting.selector && setting.relaSpread) {
+            const resultId = setting.id + '-spread';
+            const obj = $(setting.selector);
+            const dropdown = [];
+            if (setting.checkType) {
+                dropdown.push('<div class="dropdown">');
+                dropdown.push('<button class="btn btn-sm btn-outline-primary dropdown-toggle" type="button" id="'+ setting.id + 'drop" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">所有类型</button>');
+                dropdown.push('<div class="dropdown-menu" aria-labelledby="'+ setting.id + 'drop">');
+                dropdown.push('<a class="dropdown-item" href="javascript: void(0);" check-type="all">所有类型</a>');
+                for (const ct in setting.checkType) {
+                    dropdown.push('<a class="dropdown-item" href="javascript: void(0);" check-type="' + setting.checkType[ct].value +'">' + setting.checkType[ct].text + '</a>');
+                }
+                dropdown.push('</div>');
+                dropdown.push('</div>');
+            }
+            obj.html(
+                '<div class="sjs-bar">\n' +
+                '    <div class="pb-1 d-flex">\n' + dropdown.join('') +
+                '        <span class="ml-auto pr-2" id="' + setting.id + '-time">检查时间:2020-08-01 13:20:25</span>\n' +
+                '    </div>\n' +
+                '</div>' +
+                '<div id="' + resultId + '" class="sjs-sh">\n' +
+                '</div>'
+            );
+            autoFlashHeight();
+
+            const spread = SpreadJsObj.createNewSpread($('#' + resultId)[0]);
+            const sheet = spread.getActiveSheet();
+            SpreadJsObj.initSheet(sheet, setting.spreadSetting);
+            SpreadJsObj.forbiddenSpreadContextMenu('#' + resultId, spread);
+
+            spread.getActiveSheet().bind(spreadNS.Events.CellDoubleClick, function (e, info) {
+                const sheet = info.sheet;
+                const data = sheet.zh_data;
+                if (!data) { return }
+
+                const curBills = data[info.row];
+                if (!curBills) { return }
+
+                SpreadJsObj.locateTreeNode(setting.relaSpread.getActiveSheet(), curBills.ledger_id, true);
+                if (setting.afterLocated) {
+                    setting.afterLocated();
+                }
+            });
+
+            const hideCheckData = function () {
+                const tab = $(setting.tabSelector), tabPanel = $(tab.attr('content'));
+                if (tab.hasClass('active')) {
+                    $('a', '#side-menu').removeClass('active');
+                    tab.addClass('active');
+                    $('.tab-content .tab-pane').removeClass('active');
+                    tabPanel.addClass('active');
+                    showSideTools(false);
+                    if (spread) spread.refresh();
+                    if (setting.afterShow) setting.afterShow();
+                    tab.hide();
+                }
+            };
+
+            const loadCheckData = function (data, his = false) {
+                const sourceTree = setting.relaSpread.getActiveSheet().zh_tree;
+                if (!sourceTree) return;
+
+                for (const d of data.warning_data) {
+                    d.serialNo = sourceTree.getNodeIndex(sourceTree.getItems(d.ledger_id)) + 1;
+                }
+
+                $('#' + setting.id + '-time').html('检查时间:' + data.check_time.toLocaleString());
+                SpreadJsObj.loadSheetData(sheet, SpreadJsObj.DataType.Data, data.warning_data);
+                if (!his && setting.storeKey) {
+                    setLocalCache(setting.storeKey, JSON.stringify(data));
+                }
+                $(setting.tabSelector).show();
+            };
+            const showCheckList = function () {
+                const tab = $(setting.tabSelector), tabPanel = $(tab.attr('content'));
+                $('a', '#side-menu').removeClass('active');
+                tab.addClass('active');
+                $('.tab-content .tab-pane').removeClass('active');
+                tabPanel.addClass('active');
+                showSideTools(true);
+                spread.refresh();
+                if (setting.afterShow) setting.afterShow();
+            };
+            const loadHisCheckData = function () {
+                if (setting.storeKey) {
+                    const storeStr = getLocalCache(setting.storeKey);
+
+                    const storeData = storeStr ? JSON.parse(storeStr) : null;
+                    if (storeData) {
+                        loadCheckData(storeData, true);
+                        const showHis = getLocalCache(setting.storeKey + '-showHis');
+                        if (showHis === 'true') {
+                            showCheckList();
+                            removeLocalCache(setting.storeKey + '-showHis');
+                        }
+                    }
+                }
+            };
+            return {
+                spread: spread,
+                loadCheckData: loadCheckData,
+                clearCheckData: clearCheckData,
+                loadHisCheckData: loadHisCheckData,
+                show: showCheckList,
+                hide: hideCheckData,
+                autoShowHistory: autoShowHistory,
+            };
+        } else {
+            const loadCheckData = function (data) {
+                if (setting.storeKey) {
+                    setLocalCache(setting.storeKey, JSON.stringify(data));
+                }
+            };
+            return {
+                loadCheckData: loadCheckData,
+                clearCheckData: clearCheckData,
+                autoShowHistory: autoShowHistory,
+            };
+        }
+    };
+
     $.posSearch = function (setting) {
         if (!setting.selector || !setting.searchSpread) return;
         const searchHtml =

+ 6 - 1
app/view/ledger/explode.ejs

@@ -40,8 +40,8 @@
                     </div>
                 </div>
                 <div class="d-inline-block ml-3">
-                    <!--<a id="exportLedger" class="btn btn-primary btn-sm" href="/tender/<%- ctx.tender.id %>/ledger/download/台账分解.xlsx">导出台账Excel</a>-->
                     <a id="exportLedger" class="btn btn-primary btn-sm" href="javascript: void(0)">导出台账Excel</a>
+                    <a class="btn btn-sm btn-primary" href="#ledger-check-modal" data-toggle="modal" data-target="#ledger-check-modal">数据检查</a>
                 </div>
             </div>
             <div class="ml-auto">
@@ -146,6 +146,8 @@
                     </div>
                     <div id="error-list" class="tab-pane">
                     </div>
+                    <div id="check-list" class="tab-pane">
+                    </div>
                 </div>
             </div>
         </div>
@@ -167,6 +169,9 @@
                 <li class="nav-item">
                     <a class="nav-link" content="#error-list" id="error-list-tab" href="javascript: void(0);" style="display: none;">错误列表</a>
                 </li>
+                <li class="nav-item">
+                    <a class="nav-link" content="#check-list" id="check-list-tab" href="javascript: void(0);" style="display: none;">数据检查</a>
+                </li>
             </ul>
         </div>
     </div>

+ 1 - 0
app/view/ledger/explode_modal.ejs

@@ -413,3 +413,4 @@
 <% include ../shares/import_excel_modal.ejs %>
 <% include ../shares/delete_hint_modal.ejs %>
 <% include ../shares/check_data_modal.ejs %>
+<% include ../shares/ledger_check_modal.ejs %>

+ 200 - 0
app/view/shares/ledger_check_modal.ejs

@@ -0,0 +1,200 @@
+
+<!--数据检查-->
+<div class="modal fade" id="ledger-check-modal" 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">
+                <p>数据检查,将检查罗列台帐中以下内容:</p>
+                <div class="card mb-2 p-2 border-success" id="check-sibling">
+                    <div class="d-flex justify-content-between">
+                        项目节、清单同层
+                        <span class="text-muted" name="check-status">待检查</span>
+                    </div>
+                </div>
+                <div class="card mb-2 p-2 border-success" id="check-empty-code">
+                    <div class="d-flex justify-content-between">
+                        项目节、清单编号同时为空
+                        <!-- <span class="text-muted">待检查</span> -->
+                        <!-- <span class="text-muted" title="检查中"><i class="fa fa-spinner fa-spin"></i></span> -->
+                        <span class="text-success" title="完成" name="check-status"><i class="fa fa-check"></i></span>
+                    </div>
+                </div>
+                <div class="card mb-2 p-2 border-success" id="check-calc">
+                    <div class="d-flex justify-content-between">
+                        清单数量不等于计量单元之和
+                        <!-- <span class="text-muted">待检查</span> -->
+                        <!-- <span class="text-muted" title="检查中"><i class="fa fa-spinner fa-spin"></i></span> -->
+                        <span class="text-success" title="完成" name="check-status"><i class="fa fa-check"></i></span>
+                    </div>
+                </div>
+                <div class="card mb-2 p-2 border-success" id="check-zero">
+                    <div class="d-flex justify-content-between">
+                        清单数量或单价为0
+                        <!-- <span class="text-muted">待检查</span> -->
+                        <!-- <span class="text-muted" title="检查中"><i class="fa fa-spinner fa-spin"></i></span> -->
+                        <span class="text-success" title="完成" name="check-status"><i class="fa fa-check"></i></span>
+                    </div>
+                </div>
+                 <a href="javascript: void(0);" class="btn btn-sm btn-block btn-primary" id="ledger-check-begin">开始检查</a>
+                 <a href="#" class="btn btn-sm btn-block btn-primary disabled" id="ledger-check-waiting">检查中,请等待...</a>
+                <p class="text-center text-success" id="ledger-check-hint">检查完成,现在您可以查看结果。</p>
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-sm btn-secondary" data-dismiss="modal">关闭</button>
+                <!--检查完有结果显示按钮-->
+                <button type="button" class="btn btn-sm btn-primary" id="ledger-check-show">查看结果</button>
+            </div>
+        </div>
+    </div>
+</div>
+<script>
+    const ledgerCheckType = {
+        sibling: {value: 1, text: '项目节、清单同层'},
+        empty_code: {value: 2, text: '项目节、清单编号同时为空'},
+        calc: {value: 3, text: '清单数量不等于计量单元之和'},
+        zero: {value: 4, text: '清单数量或单价为0'},
+    };
+    const LedgerChecker = function (setting) {
+        const ledger = setting.ledgerTree, ledgerPos = setting.ledgerPos;
+
+        const initWaitingModal = function () {
+            $('.card', '#ledger-check-modal').removeClass('border-success');
+            $('[name=check-status]', '#ledger-check-modal').removeClass('text-success').addClass('text-muted').html('待检查');
+            $('#ledger-check-begin').show();
+            $('#ledger-check-waiting').hide();
+            $('#ledger-check-hint').hide();
+            $('#ledger-check-show').hide();
+        }
+
+        const doSomeCheck = function (selector, checkFun) {
+            const checkStatus = $('[name=check-status]', selector);
+            checkStatus.html('<i class="fa fa-spinner fa-spin"></i>');
+            const result = checkFun(ledger);
+            checkStatus.removeClass('text-muted').addClass('text-success').html('<i class="fa fa-check"></i>');
+            $(selector).addClass('border-success');
+            return result;
+        }
+
+        const ledgerCheckUtil = {
+            checkSibling: function (ledgerTree) {
+                const error = [];
+                for (const node of ledgerTree.nodes) {
+                    if (!node.children || node.children.length === 0) continue;
+                    let hasXmj, hasGcl;
+                    for (const child of node.children) {
+                        if (child.b_code) hasXmj = true;
+                        if (!child.b_code) hasGcl = true;
+                    }
+                    if (hasXmj && hasGcl) error.push(node);
+                }
+                return error;
+            },
+            checkCodeEmpty: function (ledgerTree) {
+                const error = [];
+                const checkNodeCode = function (node) {
+                    if ((!node.code || node.code === '') && (!node.b_code || node.b_code === '')) error.push(node);
+                    if (node.children && node.children.length > 0) {
+                        for (const child of node.children) {
+                            checkNodeCode(child);
+                        }
+                    }
+                }
+                for (const topLevel of ledgerTree.children) {
+                    if (topLevel.node_type !== 1) continue;
+
+                    checkNodeCode(topLevel);
+                }
+                return error;
+            },
+            checkCalc: function (ledgerTree) {
+                const error = [];
+                for (const node of ledgerTree.nodes) {
+                    if (node.children && node.children.length > 0) continue;
+
+                    const nodePos = ledgerPos.getLedgerPos(node.id);
+                    if (!nodePos || nodePos.length === 0) continue;
+
+                    const checkData = {
+                        sgfh_qty: node.sgfh_qty, qtcl_qty: node.qtcl_qty, sjcl_qty: node.sjcl_qty,
+                        quantity: node.quantity
+                    };
+                    const calcData = {
+                        sgfh_qty: 0, qtcl_qty: 0, sjcl_qty: 0,
+                        quantity: 0
+                    };
+                    for (const np of nodePos) {
+                        calcData.sgfh_qty = ZhCalc.add(calcData.sgfh_qty, np.sgfh_qty);
+                        calcData.qtcl_qty = ZhCalc.add(calcData.qtcl_qty, np.qtcl_qty);
+                        calcData.sjcl_qty = ZhCalc.add(calcData.sjcl_qty, np.sjcl_qty);
+                        calcData.quantity = ZhCalc.add(calcData.quantity, np.quantity);
+                    }
+                    if (!_.isMatch(checkData, calcData)) error.push(node);
+                }
+                return error;
+            },
+            checkZero: function (ledgerTree) {
+                const error = [];
+                for (const node of ledgerTree.nodes) {
+                    if ((!node.b_code || node.b_code === '')) continue;
+                    if (node.children && node.children.length > 0) continue;
+
+                    if (checkZero(node.sgfh_qty) && checkZero(node.qtcl_qty) && checkZero(node.sjcl_qty)
+                        && checkZero(node.deal_qty) && checkZero(node.quantity)) error.push(node);
+                }
+                return error;
+            },
+        };
+
+        const assignWarningData = function (nodes, checkType, warningData) {
+            for (const node of nodes) {
+                warningData.push({
+                    type: checkType,
+                    ledger_id: node.ledger_id,
+                    code: node.code,
+                    b_code: node.b_code,
+                    name: node.name,
+                })
+            }
+        }
+
+
+        const checkData = function () {
+            $('#ledger-check-begin').hide();
+            $('#ledger-check-waiting').show();
+            const checkData = {
+                check_time: new Date(),
+                warning_data: [],
+            }
+            const sibling = doSomeCheck('#check-sibling', ledgerCheckUtil.checkSibling) || [];
+            assignWarningData(sibling, ledgerCheckType.sibling.value, checkData.warning_data);
+            const empty_code = doSomeCheck('#check-empty-code', ledgerCheckUtil.checkCodeEmpty) || [];
+            assignWarningData(empty_code, ledgerCheckType.empty_code.value, checkData.warning_data);
+            const calc = doSomeCheck('#check-calc', ledgerCheckUtil.checkCalc) || [];
+            assignWarningData(calc, ledgerCheckType.calc.value, checkData.warning_data);
+            const zero = doSomeCheck('#check-zero', ledgerCheckUtil.checkZero) || [];
+            assignWarningData(zero, ledgerCheckType.zero.value, checkData.warning_data);
+
+            $('#ledger-check-waiting').hide();
+            setting.checkList.clearCheckData();
+            if (checkData.warning_data.length > 0) {
+                $('#ledger-check-hint').html('检查完成,现在您可以查看结果。').show();
+                $('#ledger-check-show').show();
+                setting.checkList.loadCheckData(checkData);
+            } else {
+                setting.checkList.hide();
+                $('#ledger-check-show').hide();
+                $('#ledger-check-hint').html('检查完成,没有任何问题。').show();
+            }
+        }
+
+        $('#ledger-check-begin').bind('click', checkData);
+        $('#ledger-check-modal').bind('show.bs.modal', initWaitingModal);
+        $('#ledger-check-show').bind('click', function () {
+            $('#ledger-check-modal').modal('hide');
+            setting.checkList.show();
+        });
+    }
+</script>