Przeglądaj źródła

Merge branch 'dev' of http://192.168.1.41:3000/maixinrong/Calculation into dev

Tony Kang 1 rok temu
rodzic
commit
a4f2e54fe7
82 zmienionych plików z 2323 dodań i 130 usunięć
  1. 1 1
      app.js
  2. 6 0
      app/base/base_budget_service.js
  3. 3 5
      app/base/base_controller.js
  4. 6 0
      app/const/page_show.js
  5. 1 0
      app/const/tender_info.js
  6. 5 0
      app/controller/budget_controller.js
  7. 5 0
      app/controller/change_controller.js
  8. 3 0
      app/controller/construction_controller.js
  9. 198 0
      app/controller/ctrl_price_controller.js
  10. 6 1
      app/controller/file_controller.js
  11. 20 6
      app/controller/login_controller.js
  12. 3 0
      app/controller/measure_controller.js
  13. 12 0
      app/controller/payment_controller.js
  14. 50 0
      app/controller/settle_controller.js
  15. 5 0
      app/controller/sub_proj_controller.js
  16. 3 0
      app/controller/template_controller.js
  17. 3 1
      app/controller/wap_controller.js
  18. 16 4
      app/controller/wechat_controller.js
  19. 1 1
      app/lib/budget_final.js
  20. 7 2
      app/middleware/budget_check.js
  21. 5 1
      app/middleware/construction_check.js
  22. 5 1
      app/middleware/payment_tender_check.js
  23. 9 12
      app/middleware/session_auth.js
  24. 5 2
      app/middleware/sub_project_check.js
  25. 4 0
      app/public/css/main.css
  26. BIN
      app/public/files/template/控制价示例EXCEL格式.xlsx
  27. 1 1
      app/public/js/budget_info.js
  28. 58 0
      app/public/js/budget_list.js
  29. 33 0
      app/public/js/change_information_set.js
  30. 779 0
      app/public/js/ctrl_price.js
  31. 11 2
      app/public/js/file_detail.js
  32. 59 0
      app/public/js/file_list.js
  33. 2 2
      app/public/js/material.js
  34. 13 9
      app/public/js/material_checklist.js
  35. 1 1
      app/public/js/revise_gcl_compare.js
  36. 3 2
      app/public/js/setting_manage.js
  37. 3 2
      app/public/js/shares/drag_tree.js
  38. 58 0
      app/public/js/stage.js
  39. 60 0
      app/public/js/sub_project.js
  40. 3 2
      app/public/js/tender_list.js
  41. 3 2
      app/public/js/tender_list_info.js
  42. 3 2
      app/public/js/tender_list_manage.js
  43. 6 2
      app/public/js/tender_list_progress.js
  44. 10 10
      app/public/js/tender_showhide.js
  45. 9 0
      app/router.js
  46. 1 1
      app/service/advance_audit.js
  47. 1 1
      app/service/change_apply_audit.js
  48. 8 0
      app/service/change_audit_list.js
  49. 1 1
      app/service/change_plan_audit.js
  50. 1 1
      app/service/change_project_audit.js
  51. 481 0
      app/service/control_price.js
  52. 1 1
      app/service/ledger_audit.js
  53. 1 1
      app/service/material_audit.js
  54. 1 1
      app/service/payment_detail_audit.js
  55. 3 1
      app/service/payment_rpt_audit.js
  56. 32 0
      app/service/project_stopmsg.js
  57. 4 0
      app/service/report.js
  58. 36 3
      app/service/report_memory.js
  59. 1 1
      app/service/revise_audit.js
  60. 4 2
      app/service/stage.js
  61. 4 4
      app/service/stage_audit.js
  62. 32 0
      app/service/stage_bills_pc.js
  63. 2 2
      app/service/sub_proj_info.js
  64. 2 1
      app/service/tender_cache.js
  65. 1 1
      app/view/budget/info.ejs
  66. 2 0
      app/view/budget/list.ejs
  67. 1 1
      app/view/change/index.ejs
  68. 3 3
      app/view/file/file_modal.ejs
  69. 2 0
      app/view/file/index.ejs
  70. 0 17
      app/view/ledger/explode_modal.ejs
  71. 6 3
      app/view/measure/stage.ejs
  72. 8 6
      app/view/setting/manage.ejs
  73. 1 1
      app/view/setting/sub_menu.ejs
  74. 0 0
      app/view/settle/list.ejs
  75. 0 0
      app/view/settle/list_modal.ejs
  76. 1 1
      app/view/stage/audit_modal.ejs
  77. 1 0
      app/view/sub_proj/index.ejs
  78. 96 0
      app/view/tender/ctrl_price.ejs
  79. 2 0
      app/view/tender/ctrl_price_modal.ejs
  80. 12 1
      app/view/tender/detail_modal.ejs
  81. 25 1
      config/web.js
  82. 49 0
      sql/update.sql

+ 1 - 1
app.js

