소스 검색

Merge branch 'master' of http://192.168.1.41:3000/maixinrong/Calculation

TonyKang 5 년 전
부모
커밋
8e9acb5195

+ 1 - 1
app/controller/setting_controller.js

@@ -98,7 +98,7 @@ module.exports = app => {
                 // 获取项目用户列表
                 const accountData = await ctx.service.projectAccount.getAllDataByCondition({
                     where: { project_id: projectId },
-                    columns: ['id', 'account', 'name', 'company', 'role', 'mobile', 'telephone', 'enable', 'is_admin', 'account_group'],
+                    columns: ['id', 'account', 'name', 'company', 'role', 'mobile', 'auth_mobile', 'telephone', 'enable', 'is_admin', 'account_group'],
                 });
 
                 const renderData = {

+ 35 - 16
app/extend/helper.js

@@ -386,28 +386,47 @@ module.exports = {
      */
     compareCode(str1, str2, symbol = '-') {
         if (!str1) {
-            return -1;
-        } else if (!str2) {
             return 1;
+        } else if (!str2) {
+            return -1;
         }
 
-        const path1 = str1.split(symbol);
-        const path2 = str2.split(symbol);
-        const reg = /^[0-9]*$/;
-        for (let i = 0, iLen = Math.min(path1.length, path2.length); i < iLen; i++) {
-            if (reg.test(path1[i]) && reg.test(path2[i])) {
-                const num1 = parseInt(path1[i]);
-                const num2 = parseInt(path2[i]);
-                if (num1 !== num2)  {
-                    return num1 - num2;
+        function compareSubCode(code1, code2) {
+            if (numReg.test(code1)) {
+                if (numReg.test(code2)) {
+                    return parseInt(code1) - parseInt(code2);
+                } else {
+                    return -1
+                }
+            } else {
+                if (numReg.test(code2)) {
+                    return 1;
+                } else {
+                    return code1 === code2 ? 0 : (code1 < code2 ? -1 : 1); //code1.localeCompare(code2);
                 }
-            } else if (path1[i] < path2[i]) {
-                return -1;
-            } else if (path1[i] > path2[i]) {
-                return 1;
             }
         }
-        return path1.length - path2.length;
+        const numReg = /^[0-9]+$/;
+        const aCodes = str1.split(symbol), bCodes = str2.split(symbol);
+        for (let i = 0, iLength = Math.min(aCodes.length, bCodes.length); i < iLength; ++i) {
+            const iCompare = compareSubCode(aCodes[i], bCodes[i]);
+            if (iCompare !== 0) {
+                return iCompare;
+            }
+        }
+        return aCodes.length - bCodes.length;
+    },
+
+    getChapterCode(code, symbol = '-') {
+        if (!code || code === '') return '';
+        const codePath = code.split(symbol);
+        const reg = /^[0-9]*$/;
+        if (reg.test(codePath[0])) {
+            const num = parseInt(codePath[0]);
+            return this.mul(this.div(num, 100, 0), 100);
+        } else {
+            return '';
+        }
     },
 
     /**

+ 1 - 0
app/lib/ledger.js

@@ -248,6 +248,7 @@ class billsTree {
     getDatas (fields) {
         const datas = [];
         for (const node of this.nodes) {
+            if (node.b_code && node.b_code !== '') node.chapter = this.ctx.helper.getChapterCode(node.b_code);
             const data = {};
             for (const field of fields) {
                 data[field] = node[field];

+ 112 - 4
app/lib/rpt_data_analysis.js

@@ -18,11 +18,11 @@ const changeSort = {
      * @param fieldsKey - 计算字段
      */
     fun: function (ctx, data, fieldsKey) {
-        if (!data.change || data.change_audit_list) return;
+        if (!data.change || !data.change_audit_list) return;
         // 变更令排序
         data.change.sort(function (a, b) {
             return a.code.localeCompare(b.code);
-        };
+        });
         data.change_audit_list.sort(function (a, b) {
             const aCIndex = data.change.findIndex(function (c) {
                 return c.cid === a.cid;
@@ -32,11 +32,119 @@ const changeSort = {
             });
             return aCIndex === bCIndex
                 ? ctx.helper.compareCode(a.code, b.code)
-                : aCIndex - bCindex;
-        })
+                : aCIndex - bCIndex;
+        });
     },
 };
+const gatherGcl = {
+    name: '汇总工程量清单',
+    hint: '请使用mem_stage_bills下指标,注意事项:\n' +
+        '1. 以下字段,不管报表是否实际使用,均应添加至指标映射,且在此处应勾选(不要求顺序):\n' +
+        '  清单编号(b_code), 名称(name), 单位(unit), 单价(unit_price), 树结构-是否子项(is_leaf)\n' +
+        '2. 汇总后,以下字段,均会失效, 请勿使用:\n' +
+        '  台账ID(id), 树结构-ID(ledger_id), 树结构父项-ID(ledger_pid),\n' +
+        '  树结构-层级(level), 树结构-同层排序(order), 树结构-完整路径(full_path),\n' +
+        '  图册号(drawing_code), 备注(memo), 节点类型(node_type), 总额计量(is_tp)\n' +
+        '3. 如需汇总"未计入清单章节项",请勾选"章节编号(chapter)"字段\n',
+    fun: function (ctx, data, fieldsKey) {
+        const gatherFields = function(gcl, data, fields) {
+            for (const f of fields) {
+                if (data[f]) {
+                    gcl[f] = ctx.helper.add(gcl[f], data[f]);
+                }
+            }
+        };
+        if (!data.mem_stage_bills) return;
+
+        const fields = ctx.helper._.map(fieldsKey, 'field');
+        const needFields = ['b_code', 'name', 'unit', 'unit_price', 'is_leaf'];
+        for (const nf of needFields) {
+            if (fields.indexOf(nf) === -1) return;
+        }
+        const gatherOther = fields.indexOf('chapter') >= 0;
+
+        ctx.helper._.pull(data.mem_stage_bills, 'is_leaf');
+        const gclBills = [], other = {name: '未计入清单章节项', chapter: '100'};
+
+        for (const b of data.mem_stage_bills) {
+            if (b.b_code && b.b_code !== '') {
+                let gcl = gclBills.find(function (g) {
+                    return g.b_code === b.b_code && g.name === b.name && g.unit === b.unit
+                        && ctx.helper.checkZero(ctx.helper.sub(g.unit_price, b.unit_price));
+                });
+                if (!gcl) {
+                    gcl = {
+                        b_code: b.b_code, name: b.name, unit: b.unit,
+                        unit_price: b.unit_price,
+                        qc_bgl_code: [],
+                    };
+                    gclBills.push(gcl);
+                }
+                gatherFields(gcl, b, [
+                    'deal_qty', 'deal_tp',
+                    'sgfh_qty', 'sgfh_tp', 'sjcl_qty', 'sjcl_tp', 'qtcl_qty', 'qtcl_tp', 'quantity', 'total_price',
+                    'contract_qty', 'contract_tp', 'qc_qty', 'qc_tp', 'gather_qty', 'gather_tp',
+                    'pre_contract_qty', 'pre_contract_tp', 'pre_qc_qty', 'pre_qc_tp', 'pre_gather_qty', 'pre_gather_tp',
+                    'end_contract_qty', 'end_contract_tp', 'end_qc_qty', 'end_qc_tp', 'end_gather_qty', 'end_gather_tp',
+                    'final_tp'
+                ]);
+                if (b.qc_bgl_code && b.qc_bgl_code !== '') {
+                    gcl.qc_bgl_code = gcl.qc_bgl_code.concat(b.qc_bgl_code.split(';'));
+                }
+            } else if (gatherOther) {
+                gatherFields(other, b, [
+                    'deal_tp', 'sgfh_tp', 'sjcl_tp', 'qtcl_tp', 'total_price',
+                    'contract_tp', 'qc_tp', 'gather_tp',
+                    'pre_contract_tp', 'pre_qc_tp', 'pre_gather_tp',
+                    'end_contract_tp', 'end_qc_tp', 'end_gather_tp',
+                    'final_tp'
+                ]);
+            }
+        }
+        if (gatherOther) gclBills.push(other);
+        for (const g of gclBills) {
+            if (g.qc_bgl_code) g.qc_bgl_code = g.qc_bgl_code.join(';');
+            g.final_ratio = ctx.helper.mul(ctx.helper.div(g.end_gather_tp, g.final_ratio), 100);
+        }
+
+        data.mem_stage_bills = gclBills;
+    }
+};
+const sortGcl = {
+    name: '工程量清单排序',
+    hint: '只对一张表,进行工程量清单排序,排序哪张表,根据勾选的清单编号字段决定:\n' +
+        'e.g.1 要对mem_stage_bills排序,需要勾选mem_stage_bills下的"清单编号(b_code)"字段\n' +
+        'e.g.2 要对mem_stage_im_zl排序,需要勾选mem_stage_im_zl下的"中间计量总量信息_编号(code)"字段\n' +
+        '特别的,如有"未计入清单章节项": \n' +
+        '  1. 默认"未计入清单章节项"排列在最后\n' +
+        '  2. 如须"未计入清单章节项"排在100章之后,请在清单编号字段后,依次勾选"章节编号(chapter)", "名称(name)"\n',
+    fun: function (ctx, data, fieldsKey) {
+        if (fieldsKey.length !== 1 && fieldsKey.length !== 3) return;
+        const code = fieldsKey[0].field;
+        const chapter = fieldsKey.length > 1 ? fieldsKey[1].field : '';
+        const name = fieldsKey.length > 2 ? fieldsKey[2].field : '';
+
+        const sortData = data[fieldsKey[0].table];
+        if (!sortData) return;
+
+        sortData.sort(function (a, b) {
+            if (chapter !== '') {
+                if (a[name] === '未计入清单章节项') {
+                    return b[chapter] === '100' ? 1 : -1;
+                } else if (b[name] === '未计入清单章节项') {
+                    return a[chapter] === '100' ? -1 : 1
+                } else {
+                    return ctx.helper.compareCode(a[code], b[code]);
+                }
+            } else {
+                return ctx.helper.compareCode(a[code], b[code]);
+            }
+        });
+    }
+};
 
 module.exports = {
     changeSort,
+    gatherGcl,
+    sortGcl,
 };

+ 0 - 2
app/public/js/change_set.js

@@ -543,14 +543,12 @@ function tableDataRemake(changeListData) {
         for (const cl of changeList) {
             const clinfo = cl.split(';');
             const listinfo = changeListData[clinfo[8] - 1];
-            console.log(listinfo);
             $('#table-list-select tr[data-index="'+ clinfo[8] +'"]').addClass('table-success');
             let pushbwmx = '0;0';
             if (listinfo.leafXmjs !== undefined) {
                 const leafInfo = listinfo.leafXmjs.find(function (item) {
                     return (item.bwmx === undefined || item.bwmx === clinfo[2]) && item.quantity === parseFloat(clinfo[5]);
                 });
-                console.log(leafInfo);
                 pushbwmx = leafInfo.code + '_' + (leafInfo.bwmx !== undefined ? leafInfo.bwmx : '') + ';' + leafInfo.quantity;
             } else {
                 pushbwmx = '0;' + (listinfo.quantity !== null ? listinfo.quantity : 0);

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

@@ -32,7 +32,7 @@ $(document).ready(() => {
         $('#edit-user input[name="id"]').val(account.id);
         $('#edit-user select[name="account_group"]').val(account.account_group);
         $('#edit-user input[class="account-check"]').val(account.account);
-        $('#edit-user input[class="auth-mobile"]').val(account.auth_mobile);
+        $('#edit-user input[data-mobile="auth-mobile"]').val(account.auth_mobile);
     });
 
     // 分配随机密码

+ 112 - 0
app/service/report.js

@@ -0,0 +1,112 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date
+ * @version
+ */
+
+module.exports = app => {
+    class Report extends app.BaseService {
+
+        /**
+         * 构造函数
+         *
+         * @param {Object} ctx - egg全局context
+         * @return {void}
+         */
+        constructor(ctx) {
+            super(ctx);
+        }
+
+        async getReportData(params, filters, memFieldKeys) {
+            const service = this.ctx.service;
+            const rst = {};
+            const runnableRst = [];
+            const runnableKey = []; // 这个配合runnableRst用,未来考虑并行查询优化
+            for (const filter of filters) {
+                if (runnableKey.indexOf(filter) < 0) {
+                    switch (filter) {
+                        case 'project' :
+                            runnableRst.push(service.project.getProjectById(params.project_id));
+                            runnableKey.push(filter);
+                            break;
+                        case 'tender_info' :
+                            runnableRst.push(service.tenderInfo.getTenderInfo(params.tender_id));
+                            runnableKey.push(filter);
+                            break;
+                        case 'ledger' :
+                            runnableRst.push(service.ledger.getData(params.tender_id, 0));
+                            runnableKey.push(filter);
+                            break;
+                        case 'stage_bills':
+                            runnableRst.push(service.stageBills.getLastestStageData(params.tender_id, params.stage_id));
+                            runnableKey.push(filter);
+                            break;
+                        case 'stage_bills_final':
+                            await checkStg(this.ctx, params);
+                            runnableRst.push(service.stageBillsFinal.getFinalDataEx(params.tender_id, params.stage_order));
+                            runnableKey.push(filter);
+                            break;
+                        case 'stage':
+                            runnableRst.push(service.stage.getStageById(params.stage_id));
+                            runnableKey.push(filter);
+                            break;
+                        case 'stage_pay':
+                            await checkStg(this.ctx, params);
+                            runnableRst.push(service.stagePay.getAuditorStageData(params.stage_id, params.stage_times, params.stage_order));
+                            runnableKey.push(filter);
+                            break;
+                        case 'mem_stage_im_zl':
+                            runnableRst.push(service.reportMemory.getStageImZlData(params.tender_id, params.stage_id, memFieldKeys[filter]));
+                            runnableKey.push(filter);
+                            break;
+                        case 'mem_month_progress':
+                            runnableRst.push(service.reportMemory.getMonthProgress(params.tender_id, memFieldKeys[filter]));
+                            runnableKey.push(filter);
+                            break;
+                        case 'mem_stage_bills':
+                            runnableRst.push(service.reportMemory.getStageBillsData(params.tender_id, params.stage_id, memFieldKeys[filter]));
+                            runnableKey.push(filter);
+                            break;
+                        case 'mem_stage_pos':
+                            runnableRst.push(service.reportMemory.getStagePosData(params.tender_id, params.stage_id, memFieldKeys[filter]));
+                            runnableKey.push(filter);
+                            break;
+                        case 'change':
+                            runnableRst.push(service.change.getListByStatus(params.tender_id, 3)); // 获取所有审核通过的变更主信息
+                            runnableKey.push(filter);
+                            break;
+                        case 'change_audit_list':
+                            runnableRst.push(service.changeAuditList.getChangeAuditBills(params.tender_id)); // 获取所有审核通过的变更清单
+                            runnableKey.push(filter);
+                            break;
+                        default:
+                            break;
+                    }
+                }
+            }
+            const queryRst = await Promise.all(runnableRst);
+            for (let idx = 0; idx < runnableKey.length; idx++) {
+                rst[runnableKey[idx]] = queryRst[idx];
+            }
+            for (const filter of filters) {
+                switch (filter) {
+                    case 'mem_stage_im_tz':
+                        rst[filter] = await service.reportMemory.getStageImTzData(params.tender_id, params.stage_id, memFieldKeys[filter]);
+                        break;
+                    case 'mem_stage_im_tz_bills':
+                        rst[filter] = await service.reportMemory.getStageImTzBillsData(params.tender_id, params.stage_id, memFieldKeys[filter]);
+                        break;
+                    default:
+                        break;
+                }
+            }
+            return rst;
+        }
+    }
+
+    return Report;
+};

+ 61 - 14
app/service/report_memory.js

@@ -8,6 +8,8 @@
  * @version
  */
 
+const _ = require('lodash');
+
 const StageIm = require('../lib/stage_im');
 const imType = require('../const/tender').imType;
 const audit = require('../const/audit');
@@ -21,6 +23,15 @@ const stageImVersion = '1.0';
 
 const Ledger = require('../lib/ledger');
 
+const curFields = ['contract_qty', 'contract_tp', 'qc_qty', 'qc_tp', 'gather_qty', 'gather_tp', 'postil'];
+const preFields = ['pre_contract_qty', 'pre_contract_tp', 'pre_qc_qty', 'pre_qc_tp', 'pre_gather_qty', 'pre_gather_tp'];
+const endFields = ['end_contract_qty', 'end_contract_tp', 'end_qc_qty', 'end_qc_tp', 'end_gather_qty', 'end_gather_tp'];
+const finalFields = ['final_tp', 'final_ratio'];
+
+const stageFields = curFields.concat(preFields, endFields, finalFields);
+const stageEndFields = preFields.concat(endFields, finalFields);
+const bglFields = ['qc_bgl_code'];
+
 module.exports = app => {
     class ReportMemory extends app.BaseService {
 
@@ -78,6 +89,13 @@ module.exports = app => {
             this.stageImData = null;
         }
 
+        _checkFieldsExist(source, check) {
+            for (const s of source) {
+                if (check.indexOf(s)) return true;
+            }
+            return false;
+        }
+
         // build-time: 162-384ms, redis-cache: 0-41ms, mysql + IO: 116-146ms
         // 一定程度上算是大Value缓存,数据多了以后:
         // 1. 达到redis内存阈值时,数据会swap到磁盘,此时将消耗IO时间
@@ -280,31 +298,58 @@ module.exports = app => {
             return monthProgress;
         }
 
+        async _calcBillsBgl() {
+            const helper = this.ctx.helper;
+            const tender = this.ctx.tender;
+            const stage = this.ctx.stage;
+            const bglData = this.ctx.stage.readOnly
+                ? await this.ctx.service.stageChange.getAuditorAllStageData(tender.id, stage.id, stage.curTimes, stage.curOrder)
+                : await this.ctx.service.stageChange.getLastestAllStageData(tender.id, stage.id);
+
+            for (const node of this.billsTree.nodes) {
+                node.qc_bgl_code = '';
+                if (node.children && node.children.length > 0) continue;
+
+                const nodeBgl = helper._.pullAllBy(bglData, [{lid: node.id}], 'lid');
+                const validBgl = helper._.filter(nodeBgl, function (x) {
+                    return !helper.checkZero(x.qty);
+                });
+                node.qc_bgl_code = helper._.uniq(helper._.map(validBgl, 'c_code')).join(';');
+            }
+        }
+
         async getStageBillsData(tid, sid, fields) {
             await this.ctx.service.tender.checkTender(tid);
             await this.ctx.service.stage.checkStage(sid);
 
             const billsData = await this.ctx.service.ledger.getData(this.ctx.tender.id);
-            if (this.ctx.stage.readOnly) {
-                const curStage = await this.ctx.service.stageBills.getAuditorStageData(this.ctx.tender.id,
-                    this.ctx.stage.id, this.ctx.stage.curTimes, this.ctx.stage.curOrder);
-                this.ctx.helper.assignRelaData(billsData, [
-                    {data: curStage, fields: ['contract_qty', 'contract_tp', 'qc_qty', 'qc_tp'], prefix: '', relaId: 'lid'}
-                ]);
-            } else {
-                const curStage = await this.ctx.service.stageBills.getLastestStageData(this.ctx.tender.id, this.ctx.stage.id);
+            if (this._checkFieldsExist(fields, stageFields)) {
+                if (this.ctx.stage.readOnly) {
+                    const curStage = await this.ctx.service.stageBills.getAuditorStageData(this.ctx.tender.id,
+                        this.ctx.stage.id, this.ctx.stage.curTimes, this.ctx.stage.curOrder);
+                    this.ctx.helper.assignRelaData(billsData, [
+                        {data: curStage, fields: ['contract_qty', 'contract_tp', 'qc_qty', 'qc_tp'], prefix: '', relaId: 'lid'}
+                    ]);
+                } else {
+                    const curStage = await this.ctx.service.stageBills.getLastestStageData(this.ctx.tender.id, this.ctx.stage.id);
+                    this.ctx.helper.assignRelaData(billsData, [
+                        {data: curStage, fields: ['contract_qty', 'contract_tp', 'qc_qty', 'qc_tp'], prefix: '', relaId: 'lid'}
+                    ]);
+                }
+            }
+            if (this._checkFieldsExist(fields, preFields)) {
+                const preStage = this.ctx.stage.order > 1 ? await this.ctx.service.stageBillsFinal.getFinalData(this.ctx.tender, this.ctx.stage.order - 1) : [];
                 this.ctx.helper.assignRelaData(billsData, [
-                    {data: curStage, fields: ['contract_qty', 'contract_tp', 'qc_qty', 'qc_tp'], prefix: '', relaId: 'lid'}
+                    {data: preStage, fields: ['contract_qty', 'contract_tp', 'qc_qty', 'qc_tp'], prefix: 'pre_', relaId: 'lid'}
                 ]);
             }
-            const preStage = this.ctx.stage.order > 1 ? await this.ctx.service.stageBillsFinal.getFinalData(this.ctx.tender, this.ctx.stage.order - 1) : [];
-
-            this.ctx.helper.assignRelaData(billsData, [
-                {data: preStage, fields: ['contract_qty', 'contract_tp', 'qc_qty', 'qc_tp'], prefix: 'pre_', relaId: 'lid'}
-            ]);
             this.billsTree.loadDatas(billsData);
             this.billsTree.calculateAll();
 
+            if (this._checkFieldsExist(fields, bglFields)) {
+                await this._calcBillsBgl();
+            }
+
             return this.billsTree.getDatas([
                 'id', 'tender_id', 'ledger_id', 'ledger_pid', 'level', 'order', 'full_path', 'is_leaf',
                 'code', 'b_code', 'name', 'unit', 'unit_price',
@@ -316,6 +361,8 @@ module.exports = app => {
                 'pre_contract_qty', 'pre_contract_tp', 'pre_qc_qty', 'pre_qc_tp', 'pre_gather_qty', 'pre_gather_tp',
                 'end_contract_qty', 'end_contract_tp', 'end_qc_qty', 'end_qc_tp', 'end_gather_qty', 'end_gather_tp',
                 'final_tp', 'final_ratio',
+                'qc_bgl_code',
+                'chapter',
             ]);
         }
 

+ 5 - 0
app/service/revise_audit.js

@@ -284,6 +284,11 @@ module.exports = app => {
                     } else {
                         // 同步修订信息
                         await transaction.update(this.ctx.service.ledgerRevise.tableName, {id: revise.id, status: checkType, end_time: time});
+                        // 最新一期跟台账相关的缓存数据应过期
+                        const lastStage = await this.ctx.service.stage.getLastestStage(revise.tid, true);
+                        const cacheTime = new Date();
+                        if (lastStage) await transaction.update(this.ctx.service.stage.tableName,
+                            {id: lastStage.id, cache_time_l: cacheTime, cache_time_r: cacheTime});
                         // 拷贝修订数据至台账
                         await this._replaceLedgerByRevise(transaction, revise);
                         const sum = await this.ctx.service.reviseBills.addUp({tender_id: revise.tid, is_leaf: true});

+ 0 - 1
app/service/stage.js

@@ -27,7 +27,6 @@ module.exports = app => {
             this.tableName = 'stage';
         }
 
-
         async checkStage(sid) {
             if (!this.ctx.stage) {
                 const status = auditConst.status;

+ 2 - 1
app/service/stage_bills.js

@@ -115,7 +115,8 @@ module.exports = app => {
         }
 
         async getStageUsedBills(tid, sid) {
-            const sql = 'SELECT Bills.lid, (Bills.contract_qty <> 0 or Bills.qc_qty <> 0) As used FROM ' + this.tableName + ' As Bills ' +
+            const sql = 'SELECT Bills.lid, ((not IsNull(Bills.contract_qty) and Bills.contract_qty <> 0) or (not IsNull(Bills.qc_qty) and Bills.qc_qty <> 0)) As used' +
+                '  FROM ' + this.tableName + ' As Bills ' +
                 '  INNER JOIN ( ' +
                 '    SELECT MAX(`times` * ' + timesLen + ' + `order`) As `progress`, `lid`, `sid` From ' + this.tableName +
                 '      WHERE tid = ? And sid = ?' +

+ 1 - 1
app/service/stage_pos.js

@@ -128,7 +128,7 @@ module.exports = app => {
             const self = this;
             const stagePos = await this.getLastestStageData2(tid, sid, where);
             const pids = this._.map(stagePos, function (sp) {
-                if (self.ctx.helper.checkZero(sp.contract_qty) || self.ctx.helper.checkZero(sp.qc_qty)) {
+                if (!self.ctx.helper.checkZero(sp.contract_qty) || !self.ctx.helper.checkZero(sp.qc_qty)) {
                     return sp.pid;
                 } else {
                     return -1;

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

@@ -64,9 +64,6 @@
                             <% } else { %>
                             <span class="<%- auditConst.auditStringClass[s.status] %>"><%- auditConst.auditString[s.status] %></span>
                             <% } %>
-                            <% if (s.user_id === ctx.session.sessionUser.accountId && s.order === stages.length) { %>
-                            <a href="#del-qi" class="btn btn-outline-danger btn-sm ml-1" data-toggle="modal" data-target="#del-qi">删除</a>
-                            <% } %>
                         </td>
                     </tr>
                     <% } %>

+ 0 - 22
app/view/measure/stage_modal.ejs

@@ -30,28 +30,6 @@
     </div>
 </div>
 <% } %>
-<% if (stages && stages.length >= 1) { %>
-<!--删除期-->
-<div class="modal fade" id="del-qi" data-backdrop="static">
-    <div class="modal-dialog" role="document">
-        <form class="modal-content" action="<%- preUrl + '/measure/stage/delete' %>" method="post">
-            <div class="modal-header">
-                <h5 class="modal-title">删除期</h5>
-            </div>
-            <div class="modal-body">
-                <h5>确认删除「第<%= stages.length %>期」?</h5>
-                <h5>删除后,数据无法恢复,请谨慎操作。</h5>
-            </div>
-            <div class="modal-footer">
-                <input type="hidden" name="stage_id" value="<%= stages[0].id %>">
-                <input type="hidden" name="_csrf" value="<%= ctx.csrf %>" />
-                <button type="button" class="btn btn-secondary btn-sm" data-dismiss="modal">取消</button>
-                <button type="submit" class="btn btn-danger btn-sm">确定删除</button>
-            </div>
-        </form>
-    </div>
-</div>
-<% } %>
 <!--审批流程/结果-->
 <div class="modal fade" id="sp-list" data-backdrop="static">
     <div class="modal-dialog modal-lg" role="document">

+ 1 - 1
app/view/setting/user_modal.ejs

@@ -105,7 +105,7 @@
                 </div>
                 <div class="form-group">
                     <label>认证手机</label>
-                    <input class="form-control" value="" class="auth-mobile" type="text" readonly>
+                    <input class="form-control" data-mobile="auth-mobile" value="" type="text" readonly>
                 </div>
                 <div class="form-group">
                     <label>单位名称<b class="text-danger">*</b></label>

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

@@ -25,4 +25,7 @@
     <% if (ctx.stage.auditors !== undefined && ctx.stage.auditors.length !== 0 && ctx.stage.auditors[ctx.stage.auditors.length-1].aid === ctx.session.sessionUser.accountId && ctx.stage.status === auditConst.status.checked && ctx.stage.order === ctx.stage.highOrder) { %>
         <a href="javascript: void(0);" data-toggle="modal" data-target="#sp-down-back" class="btn btn-warning btn-sm btn-block">重新审批</a>
     <% } %>
+    <% if (ctx.stage.user_id === ctx.session.sessionUser.accountId && ctx.stage.order === ctx.stage.highOrder) { %>
+        <a href="#del-qi" data-toggle="modal" data-target="#del-qi" class="btn btn-outline-danger btn-sm btn-block mt-5">删除本期</a>
+    <% } %>
 </div>

+ 22 - 0
app/view/stage/audit_modal.ejs

@@ -1220,3 +1220,25 @@
         </div>
     </div>
 <% } %>
+<% if (ctx.stage.user_id === ctx.session.sessionUser.accountId && ctx.stage.order === ctx.stage.highOrder) { %>
+<!--删除期-->
+<div class="modal fade" id="del-qi" data-backdrop="static">
+    <div class="modal-dialog" role="document">
+        <form class="modal-content" action='/tender/<%= ctx.tender.id %>/measure/stage/delete' method="post">
+            <div class="modal-header">
+                <h5 class="modal-title">删除期</h5>
+            </div>
+            <div class="modal-body">
+                <h5>确认删除「第<%= ctx.stage.order %>期」?</h5>
+                <h5>删除后,数据无法恢复,请谨慎操作。</h5>
+            </div>
+            <div class="modal-footer">
+                <input type="hidden" name="stage_id" value="<%= ctx.stage.id %>">
+                <input type="hidden" name="_csrf" value="<%= ctx.csrf %>" />
+                <button type="button" class="btn btn-secondary btn-sm" data-dismiss="modal">取消</button>
+                <button type="submit" class="btn btn-danger btn-sm">确定删除</button>
+            </div>
+        </form>
+    </div>
+</div>
+<% } %>

+ 23 - 6
config/config.local.js

@@ -13,13 +13,13 @@ module.exports = appInfo => {
     config.mysql = {
         client: {
             // host
-            host: '127.0.0.1',
+            host: '192.168.1.76',
             // 端口号
             port: '3306',
             // 用户名
-            user: 'root',
+            user: 'zh_dev',
             // 密码
-            password: 'root',
+            password: 'zongheng2019',
             // 数据库名
             database: 'calculation',
         },
@@ -34,9 +34,9 @@ module.exports = appInfo => {
     // redis设置
     config.redis = {
         client: {
-            host: '127.0.0.1',
+            host: '192.168.1.76',
             port: '6379',
-            password: 'test',
+            password: 'zongheng2019',
             db: '0',
         },
         agent: true,
@@ -65,8 +65,25 @@ module.exports = appInfo => {
         fileSize: '30mb',
     };
 
+    // session配置
+    config.session = {
+        key: 'ZHC_SESS',
+        maxAge: 7 * 24 * 3600 * 1000, // 7天
+        httpOnly: true,
+        encrypt: true,
+        //renew: true, // session临近过期更新过期时间
+        rolling: true, // 每次都更新session有效期
+    };
+
     // 是否压缩替换前端js
-    config.min = true;
+    config.min = false;
+
+    config.logger = {
+        consoleLevel: 'WARN',
+        disableConsoleAfterReady: false,
+    };
+
+    config.is_debug = true;
 
     return config;
 };

+ 3 - 96
test/app/controller/report_controller.test.js

@@ -12,99 +12,6 @@ const { app, assert } = require('egg-mock/bootstrap');
 const mockData = {};
 const path = require('path');
 
-async function getReportData(ctx, params, filters, memFieldKeys) {
-    const rst = {};
-    const runnableRst = [];
-    const runnableKey = []; // 这个配合runnableRst用,未来考虑并行查询优化
-    // console.log('params');
-    // console.log(params);
-    // console.log('memFieldKeys');
-    // console.log(memFieldKeys);
-    for (const filter of filters) {
-        if (runnableKey.indexOf(filter) < 0) {
-            switch (filter) {
-                case 'project' :
-                    runnableRst.push(ctx.service.project.getProjectById(params.project_id));
-                    runnableKey.push('project');
-                    break;
-                case 'tender_info' :
-                    runnableRst.push(ctx.service.tenderInfo.getTenderInfo(params.tender_id));
-                    runnableKey.push('tender_info');
-                    break;
-                case 'ledger' :
-                    runnableRst.push(ctx.service.ledger.getData(params.tender_id, 0));
-                    runnableKey.push('ledger');
-                    break;
-                case 'stage_bills':
-                    runnableRst.push(ctx.service.stageBills.getLastestStageData(params.tender_id, params.stage_id));
-                    runnableKey.push('stage_bills');
-                    break;
-                case 'stage_bills_final':
-                    await checkStg(ctx, params);
-                    runnableRst.push(ctx.service.stageBillsFinal.getFinalDataEx(params.tender_id, params.stage_order));
-                    runnableKey.push('stage_bills_final');
-                    break;
-                case 'stage':
-                    runnableRst.push(ctx.service.stage.getStageById(params.stage_id));
-                    runnableKey.push('stage');
-                    break;
-                case 'stage_pay':
-                    await checkStg(ctx, params);
-                    runnableRst.push(ctx.service.stagePay.getAuditorStageData(params.stage_id, params.stage_times, params.stage_order));
-                    runnableKey.push('stage_pay');
-                    break;
-                case 'mem_stage_im_zl':
-                    // memFieldKeys[filter]
-                    runnableRst.push(ctx.service.reportMemory.getStageImZlData(params.tender_id, params.stage_id));
-                    runnableKey.push('mem_stage_im_zl');
-                    break;
-                case 'mem_month_progress':
-                    runnableRst.push(ctx.service.reportMemory.getMonthProgress(params.tender_id));
-                    runnableKey.push('mem_month_progress');
-                    break;
-                case 'mem_stage_bills':
-                    runnableRst.push(ctx.service.reportMemory.getStageBillsData(params.tender_id, params.stage_id));
-                    runnableKey.push('mem_stage_pos');
-                    break;
-                case 'mem_stage_pos':
-                    runnableRst.push(ctx.service.reportMemory.getStagePosData(params.tender_id, params.stage_id));
-                    runnableKey.push('mem_stage_pos');
-                    break;
-                case 'change':
-                    runnableRst.push(ctx.service.change.getListByStatus(params.tender_id, 3)); // 获取所有审核通过的变更主信息
-                    runnableKey.push('change');
-                    break;
-                case 'change_audit_list':
-                    runnableRst.push(ctx.service.changeAuditList.getChangeAuditBills(params.tender_id)); // 获取所有审核通过的变更清单
-                    runnableKey.push('change_audit_list');
-                    break;
-                default:
-                    break;
-            }
-        }
-    }
-    const queryRst = await Promise.all(runnableRst);
-    for (let idx = 0; idx < runnableKey.length; idx++) {
-        rst[runnableKey[idx]] = queryRst[idx];
-        if (['change', 'change_audit_list'].indexOf(runnableKey[idx]) >= 0) {
-            await ctx.helper.saveBufferFile(JSON.stringify(queryRst[idx],"","\t"), ctx.app.baseDir + '/' + runnableKey[idx] +'.json');
-        }
-    }
-    for (const filter of filters) {
-        switch (filter) {
-            case 'mem_stage_im_tz':
-                rst[filter] = await ctx.service.reportMemory.getStageImTzData(params.tender_id, params.stage_id);
-                break;
-            case 'mem_stage_im_tz_bills':
-                rst[filter] = await ctx.service.reportMemory.getStageImTzBillsData(params.tender_id, params.stage_id);
-                break;
-            default:
-                break;
-        }
-    }
-    return rst;
-}
-
 describe('test/app/service/report_memory.test.js', () => {
     // 准备测试数据
     before(function* () {
@@ -132,11 +39,11 @@ describe('test/app/service/report_memory.test.js', () => {
         // test12 - 第6期
         const stage = yield ctx.service.stage.getDataByCondition({tid: 12, order: 6});
         const params = {
-            tid: stage.tid,
-            sid: stage.sid,
+            tender_id: stage.tid,
+            stage_id: stage.id,
         };
         const filters = ['change', 'change_audit_list'];
-        const result = yield getReportData(ctx, params, filters);
+        const result = yield ctx.service.report.getReportData(ctx, params, filters);
         const savePath = path.join(ctx.app.baseDir, 'report_temp');
         yield ctx.helper.recursiveMkdirSync(savePath);
         for (const table in result) {

+ 3 - 0
test/app/extend/helper.test.js

@@ -123,6 +123,9 @@ describe('test/app/extend/helper.test.js', () => {
         assert(ctx.helper.compareCode('1.1.1', '1.1.2', '.') < 0);
         assert(ctx.helper.compareCode('1.1.3', '1.2.2', '.') < 0);
         assert(ctx.helper.compareCode('2.4.3', '3.1.2', '.') < 0);
+        // 测试空编号
+        assert(ctx.helper.compareCode('', '1-1') < 0);
+        assert(ctx.helper.compareCode('1-1', '') > 0);
     });
 
     it('test arithmetic', function () {

+ 113 - 0
test/app/lib/rpt_data_analysis.test.js

@@ -0,0 +1,113 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date
+ * @version
+ */
+
+const { app, assert } = require('egg-mock/bootstrap');
+const mockData = {};
+const path = require('path');
+
+const reportDataAnalysis = require('../../../app/lib/rpt_data_analysis');
+
+describe('test/app/service/report_memory.test.js', () => {
+    // 准备测试数据
+    before(function* () {
+        const ctx = app.mockContext();
+        // 模拟登录session
+        // const postData = {
+        //     account: 'fuqingqing',
+        //     project: 'P0505',
+        //     project_password: '123456',
+        // };
+        const postData = {
+            account: '734406061@qq.com',
+            project: 'T201711273363',
+            project_password: 'mai654321',
+        };
+        ctx.session = {};
+        const loginResult = yield ctx.service.projectAccount.accountLogin(postData, 2);
+        assert(loginResult);
+        mockData.session = ctx.session;
+    });
+    // 数据
+    it('test changeSort', function* () {
+        const ctx = app.mockContext(mockData);
+
+        // test12 - 第6期
+        const stage = yield ctx.service.stage.getDataByCondition({tid: 12, order: 6});
+        const params = {
+            tender_id: stage.tid,
+            stage_id: stage.id,
+        };
+        const filters = ['change', 'change_audit_list'];
+        const data = yield ctx.service.report.getReportData(params, filters);
+        reportDataAnalysis.changeSort.fun(ctx, data);
+        assert(data.change[0].code === 'test7-BG-001');
+        assert(data.change[2].code === 'test7-BG-003');
+        const changeCid = ctx.helper._.map(data.change, 'cid');
+        const changeBillsCid = ctx.helper._.uniq(ctx.helper._.map(data.change_audit_list, 'cid'));
+        assert(changeCid.length === changeBillsCid.length);
+        changeCid.forEach(function (a, i) {
+            assert(a === changeBillsCid[i]);
+        });
+    });
+    it('test gatherGcl && sortGcl', function* () {
+        const ctx = app.mockContext(mockData);
+
+        // test12 - 第6期
+        const stage = yield ctx.service.stage.getDataByCondition({tid: 12, order: 6});
+        const params = {
+            tender_id: stage.tid,
+            stage_id: stage.id,
+        };
+        const filters = ['mem_stage_bills'];
+        const data = yield ctx.service.report.getReportData(params, filters, {
+            mem_stage_bills: [
+                'id', 'tender_id', 'ledger_id', 'ledger_pid', 'level', 'order', 'full_path', 'is_leaf',
+                'code', 'b_code', 'name', 'unit', 'unit_price',
+                'deal_qty', 'deal_tp',
+                'sgfh_qty', 'sgfh_tp', 'sjcl_qty', 'sjcl_tp', 'qtcl_qty', 'qtcl_tp', 'quantity', 'total_price',
+                'dgn_qty1', 'dgn_qty2',
+                'drawing_code', 'memo', 'node_type', 'is_tp',
+                'contract_qty', 'contract_tp', 'qc_qty', 'qc_tp', 'gather_qty', 'gather_tp', 'postil',
+                'pre_contract_qty', 'pre_contract_tp', 'pre_qc_qty', 'pre_qc_tp', 'pre_gather_qty', 'pre_gather_tp',
+                'end_contract_qty', 'end_contract_tp', 'end_qc_qty', 'end_qc_tp', 'end_gather_qty', 'end_gather_tp',
+                'final_tp', 'final_ratio',
+                'qc_bgl_code',
+                'chapter',
+            ]
+        });
+        assert(data.mem_stage_bills.length === 216);
+        reportDataAnalysis.gatherGcl.fun(ctx, data, [
+            {field: 'b_code', table: 'mem_stage_bills'},
+            {field: 'name', table: 'mem_stage_bills'},
+            {field: 'unit', table: 'mem_stage_bills'},
+            {field: 'unit_price', table: 'mem_stage_bills'},
+            {field: 'is_leaf', table: 'mem_stage_bills'},
+        ]);
+        assert(data.mem_stage_bills.length === 43);
+        reportDataAnalysis.sortGcl.fun(ctx, data, [
+            {field: 'b_code', table: 'mem_stage_bills'},
+        ]);
+        const codeIndex = ctx.helper._.map(data.mem_stage_bills, 'b_code');
+        const codeIndex100 = ['103-1', '103-2', '103-3-a', '103-3-b', '103-4', '104-1'];
+        const codeIndex200 = [
+            '203-1-a', '203-1-b', '203-1-d', '204-1-b', '204-1-g-4', '204-1-j', '205-1-o-1', '206-2',
+            '207-1-b', '207-2-a', '207-3-a', '207-4-a', '208-1-a', '208-3-a', '208-3-c', '208-4-b-1', '209-1-a',
+        ];
+        const codeIndex400 = [
+            '403-1-a', '403-1-b', '403-2-a', '403-2-b', '403-3-a', '403-3-b', '403-4-a', '403-4-b',
+            '405-1-b-5', '405-1-b-6', '410-1-b', '410-1-c', '410-2-b', '410-2-c', '410-6-c',
+            '411-5', '411-7-a', '411-8-a', '413-1-a', '417-5-a'
+        ];
+        let codeResult = codeIndex100.concat(codeIndex200, codeIndex400);
+        codeIndex.forEach(function (a, i) {
+            assert(a === codeResult[i]);
+        });
+    });
+});

+ 53 - 0
test/app/service/report.test.js

@@ -0,0 +1,53 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date
+ * @version
+ */
+
+const { app, assert } = require('egg-mock/bootstrap');
+const mockData = {};
+const path = require('path');
+
+describe('test/app/service/report_memory.test.js', () => {
+    // 准备测试数据
+    before(function* () {
+        const ctx = app.mockContext();
+        // 模拟登录session
+        const postData = {
+            account: '734406061@qq.com',
+            project: 'T201711273363',
+            project_password: 'mai654321',
+        };
+        // const postData = {
+        //     account: 'chente',
+        //     project: 'T201711273363',
+        //     project_password: '123456',
+        // };
+        ctx.session = {};
+        const loginResult = yield ctx.service.projectAccount.accountLogin(postData, 2);
+        assert(loginResult);
+        mockData.session = ctx.session;
+    });
+    // 数据
+    it('test getReportData', function* () {
+        const ctx = app.mockContext(mockData);
+
+        // test12 - 第6期
+        const stage = yield ctx.service.stage.getDataByCondition({tid: 12, order: 6});
+        const params = {
+            tender_id: stage.tid,
+            stage_id: stage.id,
+        };
+        const filters = ['change', 'change_audit_list'];
+        const result = yield ctx.service.report.getReportData(ctx, params, filters);
+        const savePath = path.join(ctx.app.baseDir, 'report_temp');
+        yield ctx.helper.recursiveMkdirSync(savePath);
+        for (const table in result) {
+            yield ctx.helper.saveBufferFile(JSON.stringify(result[table],"","\t"), path.join(savePath, table + '.json'));
+        }
+    });
+});

+ 18 - 1
test/app/service/report_memory.test.js

@@ -91,7 +91,20 @@ describe('test/app/service/report_memory.test.js', () => {
 
         // test12 - 第6期
         const stage = yield ctx.service.stage.getDataByCondition({tid: 12, order: 6});
-        const mainData = yield ctx.service.reportMemory.getStageBillsData(12, stage.id);
+        const mainData = yield ctx.service.reportMemory.getStageBillsData(12, stage.id, [
+            'id', 'tender_id', 'ledger_id', 'ledger_pid', 'level', 'order', 'full_path', 'is_leaf',
+            'code', 'b_code', 'name', 'unit', 'unit_price',
+            'deal_qty', 'deal_tp',
+            'sgfh_qty', 'sgfh_tp', 'sjcl_qty', 'sjcl_tp', 'qtcl_qty', 'qtcl_tp', 'quantity', 'total_price',
+            'dgn_qty1', 'dgn_qty2',
+            'drawing_code', 'memo', 'node_type', 'is_tp',
+            'contract_qty', 'contract_tp', 'qc_qty', 'qc_tp', 'gather_qty', 'gather_tp', 'postil',
+            'pre_contract_qty', 'pre_contract_tp', 'pre_qc_qty', 'pre_qc_tp', 'pre_gather_qty', 'pre_gather_tp',
+            'end_contract_qty', 'end_contract_tp', 'end_qc_qty', 'end_qc_tp', 'end_gather_qty', 'end_gather_tp',
+            'final_tp', 'final_ratio',
+            'qc_bgl_code',
+            'chapter',
+        ]);
         if (mainData instanceof Array) {
             yield ctx.helper.saveBufferFile(JSON.stringify(mainData,"","\t"), ctx.app.baseDir + '/mem_stage_bills.json');
             const tableDefine = {};
@@ -160,6 +173,10 @@ describe('test/app/service/report_memory.test.js', () => {
             addFields(tableDefine, '(台账 + 截止本期变更)-金额', 'final_tp', dataType.currency);
             addFields(tableDefine, '截止本期完成率(%)', 'final_ratio', dataType.double);
 
+            addFields(tableDefine, '本期-数量变更-变更令', 'qc_bgl_code', dataType.str);
+
+            addFields(tableDefine, '章节编号', 'chapter', dataType.str);
+
             delete tableDefine.ID;
             delete tableDefine.key;
             yield ctx.helper.saveBufferFile(JSON.stringify(tableDefine,"","\t"), ctx.app.baseDir + '/mem_stage_bills_define.json');