Browse Source

1. 概算投资 v0.1
2. 预付款,导航栏缩小

MaiXinRong 3 years ago
parent
commit
db80310d7a

+ 2 - 0
app.js

@@ -16,6 +16,7 @@ const _ = require('lodash');
 const BaseService = require('./app/base/base_service');
 const BaseTreeService = require('./app/base/base_tree_service');
 const BaseBillsService = require('./app/base/base_bills_service');
+const BaseBudgetService = require('./app/base/base_budget_service');
 const BaseController = require('./app/base/base_controller');
 
 const menu = require('./config/menu');
@@ -31,6 +32,7 @@ module.exports = app => {
     app.BaseService = BaseService;
     app.BaseTreeService = BaseTreeService;
     app.BaseBillsService = BaseBillsService;
+    app.BaseBudgetService = BaseBudgetService;
 
     // 控制器基类
     app.BaseController = BaseController;

+ 66 - 0
app/base/base_budget_service.js

@@ -0,0 +1,66 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date
+ * @version
+ */
+
+
+const TreeService = require('./base_tree_service');
+
+class BaseBudget extends TreeService {
+
+    /**
+     * 构造函数
+     *
+     * @param {Object} ctx - egg全局变量
+     * @param {String} tableName - 表名
+     * @return {void}
+     */
+    constructor(ctx, setting, tablename) {
+        super(ctx, {
+            mid: 'bid',
+            kid: 'tree_id',
+            pid: 'tree_pid',
+            order: 'order',
+            level: 'level',
+            isLeaf: 'is_leaf',
+            fullPath: 'full_path',
+            keyPre: setting.keyPre,
+            uuid: true,
+        });
+        this.tableName = tablename;
+    }
+
+    async initByTemplate(conn, budgetId, templateId){
+        if (!conn) throw '内部错误';
+        if (budgetId <= 0) throw '概算项目id错误';
+        const data = await this.ctx.service.tenderNodeTemplate.getData(templateId);
+        if (!data.length) throw '模板数据有误';
+
+        // 整理数据
+        const insertData = [];
+        for (const tmp of data) {
+            insertData.push({
+                id: this.uuid.v4(),
+                bid: budgetId,
+                tree_id: tmp.template_id,
+                tree_pid: tmp.pid,
+                level: tmp.level,
+                order: tmp.order,
+                full_path: tmp.full_path,
+                is_leaf: tmp.is_leaf,
+                code: tmp.code,
+                name: tmp.name,
+                node_type: tmp.node_type,
+            });
+        }
+        const operate = await conn.insert(this.tableName, insertData);
+        return operate.affectedRows === data.length;
+    }
+}
+
+module.exports = BaseBudget;

+ 147 - 0
app/controller/budget_controller.js

@@ -0,0 +1,147 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date 2021/10/27
+ * @version
+ */
+
+module.exports = app => {
+    class BudgetController extends app.BaseController {
+
+        /**
+         * 概算投资
+         *
+         * @param ctx
+         * @returns {Promise<void>}
+         */
+        async list(ctx) {
+            try {
+                const renderData = {
+                    jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.budget.list),
+                };
+                renderData.budgetList = await ctx.service.budget.getAllDataByCondition({
+                    where: { pid: ctx.session.sessionProject.id },
+                    orders: [['name', 'asc']],
+                });
+                renderData.budgetStd = await ctx.service.budgetStd.getDataByProjectId(ctx.session.sessionProject.id);
+                await this.layout('budget/list.ejs', renderData, 'budget/list_modal.ejs');
+            } catch (err) {
+                ctx.log(err);
+            }
+        }
+
+        async add(ctx) {
+            try {
+                const data = JSON.parse(ctx.request.body.data);
+                if (!data.name || data.name.length > 100) throw '项目名称有误';
+                if (!data.std_id) throw '概预算标准有误';
+                const result = await ctx.service.budget.add(data);
+                ctx.body = { err: 0, msg: '', data: result };
+            } catch (err) {
+                ctx.log(err);
+                ctx.ajaxErrorBody(err, '新建项目失败');
+            }
+        }
+
+        async del(ctx) {
+
+        }
+
+        async save(ctx) {
+
+        }
+
+        async compare(ctx) {
+            try {
+                const renderData = {
+                    jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.budget.compare),
+                };
+                await this.layout('budget/compare.ejs', renderData, 'budget/compare_modal.ejs');
+            } catch (err) {
+                ctx.log(err);
+            }
+        }
+
+        _getSpreadSetting(type) {
+            const spreadSetting = {
+                cols: [
+                    {title: '项目节编号', colSpan: '1', rowSpan: '2', field: 'code', hAlign: 0, width: 150, formatter: '@', cellType: 'tree'},
+                    {title: '清单编号', colSpan: '1', rowSpan: '2', field: 'b_code', hAlign: 0, width: 230, formatter: '@'},
+                    {title: '名称', colSpan: '1', rowSpan: '2', field: 'name', hAlign: 0, width: 230, formatter: '@'},
+                    {title: '单位', colSpan: '1', rowSpan: '2', field: 'unit', hAlign: 1, width: 50, formatter: '@', cellType: 'unit'},
+                    {title: '清单数量', colSpan: '1', rowSpan: '2', field: 'quantity', hAlign: 2, width: 80, type: 'Number'},
+                    {title: '设计数量|数量1', colSpan: '2|1', rowSpan: '1|1', field: 'dgn_qty1', hAlign: 2, width: 80, type: 'Number'},
+                    {title: '|数量2', colSpan: '|1', rowSpan: '|1', field: 'dgn_qty2', hAlign: 2, width: 80, type: 'Number'},
+                    {title: '经济指标', colSpan: '1', rowSpan: '2', field: 'dgn_price', hAlign: 2, width: 80, type: 'Number'},
+                    {title: '金额', colSpan: '1', rowSpan: '2', field: 'total_price', hAlign: 2, width: 80, type: 'Number'},
+                    {title: '图册号', colSpan: '1', rowSpan: '2', field: 'drawing_code', hAlign: 0, width: 100, formatter: '@'},
+                    {title: '备注', colSpan: '1', rowSpan: '2', field: 'memo', hAlign: 0, width: 100, formatter: '@'},
+                ],
+                emptyRows: 3,
+                headRows: 2,
+                headRowHeight: [25, 25],
+                defaultRowHeight: 21,
+                headerFont: '12px 微软雅黑',
+                font: '12px 微软雅黑',
+                readOnly: true,
+            };
+            // todo 根据设置判断预算是否需要清单
+            if (type !== 'yu') {
+                spreadSetting.cols = spreadSetting.cols.filter(x => {
+                    return ['b_code', 'quantity'].indexOf(x.field) < 0;
+                });
+            }
+            return spreadSetting;
+        }
+        _getRelaService(type) {
+            switch(type) {
+                case 'gu': return ctx.serivce.budgetGu;
+                case 'gai': return ctx.serivce.budgetGai;
+                case 'yu': return ctx.serivce.budgetYu;
+                default: return null;
+            }
+        }
+
+
+        async detail(ctx) {
+            try {
+                const renderData = {
+                    spreadSetting: this._getSpreadSetting(ctx.params.btype),
+                    jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.budget.detail),
+                    needGcl: ctx.params.btype === 'yu',
+                };
+                [renderData.stdBills, renderData.stdChapters] = await ctx.service.budgetStd.getStdList(ctx.budget.std_id, ctx.params.btype);
+                await this.layout('budget/detail.ejs', renderData, 'budget/detail_modal.ejs');
+            } catch (err) {
+                ctx.log(err);
+            }
+        }
+
+        async detailLoad(ctx) {
+            try {
+                const relaService = this._getRelaService(ctx.params.btype);
+                ctx.body = {
+                    err: 0,
+                    msg: '',
+                    data: await relaService.getData(ctx.budget.id),
+                }
+            } catch (err) {
+                ctx.log(err);
+                ctx.ajaxErrorBody(err, '获取数据错误');
+            }
+        }
+
+        async detailUpdate(ctx) {
+
+        }
+
+        async detailUploadExcel(ctx) {
+
+        }
+    }
+
+    return BudgetController;
+};

