MaiXinRong 1 неделя назад
Родитель
Сommit
d19253add5

+ 48 - 4
app/controller/cost_controller.js

@@ -87,8 +87,8 @@ module.exports = app => {
             }
         }
 
-        async getTypeStages(ctx, stage_type) {
-            const stages = await this.ctx.service.costStage.getAllStages(ctx.tender.id, stage_type, 'DESC');
+        async getTypeStages(ctx, stage_type, sort = 'DESC') {
+            const stages = await this.ctx.service.costStage.getAllStages(ctx.tender.id, stage_type, sort);
             for (const s of stages) {
                 if (s.audit_status !== audit.common.status.checked) {
                     await this.ctx.service.costStage.loadUser(s);
@@ -863,7 +863,7 @@ module.exports = app => {
 
         async gather(ctx) {
             try {
-                const ledgerStages = await this.getTypeStages(ctx, 'ledger');
+                const ledgerStages = await this.getTypeStages(ctx, 'ledger', 'ASC');
                 const renderData = {
                     ledgerStages,
                     jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.cost.cost_gather)
@@ -875,9 +875,53 @@ module.exports = app => {
                 ctx.redirect(`/sp/${ctx.subProject.id}/cost`);
             }
         }
+        async _ledgerGatherLoad(stage) {
+            const result = {};
+            result.bills = stage.audit_status !== audit.common.status.checked
+                ? await this.ctx.service.costStageLedger.getReadData(stage)
+                : await this.ctx.service.costStageLedger.getEditData(stage);
+            return result;
+        }
+        async _bookGatherLoad(stage) {
+            const result = {};
+            stage.relaStage = await this.ctx.service.costStage.getStage(stage.rela_stage.sid);
+            result.bills = await this.ctx.service.costStageLedger.getReadData(stage.relaStage);
+            result.bills.forEach(l => { delete l.postil; delete l.memo; });
+            const book = stage.audit_status !== audit.common.status.checked
+                ? await this.ctx.service.costStageBook.getReadData(stage)
+                : await this.ctx.service.costStageBook.getEditData(stage);
+            this.ctx.helper.assignRelaData(result.bills, [
+                { data: book, fields: ['in_tp', 'in_excl_tax_tp', 'postil', 'memo'], prefix: '', relaId: 'ledger_id' },
+                { data: book, fields: ['id'], prefix: 'book_', relaId: 'ledger_id' },
+            ]);
+            return result;
+        }
+        async _analysisGatherLoad(stage) {
+            const result = {};
+            result.bills = stage.audit_status !== audit.common.status.checked
+                ? await this.ctx.service.costStageAnalysis.getReadData(stage)
+                : await this.ctx.service.costStageAnalysis.getEditData(stage);
+            return result;
+        }
         async loadGatherData(ctx) {
             try {
-
+                const data = JSON.parse(ctx.request.body.data);
+                const result = {};
+
+                if (data.stages) {
+                    result.stages = [];
+                    for (const s of data.stages) {
+                        const stage = await this.ctx.service.costStage.getStage(s);
+                        const loadFun = `_${stage.stage_type}GatherLoad`;
+                        if (this[loadFun]) {
+                            const detail = await this[loadFun](stage);
+                            result.stages.push({ id: stage.id, stage_date: stage.stage_date, stage_order: stage.stage_order, detail });
+                        } else {
+                            throw '未知期数据类型';
+                        }
+                    }
+                }
+                ctx.body = { err: 0, msg: '', data: result }
             } catch(err) {
                 ctx.log(err);
                 ctx.ajaxErrorBody(err, '获取汇总数据错误');

+ 186 - 0
app/public/js/cost_gather.js

@@ -0,0 +1,186 @@
+$(document).ready(() => {
+    autoFlashHeight();
+    const ledgerGatherSpreadSetting = {
+        cols: [
+            {title: '编号', colSpan: '1', rowSpan: '2', field: 'code', hAlign: 0, width: 230, formatter: '@', cellType: 'tree'},
+            {title: '名称', colSpan: '1', rowSpan: '2', field: 'name', hAlign: 0, width: 185, formatter: '@', cellType: 'autoTip'},
+        ],
+        baseCols: [
+            {title: '编号', colSpan: '1', rowSpan: '2', field: 'code', hAlign: 0, width: 230, formatter: '@', cellType: 'tree'},
+            {title: '名称', colSpan: '1', rowSpan: '2', field: 'name', hAlign: 0, width: 185, formatter: '@', cellType: 'autoTip'},
+        ],
+        extraCols: [
+            {title: '%s|付款金额', colSpan: '4|1', rowSpan: '1|1', field: 'pay_tp', hAlign: 2, width: 80, type: 'Number'},
+            {title: '|扣款金额', colSpan: '|1', rowSpan: '|1', field: 'cut_tp', hAlign: 2, width: 80, type: 'Number'},
+            {title: '|应付金额', colSpan: '|1', rowSpan: '|1', field: 'yf_tp', hAlign: 2, width: 80, type: 'Number'},
+            {title: '|实付金额', colSpan: '|1', rowSpan: '|1', field: 'sf_tp', hAlign: 2, width: 80, type: 'Number'},
+        ],
+        emptyRows: 0,
+        headRows: 2,
+        headRowHeight: [25, 25],
+        defaultRowHeight: 21,
+        headerFont: '12px 微软雅黑',
+        font: '12px 微软雅黑',
+        readOnly: true,
+    };
+    sjsSettingObj.setFxTreeStyle(ledgerGatherSpreadSetting, sjsSettingObj.FxTreeStyle.phasePay);
+
+    const ledgerGatherSpread = SpreadJsObj.createNewSpread($('#ledger-gather-spread')[0]);
+    const ledgerGatherSheet = ledgerGatherSpread.getActiveSheet();
+    SpreadJsObj.initSheet(ledgerGatherSheet, ledgerGatherSpreadSetting);
+
+    const ledgerGatherObj = {
+        tree: createNewPathTree('ledger', {
+            id: 'tree_id', pid: 'tree_pid', order: 'tree_order',
+            level: 'tree_level', isLeaf: 'tree_is_leaf', fullPath: 'tree_full_path',
+            rootId: -1, calcFields: [],
+        }),
+        stages: [],
+        splitStages (gatherType, gatherStageIds) {
+            const gatherStages = this.stages.filter(x => { return gatherStageIds.indexOf(x.id) >= 0; });
+            gatherStages.sort((a, b) => { return a.stage_order - b.stage_order; });
+            const result = { main: gatherStages[gatherStages.length - 1], gatherPart: [] };
+            for (const gs of gatherStages) {
+                if (gatherType === 'stage') {
+                    result.gatherPart.push({title: `第${gs.stage_order}期(${gs.stage_date})`, stages: [gs]});
+                } else {
+                    let title;
+                    if (gatherType === 'month') title = `${gs.stage_date}`;
+                    if (gatherType === 'quarter') title = `${gs.gather_year} 第${gs.gather_quarter}季度`;
+                    if (gatherType === 'year') title = `${gs.gather_year}`;
+
+                    let gp = result.gatherPart.find(x => { return x.title === title; });
+                    if (!gp) {
+                        gp = { title, stages: [] };
+                        result.gatherPart.push(gp);
+                    }
+                    gp.stages.push(gs);
+                }
+            }
+            return result;
+        },
+        gather (gatherType, gatherStageIds) {
+            if (gatherStageIds.length === 0) return;
+            const gatherInfo = this.splitStages(gatherType, gatherStageIds);
+
+            this.tree.setting.calcFields.length = 0;
+            this.tree.loadDatas(gatherInfo.main.detail.bills);
+            ledgerGatherSpreadSetting.cols.length = 0;
+            ledgerGatherSpreadSetting.cols.push(...ledgerGatherSpreadSetting.baseCols);
+            for (const [i, gp] of gatherInfo.gatherPart.entries()) {
+                const loadFields = [];
+                for (const ec of ledgerGatherSpreadSetting.extraCols) {
+                    const col = JSON.parse(JSON.stringify(ec));
+                    col.title = col.title.replace('%s', gp.title);
+                    loadFields.push({ sf: col.field, tf: col.field + '_' + i});
+                    col.field = col.field + '_' + i;
+                    ledgerGatherSpreadSetting.cols.push(col);
+                    this.tree.setting.calcFields.push(col.field);
+                    this.tree.nodes.forEach(x => { x[col.field] = 0; });
+                }
+                for (const s of gp.stages) {
+                    for (b of s.detail.bills) {
+                        const node = this.tree.nodes.find(x => { return x.cost_id === b.cost_id; });
+                        if (!node) continue;
+                        for (const lf of loadFields) {
+                            node[lf.tf] = ZhCalc.add(node[lf.tf], b[lf.sf]);
+                        }
+                    }
+                }
+            }
+            treeCalc.calculateAll(this.tree);
+            SpreadJsObj.initSheet(ledgerGatherSheet, ledgerGatherSpreadSetting);
+            SpreadJsObj.loadSheetData(ledgerGatherSheet, SpreadJsObj.DataType.Tree, this.tree);
+            SpreadJsObj.locateRow(ledgerGatherSheet, 0);
+        },
+        loadStages (stages) {
+            for (const s of stages) {
+                s.gather_stage = s.stage_order;
+                s.gather_month = s.stage_date;
+                s.gather_time = moment(new Date(s.stage_date));
+                s.gather_quarter = s.gather_time.quarter();
+                s.gather_year = s.gather_time.year();
+                this.stages.push(s);
+            }
+        },
+        exportExcel() {
+            if (!this.tree || this.tree.nodes.length === 0) {
+                toastr.warning('无可导出数据');
+                return;
+            }
+            SpreadExcelObj.exportSimpleXlsxSheet(ledgerGatherSpreadSetting, this.tree.nodes, "成本管理-汇总分析.xlsx");
+        }
+    };
+
+    $('#select-qi-all').click(function() {
+        const check = this.checked;
+        $('input', 'tr[stage-id]').each((i, x) => {
+            x.checked = check;
+        })
+    });
+    $('#select-qi-ok').click(function() {
+        let loadData = [], gatherData = [], trs = $('tr[stage-id]');
+        for (let order = 0, iLength = trs.length; order < iLength; order++) {
+            const tr = trs[order];
+            if ($('input[type=checkbox]', tr)[0].checked) {
+                const sid = tr.getAttribute('stage-id');
+                const exist = ledgerGatherObj.stages.find(x => { return x.id === sid; });
+                if (!exist) loadData.push(sid);
+                gatherData.push(sid);
+            }
+        }
+        const gatherType = $('input[name=gather-type]:checked').val();
+        if (loadData.length > 0) {
+            postData(window.location.pathname + '/load', {stages: loadData}, function (result) {
+                ledgerGatherObj.loadStages(result.stages);
+                ledgerGatherObj.gather(gatherType, gatherData);
+                $('#select-qi').modal('hide');
+            }, null, true);
+        } else {
+            ledgerGatherObj.gather(gatherType, gatherData);
+            $('#select-qi').modal('hide');
+        }
+    });
+
+    $('#export-excel').click(function() {
+        ledgerGatherObj.exportExcel();
+    });
+    // 显示层次
+    (function (select, sheet) {
+        $(select).click(function () {
+            if (!sheet.zh_tree) return;
+            const tag = $(this).attr('tag');
+            const tree = sheet.zh_tree;
+            switch (tag) {
+                case "1":
+                case "2":
+                    tree.expandByLevel(parseInt(tag));
+                    SpreadJsObj.refreshTreeRowVisible(sheet);
+                    break;
+                case "last":
+                    tree.expandByCustom(() => { return true; });
+                    SpreadJsObj.refreshTreeRowVisible(sheet);
+                    break;
+            }
+        });
+    })('a[name=showLevel]', ledgerGatherSheet);
+    $.subMenu({
+        menu: '#sub-menu', miniMenu: '#sub-mini-menu', miniMenuList: '#mini-menu-list',
+        toMenu: '#to-menu', toMiniMenu: '#to-mini-menu',
+        key: 'menu.1.0.0',
+        miniHint: '#sub-mini-hint', hintKey: 'menu.hint.1.0.1',
+        callback: function (info) {
+            if (info.mini) {
+                $('.panel-title').addClass('fluid');
+                $('#sub-menu').removeClass('panel-sidebar');
+                $('.c-body table thead').css('left', '56px');
+            } else {
+                $('.panel-title').removeClass('fluid');
+                $('#sub-menu').addClass('panel-sidebar');
+                $('.c-body table thead').css('left', '176px');
+            }
+            autoFlashHeight();
+            ledgerGatherSpread.refresh();
+        }
+    });
+});

+ 1 - 0
app/router.js

@@ -572,6 +572,7 @@ module.exports = app => {
     app.post('/sp/:id/cost/tender/:tid/:stype/:sorder/audit/checkAgain', sessionAuth, subProjectCheck, tenderCheck, tenderPermissionCheck, costStageCheck, 'costController.stageAuditCheckAgain');
     app.post('/sp/:id/cost/tender/:tid/:stype/:sorder/audit/checkCancel', sessionAuth, subProjectCheck, tenderCheck, tenderPermissionCheck, costStageCheck, 'costController.stageAuditCheckCancel');
     app.get('/sp/:id/cost/tender/:tid/gather', sessionAuth, subProjectCheck, tenderCheck, tenderPermissionCheck, 'costController.gather');
+    app.post('/sp/:id/cost/tender/:tid/gather/load', sessionAuth, subProjectCheck, tenderCheck, tenderPermissionCheck, 'costController.loadGatherData');
 
     // 安全管理
     // 安全计量

+ 20 - 1
app/view/cost/gather.ejs

@@ -3,11 +3,30 @@
     <div class="panel-title">
         <div class="title-main d-flex">
             <% include ./list_mini_menu.ejs %>
+            <div>
+                <div class="d-inline-block">
+                    <div class="dropdown">
+                        <button class="btn btn-sm btn-light dropdown-toggle text-primary" type="button" id="dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
+                            <i class="fa fa-list-ol"></i> 显示层级
+                        </button>
+                        <div class="dropdown-menu" aria-labelledby="dropdownMenuButton">
+                            <a class="dropdown-item" name="showLevel" tag="1" href="javascript: void(0);">第一层</a>
+                            <a class="dropdown-item" name="showLevel" tag="2" href="javascript: void(0);">第二层</a>
+                            <a class="dropdown-item" name="showLevel" tag="3" href="javascript: void(0);">第三层</a>
+                            <a class="dropdown-item" name="showLevel" tag="4" href="javascript: void(0);">第四层</a>
+                            <a class="dropdown-item" name="showLevel" tag="last" href="javascript: void(0);">最底层</a>
+                        </div>
+                    </div>
+                </div>
+            </div>
+            <div class="ml-auto">
+                <button href="#cate-set" class="btn btn-sm btn-light text-primary" data-toggle="modal" data-target="#select-qi"><i class="fa fa-clone"></i> 选择期</button>
+            </div>
         </div>
     </div>
     <div class="content-wrap">
         <div class="c-body">
-            <div class="sjs-height-0">
+            <div class="sjs-height-0" id="ledger-gather-spread">
             </div>
         </div>
     </div>

+ 46 - 0
app/view/cost/gather_modal.ejs

@@ -0,0 +1,46 @@
+<div class="modal fade" id="select-qi" 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 scroll-y modal-height-500">
+                <table class="table table-sm" >
+                    <tr class="text-center"><th>期</th><th>报审年月</th><th width="60">选择</th></tr>
+                    <% for (const s of ledgerStages) { %>
+                    <tr stage-id="<%- s.id %>"><td><%- s.stage_order %>期</td><td><%- s.stage_date %></td><td><input type="checkbox"></td></tr>
+                    <% } %>
+                </table>
+            </div>
+            <div class="modal-body">
+                <div class="d-flex">
+                    <div class="form-check form-check-inline">汇总方式:</div>
+                    <div class="form-check form-check-inline">
+                        <input class="form-check-input" type="radio" name="gather-type" id="gather1" value="stage" checked>
+                        <label class="form-check-label" for="gather1">期</label>
+                    </div>
+                    <div class="form-check form-check-inline">
+                        <input class="form-check-input" type="radio" name="gather-type" id="gather2" value="month">
+                        <label class="form-check-label" for="gather2">月份</label>
+                    </div>
+                    <div class="form-check form-check-inline">
+                        <input class="form-check-input" type="radio" name="gather-type" id="gather3" value="quarter">
+                        <label class="form-check-label" for="gather3">季度</label>
+                    </div>
+                    <div class="form-check form-check-inline">
+                        <input class="form-check-input" type="radio" name="gather-type" id="gather4" value="year">
+                        <label class="form-check-label" for="gather4">年度</label>
+                    </div>
+                </div>
+            </div>
+            <div class="modal-footer">
+                <div class="form-check form-check-inline">
+                    <input class="form-check-input" type="checkbox" id="select-qi-all">
+                    <label class="form-check-label" for="select-qi-all">全选</label>
+                </div>
+                <button type="button" class="btn btn-secondary btn-sm" data-dismiss="modal">关闭</button>
+                <button type="button" class="btn btn-primary btn-sm" id="select-qi-ok">确认</button>
+            </div>
+        </div>
+    </div>
+</div>

+ 2 - 2
app/view/setting/limit.ejs

@@ -10,7 +10,7 @@
                 <div class="col-2">
                     <div class="d-flex flex-row">
                         <form class="ml-2 p-2" method="POST" action="/setting/limit/save">
-                            <input type="hidden" name="_csrf_j2" value="<%= ctx.csrf %>" />
+                            <input type="hidden" name="_csrf_j" value="<%= ctx.csrf %>" />
                             <button class="btn btn-sm btn-light text-primary"><i class="fa fa-plus" aria-hidden="true" type="submit"></i> 新增配置</button>
                         </form>
                     </div>
@@ -50,7 +50,7 @@
     </div>
     <div stype="display: none">
         <form id="hiddenForm" action="" method="POST">
-            <input type="hidden" name="_csrf_j2" value="<%= ctx.csrf %>" />
+            <input type="hidden" name="_csrf_j" value="<%= ctx.csrf %>" />
             <input type="hidden" id="extra" name="" value="">
         </form>
     </div>

+ 2 - 2
app/view/setting/s2b.ejs

@@ -172,12 +172,12 @@
                                     <td class="text-center">
                                         <div class="form-check form-check-inline">
                                             <input class="form-check-input" type="checkbox" id="inlineCheckbox5" <% if (t.s2b_multi_check) { %>checked<% } %> name="multi_check" onchange="updateS2bSetting(this);" value="<%- t.s2b_multi_check %>">
-                                            <input type="hidden" name="_csrf_j2" value="<%= ctx.csrf %>">
+                                            <input type="hidden" name="_csrf_j" value="<%= ctx.csrf %>">
                                             <label class="form-check-label" for="inlineCheckbox5">检查计量</label>
                                         </div>
                                         <div class="form-check form-check-inline">
                                             <input class="form-check-input" type="checkbox" id="inlineCheckbox6" <% if (t.s2b_multi_limit) { %>checked<% } %> name="multi_limit" onchange="updateS2bSetting(this);" value="<%- t.s2b_multi_limit %>">
-                                            <input type="hidden" name="_csrf_j2" value="<%= ctx.csrf %>">
+                                            <input type="hidden" name="_csrf_j" value="<%= ctx.csrf %>">
                                             <label class="form-check-label" for="inlineCheckbox6">限制上报</label>
                                         </div>
                                         <div class="form-check form-check-inline">