Просмотр исходного кода

分享标段,部分页面可查看

MaiXinRong 6 дней назад
Родитель
Сommit
ca3b0f8be6

+ 1 - 1
app/controller/ledger_controller.js

@@ -155,7 +155,7 @@ module.exports = app => {
                     categoryData,
                     syncLedgerUrl: syncLedger ? `${ctx.app.config.url3f}/${syncLedger.pull_class}/sync-tz/${tender.id}` : '',
                     nodeType: stdConst.nodeType,
-                    authMobile: pa.auth_mobile,
+                    authMobile: pa ? pa.auth_mobile : '',
                     deleteFilePermission: PermissionCheck.delFile(this.ctx.session.sessionUser.permission),
                     templateFolder,
                     posCalcTemplate,

+ 12 - 0
app/controller/tender_controller.js

@@ -721,6 +721,18 @@ module.exports = app => {
                 ctx.body = this.ajaxErrorBody(err, '保存失败');
             }
         }
+        async share(ctx) {
+            try {
+                const data = JSON.parse(ctx.request.body.data);
+                if (!data || (!data.share_token && !data.share_expiration)) throw '提交数据错误';
+                if (!ctx.session.sessionUser.is_admin) throw '您无权修改该内容';
+                await ctx.service.tender.defaultUpdate({ id: ctx.tender.id, share_token: data.share_token, share_expiration: new Date(data.share_expiration) });
+                ctx.body = { err: 0, msg: '', data };
+            } catch (err) {
+                this.log(err);
+                ctx.body = this.ajaxErrorBody(err, '保存失败');
+            }
+        }
         async saveBuildStatus(ctx) {
             try {
                 const status = parseInt(ctx.query.status || ctx.request.body.status);

+ 1 - 0
app/middleware/auto_finish_logger.js

@@ -14,6 +14,7 @@ module.exports = options => {
         await next();
 
         if (ctx.url.indexOf('/public/') === 0) return;
+        if (!ctx.logInfo) return;
 
         const responseTime = new Date();
         const runTime = responseTime.getTime() - ctx.logInfo.time.getTime();

+ 1 - 1
app/middleware/auto_logger.js

@@ -22,7 +22,7 @@ module.exports = options => {
             return ctx.getLogger('mixed');
         }
 
-        if (this.url.indexOf('/public/') !== 0) {
+        if (this.url.indexOf('/public/') !== 0 && (!this.session.sessionUser || this.session.sessionUser.loginType !== 4)) {
             const bLogger = getBussinessLogger(this);
             if (this.session.sessionUser) {
                 const logData = {

+ 37 - 4
app/middleware/session_auth.js

@@ -13,10 +13,42 @@ module.exports = options => {
      */
     return function* sessionAuth(next) {
         try {
+            const share = this.query.share;
+            if (share && this.request.url.indexOf('/tender') === 0) {
+                const tender = yield this.service.tender.getDataByCondition({ id: this.params.id });
+                if (tender.share_token !== share) {
+                    throw '分享码不正确';
+                }
+                if (tender.share_expiration < new Date()) {
+                    throw '分享码已过期';
+                }
+                const currentTime = new Date().getTime() / 1000;
+                this.session.sessionUser = {
+                    account: '游客',
+                    name: '游客',
+                    accountId: 0,
+                    loginTime: currentTime,
+                    is_admin: false,
+                    loginType: 4,
+                    loginStatus: 0,
+                    dskAccountData: null,
+                    permission: null,
+                };
+                this.session.sessionUser.sessionToken = crypto.createHmac('sha1', currentTime + '')
+                    .update(this.session.sessionUser.account).digest('hex').toString('base64');
+                const projectData = yield this.service.project.getDataByCondition({ id: tender.project_id });
+                this.session.sessionProject = {
+                    id: projectData.id,
+                    code: projectData.code,
+                    name: projectData.name,
+                    userAccount: projectData.user_account,
+                    custom: projectData.custom,
+                };
+            }
             // 判断session
             const sessionUser = this.session.sessionUser;
             if (sessionUser === undefined) {
-                throw '不存在session';
+                    throw '不存在session';
             }
             // 校验session
             if (sessionUser.account === undefined || sessionUser.loginTime === undefined) {
@@ -30,8 +62,10 @@ module.exports = options => {
             }
 
             // 获取用户新建标段权利
-            const accountInfo = yield this.service.projectAccount.getDataById(this.session.sessionUser.accountId);
-            this.session.sessionUser.permission = accountInfo !== undefined && accountInfo.permission !== '' ? JSON.parse(accountInfo.permission) : null;
+            if (sessionUser.loginType !== 4) {
+                const accountInfo = yield this.service.projectAccount.getDataById(this.session.sessionUser.accountId);
+                this.session.sessionUser.permission = accountInfo !== undefined && accountInfo.permission !== '' ? JSON.parse(accountInfo.permission) : null;
+            }
             const projectData = yield this.service.project.getDataById(this.session.sessionProject.id);
             this.session.sessionProject.page_show = yield this.service.projectAccount.getPageShow(projectData.page_show);
             this.session.sessionProject.custom = projectData.custom;
@@ -56,7 +90,6 @@ module.exports = options => {
                 this.redirect(returnUrl);
             }
         } catch (error) {
-            console.log(error);
             this.log(error);
             if (this.helper.isAjax(this.request)) {
                 return this.body = {

+ 5 - 3
app/middleware/sub_project_check.js

@@ -64,7 +64,7 @@ module.exports = options => {
             const projectData = yield this.service.project.getDataById(this.session.sessionProject.id);
             let showDataCollect = 0;
             if (this.subProject.data_collect && this.subProject.page_show.openDataCollect) {
-                if (this.session.sessionUser.is_admin) {
+                if (this.session.sessionUser.is_admin || this.session.sessionUser.loginType === 4) {
                     showDataCollect = 1;
                 } else {
                     const auditInfo = yield this.service.subProjPermission.getDataByCondition({ spid: this.subProject.id, uid: accountInfo.id });
@@ -95,8 +95,10 @@ module.exports = options => {
             }
 
             // 更新用户最近使用项目时间
-            const auditInfo = yield this.service.subProjPermission.getDataByCondition({ spid: this.subProject.id, uid: accountInfo.id });
-            if (auditInfo) yield this.service.subProjPermission.defaultUpdate({ id: auditInfo.id, last_time: new Date() });
+            if (this.session.sessionUser.loginType !== 4) {
+                const auditInfo = yield this.service.subProjPermission.getDataByCondition({ spid: this.subProject.id, uid: accountInfo.id });
+                if (auditInfo) yield this.service.subProjPermission.defaultUpdate({ id: auditInfo.id, last_time: new Date() });
+            }
         } catch (err) {
             this.log(err);
             if (this.helper.isAjax(this.request)) {

+ 1 - 2
app/middleware/tender_check.js

@@ -103,7 +103,7 @@ module.exports = options => {
             } else {
                 tender.user_permission = yield this.service.tenderPermission.getUserPermission(tender.id, this.session.sessionUser.accountId);
             }
-            if (auditorsId.indexOf(accountId) === -1 && tender.data.user_id !== accountId &&
+            if (auditorsId.indexOf(accountId) === -1 && tender.data.user_id !== accountId && this.session.sessionUser.loginType !== 4 &&
                 (tenderPermission === null || tenderPermission === undefined || tenderPermission.indexOf('2') === -1) &&
                 stageAuditorsId.indexOf(accountId) === -1 && auditAssistsId.indexOf(accountId) === -1 &&
                 settleAuditorsId.indexOf(accountId) === -1 && phasePayAuditorsId.indexOf(accountId) === -1 && changeAuditorsId.indexOf(accountId) === -1 &&
@@ -140,7 +140,6 @@ module.exports = options => {
             // tender.schedule_permission = schedule_permission;
             yield next;
         } catch (err) {
-            console.log(err);
             // 输出错误到日志
             if (err.stack) {
                 this.logger.error(err);

+ 1 - 0
app/router.js

@@ -631,6 +631,7 @@ module.exports = app => {
     app.get('/tender/:id/type', sessionAuth, 'tenderController.tenderType');
     app.post('/tender/:id/save', sessionAuth, tenderCheck, subProjectCheck, 'tenderController.saveTenderInfo');
     app.post('/tender/:id/save2', sessionAuth, tenderCheck, subProjectCheck, 'tenderController.saveTenderInfo2');
+    app.post('/tender/:id/share', sessionAuth, tenderCheck, subProjectCheck, 'tenderController.share');
     app.post('/tender/:id/build', sessionAuth, tenderCheck, subProjectCheck, 'tenderController.saveBuildStatus');
     app.post('/tender/rule', sessionAuth, 'tenderController.rule');
     app.post('/tender/:id/rule/first', sessionAuth, tenderCheck, subProjectCheck, 'tenderController.ruleFirst');

+ 1 - 1
app/service/project_account.js

@@ -213,7 +213,7 @@ module.exports = app => {
          * 账号登录
          *
          * @param {Object} data - 表单post数据
-         * @param {Number} loginType - 登录类型 1(sso登录) | 2(正常或副密码登录) | 3(接口登录或微信登录)
+         * @param {Number} loginType - 登录类型 1(sso登录) | 2(正常或副密码登录) | 3(接口登录或微信登录) | 4(游客,虚拟账号)
          * @return {Boolean} - 返回登录结果
          */
         async accountLogin(data, loginType) {

+ 17 - 0
app/service/sub_proj_permission.js

@@ -128,6 +128,21 @@ module.exports = app => {
 
         get adminPermission () {
             return {
+                budget_permission: [this.PermissionConst.budget.view],
+                file_permission: [this.PermissionConst.file.view],
+                manage_permission: [this.PermissionConst.manage.view],
+                filing_type: 'all',
+                info_permission: [this.PermissionConst.info.view],
+                datacollect_permission: [this.PermissionConst.datacollect.view],
+                contract_permission: [this.PermissionConst.contract.view],
+                fund_pay_permission: [this.PermissionConst.fund_pay.view],
+                fund_trans_permission: [this.PermissionConst.fund_trans.view],
+                payment_permission: [this.PermissionConst.payment.view],
+            }
+        }
+
+        get touristPermission () {
+            return {
                 budget_permission: this.ctx.helper.mapAllSubField(this.PermissionConst.budget, 'value'),
                 file_permission: this.ctx.helper.mapAllSubField(this.PermissionConst.file, 'value'),
                 manage_permission: this.ctx.helper.mapAllSubField(this.PermissionConst.manage, 'value'),
@@ -206,6 +221,8 @@ module.exports = app => {
         }
 
         async getSubProjectUserPermission(spid, uid) {
+            if (uid === 0) return this.touristPermission;
+
             const result = await this.getDataByCondition({ spid, uid });
             if (result) this.parsePermission(result);
             return result;

+ 2 - 0
app/view/tender/detail.ejs

@@ -66,6 +66,7 @@
                         <div class="dropdown-menu" aria-labelledby="dropdownMenuLink">
                             <a href="/tender/<%- tender.id %>/shenpi" class="dropdown-item">审批流程</a>
                             <a href="#bd-set-9" data-toggle="modal" data-target="#bd-set-9" class="dropdown-item">游客账号 <span id="tourist-num" <% if (tourists.length !== 0) { %>class="badge badge-secondary"<% } %>><% if (tourists.length !== 0) { %><%- tourists.length %><% } %></span></a>
+                            <a href="#bd-set-15" data-toggle="modal" data-target="#bd-set-15" class="dropdown-item">分享标段</a>
                             <a href="#bd-set-10" data-toggle="modal" data-target="#bd-set-10" class="dropdown-item">上报控制</a>
                             <a href="#bd-set-12" data-toggle="modal" data-target="#bd-set-12" class="dropdown-item">功能设置</a>
                             <a href="#bd-set-11" data-toggle="modal" data-target="#bd-set-11" class="dropdown-item">概况设置</a>
@@ -1501,3 +1502,4 @@
         };
     })
 </script>
+<script src="/public/js/moment/moment.min.js"></script>

+ 85 - 0
app/view/tender/detail_modal.ejs

@@ -1979,6 +1979,50 @@
             </div>
         </div>
     </div>
+    <div class="modal fade" id="bd-set-15" 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="row">
+                    <div class="col-6">
+                        <div class="input-group input-group-sm">
+                            <div class="input-group-prepend">
+                                <span class="input-group-text">分享码</span>
+                            </div>
+                            <input class="form-control nospin" value="" id="share-token" readonly>
+                            <button class="ml-1 btn btn-sm btn-primary" id="reset-share-token"  data-toggle="tooltip" data-placement="top" data-original-title="重置"><i class="fa fa-repeat"></i></button>
+                        </div>
+                    </div>
+                    <div class="col-6">
+                        <div class="input-group input-group-sm">
+                            <div class="input-group-prepend">
+                                <span class="input-group-text">到期时间</span>
+                            </div>
+                            <input class="form-control nospin" value="" id="share-expiration" readonly>
+                            <button class="ml-1 btn btn-sm btn-primary" id="refresh-share-expiration" data-toggle="tooltip" data-placement="top" data-original-title="刷新"><i class="fa fa-refresh"></i></button>
+                        </div>
+                    </div>
+                    <div class="col-12 mt-2">
+                        <div class="input-group input-group-sm">
+                            <div class="input-group-prepend">
+                                <span class="input-group-text">分享链接</span>
+                            </div>
+                            <input class="form-control nospin" value="" id="share-url" readonly>
+                            <button class="ml-1 btn btn-sm btn-primary" id="copy-share-url" data-toggle="tooltip" data-placement="bottom" data-original-title="分享"><i class="fa fa-share"></i></button>
+                        </div>
+                    </div>
+                </div>
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-sm btn-secondary" data-dismiss="modal">关闭</button>
+                <button type="button" class="btn btn-sm btn-primary" onclick="post15();">确认修改</button>
+            </div>
+        </div>
+    </div>
+</div>
     <!--游客账号-->
     <div class="modal fade" id="bd-set-9" data-backdrop="static">
         <div class="modal-dialog" role="document">
@@ -2249,6 +2293,47 @@
                 $('#bd-set-14').modal('hide');
             });
         }
+        function getRandomString(length) {
+            var result = '';
+            var characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
+            var charactersLength = characters.length;
+            for (var i = 0; i < length; i++) {
+                result += characters.charAt(Math.floor(Math.random() * charactersLength));
+            }
+            return result;
+        }
+        $('#reset-share-token').click(function() {
+            const shareToken = getRandomString(10);
+            $('#share-token').val(shareToken);
+        });
+        $('#refresh-share-expiration').click(function() {
+            $('#share-expiration').val(moment().add(30, 'days').format('YYYY-MM-DD'));
+        });
+        $('#copy-share-url').click(function() {
+            var textarea = document.createElement('textarea');
+            textarea.style.position = 'fixed';
+            textarea.style.opacity = 0;
+            textarea.value = $('#share-url').val();
+            document.body.appendChild(textarea);
+            textarea.select();
+            document.execCommand('copy');
+            document.body.removeChild(textarea);
+            toastr.success('已复制,可粘贴分享给他人');
+        });
+        function post15() {
+            const prop = {
+                share_token: $('#share-token').val(),
+                share_expiration: $('#share-expiration').val(),
+            };
+            if (!prop.share_token || !prop.share_expiration) {
+                toastr.error('请点击按钮设置分享码,更新到期时间');
+                return;
+            }
+            postData('/tender/' + tenderId + '/share', prop, function(data) {
+                $('#share-url').val(window.location.href + '?share=' + data.share_token);
+                toastr.success('保存成功,可分享该标段');
+            });
+        }
         $(function () {
             // 投资进度
             let timer2 = null;

+ 7 - 102
sql/update.sql

@@ -3,117 +3,19 @@
 -- 2. 表名前不可有库名!!!
 -- 3. 表结构部分,新添加的表,不可先创建后修改!!!
 --
+-- Version
+-- uat
+-- prod
 
 ------------------------------------
 -- 表结构
 ------------------------------------
 
-ALTER TABLE `zh_contract`
-ADD COLUMN `price` decimal(30, 8) NULL DEFAULT 0 COMMENT '合同金额(可编辑,和补充合同金额汇总得出总金额)' AFTER `total_price`;
+ALTER TABLE `zh_tender`
+ADD COLUMN `share_token` varchar(20) NOT NULL DEFAULT '' COMMENT '标段分享token' AFTER `filter_fund`,
+ADD COLUMN `share_expiration` datetime NULL COMMENT '分享token到期时间' AFTER `share_token`;
 
-ALTER TABLE `zh_contract_sp_audit`
-ADD COLUMN `csid` int NULL DEFAULT NULL COMMENT '补充合同id' AFTER `cpid`;
-
-ALTER TABLE `zh_sub_project`
-ADD COLUMN `cost_ledger_col_set` json NULL COMMENT '成本报审-列设置' AFTER `cost_calc_template`;
-
-ALTER TABLE `zh_cost_stage_analysis`
-ADD COLUMN `has_detail` int(1) NOT NULL DEFAULT 0 COMMENT '是否含有明细' AFTER `calc_type`,
-ADD COLUMN `import_cost_id` varchar(5000) NOT NULL DEFAULT '' COMMENT '导入的成本id' AFTER `has_detail`,
-ADD COLUMN `import_cost_type` tinyint(4) UNSIGNED NOT NULL DEFAULT 0 COMMENT '导入的成本类型(0无明细,1普通,2合同)' AFTER `import_cost_id`;
-
-CREATE TABLE `zh_budget_real`  (
-  `id` varchar(100) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL COMMENT '自增id',
-  `bid` int(10) NOT NULL COMMENT '概算投资项目id',
-  `tree_id` int(10) NOT NULL COMMENT '节点id',
-  `tree_pid` int(10) NOT NULL COMMENT '父节点id',
-  `level` tinyint(4) NOT NULL COMMENT '层级',
-  `order` mediumint(4) NOT NULL DEFAULT 0 COMMENT '同级排序',
-  `full_path` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NULL DEFAULT '' COMMENT '层级定位辅助字段parent.full_path.ledger_id',
-  `is_leaf` tinyint(1) NOT NULL COMMENT '是否叶子节点,界面显示辅助字段',
-  `code` varchar(50) CHARACTER SET utf8 COLLATE utf8_unicode_ci NULL DEFAULT '' COMMENT '节点编号',
-  `b_code` varchar(50) CHARACTER SET utf8 COLLATE utf8_unicode_ci NULL DEFAULT NULL,
-  `name` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NULL DEFAULT NULL COMMENT '名称',
-  `unit` varchar(15) CHARACTER SET utf8 COLLATE utf8_unicode_ci NULL DEFAULT '' COMMENT '单位',
-  `unit_price` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '单价',
-  `quantity` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '数量',
-  `total_price` decimal(24, 8) NULL DEFAULT 0.00000000 COMMENT '金额',
-  `dgn_qty1` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '设计数量1',
-  `dgn_qty2` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '设计数量2',
-  `drawing_code` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NULL DEFAULT NULL COMMENT '图册号',
-  `memo` varchar(1000) CHARACTER SET utf8 COLLATE utf8_unicode_ci NULL DEFAULT '' COMMENT '备注',
-  `node_type` int(4) UNSIGNED NULL DEFAULT 0 COMMENT '节点类别',
-  `source` varchar(30) CHARACTER SET utf8 COLLATE utf8_unicode_ci NULL DEFAULT '' COMMENT '添加源',
-  `remark` varchar(60) CHARACTER SET utf8 COLLATE utf8_unicode_ci NULL DEFAULT '' COMMENT '备注',
-  PRIMARY KEY (`id`) USING BTREE,
-  INDEX `idx_tender_id`(`bid`) USING BTREE,
-  INDEX `idx_template_pid`(`tree_pid`) USING BTREE,
-  INDEX `idx_level`(`level`) USING BTREE,
-  INDEX `idx_full_path`(`bid`, `full_path`) USING BTREE
-) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_unicode_ci COMMENT = '实际投资数据' ROW_FORMAT = Dynamic;
-
-ALTER TABLE `zh_budget_final`
-ADD COLUMN `real_dgn_qty1` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '实际投资-设计数量1' AFTER `zb_tp`,
-ADD COLUMN `real_dgn_qty2` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '实际投资-设计数量1' AFTER `real_dgn_qty1`,
-ADD COLUMN `real_dgn_qty` varchar(50) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '实际投资-设计数量1/设计数量2' AFTER `real_dgn_qty2`,
-ADD COLUMN `real_dgn_price` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '实际投资-经济指标' AFTER `real_dgn_qty`,
-ADD COLUMN `real_tp` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '实际投资-金额' AFTER `real_dgn_price`;
-
-ALTER TABLE `zh_budget_std`
-ADD COLUMN `real_chapter_id` varchar(255) CHARACTER SET ascii COLLATE ascii_general_ci NOT NULL DEFAULT '' COMMENT '实际投资-项目节-id列表(\',\'分隔)' AFTER `zb_bills_id`,
-ADD COLUMN `real_template_id` varchar(255) CHARACTER SET ascii COLLATE ascii_general_ci NOT NULL DEFAULT '' COMMENT '实际投资-新建模板-id列表(\',\'分隔)' AFTER `real_chapter_id`,
-ADD COLUMN `real_bills_id` varchar(255) CHARACTER SET ascii COLLATE ascii_general_ci NOT NULL DEFAULT '' COMMENT '实际投资-工程量清单-id列表(\',\'分隔)' AFTER `real_template_id`;
-
-ALTER TABLE `zh_sub_project_info`
-ADD COLUMN `proj_scale` varchar(20) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '建设项目规模' AFTER `chief_department`;
-
-ALTER TABLE `zh_budget`
-ADD COLUMN `ctrl_optimize_rate` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '控制目标-优化率' AFTER `final_type`,
-ADD COLUMN `ctrl_profit` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '控制目标-收益金额' AFTER `ctrl_optimize_rate`,
-ADD COLUMN `real_optimize_rate` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '实际投资-优化率' AFTER `ctrl_profit`,
-ADD COLUMN `real_profit` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '实际投资-收益金额' AFTER `real_optimize_rate`;
-
-CREATE TABLE `zh_cost_stage_duty` (
-  `id` varchar(36) COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT 'uuid',
-  `cost_id` varchar(36) COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT 'uuid(不同期保持统一)',
-  `tender_id` int(11) unsigned NOT NULL COMMENT '标段id',
-  `stage_id` varchar(36) COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '期id',
-  `tree_id` int(11) NOT NULL COMMENT '节点id',
-  `tree_pid` int(11) NOT NULL COMMENT '父节点id',
-  `tree_level` tinyint(4) NOT NULL COMMENT '层级',
-  `tree_order` mediumint(4) NOT NULL COMMENT '同级排序',
-  `tree_full_path` varchar(255) COLLATE utf8_unicode_ci NOT NULL COMMENT '层级定位辅助字段parent.full_path-tree_id',
-  `tree_is_leaf` tinyint(4) unsigned NOT NULL DEFAULT '1' COMMENT '是否叶子节点,界面显示辅助字段',
-  `code` varchar(50) COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '编号',
-  `b_code` varchar(50) COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '清单编号',
-  `name` varchar(255) COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '名称',
-  `unit` varchar(255) COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '单位',
-  `unit_price` decimal(24,8) NOT NULL DEFAULT '0.00000000' COMMENT '单价',
-  `quantity` decimal(24,8) NOT NULL DEFAULT '0.00000000' COMMENT '数量',
-  `total_price` decimal(24,8) NOT NULL DEFAULT '0.00000000' COMMENT '金额',
-  `drawing_code` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NULL DEFAULT NULL COMMENT '图册号',
-  `memo` varchar(1000) COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '备注',
-  `add_user_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建人',
-  `add_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
-  `update_user_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '最后编辑人',
-  `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '最后编辑时间',
-  `read_unit_price` decimal(24,8) NOT NULL DEFAULT '0.00000000' COMMENT '单价-只读',
-  `read_quantity` decimal(24,8) NOT NULL DEFAULT '0.00000000' COMMENT '数量-只读',
-  `read_total_price` decimal(24,8) NOT NULL DEFAULT '0.00000000' COMMENT '金额-只读',
-  `calc_his` json DEFAULT NULL COMMENT '本期历史',
-  PRIMARY KEY (`id`)
-) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
-
-ALTER TABLE `zh_cost_stage`
-ADD COLUMN `extra_info` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '附加信息' AFTER `calc_template`;
-
-ALTER TABLE `zh_budget_final_list`
-ADD COLUMN `contract` varchar(1000) NOT NULL DEFAULT ''  COMMENT '关联合同id(,分隔)' AFTER `status`;
 
 ------------------------------------
 -- 表数据
-------------------------------------
-update zh_contract set price = total_price;
+------------------------------------

+ 119 - 0
sql/update20260720.sql

@@ -0,0 +1,119 @@
+-- sql提交须知:
+-- 1. 请按如下分类提交sql!!!
+-- 2. 表名前不可有库名!!!
+-- 3. 表结构部分,新添加的表,不可先创建后修改!!!
+--
+-- Version 3.5.54.0735
+-- uat 2026-07-17
+-- prod 2026-07-20
+
+------------------------------------
+-- 表结构
+------------------------------------
+
+ALTER TABLE `zh_contract`
+ADD COLUMN `price` decimal(30, 8) NULL DEFAULT 0 COMMENT '合同金额(可编辑,和补充合同金额汇总得出总金额)' AFTER `total_price`;
+
+ALTER TABLE `zh_contract_sp_audit`
+ADD COLUMN `csid` int NULL DEFAULT NULL COMMENT '补充合同id' AFTER `cpid`;
+
+ALTER TABLE `zh_sub_project`
+ADD COLUMN `cost_ledger_col_set` json NULL COMMENT '成本报审-列设置' AFTER `cost_calc_template`;
+
+ALTER TABLE `zh_cost_stage_analysis`
+ADD COLUMN `has_detail` int(1) NOT NULL DEFAULT 0 COMMENT '是否含有明细' AFTER `calc_type`,
+ADD COLUMN `import_cost_id` varchar(5000) NOT NULL DEFAULT '' COMMENT '导入的成本id' AFTER `has_detail`,
+ADD COLUMN `import_cost_type` tinyint(4) UNSIGNED NOT NULL DEFAULT 0 COMMENT '导入的成本类型(0无明细,1普通,2合同)' AFTER `import_cost_id`;
+
+CREATE TABLE `zh_budget_real`  (
+  `id` varchar(100) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL COMMENT '自增id',
+  `bid` int(10) NOT NULL COMMENT '概算投资项目id',
+  `tree_id` int(10) NOT NULL COMMENT '节点id',
+  `tree_pid` int(10) NOT NULL COMMENT '父节点id',
+  `level` tinyint(4) NOT NULL COMMENT '层级',
+  `order` mediumint(4) NOT NULL DEFAULT 0 COMMENT '同级排序',
+  `full_path` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NULL DEFAULT '' COMMENT '层级定位辅助字段parent.full_path.ledger_id',
+  `is_leaf` tinyint(1) NOT NULL COMMENT '是否叶子节点,界面显示辅助字段',
+  `code` varchar(50) CHARACTER SET utf8 COLLATE utf8_unicode_ci NULL DEFAULT '' COMMENT '节点编号',
+  `b_code` varchar(50) CHARACTER SET utf8 COLLATE utf8_unicode_ci NULL DEFAULT NULL,
+  `name` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NULL DEFAULT NULL COMMENT '名称',
+  `unit` varchar(15) CHARACTER SET utf8 COLLATE utf8_unicode_ci NULL DEFAULT '' COMMENT '单位',
+  `unit_price` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '单价',
+  `quantity` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '数量',
+  `total_price` decimal(24, 8) NULL DEFAULT 0.00000000 COMMENT '金额',
+  `dgn_qty1` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '设计数量1',
+  `dgn_qty2` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '设计数量2',
+  `drawing_code` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NULL DEFAULT NULL COMMENT '图册号',
+  `memo` varchar(1000) CHARACTER SET utf8 COLLATE utf8_unicode_ci NULL DEFAULT '' COMMENT '备注',
+  `node_type` int(4) UNSIGNED NULL DEFAULT 0 COMMENT '节点类别',
+  `source` varchar(30) CHARACTER SET utf8 COLLATE utf8_unicode_ci NULL DEFAULT '' COMMENT '添加源',
+  `remark` varchar(60) CHARACTER SET utf8 COLLATE utf8_unicode_ci NULL DEFAULT '' COMMENT '备注',
+  PRIMARY KEY (`id`) USING BTREE,
+  INDEX `idx_tender_id`(`bid`) USING BTREE,
+  INDEX `idx_template_pid`(`tree_pid`) USING BTREE,
+  INDEX `idx_level`(`level`) USING BTREE,
+  INDEX `idx_full_path`(`bid`, `full_path`) USING BTREE
+) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_unicode_ci COMMENT = '实际投资数据' ROW_FORMAT = Dynamic;
+
+ALTER TABLE `zh_budget_final`
+ADD COLUMN `real_dgn_qty1` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '实际投资-设计数量1' AFTER `zb_tp`,
+ADD COLUMN `real_dgn_qty2` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '实际投资-设计数量1' AFTER `real_dgn_qty1`,
+ADD COLUMN `real_dgn_qty` varchar(50) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '实际投资-设计数量1/设计数量2' AFTER `real_dgn_qty2`,
+ADD COLUMN `real_dgn_price` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '实际投资-经济指标' AFTER `real_dgn_qty`,
+ADD COLUMN `real_tp` decimal(24, 8) NOT NULL DEFAULT 0.00000000 COMMENT '实际投资-金额' AFTER `real_dgn_price`;
+
+ALTER TABLE `zh_budget_std`
+ADD COLUMN `real_chapter_id` varchar(255) CHARACTER SET ascii COLLATE ascii_general_ci NOT NULL DEFAULT '' COMMENT '实际投资-项目节-id列表(\',\'分隔)' AFTER `zb_bills_id`,
+ADD COLUMN `real_template_id` varchar(255) CHARACTER SET ascii COLLATE ascii_general_ci NOT NULL DEFAULT '' COMMENT '实际投资-新建模板-id列表(\',\'分隔)' AFTER `real_chapter_id`,
+ADD COLUMN `real_bills_id` varchar(255) CHARACTER SET ascii COLLATE ascii_general_ci NOT NULL DEFAULT '' COMMENT '实际投资-工程量清单-id列表(\',\'分隔)' AFTER `real_template_id`;
+
+ALTER TABLE `zh_sub_project_info`
+ADD COLUMN `proj_scale` varchar(20) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '建设项目规模' AFTER `chief_department`;
+
+ALTER TABLE `zh_budget`
+ADD COLUMN `ctrl_optimize_rate` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '控制目标-优化率' AFTER `final_type`,
+ADD COLUMN `ctrl_profit` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '控制目标-收益金额' AFTER `ctrl_optimize_rate`,
+ADD COLUMN `real_optimize_rate` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '实际投资-优化率' AFTER `ctrl_profit`,
+ADD COLUMN `real_profit` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '实际投资-收益金额' AFTER `real_optimize_rate`;
+
+CREATE TABLE `zh_cost_stage_duty` (
+  `id` varchar(36) COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT 'uuid',
+  `cost_id` varchar(36) COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT 'uuid(不同期保持统一)',
+  `tender_id` int(11) unsigned NOT NULL COMMENT '标段id',
+  `stage_id` varchar(36) COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '期id',
+  `tree_id` int(11) NOT NULL COMMENT '节点id',
+  `tree_pid` int(11) NOT NULL COMMENT '父节点id',
+  `tree_level` tinyint(4) NOT NULL COMMENT '层级',
+  `tree_order` mediumint(4) NOT NULL COMMENT '同级排序',
+  `tree_full_path` varchar(255) COLLATE utf8_unicode_ci NOT NULL COMMENT '层级定位辅助字段parent.full_path-tree_id',
+  `tree_is_leaf` tinyint(4) unsigned NOT NULL DEFAULT '1' COMMENT '是否叶子节点,界面显示辅助字段',
+  `code` varchar(50) COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '编号',
+  `b_code` varchar(50) COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '清单编号',
+  `name` varchar(255) COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '名称',
+  `unit` varchar(255) COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '单位',
+  `unit_price` decimal(24,8) NOT NULL DEFAULT '0.00000000' COMMENT '单价',
+  `quantity` decimal(24,8) NOT NULL DEFAULT '0.00000000' COMMENT '数量',
+  `total_price` decimal(24,8) NOT NULL DEFAULT '0.00000000' COMMENT '金额',
+  `drawing_code` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NULL DEFAULT NULL COMMENT '图册号',
+  `memo` varchar(1000) COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '备注',
+  `add_user_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建人',
+  `add_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+  `update_user_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '最后编辑人',
+  `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '最后编辑时间',
+  `read_unit_price` decimal(24,8) NOT NULL DEFAULT '0.00000000' COMMENT '单价-只读',
+  `read_quantity` decimal(24,8) NOT NULL DEFAULT '0.00000000' COMMENT '数量-只读',
+  `read_total_price` decimal(24,8) NOT NULL DEFAULT '0.00000000' COMMENT '金额-只读',
+  `calc_his` json DEFAULT NULL COMMENT '本期历史',
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
+
+ALTER TABLE `zh_cost_stage`
+ADD COLUMN `extra_info` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '附加信息' AFTER `calc_template`;
+
+ALTER TABLE `zh_budget_final_list`
+ADD COLUMN `contract` varchar(1000) NOT NULL DEFAULT ''  COMMENT '关联合同id(,分隔)' AFTER `status`;
+
+------------------------------------
+-- 表数据
+------------------------------------
+update zh_contract set price = total_price;