+ 37 - 0
app/middleware/budget_check.js

@@ -0,0 +1,37 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date
+ * @version
+ */
+
+module.exports = options => {
+    /**
+     * 标段校验 中间件
+     * 1. 读取标段数据(包括属性)
+     * 2. 检验用户是否可见标段(不校验具体权限)
+     *
+     * @param {function} next - 中间件继续执行的方法
+     * @return {void}
+     */
+    return function* budgetCheck(next) {
+        try {
+            // 读取标段数据
+            const id = parseInt(this.params.id);
+            if (!id) throw '参数错误';
+            this.budget = yield this.service.budget.getDataById(id);
+            if (!this.budget) throw '项目不存在';
+            yield next;
+        } catch (err) {
+            this.log(err);
+            if (this.helper.isAjax(this.request)) {
+                this.ajaxErrorBody(err, '概算投资项目未知错误');
+            } else {
+                this.postError(err, '概算投资项目未知错误');
+            }
+        }
+    };
+};

+ 16 - 1
app/public/js/advance.js

@@ -32,7 +32,22 @@ $(document).ready(function () {
     //     })
     //     return false
     // })
-
+    $.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');
+            } else {
+                $('.panel-title').removeClass('fluid');
+                $('#sub-menu').addClass('panel-sidebar');
+            }
+            autoFlashHeight();
+        }
+    });
     $('.ml-auto').submit(function () {
         if (!advancePayTotal) {
             $('#erro').modal('show');

+ 18 - 1
app/public/js/advance_audit.js

@@ -9,7 +9,24 @@
  */
 
 $(document).ready(function () {
-    autoFlashHeight()
+    autoFlashHeight();
+
+    $.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');
+            } else {
+                $('.panel-title').removeClass('fluid');
+                $('#sub-menu').addClass('panel-sidebar');
+            }
+            autoFlashHeight();
+        }
+    });
 
     const decimal = 2
     let oldVal = null

+ 61 - 0
app/public/js/budget_compare.js

@@ -0,0 +1,61 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date
+ * @version
+ */
+
+$(document).ready(() => {
+    autoFlashHeight();
+    const compareSpread = SpreadJsObj.createNewSpread($('#cost-compare')[0]);
+    const compareSheet = compareSpread.getActiveSheet();
+
+    const spreadSetting = {
+        cols: [
+            {title: '项目节编号', colSpan: '1', rowSpan: '2', field: 'code', hAlign: 0, width: 150, formatter: '@', cellType: 'tree'},
+            {title: '名称', colSpan: '1', rowSpan: '2', field: 'name', hAlign: 0, width: 230, formatter: '@'},
+            {title: '单位', colSpan: '1', rowSpan: '2', field: 'unit', hAlign: 1, width: 50, formatter: '@', cellType: 'unit'},
+            {title: '投资估算|数量1/数量2', colSpan: '3|1', rowSpan: '1|1', field: 'gu_qty', hAlign: 2, width: 80, type: 'Number'},
+            {title: '|经济指标', colSpan: '|1', rowSpan: '|1', field: 'gu_dgn_price', hAlign: 2, width: 80, type: 'Number'},
+            {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'gu_tp', hAlign: 2, width: 80, type: 'Number'},
+            {title: '初步概算|数量1/数量2', colSpan: '3|1', rowSpan: '1|1', field: 'gai_qty', hAlign: 2, width: 80, type: 'Number'},
+            {title: '|经济指标', colSpan: '|1', rowSpan: '|1', field: 'gu_dgn_price', hAlign: 2, width: 80, type: 'Number'},
+            {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'gai_tp', hAlign: 2, width: 80, type: 'Number'},
+            {title: '施工图预算|数量1/数量2', colSpan: '3|1', rowSpan: '1|1', field: 'yu_qty', hAlign: 2, width: 80, type: 'Number'},
+            {title: '|经济指标', colSpan: '|1', rowSpan: '|1', field: 'gu_dgn_price', hAlign: 2, width: 80, type: 'Number'},
+            {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'yu_tp', hAlign: 2, width: 80, type: 'Number'},
+            {title: '决算|数量1/数量2', colSpan: '3|1', rowSpan: '1|1', field: 'final_qty', hAlign: 2, width: 80, type: 'Number'},
+            {title: '|经济指标', colSpan: '|1', rowSpan: '|1', field: 'gu_dgn_price', hAlign: 2, width: 80, type: 'Number'},
+            {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'final_tp', hAlign: 2, width: 80, type: 'Number'},
+        ],
+        emptyRows: 3,
+        headRows: 2,
+        headRowHeight: [25, 25],
+        defaultRowHeight: 21,
+        headerFont: '12px 微软雅黑',
+        font: '12px 微软雅黑',
+        readOnly: true,
+    };
+    SpreadJsObj.initSheet(compareSheet, spreadSetting);
+
+    $.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');
+            } else {
+                $('.panel-title').removeClass('fluid');
+                $('#sub-menu').addClass('panel-sidebar');
+            }
+            autoFlashHeight();
+            compareSpread.refresh();
+        }
+    });
+});