@@ -86,7 +86,7 @@ module.exports = app => {
         app.jsFiles[c] = {};
         for (const a in controller) {
             const action = controller[a];
-            if (app.config.min && action.mergeFiles && action.mergeFile.length > 0) {
+            if (app.config.min && action.mergeFiles && action.mergeFiles.length > 0) {
                 app.jsFiles[c][a] = action.files.concat([JsFiles.webPath + action.mergeFile + '.' + app.config.version + '.min.js']);
             } else {
                 app.jsFiles[c][a] = action.files.concat(action.mergeFiles || []);

+ 6 - 0
app/base/base_budget_service.js

@@ -262,6 +262,12 @@ class BaseBudget extends TreeService {
         return false;
     }
 
+    clearParentingData(data) {
+        data.unit_price = 0;
+        data.quantity = 0;
+        data.total_price = 0;
+    }
+
     async updateCalc(budgetId, data) {
         const helper = this.ctx.helper;
         // 简单验证数据

+ 3 - 5
app/base/base_controller.js

@@ -44,11 +44,9 @@ class BaseController extends Controller {
         }
         menuList.datacollect.display = ctx.session && ctx.session.sessionProject ? ctx.session.sessionProject.showDataCollect : false;
         menuList.payment.display = ctx.session && ctx.session.sessionProject ? ctx.session.sessionProject.showPayment : false;
-        if (ctx.session && ctx.session.sessionProject && ctx.session.sessionProject.page_show && ctx.session.sessionProject.page_show.openManagement) {
-            menuList.management.display = true;
-        } else {
-            menuList.management.display = false;
-        }
+        menuList.management.display = ctx.session && ctx.session.sessionProject ? ctx.session.sessionProject.page_show.openManagement : false;
+        menuList.file.display = ctx.session && ctx.session.sessionProject ? ctx.session.sessionProject.page_show.openFile : false;
+        menuList.construction.display = ctx.session && ctx.session.sessionProject ? ctx.session.sessionProject.page_show.openConstruction : false;
         // 菜单列表
         ctx.menuList = menuList;
         ctx.showProject = false;

+ 6 - 0
app/const/page_show.js

@@ -49,7 +49,13 @@ const defaultSetting = {
     openChangeState: 0,
     isPreset: 0,
     isOnlyChecked: 1,
+    openSettle: 0,
     openStageStart: 0,
+    openDataCollect: 1,
+    openFile: 1,
+    openBudget: 1,
+    openPayment: 1,
+    openConstruction: 1,
 };
 
 

+ 1 - 0
app/const/tender_info.js

@@ -19,6 +19,7 @@ const defaultInfo = {
         projectType: '',
         dealType: '',
         finalCode: '',
+        budgetApprovalCode: '',
     },
     // 参建单位
     construction_unit: {

+ 5 - 0
app/controller/budget_controller.js

@@ -27,6 +27,9 @@ module.exports = app => {
          */
         async list(ctx) {
             try {
+                if (!ctx.session.sessionProject.showBudget) {
+                    throw '该功能已关闭或无法查看';
+                }
                 const renderData = {
                     jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.budget.list),
                     auditConst,
@@ -45,6 +48,8 @@ module.exports = app => {
                 await this.layout('budget/list.ejs', renderData, 'budget/list_modal.ejs');
             } catch (err) {
                 ctx.log(err);
+                ctx.session.postError = err.toString();
+                ctx.redirect(this.menu.menu.dashboard.url);
             }
         }
 

+ 5 - 0
app/controller/change_controller.js

@@ -963,6 +963,11 @@ module.exports = app => {
                     case 'changeOrder':
                         await ctx.service.changeAuditList.changeOrder(data.postData);
                         break;
+                    case 'set_all_valuation':
+                        await ctx.service.changeAuditList.setAllValuation(ctx.change.cid, data.is_valuation);
+                        // 取所有工料表
+                        responseData.data = await ctx.service.changeAuditList.getList(ctx.change.cid);
+                        break;
                     default: throw '参数有误';
                 }
 

+ 3 - 0
app/controller/construction_controller.js

@@ -26,6 +26,9 @@ module.exports = app => {
          */
         async index(ctx) {
             try {
+                if (!ctx.session.sessionProject.page_show.openConstruction) {
+                    throw '该功能已关闭或无法查看';
+                }
                 // 获取用户新建标段权利
                 const accountInfo = await this.ctx.service.projectAccount.getDataById(ctx.session.sessionUser.accountId);
                 const userPermission = accountInfo !== undefined && accountInfo.permission !== ''

+ 198 - 0
app/controller/ctrl_price_controller.js

@@ -0,0 +1,198 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date 2021/10/27
+ * @version
+ */
+const stdDataAddType = {
+    withParent: 1,
+    child: 2,
+    next: 3,
+};
+const LzString = require('lz-string');
+module.exports = app => {
+    class CtrlPriceController extends app.BaseController {
+
+        _getSpreadSetting() {
+            const spreadSetting = {
+                cols: [
+                    {title: '项目节编号', colSpan: '1', rowSpan: '2', field: 'code', hAlign: 0, width: 180, formatter: '@', cellType: 'tree'},
+                    {title: '清单编号', colSpan: '1', rowSpan: '2', field: 'b_code', hAlign: 0, width: 120, 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', comboEdit: true},
+                    {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', readOnly: true},
+                    {title: '清单数量', colSpan: '1', rowSpan: '2', field: 'quantity', hAlign: 2, width: 80, type: 'Number'},
+                    {title: '单价', colSpan: '1', rowSpan: '2', field: 'unit_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 微软雅黑',
+                localCache: { key: 'budget', colWidth: true },
+            };
+            return spreadSetting;
+        }
+
+        async index(ctx) {
+            try {
+                await this.ctx.service.controlPrice.checkInit(ctx.tender);
+                const [stdBills, stdChapters] = await this.ctx.service.valuation.getValuationStdList(
+                    ctx.tender.data.valuation, ctx.tender.data.measure_type);
+
+                const renderData = {
+                    spreadSetting: this._getSpreadSetting(),
+                    jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.tender.ctrlPrice),
+                    stdBills,
+                    stdChapters,
+                };
+                await this.layout('tender/ctrl_price.ejs', renderData, 'tender/ctrl_price_modal.ejs');
+            } catch (err) {
+                ctx.log(err);
+            }
+        }
+
+        async load(ctx) {
+            try {
+                ctx.body = {
+                    err: 0, msg: '',
+                    data: await this.ctx.service.controlPrice.getData(ctx.tender.id),
+                }
+            } catch (err) {
+                ctx.log(err);
+                ctx.ajaxErrorBody(err, '获取数据错误');
+            }
+        }
+
+        async _billsBase(relaService, type, data) {
+            if (isNaN(data.id) || data.id <= 0) throw '数据错误';
+            if (type !== 'add') {
+                if (isNaN(data.count) || data.count <= 0) data.count = 1;
+            }
+            switch (type) {
+                case 'add':
+                    return await relaService.addNodeBatch(this.ctx.tender.id, data.id, {}, data.count);
+                case 'delete':
+                    return await relaService.delete(this.ctx.tender.id, data.id, data.count);
+                case 'up-move':
+                    return await relaService.upMoveNode(this.ctx.tender.id, data.id, data.count);
+                case 'down-move':
+                    return await relaService.downMoveNode(this.ctx.tender.id, data.id, data.count);
+                case 'up-level':
+                    return await relaService.upLevelNode(this.ctx.tender.id, data.id, data.count);
+                case 'down-level':
+                    return await relaService.downLevelNode(this.ctx.tender.id, data.id, data.count);
+            }
+        }
+        async _addStd(relaService, data) {
+            if ((isNaN(data.id) || data.id <= 0) || !data.stdType || !data.stdNode) throw '参数错误';
+
+            let stdLib, addType;
+            switch (data.stdType) {
+                case 'xmj':
+                    stdLib = this.ctx.service.stdXmj;
+                    addType = stdDataAddType.withParent;
+                    break;
+                case 'gcl':
+                    stdLib = this.ctx.service.stdGcl;
+                    const selectNode = await relaService.getDataByKid(this.ctx.tender.id, data.id);
+                    addType = selectNode.b_code ? stdDataAddType.next : stdDataAddType.child;
+                    break;
+                default:
+                    throw '未知标准库';
+            }
+            const stdData = await stdLib.getDataByDataId(data.stdLibId, data.stdNode);
+            switch (addType) {
+                case stdDataAddType.child:
+                    return await relaService.addStdNodeAsChild(this.ctx.tender.id, data.id, stdData);
+                case stdDataAddType.next:
+                    return await relaService.addStdNode(this.ctx.tender.id, data.id, stdData);
+                case stdDataAddType.withParent:
+                    return await relaService.addStdNodeWithParent(this.ctx.tender.id, stdData, stdLib);
+                default:
+                    throw '未知添加方式';
+            }
+        }
+        async _pasteBlock(relaService, data) {
+            if ((isNaN(data.id) || data.id <= 0) ||
+                (!data.tid && data.tid <= 0) ||
+                (!data.block || data.block.length <= 0)) throw '参数错误';
+            return await relaService.pasteBlockData(this.ctx.tender.id, data.id, data.block);
+        }
+        async update(ctx) {
+            try {
+                if (!ctx.tender) throw '项目数据错误';
+
+                const relaService = this.ctx.service.controlPrice;
+                const data = JSON.parse(ctx.request.body.data);
+                if (!data.postType || !data.postData) throw '数据错误';
+                const responseData = { err: 0, msg: '', data: {} };
+
+                switch (data.postType) {
+                    case 'add':
+                    case 'delete':
+                    case 'up-move':
+                    case 'down-move':
+                    case 'up-level':
+                    case 'down-level':
+                        responseData.data = await this._billsBase(relaService, data.postType, data.postData);
+                        break;
+                    case 'update':
+                        ctx.helper.checkDgnQtyPrecision(data.postData);
+                        responseData.data = await relaService.updateCalc(ctx.tender.id, data.postData);
+                        break;
+                    case 'add-std':
+                        responseData.data = await this._addStd(relaService, data.postData);
+                        break;
+                    case 'paste-block':
+                        responseData.data = await this._pasteBlock(relaService, data.postData);
+                        break;
+                    default:
+                        throw '未知操作';
+                }
+                ctx.body = responseData;
+            } catch (err) {
+                this.log(err);
+                ctx.body = this.ajaxErrorBody(err, '数据错误');
+            }
+        }
+
+        async uploadExcel(ctx) {
+            try {
+                if (!ctx.tender) throw '项目数据错误';
+
+                const ueType = ctx.params.ueType;
+                const compressData = ctx.request.body.data;
+                const data = JSON.parse(LzString.decompressFromUTF16(compressData));
+                const responseData = { err: 0, msg: '', data: {} };
+                switch (ueType) {
+                    case 'tz':
+                        const templateId = await this.ctx.service.valuation.getValuationTemplate(
+                            this.ctx.tender.data.valuation, this.ctx.tender.data.measure_type);
+                        responseData.data = await this.ctx.service.controlPrice.importExcel(templateId, data.sheet, data.filter);
+                        break;
+                    case 'gcl2xmj':
+                        responseData.data = await this.ctx.service.controlPrice.importGclExcel(data.id, data.sheet);
+                        break;
+                    default:
+                        throw '数据错误';
+                }
+                ctx.body = responseData;
+            } catch (err) {
+                this.log(err);
+                ctx.body = { err: 1, msg: err.toString(), data: null };
+            }
+        }
+    }
+
+    return CtrlPriceController;
+};

+ 6 - 1
app/controller/file_controller.js

@@ -23,6 +23,9 @@ module.exports = app => {
          */
         async index(ctx) {
             try {
+                if (!ctx.session.sessionProject.page_show.openFile) {
+                    throw '该功能已关闭或无法查看';
+                }
                 const renderData = {
                     jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.file.index),
                     auditConst,
@@ -33,6 +36,8 @@ module.exports = app => {
                 await this.layout('file/index.ejs', renderData, 'file/modal.ejs');
             } catch (err) {
                 ctx.log(err);
+                ctx.session.postError = err.toString();
+                ctx.redirect(this.menu.menu.dashboard.url);
             }
         }
 
@@ -345,4 +350,4 @@ module.exports = app => {
     }
 
     return BudgetController;
-};
+};

+ 20 - 6
app/controller/login_controller.js

@@ -142,7 +142,9 @@ module.exports = app => {
                     throw '该微信号未绑定此项目';
                 }
                 if (pa.enable !== 1) {
-                    throw '该账号已被停用,请联系销售人员';
+                    // 判断是否有设置停用提示,有则展示
+                    const msg = await ctx.service.projectStopmsg.getMsg(projectData.id);
+                    throw msg;
                 }
                 // 设置项目和用户session记录
                 const result = await ctx.service.projectAccount.accountLogin({ project: projectData, accountData: pa }, 3);
@@ -176,7 +178,11 @@ module.exports = app => {
                 }
 
                 if (result === 2) {
-                    throw '该账号已被停用,请联系销售人员';
+                    // 查找项目数据
+                    const projectData = await this.ctx.service.project.getProjectByCode(ctx.request.body.project.toString().trim());
+                    // 判断是否有设置停用提示,有则展示
+                    const msg = await ctx.service.projectStopmsg.getMsg(projectData.id);
+                    throw msg;
                 }
 
                 // 调用 rotateCsrfSecret 刷新用户的 CSRF token
@@ -320,7 +326,9 @@ module.exports = app => {
                     throw '';
                 } else {
                     if (pa.enable !== 1) {
-                        throw '该账号已被停用,请联系销售人员';
+                        // 判断是否有设置停用提示,有则展示
+                        const msg = await ctx.service.projectStopmsg.getMsg(ctx.projectData.id);
+                        throw msg;
                     }
                     const result = await ctx.service.projectAccount.accountLogin({ project: ctx.projectData, accountData: pa }, 3);
                     if (!result) {
@@ -373,7 +381,9 @@ module.exports = app => {
                     }
 
                     if (pa.enable !== 1) {
-                        throw '该账号已被停用,请联系销售人员';
+                        // 判断是否有设置停用提示,有则展示
+                        const msg = await ctx.service.projectStopmsg.getMsg(pa.project_id);
+                        throw msg;
                     }
                     const updateData = {
                         bind: 1,
@@ -544,7 +554,9 @@ module.exports = app => {
                   throw '您无权限登录系统。';
               }
               if (account.enable !== 1) {
-                throw '该账号已被停用,请联系销售人员';
+                  // 判断是否有设置停用提示,有则展示
+                  const msg = await ctx.service.projectStopmsg.getMsg(project.id);
+                  throw msg;
             }
             const result = await ctx.service.projectAccount.accountLogin({ project, accountData: account }, 3);
             if (!result) {
@@ -612,7 +624,9 @@ module.exports = app => {
               throw '用户名或密码错误';
             }
             if (result === 2) {
-                throw '该账号已被停用,请联系销售人员';
+                // 判断是否有设置停用提示,有则展示
+                const msg = await ctx.service.projectStopmsg.getMsg(projectData.id);
+                throw msg;
             }
           } catch (error) {
             response.code = -1;

+ 3 - 0
app/controller/measure_controller.js

@@ -52,6 +52,9 @@ module.exports = app => {
                 for (const s of renderData.stages) {
                     // 根据期状态返回展示用户
                     s.curAuditors = await ctx.service.stageAudit.getAuditorsByStatus(s.id, s.status, s.times);
+                    if (s.status === auditConst.status.checkNoPre) {
+                        s.curAuditors2 = await ctx.service.stageAudit.getAuditorsByStatus(s.id, auditConst.status.checking, s.times);
+                    }
                 }
                 await this.layout('measure/stage.ejs', renderData, 'measure/stage_modal.ejs');
             } catch (err) {

+ 12 - 0
app/controller/payment_controller.js

@@ -31,6 +31,9 @@ module.exports = app => {
          */
         async index(ctx) {
             try {
+                if (!ctx.session.sessionProject.showPayment) {
+                    throw '该功能已关闭或无法查看';
+                }
                 const auditPermission = await this.ctx.service.paymentPermissionAudit.getOnePermission(ctx.session.sessionUser.is_admin, ctx.session.sessionUser.accountId);
                 if (!auditPermission) {
                     throw '权限不足';
@@ -96,6 +99,9 @@ module.exports = app => {
 
         async permissionSave(ctx) {
             try {
+                if (!ctx.session.sessionProject.showPayment) {
+                    throw '该功能已关闭或无法查看';
+                }
                 if (ctx.session.sessionUser.is_admin === 0) throw '没有设置权限';
                 const projectId = ctx.session.sessionProject.id;
                 const responseData = {
@@ -157,6 +163,9 @@ module.exports = app => {
 
         async paymentInfoSave(ctx) {
             try {
+                if (!ctx.session.sessionProject.showPayment) {
+                    throw '该功能已关闭或无法查看';
+                }
                 const data = JSON.parse(ctx.request.body.data);
                 if (!data.type) throw '提交数据错误';
                 switch (data.type) {
@@ -174,6 +183,9 @@ module.exports = app => {
 
         async save(ctx) {
             try {
+                if (!ctx.session.sessionProject.showPayment) {
+                    throw '该功能已关闭或无法查看';
+                }
                 const projectId = ctx.session.sessionProject.id;
                 const auditPermission = await this.ctx.service.paymentPermissionAudit.getOnePermission(ctx.session.sessionUser.is_admin, ctx.session.sessionUser.accountId);
                 if (!auditPermission) {

+ 50 - 0
app/controller/settle_controller.js

@@ -0,0 +1,50 @@
+'use strict';
+
+/**
+ * 过程结算相关控制器
+ *
+ * @author Mai
+ * @date 2023/10/27
+ * @version
+ */
+
+
+module.exports = app => {
+
+    class SettleController extends app.BaseController {
+
+        /**
+         * 构造函数
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        constructor(ctx) {
+            super(ctx);
+            ctx.showProject = true;
+            ctx.showTender = true;
+            ctx.showTitle = true;
+        }
+
+        /**
+         * 期列表(Get)
+         * @param ctx
+         * @return {Promise<void>}
+         */
+        async list(ctx) {
+            try {
+                const renderData = {
+                    tender: ctx.tender.data,
+                    preUrl: `/tender/${ctx.tender.id}/measure/stage`,
+                };
+                renderData.settles = await ctx.service.settle.getValidSettle(ctx.tender.id);
+                await this.layout('settle/list.ejs', renderData, 'settle/list_modal.ejs');
+            } catch (err) {
+                this.log(err);
+                ctx.redirect(this.menu.menu.dashboard.url);
+            }
+        }
+    }
+
+    return SettleController;
+};

+ 5 - 0
app/controller/sub_proj_controller.js

@@ -20,6 +20,9 @@ module.exports = app => {
          */
         async index(ctx) {
             try {
+                if (!ctx.session.sessionProject.showSubProj) {
+                    throw '该功能已关闭或无法查看';
+                }
                 const renderData = {
                     jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.subProject.list),
                     auditConst,
@@ -43,6 +46,8 @@ module.exports = app => {
                 await this.layout('sub_proj/index.ejs', renderData, 'sub_proj/modal.ejs');
             } catch (err) {
                 ctx.log(err);
+                ctx.session.postError = err.toString();
+                ctx.redirect(this.menu.menu.dashboard.url);
             }
         }
 

+ 3 - 0
app/controller/template_controller.js

@@ -34,6 +34,9 @@ module.exports = app => {
                         case '估概预算示例EXCEL格式.xlsx':
                             fileName = path.join(this.app.baseDir, 'app', 'public', 'files', 'template', file);
                             break;
+                        case '控制价示例EXCEL格式.xlsx':
+                            fileName = path.join(this.app.baseDir, 'app', 'public', 'files', 'template', file);
+                            break;
                         default:
                             throw '参数错误'
                     }

+ 3 - 1
app/controller/wap_controller.js

@@ -73,7 +73,9 @@ module.exports = app => {
                 }
 
                 if (result === 2) {
-                    throw '该账号已被停用,请联系销售人员';
+                    // 判断是否有设置停用提示,有则展示
+                    const msg = await ctx.service.projectStopmsg.getMsg(ctx.session.sessionProject.id);
+                    throw msg;
                 }
 
                 // 调用 rotateCsrfSecret 刷新用户的 CSRF token

+ 16 - 4
app/controller/wechat_controller.js

@@ -98,7 +98,11 @@ module.exports = app => {
                 }
 
                 if (result === 2) {
-                    throw '该账号已被停用,请联系销售人员';
+                    // 查找项目数据
+                    const projectData = await this.ctx.service.project.getProjectByCode(ctx.request.body.project.toString().trim());
+                    // 判断是否有设置停用提示,有则展示
+                    const msg = await ctx.service.projectStopmsg.getMsg(projectData.id);
+                    throw msg;
                 }
                 const accountData = result;
                 if (accountData.wx_openid || ctx.session.wechatToken.openid === accountData.wx_openid) {
@@ -168,7 +172,9 @@ module.exports = app => {
                     throw '该微信号未绑定此项目';
                 }
                 if (pa.enable !== 1) {
-                    throw '该账号已被停用,请联系销售人员';
+                    // 判断是否有设置停用提示,有则展示
+                    const msg = await ctx.service.projectStopmsg.getMsg(projectData.id);
+                    throw msg;
                 }
                 // 设置项目和用户session记录
                 const result = await ctx.service.projectAccount.accountLogin({ project: projectData, accountData: pa }, 3);
@@ -379,7 +385,11 @@ module.exports = app => {
                 }
 
                 if (result === 2) {
-                    throw '该账号已被停用,请联系销售人员';
+                    // 查找项目数据
+                    const projectData = await this.ctx.service.project.getProjectByCode(ctx.request.body.project.toString().trim());
+                    // 判断是否有设置停用提示,有则展示
+                    const msg = await ctx.service.projectStopmsg.getMsg(projectData.id);
+                    throw msg;
                 }
                 const accountData = result;
                 const qywx_userid = ctx.request.body.userid;
@@ -464,7 +474,9 @@ module.exports = app => {
                     throw '该企业微信号未绑定此项目';
                 }
                 if (pa.enable !== 1) {
-                    throw '该账号已被停用,请联系销售人员';
+                    // 判断是否有设置停用提示,有则展示
+                    const msg = await ctx.service.projectStopmsg.getMsg(projectData.id);
+                    throw msg;
                 }
                 // 设置项目和用户session记录
                 const result = await ctx.service.projectAccount.accountLogin({ project: projectData, accountData: pa }, 3);

+ 1 - 1
app/lib/budget_final.js

@@ -135,7 +135,7 @@ class BudgetFinal {
 
     async _loadZb(budget) {
         const helper = this.ctx.helper;
-        const zb = await this.ctx.service.budgetYu.getData(budget.id);
+        const zb = await this.ctx.service.budgetZb.getData(budget.id);
         const zbTree = new BillsTree(this.ctx, { id: 'tree_id', pid: 'tree_pid', order: 'order', level: 'level', rootId: -1, calcFields: ['total_price'] });
         zbTree.loadDatas(zb);
         zbTree.calculateAll();

+ 7 - 2
app/middleware/budget_check.js

@@ -19,11 +19,16 @@ module.exports = options => {
      */
     return function* budgetCheck(next) {
         try {
+            if (!this.session.sessionProject.showBudget) {
+                throw '该功能已关闭或无法查看';
+            }
             // 读取标段数据
             const id = parseInt(this.params.id);
             if (!id) throw '参数错误';
             this.budget = yield this.service.budget.getCurBudget(id);
             if (!this.budget) throw '项目不存在';
+            const subProj = yield this.service.subProject.getDataByCondition({ budget_id: this.budget.id });
+            if (subProj) this.budget.name = subProj.name || '';
             if (this.session.sessionUser.is_admin) {
                 this.budget.readOnly = false;
             } else {
@@ -38,8 +43,8 @@ module.exports = options => {
                 this.ajaxErrorBody(err, '概算投资项目未知错误');
             } else {
                 this.postError(err, '概算投资项目未知错误');
-                this.redirect(this.request.headers.referer);
+                err === '该功能已关闭或无法查看' ? this.redirect('/dashboard') : this.redirect(this.request.headers.referer);
             }
         }
     };
-};
+};

+ 5 - 1
app/middleware/construction_check.js

@@ -22,6 +22,9 @@ module.exports = options => {
      */
     return function* constructionCheck(next) {
         try {
+            if (!this.session.sessionProject.page_show.openConstruction) {
+                throw '该功能已关闭或无法查看';
+            }
             const id = parseInt(this.params.tid);
             if (!id) throw '参数错误';
             const tender = yield this.service.tender.getTender(id, ['id', 'project_id', 'name']);
@@ -62,7 +65,8 @@ module.exports = options => {
                 if (this.helper.isWap(this.request)) {
                     this.redirect('/wap/list');
                 } else {
-                    err === '您无权查看该内容' ? this.redirect(this.request.headers.referer) : this.redirect('/construction');
+                    this.postError(err, '未知错误');
+                    err === '该功能已关闭或无法查看' ? this.redirect('/dashboard') : (err === '您无权查看该内容' ? this.redirect(this.request.headers.referer) : this.redirect('/construction'));
                 }
             }
         }

+ 5 - 1
app/middleware/payment_tender_check.js

@@ -23,6 +23,9 @@ module.exports = options => {
      */
     return function* paymentTenderCheck(next) {
         try {
+            if (!this.session.sessionProject.showPayment) {
+                throw '该功能已关闭或无法查看';
+            }
             if (!this.params.id) {
                 throw '当前未打开标段';
             }
@@ -73,7 +76,8 @@ module.exports = options => {
                 if (this.helper.isWap(this.request)) {
                     this.redirect('/wap/list');
                 } else {
-                    err === '您无权查看该内容' ? this.redirect(this.request.headers.referer) : this.redirect('/payment');
+                    this.postError(err, '未知错误');
+                    err === '该功能已关闭或无法查看' ? this.redirect('/dashboard') : (err === '您无权查看该内容' ? this.redirect(this.request.headers.referer) : this.redirect('/payment'));
                 }
             }
         }

+ 9 - 12
app/middleware/session_auth.js

@@ -40,7 +40,7 @@ module.exports = options => {
             this.session.sessionProject.funSet = projectData.fun_set ? JSON.parse(projectData.fun_set) : null;
             // 判断是否有权限查看决策大屏
             let showDataCollect = 0;
-            if (projectData.data_collect) {
+            if (projectData.data_collect && this.session.sessionProject.page_show.openDataCollect) {
                 if (sessionUser.is_admin) {
                     showDataCollect = 1;
                 } else {
@@ -83,20 +83,17 @@ module.exports = options => {
             let showPayment = 0;
             if (sessionUser.is_admin) {
                 this.session.sessionProject.showSubProj = true;
-                this.session.sessionProject.showBudget = true;
-                showPayment = 1;
+                this.session.sessionProject.showBudget = this.session.sessionProject.page_show.openBudget;
+                showPayment = this.session.sessionProject.page_show.openPayment ? 1 : 0;
             } else {
                 this.session.sessionProject.showSubProj = false;
-                this.session.sessionProject.showBudget = yield this.service.subProjPermission.showBudget(sessionUser.accountId);
-                // const grounpInfo = yield this.service.paymentPermissionAudit.getGroupInfo(projectData.id, accountInfo.account_group);
-                // if (grounpInfo) {
-                //     showPayment = 1;
-                // } else {
-                const auditInfo = yield this.service.paymentPermissionAudit.getDataByCondition({ pid: projectData.id, uid: accountInfo.id });
-                if (auditInfo) {
-                    showPayment = 1;
+                this.session.sessionProject.showBudget = this.session.sessionProject.page_show.openBudget ? yield this.service.subProjPermission.showBudget(sessionUser.accountId) : false;
+                if (this.session.sessionProject.page_show.openPayment) {
+                    const auditInfo = yield this.service.paymentPermissionAudit.getDataByCondition({ pid: projectData.id, uid: accountInfo.id });
+                    if (auditInfo) {
+                        showPayment = 1;
+                    }
                 }
-                // }
             }
             this.session.sessionProject.showPayment = showPayment;
 

+ 5 - 2
app/middleware/sub_project_check.js

@@ -19,6 +19,9 @@ module.exports = options => {
      */
     return function* subProjectCheck(next) {
         try {
+            if (!this.session.sessionProject.page_show.openFile && !this.session.sessionProject.showSubProj) {
+                throw '该功能已关闭或无法查看';
+            }
             // 读取标段数据
             const id = this.params.id || this.query.id;
             if (!id) throw '参数错误';
@@ -41,8 +44,8 @@ module.exports = options => {
                 this.ajaxErrorBody(err, '未知错误');
             } else {
                 this.postError(err, '未知错误');
-                this.redirect(this.request.headers.referer);
+                err === '该功能已关闭或无法查看' ? this.redirect('/dashboard') : this.redirect(this.request.headers.referer);
             }
         }
     };
-};
+};

+ 4 - 0
app/public/css/main.css

@@ -2152,3 +2152,7 @@ animation:shake 1s .2s ease both;}
 .form-control-width{
     min-width: 450px;
 }
+.vertical-middle{
+    display: flex;
+    margin: auto;
+}

BIN
app/public/files/template/控制价示例EXCEL格式.xlsx


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

@@ -284,7 +284,7 @@ $(document).ready(() => {
                                             <td class="text-left pl-3">${level2.name}</td>
                                             <td>${level2.gai_tp ? ZhCalc.div(level2.gai_tp, 10000) : 0}</td>
                                             <td>${level2.final_tp ? ZhCalc.div(level2.final_tp, 10000) : 0}</td>
-                                            <td class="${ZhCalc.sub(level2.final_tp, level2.gai_tp) > 0 ? 'text-danger' : ZhCalc.sub(level2.final_tp, level2.gai_tp) === 0 ? '' : 'text-success'}">${ZhCalc.div(ZhCalc.sub(level2.final_tp, level2.gai_tp), 10000)}</td>
+                                            <td class="${level2.gai_tp !== 0 ? (ZhCalc.sub(level2.final_tp, level2.gai_tp) > 0 ? 'text-danger' : ZhCalc.sub(level2.final_tp, level2.gai_tp) === 0 ? '' : 'text-success') : ''}">${level2.gai_tp ? ZhCalc.round(ZhCalc.div(ZhCalc.sub(level2.final_tp, level2.gai_tp), level2.gai_tp), 2) : 0}</td>
                                         </tr>`;
                 }
                 $('#jianan-table').html(jianAnHtml);

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

@@ -92,6 +92,22 @@ $(document).ready(() => {
                 const selectId = $('tr.table-active').attr('tree_id');
                 return selectId || setting.treeSetting.rootId;
             },
+            expandByLevel: function(level){
+                budgetTree.expandByLevel(level);
+                for (const node of budgetTree.nodes) {
+                    const tr = $(`tr[tree_id=${node.id}]`);
+                    if (node.expanded) {
+                        $('.fold-switch', tr).html(`<i class="fa fa-minus-square-o"></i>`);
+                    } else {
+                        $('.fold-switch', tr).html(`<i class="fa fa-plus-square-o"></i>`);
+                    }
+                    if (node.visible) {
+                        tr.show();
+                    } else {
+                        tr.hide();
+                    }
+                }
+            }
         };
 
         Utils.reloadTable();
@@ -114,6 +130,48 @@ $(document).ready(() => {
                 }
             }
         });
+        const getChildrenLevel = function (node) {
+            let iLevel = node.tree_level || 1;
+            if (node.children && node.children.length > 0) {
+                for (const c of node.children) {
+                    iLevel = Math.max(iLevel, getChildrenLevel(c));
+                }
+            }
+            return iLevel;
+        };
+        let tenderTreeShowLevel = $.cs_showLevel({
+            selector: '#show-level',
+            levels: [
+                {
+                    type: 'sort', count: 5, visible_count: function () {
+                        return budgetTree.children.map(getChildrenLevel).reduce((x, y) => { return Math.max(x, y); }, 0) - 1;
+                    }
+                },
+                {
+                    type: 'last', title: '最底层', visible: function () {
+                        const count = budgetTree.children.map(getChildrenLevel).reduce((x, y) => { return Math.max(x, y); }, 0) - 1;
+                        return count > 0;
+                    }
+                },
+            ],
+            showLevel: function (tag) {
+                switch (tag) {
+                    case "1":
+                    case "2":
+                    case "3":
+                    case "4":
+                    case "5":
+                        Utils.expandByLevel(parseInt(tag));
+                        break;
+                    case "last":
+                        Utils.expandByLevel(20);
+                        break;
+                    default: return;
+                }
+            }
+        });
+        tenderTreeShowLevel.initShowLevel();
+        tenderTreeShowLevel.refreshMenuVisible();
         return { budgetTree, TableObj, ...Utils };
     })({
         treeSetting: { id: 'id', pid: 'tree_pid', level: 'tree_level', order: 'tree_order', rootId: '-1' },

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

@@ -757,6 +757,23 @@ $(document).ready(() => {
             // 防止ctrl+z撤销数据
             SpreadJsObj.reLoadRowData(info.sheet, info.row);
         },
+        setAllValuation(is_valuation) {
+            if (changeList.length === 0) {
+                toastr.warning('暂无清单无法设置清单计价');
+                return;
+            }
+            const needChangeList = _.filter(changeList, { is_valuation: is_valuation ? 0 : 1 });
+            if (needChangeList.length === 0) {
+                toastr.warning('全部清单已设置清单'+ (is_valuation ? '' : '不') +'计价');
+                return;
+            }
+            postData(window.location.pathname + '/save', { type:'set_all_valuation', is_valuation }, function (result) {
+                changeList = result;
+                SpreadJsObj.loadSheetData(changeSpreadSheet, SpreadJsObj.DataType.Data, changeList);
+                changeSpreadObj.makeSjsFooter();
+                changeSpreadObj.resetXmjSpread(SpreadJsObj.getSelectObject(changeSpreadSheet));
+            });
+        },
         _checkExprValid(expr) {
             if (!expr) return [true, null];
             const param = [];
@@ -1237,6 +1254,22 @@ $(document).ready(() => {
                         return isUsed;
                     }
                 },
+                sprEdit: '----',
+                'allNotValuation': {
+                    name: '设置全部清单不计价',
+                    icon: 'fa-magic',
+                    callback: function (key, opt) {
+                        changeSpreadObj.setAllValuation(0);
+                    },
+                },
+                'allValuation': {
+                    name: '设置全部清单计价',
+                    icon: 'fa-magic',
+                    callback: function (key, opt) {
+                        changeSpreadObj.setAllValuation(1);
+                    },
+                },
+
             }
         });
 

+ 779 - 0
app/public/js/ctrl_price.js

@@ -0,0 +1,779 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date
+ * @version
+ */
+
+const invalidFields = {
+    parent: ['quantity', 'total_price', 'unit_price'],
+    gcl: ['dgn_qty1', 'dgn_qty2', 'total_price'],
+    xmj: ['quantity', 'unit_price'],
+};
+$(document).ready(() => {
+    const copyBlockTag = 'zh.calc.copyBlock';
+    let stdXmj, stdGcl, searchCtrlPrice;
+    autoFlashHeight();
+    const priceSpread = SpreadJsObj.createNewSpread($('#ctrl-price-spread')[0]);
+    const priceSheet = priceSpread.getActiveSheet();
+    sjsSettingObj.setFxTreeStyle(spreadSetting, sjsSettingObj.FxTreeStyle.jz);
+    spreadSetting.readOnly = readOnly;
+    SpreadJsObj.initSheet(priceSheet, 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();
+            priceSpread.refresh();
+        }
+    });
+    const priceTree = createNewPathTree('ledger', {
+        id: 'tree_id',
+        pid: 'tree_pid',
+        order: 'order',
+        level: 'level',
+        rootId: -1,
+        keys: ['id', 'tid', 'tree_id'],
+        autoExpand: 3,
+        markExpandKey: 'price-expand',
+        markExpandSubKey: window.location.pathname.split('/')[2],
+        calcFields: ['total_price'],
+        calcFun: function (node) {
+            node.dgn_price = ZhCalc.div(node.total_price, node.dgn_qty1, 2);
+        },
+    });
+
+    $.divResizer({
+        select: '#right-spr',
+        callback: function () {
+            priceSpread.refresh();
+            if (stdXmj) stdXmj.spread.refresh();
+            if (stdGcl) stdGcl.spread.refresh();
+            if (searchCtrlPrice) searchCtrlPrice.spread.refresh();
+        }
+    });
+
+    const priceTreeOpr = {
+        refreshTree: function (sheet, data) {
+            SpreadJsObj.massOperationSheet(sheet, function () {
+                const tree = sheet.zh_tree;
+                // 处理删除
+                if (data.delete) {
+                    data.delete.sort(function (a, b) {
+                        return b.deleteIndex - a.deleteIndex;
+                    });
+                    for (const d of data.delete) {
+                        sheet.deleteRows(d.deleteIndex, 1);
+                    }
+                }
+                // 处理新增
+                if (data.create) {
+                    const newNodes = data.create;
+                    if (newNodes) {
+                        newNodes.sort(function (a, b) {
+                            return a.index - b.index;
+                        });
+
+                        for (const node of newNodes) {
+                            sheet.addRows(node.index, 1);
+                            SpreadJsObj.reLoadRowData(sheet, tree.nodes.indexOf(node), 1);
+                        }
+                    }
+                }
+                // 处理更新
+                if (data.update) {
+                    const rows = [];
+                    for (const u of data.update) {
+                        rows.push(tree.nodes.indexOf(u));
+                    }
+                    SpreadJsObj.reLoadRowsData(sheet, rows);
+                }
+                // 处理展开
+                if (data.expand) {
+                    const expanded = [];
+                    for (const e of data.expand) {
+                        if (expanded.indexOf(e) === -1) {
+                            const posterity = tree.getPosterity(e);
+                            for (const p of posterity) {
+                                sheet.setRowVisible(tree.nodes.indexOf(p), p.visible);
+                                expanded.push(p);
+                            }
+                        }
+                    }
+                }
+            });
+        },
+        getDefaultSelectInfo: function (sheet) {
+            const tree = sheet.zh_tree;
+            if (!tree) return;
+            const sel = sheet.getSelections()[0];
+            const node = sheet.zh_tree.nodes[sel.row];
+            if (!node) return;
+            let count = 1;
+            if (sel.rowCount > 1) {
+                for (let r = 1; r < sel.rowCount; r++) {
+                    const rNode = sheet.zh_tree.nodes[sel.row + r];
+                    if (rNode.level > node.level) continue;
+                    if ((rNode.level < node.level) || (rNode.level === node.level && rNode.pid !== node.pid)) {
+                        toastr.warning('请选择同一节点下的节点,进行该操作');
+                        return;
+                    }
+                    count += 1;
+                }
+            }
+            return [tree, node, count];
+        },
+        /**
+         * 刷新顶部按钮是否可用
+         * @param sheet
+         * @param selections
+         */
+        refreshOperationValid: function (sheet, selection) {
+            const setObjEnable = function (obj, enable) {
+                if (enable) {
+                    obj.removeClass('disabled');
+                } else {
+                    obj.addClass('disabled');
+                }
+            };
+            const invalidAll = function () {
+                setObjEnable($('a[name=base-opr][type=add]'), false);
+                setObjEnable($('a[name=base-opr][type=delete]'), false);
+                setObjEnable($('a[name=base-opr][type=up-move]'), false);
+                setObjEnable($('a[name=base-opr][type=down-move]'), false);
+                setObjEnable($('a[name=base-opr][type=up-level]'), false);
+                setObjEnable($('a[name=base-opr][type=down-level]'), false);
+            };
+            const sel = selection ? selection[0] : sheet.getSelections()[0];
+            const row = sel ? sel.row : -1;
+            const tree = sheet.zh_tree;
+            if (!tree) {
+                invalidAll();
+                return;
+            }
+            const first = sheet.zh_tree.nodes[row];
+            if (!first) {
+                invalidAll();
+                return;
+            }
+            let last = first, sameParent = true;
+            if (sel.rowCount > 1 && first) {
+                for (let r = 1; r < sel.rowCount; r++) {
+                    const rNode = tree.nodes[sel.row + r];
+                    if (!rNode) {
+                        sameParent = false;
+                        break;
+                    }
+                    if (rNode.level > first.level) continue;
+                    if ((rNode.level < first.level) || (rNode.level === first.level && rNode.pid !== first.pid)) {
+                        sameParent = false;
+                        break;
+                    }
+                    last = rNode;
+                }
+            }
+            const preNode = tree.getPreSiblingNode(first);
+            const valid = !readOnly;
+
+            setObjEnable($('a[name=base-opr][type=add]'), valid && first && first.level > 1);
+            setObjEnable($('a[name=base-opr][type=delete]'), valid && first && sameParent && first.level > 1);
+            setObjEnable($('a[name=base-opr][type=up-move]'), valid && first && sameParent && first.level > 1 && preNode);
+            setObjEnable($('a[name=base-opr][type=down-move]'), valid && first && sameParent && first.level > 1 && !tree.isLastSibling(last));
+            setObjEnable($('a[name=base-opr][type=up-level]'), valid && first && sameParent && tree.getParent(first) && first.level > 2 && tree.isLastSibling(last));
+            setObjEnable($('a[name=base-opr][type=down-level]'), valid && first && sameParent && first.level > 1 && preNode);
+        },
+        selectionChanged: function (e, info) {
+            if (info.newSelections) {
+                if (!info.oldSelections || info.newSelections[0].row !== info.oldSelections[0].row) {
+                    priceTreeOpr.refreshOperationValid(info.sheet);
+                }
+            }
+        },
+        /**
+         * 新增节点
+         * @param spread
+         */
+        baseOpr: function (sheet, type, addCount = 1) {
+            const self = this;
+            const [tree, node, count] = this.getDefaultSelectInfo(sheet);
+            if (!tree || !node || !count) return;
+
+            if (type === 'delete') {
+                deleteAfterHint(function () {
+                    postData(window.location.pathname + '/update', {
+                        postType: type,
+                        postData: { id: node.tree_id, count: type === 'add' ? addCount : count }
+                    }, function (result) {
+                        const refreshData = tree.loadPostData(result);
+                        self.refreshTree(sheet, refreshData);
+                        const sel = sheet.getSelections()[0];
+                        if (sel) {
+                            sheet.setSelection(sel.row, sel.col, 1, sel.colCount);
+                        }
+                        self.refreshOperationValid(sheet);
+                    });
+                });
+            } else {
+                postData(window.location.pathname + '/update', {
+                    postType: type,
+                    postData: { id: node.tree_id, count: type === 'add' ? addCount : count }
+                }, function (result) {
+                    const refreshData = tree.loadPostData(result);
+                    self.refreshTree(sheet, refreshData);
+                    if (['up-move', 'down-move'].indexOf(type) > -1) {
+                        const sel = sheet.getSelections()[0];
+                        if (sel) {
+                            sheet.setSelection(tree.nodes.indexOf(node), sel.col, sel.rowCount, sel.colCount);
+                            SpreadJsObj.reloadRowsBackColor(sheet, [sel.row, tree.nodes.indexOf(node)]);
+                        }
+                    } else if (type === 'add') {
+                        const sel = sheet.getSelections()[0];
+                        if (sel) {
+                            sheet.setSelection(tree.nodes.indexOf(refreshData.create[0]), sel.col, sel.rowCount, sel.colCount);
+                            SpreadJsObj.reloadRowsBackColor(sheet, [sel.row, tree.nodes.indexOf(refreshData.create[0])]);
+                        }
+                    }
+                    self.refreshOperationValid(sheet);
+                });
+            }
+        },
+        editStarting(e, info) {
+            if (!info.sheet.zh_setting || !info.sheet.zh_tree) return;
+            const col = info.sheet.zh_setting.cols[info.col];
+            const node = info.sheet.zh_tree.nodes[info.row];
+            if (!node) {
+                info.cancel = true;
+                return;
+            }
+            switch (col.field) {
+                case 'unit_price':
+                case 'quantity':
+                    info.cancel = (node.children && node.children.length > 0) || !node.b_code;
+                    break;
+                case 'total_price':
+                    info.cancel = (node.children && node.children.length > 0) || node.b_code;
+                    break;
+                case 'dgn_price':
+                    info.cance = false;
+                    break;
+                case 'dgn_qty1':
+                case 'dgn_qty2':
+                    info.cancel = !_.isEmpty(node.b_code);
+                    break;
+            }
+        },
+        /**
+         * 编辑单元格响应事件
+         * @param {Object} e
+         * @param {Object} info
+         */
+        editEnded: function (e, info) {
+            if (info.sheet.zh_setting) {
+                const col = info.sheet.zh_setting.cols[info.col];
+                const sortData = info.sheet.zh_tree.nodes;
+                const node = sortData[info.row];
+                const data = { id: node.id, tid: node.tid, tree_id: node.tree_id };
+                // 未改变值则不提交
+                const orgValue = node[col.field];
+                const newValue = trimInvalidChar(info.editingText);
+                if (orgValue == info.editingText || ((!orgValue || orgValue === '') && (newValue === ''))) return;
+
+                if (node.b_code && invalidFields.gcl.indexOf(col.field) >=0) {
+                    toastr.error('工程量清单请勿输入设计数量、金额');
+                    return;
+                }
+
+                // 获取更新数据
+                if (newValue) {
+                    if (col.type === 'Number') {
+                        const num = _.toNumber(newValue);
+                        if (_.isNaN(num)) {
+                            toastr.error('请输入数字');
+                            SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                            return;
+                        }
+                        data[col.field] = num;
+                    } else {
+                        data[col.field] = newValue;
+                    }
+                } else {
+                    data[col.field] = col.type === 'Number' ? 0 : '';
+                }
+                console.log(data);
+                // 更新至服务器
+                postData(window.location.pathname + '/update', {postType: 'update', postData: data}, function (result) {
+                    const refreshNode = priceTree.loadPostData(result);
+                    priceTreeOpr.refreshTree(info.sheet, refreshNode);
+                });
+            }
+        },
+        clipboardPasting: function (e, info) {
+            const tree = info.sheet.zh_tree, setting = info.sheet.zh_setting;
+            info.cancel = true;
+            if (!setting || !tree) return;
+
+            const pasteData = info.pasteData.html
+                ? SpreadJsObj.analysisPasteHtml(info.pasteData.html)
+                : (info.pasteData.text === ''
+                    ? SpreadJsObj.Clipboard.getAnalysisPasteText()
+                    : SpreadJsObj.analysisPasteText(info.pasteData.text));
+            const hint = {
+                invalidNum: {type: 'warning', msg: '粘贴的数字非法'},
+                parent: {type: 'warning', msg: `含有子项,不可粘贴${needGcl ? '数量、单价、' : ''}金额`},
+                gcl: {type: 'warning', msg: '工程量清单,不可粘贴项目节数量、金额'},
+                xmj: {type: 'warning', msg: '项目节,不可粘贴数量、单价'},
+            };
+            const datas = [], filterNodes = [];
+
+            let filterRow = 0;
+            for (let iRow = 0; iRow < info.cellRange.rowCount; iRow ++) {
+                const curRow = info.cellRange.row + iRow;
+                const node = tree.nodes[curRow];
+                if (!node) continue;
+
+                let bPaste = false;
+                const data = info.sheet.zh_tree.getNodeKeyData(node);
+                for (let iCol = 0; iCol < info.cellRange.colCount; iCol++) {
+                    const curCol = info.cellRange.col + iCol;
+                    const colSetting = info.sheet.zh_setting.cols[curCol];
+                    const value = trimInvalidChar(pasteData[iRow-filterRow][iCol]);
+                    if (node.children && node.children.length > 0 && invalidFields.parent.indexOf(colSetting.field) >= 0) {
+                        toastMessageUniq(hint.parent);
+                        continue;
+                    }
+                    if (!_.isEmpty(node.b_code) && invalidFields.gcl.indexOf(colSetting.field) >= 0) {
+                        toastMessageUniq(hint.gcl);
+                        continue;
+                    }
+                    if (_.isEmpty(node.b_code) && invalidFields.xmj.indexOf(colSetting.field) >= 0) {
+                        toastMessageUniq(hint.xmj);
+                        continue;
+                    }
+
+                    if (colSetting.type === 'Number') {
+                        if (colSetting.type === 'Number') {
+                            const num = _.toNumber(value);
+                            if (_.isNaN(num)) {
+                                toastMessageUniq(hint.invalidExpr);
+                                continue;
+                            }
+                            data[colSetting.field] = num;
+                        } else {
+                            data[colSetting.field] = value;
+                        }
+                    } else {
+                        data[colSetting.field] = value;
+                    }
+                    bPaste = true;
+                }
+                if (bPaste) {
+                    datas.push(data);
+                } else {
+                    filterNodes.push(node);
+                }
+            }
+            if (datas.length > 0) {
+                postData(window.location.pathname + '/update', {postType: 'update', postData: datas}, function (result) {
+                    const refreshNode = tree.loadPostData(result);
+                    if (refreshNode.update) refreshNode.update = refreshNode.update.concat(filterNodes);
+                    priceTreeOpr.refreshTree(info.sheet, refreshNode);
+                }, function () {
+                    SpreadJsObj.reLoadRowData(info.sheet, info.cellRange.row, info.cellRange.rowCount);
+                });
+            } else {
+                SpreadJsObj.reLoadRowData(info.sheet, info.cellRange.row, info.cellRange.rowCount);
+            }
+        },
+        deletePress: function (sheet) {
+            const tree = sheet.zh_tree, setting = sheet.zh_setting;
+            if (!setting || !tree) return;
+
+            const sortData = sheet.zh_tree.nodes;
+            const datas = [];
+            const sel = sheet.getSelections()[0];
+            for (let iRow = sel.row; iRow < sel.row + sel.rowCount; iRow++) {
+                const node = sortData[iRow];
+                if (node) {
+                    let bDel = false;
+                    const data = tree.getNodeKeyData(node);
+                    for (let iCol = sel.col; iCol < sel.col + sel.colCount; iCol++) {
+                        const style = sheet.getStyle(iRow, iCol);
+                        if (!style.locked) {
+                            const colSetting = setting.cols[iCol];
+                            data[colSetting.field] = colSetting.type === 'Number' ? 0 : '';
+                            bDel = true;
+                        }
+                    }
+                    if (bDel) datas.push(data);
+                }
+            }
+            if (datas.length > 0) {
+                postData(window.location.pathname + '/update', {postType: 'update', postData: datas}, function (result) {
+                    const refreshNode = tree.loadPostData(result);
+                    priceTreeOpr.refreshTree(sheet, refreshNode);
+                });
+            }
+        },
+        pasteBlock: function (sheet, copyInfo) {
+            const self = this;
+            const [tree, node] = this.getDefaultSelectInfo(sheet);
+
+            postData(window.location.pathname + '/update', {
+                postType: 'paste-block',
+                postData: {
+                    id: tree.getNodeKey(node),
+                    tid: copyInfo.tid,
+                    block: copyInfo.block,
+                }
+            }, function (data) {
+                const result = tree.loadPostData(data);
+                self.refreshTree(sheet, result);
+                const sel = sheet.getSelections()[0];
+                if (sel) {
+                    sheet.setSelection(tree.nodes.indexOf(result.create[0]), sel.col, sel.rowCount, sel.colCount);
+                    SpreadJsObj.reloadRowsBackColor(sheet, [sel.row, tree.nodes.indexOf(result.create[0])]);
+                }
+                self.refreshOperationValid(sheet);
+                removeLocalCache(copyBlockTag);
+            }, null, true);
+        }
+    };
+    priceSpread.bind(spreadNS.Events.SelectionChanged, priceTreeOpr.selectionChanged);
+    if (!readOnly) {
+        // 增删上下移升降级
+        $('a[name="base-opr"]').click(function () {
+            priceTreeOpr.baseOpr(priceSheet, this.getAttribute('type'));
+        });
+        priceSpread.bind(spreadNS.Events.EditStarting, priceTreeOpr.editStarting);
+        priceSpread.bind(spreadNS.Events.EditEnded, priceTreeOpr.editEnded);
+        priceSpread.bind(spreadNS.Events.ClipboardPasting, priceTreeOpr.clipboardPasting);
+        SpreadJsObj.addDeleteBind(priceSpread, priceTreeOpr.deletePress);
+        $.contextMenu({
+            selector: '#budget-spread',
+            build: function ($trigger, e) {
+                const target = SpreadJsObj.safeRightClickSelection($trigger, e, priceSpread);
+                return target.hitTestType === spreadNS.SheetArea.viewport || target.hitTestType === spreadNS.SheetArea.rowHeader;
+            },
+            items: {
+                copyBlock: {
+                    name: '复制整块',
+                    icon: 'fa-files-o',
+                    callback: function (key, opt) {
+                        const copyBlockList = [];
+                        const sheet = priceSheet;
+                        const sel = sheet.getSelections()[0];
+                        let iRow = sel.row;
+                        const pid = sheet.zh_tree.nodes[iRow].tree_pid;
+                        while (iRow < sel.row + sel.rowCount) {
+                            const node = sheet.zh_tree.nodes[iRow];
+                            if (node.tree_pid !== pid) {
+                                toastr.error('仅可同时选中同层节点');
+                                return;
+                            }
+                            const posterity = sheet.zh_tree.getPosterity(node);
+                            iRow += posterity.length + 1;
+                            posterity.unshift(node);
+                            copyBlockList.push(sheet.zh_tree.getDefaultData(posterity));
+                        }
+                        setLocalCache(copyBlockTag, JSON.stringify({ block: copyBlockList }));
+                    },
+                    visible: function (key, opt) {
+                        const select = SpreadJsObj.getSelectObject(priceSheet);
+                        return !!select;
+                    },
+                    disabled: function (key, opt) {
+                        const select = SpreadJsObj.getSelectObject(priceSheet);
+                        return !!select && select.level <= 1;
+                    }
+                },
+                copyBlockXmj: {
+                    name: '复制整块(只复制项目节)',
+                    icon: 'fa-files-o',
+                    callback: function (key, opt) {
+                        const copyBlockList = [];
+                        const sheet = priceSheet;
+                        const sel = sheet.getSelections()[0];
+                        let iRow = sel.row;
+                        const pid = sheet.zh_tree.nodes[iRow].ledger_pid;
+                        while (iRow < sel.row + sel.rowCount) {
+                            const node = sheet.zh_tree.nodes[iRow];
+                            if (node.ledger_pid !== pid) {
+                                toastr.error('仅可同时选中同层节点');
+                                return;
+                            }
+                            const posterity = sheet.zh_tree.getPosterity(node);
+                            iRow += posterity.length + 1;
+                            const copyPosterity = posterity.filter(x => { return !x.b_code; });
+                            copyPosterity.unshift(node);
+                            const copyData = sheet.zh_tree.getDefaultData(copyPosterity);
+                            for (const p of copyData) {
+                                const children = copyData.filter(y => {return y.tree_pid === p.tree_id}) || [];
+                                p.is_leaf = children.length === 0;
+                            }
+                            copyBlockList.push(copyData);
+                        }
+                        setLocalCache(copyBlockTag, JSON.stringify({ block: copyBlockList }));
+                    },
+                    visible: function (key, opt) {
+                        const select = SpreadJsObj.getSelectObject(priceSheet);
+                        return needGcl && !!select;
+                    },
+                    disabled: function (key, opt) {
+                        const select = SpreadJsObj.getSelectObject(priceSheet);
+                        return !!select && select.level <= 1;
+                    }
+                },
+                pasteBlock: {
+                    name: '粘贴整块',
+                    icon: 'fa-clipboard',
+                    disabled: function (key, opt) {
+                        const copyInfo = JSON.parse(getLocalCache(copyBlockTag));
+                        return !(copyInfo && copyInfo.block && copyInfo.block.length > 0);
+                    },
+                    callback: function (key, opt) {
+                        const copyInfo = JSON.parse(getLocalCache(copyBlockTag));
+                        if (copyInfo.block.length > 0) {
+                            priceTreeOpr.pasteBlock(priceSheet, copyInfo);
+                        } else {
+                            document.execCommand('paste');
+                        }
+                    },
+                    visible: function (key, opt) {
+                        return !readOnly;
+                    }
+                }
+            }
+        });
+    }
+    postData(window.location.pathname + '/load', {}, function (result) {
+        priceTree.loadDatas(result);
+        treeCalc.calculateAll(priceTree);
+        SpreadJsObj.loadSheetData(priceSheet, SpreadJsObj.DataType.Tree, priceTree);
+        priceTreeOpr.refreshOperationValid(priceSheet);
+    });
+
+    const stdLibCellDoubleClick = function (e, info) {
+        const stdSheet = info.sheet;
+        if (!stdSheet.zh_setting || !stdSheet.zh_tree || !priceSheet.zh_tree) return;
+
+        const stdTree = stdSheet.zh_tree;
+        const stdNode = stdTree.nodes[info.row];
+        if (!stdNode) return;
+
+        const priceTree = priceSheet.zh_tree;
+        const sel = priceSheet.getSelections()[0];
+        const mainNode = priceTree.nodes[sel.row];
+        if (info.sheet.zh_setting.stdType === 'gcl') {
+            if (mainNode.code && mainNode.code !== '' && !priceTree.isLeafXmj(mainNode)) {
+                toastr.warning('非最底层项目下,不应添加清单');
+                return;
+            }
+        }
+
+        postData(window.location.pathname + '/update', {
+            postType: 'add-std',
+            postData: {
+                id: priceTree.getNodeKey(mainNode),
+                tender_id: mainNode.tender_id,
+                stdType: info.sheet.zh_setting.stdType,
+                stdLibId: stdNode.list_id,
+                stdNode: stdTree.getNodeKey(stdNode)
+            }
+        }, function (result) {
+            const refreshNode = priceTree.loadPostData(result);
+            priceTreeOpr.refreshTree(priceSheet, refreshNode);
+            if (refreshNode.create && refreshNode.create.length > 0) {
+                priceSheet.setSelection(refreshNode.create[refreshNode.create.length - 1].index, sel.col, sel.rowCount, sel.colCount);
+                SpreadJsObj.reloadRowsBackColor(priceSheet, [sel.row, priceTree.nodes.indexOf(mainNode), refreshNode.create[refreshNode.create.length - 1].index]);
+            } else {
+                const node = _.find(priceTree.nodes, {code: stdNode.code, name: stdNode.name});
+                if (node) {
+                    priceSheet.setSelection(priceTree.nodes.indexOf(node), sel.col, sel.rowCount, sel.colCount);
+                    SpreadJsObj.reloadRowsBackColor(priceSheet, [sel.row, priceTree.nodes.indexOf(node)]);
+                }
+            }
+            priceTreeOpr.refreshOperationValid(priceSheet);
+            priceSpread.focus();
+        });
+    };
+    const stdXmjSetting = {
+        selector: '#std-xmj',
+        stdType: 'xmj',
+        libs: stdChapters,
+        treeSetting: {
+            id: 'chapter_id',
+            pid: 'pid',
+            order: 'order',
+            level: 'level',
+            rootId: -1,
+            keys: ['id', 'list_id', 'chapter_id'],
+        },
+        spreadSetting: {
+            cols: [
+                {title: '项目节编号', field: 'code', hAlign: 0, width: 120, formatter: '@', cellType: 'tree'},
+                {title: '名称', field: 'name', hAlign: 0, width: 150, formatter: '@'},
+                {title: '单位', field: 'unit', hAlign: 1, width: 50, formatter: '@'}
+            ],
+            treeCol: 0,
+            emptyRows: 0,
+            headRows: 1,
+            headRowHeight: [32],
+            defaultRowHeight: 21,
+            headerFont: '12px 微软雅黑',
+            font: '12px 微软雅黑',
+            headColWidth: [30],
+            selectedBackColor: '#fffacd',
+            readOnly: true,
+        },
+        cellDoubleClick: !readOnly ? stdLibCellDoubleClick : null,
+        page: 'ctrlPrice',
+        tid: window.location.pathname.split('/')[2],
+    };
+    const stdGclSetting = {
+        selector: '#std-gcl',
+        stdType: 'gcl',
+        libs: stdBills,
+        treeSetting: {
+            id: 'bill_id',
+            pid: 'pid',
+            order: 'order',
+            level: 'level',
+            rootId: -1,
+            keys: ['id', 'list_id', 'bill_id']
+        },
+        spreadSetting: {
+            cols: [
+                {title: '清单编号', field: 'b_code', hAlign: 0, width: 120, formatter: '@', cellType: 'tree'},
+                {title: '名称', field: 'name', hAlign: 0, width: 150, formatter: '@'},
+                {title: '单位', field: 'unit', hAlign: 1, width: 50, formatter: '@'}
+            ],
+            treeCol: 0,
+            emptyRows: 0,
+            headRows: 1,
+            headRowHeight: [32],
+            defaultRowHeight: 21,
+            headerFont: '12px 微软雅黑',
+            font: '12px 微软雅黑',
+            headColWidth: [30],
+            selectedBackColor: '#fffacd',
+            readOnly: true,
+        },
+        cellDoubleClick: !readOnly ? stdLibCellDoubleClick : null,
+        page: 'ctrlPrice',
+        tid: window.location.pathname.split('/')[2],
+    };
+    // 展开收起标准清单
+    $('a', 'ul.right-nav').bind('click', function (e) {
+        e.preventDefault();
+        const tab = $(this), tabPanel = $(tab.attr('content'));
+        // 展开工具栏、切换标签
+        if (!tab.hasClass('active')) {
+            $('a', '.side-menu').removeClass('active');
+            $('.tab-content .tab-select-show.tab-pane.active').removeClass('active');
+            tab.addClass('active');
+            tabPanel.addClass('active');
+            showSideTools(tab.hasClass('active'));
+            if (tab.attr('content') === '#std-xmj') {
+                if (!stdXmj) stdXmj = $.stdLib(stdXmjSetting);
+                stdXmj.spread.refresh();
+            } else if (tab.attr('content') === '#std-gcl') {
+                if (!stdGcl) stdGcl = $.stdLib(stdGclSetting);
+                stdGcl.spread.refresh();
+            } else if (tab.attr('content') === '#search') {
+                if (!searchCtrlPrice) {
+                    const searchSetting = {
+                        selector: '#search',
+                        searchSpread: priceSpread,
+                        keyId: 'tree_id',
+                        resultSpreadSetting: {
+                            cols: [
+                                {title: '项目节编号', field: 'code', hAlign: 0, width: 120, formatter: '@'},
+                                {title: '清单编号', field: 'b_code', hAlign: 0, width: 80, formatter: '@'},
+                                {title: '名称', field: 'name', width: 150, hAlign: 0, formatter: '@'},
+                                {title: '单位', field: 'unit', width: 50, hAlign: 1, formatter: '@'},
+                            ],
+                            emptyRows: 0,
+                            headRows: 1,
+                            headRowHeight: [32],
+                            headColWidth: [30],
+                            defaultRowHeight: 21,
+                            headerFont: '12px 微软雅黑',
+                            font: '12px 微软雅黑',
+                            selectedBackColor: '#fffacd',
+                            readOnly: true,
+                        },
+                    };
+                    searchSetting.searchRangeStr = '项目节编号/清单编号/名称';
+                    searchCtrlPrice = $.billsSearch(searchSetting);
+                }
+                searchCtrlPrice.spread.refresh();
+            }
+        } else { // 收起工具栏
+            tab.removeClass('active');
+            tabPanel.removeClass('active');
+            showSideTools(tab.hasClass('active'));
+        }
+        priceSpread.refresh();
+    });
+    // 显示层次
+    (function (select, sheet) {
+        $(select).click(function () {
+            if (!sheet.zh_tree) return;
+            const tag = $(this).attr('tag');
+            const tree = sheet.zh_tree;
+            setTimeout(() => {
+                showWaitingView();
+                switch (tag) {
+                    case "1":
+                    case "2":
+                    case "3":
+                    case "4":
+                    case "5":
+                        tree.expandByLevel(parseInt(tag));
+                        SpreadJsObj.refreshTreeRowVisible(sheet);
+                        break;
+                    case "last":
+                        tree.expandByCustom(() => { return true; });
+                        SpreadJsObj.refreshTreeRowVisible(sheet);
+                        break;
+                    case "leafXmj":
+                        tree.expandToLeafXmj();
+                        SpreadJsObj.refreshTreeRowVisible(sheet);
+                        break;
+                }
+                closeWaitingView();
+            }, 100);
+        });
+    })('a[name=showLevel]', priceSheet);
+    // 导入
+    $('#ctrl-price-import').click(() => {
+        importExcel.doImport({
+            template: {
+                hint: '导入Excel',
+                url: '/template/控制价示例EXCEL格式.xlsx',
+            },
+            filter: true,
+            callback: function (sheet, filter) {
+                postDataCompress(window.location.pathname + '/upload-excel/tz', {sheet, filter}, function (result) {
+                    priceTree.loadDatas(result);
+                    treeCalc.calculateAll(priceTree);
+                    SpreadJsObj.reLoadSheetData(priceSheet);
+                    checkShowLast(result.length);
+                }, null);
+            },
+        });
+    });
+});

+ 11 - 2
app/public/js/file_detail.js

@@ -116,6 +116,7 @@ $(document).ready(function() {
             if (!filingObj.curFiling) return;
 
             filingObj.curTotalPage = Math.ceil(filingObj.curFiling.source_node.file_count / this.pageCount);
+            filingObj.curPage = Math.min(filingObj.curTotalPage, filingObj.curPage);
             $('#curPage').html(filingObj.curPage);
             $('#curTotalPage').html(filingObj.curTotalPage);
             if (filingObj.curTotalPage > 1) {
@@ -261,8 +262,8 @@ $(document).ready(function() {
                 }
                 filingObj.updateFilingFileCount(filingObj.curFiling, data.filing.file_count);
                 await filingObj.loadFiles(filingObj.curFiling, filingObj.curPage);
-                filingObj.refreshFilesTable();
                 filingObj.refreshPages();
+                filingObj.refreshFilesTable();
                 if (callback) callback();
             });
         }
@@ -999,6 +1000,7 @@ $(document).ready(function() {
                 self.syncFiling(self.curFiling, selectFilingId);
                 toastr.success('同步成功');
                 $('[name=cbft]').each((i, x) => { x.checked = false; });
+                $('#filing-select-all')[0].checked = false;
             });
             $('#batch-del-filing').click(() => {
                 const selectUser = $('[name=ftu-check]:checked');
@@ -1015,7 +1017,13 @@ $(document).ready(function() {
                 const id = this.getAttribute('uid');
                 self.delFiling(self.curFiling, id);
                 self.loadCurFiling();
-            })
+            });
+            $('#user-select-all').click(function(){
+                $('input[uid]').attr('checked', this.checked);
+            });
+            $('#filing-select-all').click(function(){
+                $('input[name=cbft]').attr('checked', this.checked);
+            });
         }
         analysisFiling(data) {
             this.permissionUser = data;
@@ -1044,6 +1052,7 @@ $(document).ready(function() {
                 html.push('</tr>');
             }
             $(this.setting.list).html(html.join(''));
+            $('#user-select-all')[0].checked = false;
         }
         setCurFiling(filingType) {
             this.curFiling = filingType;

+ 59 - 0
app/public/js/file_list.js

@@ -80,6 +80,22 @@ $(document).ready(() => {
                 const selectId = $('tr.table-active').attr('tree_id');
                 return selectId || setting.treeSetting.rootId;
             },
+            expandByLevel: function(level){
+                projectTree.expandByLevel(level);
+                for (const node of projectTree.nodes) {
+                    const tr = $(`tr[tree_id=${node.id}]`);
+                    if (node.expanded) {
+                        $('.fold-switch', tr).html(`<i class="fa fa-minus-square-o"></i>`);
+                    } else {
+                        $('.fold-switch', tr).html(`<i class="fa fa-plus-square-o"></i>`);
+                    }
+                    if (node.visible) {
+                        tr.show();
+                    } else {
+                        tr.hide();
+                    }
+                }
+            }
         };
 
         Utils.reloadTable();
@@ -101,6 +117,49 @@ $(document).ready(() => {
                 }
             }
         });
+
+        const getChildrenLevel = function (node) {
+            let iLevel = node.tree_level || 1;
+            if (node.children && node.children.length > 0) {
+                for (const c of node.children) {
+                    iLevel = Math.max(iLevel, getChildrenLevel(c));
+                }
+            }
+            return iLevel;
+        };
+        let tenderTreeShowLevel = $.cs_showLevel({
+            selector: '#show-level',
+            levels: [
+                {
+                    type: 'sort', count: 5, visible_count: function () {
+                        return projectTree.children.map(getChildrenLevel).reduce((x, y) => { return Math.max(x, y); }, 0) - 1;
+                    }
+                },
+                {
+                    type: 'last', title: '最底层', visible: function () {
+                        const count = projectTree.children.map(getChildrenLevel).reduce((x, y) => { return Math.max(x, y); }, 0) - 1;
+                        return count > 0;
+                    }
+                },
+            ],
+            showLevel: function (tag) {
+                switch (tag) {
+                    case "1":
+                    case "2":
+                    case "3":
+                    case "4":
+                    case "5":
+                        Utils.expandByLevel(parseInt(tag));
+                        break;
+                    case "last":
+                        Utils.expandByLevel(20);
+                        break;
+                    default: return;
+                }
+            }
+        });
+        tenderTreeShowLevel.initShowLevel();
+        tenderTreeShowLevel.refreshMenuVisible();
         return { projectTree, TableObj, ...Utils };
     })({
         treeSetting: { id: 'id', pid: 'tree_pid', level: 'tree_level', order: 'tree_order', rootId: '-1' },

+ 2 - 2
app/public/js/material.js

@@ -584,7 +584,7 @@ $(document).ready(() => {
                     return;
                 }
                 // 未改变值则不提交
-                let validText = is_numeric(info.editingText) ? parseFloat(info.editingText) : (info.editingText ? trimInvalidChar(info.editingText) : null);
+                let validText = col.type === 'Number' && is_numeric(info.editingText) ? parseFloat(info.editingText) : (info.editingText ? trimInvalidChar(info.editingText) : null);
                 const orgValue = select[col.field];
                 if (orgValue == validText || ((!orgValue || orgValue === '') && (validText === ''))) {
                     SpreadJsObj.reLoadRowData(info.sheet, info.row);
@@ -744,7 +744,7 @@ $(document).ready(() => {
                     if (!colSetting) continue;
 
                     let validText = info.sheet.getText(curRow, curCol);
-                    validText = is_numeric(validText) ? parseFloat(validText) : (validText ? trimInvalidChar(validText) : null);
+                    validText = colSetting.type === 'Number' && is_numeric(validText) ? parseFloat(validText) : (validText ? trimInvalidChar(validText) : null);
                     const orgValue = sortData[curRow][colSetting.field];
                     if (orgValue == validText || ((!orgValue || orgValue === '') && (validText === ''))) {
                         sameCol++;

+ 13 - 9
app/public/js/material_checklist.js

@@ -1186,7 +1186,8 @@ $(document).ready(() => {
                     if (!ignoreUnitPrice) findObject.unit_price = t.unit_price ? parseFloat(t.unit_price) : null;
                     const order = _.findIndex(gclGatherData, findObject);
                     const mlOrder = _.findIndex(materialChecklistData, findObject);
-                    if (mlOrder === -1 && order !== -1 && _.findIndex(pushChecklist, findObject) === -1) {
+                    const haveQuantity = _.find(t.children, function (item) { return item.quantity && item.quantity !== 0 }) ? 1 : 0;
+                    if (mlOrder === -1 && order !== -1 && haveQuantity && _.findIndex(pushChecklist, findObject) === -1) {
                         pushChecklist.push({
                             b_code: gclGatherData[order].b_code,
                             name: gclGatherData[order].name,
@@ -1198,6 +1199,8 @@ $(document).ready(() => {
                         });
                     } else if (mlOrder === -1 && order === -1) {
                         continue;
+                    } else if (!haveQuantity) {
+                        continue;
                     }
                     needPushTree.push(t);
                     // for (const c of t.children) {
@@ -1215,7 +1218,6 @@ $(document).ready(() => {
                 if (needPushTree.length === 0) {
                     throw '不存在需要导入的工料清单含量';
                 }
-                console.log(needPushTree);
                 // 先上传需要生成的清单及工料
                 if (pushChecklist.length > 0 || pushBillsData.length > 0) {
                     postData(window.location.pathname + '/save', { type:'exportCB', addChecklist: pushChecklist, addBillsList: pushBillsData }, async function (result) {
@@ -1330,14 +1332,16 @@ $(document).ready(() => {
                 }
                 const mbList = [];
                 for (const mb of t.children) {
-                    const mbInfo = _.find(materialBillsData, { code: mb.gljcode+'', name: mb.name+'', unit: mb.unit+'' });
-                    if (mbInfo) {
-                        const num = parseFloat(mb.quantity);
-                        if (num < 0 || !/^\d+(\.\d{1,6})?$/.test(num)) {
-                            // toastr.warning('已保留6位小数');
-                            mb.quantity = ZhCalc.round(num, 6);
+                    if (mb.quantity) {
+                        const mbInfo = _.find(materialBillsData, { code: mb.gljcode+'', name: mb.name+'', unit: mb.unit+'' });
+                        if (mbInfo) {
+                            const num = parseFloat(mb.quantity);
+                            if (num < 0 || !/^\d+(\.\d{1,6})?$/.test(num)) {
+                                // toastr.warning('已保留6位小数');
+                                mb.quantity = ZhCalc.round(num, 6);
+                            }
+                            mbList.push({ id: mbInfo.id, quantity: mb.quantity });
                         }
-                        mbList.push({ id: mbInfo.id, quantity: mb.quantity ? mb.quantity : 0 });
                     }
                 }
                 if (mbList.length === 0 && i+1 === tree.length) {

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

@@ -130,7 +130,7 @@ $(document).ready(() => {
         if (data) {
             for (const d of data) {
                 if (['1000', '1100', '1200', '1300'].indexOf(d.code) >= 0) {
-                    if (checkZero(d.total_price) && checkZero(d.deal_bills_tp)) {
+                    if (checkZero(d.new_total_price) && checkZero(d.org_total_price) && checkZero(d.deal_bills_tp)) {
                         continue;
                     }
                 }

+ 3 - 2
app/public/js/setting_manage.js

@@ -575,9 +575,10 @@ const tenderListSpec = (function(){
         const html = [];
         html.push('<tr pid="' + pid + '"', (node.cid ? '' : 'class="tender-info" data-id="'+ node.id +'"'), '>');
         // 名称
-        html.push('<td style="width: 80%" class="in-' + node.level + '">');
+        html.push('<td style="width: 80%" class="in-' + node.level + '"' + (node.cid ? '' : 'tid="' + node.id + '"') + '>');
         if (node.cid) {
-            html.push('<span onselectstart="return false" style="{-moz-user-select:none}" class="fold-switch mr-1" title="收起" cid="'+ node.sort_id +'"><i class="fa fa-minus-square-o"></i></span> <i class="fa fa-folder-o"></i> ', node.name);
+            html.push('<span onselectstart="return false" style="{-moz-user-select:none}" class="fold-switch mr-1" title="收起" cid="'+ node.sort_id +'"><i class="fa fa-minus-square-o"></i></span> <i class="fa fa-folder-o"></i> ');
+            html.push((node.level === 1 ? '<b>' : ''), node.name, (node.level === 1 ? '</b>' : ''));
         } else {
             html.push('<div class="d-flex justify-content-between align-items-center">');
             html.push('<div>');

+ 3 - 2
app/public/js/shares/drag_tree.js

@@ -261,15 +261,16 @@ const createDragTree = function (setting) {
          */
         expandByCustom(checkFun) {
             this._recursiveExpand(this.children, null, checkFun);
-            this._saveMarkExpandFold();
+            // this._saveMarkExpandFold();
         }
         /**
          * 展开到第几层
          * @param {Number} level - 展开层数
          */
         expandByLevel(level) {
+            const levelField = this.setting.level;
             this.expandByCustom(function (n) {
-                return n.level < level;
+                return n[levelField] < level;
             });
         }
 

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

@@ -1588,6 +1588,10 @@ $(document).ready(() => {
                             if (differ) updateStage.push({ lid: p.id, contract_tp: tp });
                         }
                     }
+                    if (updateStage.length === 0) {
+                        toastr.warning('所选节点及子项已全部计量');
+                        return;
+                    }
 
                     if (updateStage.length > 0) {
                         if (updateStage.length > 1000) {
@@ -1607,6 +1611,60 @@ $(document).ready(() => {
                     }
                 },
             },
+            'remainXmjQty': {
+                name: '填项目节数量',
+                callback: function(key, opt) {
+                    const sheet = spSpread.getActiveSheet();
+                    const node = SpreadJsObj.getSelectObject(slSpread.getActiveSheet());
+
+                    const updateDgn = [];
+                    const posterity = stageTree.getPosterity(node);
+                    posterity.unshift(node);
+                    for (const p of posterity) {
+                        if (p.b_code) continue;
+                        const posRange = stagePos.ledgerPos[itemsPre + p.id] || [];
+                        if (posRange.length > 0) continue;
+
+                        if (p.dgn_qty1 !== p.deal_dgn_qty1 || p.dgn_qty2 !== p.deal_dgn_qty2) updateDgn.push({ id: p.id, deal_dgn_qty1: p.dgn_qty1, deal_dgn_qty2: p.dgn_qty2 });
+                    }
+                    if (updateDgn.length === 0) {
+                        toastr.warning('所选项目节及子项的项目节数量已与台账相等');
+                        return;
+                    }
+
+                    if (updateDgn.length > 1000) {
+                        toastr.warning('提交的数据太大,仅提交1000条数据');
+                        updateDgn.length = 1000;
+                    }
+                    postData(window.location.pathname + '/update', {bills: { dgn: updateDgn }}, function (result) {
+                        const nodes = stageTree.loadPostStageData(result);
+                        stageTreeSpreadObj.refreshTreeNodes(slSpread.getActiveSheet(), nodes);
+                        if (detail) {
+                            detail.loadStageLedgerUpdateData(result, nodes);
+                        } else {
+                            stageIm.loadUpdateLedgerData(result, nodes);
+                        }
+                        stageTreeSpreadObj.loadExprToInput(sheet);
+                    });
+                    // postData(window.location.pathname + '/update', { dgn: 'all' }, function (result) {
+                    //     const nodes = stageTree.loadPostStageData(result);
+                    //     stageTreeSpreadObj.refreshTreeNodes(slSpread.getActiveSheet(), nodes);
+                    //     if (detail) {
+                    //         detail.loadStageLedgerUpdateData(result, nodes);
+                    //     } else {
+                    //         stageIm.loadUpdateLedgerData(result, nodes);
+                    //     }
+                    //     stageTreeSpreadObj.loadExprToInput(sheet);
+                    // });
+                },
+                visible: function(key, opt) {
+                    return !readOnly;
+                },
+                disabled: function (key, opt) {
+                    const node = SpreadJsObj.getSelectObject(slSpread.getActiveSheet());
+                    return !node || !!node.b_code;
+                }
+            },
             zjjlSpr: '----',
             'locateZjjl': {
                 name: '定位至中间计量',

+ 60 - 0
app/public/js/sub_project.js

@@ -12,6 +12,7 @@ $(document).ready(function() {
         const ProjectTree = createDragTree(setting.treeSetting);
         ProjectTree.loadDatas(setting.source);
         const TableObj = $(setting.table);
+        let tenderTreeShowLevel;
 
         const Utils = {
             getRowTdHtml: function (node, tree) {
@@ -134,6 +135,22 @@ $(document).ready(function() {
                 postData('/subproj/move', { id: node.id, type }, function (result) {
                     Utils.refreshTreeTable(result);
                 });
+            },
+            expandByLevel: function(level){
+                ProjectTree.expandByLevel(level);
+                for (const node of ProjectTree.nodes) {
+                    const tr = $(`tr[tree_id=${node.id}]`);
+                    if (node.expanded) {
+                        $('.fold-switch', tr).html(`<i class="fa fa-minus-square-o"></i>`);
+                    } else {
+                        $('.fold-switch', tr).html(`<i class="fa fa-plus-square-o"></i>`);
+                    }
+                    if (node.visible) {
+                        tr.show();
+                    } else {
+                        tr.hide();
+                    }
+                }
             }
         };
 
@@ -236,6 +253,49 @@ $(document).ready(function() {
                 }
             }
         });
+
+        const getChildrenLevel = function (node) {
+            let iLevel = node.tree_level || 1;
+            if (node.children && node.children.length > 0) {
+                for (const c of node.children) {
+                    iLevel = Math.max(iLevel, getChildrenLevel(c));
+                }
+            }
+            return iLevel;
+        };
+        tenderTreeShowLevel = $.cs_showLevel({
+            selector: '#show-level',
+            levels: [
+                {
+                    type: 'sort', count: 5, visible_count: function () {
+                        return ProjectTree.children.map(getChildrenLevel).reduce((x, y) => { return Math.max(x, y); }, 0) - 1;
+                    }
+                },
+                {
+                    type: 'last', title: '最底层', visible: function () {
+                        const count = ProjectTree.children.map(getChildrenLevel).reduce((x, y) => { return Math.max(x, y); }, 0) - 1;
+                        return count > 0;
+                    }
+                },
+            ],
+            showLevel: function (tag) {
+                switch (tag) {
+                    case "1":
+                    case "2":
+                    case "3":
+                    case "4":
+                    case "5":
+                        Utils.expandByLevel(parseInt(tag));
+                        break;
+                    case "last":
+                        Utils.expandByLevel(20);
+                        break;
+                    default: return;
+                }
+            }
+        });
+        tenderTreeShowLevel.initShowLevel();
+        tenderTreeShowLevel.refreshMenuVisible();
         return { ProjectTree, TableObj, ...Utils };
     })({
         treeSetting: { id: 'id', pid: 'tree_pid', level: 'tree_level', order: 'tree_order', rootId: '-1' },

+ 3 - 2
app/public/js/tender_list.js

@@ -19,7 +19,8 @@ const tenderListSpec = (function(){
         // 名称
         html.push('<td style="width: 40%" class="in-' + node.level + '">');
         if (node.cid) {
-            html.push('<span onselectstart="return false" style="{-moz-user-select:none}" class="fold-switch mr-1" title="收起" cid="'+ node.sort_id +'"><i class="fa fa-minus-square-o"></i></span> <i class="fa fa-folder-o"></i> ', node.name);
+            html.push('<span onselectstart="return false" style="{-moz-user-select:none}" class="fold-switch mr-1" title="收起" cid="'+ node.sort_id +'"><i class="fa fa-minus-square-o"></i></span> <i class="fa fa-folder-o"></i> ');
+            html.push((node.level === 1 ? '<b>' : ''), node.name, (node.level === 1 ? '</b>' : ''));
         } else {
             html.push('<span class="text-muted mr-2">');
             html.push(arr.indexOf(node) === arr.length - 1 ? '└' : '├');
@@ -80,4 +81,4 @@ const tenderListSpec = (function(){
         return html.join('');
     }
     return { getTenderNodeHtml, getTenderTreeHeaderHtml }
-})();
+})();

+ 3 - 2
app/public/js/tender_list_info.js

@@ -14,7 +14,8 @@ const tenderListSpec = (function(){
         // 名称
         html.push('<td style="min-width: 300px;" class="in-' + node.level + '">');
         if (node.cid) {
-            html.push('<span onselectstart="return false" style="{-moz-user-select:none}" class="fold-switch mr-1" title="收起" cid="'+ node.sort_id +'"><i class="fa fa-minus-square-o"></i></span> <i class="fa fa-folder-o"></i> ', node.name);
+            html.push('<span onselectstart="return false" style="{-moz-user-select:none}" class="fold-switch mr-1" title="收起" cid="'+ node.sort_id +'"><i class="fa fa-minus-square-o"></i></span> <i class="fa fa-folder-o"></i> ');
+            html.push((node.level === 1 ? '<b>' : ''), node.name, (node.level === 1 ? '</b>' : ''));
         } else {
             html.push('<span class="text-muted mr-2">');
             html.push(arr.indexOf(node) === arr.length - 1 ? '└' : '├');
@@ -46,7 +47,7 @@ const tenderListSpec = (function(){
                 ? transFormToChinese(node.cur_flow[0].audit_order) + '审'
                 : node.cur_flow instanceof Array ? (node.cur_flow[0].name + (node.cur_flow[0].role ? '-'+node.cur_flow[0].role  : '')): (node.cur_flow.name + (node.cur_flow.role ? '-'+node.cur_flow.role  : ''));
             if (node.stage_status !== undefined) {
-                html.push((node.stage_status === auditConst.stage.status.uncheck || node.ledger_status === auditConst.ledger.status.uncheck)
+                html.push(((node.stage_count && node.stage_status === auditConst.stage.status.uncheck) || node.ledger_status === auditConst.ledger.status.uncheck)
                     ? curUser
                     : `<a href="#sp-list" data-toggle="modal" data-target="#sp-list"  data-type="${node.stage_count ? 'stage' : 'ledger'}" data-tender="${node.id}" data-order="${node.stage_count ? node.stage_count + '' : ''}">${curUser}</a>`
                 );

+ 3 - 2
app/public/js/tender_list_manage.js

@@ -5,7 +5,8 @@ const tenderListSpec = (function(){
         // 名称
         html.push('<td style="width: 45%" class="in-' + node.level + '">');
         if (node.cid) {
-            html.push('<span onselectstart="return false" style="{-moz-user-select:none}" class="fold-switch mr-1" title="收起" cid="'+ node.sort_id +'"><i class="fa fa-minus-square-o"></i></span> <i class="fa fa-folder-o"></i> ', node.name);
+            html.push('<span onselectstart="return false" style="{-moz-user-select:none}" class="fold-switch mr-1" title="收起" cid="'+ node.sort_id +'"><i class="fa fa-minus-square-o"></i></span> <i class="fa fa-folder-o"></i> ');
+            html.push((node.level === 1 ? '<b>' : ''), node.name, (node.level === 1 ? '</b>' : ''));
         } else {
             html.push('<span class="text-muted mr-2">');
             html.push(arr.indexOf(node) === arr.length - 1 ? '└' : '├');
@@ -56,4 +57,4 @@ const tenderListSpec = (function(){
         return html.join('');
     }
     return { getTenderNodeHtml, getTenderTreeHeaderHtml }
-})();
+})();

+ 6 - 2
app/public/js/tender_list_progress.js

@@ -31,7 +31,8 @@ const tenderListSpec = (function(){
         // 名称
         html.push('<td width="25%" class="in-' + node.level + '">');
         if (node.cid) {
-            html.push('<span onselectstart="return false" style="{-moz-user-select:none}" class="fold-switch mr-1" title="收起" cid="'+ node.sort_id +'"><i class="fa fa-minus-square-o"></i></span> <i class="fa fa-folder-o"></i> ', node.name);
+            html.push('<span onselectstart="return false" style="{-moz-user-select:none}" class="fold-switch mr-1" title="收起" cid="'+ node.sort_id +'"><i class="fa fa-minus-square-o"></i></span> <i class="fa fa-folder-o"></i> ');
+            html.push((node.level === 1 ? '<b>' : ''), node.name, (node.level === 1 ? '</b>' : ''));
         } else {
             html.push('<span class="text-muted mr-2">');
             html.push(arr.indexOf(node) === arr.length - 1 ? '└' : '├');
@@ -54,11 +55,14 @@ const tenderListSpec = (function(){
         html.push('<td style="width: 8%">');
         if (!node.cid && node.cur_flow) {
             if (node.id === 4811) console.log(node.cur_flow);
+            if (node.id === 3866) {
+                console.log(node);
+            }
             const curUser = node.cur_flow instanceof Array && node.cur_flow[0].audit_type && node.cur_flow[0].audit_type !== auditType.key.common
                 ? transFormToChinese(node.cur_flow[0].audit_order) + '审'
                 : node.cur_flow instanceof Array ? (node.cur_flow[0].name + (node.cur_flow[0].role ? '-'+node.cur_flow[0].role  : '')): (node.cur_flow.name + (node.cur_flow.role ? '-'+node.cur_flow.role  : ''));
             if (node.stage_status !== undefined) {
-                html.push((node.stage_status === auditConst.stage.status.uncheck || node.ledger_status === auditConst.ledger.status.uncheck)
+                html.push(((node.stage_count && node.stage_status === auditConst.stage.status.uncheck) || node.ledger_status === auditConst.ledger.status.uncheck)
                     ? curUser
                     : `<a href="#sp-list" data-toggle="modal" data-target="#sp-list"  data-type="${node.stage_count ? 'stage' : 'ledger'}" data-tender="${node.id}" data-order="${node.stage_count ? node.stage_count + '' : ''}">${curUser}</a>`
                 );

+ 10 - 10
app/public/js/tender_showhide.js

@@ -9,7 +9,7 @@
  */
 let hideList = [];
 // const tTree = typeof tenderTree !== 'undefined' ? tenderTree : typeof tenderTree2 !== 'undefined' ? tenderTree2 : [];
-const tTree = tenderTree;
+// const tTree = tenderTree;
 let returnItem;
 const findTenderTreeNode = function(sortId, tree) {
     tree.forEach((item) => {
@@ -55,7 +55,7 @@ function recursiveExpand(nodes, parent, checkFun) {
 }
 function expandByCustom(checkFun) {
     hideList = [];
-    recursiveExpand(tTree, null, checkFun);
+    recursiveExpand(tenderTree, null, checkFun);
 }
 
 function expandByLevel(level) {
@@ -66,7 +66,7 @@ function expandByLevel(level) {
 }
 
 // 根据标段类别设置排序
-function sortTenderTree(teTree = tTree) {
+function sortTenderTree(teTree = tenderTree) {
     for (const tender of teTree) {
         if (tender.sort) {
             // 当前层排序
@@ -98,7 +98,7 @@ function localHideList(wap = false) {
             hideList = JSON.parse(userTenderHideList);
             for (const h of hideList) {
                 const cid = h.sort_id;
-                const node = findTenderTreeNode(parseInt(cid), tTree);
+                const node = findTenderTreeNode(parseInt(cid), tenderTree);
                 $('.c-body tr td span[cid="' + cid + '"]').children('i').removeClass('fa-minus-square-o').addClass('fa-plus-square-o');
                 $('.c-body tr td span[cid="' + cid + '"]').attr('title', '展开');
                 doTrStatus(returnItem, 'hide');
@@ -174,7 +174,7 @@ $(document).ready(() => {
             $(this).children('i').removeClass('fa-minus-square-o').addClass('fa-plus-square-o');
             $(this).attr('title', '展开');
             const cid = $(this).attr('cid');
-            const node = findTenderTreeNode(parseInt(cid), tTree);
+            const node = findTenderTreeNode(parseInt(cid), tenderTree);
             doTrStatus(returnItem, 'hide');
             hideList.push({sort_id: cid});
             setLocalCache(uphlname, JSON.stringify(hideList));
@@ -182,7 +182,7 @@ $(document).ready(() => {
             $(this).children('i').removeClass('fa-plus-square-o').addClass('fa-minus-square-o');
             $(this).attr('title', '收起');
             const cid = $(this).attr('cid');
-            const node = findTenderTreeNode(parseInt(cid), tTree);
+            const node = findTenderTreeNode(parseInt(cid), tenderTree);
             doTrStatus(returnItem, 'show');
             const index = hideList.findIndex(function(item) {
                 return parseInt(item.sort_id) === parseInt(cid);
@@ -200,10 +200,10 @@ $(document).ready(() => {
         if (item === 'open') {
             setLocalCache(uphlname, JSON.stringify(hideList));
         }
-        for (const tree of tTree) {
+        for (const tree of tenderTree) {
             if (tree && tree.sort_id !== undefined) {
                 const cid = tree.sort_id;
-                const node = findTenderTreeNode(parseInt(cid), tTree);
+                const node = findTenderTreeNode(parseInt(cid), tenderTree);
                 if (item === 'open') {
                     $('.c-body tr td span[cid="' + cid + '"]').children('i').removeClass('fa-plus-square-o').addClass('fa-minus-square-o');
                     $('.c-body tr td span[cid="' + cid + '"]').attr('title', '收起');
@@ -223,12 +223,12 @@ $(document).ready(() => {
         levels: [
             {
                 type: 'sort', count: 5, visible_count: function () {
-                    return tTree.map(getChildrenLevel).reduce((x, y) => { return Math.max(x, y); }, 0) - 1;
+                    return tenderTree.map(getChildrenLevel).reduce((x, y) => { return Math.max(x, y); }, 0) - 1;
                 }
             },
             {
                 type: 'last', title: '最底层', visible: function () {
-                    const count = tTree.map(getChildrenLevel).reduce((x, y) => { return Math.max(x, y); }, 0) - 1;
+                    const count = tenderTree.map(getChildrenLevel).reduce((x, y) => { return Math.max(x, y); }, 0) - 1;
                     return count > 0;
                 }
             },

+ 9 - 0
app/router.js

@@ -185,6 +185,12 @@ module.exports = app => {
     app.post('/tender/:id/load', sessionAuth, tenderCheck, 'tenderController.loadData');
     app.post('/tender/:id/saveRela', sessionAuth, tenderCheck, 'tenderController.saveRelaData');
 
+    app.get('/tender/:id/ctrl-price', sessionAuth, tenderCheck, 'ctrlPriceController.index');
+    app.post('/tender/:id/ctrl-price/load', sessionAuth, tenderCheck, 'ctrlPriceController.load');
+    app.post('/tender/:id/ctrl-price/update', sessionAuth, tenderCheck, 'ctrlPriceController.update');
+    app.post('/tender/:id/ctrl-price/upload-excel/:ueType', sessionAuth, tenderCheck, 'ctrlPriceController.uploadExcel');
+
+
     // 预付款
     app.get('/tender/:id/advance/:type', sessionAuth, tenderCheck, 'advanceController.index');
     // app.get('/tender/:id/advance/material', sessionAuth, tenderCheck, 'advanceController.materialList');
@@ -414,6 +420,9 @@ module.exports = app => {
     app.get('/tender/:id/measure/stage/:order/manager', sessionAuth, tenderCheck, uncheckTenderCheck, stageCheck, 'stageController.manager');
     app.post('/tender/:id/measure/stage/:order/manager/audit/delete', sessionAuth, tenderCheck, uncheckTenderCheck, stageCheck, tenderBuildCheck, 'stageController.managerAuditDelete');
 
+    // 过程结算
+    app.get('/tender/:id/settle', sessionAuth, tenderCheck, uncheckTenderCheck, 'settleController.list');
+
     // 报表
     app.get('/tender/:id/report', sessionAuth, tenderCheck, uncheckTenderCheck, 'reportController.index');
     app.get('/tender/:id/measure/stage/:order/report', sessionAuth, tenderCheck, uncheckTenderCheck, stageCheck, 'reportController.index');

+ 1 - 1
app/service/advance_audit.js

@@ -175,7 +175,7 @@ module.exports = app => {
         async getAuditors(vid, times = 1) {
             const sql =
                 'SELECT la.`audit_id`, pa.`name`, pa.`company`, pa.`role`, pa.`mobile`, pa.`telephone`, la.`times`, la.`order`, la.`status`, la.`opinion`, la.`create_time`, la.`end_time`, g.`sort` ' +
-                'FROM ?? AS la, ?? AS pa, (SELECT `audit_id`,(@i:=@i+1) as `sort` FROM ??, (select @i:=0) as it WHERE `vid` = ? AND `times` = ? GROUP BY `audit_id`) as g ' +
+                'FROM ?? AS la, ?? AS pa, (SELECT t1.`audit_id`,(@i:=@i+1) as `sort` FROM (SELECT t.`audit_id` FROM (select `audit_id` from ?? WHERE `vid` = ? AND `times` = ? ORDER BY `order` LIMIT 200) t GROUP BY t.`audit_id`) t1, (select @i:=0) as it) as g ' +
                 'WHERE la.`vid` = ? and la.`times` = ? and la.`audit_id` = pa.`id` and g.`audit_id` = la.`audit_id` order by la.`order`';
             const sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, this.tableName, vid, times, vid, times];
             const result = await this.db.query(sql, sqlParam);

+ 1 - 1
app/service/change_apply_audit.js

@@ -38,7 +38,7 @@ module.exports = app => {
          */
         async getAuditors(caId, times = 1) {
             const sql = 'SELECT la.`aid`, pa.`name`, pa.`company`, pa.`role`, pa.`mobile`, pa.`telephone`, la.`times`, la.`order`, la.`status`, la.`opinion`, la.`begin_time`, la.`end_time`, g.`sort` ' +
-                'FROM ?? AS la, ?? AS pa, (SELECT `aid`,(@i:=@i+1) as `sort` FROM ??, (select @i:=0) as it WHERE `caid` = ? AND `times` = ? GROUP BY `aid`) as g ' +
+                'FROM ?? AS la, ?? AS pa, (SELECT t1.`aid`,(@i:=@i+1) as `sort` FROM (SELECT t.`aid` FROM (select `aid` from ?? WHERE `caid` = ? AND `times` = ? ORDER BY `order` LIMIT 200) t GROUP BY t.`aid`) t1, (select @i:=0) as it) as g ' +
                 'WHERE la.`caid` = ? and la.`times` = ? and la.`aid` = pa.`id` and g.`aid` = la.`aid` order by la.`order`';
             const sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, this.tableName, caId, times, caId, times];
             const result = await this.db.query(sql, sqlParam);

+ 8 - 0
app/service/change_audit_list.js

@@ -1040,6 +1040,14 @@ module.exports = app => {
                 throw err;
             }
         }
+
+        async setAllValuation(cid, is_valuation) {
+            return await this.db.update(this.tableName, { is_valuation }, {
+                where: {
+                    cid,
+                },
+            });
+        }
     }
 
     return ChangeAuditList;

+ 1 - 1
app/service/change_plan_audit.js

@@ -38,7 +38,7 @@ module.exports = app => {
          */
         async getAuditors(cpId, times = 1) {
             const sql = 'SELECT la.`aid`, pa.`name`, pa.`company`, pa.`role`, pa.`mobile`, pa.`telephone`, la.`times`, la.`order`, la.`status`, la.`opinion`, la.`begin_time`, la.`end_time`, g.`sort` ' +
-                'FROM ?? AS la, ?? AS pa, (SELECT `aid`,(@i:=@i+1) as `sort` FROM ??, (select @i:=0) as it WHERE `cpid` = ? AND `times` = ? GROUP BY `aid`) as g ' +
+                'FROM ?? AS la, ?? AS pa, (SELECT t1.`aid`,(@i:=@i+1) as `sort` FROM (SELECT t.`aid` FROM (select `aid` from ?? WHERE `cpid` = ? AND `times` = ? ORDER BY `order` LIMIT 200) t GROUP BY t.`aid`) t1, (select @i:=0) as it) as g ' +
                 'WHERE la.`cpid` = ? and la.`times` = ? and la.`aid` = pa.`id` and g.`aid` = la.`aid` order by la.`order`';
             const sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, this.tableName, cpId, times, cpId, times];
             const result = await this.db.query(sql, sqlParam);

+ 1 - 1
app/service/change_project_audit.js

@@ -38,7 +38,7 @@ module.exports = app => {
          */
         async getAuditors(cpId, times = 1) {
             const sql = 'SELECT la.`aid`, pa.`name`, pa.`company`, pa.`role`, pa.`mobile`, pa.`telephone`, la.`times`, la.`order`, la.`status`, la.`opinion`, la.`begin_time`, la.`end_time`, g.`sort` ' +
-                'FROM ?? AS la, ?? AS pa, (SELECT `aid`,(@i:=@i+1) as `sort` FROM ??, (select @i:=0) as it WHERE `cpid` = ? AND `times` = ? GROUP BY `aid`) as g ' +
+                'FROM ?? AS la, ?? AS pa, (SELECT t1.`aid`,(@i:=@i+1) as `sort` FROM (SELECT t.`aid` FROM (select `aid` from ?? WHERE `cpid` = ? AND `times` = ? ORDER BY `order` LIMIT 200) t GROUP BY t.`aid`) t1, (select @i:=0) as it) as g ' +
                 'WHERE la.`cpid` = ? and la.`times` = ? and la.`aid` = pa.`id` and g.`aid` = la.`aid` order by la.`order`';
             const sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, this.tableName, cpId, times, cpId, times];
             const result = await this.db.query(sql, sqlParam);

+ 481 - 0
app/service/control_price.js

@@ -0,0 +1,481 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date
+ * @version
+ */
+
+
+const TreeService = require('../base/base_tree_service');
+const billsUtils = require('../lib/bills_utils');
+const readOnlyFields = ['id', 'tid', 'tree_id', 'tree_pid', 'order', 'level', 'full_path', 'is_leaf'];
+const calcFields = ['unit_price', 'quantity', 'total_price'];
+
+class ControlPrice extends TreeService {
+
+    /**
+     * 构造函数
+     *
+     * @param {Object} ctx - egg全局变量
+     * @param {String} tableName - 表名
+     * @return {void}
+     */
+    constructor(ctx) {
+        super(ctx, {
+            mid: 'tid',
+            kid: 'tree_id',
+            pid: 'tree_pid',
+            order: 'order',
+            level: 'level',
+            isLeaf: 'is_leaf',
+            fullPath: 'full_path',
+            keyPre: 'ctrl_price_maxLid:',
+            uuid: true,
+        });
+        this.tableName = 'control_price';
+    }
+
+    async checkInit(tender) {
+        const count = await this.count({ tid: tender.id });
+        if (count > 0) return;
+
+        const templateId = await this.ctx.service.valuation.getValuationTemplate(
+            this.ctx.tender.data.valuation, this.ctx.tender.data.measure_type);
+        await this.initByTemplate(tender.id, templateId);
+    }
+
+    async initByTemplate(tenderId, templateId){
+        if (tenderId <= 0) throw '标段数据错误';
+        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(),
+                tid: tenderId,
+                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,
+                unit: tmp.unit,
+                node_type: tmp.node_type,
+            });
+        }
+        const operate = await this.db.insert(this.tableName, insertData);
+        return operate.affectedRows === data.length;
+    }
+
+    async addChild(tenderId, selectId, data) {
+        if ((tenderId <= 0) || (selectId <= 0)) return [];
+        const selectData = await this.getDataByKid(tenderId, selectId);
+        if (!selectData) {
+            throw '新增节点数据错误';
+        }
+        const children = await this.getChildrenByParentId(tenderId, selectId);
+
+        const maxId = await this._getMaxLid(tenderId);
+
+        data.id = this.uuid.v4();
+        data.tid = tenderId;
+        data.tree_id = maxId + 1;
+        data.tree_pid = selectData.tree_id;
+        data.level = selectData.level + 1;
+        data.order = children.length + 1;
+        data.full_path = selectData.full_path + '-' + data.tree_id;
+        data.is_leaf = true;
+
+        this.transaction = await this.db.beginTransaction();
+        try {
+            const result = await this.transaction.insert(this.tableName, data);
+            if (children.length === 0) {
+                await this.transaction.update(this.tableName,
+                    { is_leaf: false, quantity: 0, unit_price: 0, total_price: 0 },
+                    { where: { tid: tenderId, tree_id: selectData.tree_id } });
+            }
+            await this.transaction.commit();
+        } catch(err) {
+            this.transaction.rollback();
+            throw err;
+        }
+
+        this._cacheMaxLid(tenderId, maxId + 1);
+
+        // 查询应返回的结果
+        const resultData = {};
+        resultData.create = await this.getDataByKid(tenderId, data.tree_id);
+        if (children.length === 0) resultData.update = await this.getDataByKid(tenderId, selectId);
+        return resultData;
+    }
+
+    _filterStdData(stdData) {
+        const result = {
+            name: stdData.name,
+            unit: stdData.unit,
+            node_type: stdData.node_type,
+        };
+        result.code = stdData.code ? stdData.code : '';
+        result.b_code = stdData.b_code ? stdData.b_code : '';
+        return result;
+    }
+
+    async _addChildNodeData(tenderId, parentData, data) {
+        if (tenderId <= 0) return undefined;
+        if (!data) data = {};
+        const pid = parentData ? parentData.tree_id : this.rootId;
+
+        const maxId = await this._getMaxLid(tenderId);
+
+        data.id = this.uuid.v4();
+        data.tid = tenderId;
+        data.tree_id = maxId + 1;
+        data.tree_pid = pid;
+        if (data.order === undefined) data.order = 1;
+        data.level = parentData ? parentData.level + 1 : 1;
+        data.full_path = parentData ? parentData.full_path + '-' + data.tree_id : '' + data.tree_id;
+        if (data.is_leaf === undefined) data.is_leaf = true;
+        const result = await this.transaction.insert(this.tableName, data);
+
+        this._cacheMaxLid(tenderId, maxId + 1);
+
+        return [result, data];
+    }
+
+    async _addChildAutoOrder(tenderId, parentData, data) {
+        const findPreData = function(list, a) {
+            if (!list || list.length === 0) { return null; }
+            for (let i = 0, iLen = list.length; i < iLen; i++) {
+                if (billsUtils.compareCode(list[i].code, a.code) > 0) {
+                    return i > 0 ? list[i - 1] : null;
+                }
+            }
+            return list[list.length - 1];
+        };
+
+        const pid = parentData ? parentData.tree_id : this.rootId;
+        const children = await this.getChildrenByParentId(tenderId, pid);
+        const preData = findPreData(children, data);
+        if (!preData || children.indexOf(preData) < children.length - 1) {
+            await this._updateChildrenOrder(tenderId, pid, preData ? preData.order + 1 : 1);
+        }
+        data.order = preData ? preData.order + 1 : 1;
+        const [addResult, node] = await this._addChildNodeData(tenderId, parentData, data);
+
+        return [addResult, node];
+    }
+
+    async addStdNodeWithParent(tenderId, stdData, stdLib) {
+        // 查询完整标准清单,并按层次排序
+        const fullLevel = await stdLib.getFullLevelDataByFullPath(stdData.list_id, stdData.full_path);
+        fullLevel.sort(function(x, y) {
+            return x.level - y.level;
+        });
+
+        let isNew = false,
+            node,
+            firstNew,
+            updateParent,
+            addResult;
+        const expandIds = [];
+        this.transaction = await this.db.beginTransaction();
+        try {
+            // 从最顶层节点依次查询是否存在,否则添加
+            for (let i = 0, len = fullLevel.length; i < len; i++) {
+                const stdNode = fullLevel[i];
+
+                if (isNew) {
+                    const newData = this._filterStdData(stdNode);
+                    newData.is_leaf = (i === len - 1);
+                    [addResult, node] = await this._addChildNodeData(tenderId, node, newData);
+                } else {
+                    const parent = node;
+                    node = await this.getDataByCondition({
+                        tid: tenderId,
+                        tree_pid: parent ? parent.tree_id : this.rootId,
+                        code: stdNode.code,
+                        name: stdNode.name,
+                    });
+                    if (!node) {
+                        isNew = true;
+                        const newData = this._filterStdData(stdNode);
+                        newData.is_leaf = (i === len - 1);
+                        [addResult, node] = await this._addChildAutoOrder(tenderId, parent, newData);
+                        if (parent && parent.is_leaf) {
+                            await this.transaction.update(this.tableName, { id: parent.id, is_leaf: false,
+                                unit_price: 0, quantity: 0, total_price: 0});
+                            updateParent = parent;
+                        }
+                        firstNew = node;
+                    } else {
+                        expandIds.push(node.tree_id);
+                    }
+                }
+            }
+            await this.transaction.commit();
+        } catch (err) {
+            await this.transaction.rollback();
+            throw err;
+        }
+
+        // 查询应返回的结果
+        let createData = [],
+            updateData = [];
+        if (firstNew) {
+            createData = await this.getDataByFullPath(tenderId, firstNew.full_path + '%');
+            updateData = await this.getNextsData(tenderId, firstNew.tree_pid, firstNew.order);
+            if (updateParent) {
+                updateData.push(await this.getDataByCondition({ id: updateParent.id }));
+            }
+        }
+        return { create: createData, update: updateData };
+    }
+
+    async addBillsNode(tenderId, selectId, data) {
+        return await this.addNode(tenderId, selectId, data ? data : {});
+    }
+
+    async addStdNode(tenderId, selectId, stdData) {
+        const newData = this._filterStdData(stdData);
+        const result = await this.addBillsNode(tenderId, selectId, newData);
+        return result;
+    }
+
+    async addStdNodeAsChild(tenderId, selectId, stdData) {
+        const newData = this._filterStdData(stdData);
+        const result = await this.addChild(tenderId, selectId, newData);
+        return result;
+    }
+
+    clearParentingData(data) {
+        data.unit_price = 0;
+        data.quantity = 0;
+        data.total_price = 0;
+    }
+
+    _filterUpdateInvalidField(id, data) {
+        const result = { id };
+        for (const prop in data) {
+            if (readOnlyFields.indexOf(prop) === -1) result[prop] = data[prop];
+        }
+        return result;
+    }
+    _checkCalcField(data) {
+        for (const prop in data) {
+            if (calcFields.indexOf(prop) >= 0) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    async updateCalc(tenderId, data) {
+        const helper = this.ctx.helper;
+        // 简单验证数据
+        if (tenderId <= 0 || !this.ctx.tender) throw '标段不存在';
+        if (!data) throw '提交数据错误';
+        const datas = data instanceof Array ? data : [data];
+        const ids = [];
+        for (const row of datas) {
+            if (tenderId !== row.tid) throw '提交数据错误';
+            ids.push(row.id);
+        }
+
+        const decimal = this.ctx.tender.info.decimal;
+        const conn = await this.db.beginTransaction();
+        try {
+            for (const row of datas) {
+                const updateNode = await this.getDataById(row.id);
+                if (!updateNode || tenderId !== updateNode.tid || row.tree_id !== updateNode.tree_id) throw '提交数据错误';
+                let updateData;
+
+                // 项目节、工程量清单相关
+                if (row.b_code) {
+                    row.dgn_qty1 = 0;
+                    row.dgn_qty2 = 0;
+                    row.code = '';
+                }
+                if (row.code) row.b_code = '';
+
+                if (this._checkCalcField(row)) {
+                    let calcData = JSON.parse(JSON.stringify(row));
+                    if (row.quantity !== undefined || row.unit_price !== undefined) {
+                        calcData.quantity = row.quantity === undefined ? updateNode.quantity : helper.round(row.quantity, decimal.qty);
+                        calcData.unit_price = row.unit_price === undefined ? updateNode.unit_price : helper.round(row.unit_price, decimal.up);
+                        calcData.total_price = helper.mul(calcData.quantity, calcData.unit_price, decimal.tp);
+                    } else if (row.total_price !== undefined ) {
+                        calcData.quantity = 0;
+                        calcData.unit_price = 0;
+                        calcData.total_price = helper.round(row.total_price, decimal.tp);
+                    }
+                    updateData = this._filterUpdateInvalidField(updateNode.id, this._.defaults(calcData, row));
+                } else {
+                    updateData = this._filterUpdateInvalidField(updateNode.id, row);
+                }
+                await conn.update(this.tableName, updateData);
+            }
+            await conn.commit();
+        } catch (err) {
+            await conn.rollback();
+            throw err;
+        }
+
+        return { update: await this.getDataById(ids) };
+    }
+    async pasteBlockData (tid, sid, pasteData, defaultData) {
+        if ((tid <= 0) || (sid <= 0)) return [];
+
+        if (!pasteData || pasteData.length <= 0) throw '复制数据错误';
+        for (const pd of pasteData) {
+            if (!pd || pd.length <= 0) throw '复制数据错误';
+            pd.sort(function (x, y) {
+                return x.level - y.level
+            });
+            if (pd[0].tree_pid !== pasteData[0][0].tree_pid) throw '复制数据错误:仅可操作同层节点';
+        }
+        const decimal = this.ctx.tender.info.decimal;
+        this.newBills = false;
+        const selectData = await this.getDataByKid(tid, sid);
+        if (!selectData) throw '粘贴数据错误';
+        const newParentPath = selectData.full_path.replace(selectData.tree_id, '');
+
+        const pasteBillsData = [];
+        let maxId = await this._getMaxLid(tid);
+        for (const [i, pd] of pasteData.entries()) {
+            for (const d of pd) {
+                d.children = pd.filter(function (x) {
+                    return x.tree_pid === d.tree_id;
+                });
+            }
+            const pbd = [];
+            for (const [j, d] of pd.entries()) {
+                const newBills = {
+                    id: this.uuid.v4(),
+                    tid: tid,
+                    tree_id: maxId + j + 1,
+                    tree_pid: j === 0 ? selectData.tree_pid : d.tree_pid,
+                    level: d.level + selectData.level - pd[0].level,
+                    order: j === 0 ? selectData.order + i + 1 : d.order,
+                    is_leaf: d.is_leaf,
+                    code: d.code,
+                    b_code: d.b_code,
+                    name: d.name,
+                    unit: d.unit,
+                    source: d.source,
+                    remark: d.remark,
+                    drawing_code: d.drawing_code,
+                    memo: d.memo,
+                    node_type: d.node_type,
+                };
+                for (const c of d.children) {
+                    c.tree_pid = newBills.tree_id;
+                }
+                if (d.b_code && d.is_leaf) {
+                    newBills.dgn_qty1 = 0;
+                    newBills.dgn_qty2 = 0;
+                    newBills.unit_price = this.ctx.helper.round(d.unit_price, decimal.up);
+                    newBills.quantity = this.ctx.helper.round(d.quantity, decimal.qty);
+                    newBills.total_price = this.ctx.helper.mul(newBills.quantity, newBills.unit_price, decimal.tp);
+                } else {
+                    newBills.dgn_qty1 = this.ctx.helper.round(d.dgn_qty1, decimal.qty);
+                    newBills.dgn_qty2 = this.ctx.helper.round(d.dgn_qty2, decimal.qty);
+                    newBills.unit_price = 0;
+                    newBills.quantity = 0;
+                    newBills.total_price = d.is_leaf ? this.ctx.helper.round(d.total_price, decimal.tp) : 0;
+                }
+                if (defaultData) this.ctx.helper._.assignIn(newBills, defaultData);
+                pbd.push(newBills);
+            }
+            for (const d of pbd) {
+                const parent = pbd.find(function (x) {
+                    return x.tree_id === d.tree_pid;
+                });
+                d.full_path = parent
+                    ? parent.full_path + '-' + d.tree_id
+                    : newParentPath + d.tree_id;
+                if (defaultData) this.ctx.helper._.assignIn(pbd, defaultData);
+                pasteBillsData.push(d);
+            }
+            maxId = maxId + pbd.length;
+        }
+
+        this.transaction = await this.db.beginTransaction();
+        try {
+            // 选中节点的所有后兄弟节点,order+粘贴节点个数
+            await this._updateChildrenOrder(tid, selectData.tree_pid, selectData.order + 1, pasteData.length);
+            // 数据库创建新增节点数据
+            if (pasteBillsData.length > 0) await this.transaction.insert(this.tableName, pasteBillsData);
+            this._cacheMaxLid(tid, maxId);
+            await this.transaction.commit();
+        } catch (err) {
+            await this.transaction.rollback();
+            throw err;
+        }
+
+        // 查询应返回的结果
+        const updateData = await this.getNextsData(selectData.tid, selectData.tree_pid, selectData.order + pasteData.length);
+        return { create: pasteBillsData, update: updateData };
+    }
+
+    async importExcel(templateId, excelData, filter) {
+        const AnalysisExcel = require('../lib/analysis_excel').AnalysisExcelTree;
+        const analysisExcel = new AnalysisExcel(this.ctx, this.setting, ['code', 'b_code']);
+        const tempData = await this.ctx.service.tenderNodeTemplate.getData(templateId, true);
+        const cacheTree = analysisExcel.analysisData(excelData, tempData, {
+            filterZeroGcl: filter, filterPos: true, filterGcl: false, filterCalc: true,
+        });
+        const orgMaxId = await this._getMaxLid(this.ctx.tender.id);
+        const conn = await this.db.beginTransaction();
+        try {
+            await conn.delete(this.tableName, { tid: this.ctx.tender.id });
+            const datas = [];
+            for (const node of cacheTree.items) {
+                const data = {
+                    id: node.id,
+                    tid: this.ctx.tender.id,
+                    tree_id: node.tree_id,
+                    tree_pid: node.tree_pid,
+                    level: node.level,
+                    order: node.order,
+                    is_leaf: !node.children || node.children.length === 0,
+                    full_path: node.full_path,
+                    code: node.code || '',
+                    b_code: node.b_code || '',
+                    name: node.name || '',
+                    unit: node.unit || '',
+                    unit_price: !node.children || node.children.length === 0 ? node.unit_price || 0 : 0,
+                    dgn_qty1: node.dgn_qty1 || 0,
+                    dgn_qty2: node.dgn_qty2 || 0,
+                    memo: node.memo,
+                    drawing_code: node.drawing_code,
+                    node_type: node.node_type,
+                    quantity: !node.children || node.children.length === 0 ? node.quantity || 0 : 0,
+                    total_price: !node.children || node.children.length === 0 ? node.total_price || 0 : 0,
+                };
+                datas.push(data);
+            }
+            await conn.insert(this.tableName, datas);
+            await conn.commit();
+            if (orgMaxId) this._cacheMaxLid(this.ctx.tender.id, cacheTree.keyNodeId);
+            return datas;
+        } catch (err) {
+            await conn.rollback();
+            throw err;
+        }
+    }
+
+    async getSumTp(tid) {
+        const sql = 'SELECT Sum(total_price) As total_price FROM ' + this.tableName + ' Where tid = ? and is_leaf = true';
+        const result = await this.db.queryOne(sql, [tid]);
+        return result.total_price;
+    }
+}
+
+module.exports = ControlPrice;

+ 1 - 1
app/service/ledger_audit.js

@@ -91,7 +91,7 @@ module.exports = app => {
         async getAuditors(tenderId, times = 1) {
             const sql =
                 'SELECT la.`audit_id`, pa.`name`, pa.`company`, pa.`role`, pa.`mobile`, pa.`telephone`, la.`times`, la.`audit_order`, la.`status`, la.`opinion`, la.`begin_time`, la.`end_time`, g.`sort` ' +
-                'FROM ?? AS la, ?? AS pa, (SELECT `audit_id`,(@i:=@i+1) as `sort` FROM ??, (select @i:=0) as it WHERE `tender_id` = ? AND `times` = ? GROUP BY `audit_id`) as g ' +
+                'FROM ?? AS la, ?? AS pa, (SELECT t1.`audit_id`,(@i:=@i+1) as `sort` FROM (SELECT t.`audit_id` FROM (select `audit_id` from ?? WHERE `tender_id` = ? AND `times` = ? ORDER BY `audit_order` LIMIT 200) t GROUP BY t.`audit_id`) t1, (select @i:=0) as it) as g ' +
                 'WHERE la.`tender_id` = ? and la.`times` = ? and la.`audit_id` = pa.`id` and g.`audit_id` = la.`audit_id` order by la.`audit_order`';
             const sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, this.tableName, tenderId, times, tenderId, times];
             const result = await this.db.query(sql, sqlParam);

+ 1 - 1
app/service/material_audit.js

@@ -53,7 +53,7 @@ module.exports = app => {
          */
         async getAuditors(materialId, times = 1) {
             const sql = 'SELECT la.`aid`, pa.`name`, pa.`company`, pa.`role`, pa.`mobile`, pa.`telephone`, la.`times`, la.`order`, la.`status`, la.`opinion`, la.`begin_time`, la.`end_time`, g.`sort` ' +
-                'FROM ?? AS la, ?? AS pa, (SELECT `aid`,(@i:=@i+1) as `sort` FROM ??, (select @i:=0) as it WHERE `mid` = ? AND `times` = ? GROUP BY `aid`) as g ' +
+                'FROM ?? AS la, ?? AS pa, (SELECT t1.`aid`,(@i:=@i+1) as `sort` FROM (SELECT t.`aid` FROM (select `aid` from ?? WHERE `mid` = ? AND `times` = ? ORDER BY `order` LIMIT 200) t GROUP BY t.`aid`) t1, (select @i:=0) as it) as g ' +
                 'WHERE la.`mid` = ? and la.`times` = ? and la.`aid` = pa.`id` and g.`aid` = la.`aid` order by la.`order`';
             const sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, this.tableName, materialId, times, materialId, times];
             const result = await this.db.query(sql, sqlParam);

+ 1 - 1
app/service/payment_detail_audit.js

@@ -77,7 +77,7 @@ module.exports = app => {
          */
         async getAuditors(detailId, times = 1) {
             const sql = 'SELECT la.`aid`, pa.`name`, pa.`company`, pa.`role`, pa.`mobile`, pa.`telephone`, la.`times`, la.`order`, la.`status`, la.`opinion`, la.`begin_time`, la.`end_time`, g.`sort` ' +
-                'FROM ?? AS la, ?? AS pa, (SELECT `aid`,(@i:=@i+1) as `sort` FROM ??, (select @i:=0) as it WHERE `td_id` = ? AND `times` = ? GROUP BY `aid`) as g ' +
+                'FROM ?? AS la, ?? AS pa, (SELECT t1.`aid`,(@i:=@i+1) as `sort` FROM (SELECT t.`aid` FROM (select `aid` from ?? WHERE `td_id` = ? AND `times` = ? ORDER BY `order` LIMIT 200) t GROUP BY t.`aid`) t1, (select @i:=0) as it) as g ' +
                 'WHERE la.`td_id` = ? and la.`times` = ? and la.`aid` = pa.`id` and g.`aid` = la.`aid` order by la.`order`';
             const sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, this.tableName, detailId, times, detailId, times];
             const result = await this.db.query(sql, sqlParam);

+ 3 - 1
app/service/payment_rpt_audit.js

@@ -35,7 +35,9 @@ module.exports = app => {
                 let sign_time = null;
                 if ((this.ctx.detail.status !== auditConst.status.uncheck && this.ctx.detail.status !== auditConst.status.checkNo && this.ctx.detail.uid === uid) ||
                     (this._.findIndex(this.ctx.detail.auditors, { aid: uid }) === -1 && this.ctx.detail.uid !== uid) ||
-                    (this._.findIndex(this.ctx.detail.auditors, { aid: uid }) !== -1 && this.ctx.detail.curAuditor && this.ctx.detail.curAuditor.aid !== uid)) {
+                    (this._.findIndex(this.ctx.detail.auditors, { aid: uid }) !== -1 &&
+                        ((this.ctx.detail.status === auditConst.status.checked && !this.ctx.detail.curAuditor) ||
+                            (this.ctx.detail.curAuditor && this.ctx.detail.curAuditor.aid !== uid)))) {
                     let report_json = JSON.parse(this.ctx.detail.report_json);
                     report_json = await this.ctx.service.paymentDetail.signOneSignatureData(report_json, rptAudit.signature_name, signature_msg);
                     // 同步期信息

+ 32 - 0
app/service/project_stopmsg.js

@@ -0,0 +1,32 @@
+'use strict';
+
+/**
+ * 系统维护数据模型
+ *
+ * @author CaiAoLin
+ * @date 2017/10/25
+ * @version
+ */
+module.exports = app => {
+
+    class ProjectStopMsg extends app.BaseService {
+
+        /**
+         * 构造函数
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        constructor(ctx) {
+            super(ctx);
+            this.tableName = 'project_stopmsg';
+        }
+
+        async getMsg(pid) {
+            const info = await this.getDataByCondition({ pid, is_checked: 1 });
+            return info ? info.msg : '该账号已被停用,请联系销售人员';
+        }
+    }
+
+    return ProjectStopMsg;
+};

+ 4 - 0
app/service/report.js

@@ -393,6 +393,10 @@ module.exports = app => {
                             runnableRst.push(service.reportMemory.getPmDeal());
                             runnableKey.push(filter);
                             break;
+                        case 'mem_pm_deal_tree':
+                            runnableRst.push(service.reportMemory.getPmDealTree());
+                            runnableKey.push(filter);
+                            break;
                         case 'mem_schedule_month':
                             runnableRst.push(service.scheduleMonth.getReportData(params.tender_id));
                             runnableKey.push(filter);

+ 36 - 3
app/service/report_memory.js

@@ -1471,9 +1471,12 @@ module.exports = app => {
             let result = [];
             if (!PermissionCheck.viewPmData(this.ctx.session.sessionUser.permission)) return result;
 
-            const selects = await this.ctx.service.project.getPmDealCache(this.ctx.session.sessionProject.id);
-            const pm = require('../lib/pm');
-            this.pmDeal = await pm.dealData(this.ctx, this.ctx.session.sessionProject.code, selects);
+            let selects;
+            if (!this.pmDeal) {
+                selects = await this.ctx.service.project.getPmDealCache(this.ctx.session.sessionProject.id);
+                const pm = require('../lib/pm');
+                this.pmDeal = await pm.dealData(this.ctx, this.ctx.session.sessionProject.code, selects);
+            }
 
             result = this.pmDeal.contracts ? this.pmDeal.contracts.filter(x => { return x.ContractsType === 2 }) : [];
             const fields = ['Code', 'Name', 'ContractName', 'ContractCode', 'ContractPrice', 'ContractReturned', 'ContractsPaid', 'ContractStatus', 'ContractLocking', 'CreateTime'], self = this;
@@ -1496,6 +1499,36 @@ module.exports = app => {
             });
             return result;
         }
+
+        async getPmDealTree() {
+            const result = [];
+            if (!PermissionCheck.viewPmData(this.ctx.session.sessionUser.permission)) return result;
+
+            if (!this.pmDeal) {
+                const selects = await this.ctx.service.project.getPmDealCache(this.ctx.session.sessionProject.id);
+                const pm = require('../lib/pm');
+                this.pmDeal = await pm.dealData(this.ctx, this.ctx.session.sessionProject.code, selects);
+            }
+
+            for (const tc of this.pmDeal.tree_contracts) {
+                tc.children = this.pmDeal.tree_contracts.filter(x => { return x.ParentId === tc.TreeId && x.TreeType === tc.TreeType});
+            }
+            const root = this.pmDeal.tree_contracts.filter(x => { return x.Depth === 0; });
+            root.sort((x, y) => {
+                const type = x.TreeType - y.TreeType;
+                return type ? type : x.Serail - y.Serail;
+            });
+            const sortChildren = function (children, data) {
+                if (!children || children.length === 0) return;
+                for (const c of children) {
+                    data.push(c);
+                    sortChildren(c.children, data);
+                }
+            };
+            sortChildren(root, result);
+            result.forEach(x => { delete x.children });
+            return result;
+        }
     }
 
     return ReportMemory;

+ 1 - 1
app/service/revise_audit.js

@@ -76,7 +76,7 @@ module.exports = app => {
         async getAuditors2ReviseList(rid, times = 1) {
             const sql =
                 'SELECT la.`audit_id`, pa.`name`, pa.`company`, pa.`role`, pa.`mobile`, pa.`telephone`, la.`times`, la.`audit_order`, la.`status`, la.`opinion`, la.`begin_time`, la.`end_time`, g.`sort` ' +
-                'FROM ?? AS la, ?? AS pa, (SELECT `audit_id`,(@i:=@i+1) as `sort` FROM ??, (select @i:=0) as it WHERE `rid` = ? AND `times` = ? GROUP BY `audit_id`) as g ' +
+                'FROM ?? AS la, ?? AS pa, (SELECT t1.`audit_id`,(@i:=@i+1) as `sort` FROM (SELECT t.`audit_id` FROM (select `audit_id` from ?? WHERE `rid` = ? AND `times` = ? ORDER BY `audit_order` LIMIT 200) t GROUP BY t.`audit_id`) t1, (select @i:=0) as it) as g ' +
                 'WHERE la.`rid` = ? and la.`times` = ? and la.`audit_id` = pa.`id` and g.`audit_id` = la.`audit_id` order by la.`audit_order`';
             const sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, this.tableName, rid, times, rid, times];
             const result = await this.db.query(sql, sqlParam);

+ 4 - 2
app/service/stage.js

@@ -622,7 +622,8 @@ module.exports = app => {
                         break;
                     case 'ybbqwc':
                         const sumGcl = await this.ctx.service.stageBills.getSumTotalPriceGcl(stage, '^[^0-9]*1[0-9]{2}(-|$)');
-                        cb.value = this.ctx.helper.add(sumGcl.contract_tp, sumGcl.qc_tp);
+                        const sumPc = await this.ctx.service.stageBillsPc.getSumTotalPriceGcl(stage, '^[^0-9]*1[0-9]{2}(-|$)');
+                        cb.value = this.ctx.helper.sum([sumGcl.contract_tp, sumGcl.qc_tp, sumPc.pc_tp]);
                         break;
                     case 'ybbqbg':
                         cb.value = bg.common;
@@ -822,7 +823,8 @@ module.exports = app => {
                     case 'bab':
                     case 'jiub':
                         const sumGcl = await this.ctx.service.stageBills.getSumTotalPriceGclByMaterial(stage_list, cb.filter);
-                        cb.value = this.ctx.helper.add(sumGcl.contract_tp, sumGcl.qc_tp);
+                        const sumPc = await this.ctx.service.stageBillsPc.getSumTotalPriceGclByMaterial(stage_list, cb.filter);
+                        cb.value = this.ctx.helper.sum([sumGcl.contract_tp, sumGcl.qc_tp, sumPc.pc_tp]);
                         break;
                     case 'bqyf':
                         cb.value = this.ctx.helper.roundNum(this._.sumBy(stage_list, 'yf_tp'), 2);

+ 4 - 4
app/service/stage_audit.js

@@ -1069,10 +1069,10 @@ module.exports = app => {
                 });
 
                 // 计量明细:重复数据删除原报,新增数据修改为原报
-                await this.ctx.service.stageBills.updateStageBills4CheckCancel(stage.id, stage.times, 0, stage.time, 1, transaction);
-                await this.ctx.service.stagePos.updateStagePos4CheckCancel(stage.id, stage.times, 0, stage.time, 1, transaction);
-                await this.ctx.service.stageDetail.updateStageDetail4CheckCancel(stage.id, stage.times, 0, stage.time, 1, transaction);
-                await this.ctx.service.stageChange.updateStageChange4CheckCancel(stage.id, stage.times, 0, stage.time, 1, transaction);
+                await this.ctx.service.stageBills.updateStageBills4CheckCancel(stage.id, stage.times, 0, stage.times, 1, transaction);
+                await this.ctx.service.stagePos.updateStagePos4CheckCancel(stage.id, stage.times, 0, stage.times, 1, transaction);
+                await this.ctx.service.stageDetail.updateStageDetail4CheckCancel(stage.id, stage.times, 0, stage.times, 1, transaction);
+                await this.ctx.service.stageChange.updateStageChange4CheckCancel(stage.id, stage.times, 0, stage.times, 1, transaction);
                 // 合同支付明细:保留审批人数据,覆盖到原报中并删除该审批人合同支付数据
                 await this.ctx.service.stagePay.deleteAuditStagePays(stage, stage.times, 0, transaction);
                 await this.ctx.service.stagePay.copyAuditStagePays(stage, stage.times, 0, transaction, this.ctx.stage.times, 1);

+ 32 - 0
app/service/stage_bills_pc.js

@@ -30,6 +30,16 @@ module.exports = app => {
             return result;
         }
 
+        async getSumTotalPriceFilter(stage, operate, filter) {
+            const sql = 'SELECT Sum(`contract_pc_tp`) As `contract_pc_tp`, Sum(`qc_pc_tp`) As `qc_pc_tp`, Sum(`pc_tp`) As `pc_tp`, Sum(`positive_qc_pc_tp`) As `positive_qc_pc_tp`, Sum(`negative_qc_pc_tp`) As `negative_qc_pc_tp`' +
+                '  FROM ' + this.tableName + ' As Bills' +
+                '  INNER JOIN ' + this.ctx.service.ledger.departTableName(stage.tid) + ' As Ledger ON Bills.lid = Ledger.id' +
+                '  WHERE Bills.sid = ? And Ledger.b_code ' + operate + ' ?';
+            const sqlParam = [stage.id, filter];
+            const result = await this.db.queryOne(sql, sqlParam);
+            return result;
+        }
+
         async getSumTotalPriceByMaterial(stage_list) {
             let contract_pc_tp = 0;
             let qc_pc_tp = 0;
@@ -45,6 +55,28 @@ module.exports = app => {
             return { contract_pc_tp, qc_pc_tp, pc_tp };
         }
 
+        async getSumTotalPriceGclByMaterial(stage_list, regText) {
+            let contract_pc_tp = 0;
+            let qc_pc_tp = 0;
+            let pc_tp = 0;
+            for (const stage of stage_list) {
+                const result = await this.getSumTotalPriceGcl(stage, regText);
+                if (result) {
+                    contract_pc_tp = this.ctx.helper.add(contract_pc_tp, result.contract_pc_tp);
+                    qc_pc_tp = this.ctx.helper.add(qc_pc_tp, result.qc_pc_tp);
+                    pc_tp = this.ctx.helper.add(pc_tp, result.pc_tp);
+                }
+            }
+            return { contract_pc_tp, qc_pc_tp, pc_tp };
+        }
+
+        async getSumTotalPriceGcl(stage, regText) {
+            if (regText) {
+                return await this.getSumTotalPriceFilter(stage, 'REGEXP', regText);
+            }
+            return await this.getSumTotalPriceFilter(stage, '<>', this.db.escape(''));
+        }
+
         async getEndStageData(stage) {
             const sql = 'SELECT lid, SUM(contract_pc_tp) AS end_contract_pc_tp, SUM(qc_pc_tp) AS end_qc_pc_tp,' +
                 '    SUM(pc_tp) AS end_pc_tp, Sum(`positive_qc_pc_tp`) As `positive_qc_pc_tp`, Sum(`negative_qc_pc_tp`) As `negative_qc_pc_tp`,' +

+ 2 - 2
app/service/sub_proj_info.js

@@ -12,8 +12,8 @@ const defaultInfo = {
     main_quantity: [
         { id: 1, name: '路基土石方', unit: 'm3', dgn_qty: 0, final_qty: 0, },
         { id: 2, name: '特殊路基处理', unit: 'km', dgn_qty: 0, final_qty: 0, },
-        { id: 3, name: '路基排水工', unit: 'm3', dgn_qty: 0, final_qty: 0, },
-        { id: 4, name: '路基防护工', unit: 'm3', dgn_qty: 0, final_qty: 0, },
+        { id: 3, name: '路基排水工', unit: 'm3', dgn_qty: 0, final_qty: 0, },
+        { id: 4, name: '路基防护工', unit: 'm3', dgn_qty: 0, final_qty: 0, },
         { id: 5, name: '路面工程', unit: 'm2', dgn_qty: 0, final_qty: 0, },
         { id: 6, name: '大、特大桥', unit: 'm/座', dgn_qty: 0, final_qty: 0, },
         { id: 7, name: '中、小桥', unit: 'm/座', dgn_qty: 0, final_qty: 0, },

+ 2 - 1
app/service/tender_cache.js

@@ -40,6 +40,7 @@ module.exports = app => {
                 tender.cur_flow.title = '台账';
                 tender.pre_flow = cache.ledger_flow_pre_info ? JSON.parse(cache.ledger_flow_pre_info) : null;
                 tender.stage_tp = {};
+                tender.stage_count = 0;
                 tender.progress = {
                     title: '台账',
                     status: auditConst.ledger.tiStatusString[cache.ledger_status],
@@ -530,4 +531,4 @@ module.exports = app => {
     }
 
     return TenderCache;
-};
+};

+ 1 - 1
app/view/budget/info.ejs

@@ -29,7 +29,7 @@
                                         <th class="pl-3">费用名称</th>
                                         <th width="20%">设计概算</th>
                                         <th width="20%">工程决算</th>
-                                        <th width="20%">增减金额</th>
+                                        <th width="20%">增幅(%)</th>
                                     </tr>
                                     </thead>
                                     <tbody class="text-right" id="jianan-table">

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

@@ -2,6 +2,8 @@
     <div class="panel-title fluid">
         <div class="title-main  d-flex justify-content-between">
             <div>动态投资</div>
+            <div class="d-inline-block ml-1" id="show-level"></div>
+            <div class="ml-auto"></div>
         </div>
     </div>
     <div class="content-wrap">

+ 1 - 1
app/view/change/index.ejs

@@ -101,7 +101,7 @@
                     <% if (changes.length > 0) { %>
                     <% for (const [index, c] of changes.entries()) { %>
                     <tr>
-                        <td><%- (pageInfo.page-1)*pageInfo.pageSize + index+1 %></td>
+                        <td class="text-center"><%- (pageInfo.page-1)*pageInfo.pageSize + index+1 %></td>
                         <td><a href="/tender/<%- tender.id %>/change/<%- c.cid %>/information"><% if (c.status !== auditConst.status.checked) { %><%- c.code %><% } else { %><%- c.p_code %><% } %></a></td>
                         <td><%- c.name %></td>
                         <td><%- qualityArray[c.quality] %><% c.quality %></td>

+ 3 - 3
app/view/file/file_modal.ejs

@@ -9,11 +9,11 @@
                 <div class="row">
                     <div class="col-4">
                         <div class="d-flex justify-content-center bg-graye">
-                            <div class="p-2">文档类别</div>
+                            <div class="p-2 vertical-middle"><input class="mr-1" type="checkbox" id="filing-select-all">文档类别</div>
                         </div>
                         <div class="modal-height-400">
                             <div class="category">
-                                <ul>
+                                <ul style="list-style: none">
                                     <% for (const ft of filingTypes) { %>
                                     <li>
                                         <div class="form-check">
@@ -44,7 +44,7 @@
                         <div class="modal-height-400 scroll-y">
                             <table class="table table-bordered">
                                 <thead>
-                                <tr><th>选择</th><th>用户名</th><th>授权时间</th><th>权限</th><th>操作</th></tr>
+                                <tr><th class="vertical-middle"><input class="mr-1" type="checkbox" id="user-select-all">选择</th><th>用户名</th><th>授权时间</th><th>权限</th><th>操作</th></tr>
                                 </thead>
                                 <tbody id="filing-valid">
                                 </tbody>

+ 2 - 0
app/view/file/index.ejs

@@ -2,6 +2,8 @@
     <div class="panel-title fluid">
         <div class="title-main  d-flex justify-content-between">
             <div>资料归集</div>
+            <div class="d-inline-block ml-1" id="show-level"></div>
+            <div class="ml-auto"></div>
         </div>
     </div>
     <div class="content-wrap">

+ 0 - 17
app/view/ledger/explode_modal.ejs

@@ -425,23 +425,6 @@
     $('#sp-list').on('hidden.bs.modal', function (e) {
         $(document.body).addClass('modal-open');
     });
-
-    // 展开历史审核记录
-    $('.modal-body #fold-btn').click(function () {
-        const type = $(this).data('target')
-        const auditCard = $(this).parent().parent()
-        if (type === 'show') {
-            $(this).data('target', 'hide')
-            auditCard.find('.fold-card').slideDown('swing', () => {
-                auditCard.find('#fold-btn').text('收起历史审核记录')
-            })
-        } else {
-            $(this).data('target', 'show')
-            auditCard.find('.fold-card').slideUp('swing', () => {
-                auditCard.find('#fold-btn').text('展开历史审核记录')
-            })
-        }
-    });
 </script>
 <% } %>
 <% include ../shares/merge_peg_modal.ejs %>

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

@@ -7,6 +7,9 @@
                 期列表
             </h2>
             <div class="ml-auto">
+                <% if (ctx.session.sessionProject.page_show.openSettle) { %>
+                <a href="/tender/<%= ctx.tender.id %>/settle" class="btn btn-primary btn-sm">计量结算</a>
+                <% } %>
                 <% if (ctx.session.sessionUser.accountId === ctx.tender.data.user_id && ctx.tender.data.ledger_status === auditConst.status.checked &&
                         (stages.length === 0 || stages[0].status === auditConst.status.checked)) { %>
                     <% if (!ctx.session.sessionProject.page_show.close1stStageCheckDealParam && ctx.helper.checkZero(ctx.tender.info.deal_param.contractPrice) && stages.length === 0) { %>
@@ -92,11 +95,11 @@
                         <td class="text-center">
                             <% if (s.status === auditConst.status.uncheck && s.user_id === ctx.session.sessionUser.accountId) { %>
                             <a href="<%- '/tender/' + ctx.tender.id + '/measure/stage/' + s.order %>" target="_blank" class="btn <%- auditConst.statusButtonClass[s.status] %> btn-sm"><%- auditConst.statusButton[s.status] %></a>
-                            <% } else if (s.status === auditConst.status.checkNo && s.curAuditor && s.user_id === ctx.session.sessionUser.accountId) { %>
+                            <% } else if (s.status === auditConst.status.checkNo && s.curAuditors && s.user_id === ctx.session.sessionUser.accountId) { %>
                             <a href="<%- '/tender/' + ctx.tender.id + '/measure/stage/' + s.order %>" target="_blank" class="btn <%- auditConst.statusButtonClass[s.status] %> btn-sm"><%- auditConst.statusButton[s.status] %></a>
-                            <% } else if (s.status === auditConst.status.checking && s.curAuditor && s.curAuditor.aid === ctx.session.sessionUser.accountId) { %>
+                            <% } else if (s.status === auditConst.status.checking && s.curAuditors && s.curAuditors.findIndex(x => { return x.aid === ctx.session.sessionUser.accountId; }) >= 0) { %>
                             <a href="<%- '/tender/' + ctx.tender.id + '/measure/stage/' + s.order %>" target="_blank" class="btn <%- auditConst.statusButtonClass[s.status] %> btn-sm"><%- auditConst.statusButton[s.status] %></a>
-                            <% } else if (s.status === auditConst.status.checkNoPre && s.curAuditor && s.curAuditor2.aid === ctx.session.sessionUser.accountId) { %>
+                            <% } else if (s.status === auditConst.status.checkNoPre && s.curAuditors && s.curAuditor2.findIndex(x => { return x.aid === ctx.session.sessionUser.accountId; }) >= 0) { %>
                             <a href="<%- '/tender/' + ctx.tender.id + '/measure/stage/' + s.order %>" target="_blank" class="btn <%- auditConst.statusButtonClass[s.status] %> btn-sm"><%- auditConst.statusButton[s.status] %></a>
                             <% } else { %>
                             <span class="<%- auditConst.auditStringClass[s.status] %>"><%- auditConst.auditString[s.status] %></span>

+ 8 - 6
app/view/setting/manage.ejs

@@ -1,11 +1,13 @@
 <% include ./sub_menu.ejs %>
 <div class="panel-content">
-    <div class="panel-title">
-        <div class="title-main">
-            <h2>标段管理
-                <!--  <a href="#add-company" data-toggle="modal" data-target="#add-company" class="btn btn-primary btn-sm pull-right">添加单位</a>
-                 <a href="#ver" data-toggle="modal" data-target="#add-user" class="btn btn-primary btn-sm pull-right">添加账号</a> -->
-            </h2>
+    <div class="panel-title fluid">
+        <div class="title-main d-flex justify-content-between">
+            <div>
+                <div class="d-inline-block mr-2" style="font-weight: 500">
+                    标段管理
+                </div>
+                <div class="d-inline-block" id="show-level"></div>
+            </div>
         </div>
     </div>
     <div class="content-wrap">

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

@@ -1,6 +1,6 @@
 <div class="panel-sidebar">
     <div class="sidebar-title">
-        项目信息
+        项目设置
     </div>
     <div class="scrollbar-auto">
         <div class="nav-box">

+ 0 - 0
app/view/settle/list.ejs


+ 0 - 0
app/view/settle/list_modal.ejs


+ 1 - 1
app/view/stage/audit_modal.ejs

@@ -685,7 +685,7 @@
                 <h5 class="modal-title">重新审批</h5>
             </div>
             <div class="modal-body">
-                <h5>确认由「终审-<%= ctx.stage.auditors[ctx.stage.auditors.length-1].name %>」重新审批「第<%= ctx.stage.order %>期」?
+                <h5>确认由「终审-<%= ctx.session.sessionUser.name %>」重新审批「第<%= ctx.stage.order %>期」?
                 </h5>
                 <% if (ctx.session.sessionUser.loginStatus === 0) { %>
                 <div class="form-group">

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

@@ -2,6 +2,7 @@
     <div class="panel-title fluid">
         <div class="title-main  d-flex justify-content-between">
             <div>项目列表</div>
+            <div class="d-inline-block ml-1" id="show-level"></div>
             <% if (ctx.session.sessionUser.is_admin) { %>
             <div class="ml-auto">
                 <a href="#add-folder" name="add" data-toggle="modal" data-target="#add-folder" class="btn btn-sm btn-primary">新建文件夹</a>

+ 96 - 0
app/view/tender/ctrl_price.ejs

@@ -0,0 +1,96 @@
+<% include ./tender_sub_menu.ejs %>
+<div class="panel-content">
+    <div class="panel-title">
+        <div class="title-main d-flex">
+            <% include ./tender_sub_mini_menu.ejs %>
+            <div>
+                <div class="d-inline-block">控制价</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);" name="base-opr" type="add" 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);" name="base-opr" type="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);" name="base-opr" type="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);" name="base-opr" type="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);" name="base-opr" type="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);" name="base-opr" type="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>
+                </div>
+                <div class="d-inline-block ml-3">
+                    <a class="btn btn-sm btn-primary" href="javascript: void(0);" id="ctrl-price-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="ctrl-price-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>
+                    <div id="std-gcl" class="tab-pane tab-select-show">
+                    </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>
+                <li class="nav-item">
+                    <a class="nav-link" content="#std-gcl" href="javascript: void(0);">工程量清单</a>
+                </li>
+            </ul>
+        </div>
+    </div>
+</div>
+<script>
+    const stdChapters = JSON.parse(unescape('<%- escape(JSON.stringify(stdChapters)) %>'));
+    const stdBills = JSON.parse(unescape('<%- escape(JSON.stringify(stdBills)) %>'));
+    const readOnly = false;
+    const spreadSetting = JSON.parse('<%- JSON.stringify(spreadSetting) %>');
+    let decimal = JSON.parse('<%- JSON.stringify(ctx.tender.info.decimal) %>');
+</script>

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

@@ -0,0 +1,2 @@
+<% include ../shares/delete_hint_modal.ejs %>
+<% include ../shares/import_excel_modal.ejs %>

+ 12 - 1
app/view/tender/detail_modal.ejs

@@ -72,7 +72,7 @@
                                             <input type="text" class="form-control" value="" id="deal-type">
                                         </div>
                                     </div>
-                                    <div class="col-12">
+                                    <div class="col-12 mb-2">
                                         <div class="input-group input-group-sm">
                                             <div class="input-group-prepend">
                                                 <span class="input-group-text" style="width:90px">结算书编号</span>
@@ -80,6 +80,14 @@
                                             <input type="text" class="form-control" value="" id="final-code">
                                         </div>
                                     </div>
+                                    <div class="col-12 mb-2">
+                                        <div class="input-group input-group-sm">
+                                            <div class="input-group-prepend">
+                                                <span class="input-group-text" style="width:90px">概算批复编号</span>
+                                            </div>
+                                            <input type="text" class="form-control" value="" id="budget-approval-code">
+                                        </div>
+                                    </div>
                                 </div>
                             </div>
                         </div>
@@ -406,6 +414,7 @@
                                                 <span class="input-group-text">业主控制价</span>
                                             </div>
                                             <input type="number" class="form-control nospin" value="" id="control-price" onchange="checkNumberValid(this)">
+                                            <a class="ml-1 btn btn-sm btn-primary" href="/tender/<%= ctx.tender.id %>/ctrl-price"><i class="fa fa-list"></i></a>
                                         </div>
                                     </div>
                                     <div class="col-12 mb-2">
@@ -966,6 +975,7 @@
         $('#project-type').val(property.deal_info.projectType);
         $('#deal-type').val(property.deal_info.dealType);
         $('#final-code').val(property.deal_info.finalCode);
+        $('#budget-approval-code').val(property.deal_info.budgetApprovalCode);
 
         // 参建单位
         // 建设单位
@@ -1031,6 +1041,7 @@
                 projectType: $('#project-type').val(),
                 dealType: $('#deal-type').val(),
                 finalCode: $('#final-code').val(),
+                budgetApprovalCode: $('#budget-approval-code').val(),
             },
             construction_unit: {
                 build: {

+ 25 - 1
config/web.js

@@ -138,6 +138,27 @@ const JsFiles = {
                 ],
                 mergeFile: 'tender_shenpi',
             },
+            ctrlPrice: {
+                files: [
+                    '/public/js/js-xlsx/xlsx.full.min.js',
+                    '/public/js/js-xlsx/xlsx.utils.js',
+                    '/public/js/spreadjs/sheets/v11/gc.spread.sheets.all.11.2.2.min.js',
+                    '/public/js/decimal.min.js',
+                    '/public/js/math.min.js',
+                ],
+                mergeFiles: [
+                    '/public/js/sub_menu.js',
+                    '/public/js/div_resizer.js',
+                    '/public/js/spreadjs_rela/spreadjs_zh.js',
+                    '/public/js/shares/sjs_setting.js',
+                    '/public/js/shares/cs_tools.js',
+                    '/public/js/zh_calc.js',
+                    '/public/js/path_tree.js',
+                    '/public/js/std_lib.js',
+                    '/public/js/ctrl_price.js',
+                ],
+                mergeFile: 'ctrl_price',
+            }
         },
         ledger: {
             explode: {
@@ -1026,6 +1047,7 @@ const JsFiles = {
                     '/public/js/shares/drag_tree.js',
                     '/public/js/path_tree.js',
                     '/public/js/shares/tenders2tree.js',
+                    '/public/js/shares/show_level.js',
                     '/public/js/spreadjs_rela/spreadjs_zh.js',
                     '/public/js/sub_project.js',
                 ],
@@ -1064,6 +1086,7 @@ const JsFiles = {
                     '/public/js/shares/drag_tree.js',
                     '/public/js/path_tree.js',
                     '/public/js/shares/tenders2tree.js',
+                    '/public/js/shares/show_level.js',
                     '/public/js/spreadjs_rela/spreadjs_zh.js',
                     '/public/js/file_list.js',
                 ],
@@ -1095,6 +1118,7 @@ const JsFiles = {
                     '/public/js/shares/drag_tree.js',
                     '/public/js/path_tree.js',
                     '/public/js/shares/tenders2tree.js',
+                    '/public/js/shares/show_level.js',
                     '/public/js/spreadjs_rela/spreadjs_zh.js',
                     '/public/js/budget_list.js',
                 ],
@@ -1308,7 +1332,7 @@ const JsFiles = {
                     '/public/js/setting_manage.js',
                     '/public/js/tender_list_base.js',
                 ],
-                mergeFile: 'tender_list',
+                mergeFile: 'setting_manage',
             },
         },
     },

+ 49 - 0
sql/update.sql

@@ -33,5 +33,54 @@ CREATE TABLE `zh_budget_zb` (
   KEY `idx_full_path` (`bid`,`full_path`) USING BTREE
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='招标预算数据';
 
+ALTER TABLE `zh_budget_final`
+ADD COLUMN `zb_dgn_qty1`  decimal(24,8) NOT NULL DEFAULT 0.00000000 COMMENT '预算-设计数量1' AFTER `yu_tp`,
+ADD COLUMN `zb_dgn_qty2`  decimal(24,8) NOT NULL DEFAULT 0.00000000 COMMENT '预算-设计数量1' AFTER `zb_dgn_qty1`,
+ADD COLUMN `zb_dgn_qty`  varchar(50) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '预算-设计数量1/设计数量2' AFTER `zb_dgn_qty2`,
+ADD COLUMN `zb_dgn_price`  decimal(24,8) NOT NULL DEFAULT 0.00000000 COMMENT '预算-经济指标' AFTER `zb_dgn_qty`,
+ADD COLUMN `zb_tp`  decimal(24,8) NOT NULL DEFAULT 0.00000000 COMMENT '预算-金额' AFTER `zb_dgn_price`;
+
+CREATE TABLE `zh_control_price` (
+  `id` varchar(36) NOT NULL COMMENT 'uuid',
+  `tid` 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) DEFAULT '' COMMENT '层级定位辅助字段parent.full_path-tree_id',
+  `is_leaf` tinyint(1) NOT NULL COMMENT '是否叶子节点,界面显示辅助字段',
+  `code` varchar(50) DEFAULT '' COMMENT '节点编号',
+  `b_code` varchar(50) DEFAULT NULL,
+  `name` varchar(255) DEFAULT NULL COMMENT '名称',
+  `unit` varchar(15) 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) 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) DEFAULT NULL COMMENT '图册号',
+  `memo` varchar(1000) DEFAULT '' COMMENT '备注',
+  `node_type` int(4) unsigned DEFAULT '0' COMMENT '节点类别',
+  `source` varchar(30) DEFAULT '' COMMENT '添加源',
+  `remark` varchar(60) DEFAULT '' COMMENT '备注',
+  PRIMARY KEY (`id`),
+  KEY `idx_tid` (`tid`),
+  KEY `idx_tree_pid` (`tree_pid`),
+  KEY `idx_level` (`level`),
+  KEY `idx_full_path` (`tid`,`full_path`) USING BTREE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='招标控制价';
+
 ALTER TABLE `zh_material_list_notjoin`
 ADD COLUMN `type` tinyint(1) NOT NULL DEFAULT 1 COMMENT '1为本期不参与调差,2为数量变更不参与调差' AFTER `mx_id`;
+
+ALTER TABLE `zh_stage_bills_pc`
+MODIFY COLUMN `lid` varchar(36) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '项目节id' AFTER `sorder`;
+
+CREATE TABLE `calculation`.`zh_project_stopmsg`  (
+  `id` int NOT NULL AUTO_INCREMENT,
+  `pid` int NOT NULL COMMENT '项目id',
+  `msg` varchar(255) NULL COMMENT '停用信息',
+  `is_checked` tinyint(1) NULL DEFAULT 0 COMMENT '是否选中',
+  `in_time` datetime NULL COMMENT '创建时间',
+  PRIMARY KEY (`id`)
+) ENGINE = InnoDB CHARACTER SET = utf8 COMMENT = '用户账号停用提示表';