Browse Source

批量导入(其他标段)工程量清单数据1.0

MaiXinRong 2 years ago
parent
commit
f882610011

+ 11 - 1
app/controller/tender_controller.js

@@ -1206,7 +1206,8 @@ module.exports = app => {
         async listLoad(ctx) {
             try {
                 const data = JSON.parse(ctx.request.body.data);
-                if (!data.tid || !data.lid || !data.type) throw '数据错误';
+                if (!data.type || !data.tid) throw '数据错误';
+                if (data.type !== 'stageBatch' && !data.lid) throw '数据错误';
                 const responseData = {
                     err: 0,
                     msg: '',
@@ -1244,6 +1245,11 @@ module.exports = app => {
                             if (history) responseData.data.history = { tenders: history.tenders, load_time: history.load_time, type: 'ledger' };
                         }
                     }
+                } else if (data.type === 'stageBatch') {
+                    responseData.data.tenders = tenderList.filter(x => {
+                        return x.ledger_status === auditConst.ledger.status.checked && !!x.lastStage;
+                    });
+                    responseData.data.history = await this.ctx.service.sumLoadHistory.getBatchHistory(data.tid);
                 } else if (data.type === 'revise') {
                     responseData.data.tenders = tenderList.filter(x => {
                         return x.ledger_status === auditConst.ledger.status.checked;
@@ -1297,6 +1303,10 @@ module.exports = app => {
                     responseData.data.tenders = tenderList.filter(x => {
                         return x.ledger_status === auditConst.ledger.status.checked && !!x.lastCheckedStage;
                     });
+                } else if (data.type === 'stageBatch') {
+                    responseData.data.tenders = tenderList.filter(x => {
+                        return x.ledger_status === auditConst.ledger.status.checked && !!x.lastStage;
+                    });
                 }
                 ctx.body = responseData;
             } catch(err) {

+ 0 - 1
app/middleware/tender_check.js

@@ -117,7 +117,6 @@ module.exports = options => {
             tender.schedule_permission = schedule_permission;
             yield next;
         } catch (err) {
-            console.log(err);
             // 输出错误到日志
             if (err.stack) {
                 this.logger.error(err);

+ 209 - 0
app/public/js/shares/batch_import.js

@@ -0,0 +1,209 @@
+const BatchImportStageGcl = function (setting) {
+    const biObj = {
+        setting,
+        spread: null,
+        sheet: null,
+        tenderSourceTree: null,
+        history: [],
+        batchTree: null,
+        rebuildStageSelect: function () {
+            const getItems = function (data) {
+                const items = [];
+                if (data) {
+                    for (let i = 1; i <= data.stageCount; i++) {
+                        items.push({value: i, text: `第${i}期`});
+                    }
+                }
+                return items;
+            };
+            for (let i = 0; i < biObj.sheet.getRowCount(); i++) {
+                const data = biObj.batchTree.nodes[i];
+                if (!data.tid) continue;
+
+                const items = getItems(data);
+                const cellType = new spreadNS.CellTypes.ComboBox().itemHeight(10).editorValueType(spreadNS.CellTypes.EditorValueType.value).items(items);
+                biObj.sheet.getCell(i, 2).cellType(cellType);
+            }
+        },
+        trEditEnded: function (e, info) {
+            const data = SpreadJsObj.getSelectObject(info.sheet);
+            const col = info.sheet.zh_setting.cols[info.col];
+            data[col.field] = info.sheet.getValue(info.row, info.col);
+        },
+        reloadBatchTree() {
+            this.batchTree.clearDatas();
+            for (const h of this.history) {
+                if (!h.ledger_node) continue;
+
+                const ledgerData = { lid: h.lid, ledger_id: h.ledger_node.ledger_id, code: h.ledger_node.code, name: h.ledger_node.name, ledger_node: h.ledger_node };
+                const batchNode = this.batchTree.addNode(ledgerData, null);
+                for (const t of h.tenders) {
+                    const tenderData = JSON.parse(JSON.stringify(t));
+                    const tender = this.tenderSourceTree.nodes.find(y => { return y.tid === t.tid });
+                    tenderData.stageCount = tender.stageCount;
+                    this.batchTree.addNode(tenderData, batchNode);
+                }
+            }
+            this.batchTree.sortTreeNode(true);
+        },
+        loadHistory: function () {
+            if (biObj.batching) return;
+
+            biObj.tender_id = biObj.setting.stageTree.nodes[0].tender_id;
+            postData('/list/load', {type: 'stageBatch', tid: biObj.tender_id}, data => {
+                biObj.history = data.history || [];
+                // 屏蔽自己
+                const curIndex = data.tenders.findIndex(x => { return x.id === biObj.tender_id });
+                if (curIndex >= 0) data.tenders.splice(curIndex, 1);
+                biObj.tenderSourceTree = Tender2Tree.convert(data.category, data.tenders, data.ledgerAuditConst, data.stageAuditConst);
+                for (const h of biObj.history) {
+                    h.ledger_order = biObj.setting.stageTree.nodes.findIndex(x => { return x.id === h.lid; });
+                    h.ledger_node = h.ledger_order >= 0 ? biObj.setting.stageTree.nodes[h.ledger_order] : null;
+                    if (h.tenders) h.tenders = h.tenders.filter(x => { return biObj.tenderSourceTree.nodes.find(y => { return x.tid === y.tid; })});
+                }
+                biObj.history.sort((x, y) => { return x.ledger_order - y.ledger_order; });
+                biObj.reloadBatchTree();
+                SpreadJsObj.loadSheetData(biObj.sheet, SpreadJsObj.DataType.Tree, biObj.batchTree);
+                biObj.rebuildStageSelect();
+            });
+        },
+        initBatchImport: function () {
+            if (this.spread) return;
+
+            this.spread = SpreadJsObj.createNewSpread($('#bi-spread')[0]);
+            this.sheet = this.spread.getActiveSheet();
+            SpreadJsObj.initSheet(this.sheet, {
+                cols: [
+                    // {title: '选择', field: 'selected', hAlign: 1, width: 40, formatter: '@', cellType: 'checkbox'},
+                    {title: '编号', field: 'code', hAlign: 0, width: 180, formatter: '@', cellType: 'tree'},
+                    {title: '名称/引用标段', field: 'name', hAlign: 0, width: 180, formatter: '@'},
+                    {title: '可选期', field: 'stage', hAlign: 1, width: 60, formatter: '@'},
+                    // {title: '覆盖数据', field: 'is_cover', hAlign: 1, width: 60, cellType: 'checkbox'},
+                    {title: '状态', field: 'status', hAlign: 1, width: 60, formatter: '@'},
+                    {title: '错误信息', field: 'error', hAlign: 1, width: 60, formatter: '@'},
+                ],
+                emptyRows: 0,
+                headRows: 1,
+                headRowHeight: [32],
+                defaultRowHeight: 21,
+                headerFont: '12px 微软雅黑',
+                font: '12px 微软雅黑',
+                selectedBackColor: '#fffacd',
+            });
+            this.spread.bind(spreadNS.Events.EditEnded, biObj.trEditEnded);
+
+            this.batchTree = createNewPathTree('gather', {
+                id: 'id',
+                pid: 'pid',
+                order: 'order',
+                level: 'level',
+                rootId: -1,
+                fullPath: 'full_path',
+            });
+        },
+        checkErrors: function () {
+            const hasError = this.batchTree.children.findIndex(x => { return x.error > 0; }) >= 0;
+            if (hasError) {
+                $('#bi-download-error').show();
+            } else {
+                $('#bi-download-error').hide();
+            }
+        },
+        importStageGcl: async function (node, cover) {
+            const updateData = { lid: node.lid, type: 'stage', cover, tenders: [] };
+            for (const tender of node.children) {
+                updateData.tenders.push({ tid: tender.tid, name: tender.name, stageCount: tender.stageCount, stage: tender.stage });
+            }
+
+            const result = await postDataAsync(window.location.pathname + '/sumLoad', updateData);
+            biObj.setting.afterLoad(result, node.ledger_node);
+            node.errors = result.sumLoadHis.errors;
+            node.error = node.errors ? node.errors.length : 0;
+        },
+        batchImport: async function () {
+            $('#bi-start')[0].disabled = true;
+            biObj.batching = true;
+            const cover = $('#bi-cover')[0].checked;
+            for (const node of this.batchTree.children) {
+                if (!node.children || node.children.length === 0) continue;
+                const row = this.batchTree.getNodeIndex(node);
+                try {
+                    node.status = '开始导入';
+                    SpreadJsObj.reLoadRowData(biObj.sheet, row);
+                    await biObj.importStageGcl(node, cover);
+                    node.status = '导入完成';
+                    SpreadJsObj.reLoadRowData(biObj.sheet, row);
+                } catch(err) {
+                    console.log(err);
+                    node.status = '导入失败';
+                    SpreadJsObj.reLoadRowData(biObj.sheet, row);
+                }
+            }
+            biObj.batching = false;
+            $('#bi-start')[0].disabled = false;
+            biObj.checkErrors();
+        },
+        downloadErrors: function () {
+            const errorType = {
+                less: '数量变少',
+                miss: '找不到清单',
+                'qc-conflict': '变更冲突(已调用变更令)'
+            };
+            // const setting = {
+            //     header: ['清单编号', '清单名称','单位', '合同数量', '变更数量', '错误类型'],
+            //     width: [80, 200, 60, 80, 80, 180],
+            //     hAlign: ['left', 'left', 'center', 'right', 'right', 'left'],
+            // };
+            // const sheets = [];
+            // for (const node of this.batchTree.children) {
+            //     if (node.error > 0) {
+            //         sheets.push({ name: node.code, setting, data: node.errors.map(x => {
+            //             return [x.b_code, x.name, x.unit, x.qty, x.qc_qty, errorType[x.type]]; })
+            //         });
+            //     }
+            // }
+            // XLSXObj.exportXlsxSheets(sheets, '批量导入错误.xlsx');
+            const setting = {
+                cols: [
+                    {title: '清单编号', field: 'b_code', hAlign: 0, width: 80, formatter: '@'},
+                    {title: '清单名称', field: 'name', hAlign: 0, width: 180, formatter: '@'},
+                    {title: '单位', field: 'unit', hAlign: 1, width: 60, formatter: '@'},
+                    {title: '合同数量', field: 'qty', hAlign: 2, width: 80, formatter: '@'},
+                    {title: '变更数量', field: 'qc_qty', hAlign: 2, width: 80, formatter: '@'},
+                    {title: '错误类型', field: 'type', hAlign: 0, width: 150, formatter: '@', getValue(data) { return errorType[data.type]; }},
+                ],
+                emptyRows: 0,
+                headRows: 1,
+                headRowHeight: [32],
+                defaultRowHeight: 21,
+                headerFont: '12px 微软雅黑',
+                font: '12px 微软雅黑',
+            };
+            const sheets = [];
+            for (const node of this.batchTree.children) {
+                if (node.error > 0) {
+                    sheets.push({ name: node.code, setting, data: node.errors });
+                }
+            }
+            SpreadExcelObj.exportSimpleXlsxSheets(sheets, '批量导入错误.xlsx');
+        }
+    };
+
+    $('#batch-import').on('shown.bs.modal', () => {
+        biObj.initBatchImport();
+        biObj.loadHistory();
+    });
+
+    $('#bi-start').click(function () {
+        biObj.batchImport();
+    });
+    $('#bi-download-error').click(function () {
+        biObj.downloadErrors();
+    });
+
+    const show = function () {
+        $('#batch-import').modal('show');
+    };
+
+    return { show }
+};

+ 62 - 22
app/public/js/shares/export_excel.js

@@ -22,22 +22,7 @@ const SpreadExcelObj = (function() {
         document.body.removeChild(div);
     };
 
-    const exportSpread2XlsxWithHeader = function (spread, file) {
-        spread.getActiveSheet().options.isProtected = false;
-        const excelIo = new GC.Spread.Excel.IO();
-        const sJson = JSON.stringify(spread.toJSON({columnHeadersAsFrozenRows: true, rowHeadersAsFrozenColumns: true}));
-        excelIo.save(sJson, function(blob) {
-            saveAs(blob, file);
-        });
-        spread.getActiveSheet().options.isProtected = true;
-    };
-
-    const exportSimpleXlsxSheet = function (setting, data, file) {
-        const div = _createHideSpread();
-
-        const spread = SpreadJsObj.createNewSpread(div, true);
-        const sheet = spread.getActiveSheet();
-
+    const exportSimpleXlsxSheetData = function (sheet, setting, data) {
         SpreadJsObj.beginMassOperation(sheet);
         sheet.options.isProtected = false;
         sheet.setColumnCount(setting.cols.length);
@@ -71,7 +56,7 @@ const SpreadExcelObj = (function() {
                 const cell = sheet.getCell(curRow, iCol);
                 const col = setting.cols[iCol];
                 if (col.field !== '' && d[col.field]) {
-                    cell.value(d[col.field]);
+                    cell.value(col.getValue ? col.getValue(d) : d[col.field]);
                     if (typeof d[col.field] === 'string') {
                         cell.formatter('@');
                     }
@@ -85,6 +70,35 @@ const SpreadExcelObj = (function() {
             }
         }
         SpreadJsObj.endMassOperation(sheet);
+    };
+
+    const exportSimpleXlsxSheet = function (setting, data, file) {
+        const div = _createHideSpread();
+
+        const spread = SpreadJsObj.createNewSpread(div, true);
+        const sheet = spread.getActiveSheet();
+        exportSimpleXlsxSheetData(sheet, setting, data);
+
+        const excelIo = new GC.Spread.Excel.IO();
+        const sJson = JSON.stringify(spread.toJSON());
+        excelIo.save(sJson, function(blob) {
+            saveAs(blob, file);
+            _removeHideSpread(div);
+        });
+    };
+
+    const exportSimpleXlsxSheets = function (sheets, file) {
+        if (!sheets || sheets.length === 0) return;
+
+        const div = _createHideSpread();
+        const spread = new spreadNS.Workbook(div, {sheetCount: sheets.length});
+
+        for (const [i, sheetData] of sheets.entries()) {
+            const sheet = spread.getSheet(i);
+            sheet.name(sheetData.name);
+            exportSimpleXlsxSheetData(sheet, sheetData.setting, sheetData.data);
+
+        }
 
         const excelIo = new GC.Spread.Excel.IO();
         const sJson = JSON.stringify(spread.toJSON());
@@ -94,14 +108,24 @@ const SpreadExcelObj = (function() {
         });
     };
 
-    return {exportSimpleXlsxSheet, exportSpread2XlsxWithHeader}
+    const exportSpread2XlsxWithHeader = function (spread, file) {
+        spread.getActiveSheet().options.isProtected = false;
+        const excelIo = new GC.Spread.Excel.IO();
+        const sJson = JSON.stringify(spread.toJSON({columnHeadersAsFrozenRows: true, rowHeadersAsFrozenColumns: true}));
+        excelIo.save(sJson, function(blob) {
+            saveAs(blob, file);
+        });
+        spread.getActiveSheet().options.isProtected = true;
+    };
+
+    return {exportSimpleXlsxSheet, exportSpread2XlsxWithHeader, exportSimpleXlsxSheets}
 })();
 
 const XLSXObj = (function () {
-    const exportXlsxSheet = function (setting, data, file) {
+    const transportSheetData = function (setting, data) {
         const headerStyle = {
             font: { sz: 10, bold: true },
-            alignment: {horizontal: 'center'},
+            alignment: { horizontal: 'center' },
         };
         const sHeader = setting.header
             .map((v, i) => Object.assign({}, {v: v, s: headerStyle, position: String.fromCharCode(65+i) + 1 }))
@@ -118,6 +142,11 @@ const XLSXObj = (function () {
         const result = Object.assign({}, output,
             {'!ref': outputPos[0] + ':' + outputPos[outputPos.length - 1]},
             {'!cols': setting.width.map((w) => Object.assign({}, {wpx: w}))});
+        return result;
+    };
+
+    const exportXlsxSheet = function (setting, data, file) {
+        const result = transportSheetData(setting, data);
         const xlsxData = {
             SheetNames: ['Sheet1'],
             Sheets: {
@@ -128,5 +157,16 @@ const XLSXObj = (function () {
         saveAs(blob, file);
     };
 
-    return {exportXlsxSheet}
-});
+    const exportXlsxSheets = function (sheets, file) {
+        const xlsxData = { SheetNames: [], Sheets: {} };
+        for (const sheet of sheets) {
+            const xlsxSheet = transportSheetData(sheet.setting, sheet.data);
+            xlsxData.SheetNames.push(sheet.name);
+            xlsxData.Sheets[sheet.name] = xlsxSheet;
+        }
+        const blob = xlsxUtils.format2Blob(xlsxData);
+        saveAs(blob, file);
+    };
+
+    return { exportXlsxSheet, exportXlsxSheets }
+})();

+ 27 - 0
app/public/js/stage.js

@@ -1479,6 +1479,23 @@ $(document).ready(() => {
             if (checkedChanges) checkedChanges.reloadChangeData();
         }
     });
+    const batchImport = BatchImportStageGcl({
+        stageTree,
+        afterLoad: function (result, select) {
+            const nodes = stageTree.loadPostStageData(result);
+            const posterity = stageTree.getPosterity(select);
+            for (const p of posterity) {
+                p.is_import = !!result.import_change.data.find(x => { return x.lid === p.id });
+            }
+            stageTreeSpreadObj.refreshTreeNodes(slSpread.getActiveSheet(), nodes);
+            if (detail) {
+                detail.loadStageLedgerUpdateData(result, nodes);
+            } else {
+                stageIm.loadUpdateLedgerData(result, nodes);
+            }
+            if (checkedChanges) checkedChanges.reloadChangeData();
+        }
+    });
     $.contextMenu({
         selector: '#stage-ledger',
         build: function ($trigger, e) {
@@ -1578,6 +1595,16 @@ $(document).ready(() => {
                     tenderSelect.showSelect(SpreadJsObj.getSelectObject(slSpread.getActiveSheet()));
                 }
             },
+            batchImportStageGcl: {
+                name: '批量导入(其他标段)工程量清单计量数据',
+                icon: 'fa-link',
+                disable: function (key, opt) {
+                    return readOnly;
+                },
+                callback: function (key, opt){
+                    batchImport.show();
+                }
+            },
             shoufangdanSpr: '---',
             shoufangdan: {
                 name: '生成收方单',

+ 11 - 0
app/service/sum_load_history.js

@@ -48,6 +48,17 @@ module.exports = app => {
             return await this.getHistroy(tid, lid, 'stage');
         }
 
+        async getBatchHistory(tid) {
+            const sql = `SELECT * FROM ${this.tableName} WHERE id IN (SELECT MAX(id) FROM ${this.tableName} WHERE tid = ? GROUP by lid);`;
+            const result = await this.db.query(sql, [tid]);
+            result.forEach(x => {
+                if (x && x.tenders) x.tenders = JSON.parse(x.tenders);
+                delete x.errors;
+            });
+            return result;
+        }
+
+
         async saveLedgerHistory(tid, lid, tenders, errors) {
             const data = {
                 tid, lid, type: 'ledger',

+ 32 - 0
app/view/shares/batch_import_modal.ejs

@@ -0,0 +1,32 @@
+<div class="modal fade" id="batch-import" data-backdrop="static">
+    <div class="modal-dialog modal-lg" role="document">
+        <div class="modal-content">
+            <div class="modal-header">
+                <h5 class="modal-title" id="batch-import-title">批量导入(其他标段)工程量清单计量数据</h5>
+                <button type="button" class="close" data-dismiss="modal" aria-label="Close">
+                    <span aria-hidden="true">&times;</span>
+                </button>
+            </div>
+            <div class="modal-body">
+                <div class="form-group mb-2">
+                    <div class="d-flex">
+                        <div class="d-inline-block">
+                            <div class="form-check form-check-inline">
+                                <input class="form-check-input" type="checkbox" id="bi-cover">
+                                <label class="form-check-label" for="bi-cover">覆盖数据</label>
+                            </div>
+                        </div>
+                        <div class="ml-auto">
+                            <button class="btn btn-sm btn-primary" id="bi-start">导入</button>
+                            <button class="btn btn-sm btn-primary" id="bi-download-error" style="display: none">下载错误信息</button>
+                        </div>
+                    </div>
+                </div>
+                <div class="form-group">
+                    <div class="modal-height-300" id="bi-spread">
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+</div>

+ 2 - 1
app/view/stage/modal.ejs

@@ -652,4 +652,5 @@
 <% include ../shares/tender_select_modal.ejs %>
 <% include ../shares/import_excel_modal.ejs%>
 <% include ../shares/stage_stash_modal.ejs%>
-<% include ../shares/delete_hint_modal.ejs%>
+<% include ../shares/delete_hint_modal.ejs%>
+<% include ../shares/batch_import_modal.ejs%>

+ 1 - 0
config/web.js

@@ -355,6 +355,7 @@ const JsFiles = {
                     '/public/js/stage_im.js',
                     '/public/js/shares/tenders2tree.js',
                     '/public/js/shares/tender_select.js',
+                    '/public/js/shares/batch_import.js',
                     // '/public/js/shares/stage_excel.js',
                     '/public/js/ledger_check.js',
                     '/public/js/stage.js',