+ 33 - 0
app/public/js/budget_detail.js

@@ -0,0 +1,33 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date
+ * @version
+ */
+
+$(document).ready(() => {
+    autoFlashHeight();
+    const budgetSpread = SpreadJsObj.createNewSpread($('#budget-spread')[0]);
+    const budgetSheet = budgetSpread.getActiveSheet();
+    SpreadJsObj.initSheet(budgetSheet, spreadSetting);
+
+    $.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');
+            } else {
+                $('.panel-title').removeClass('fluid');
+                $('#sub-menu').addClass('panel-sidebar');
+            }
+            autoFlashHeight();
+        }
+    });
+});

+ 28 - 0
app/public/js/budget_list.js

@@ -0,0 +1,28 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date
+ * @version
+ */
+
+const budgetNameChange = function (obj) {
+    if (obj.value.length > 100) {
+        obj.classList.add('is-invalid');
+        $('.invalid-feedback').show();
+    } else {
+        obj.classList.remove('is-invalid');
+        $('.invalid-feedback').hide();
+    }
+};
+
+const addBudget = function () {
+    const name = $('#add-budget-name').val();
+    if (!name || name.length > 100) return;
+    const std_id = parseInt($('[name=std_id]:checked').val());
+    postData('/budget/add', { name, std_id }, function () {
+        window.location.reload();
+    });
+};

+ 12 - 0
app/router.js

@@ -29,6 +29,7 @@ module.exports = app => {
     const scheduleCheck = app.middlewares.scheduleCheck();
     // 修订
     const reviseCheck = app.middlewares.reviseCheck();
+    const budgetCheck = app.middlewares.budgetCheck();
     // 登入登出相关
     app.get('/login', 'loginController.index');
     app.get('/login/port', api2otherCheck, 'loginController.port');
@@ -573,4 +574,15 @@ module.exports = app => {
 
     // 决策大屏
     app.get('/datacollect', sessionAuth, 'datacollectController.index');
+
+    // 概算投资
+    app.get('/budget', sessionAuth, 'budgetController.list');
+    app.post('/budget/add', sessionAuth, 'budgetController.add');
+    app.post('/budget/del', sessionAuth, 'budgetController.del');
+    app.post('/budget/save', sessionAuth, 'budgetController.save');
+    app.get('/budget/:id/compare', sessionAuth, budgetCheck, 'budgetController.compare');
+    app.get('/budget/:id/:btype', sessionAuth, budgetCheck, 'budgetController.detail');
+    app.post('/budget/:id/:btype/load', sessionAuth, budgetCheck, 'budgetController.detailLoad');
+    app.post('/budget/:id/:btype/update', sessionAuth, tenderCheck, uncheckTenderCheck, 'budgetController.detailUpdate');
+    app.post('/budget/:id/:btype/upload-excel', sessionAuth, tenderCheck, uncheckTenderCheck, 'budgetController.detailUploadExcel');
 };

+ 140 - 0
app/service/budget.js

@@ -0,0 +1,140 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date 2021/11/9
+ * @version
+ */
+
+
+module.exports = app => {
+
+    class Budget extends app.BaseService {
+
+        /**
+         * 构造函数
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        constructor(ctx) {
+            super(ctx);
+            this.tableName = 'budget';
+        }
+
+        /**
+         * 数据规则
+         *
+         * @param {String} scene - 场景
+         * @return {Object} - 返回数据规则
+         */
+        rule(scene) {
+            let rule = {};
+            switch (scene) {
+                case 'add':
+                    rule = {
+                        name: { type: 'string', required: true, min: 2 },
+                        std_id: { type: 'string', required: true, min: 1 },
+                    };
+                    break;
+                case 'save':
+                    rule = {
+                        name: { type: 'string', required: true, min: 2, max: 100, },
+                    };
+                default:
+                    break;
+            }
+
+            return rule;
+        }
+
+
+        /**
+         * 新增标段
+         *
+         * @param {Object} data - 提交的数据
+         * @return {Boolean} - 返回新增结果
+         */
+        async add(data) {
+            const budgetStd = await this.ctx.service.budgetStd.getDataById(data.std_id);
+            if (!budgetStd) throw '选择的概算标准不存在,请刷新页面重试';
+
+            const conn = await this.db.beginTransaction();
+            try {
+                // 获取当前用户信息
+                const sessionUser = this.ctx.session.sessionUser;
+                // 获取当前项目信息
+                const sessionProject = this.ctx.session.sessionProject;
+
+                const insertData = {
+                    pid: sessionProject.id, user_id: sessionUser.accountId, in_time: new Date(),
+                    name: data.name, std_id: data.std_id,
+                };
+                const operate = await conn.insert(this.tableName, insertData);
+
+                if (operate.insertId === 0) throw '新增标段数据失败';
+
+                // 获取合同支付模板 并添加到标段
+                await this.ctx.service.budgetGu.initByTemplate(conn, operate.insertId, budgetStd.gu_template_id);
+                await this.ctx.service.budgetGai.initByTemplate(conn, operate.insertId, budgetStd.gu_template_id);
+                await this.ctx.service.budgetYu.initByTemplate(conn, operate.insertId, budgetStd.gu_template_id);
+                await conn.commit();
+                return await this.getDataById(operate.insertId);
+            } catch (error) {
+                await conn.rollback();
+                throw error;
+            }
+        }
+
+        /**
+         * 保存标段
+         *
+         * @param {Number} id
+         * @param {Object} postData - 表单post过来的数
+         * @return {Boolean} - 返回执行结果
+         */
+        async save(id, postData) {
+            const rowData = { id, name: postData.name };
+            const result = await this.db.update(this.tableName, rowData);
+            return result.affectedRows > 0;
+        }
+
+        /**
+         * 假删除
+         *
+         * @param {Number} id - 删除的id
+         * @return {Boolean} - 删除结果
+         */
+        async deleteBudget(id) {
+            const updateData = { id, status: this.status.DISABLE };
+            const result = await this.db.update(this.tableName, updateData);
+
+            return result.affectedRows > 0;
+        }
+
+        /**
+         * 真删除
+         * @param {Number} id - 删除的标段id
+         * @return {Promise<boolean>} - 结果
+         */
+        async deleteBudgetNoBackup(id) {
+            const transaction = await this.db.beginTransaction();
+            try {
+                await transaction.delete(this.tableName, { id });
+                await transaction.delete(this.ctx.service.guBudget.tableName, { bid: id });
+                await transaction.delete(this.ctx.service.gaiBudget.tableName, { bid: id });
+                await transaction.delete(this.ctx.service.yuBudget.tableName, { bid: id });
+                await transaction.commit();
+                return true;
+            } catch (err) {
+                this.ctx.log(err);
+                await transaction.rollback();
+                return false;
+            }
+        }
+    }
+
+    return Budget;
+};

+ 30 - 0
app/service/budget_gai.js

@@ -0,0 +1,30 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date
+ * @version
+ */
+
+
+
+module.exports = app => {
+    class BudgetGai extends app.BaseBudgetService {
+
+        /**
+         * 构造函数
+         *
+         * @param {Object} ctx - egg全局变量
+         * @param {String} tableName - 表名
+         * @return {void}
+         */
+        constructor(ctx) {
+            super(ctx, { keyPre: 'budget_gai_maxLid:' });
+            this.tableName = 'budget_gai';
+        }
+    }
+
+    return BudgetGai;
+};

+ 30 - 0
app/service/budget_gu.js

@@ -0,0 +1,30 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date
+ * @version
+ */
+
+
+
+module.exports = app => {
+    class BudgetGu extends app.BaseBudgetService {
+
+        /**
+         * 构造函数
+         *
+         * @param {Object} ctx - egg全局变量
+         * @param {String} tableName - 表名
+         * @return {void}
+         */
+        constructor(ctx) {
+            super(ctx, { keyPre: 'budget_gu_maxLid:' });
+            this.tableName = 'budget_gu';
+        }
+    }
+
+    return BudgetGu;
+};

+ 55 - 0
app/service/budget_std.js

@@ -0,0 +1,55 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date
+ * @version
+ */
+
+module.exports = app => {
+
+    class BudgetStd extends app.BaseService {
+        /**
+         * 构造函数
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        constructor(ctx) {
+            super(ctx);
+            this.tableName = 'budget_std';
+        }
+
+        async getDataByProjectId(pid) {
+            const project = await this.ctx.service.project.getDataById(pid);
+            const sid =  this._.map(project.budget.split(','), this._.toInteger);
+            return await this.getAllDataByCondition({
+                where: { id: sid },
+                columns: ['id', 'name'],
+            });
+        }
+
+        async getStdList(id, type) {
+            if (!type) return [[], []];
+            const gclField = type + '_bills_id', xmjField = type + '_chapter_id';
+            const std = await this.service.budgetStd.getDataById(id);
+            const billsId = std[gclField] ? this._.map(std[gclField].split(','), this._.toInteger) : [];
+            const chaptersId = std[xmjField] ? this._.map(std[xmjField].split(','), this._.toInteger) : [];
+            const sql = 'SELECT `id`, `name`' +
+                '  From ?? ' +
+                '  WHERE `id` in ( ? ) ORDER BY FIELD(`id`, ?)';
+            const sqlParam = ['zh_std_gcl_list', billsId, billsId];
+            const billsList = billsId.length > 0 ? await this.db.query(sql, sqlParam) : [];
+            const sql2 = 'SELECT `id`, `name`' +
+                '  From ?? ' +
+                '  WHERE `id` in ( ? ) ORDER BY FIELD(`id`, ?)';
+            const sqlParam2 = ['zh_std_xmj_list', chaptersId, chaptersId];
+            const chapterList = chaptersId.length > 0 ? await this.db.query(sql2, sqlParam2) : [];
+            return [billsList, chapterList];
+        }
+    }
+
+    return BudgetStd;
+};

+ 28 - 0
app/service/budget_yu.js

@@ -0,0 +1,28 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date
+ * @version
+ */
+
+module.exports = app => {
+    class BudgetYu extends app.BaseBudgetService {
+
+        /**
+         * 构造函数
+         *
+         * @param {Object} ctx - egg全局变量
+         * @param {String} tableName - 表名
+         * @return {void}
+         */
+        constructor(ctx) {
+            super(ctx, { keyPre: 'budget_yu_maxLid:' });
+            this.tableName = 'budget_yu';
+        }
+    }
+
+    return BudgetYu;
+};

+ 1 - 0
app/view/advance/detail.ejs

@@ -2,6 +2,7 @@
 <div class="panel-content">
     <div class="panel-title">
         <div class="title-main d-flex justify-content-between">
+            <% include ../tender/tender_sub_mini_menu.ejs %>
             <div>
                 <div class="d-inline-block">
                     第<%- advance.order %>期

+ 1 - 0
app/view/advance/index.ejs

@@ -2,6 +2,7 @@
 <div class="panel-content">
     <div class="panel-title">
         <div class="title-main d-flex justify-content-between">
+            <% include ../tender/tender_sub_mini_menu.ejs %>
             <div>
                 <div class="d-inline-block">
                     <div class="btn-group group-tab">

+ 37 - 0
app/view/budget/compare.ejs

@@ -0,0 +1,37 @@
+<% include ./sub_menu.ejs %>
+<div class="panel-content">
+    <div class="panel-title">
+        <div class="title-main d-flex">
+            <% include ./sub_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="5" href="javascript: void(0);">第五层</a>
+                            <a class="dropdown-item" name="showLevel" tag="last" href="javascript: void(0);">最底层</a>
+                            <a class="dropdown-item" name="showLevel" tag="leafXmj" href="javascript: void(0);">只显示项目节</a>
+                        </div>
+                    </div>
+                </div>
+                <div class="d-inline-block ml-3">
+                    <a class="btn btn-sm btn-primary" href="javascript: void(0);" id="final-compare">决算对比</a>
+                </div>
+            </div>
+        </div>
+    </div>
+    <div class="content-wrap">
+        <div class="c-header p-0">
+        </div>
+        <div class="w-100 sub-content">
+            <div class="sjs-height-1" id="cost-compare">
+            </div>
+        </div>
+    </div>
+</div>

+ 0 - 0
app/view/budget/compare_modal.ejs


+ 121 - 0
app/view/budget/detail.ejs

@@ -0,0 +1,121 @@
+<% include ./sub_menu.ejs %>
+<div class="panel-content">
+    <div class="panel-title">
+        <div class="title-main d-flex">
+            <% include ./sub_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="5" href="javascript: void(0);">第五层</a>
+                            <a class="dropdown-item" name="showLevel" tag="last" href="javascript: void(0);">最底层</a>
+                        </div>
+                    </div>
+                </div>
+                <div class="d-inline-block">
+                    <a href="javascript:void(0)" id="insert" class="btn btn-sm btn-light text-primary" data-toggle="tooltip" data-placement="bottom" title="" data-original-title="新增"><i class="fa fa-plus" aria-hidden="true"></i></a>
+                    <a href="javascript:void(0)" id="delete" class="btn btn-sm btn-light text-primary" data-toggle="tooltip" data-placement="bottom" title="" data-original-title="删除"><i class="fa fa-remove" aria-hidden="true"></i></a>
+                    <a href="javascript:void(0)" id="up-level" class="btn btn-sm btn-light text-primary" data-toggle="tooltip" data-placement="bottom" title="" data-original-title="升级"><i class="fa fa-arrow-left" aria-hidden="true"></i></a>
+                    <a href="javascript:void(0)" id="down-level" class="btn btn-sm btn-light text-primary" data-toggle="tooltip" data-placement="bottom" title="" data-original-title="降级"><i class="fa fa-arrow-right" aria-hidden="true"></i></a>
+                    <a href="javascript:void(0)" id="down-move" class="btn btn-sm btn-light text-primary" data-toggle="tooltip" data-placement="bottom" title="" data-original-title="下移"><i class="fa fa-arrow-down" aria-hidden="true"></i></a>
+                    <a href="javascript:void(0)" id="up-move" class="btn btn-sm btn-light text-primary" data-toggle="tooltip" data-placement="bottom" title="" data-original-title="上移"><i class="fa fa-arrow-up" aria-hidden="true"></i></a>
+                    <a href="javascript:void(0)" id="copy" class="btn btn-sm btn-light text-primary" data-toggle="tooltip" data-placement="bottom" title="" data-original-title="复制"><i class="fa fa-files-o" aria-hidden="true"></i></a>
+                    <a href="javascript:void(0)" id="cut" class="btn btn-sm btn-light text-primary" data-toggle="tooltip" data-placement="bottom" title="" data-original-title="剪切"><i class="fa fa-scissors" aria-hidden="true"></i></a>
+                    <a href="javascript:void(0)" id="paste" class="btn btn-sm btn-light text-primary" data-toggle="tooltip" data-placement="bottom" title="" data-original-title="粘贴"><i class="fa fa-clipboard" aria-hidden="true"></i></a>
+                </div>
+                <div class="d-inline-block ml-3">
+                    <a class="btn btn-sm btn-primary" href="javascript: void(0);" id="budget-import">导入</a>
+                </div>
+            </div>
+        </div>
+    </div>
+    <div class="content-wrap pr-46">
+        <div class="c-header p-0">
+        </div>
+        <div class="row w-100 sub-content">
+            <div id="left-view" class="c-body" style="width: 100%">
+                <div class="sjs-height-1" id="budget-spread">
+                </div>
+            </div>
+            <div id="right-view" class="c-body" style="display: none; width: 33%;">
+                <div class="resize-x" id="right-spr" r-Type="width" div1="#left-view" div2="#right-view" title="调整大小" a-type="percent"></div>
+                <div class="tab-content">
+                    <div id="search" class="tab-pane tab-select-show">
+                        <div class="sjs-bar-1">
+                            <div class="input-group input-group-sm pb-1">
+                                <div class="input-group-prepend">
+                                    <div class="input-group-text">
+                                        <input type="radio" name="searchType" id="over"> 超计
+                                    </div>
+                                    <div class="input-group-text">
+                                        <input type="radio" name="searchType" id="empty"> 漏计
+                                    </div>
+                                </div>
+                                <input type="text" class="form-control form-control-sm" placeholder="可查找 项目节编号 / 清单编号 /名称" id="keyword">
+                                <div class="input-group-append">
+                                    <button class="btn btn-outline-secondary btn-sm" type="button" id="searchLedger">搜索</button>
+                                </div>
+                            </div>
+                        </div>
+                        <div id="search-result" class="sjs-sh-1">
+                        </div>
+                    </div>
+                    <div id="std-xmj" class="tab-pane tab-select-show">
+                        <div class="sjs-bar-2">
+                            <div class="pb-1">
+                                <select class="form-control form-control-sm">
+                                    <% for (const c of stdChapters) { %>
+                                    <option value="<%- c.id %>" <% if (stdChapters.indexOf(c) === 0) { %>selected<% } %>><%- c.name %></option>
+                                    <% } %>
+                                </select>
+                            </div>
+                        </div>
+                        <div id="std-xmj-spread" class="sjs-sh-2">
+                        </div>
+                    </div>
+                    <% if (needGcl) { %>
+                    <div id="std-gcl" class="tab-pane tab-select-show">
+                        <div class="sjs-bar-3">
+                            <div class="pb-1">
+                                <select class="form-control form-control-sm">
+                                    <% for (const b of stdBills) { %>
+                                    <option value="<%- b.id %>" <% if (stdBills.indexOf(b) === 0) { %>selected<% } %>><%- b.name %></option>
+                                    <% } %>
+                                </select>
+                            </div>
+                        </div>
+                        <div id="std-gcl-spread" class="sjs-sh-3">
+                        </div>
+                    </div>
+                    <% } %>
+                </div>
+            </div>
+        </div>
+        <div class="side-menu">
+            <ul class="nav flex-column right-nav">
+                <li class="nav-item">
+                    <a class="nav-link" content="#search" href="javascript: void(0);">查找定位</a>
+                </li>
+                <li class="nav-item">
+                    <a class="nav-link" content="#std-xmj" href="javascript: void(0);">项目节</a>
+                </li>
+                <% if (needGcl) { %>
+                <li class="nav-item">
+                    <a class="nav-link" content="#std-gcl" href="javascript: void(0);">工程量清单</a>
+                </li>
+                <% } %>
+            </ul>
+        </div>
+    </div>
+</div>
+<script>
+    const needGcl = <%- needGcl %>;
+    const spreadSetting = JSON.parse('<%- JSON.stringify(spreadSetting) %>');
+</script>

+ 0 - 0
app/view/budget/detail_modal.ejs


+ 45 - 0
app/view/budget/list.ejs

@@ -0,0 +1,45 @@
+<div class="panel-content">
+    <div class="panel-title fluid">
+        <div class="title-main  d-flex justify-content-between">
+            <div class="ml-auto">
+                <a href="#add-budget" name="add" data-toggle="modal" data-target="#add-budget" class="btn btn-sm btn-primary pull-right">新建项目</a>
+            </div>
+        </div>
+    </div>
+    <div class="content-wrap">
+        <div class="sjs-height-0" style="background-color: #fff">
+            <div class="c-body">
+                <% if (!budgetList || budgetList.length === 0) { %>
+                <div class="jumbotron">
+                    <h3 class="display-6">还没有项目数据</h3>
+                </div>
+                <% } else { %>
+                <table class="table table-hover table-bordered">
+                    <thead>
+                    <tr>
+                        <th class="text-center">项目名称</th>
+                        <th class="text-center">创建人</th>
+                        <th class="text-center">创建时间</th>
+                        <th class="text-center">操作</th>
+                    </tr>
+                    </thead>
+                    <tbody>
+                    <% for (const bl of budgetList) { %>
+                    <tr>
+                        <td><a href="/budget/<%- bl.id %>/compare"><%- bl.name %></a></td>
+                        <td><%- bl.username %></td>
+                        <td><%- ctx.moment(bl.in_time).format('YYYY-MM-DD HH:mm:ss') %></td>
+                        <td>
+                            <a href="#modify-budget" data-toggle="modal" data-target="#modify-budget" class="btn btn-outline-primary btn-sm">编辑</a>
+                            <a href="#select-rela" data-toggle="modal" data-target="#select-rela" class="btn btn-outline-primary btn-sm">关联标段</a>
+                            <a href="#del-budget" data-toggle="modal" data-target="#del-budget" class="btn btn-outline-danger btn-sm ml-1">删除</a>
+                        </td>
+                    </tr>
+                    <% } %>
+                    </tbody>
+                </table>
+                <% } %>
+            </div>
+        </div>
+    </div>
+</div>

+ 115 - 0
app/view/budget/list_modal.ejs

@@ -0,0 +1,115 @@
+<!--弹出添加标段-->
+<div class="modal fade" id="add-budget" 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">
+                <div class="form-group">
+                    <label>项目名称<b class="text-danger">*</b></label>
+                    <input class="form-control form-control-sm"  placeholder="输入项目名称" type="text" id="add-budget-name" onchange="budgetNameChange(this);">
+                    <div class="invalid-feedback">名称超过100个字,请缩减名称。</div>
+                </div>
+                <div class="form-group">
+                    <label>概预算标准<b class="text-danger">*</b></label>
+                    <div>
+                        <% for (const bs of budgetStd) { %>
+                        <div class="form-check form-check-inline mt-2">
+                            <input class="form-check-input" name="std_id" type="radio" id="std<%- bs.id %>" value="<%- bs.id %>" checked="">
+                            <label class="form-check-label" for="std<%- bs.id %>"><%- bs.name %></label>
+                        </div>
+                        <% } %>
+                    </div>
+                </div>
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-secondary btn-sm" data-dismiss="modal">关闭</button>
+                <button type="button" class="btn btn-primary btn-sm" id="add-budget-ok" onclick="addBudget();">确定添加</button>
+            </div>
+        </div>
+    </div>
+</div>
+<!--编辑标段-->
+<div class="modal fade" id="modify-budget" 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">
+                <div class="form-group">
+                    <label>项目名称<b class="text-danger">*</b></label>
+                    <input class="form-control form-control-sm is-invalid"  placeholder="输入项目名称" type="text" value="名称超过100个字">
+                    <div class="invalid-feedback">
+                        名称超过100个字,请缩减名称。
+                    </div>
+                </div>
+
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-secondary" data-dismiss="modal">取消</button>
+                <button type="button" class="btn btn-primary">确定修改</button>
+            </div>
+        </div>
+    </div>
+</div>
+<!--删除标段-->
+<div class="modal fade" id="del-budget" 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">
+                <h6>确认删除「XXX高速公路」?</h6>
+                <h6>删除后,数据无法恢复,请谨慎操作。</h6>
+            </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-danger">确定删除</button>
+            </div>
+        </div>
+    </div>
+</div>
+<!--弹出关联标段-->
+<div class="modal fade" id="select-rela" 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">
+                <h5>可选标段</h5>
+                <table class="table table-sm table-bordered">
+                    <thead>
+                    <tr class="text-center">
+                        <th>选择</th>
+                        <th>标段名称</th>
+                        <th>期数</th>
+                        <th>状态</th>
+                    </tr>
+                    </thead>
+                    <tr>
+                        <td class="text-center"><input type="checkbox"></td><td>第一合同段</td><td>第15期</td><td>审批完成</td>
+                    </tr>
+                    <tr>
+                        <td class="text-center"><input type="checkbox"></td><td>第二合同段</td><td>第16期</td><td>审批完成</td>
+                    </tr>
+                    <tr>
+                        <td class="text-center"><input type="checkbox"></td><td>第三合同段</td><td>第15期</td><td>审批完成</td>
+                    </tr>
+                    <tr>
+                        <td class="text-center"><input type="checkbox"></td><td>第四合同段</td><td>第14期</td><td>审批完成</td>
+                    </tr>
+                </table>
+            </div>
+            <div class="modal-footer d-flex justify-content-between">
+                <div>
+                    <button type="button" class="btn btn-sm btn-secondary" data-dismiss="modal">取消</button>
+                    <button type="button" class="btn btn-sm btn-primary" >确定</button>
+                </div>
+            </div>
+        </div>
+    </div>
+</div>

+ 14 - 0
app/view/budget/sub_menu.ejs

@@ -0,0 +1,14 @@
+<div class="panel-sidebar" id="sub-menu">
+    <div class="sidebar-title" data-toggle="tooltip" data-placement="right" data-original-title="<%- ctx.budget.name %>">
+        <%- (ctx.budget.name.length > 15 ? ctx.budget.name.substring(0,15) + '...' : ctx.budget.name) %>
+    </div>
+    <div class="scrollbar-auto">
+        <% include ./sub_menu_list.ejs %>
+        <div class="side-fold"><a href="javascript: void(0)" data-toggle="tooltip" data-placement="top" data-original-title="折叠侧栏" id="to-mini-menu"><i class="fa fa-upload fa-rotate-270"></i></a></div>
+    </div>
+    <script>
+        new Vue({
+            el: '.scrollbar-auto',
+        });
+    </script>
+</div>

+ 5 - 0
app/view/budget/sub_menu_list.ejs

@@ -0,0 +1,5 @@
+<nav-menu title="返回" url="/budget" tclass="text-primary" ml="1" icon="fa-chevron-left"></nav-menu>
+<nav-menu title="造价对比" url="/budget/<%= ctx.budget.id %>/compare" ml="3" active="<%= ctx.url.indexOf('/compare') %>"></nav-menu>
+<nav-menu title="投资估算" url="/budget/<%= ctx.budget.id %>/gu" ml="3" active="<%= ctx.url.indexOf('/gu') %>"></nav-menu>
+<nav-menu title="初步概算" url="/budget/<%= ctx.budget.id %>/gai%>" ml="3" active="<%= ctx.url.indexOf('/gai') %>"></nav-menu>
+<nav-menu title="施工图预算" url="/budget/<%= ctx.budget.id %>/yu" ml="3" active="<%= ctx.url.indexOf('/yu') %>"></nav-menu>

+ 16 - 0
app/view/budget/sub_mini_menu.ejs

@@ -0,0 +1,16 @@
+<!--折起的菜单-->
+<div class="min-side" id="sub-mini-menu" style="display: none;">
+    <div id="sub-mini-hint" class="side-switch" data-container="body" data-toggle="popover" data-placement="bottom" data-content="这里打开收起的菜单栏"></div>
+    <div class="side-switch">
+        <i class="fa fa-bars"></i>
+    </div>
+    <div class="side-menu" id="mini-menu-list" style="display: none">
+        <% include ./sub_menu_list.ejs %>
+        <div class="side-fold"><a href="javascript: void(0);" data-toggle="tooltip" data-placement="top" data-original-title="展开侧栏" id="to-menu"><i class="fa fa-upload fa-rotate-90"></i></a></div>
+    </div>
+</div>
+<script>
+    new Vue({
+        el: '.side-menu',
+    });
+</script>

+ 8 - 0
config/menu.js

@@ -33,6 +33,14 @@ const menu = {
         children: null,
         caption: '项目',
     },
+    budget: {
+        name: '概算投资',
+        icon: 'fa-pie-chart',
+        display: true,
+        url: '/budget',
+        children: null,
+        caption: '概算投资',
+    },
     // sum: {
     //     name: '总分包',
     //     icon: 'fa-sitemap',

+ 44 - 2
config/web.js

@@ -748,12 +748,12 @@ const JsFiles = {
         advance: {
             main: {
                 files: ['/public/js/decimal.min.js'],
-                mergeFiles: ['/public/js/zh_calc.js', '/public/js/advance.js'],
+                mergeFiles: ['/public/js/zh_calc.js', '/public/js/advance.js', '/public/js/sub_menu.js'],
                 mergeFile: 'advance',
             },
             info: {
                 files: ['/public/js/decimal.min.js'],
-                mergeFiles: ['/public/js/zh_calc.js', '/public/js/advance_audit.js'],
+                mergeFiles: ['/public/js/zh_calc.js', '/public/js/advance_audit.js', '/public/js/sub_menu.js'],
                 mergeFile: 'advance_audit',
             },
         },
@@ -881,6 +881,48 @@ const JsFiles = {
                 mergeFile: 'index',
             },
         },
+        budget: {
+            list: {
+                files: ['/public/js/moment/moment.min.js'],
+                mergeFiles: [
+                    '/public/js/budget_list.js',
+                ],
+                mergeFile: 'budget_list',
+            },
+            compare: {
+                files: [
+                    '/public/js/moment/moment.min.js',
+                    '/public/js/spreadjs/sheets/v11/gc.spread.sheets.all.11.2.2.min.js',
+                    '/public/js/decimal.min.js',
+                    '/public/js/component/menu.js',
+                ],
+                mergeFiles: [
+                    '/public/js/sub_menu.js',
+                    '/public/js/spreadjs_rela/spreadjs_zh.js',
+                    '/public/js/zh_calc.js',
+                    '/public/js/path_tree.js',
+                    '/public/js/budget_compare.js',
+                ],
+                mergeFile: 'budget_compare.js'
+            },
+            detail: {
+                files: [
+                    '/public/js/moment/moment.min.js',
+                    '/public/js/spreadjs/sheets/v11/gc.spread.sheets.all.11.2.2.min.js',
+                    '/public/js/decimal.min.js',
+                    '/public/js/component/menu.js',
+                ],
+                mergeFiles: [
+                    '/public/js/sub_menu.js',
+                    '/public/js/div_resizer.js',
+                    '/public/js/spreadjs_rela/spreadjs_zh.js',
+                    '/public/js/zh_calc.js',
+                    '/public/js/path_tree.js',
+                    '/public/js/budget_detail.js',
+                ],
+                mergeFile: 'budget_detail.js'
+            }
+        }
     },
 };
 

+ 20 - 0
sql/update.sql

@@ -18,3 +18,23 @@ CREATE TABLE `zh_pay_attachment` (
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
 
 ALTER TABLE `zh_change_audit_list` ADD `mx_id` VARCHAR(50) NULL DEFAULT NULL COMMENT '计量单元id' AFTER `gcl_id`;
+
+-- 估概预标准
+INSERT INTO `calculation`.`zh_permission` (`id`, `name`, `controller`, `action`, `pid`, `icon_class`, `create_time`, `isshow`) VALUES ('72', '估概预标准', 'budget', 'all', '44', '', '0', '1');
+
+ALTER TABLE `zh_project`
+ADD COLUMN `budget`  varchar(100) CHARACTER SET ascii COLLATE ascii_general_ci NOT NULL DEFAULT '' COMMENT '估概预标准-id列表(\',\'分隔)' AFTER `valuation`;
+
+CREATE TABLE `zh_budget_std` (
+  `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '估概预标准id',
+  `name` varchar(255) COLLATE utf8_unicode_ci NOT NULL COMMENT '名称',
+  `gu_chapter_id` varchar(255) CHARACTER SET ascii NOT NULL DEFAULT '' COMMENT '估算-项目节-id列表('',''分隔)',
+  `gu_template_id` varchar(255) CHARACTER SET ascii NOT NULL DEFAULT '' COMMENT '估算-新建模板-id列表('',''分隔)',
+  `gai_chapter_id` varchar(255) CHARACTER SET ascii NOT NULL DEFAULT '' COMMENT '概算-项目节-id列表('',''分隔)',
+  `gai_template_id` varchar(255) CHARACTER SET ascii NOT NULL DEFAULT '' COMMENT '概算-新建模板-id列表('',''分隔)',
+  `yu_chapter_id` varchar(255) CHARACTER SET ascii NOT NULL DEFAULT '' COMMENT '预算-项目节-id列表('',''分隔)',
+  `yu_template_id` varchar(255) CHARACTER SET ascii NOT NULL DEFAULT '' COMMENT '预算-新建模板-id列表('',''分隔)',
+  `yu_bills_id` varchar(255) CHARACTER SET ascii NOT NULL DEFAULT '' COMMENT '预算-工程量清单-id列表('',''分隔)',
+  `in_time` datetime NOT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci COMMENT='估概预标准列表';