浏览代码

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

Tony Kang 1 年之前
父节点
当前提交
6043c88b7d
共有 100 个文件被更改,包括 3279 次插入441 次删除
  1. 7 1
      app/base/base_controller.js
  2. 12 7
      app/base/base_tree_service.js
  3. 3 0
      app/const/audit.js
  4. 20 0
      app/const/fun_set.js
  5. 2 0
      app/const/page_show.js
  6. 1 0
      app/const/source_type.js
  7. 4 1
      app/controller/budget_controller.js
  8. 145 43
      app/controller/change_controller.js
  9. 30 0
      app/controller/login_controller.js
  10. 4 2
      app/controller/setting_controller.js
  11. 14 7
      app/controller/stage_controller.js
  12. 49 1
      app/controller/sub_proj_controller.js
  13. 20 0
      app/lib/budget_final.js
  14. 1 3
      app/lib/pay_calc.js
  15. 73 38
      app/lib/rm/budget.js
  16. 1 0
      app/lib/rm/material.js
  17. 4 0
      app/lib/rm/tender.js
  18. 106 0
      app/lib/rm/tender_budget.js
  19. 1 0
      app/middleware/change_apply_check.js
  20. 1 1
      app/middleware/stage_check.js
  21. 16 0
      app/public/js/budget_compare.js
  22. 23 2
      app/public/js/budget_list.js
  23. 28 13
      app/public/js/change.js
  24. 154 5
      app/public/js/change_apply_information.js
  25. 3 0
      app/public/js/change_company.js
  26. 130 2
      app/public/js/change_information.js
  27. 23 13
      app/public/js/change_information_approval.js
  28. 402 48
      app/public/js/change_information_set.js
  29. 27 17
      app/public/js/change_information_show.js
  30. 2 2
      app/public/js/change_plan_information.js
  31. 1 0
      app/public/js/change_revise.js
  32. 19 1
      app/public/js/file_list.js
  33. 10 5
      app/public/js/ledger.js
  34. 1 1
      app/public/js/material.js
  35. 1 1
      app/public/js/material_checklist.js
  36. 1 1
      app/public/js/material_list.js
  37. 1 1
      app/public/js/measure_stage.js
  38. 208 0
      app/public/js/sp_data.js
  39. 11 9
      app/public/js/spreadjs_rela/spreadjs_zh.js
  40. 26 6
      app/public/js/stage.js
  41. 11 9
      app/public/js/stage_audit.js
  42. 20 17
      app/public/js/stage_change.js
  43. 6 3
      app/public/js/stage_pay.js
  44. 54 1
      app/public/js/sub_project.js
  45. 7 0
      app/router.js
  46. 30 0
      app/service/budget.js
  47. 28 0
      app/service/budget_zb.js
  48. 137 28
      app/service/change.js
  49. 3 2
      app/service/change_audit.js
  50. 8 2
      app/service/change_audit_list.js
  51. 112 0
      app/service/change_history.js
  52. 9 0
      app/service/ledger.js
  53. 3 2
      app/service/ledger_revise.js
  54. 5 2
      app/service/material.js
  55. 24 21
      app/service/material_audit.js
  56. 13 0
      app/service/material_bills.js
  57. 15 0
      app/service/material_exponent.js
  58. 15 1
      app/service/material_list.js
  59. 1 1
      app/service/payment_detail.js
  60. 9 0
      app/service/pos.js
  61. 31 2
      app/service/report.js
  62. 5 0
      app/service/schedule_month.js
  63. 5 0
      app/service/schedule_stage.js
  64. 22 1
      app/service/stage_audit.js
  65. 3 1
      app/service/stage_bills.js
  66. 8 7
      app/service/stage_change.js
  67. 9 3
      app/service/stage_change_final.js
  68. 17 15
      app/service/stage_pay.js
  69. 10 10
      app/service/stage_stash.js
  70. 148 0
      app/service/sub_proj_info.js
  71. 109 13
      app/service/sub_project.js
  72. 36 0
      app/service/tender_cache.js
  73. 1 0
      app/view/budget/list.ejs
  74. 1 0
      app/view/budget/sub_menu_list.ejs
  75. 21 4
      app/view/change/index.ejs
  76. 56 6
      app/view/change/information.ejs
  77. 66 6
      app/view/change/information_modal.ejs
  78. 1 0
      app/view/change/revise.ejs
  79. 1 0
      app/view/file/index.ejs
  80. 1 1
      app/view/layout/menu.ejs
  81. 1 0
      app/view/ledger/explode_modal.ejs
  82. 1 1
      app/view/measure/stage_modal.ejs
  83. 0 3
      app/view/report/index.ejs
  84. 97 19
      app/view/setting/fun.ejs
  85. 37 0
      app/view/setting/fun_modal.ejs
  86. 0 17
      app/view/setting/info.ejs
  87. 26 0
      app/view/shares/hint_modal.ejs
  88. 9 9
      app/view/stage/audit_modal.ejs
  89. 1 0
      app/view/stage/index.ejs
  90. 136 0
      app/view/sub_proj/data_index.ejs
  91. 1 0
      app/view/sub_proj/index.ejs
  92. 266 0
      app/view/sub_proj/info.ejs
  93. 14 0
      app/view/sub_proj/sp_info_menu.ejs
  94. 3 0
      app/view/sub_proj/sp_info_menu_list.ejs
  95. 16 0
      app/view/sub_proj/sp_info_mini_menu.ejs
  96. 1 1
      app/view/tender/detail.ejs
  97. 1 1
      app/view/tender/detail_modal.ejs
  98. 1 1
      app/view/tender/modal.ejs
  99. 22 0
      config/web.js
  100. 0 0
      publish.md

+ 7 - 1
app/base/base_controller.js

@@ -35,7 +35,13 @@ class BaseController extends Controller {
                 }
             }
         }
-        if (ctx.controllerName === 'sp') ctx.menu = menuList.file;
+        if (ctx.controllerName === 'sp') {
+            if (ctx.url.indexOf('file') > 0) {
+                ctx.menu = menuList.file;
+            } else {
+                ctx.menu = menuList.budget;
+            }
+        }
         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) {

+ 12 - 7
app/base/base_tree_service.js

@@ -526,14 +526,14 @@ class TreeService extends Service {
      * @return {Promise<*>}
      * @private
      */
-    async _deleteNodeData(mid, deleteNode) {
+    async _deletePosterity(mid, node) {
         this.initSqlBuilder();
         this.sqlBuilder.setAndWhere(this.setting.mid, {
             value: mid,
             operate: '=',
         });
         this.sqlBuilder.setAndWhere(this.setting.fullPath, {
-            value: this.db.escape(deleteNode[this.setting.fullPath] + '%'),
+            value: this.db.escape(node[this.setting.fullPath] + '-%'),
             operate: 'Like',
         });
         const [sql, sqlParam] = this.sqlBuilder.build(this.tableName, 'delete');
@@ -555,13 +555,15 @@ class TreeService extends Service {
         if (!select) throw '删除节点数据错误';
         const parent = await this.getDataByKid(mid, select[this.setting.pid]);
         // 获取将要被删除的数据
-        const deleteData = await this.getDataByFullPath(mid, select[this.setting.fullPath] + '%');
+        const deleteData = await this.getDataByFullPath(mid, select[this.setting.fullPath] + '-%');
+        deleteData.unshift(select);
         if (deleteData.length === 0) throw '删除节点数据错误';
 
         this.transaction = await this.db.beginTransaction();
         try {
             // 删除
-            const operate = await this._deleteNodeData(mid, select);
+            await this.transaction.delete(this.tableName, { id: select.id });
+            const operate = await this._deletePosterity(mid, select);
             // 选中节点--父节点 只有一个子节点时,应升级isLeaf
             if (parent) {
                 const count = await this.db.count(this.tableName, this.getCondition({mid: mid, pid: select[this.setting.pid]}));
@@ -601,14 +603,16 @@ class TreeService extends Service {
         const childCount = parent ? await this.count(this.getCondition({mid: mid, pid: parent[this.setting.kid]})) : -1;
         let deleteData = [];
         for (const s of selects) {
-            deleteData = deleteData.concat(await this.getDataByFullPath(mid, s[this.setting.fullPath] + '%'));
+            deleteData = deleteData.concat(await this.getDataByFullPath(mid, s[this.setting.fullPath] + '-%'));
+            deleteData.push(s);
         }
 
         this.transaction = await this.db.beginTransaction();
         try {
             // 删除
+            await this.transaction.delete(this.tableName, { id: selects.map(x => { return x.id }) });
             for (const s of selects) {
-                const operate = await this._deleteNodeData(mid, s);
+                const operate = await this._deletePosterity(mid, s);
             }
             // 选中节点--父节点 只有一个子节点时,应升级isLeaf
             if (parent && childCount === count) {
@@ -982,8 +986,9 @@ class TreeService extends Service {
         let updateData = await this.getNextsData(mid, pre[this.setting.pid], pre[this.setting.order] - 1);
         // 选中节点及子节点
         for (const p of newPath) {
-            updateData = updateData.concat(await this.getDataByFullPath(mid, p + '%'));
+            updateData = updateData.concat(await this.getDataByFullPath(mid, p + '-%'));
         }
+        updateData = updateData.concat(await this.getDataById(selects.map(x => { return x.id; })));
         // 选中节点--原前兄弟节点&全部后兄弟节点
         return { update: updateData };
     }

+ 3 - 0
app/const/audit.js

@@ -317,6 +317,7 @@ const auditStatus = {
     backnew: 6, // 退回到上一个审批人
     checkAgain: 7, // 重新审批
     revise: 9, // 修订变更
+    cancelRevise: 10, // 撤销修订
 };
 
 const auditStatusString = [];
@@ -328,6 +329,7 @@ auditStatusString[auditStatus.back] = '退回';
 auditStatusString[auditStatus.backnew] = '审批退回';
 auditStatusString[auditStatus.checkAgain] = '重新审批';
 auditStatusString[auditStatus.revise] = '修订变更';
+auditStatusString[auditStatus.cancelRevise] = '撤销修订';
 
 const auditStatusClass = [];
 auditStatusClass[auditStatus.uncheck] = '';
@@ -338,6 +340,7 @@ auditStatusClass[auditStatus.back] = 'text-warning';
 auditStatusClass[auditStatus.backnew] = 'text-warning';
 auditStatusClass[auditStatus.checkAgain] = 'text-warning';
 auditStatusClass[auditStatus.revise] = 'text-warning';
+auditStatusClass[auditStatus.cancelRevise] = 'text-success';
 
 /* ------------------------------------------------------- */
 

+ 20 - 0
app/const/fun_set.js

@@ -47,6 +47,25 @@ const changeClass = [
     },
 ];
 
+// 计量调用上限,变更令里使用
+const changeState = [
+    {
+        order: 1,
+        name: '临时变更令',
+        value: 100,
+    },
+    {
+        order: 2,
+        name: '立项变更令',
+        value: 100,
+    },
+    {
+        order: 3,
+        name: '正式变更令',
+        value: 100,
+    },
+];
+
 const defaultInfo = {
     // 合同信息
     stage_start: {
@@ -55,6 +74,7 @@ const defaultInfo = {
         end_day: 0,
     },
     change_class: changeClass,
+    change_state: changeState,
 };
 
 module.exports = {

+ 2 - 0
app/const/page_show.js

@@ -45,6 +45,8 @@ const defaultSetting = {
     openChangeProject: 0,
     openChangeApply: 0,
     openChangePlan: 0,
+    openChangeWhiteList: 0,
+    openChangeState: 0,
     isPreset: 0,
     isOnlyChecked: 1,
     openStageStart: 0,

+ 1 - 0
app/const/source_type.js

@@ -10,6 +10,7 @@ const sourceTypeData = [
     { id: 30, name: '材料调差', key: 'material' },
     { id: 100, name: '支付审批', key: 'payment' },
     { id: 101, name: '安全生产费', key: 'payment_safe' },
+    { id: 200, name: '动态投资', key: 'budget' },
 ];
 const sourceType = (function (data){
     const result = {};

+ 4 - 1
app/controller/budget_controller.js

@@ -38,6 +38,7 @@ module.exports = app => {
                     bl.gu_tp = await ctx.service.budgetGu.getSumTp(bl.budget_id);
                     bl.gai_tp = await ctx.service.budgetGai.getSumTp(bl.budget_id);
                     bl.yu_tp = await ctx.service.budgetYu.getSumTp(bl.budget_id);
+                    bl.zb_tp = await ctx.service.budgetZb.getSumTp(bl.budget_id);
                 }
                 renderData.tenderList = await ctx.service.tender.getList4Select('stage');
                 renderData.categoryData = await this.ctx.service.category.getAllCategory(this.ctx.session.sessionProject.id);
@@ -106,6 +107,7 @@ module.exports = app => {
                     data.gu = await ctx.service.budgetGu.getData(ctx.budget.id);
                     data.gai = await ctx.service.budgetGai.getData(ctx.budget.id);
                     data.yu = await ctx.service.budgetYu.getData(ctx.budget.id);
+                    data.zb = await ctx.service.budgetZb.getData(ctx.budget.id);
                 }
                 ctx.body = { err: 0, msg: '', data };
             } catch (err) {
@@ -167,13 +169,14 @@ module.exports = app => {
                 case 'gu': return this.ctx.service.budgetGu;
                 case 'gai': return this.ctx.service.budgetGai;
                 case 'yu': return this.ctx.service.budgetYu;
+                case 'zb': return this.ctx.service.budgetZb;
                 default: return null;
             }
         }
         async _getNeedGcl() {
             if (!this.ctx.params.btype) throw '参数错误';
             const funRela = await this.ctx.service.project.getFunRela(this.ctx.session.sessionProject.id);
-            return this.ctx.params.btype === 'yu' && !!funRela.needGcl;
+            return ['yu', 'zb'].indexOf(this.ctx.params.btype) >= 0 && !!funRela.needGcl;
         }
 
         async detail(ctx) {

+ 145 - 43
app/controller/change_controller.js

@@ -24,6 +24,7 @@ const shenpiConst = require('../const/shenpi');
 const tenderMenu = require('../../config/menu').tenderMenu;
 const measureType = require('../const/tender').measureType;
 const spreadConst = require('../const/spread');
+const stdConst = require('../const/standard');
 // const tenderMenu = require('../../config/menu').tenderMenu;
 
 module.exports = app => {
@@ -51,8 +52,9 @@ module.exports = app => {
             const pageSize = ctx.pageSize;
             const sorts = ctx.query.sort ? ctx.query.sort : 0;
             const orders = ctx.query.order ? ctx.query.order : 0;
-            const changes = await ctx.service.change.getListByStatus(tender.id, status, 1, sorts, orders);
-            const total = await ctx.service.change.getCountByStatus(tender.id, status);
+            const state = ctx.session.sessionProject.page_show.openChangeState && ctx.query.state ? parseInt(ctx.query.state) : 0;
+            const changes = await ctx.service.change.getListByStatus(tender.id, status, 1, sorts, orders, state);
+            const total = await ctx.service.change.getCountByStatus(tender.id, status, state);
             let page_total = 0;
             const tp = await ctx.service.change.getTp(tender.id, status);
             if (changes !== null) {
@@ -88,7 +90,7 @@ module.exports = app => {
                             auditStatus = checkingAudit.uid === ctx.session.sessionUser.accountId ? 1 : 0;
                             break;
                         case 9:
-                            auditStatus = 9;
+                            auditStatus = c.uid === ctx.session.sessionUser.accountId ? 9 : 0;
                             const changeUsedData = await ctx.service.stageChange.getFinalUsedData(ctx.tender.id, c.cid);
                             c.stageChangeNum = this.ctx.helper.sum(changeUsedData.map(x => { return Math.abs(x.qty); }));
                             break;
@@ -166,6 +168,7 @@ module.exports = app => {
                 dealCode: ctx.tender.info.deal_info.dealCode,
                 auditConst: audit.flow,
                 changeConst,
+                state,
                 ruleType: codeRuleConst.ruleType.change,
                 ruleConst: codeRuleConst.measure,
                 tenderMenu: this.menu.tenderMenu,
@@ -174,6 +177,17 @@ module.exports = app => {
                 changePlanList,
                 apLists,
             };
+
+            if (ctx.session.sessionProject.page_show.openChangeState) {
+                // 工程变更类别读取
+                const projectData = await ctx.service.project.getDataById(ctx.session.sessionProject.id);
+                const fun_set = await ctx.service.project.getFunSet(projectData.fun_set);
+                const changeState = fun_set.change_state;
+                for (const cs of changeState) {
+                    cs.count = await ctx.service.change.getCountByStatus(tender.id, status, cs.order);
+                }
+                renderData.changeState = changeState;
+            }
             await this.layout('change/index.ejs', renderData, 'change/modal.ejs');
         }
 
@@ -277,8 +291,10 @@ module.exports = app => {
                 if (!data.code || data.code === '') {
                     throw '变更令号不能为空';
                 }
-
-                const change = await ctx.service.change.add(tenderId, ctx.session.sessionUser.accountId, data.code, data.plan_code, data.name);
+                const projectData = await ctx.service.project.getDataById(ctx.session.sessionProject.id);
+                const fun_set = await ctx.service.project.getFunSet(projectData.fun_set);
+                const stateInfo = ctx.helper._.find(fun_set.change_state, { order: 3 });
+                const change = await ctx.service.change.add(tenderId, ctx.session.sessionUser.accountId, data.code, data.plan_code, data.name, stateInfo.value);
 
                 ctx.body = { err: 0, msg: '', data: change };
             } catch (err) {
@@ -617,12 +633,14 @@ module.exports = app => {
                 // 获取用户人验证手机号
                 const pa = await ctx.service.projectAccount.getDataById(ctx.session.sessionUser.accountId);
                 const auth_mobile = pa.auth_mobile;
+                const unitList = await ctx.service.constructionUnit.getAllDataByCondition({ where: { pid: ctx.session.sessionProject.id } });
                 const renderData = {
                     tender,
                     change,
                     othersChange,
                     changeConst,
                     changeClass: fun_set.change_class,
+                    changeState: fun_set.change_state,
                     auditStatus,
                     auditConst: audit.flow,
                     ledgerConsts: audit.ledger.status,
@@ -635,10 +653,34 @@ module.exports = app => {
                     upUnit: change.up_decimal ? change.up_decimal : ctx.tender.info.decimal.up,
                     authMobile: auth_mobile,
                     shenpiConst,
+                    unitList,
                     jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.change.information),
                     preUrl: '/tender/' + ctx.tender.id + '/change/' + ctx.change.cid + '/information',
                     precision: ctx.tender.info.precision,
                 };
+                // 获取是否已存在调用变更令
+                let changeUsedData = await ctx.service.stageChange.getFinalUsedData(ctx.tender.id, change.cid);
+                changeUsedData = ctx.helper._.orderBy(ctx.helper._.filter(changeUsedData, function(item) {
+                    return item.qty !== null;
+                }), ['sorder'], ['desc']);
+                const useChangeUsedData = [];
+                if (changeUsedData.length > 0) { // 防止未创建期时调用
+                    // const stage = await this.ctx.service.stage.getLastestStage(ctx.tender.id, true);
+                    for (const cu of changeUsedData) {
+                        // if (cu.sorder !== 0 ||
+                        //     (cu.sorder === 0 && !(stage.status === audit.stage.status.uncheck || stage.status === audit.stage.status.checkNo))) {
+                        const index = ctx.helper._.findIndex(useChangeUsedData, { cbid: cu.cbid });
+                        if (index !== -1) {
+                            useChangeUsedData[index].qty = ctx.helper.add(useChangeUsedData[index].qty, cu.qty);
+                        } else {
+                            useChangeUsedData.push(cu);
+                        }
+                        // }
+                    }
+                }
+                renderData.changeUsedData = useChangeUsedData;
+                renderData.stageChangeNum = this.ctx.helper.sum(changeUsedData.map(x => { return Math.abs(x.qty); }));
+
                 // 根据auditStatus状态获取的不同的数据
                 if (auditStatus === 1 || auditStatus === 2 || auditStatus === 9) {
                     renderData.changeUnits = changeConst.units;
@@ -649,7 +691,6 @@ module.exports = app => {
                         columns: ['id', 'name', 'company', 'role', 'enable', 'is_admin', 'account_group'],
                     });
                     renderData.accountList = accountList;
-                    const unitList = await ctx.service.constructionUnit.getAllDataByCondition({ where: { pid: ctx.session.sessionProject.id } });
                     renderData.accountGroup = unitList.map(item => {
                         const groupList = accountList.filter(item1 => item1.company === item.name);
                         return { groupName: item.name, groupList };
@@ -683,9 +724,36 @@ module.exports = app => {
                     renderData.companyList = companyList;
                     // 获取已选清单
                     const changeList = await ctx.service.changeAuditList.getList(change.cid);
-                    renderData.changeList = changeList;
                     renderData.changeLedgerList = await ctx.service.changeLedger.getAllDataByCondition({ where: { tender_id: ctx.tender.id } });
                     renderData.changePosList = await ctx.service.changePos.getAllDataByCondition({ where: { tid: ctx.tender.id } });
+                    const stateInfo = ctx.helper._.find(fun_set.change_state, { order: ctx.change.state });
+                    renderData.deLimit = stateInfo.value;
+                    if (stateInfo.value !== ctx.change.delimit) {
+                        // 需要更新所有的计量上限值,除了已调用的值
+                        const updateList = [];
+                        for (const cl of changeList) {
+                            const one = {
+                                id: cl.id,
+                            };
+                            if (useChangeUsedData.length > 0 && ctx.helper._.findIndex(useChangeUsedData, { cbid: cl.id }) !== -1) {
+                                // 获取比例值
+                                const uc = ctx.helper._.find(useChangeUsedData, { cbid: cl.id });
+                                const minLimit = Math.ceil(ctx.helper.mul(ctx.helper.div(uc.qty, cl.camount), 100));
+                                if (minLimit <= renderData.deLimit) {
+                                    one.delimit = renderData.deLimit;
+                                    cl.delimit = renderData.deLimit;
+                                }
+                            } else if (cl.delimit !== renderData.deLimit) {
+                                one.delimit = renderData.deLimit;
+                                cl.delimit = renderData.deLimit;
+                            }
+                            if (!ctx.helper._.isEqual(one, { id: cl.id })) updateList.push(one);
+                        }
+                        console.log(updateList);
+                        if (updateList.length > 0) await ctx.service.changeAuditList.defaultUpdateRows(updateList);
+                        await ctx.service.change.defaultUpdate({ delimit: stateInfo.value }, { where: { cid: change.cid } });
+                    }
+                    renderData.changeList = changeList;
 
                     // 判断是否更新变更类别
                     if (ctx.helper._.findIndex(fun_set.change_class, { value: change.class, checked: true }) === -1) {
@@ -728,10 +796,12 @@ module.exports = app => {
                         // 清单表页赋值
                         for (const [index, au] of auditList2.entries()) {
                             if (au.usite !== 0) {
-                                cl['audit_amount_' + au.uid] = audit_amount[index - 1] ? audit_amount[index - 1] : null;
+                                cl['audit_amount_' + au.uid] = audit_amount[index - 1] !== undefined ? audit_amount[index - 1] : null;
                             }
                         }
-                        cl.changed_amount = (change.status === audit.flow.status.backnew || change.status === audit.flow.status.checking || change.status === audit.flow.status.checked) && audit_amount !== '' ? audit_amount[audit_amount.length - 1] : cl.camount;
+                        cl.changed_amount = ctx.helper._.indexOf([audit.flow.status.backnew, audit.flow.status.checking, audit.flow.status.checked], change.status) !== -1 ?
+                            (audit_amount !== '' ? audit_amount[audit_amount.length - 1] :
+                                (ctx.helper._.indexOf([audit.flow.status.backnew, audit.flow.status.checking], change.status) !== -1 ? cl.camount : 0)) : cl.camount;
                         // cl.changed_amount = ctx.helper.add(cl.oamount ? parseFloat(cl.oamount) : 0, changed_amount ? parseFloat(changed_amount) : 0);
                     }
                     renderData.changeList = changeList;
@@ -779,10 +849,12 @@ module.exports = app => {
                         // 清单表页赋值
                         for (const [index, au] of auditList2.entries()) {
                             if (au.usite !== 0) {
-                                cl['audit_amount_' + au.uid] = au.uid === ctx.session.sessionUser.accountId ? cl.spamount : (audit_amount[index - 1] ? audit_amount[index - 1] : null);
+                                cl['audit_amount_' + au.uid] = au.uid === ctx.session.sessionUser.accountId ? cl.spamount : (audit_amount[index - 1] !== undefined ? audit_amount[index - 1] : null);
                             }
                         }
-                        cl.changed_amount = (change.status === audit.flow.status.backnew || change.status === audit.flow.status.checking || change.status === audit.flow.status.checked) && audit_amount !== '' ? audit_amount[audit_amount.length - 1] : cl.camount;
+                        cl.changed_amount = ctx.helper._.indexOf([audit.flow.status.backnew, audit.flow.status.checking, audit.flow.status.checked], change.status) !== -1 ?
+                            (audit_amount !== '' ? audit_amount[audit_amount.length - 1] :
+                                (ctx.helper._.indexOf([audit.flow.status.backnew, audit.flow.status.checking], change.status) !== -1 ? cl.camount : 0)) : cl.camount;
                         // cl.changed_amount = ctx.helper.add(cl.oamount ? parseFloat(cl.oamount) : 0, changed_amount ? parseFloat(changed_amount) : 0);
                     }
                     renderData.changeList = changeList;
@@ -790,29 +862,6 @@ module.exports = app => {
                     renderData.changePosList = await ctx.service.changePos.getAllDataByCondition({ where: { tid: ctx.tender.id } });
                 }
                 renderData.auditList = auditList;
-
-                // 获取是否已存在调用变更令
-                let changeUsedData = await ctx.service.stageChange.getFinalUsedData(ctx.tender.id, change.cid);
-                changeUsedData = ctx.helper._.orderBy(ctx.helper._.filter(changeUsedData, function(item) {
-                    return item.qty !== null;
-                }), ['sorder'], ['desc']);
-                const useChangeUsedData = [];
-                if (changeUsedData.length > 0) { // 防止未创建期时调用
-                    // const stage = await this.ctx.service.stage.getLastestStage(ctx.tender.id, true);
-                    for (const cu of changeUsedData) {
-                        // if (cu.sorder !== 0 ||
-                        //     (cu.sorder === 0 && !(stage.status === audit.stage.status.uncheck || stage.status === audit.stage.status.checkNo))) {
-                        const index = ctx.helper._.findIndex(useChangeUsedData, { cbid: cu.cbid });
-                        if (index !== -1) {
-                            useChangeUsedData[index].qty = ctx.helper.add(useChangeUsedData[index].qty, cu.qty);
-                        } else {
-                            useChangeUsedData.push(cu);
-                        }
-                        // }
-                    }
-                }
-                renderData.changeUsedData = useChangeUsedData;
-                renderData.stageChangeNum = this.ctx.helper.sum(changeUsedData.map(x => { return Math.abs(x.qty); }));
                 await this.layout('change/information.ejs', renderData, 'change/information_modal.ejs');
             } catch (err) {
                 this.log(err);
@@ -835,10 +884,18 @@ module.exports = app => {
                 };
                 switch (data.type) {
                     case 'add':
-                        responseData.data = await ctx.service.changeAuditList.add(data.postData);
-                        break;
                     case 'batchadd':
-                        responseData.data = await ctx.service.changeAuditList.batchAdd(data);
+                        if (!ctx.session.sessionProject.page_show.openChangeWhiteList) {
+                            throw '空白清单添加功能未开启。';
+                        }
+                        const projectData = await ctx.service.project.getDataById(ctx.session.sessionProject.id);
+                        const fun_set = await ctx.service.project.getFunSet(projectData.fun_set);
+                        const stateInfo = ctx.helper._.find(fun_set.change_state, { order: ctx.change.state });
+                        if (data.type === 'add') {
+                            responseData.data = await ctx.service.changeAuditList.add(data.postData, stateInfo.value);
+                        } else {
+                            responseData.data = await ctx.service.changeAuditList.batchAdd(data, stateInfo.value);
+                        }
                         break;
                     case 'del':
                         await ctx.service.changeAuditList.del(data);
@@ -868,9 +925,16 @@ module.exports = app => {
                         // responseData.data = await ctx.service.changeAuditList.getList(ctx.change.cid);
                         break;
                     case 'info':
-                        await ctx.service.change.saveInfo(data.updateData);
+                        let value = 100;
+                        if (data.updateData.state && parseInt(data.updateData.state) !== ctx.change.state) {
+                            const projectData = await ctx.service.project.getDataById(ctx.session.sessionProject.id);
+                            const fun_set = await ctx.service.project.getFunSet(projectData.fun_set);
+                            const stateInfo = ctx.helper._.find(fun_set.change_state, { order: parseInt(data.updateData.state) });
+                            value = stateInfo.value;
+                        }
+                        responseData.data = await ctx.service.change.saveInfo(data.updateData, value);
                         // 取所有工料表
-                        responseData.data = '保存成功';
+                        // responseData.data = '保存成功';
                         break;
                     case 'paste_amount_rows':
                         await ctx.service.changeAuditList.saveDatas(data.updateData);
@@ -934,11 +998,11 @@ module.exports = app => {
                     // 判断上一次审批是否为非原报为审批人
                     const nowUidList = ctx.helper._.map(ctx.change.auditors, 'uid');
                     // 判断条件修订可上报条件
-                    // 1.有原报,有其他人   可以添加原报作为审核人,但是不能只原报上报
-                    // 2.有原报,无其他人   可以添加原报作为审核人,可以只原报上报
-                    // 3.无原报,有其他人   不可以添加原报
+                    // 1.有原报,不管有无其他人   可以添加原报作为审核人,但是不能只原报上报
+                    // 2.无原报,有其他人   不可以添加原报
                     const noYBUidList = ctx.change.status === audit.flow.status.revise ? ctx.helper._.initial(ctx.helper._.tail(lastUidList)) : ctx.helper._.tail(lastUidList);
-                    if (!ctx.helper._.isEqual(lastUidList, nowUidList) && ctx.helper._.includes(noYBUidList, lastUidList[0]) && nowUidList.length === 2 && nowUidList[0] === nowUidList[1]) {
+                    // if (!ctx.helper._.isEqual(lastUidList, nowUidList) && ctx.helper._.includes(noYBUidList, lastUidList[0]) && nowUidList.length === 2 && nowUidList[0] === nowUidList[1]) {
+                    if (nowUidList.length === 2 && nowUidList[0] === nowUidList[1]) {
                         throw '该变更令不能指定原报人为单独审批人';
                     }
                     if (!ctx.helper._.isEqual(lastUidList, nowUidList) && !ctx.helper._.includes(noYBUidList, lastUidList[0]) && ctx.helper._.includes(ctx.helper._.tail(nowUidList), nowUidList[0])) {
@@ -1539,6 +1603,39 @@ module.exports = app => {
         }
 
         /**
+         * 变更令撤销修订
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        async cancelRevise(ctx) {
+            try {
+                const changeData = await ctx.service.change.getDataByCondition({ cid: ctx.request.body.cid });
+                if (!changeData) {
+                    throw '变更令数据错误';
+                }
+                if (!(changeData.status === audit.flow.status.revise && (ctx.session.sessionUser.accountId === changeData.uid || ctx.session.sessionUser.accountId === ctx.session.sessionUser.is_admin))) {
+                    throw '您无权进行该操作';
+                }
+                // 重新审批
+                const result = await ctx.service.change.cancelRevise(changeData.cid, changeData.times);
+                if (!result) {
+                    throw '撤销修订失败';
+                }
+                ctx.body = {
+                    err: 0,
+                    url: ctx.request.header.referer,
+                    msg: '',
+                };
+            } catch (err) {
+                console.log(err);
+                ctx.body = {
+                    err: 1,
+                    msg: err,
+                };
+            }
+        }
+
+        /**
          * 获取变更清单
          * @param ctx
          * @return {Promise<void>}
@@ -1667,6 +1764,7 @@ module.exports = app => {
                 auditConst: audit.flow,
                 audit: audit.flow,
                 stdChapters,
+                nodeType: stdConst.nodeType,
             };
         }
 
@@ -3112,6 +3210,10 @@ module.exports = app => {
                         // 取所有工料表
                         responseData.data = await ctx.service.changeApplyList.getList(ctx.change.id);
                         break;
+                    case 'paste_amount_rows':
+                        await ctx.service.changeApplyList.saveDatas(data.updateData);
+                        responseData.data = await ctx.service.changeApplyList.getList(ctx.change.id);
+                        break;
                     case 'list_rule':
                         const result = await ctx.service.tender.saveTenderData(ctx.tender.id, { c_apply_list_rule: data.postData });
                         if (!result) {

+ 30 - 0
app/controller/login_controller.js

@@ -590,6 +590,36 @@ module.exports = app => {
           ctx.body = response
         }
 
+        /** (项目管理) 验证账号密码 */
+        async syncValidAccount(ctx) {
+          const response = {
+            code: 0,
+            data: {},
+            msg: '',
+          };
+          try {
+            if (!ctx.data) {
+              throw '参数有误';
+            }
+            const { code, account, password} = ctx.data;
+            if (!code || !account || !password) {
+              throw '参数有误';
+            }
+            const projectData = await ctx.service.project.getProjectByCode(code)
+            if (!projectData) throw '未找到项目';
+            const result = await ctx.service.projectAccount.accountLogin({ project: code, project_password: password, account  }, 2);
+            if (!result) {
+              throw '用户名或密码错误';
+            }
+            if (result === 2) {
+                throw '该账号已被停用,请联系销售人员';
+            }
+          } catch (error) {
+            response.code = -1;
+            response.msg = error.toString();
+          }
+          ctx.body = response;
+        }
     }
 
     return LoginController;

+ 4 - 2
app/controller/setting_controller.js

@@ -885,7 +885,7 @@ module.exports = app => {
                 ctx.body = { err: 0, msg: '', data: result };
             } catch (err) {
                 this.log(err);
-                this.ajaxErrorBody(error, '保存数据失败');
+                this.ajaxErrorBody(err, '保存数据失败');
             }
         }
 
@@ -970,6 +970,8 @@ module.exports = app => {
                 this.ctx.session.sessionProject.page_show.openChangeProject = data.openChangeProject ? 1 : 0;
                 this.ctx.session.sessionProject.page_show.openChangeApply = data.openChangeApply ? 1 : 0;
                 this.ctx.session.sessionProject.page_show.openChangePlan = data.openChangePlan ? 1 : 0;
+                this.ctx.session.sessionProject.page_show.openChangeWhiteList = data.openChangeWhiteList ? 1 : 0;
+                this.ctx.session.sessionProject.page_show.openChangeState = data.openChangeState ? 1 : 0;
                 this.ctx.session.sessionProject.page_show.openMaterialTax = data.openMaterialTax ? 1 : 0;
                 this.ctx.session.sessionProject.page_show.openMaterialChecklist = data.openMaterialChecklist ? 1 : 0;
                 this.ctx.session.sessionProject.page_show.openMaterialSelf = data.openMaterialSelf ? 1 : 0;
@@ -1011,7 +1013,7 @@ module.exports = app => {
                 await this.layout('setting/s2b.ejs', {
                     projectData,
                     tenders,
-                })
+                });
             } catch (error) {
                 ctx.helper.log(error);
                 ctx.redirect('/dashboard');

+ 14 - 7
app/controller/stage_controller.js

@@ -224,6 +224,7 @@ module.exports = app => {
             this.ledgerExtraColumn = ['is_tp'];
             if (this.ctx.session.sessionProject.gxby) this.ledgerExtraColumn.push('gxby_status', 'gxby_url', 'gxby_limit');
             if (this.ctx.session.sessionProject.dagl) this.ledgerExtraColumn.push('dagl_status', 'dagl_url', 'dagl_limit');
+            this.ledgerMemoColumn = ['memo'];
 
 
             this.posColumn = ['id', 'tid', 'lid', 'name', 'position', 'porder', 'quantity', 'add_stage_order', 'drawing_code'];
@@ -232,13 +233,16 @@ module.exports = app => {
             this.posExtraColumn = [];
             if (this.ctx.session.sessionProject.gxby) this.posExtraColumn.push('gxby_status', 'gxby_url', 'gxby_limit');
             if (this.ctx.session.sessionProject.dagl) this.posExtraColumn.push('dagl_status', 'dagl_url', 'dagl_limit');
+            this.posMemoColumn = [];
 
             if (!sjsRela) return;
             if (sjsRela) {
                 for (const field of sjsRela.ledgerCol) {
                     if (field.show) {
+                        this.ledgerMemoColumn.push(field.field);
                         this.ledgerColumn.push(field.field);
                         this.posColumn.push(field.field);
+                        this.posMemoColumn.push(field.field);
                     }
                 }
             }
@@ -248,7 +252,7 @@ module.exports = app => {
             const ledgerData = ctx.stage.ledgerHis
                 ? await ctx.helper.loadLedgerDataFromOss(ctx.stage.ledgerHis.bills_file)
                 : await ctx.service.ledger.getAllDataByCondition({ columns: this.ledgerColumn, where: { tender_id: ctx.tender.id } });
-            const memoData = ctx.stage.ledgerHis ? await ctx.service.ledger.getAllDataByCondition({ columns: ['id', 'memo'], where: { tender_id: ctx.tender.id } }) : [];
+            const memoData = ctx.stage.ledgerHis ? await ctx.service.ledger.getMemoData(ctx.tender.id, this.ledgerMemoColumn) : [];
             const dgnData = await ctx.service.stageBillsDgn.getDgnData(ctx.tender.id);
             const extraData = await ctx.service.ledgerExtra.getData(ctx.tender.id, this.ledgerExtraColumn);
             const pcData = await ctx.service.stageBillsPc.getAllDataByCondition({ where: { sid: ctx.stage.id } });
@@ -268,7 +272,7 @@ module.exports = app => {
             const preStageData = ctx.stage.order > 1 ? await ctx.service.stageBillsFinal.getFinalData(ctx.tender.data, ctx.stage.order - 1) : [];
             this.ctx.helper.assignRelaData(ledgerData, [
                 { data: dgnData, fields: ['deal_dgn_qty1', 'deal_dgn_qty2', 'c_dgn_qty1', 'c_dgn_qty2'], prefix: '', relaId: 'id' },
-                { data: memoData, fields: ['memo'], prefix: '', relaId: 'id' },
+                { data: memoData, fields: this.ledgerMemoColumn, prefix: '', relaId: 'id' },
                 { data: extraData, fields: this.ledgerExtraColumn, prefix: '', relaId: 'id' },
                 { data: importData, fields: ['is_import'], prefix: '', relaId: 'lid' },
                 { data: curStageData, fields: ['contract_qty', 'contract_expr', 'contract_tp', 'qc_qty', 'qc_tp', 'qc_minus_qty', 'postil'], prefix: '', relaId: 'lid' },
@@ -282,6 +286,7 @@ module.exports = app => {
             const posData = ctx.stage.ledgerHis
                 ? await ctx.helper.loadLedgerDataFromOss(ctx.stage.ledgerHis.pos_file)
                 : await ctx.service.pos.getAllDataByCondition({ columns: this.posColumn, where: { tid: ctx.tender.id } });
+            const memoData = ctx.stage.ledgerHis ? await ctx.service.pos.getMemoData(ctx.tender.id, this.posMemoColumn) : [];
             const extraData = await ctx.service.posExtra.getData(ctx.tender.id, this.posExtraColumn);
             // 根据当前人,或指定对象查询数据
             if (ctx.stage.readOnly) {
@@ -298,6 +303,7 @@ module.exports = app => {
             // 查询截止上期数据
             const preStageData = ctx.stage.order > 1 ? await ctx.service.stagePosFinal.getFinalData(ctx.tender.data, ctx.stage.order - 1) : [];
             this.ctx.helper.assignRelaData(posData, [
+                { data: memoData, fields: this.posMemoColumn, prefix: '', relaId: 'id'},
                 { data: extraData, fields: this.posExtraColumn, prefix: '', relaId: 'id'},
                 { data: curStageData, fields: ['contract_qty', 'contract_expr', 'qc_qty', 'qc_minus_qty', 'postil'], prefix: '', relaId: 'pid' },
                 { data: preStageData, fields: ['contract_qty', 'qc_qty', 'qc_minus_qty'], prefix: 'pre_', relaId: 'pid' },
@@ -552,7 +558,8 @@ module.exports = app => {
                 const bills = data.bills ? data.bills : await ctx.service.ledger.getDataById(data.pos.lid);
                 const pos = data.pos;
                 const projectFunInfo = await this.ctx.service.project.getFunRela(ctx.session.sessionProject.id);
-                const changes = await ctx.service.change.getValidChanges(ctx.tender, ctx.stage, data, projectFunInfo.minusNoValue && ctx.tender.info.fun_rela.stage_change.minusNoValue);
+                // const changes = await ctx.service.change.getValidChanges(ctx.tender, ctx.stage, data, projectFunInfo.minusNoValue && ctx.tender.info.fun_rela.stage_change.minusNoValue);
+                const changes = await ctx.service.change.getValidChanges(ctx.tender, ctx.stage, data, true);
                 const useChanges = ctx.stage.readOnly
                     ? await ctx.service.stageChange.getAuditorStageData(ctx.tender.id, ctx.stage.id, ctx.stage.curTimes, ctx.stage.curOrder, bills.id, pos ? pos.id : -1)
                     : await ctx.service.stageChange.getLastestStageData(ctx.tender.id, ctx.stage.id, bills.id, pos ? pos.id : -1);
@@ -577,12 +584,12 @@ module.exports = app => {
                 }
                 let result;
                 if (data.target.pos) {
-                    result = await ctx.service.stageChange.posChange(data.target.pos, data.target.minus, data.change);
+                    result = await ctx.service.stageChange.posChange(data.target.pos, data.target.noValue, data.change);
                     result.change = { target: { lid: data.target.pos.lid, pid: data.target.pos.id } };
                     result.change.data = await ctx.service.stageChange.getLastestStageData(ctx.tender.id,
                         ctx.stage.id, data.target.pos.lid, data.target.pos.id);
                 } else {
-                    result = await ctx.service.stageChange.billsChange(data.target.bills, data.target.minus, data.change);
+                    result = await ctx.service.stageChange.billsChange(data.target.bills, data.target.noValue, data.change);
                     result.change = { target: { lid: data.target.bills.id, pid: '-1' } };
                     result.change.data = await ctx.service.stageChange.getLastestStageData(ctx.tender.id,
                         ctx.stage.id, data.target.bills.id, '-1');
@@ -1405,7 +1412,7 @@ module.exports = app => {
                 const data = JSON.parse(ctx.request.body.data);
                 await ctx.service.stageAudit.saveAudit(ctx.stage.id, ctx.stage.times, data);
 
-                const auditors = await ctx.service.stageAudit.getUserGroup(ctx.stage.id, ctx.stage.times);
+                const auditors = await ctx.service.stageAudit.getUniqUserGroup(ctx.stage.id, ctx.stage.times);
                 ctx.body = { err: 0, msg: '', data: auditors };
             } catch (err) {
                 this.log(err);
@@ -2023,7 +2030,7 @@ module.exports = app => {
                     auditList = await ctx.service.stageAudit.getAuditorHistory(lastStage.id, times, true);
                 }
                 renderData.lastStage = lastStage;
-                renderData.lastAuditList = auditList;
+                renderData.lastAuditList = auditList.reverse();
                 renderData.jsFiles = this.app.jsFiles.common.concat(this.app.jsFiles.stage.manager);
                 await this.layout('stage/manager.ejs', renderData, 'stage/manager_modal.ejs');
             } catch (err) {

+ 49 - 1
app/controller/sub_proj_controller.js

@@ -82,6 +82,18 @@ module.exports = app => {
             }
         }
 
+        async move(ctx) {
+            try {
+                const data = JSON.parse(ctx.request.body.data);
+                if (!data.id || !data.type) throw '提交数据错误';
+                const result = await ctx.service.subProject.move(data);
+                ctx.body = { err: 0, msg: '', data: result };
+            } catch (err) {
+                ctx.log(err);
+                ctx.ajaxErrorBody(err, '调整所属文件夹失败');
+            }
+        }
+
         async del(ctx) {
             try {
                 const data = JSON.parse(ctx.request.body.data);
@@ -170,7 +182,43 @@ module.exports = app => {
                 await ctx.service.subProjPermission.savePermission(data.id, data.member);
                 ctx.body = { err: 0, msg: '', data: '' };
             } catch (err) {
-                console.log(err);
+                ctx.log(err);
+                ctx.ajaxErrorBody(err, '保存数据失败');
+            }
+        }
+
+        async info(ctx) {
+            try {
+                const info = await this.ctx.service.subProjInfo.getInfo(ctx.subProject.id);
+                const renderData = {
+                    info,
+                    jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.subProject.info),
+                };
+                await this.layout('sub_proj/info.ejs', renderData);
+            } catch (err) {
+                ctx.log(err);
+            }
+        }
+
+        async dataIndex(ctx) {
+            try {
+                const info = await this.ctx.service.subProjInfo.getInfo(ctx.subProject.id);
+                const renderData = {
+                    info,
+                    jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.subProject.data),
+                };
+                await this.layout('sub_proj/data_index.ejs', renderData);
+            } catch (err) {
+                ctx.log(err);
+            }
+        }
+
+        async saveInfo(ctx) {
+            try {
+                const data = JSON.parse(ctx.request.body.data);
+                const result = await ctx.service.subProjInfo.saveInfo(ctx.subProject.id, data);
+                ctx.body = { err: 0, msg: '', data: result };
+            } catch(err) {
                 ctx.log(err);
                 ctx.ajaxErrorBody(err, '保存数据失败');
             }

+ 20 - 0
app/lib/budget_final.js

@@ -133,6 +133,20 @@ class BudgetFinal {
         });
     }
 
+    async _loadZb(budget) {
+        const helper = this.ctx.helper;
+        const zb = await this.ctx.service.budgetYu.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();
+        this.finalTree.loadTree(zbTree, function (cur, source) {
+            cur.base = true;
+            cur.zb_dgn_qty1 = helper.add(cur.zb_dgn_qty1, source.dgn_qty1);
+            cur.zb_dgn_qty2 = helper.add(cur.zb_dgn_qty2, source.dgn_qty2);
+            cur.zb_tp = helper.add(cur.zb_tp, source.total_price);
+        });
+    }
+
     async _loadTender(id) {
         const helper = this.ctx.helper;
         const bills = await this.ctx.service.ledger.getFinalData(id, ['id', 'ledger_id', 'ledger_pid', 'level', 'order', 'full_path', 'is_leaf',
@@ -215,6 +229,10 @@ class BudgetFinal {
             node.yu_dgn_qty = node.yu_dgn_qty1
                 ? (node.yu_dgn_qty2 ? node.yu_dgn_qty1 + '/' + node.yu_dgn_qty2 : node.yu_dgn_qty1 + '')
                 : (node.yu_dgn_qty2 ? '/' + node.yu_dgn_qty2 : '');
+            node.zb_dgn_price = helper.div(node.zb_tp, node.zb_dgn_qty1, 2);
+            node.zb_dgn_qty = node.zb_dgn_qty1
+                ? (node.zb_dgn_qty2 ? node.zb_dgn_qty1 + '/' + node.zb_dgn_qty2 : node.zb_dgn_qty1 + '')
+                : (node.zb_dgn_qty2 ? '/' + node.zb_dgn_qty2 : '');
 
             node.final_dgn_price = helper.div(node.final_tp, node.final_dgn_qty1, 2);
             node.final_dgn_qty = node.final_dgn_qty1
@@ -246,6 +264,7 @@ class BudgetFinal {
                 gu_dgn_qty1: x.gu_dgn_qty1 || 0, gu_dgn_qty2: x.gu_dgn_qty2 || 0, gu_dgn_qty: x.gu_dgn_qty, gu_dgn_price: x.gu_dgn_price || 0, gu_tp: x.gu_tp || 0,
                 gai_dgn_qty1: x.gai_dgn_qty1 || 0, gai_dgn_qty2: x.gai_dgn_qty2 || 0, gai_dgn_qty: x.gai_dgn_qty, gai_dgn_price: x.gai_dgn_price || 0, gai_tp: x.gai_tp || 0,
                 yu_dgn_qty1: x.yu_dgn_qty1 || 0, yu_dgn_qty2: x.yu_dgn_qty2 || 0, yu_dgn_qty: x.yu_dgn_qty, yu_dgn_price: x.yu_dgn_price || 0, yu_tp: x.yu_tp || 0,
+                zb_dgn_qty1: x.zb_dgn_qty1 || 0, zb_dgn_qty2: x.zb_dgn_qty2 || 0, zb_dgn_qty: x.zb_dgn_qty, zb_dgn_price: x.zb_dgn_price || 0, zb_tp: x.zb_tp || 0,
 
                 dgn_qty1: x.dgn_qty1 || 0, dgn_qty2: x.dgn_qty2 || 0, total_price: x.total_price || 0,
                 final_dgn_qty1: x.final_dgn_qty1 || 0, final_dgn_qty2: x.final_dgn_qty2 || 0, final_tp: x.final_tp || 0,
@@ -266,6 +285,7 @@ class BudgetFinal {
         await this._loadGai(budget);
         await this._loadGu(budget);
         await this._loadYu(budget);
+        await this._loadZb(budget);
         for (const t of final.tender) {
             await this._loadTender(t);
         }

+ 1 - 3
app/lib/pay_calc.js

@@ -54,17 +54,14 @@ class PayCalculate {
 
     _calculateTpExpr(pay, pays) {
         let formula = pay.expr;
-        const bPrint = (pay.expr === 'f18+f12');
         const orderParam = pay.expr.match(this.orderReg);
         if (orderParam) {
             for (const op of orderParam) {
                 const order = parseInt(op.substring(1, op.length));
                 const orderPay = pays.find(x => { return x.order === order });
-                if (bPrint) console.log(order, orderPay);
                 formula = formula.replace(op, orderPay && orderPay.tp || 0);
             }
         }
-        if (bPrint) console.log(formula);
         for (const b of this.bases) {
             if ((b.code === 'bqwc' || b.code === 'bqht') && (!pay.pre_used && pay.sprice)) {
                 switch (b.code) {
@@ -74,6 +71,7 @@ class PayCalculate {
                     case 'bqht':
                         formula = formula.replace(b.reg, this.ctx.helper.sub(this.add.contract_tp, pay.sprice));
                         break;
+                    default: throw '未知参数';
                 }
             } else {
                 formula = formula.replace(b.reg, b.value);

+ 73 - 38
app/lib/rm/budget.js

@@ -8,13 +8,36 @@
  * @version
  */
 
+const RptMemBase = require('./base');
+const bindData = {};
 const ledger = require('../ledger');
 
-class reportMemoryBudget {
+class rptMemPaymentSafe extends RptMemBase {
     constructor(ctx) {
-        this.ctx = ctx;
-        this.budget = null;
-        this.getBudget = false;
+        super(ctx, bindData);
+    }
+
+    async doCheckSubProj(id) {
+        if (this.ctx.subProject) return;
+        this.ctx.subProject = await this.ctx.service.subProject.getDataById(id);
+        this.ctx.subProject.info = await this.ctx.service.subProjInfo.getInfo4Report(ctx.subProject.id);
+        if (!this.ctx.budget) {
+            this.ctx.budget = await this.ctx.service.budget.getCurBudget(this.ctx.subProject.budget_id);
+        }
+    }
+
+    async doCheckBudget(id) {
+        if (this.ctx.budget) return;
+        this.ctx.budget = await this.ctx.service.budget.getCurBudget(id);
+        if (!this.ctx.subProject) {
+            this.ctx.subProject = await this.ctx.service.subProject.getDataByCondition({ budget_id: id });
+            this.ctx.subProject.info = await this.ctx.service.subProjInfo.getInfo4Report(ctx.subProject.id);
+        }
+    }
+
+    async doBeforeLoadReport(params) {
+        await this.doCheckSubProj(params.sp_id);
+        await this.doCheckBudget(params.budget_id);
     }
 
     async budgetGai(bid, showLevel = false) {
@@ -22,21 +45,32 @@ class reportMemoryBudget {
         const tree = new ledger.billsTree(this.ctx, { id: 'tree_id', pid: 'tree_pid', order: 'order', level: 'level', rootId: -1, calcFields: ['total_price'] });
         tree.loadDatas(gai);
         tree.calculateAll();
-        return showLevel ? tree.getDefaultDatasByLevel(this.ctx.tender.rpt_show_level) : tree.getDefaultDatas();
+        // return showLevel ? tree.getDefaultDatasByLevel(this.ctx.tender.rpt_show_level) : tree.getDefaultDatas();
+        return tree.getDefaultDatas();
     }
     async budgetYu(bid, showLevel = false) {
         const yu = await this.ctx.service.budgetYu.getAllDataByCondition({ where: { bid } });
         const tree = new ledger.billsTree(this.ctx, { id: 'tree_id', pid: 'tree_pid', order: 'order', level: 'level', rootId: -1, calcFields: ['total_price'] });
         tree.loadDatas(yu);
         tree.calculateAll();
-        return showLevel ? tree.getDefaultDatasByLevel(this.ctx.tender.rpt_show_level) : tree.getDefaultDatas();
+        // return showLevel ? tree.getDefaultDatasByLevel(this.ctx.tender.rpt_show_level) : tree.getDefaultDatas();
+        return tree.getDefaultDatas();
     }
     async budgetGu(bid, showLevel = false) {
         const gu = await this.ctx.service.budgetGu.getAllDataByCondition({ where: { bid } });
         const tree = new ledger.billsTree(this.ctx, { id: 'tree_id', pid: 'tree_pid', order: 'order', level: 'level', rootId: -1, calcFields: ['total_price'] });
         tree.loadDatas(gu);
         tree.calculateAll();
-        return showLevel ? tree.getDefaultDatasByLevel(this.ctx.tender.rpt_show_level) : tree.getDefaultDatas();
+        // return showLevel ? tree.getDefaultDatasByLevel(this.ctx.tender.rpt_show_level) : tree.getDefaultDatas();
+        return tree.getDefaultDatas();
+    }
+    async budgetZb(bid, showLevel = false) {
+        const zb = await this.ctx.service.budgetZb.getAllDataByCondition({ where: { bid } });
+        const tree = new ledger.billsTree(this.ctx, { id: 'tree_id', pid: 'tree_pid', order: 'order', level: 'level', rootId: -1, calcFields: ['total_price'] });
+        tree.loadDatas(zb);
+        tree.calculateAll();
+        // return showLevel ? tree.getDefaultDatasByLevel(this.ctx.tender.rpt_show_level) : tree.getDefaultDatas();
+        return tree.getDefaultDatas();
     }
     async budgetFinal(bid, showLevel = false) {
         const budget = this.ctx.budget && this.ctx.budget.id === bid
@@ -46,39 +80,40 @@ class reportMemoryBudget {
         const final = await this.ctx.service.budgetFinal.getAllDataByCondition({ where: { final_id: budget.final_id } });
         const tree = new ledger.billsTree(this.ctx, { id: 'tree_id', pid: 'tree_pid', order: 'order', level: 'level', rootId: -1, calcFields: [] });
         tree.loadDatas(final);
-        return showLevel ? tree.getDefaultDatasByLevel(this.ctx.tender.rpt_show_level) : tree.getDefaultDatas();
-    }
-
-    async _getTenderBudget(tid) {
-        if (this.getBudget) return;
-
-        const budgets = await this.ctx.service.budget.getBudget(true);
-        this.budget = budgets.find(x => {
-            const relaTender = x.rela_tender.split(',');
-            return relaTender.indexOf(tid + '') >= 0;
-        });
-        this.getBudget = true;
-    }
-
-    async tenderGai(tid, filter = false) {
-        await this._getTenderBudget(tid);
-        return this.budget ? await this.budgetGai(this.budget.id, filter) : [];
-    }
-
-    async tenderYu(tid, filter = false) {
-        await this._getTenderBudget(tid);
-        return this.budget ? await this.budgetYu(this.budget.id, filter) : [];
-    }
-
-    async tenderGu(tid, filter = false) {
-        await this._getTenderBudget(tid);
-        return this.budget ? await this.budgetGu(this.budget.id, filter) : [];
+        // return showLevel ? tree.getDefaultDatasByLevel(this.ctx.tender.rpt_show_level) : tree.getDefaultDatas();
+        return tree.getDefaultDatas();
     }
 
-    async tenderFinal(tid, filter = false) {
-        await this._getTenderBudget(tid);
-        return this.budget ? await this.budgetFinal(this.budget.id, filter) : [];
+    getCommonData(params, tableName, fields, customDefine, customSelect) {
+        switch (tableName) {
+            case 'mem_info':
+                return this.ctx.subProject.info;
+            case 'mem_qty_info':
+                return [...this.ctx.subProject.info.main_quantity, ...this.ctx.subProject.info.gcl_quantity];
+            case 'mem_budget_gu':
+                return this.budgetGu(this.ctx.budget.id);
+            case 'mem_budget_gai':
+                return this.budgetGai(this.ctx.budget.id);
+            case 'mem_budget_yu':
+                return this.budgetYu(this.ctx.budget.id);
+            case 'mem_budget_zb':
+                return this.budgetZb(this.ctx.budget.id);
+            case 'mem_budget_final':
+                return this.budgetFinal(this.ctx.budget.id);
+            case 'mem_budget_gu_filter':
+                return this.budgetGu(this.ctx.budget.id, true);
+            case 'mem_budget_gai_filter':
+                return this.budgetGai(this.ctx.budget.id, true);
+            case 'mem_budget_yu_filter':
+                return this.budgetYu(this.ctx.budget.id, true);
+            case 'mem_budget_zb_filter':
+                return this.budgetZb(this.ctx.budget.id, true);
+            case 'mem_budget_final_filter':
+                return this.budgetFinal(this.ctx.budget.id, true);
+            default:
+                return [];
+        }
     }
 }
 
-module.exports = reportMemoryBudget;
+module.exports = rptMemPaymentSafe;

+ 1 - 0
app/lib/rm/material.js

@@ -113,6 +113,7 @@ class ReportMemoryMaterial {
             opinion: user.opinion,
             end_time: auditors && auditors.length > 0 ? auditors[0].begin_time : null,
             sort: 0,
+            tp_data: this.ctx.material.tp_data,
         }, ...auditors];
         return result;
     }

+ 4 - 0
app/lib/rm/tender.js

@@ -182,6 +182,8 @@ class rptMemPaymentSafe extends RptMemBase {
                 return params.budget_id ? budgetSource.budgetGai(params.budget_id) : budgetSource.tenderGai(params.tender_id);
             case 'mem_budget_yu':
                 return params.budget_id ? budgetSource.budgetYu(params.budget_id) : budgetSource.tenderYu(params.tender_id);
+            case 'mem_budget_zb':
+                return params.budget_id ? budgetSource.budgetZb(params.budget_id) : budgetSource.tenderZb(params.tender_id);
             case 'mem_budget_final':
                 return params.budget_id ? budgetSource.budgetFinal(params.budget_id) : budgetSource.tenderFinal(params.tender_id);
             case 'mem_budget_gu_filter':
@@ -190,6 +192,8 @@ class rptMemPaymentSafe extends RptMemBase {
                 return params.budget_id ? budgetSource.budgetGai(params.budget_id, true) : budgetSource.tenderGai(params.tender_id, true);
             case 'mem_budget_yu_filter':
                 return params.budget_id ? budgetSource.budgetYu(params.budget_id, true) : budgetSource.tenderYu(params.tender_id, true);
+            case 'mem_budget_zb_filter':
+                return params.budget_id ? budgetSource.budgetZb(params.budget_id, true) : budgetSource.tenderZb(params.tender_id, true);
             case 'mem_budget_final_filter':
                 return params.budget_id ? budgetSource.budgetFinal(params.budget_id, true) : budgetSource.tenderFinal(params.tender_id, true);
             case 'mem_pm_deal_pay':

+ 106 - 0
app/lib/rm/tender_budget.js

@@ -0,0 +1,106 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date
+ * @version
+ */
+
+const ledger = require('../ledger');
+
+class reportMemoryBudget {
+    constructor(ctx) {
+        this.ctx = ctx;
+        this.budget = null;
+        this.getBudget = false;
+    }
+
+    async budgetInfo(bid) {
+        const subProject = await this.ctx.service.subProject.getDataByCondition({ budget_id: bid });
+        return await this.ctx.service.subProjInfo.getInfo4Report(subProject.id);
+    }
+
+    async budgetGai(bid, showLevel = false) {
+        const gai = await this.ctx.service.budgetGai.getAllDataByCondition({ where: { bid } });
+        const tree = new ledger.billsTree(this.ctx, { id: 'tree_id', pid: 'tree_pid', order: 'order', level: 'level', rootId: -1, calcFields: ['total_price'] });
+        tree.loadDatas(gai);
+        tree.calculateAll();
+        return showLevel ? tree.getDefaultDatasByLevel(this.ctx.tender.rpt_show_level) : tree.getDefaultDatas();
+    }
+    async budgetYu(bid, showLevel = false) {
+        const yu = await this.ctx.service.budgetYu.getAllDataByCondition({ where: { bid } });
+        const tree = new ledger.billsTree(this.ctx, { id: 'tree_id', pid: 'tree_pid', order: 'order', level: 'level', rootId: -1, calcFields: ['total_price'] });
+        tree.loadDatas(yu);
+        tree.calculateAll();
+        return showLevel ? tree.getDefaultDatasByLevel(this.ctx.tender.rpt_show_level) : tree.getDefaultDatas();
+    }
+    async budgetGu(bid, showLevel = false) {
+        const gu = await this.ctx.service.budgetGu.getAllDataByCondition({ where: { bid } });
+        const tree = new ledger.billsTree(this.ctx, { id: 'tree_id', pid: 'tree_pid', order: 'order', level: 'level', rootId: -1, calcFields: ['total_price'] });
+        tree.loadDatas(gu);
+        tree.calculateAll();
+        return showLevel ? tree.getDefaultDatasByLevel(this.ctx.tender.rpt_show_level) : tree.getDefaultDatas();
+    }
+    async budgetZb(bid, showLevel = false) {
+        const zb = await this.ctx.service.budgetZb.getAllDataByCondition({ where: { bid } });
+        const tree = new ledger.billsTree(this.ctx, { id: 'tree_id', pid: 'tree_pid', order: 'order', level: 'level', rootId: -1, calcFields: ['total_price'] });
+        tree.loadDatas(zb);
+        tree.calculateAll();
+        return showLevel ? tree.getDefaultDatasByLevel(this.ctx.tender.rpt_show_level) : tree.getDefaultDatas();
+    }
+    async budgetFinal(bid, showLevel = false) {
+        const budget = this.ctx.budget && this.ctx.budget.id === bid
+            ? this.ctx.budget
+            : await this.ctx.service.budget.getDataById(bid);
+        if (!budget.final_id) return [];
+        const final = await this.ctx.service.budgetFinal.getAllDataByCondition({ where: { final_id: budget.final_id } });
+        const tree = new ledger.billsTree(this.ctx, { id: 'tree_id', pid: 'tree_pid', order: 'order', level: 'level', rootId: -1, calcFields: [] });
+        tree.loadDatas(final);
+        return showLevel ? tree.getDefaultDatasByLevel(this.ctx.tender.rpt_show_level) : tree.getDefaultDatas();
+    }
+
+    async _getTenderBudget(tid) {
+        if (this.getBudget) return;
+
+        const budgets = await this.ctx.service.budget.getBudget(true);
+        this.budget = budgets.find(x => {
+            const relaTender = x.rela_tender.split(',');
+            return relaTender.indexOf(tid + '') >= 0;
+        });
+        this.getBudget = true;
+    }
+
+    async tenderBudgetInfo(tid) {
+        await this._getTenderBudget(tid);
+        return this.budget ? await this.budgetInfo(this.budget.id) : [];
+    }
+
+    async tenderGai(tid, filter = false) {
+        await this._getTenderBudget(tid);
+        return this.budget ? await this.budgetGai(this.budget.id, filter) : [];
+    }
+
+    async tenderYu(tid, filter = false) {
+        await this._getTenderBudget(tid);
+        return this.budget ? await this.budgetYu(this.budget.id, filter) : [];
+    }
+
+    async tenderGu(tid, filter = false) {
+        await this._getTenderBudget(tid);
+        return this.budget ? await this.budgetGu(this.budget.id, filter) : [];
+    }
+
+    async tenderZb(tid, filter = false) {
+        await this._getTenderBudget(tid);
+        return this.budget ? await this.budgetZb(this.budget.id, filter) : [];
+    }
+
+    async tenderFinal(tid, filter = false) {
+        await this._getTenderBudget(tid);
+        return this.budget ? await this.budgetFinal(this.budget.id, filter) : [];
+    }
+}
+
+module.exports = reportMemoryBudget;

+ 1 - 0
app/middleware/change_apply_check.js

@@ -98,6 +98,7 @@ module.exports = options => {
             }
             // 调差的readOnly 指表格和页面只能看不能改,和审批无关
             change.readOnly = !((change.status === status.uncheck || change.status === status.checkNo) && accountId === change.uid);
+            change.shenpiPower = change.status === status.checking && change.curAuditor.aid === accountId;
             this.change = change;
             yield next;
         } catch (err) {

+ 1 - 1
app/middleware/stage_check.js

@@ -134,7 +134,7 @@ module.exports = options => {
             // 根据状态判断是否需要更新审批人列表
             yield this.service.stage.doCheckStageCanCancel(stage);
             this.stage = stage;
-
+            // 根据状态判断是否需要更新审批人列表
             if ((stage.status === status.uncheck || stage.status === status.checkNo) && this.tender.info.shenpi.stage !== shenpiConst.sp_status.sqspr) {
                 const shenpi_status = this.tender.info.shenpi.stage;
                 // 进一步比较审批流是否与审批流程设置的相同,不同则替换为固定审批流或固定的终审

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

@@ -33,6 +33,9 @@ $(document).ready(() => {
             {title: '施工图预算|数量1/数量2', colSpan: '3|1', rowSpan: '1|1', field: 'yu_dgn_qty', hAlign: 2, width: 80, bc_type: 'number'},
             {title: '|经济指标', colSpan: '|1', rowSpan: '|1', field: 'yu_dgn_price', hAlign: 2, width: 80, type: 'Number', bc_type: 'number'},
             {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'yu_tp', hAlign: 2, width: 80, type: 'Number', bc_type: 'number'},
+            {title: '招标预算|数量1/数量2', colSpan: '3|1', rowSpan: '1|1', field: 'zb_dgn_qty', hAlign: 2, width: 80, bc_type: 'number'},
+            {title: '|经济指标', colSpan: '|1', rowSpan: '|1', field: 'zb_dgn_price', hAlign: 2, width: 80, type: 'Number', bc_type: 'number'},
+            {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'zb_tp', hAlign: 2, width: 80, type: 'Number', bc_type: 'number'},
             {title: '台账|数量1/数量2', colSpan: '3|1', rowSpan: '1|1', field: 'dgn_qty', hAlign: 2, width: 80, bc_type: 'number', visible: false},
             {title: '|经济指标', colSpan: '|1', rowSpan: '|1', field: 'dgn_price', hAlign: 2, width: 80, type: 'Number', bc_type: 'number', visible: false},
             {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'total_price', hAlign: 2, width: 80, type: 'Number', bc_type: 'number', visible: false},
@@ -163,6 +166,15 @@ $(document).ready(() => {
                 cur.yu_dgn_qty2 = ZhCalc.add(cur.yu_dgn_qty2, source.dgn_qty2);
                 cur.yu_tp = ZhCalc.add(cur.yu_tp, source.total_price);
             });
+            const zbTree = createNewPathTree('ledger', setting);
+            zbTree.loadDatas(result.zb);
+            treeCalc.calculateAll(zbTree);
+            compareTree.loadTree(zbTree, function (cur, source) {
+                cur.base = true;
+                cur.zb_dgn_qty1 = ZhCalc.add(cur.zb_dgn_qty1, source.dgn_qty1);
+                cur.zb_dgn_qty2 = ZhCalc.add(cur.zb_dgn_qty2, source.dgn_qty2);
+                cur.zb_tp = ZhCalc.add(cur.zb_tp, source.total_price);
+            });
             compareTree.afterLoad(node => {
                 node.gu_dgn_price = ZhCalc.div(node.gu_tp, node.gu_dgn_qty1, 2);
                 node.gu_dgn_qty = node.gu_dgn_qty1
@@ -176,6 +188,10 @@ $(document).ready(() => {
                 node.yu_dgn_qty = node.yu_dgn_qty1
                     ? (node.yu_dgn_qty2 ? node.yu_dgn_qty1 + '/' + node.yu_dgn_qty2 : node.yu_dgn_qty1)
                     : (node.yu_dgn_qty2 ? '/' + node.yu_dgn_qty2 : '');
+                node.zb_dgn_price = ZhCalc.div(node.zb_tp, node.zb_dgn_qty1, 2);
+                node.zb_dgn_qty = node.zb_dgn_qty1
+                    ? (node.zb_dgn_qty2 ? node.zb_dgn_qty1 + '/' + node.zb_dgn_qty2 : node.zb_dgn_qty1)
+                    : (node.zb_dgn_qty2 ? '/' + node.zb_dgn_qty2 : '');
             });
             compareTree.resortChildrenByCustom(function (x, y) {
                 const iCode = compareCode(x.code, y.code);

+ 23 - 2
app/public/js/budget_list.js

@@ -32,7 +32,7 @@ $(document).ready(() => {
                 html.push('<td width="20%" class="in-' + node.tree_level + '">');
                 if (node.is_folder) {
                     if (node.children.length > 0) {
-                        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="收起" id="'+ node.id +'"><i class="fa fa-minus-square-o"></i></span> <i class="fa fa-folder-o"></i> ', node.name);
                     } else {
                         html.push('<i class="fa fa-folder-o"></i> ', node.name);
                     }
@@ -62,8 +62,10 @@ $(document).ready(() => {
                     html.push(`<td></td>`);
                 } else {
                     html.push(`<td>`);
+                    if (node.permission.indexOf(2) >= 0)
+                        html.push(`<a href="/sp/${node.id}/info" class="btn btn-outline-primary btn-sm" data-target="#project-info" target="_blank" name="info">项目概况</a>`);
                     if (node.manage_permission.indexOf(1) >= 0)
-                        html.push('<button class="btn btn-outline-primary btn-sm" data-target="#select-rela" name="del" onclick="showModal(this);">关联标段</button>');
+                        html.push('<button class="btn btn-outline-primary btn-sm ml-1" data-target="#select-rela" name="del" onclick="showModal(this);">关联标段</button>');
                     html.push('</td>');
                 }
                 return html.join('');
@@ -93,6 +95,25 @@ $(document).ready(() => {
         };
 
         Utils.reloadTable();
+
+        $('body').on('click', '.fold-switch', function() {
+            const id = this.getAttribute('id');
+            const node = budgetTree.getItems(id);
+            budgetTree.setExpanded(node, !node.expanded);
+            const posterity = budgetTree.getPosterity(node);
+            if (node.expanded) {
+                $(this).html(`<i class="fa fa-minus-square-o"></i>`);
+            } else {
+                $(this).html(`<i class="fa fa-plus-square-o"></i>`);
+            }
+            for (const p of posterity) {
+                if (p.visible) {
+                    $(`tr[tree_id=${p.id}]`).show();
+                } else {
+                    $(`tr[tree_id=${p.id}]`).hide();
+                }
+            }
+        });
         return { budgetTree, TableObj, ...Utils };
     })({
         treeSetting: { id: 'id', pid: 'tree_pid', level: 'tree_level', order: 'tree_order', rootId: '-1' },

+ 28 - 13
app/public/js/change.js

@@ -289,19 +289,34 @@ $(document).ready(() => {
 
     //状态切换
     $('#status_select a').on('click', function () {
-       const status = $(this).data('val');
-       let url = '/tender/'+ $('#tenderId').val() +'/change';
-       if (status !== 0) {
-           url += '/status/'+ status;
-       }
-       const filterString = setChangeFilterData('change-'+ $('#tenderId').val() +'-list-order');
-       if (filterString) url = url + filterString;
-        // let orderSetting = getLocalCache('change-'+ $('#tenderId').val() +'-list-order');
-       // if (orderSetting) {
-       //     const orders = orderSetting.split('|');
-       //     url += '?sort=' + orders[0] + '&order=' + orders[1];
-       // }
-       window.location.href = url;
+        const status = parseInt($(this).data('val'));
+        let url = '/tender/'+ $('#tenderId').val() +'/change';
+        if (status !== 0) {
+            url += '/status/'+ status;
+        }
+        const state = parseInt($('#state_zhankai').attr('data-value')) || 0;
+        const filterString = setChangeFilterData('change-'+ $('#tenderId').val() +'-list-order');
+        url = filterString ? url + filterString : url;
+        if (state) {
+            url = filterString ? url + '&state=' + state : url + '?state=' + state;
+        }
+        window.location.href = url;
+    });
+
+    //变更令状态切换
+    $('#state_select a').on('click', function () {
+        const state = parseInt($(this).data('val'));
+        let url = '/tender/'+ $('#tenderId').val() +'/change';
+        const status = parseInt($('#zhankai').attr('data-value'));
+        if (status !== 0) {
+            url += '/status/'+ status;
+        }
+        const filterString = setChangeFilterData('change-'+ $('#tenderId').val() +'-list-order');
+        url = filterString ? url + filterString : url;
+        if (state) {
+            url = filterString ? url + '&state=' + state : url + '?state=' + state;
+        }
+        window.location.href = url;
     });
     // 不再显示首次使用
     $('#changeFirst').click(function () {

+ 154 - 5
app/public/js/change_apply_information.js

@@ -197,6 +197,7 @@ $(document).ready(() => {
 
     const changeSpreadSetting = {
         cols: [
+            {title: '新增单价', colSpan: '1', rowSpan: '2', field: 'new_up', hAlign: 1, width: 35, cellType: 'checkbox', readOnly: 'readOnly.isEdit'},
             {title: '清单编号', colSpan: '1', rowSpan: '2', field: 'code', hAlign: 0, width: 110, formatter: '@', readOnly: 'readOnly.isEdit'},
             {title: '名称', colSpan: '1', rowSpan: '2', field: 'name', hAlign: 0, width: 130, formatter: '@', readOnly: 'readOnly.isEdit'},
             {title: '单位', colSpan: '1', rowSpan: '2', field: 'unit', hAlign: 1, width: 60, formatter: '@', readOnly: 'readOnly.isEdit', cellType: 'unit', comboItems: changeUnits, comboEdit: true},
@@ -205,6 +206,8 @@ $(document).ready(() => {
             {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'oa_tp', hAlign: 2, width: 80, type: 'Number', readOnly: true, getValue: 'getValue.oa_tp'},
             {title: '申请变更增(+)减(-)|数量', colSpan: '2|1', rowSpan: '1|1', field: 'camount', hAlign: 2, width: 60, type: 'Number', readOnly: 'readOnly.isEdit', getValue: 'getValue.camount'},
             {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'ca_tp', hAlign: 2, width: 80, type: 'Number', readOnly: true, getValue: 'getValue.ca_tp'},
+            {title: '备注1', colSpan: '1', rowSpan: '2', field: 'ex_memo1', hAlign: 0, width: 110, formatter: '@', readOnly: 'readOnly.isEdit2', cellType: 'ellipsisAutoTip', scrollHeightClass: '.sjs-height-0'},
+            {title: '备注2', colSpan: '1', rowSpan: '2', field: 'ex_memo2', hAlign: 0, width: 110, formatter: '@', readOnly: 'readOnly.isEdit2', cellType: 'ellipsisAutoTip', scrollHeightClass: '.sjs-height-0'},
         ],
         emptyRows: !readOnly ? 3 : 0,
         headRows: 2,
@@ -212,7 +215,7 @@ $(document).ready(() => {
         defaultRowHeight: 21,
         headerFont: '12px 微软雅黑',
         font: '12px 微软雅黑',
-        readOnly: readOnly,
+        readOnly: change.status === auditConst.status.checking ? false : readOnly,
         localCache: {
             key: 'changes-apply-list-spread',
             colWidth: true,
@@ -243,6 +246,9 @@ $(document).ready(() => {
             isEdit: function (data) {
                 return readOnly;
             },
+            isEdit2: function (data) {
+                return !(!readOnly || change.shenpiPower);
+            },
         },
     };
     const changeSpreadObj = {
@@ -250,6 +256,8 @@ $(document).ready(() => {
             // 增加汇总行并设为锁定禁止编辑状态
             changeSpreadSheet.addRows(changeSpreadSheet.getRowCount(), 1);
             changeSpreadSheet.setValue(changeSpreadSheet.getRowCount() - 1, 0, '合计');
+            const cellType1 = new GC.Spread.Sheets.CellTypes.Text();
+            changeSpreadSheet.getCell(changeSpreadSheet.getRowCount() - 1, 0).cellType(cellType1);
             changeSpreadSheet.setStyle(changeSpreadSheet.getRowCount() - 1, -1, style1);
             changeSpreadObj.countSum();
         },
@@ -258,11 +266,11 @@ $(document).ready(() => {
             let oSum = 0,
                 cSum = 0;
             for (let i = 0; i < rowCount - 1; i++) {
-                oSum = ZhCalc.add(oSum, changeSpreadSheet.getValue(i, 5));
-                cSum = ZhCalc.add(cSum, changeSpreadSheet.getValue(i, 7));
+                oSum = ZhCalc.add(oSum, changeSpreadSheet.getValue(i, 6));
+                cSum = ZhCalc.add(cSum, changeSpreadSheet.getValue(i, 8));
             }
-            changeSpreadSheet.setValue(changeSpreadSheet.getRowCount() - 1, 5, oSum !== 0 ? oSum : null);
-            changeSpreadSheet.setValue(changeSpreadSheet.getRowCount() - 1, 7, cSum !== 0 ? cSum : null);
+            changeSpreadSheet.setValue(changeSpreadSheet.getRowCount() - 1, 6, oSum !== 0 ? oSum : null);
+            changeSpreadSheet.setValue(changeSpreadSheet.getRowCount() - 1, 8, cSum !== 0 ? cSum : null);
         },
         deletePress: function (sheet) {
             return;
@@ -378,11 +386,13 @@ $(document).ready(() => {
                 const sortData = info.sheet.zh_data || [];
                 const range = info.cellRange;
                 const data = [];
+                let haveNew = false;
                 for (let iRow = 0; iRow < range.rowCount; iRow++) {
                     let bPaste = true;
                     const curRow = range.row + iRow;
                     // const materialData = JSON.parse(JSON.stringify(sortData[curRow]));
                     const cLData = curRow >= sortData.length ? {unit: ''} : {id: sortData[curRow].id};
+                    haveNew = curRow >= sortData.length ? curRow : false;
                     const hintRow = range.rowCount > 1 ? curRow : '';
                     let sameCol = 0;
                     for (let iCol = 0; iCol < range.colCount; iCol++) {
@@ -484,8 +494,35 @@ $(document).ready(() => {
                 });
             }
         };
+        changeSpreadObj.buttonClicked = function (e, info) {
+            if (info.sheet.zh_setting) {
+                const select = SpreadJsObj.getSelectObject(info.sheet);
+                const col = info.sheet.zh_setting.cols[info.col];
+                if(!select) {
+                    toastr.error('请添加清单编号再勾选');
+                    if (info.sheet.isEditing()) {
+                        info.sheet.endEdit(true);
+                    }
+                    return;
+                } else if (col.field === 'new_up') {
+                    if (info.sheet.isEditing()) {
+                        info.sheet.endEdit(true);
+                    }
+                    select.new_up = info.sheet.getValue(info.row, info.col) ? 0 : 1;
+                    postData(preUrl + '/list/save', { type: 'update', updateData: { id: select.id, new_up: select.new_up } }, function (result) {
+                        changeList.splice(info.row, 1, select);
+                        SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                        changeSpreadObj.countSum();
+                    }, function () {
+                        select.new_up = info.sheet.getValue(info.row, info.col) ? 1 : 0;
+                        SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                    });
+                }
+            }
+        };
         // changeSpread.bind(spreadNS.Events.CellChanged, changeSpreadObj.cellChanged);
         changeSpread.bind(spreadNS.Events.EditEnded, changeSpreadObj.editEnded);
+        changeSpread.bind(spreadNS.Events.ButtonClicked, changeSpreadObj.buttonClicked);
         changeSpread.bind(spreadNS.Events.ClipboardPasted, changeSpreadObj.clipboardPasted);
         changeSpread.bind(spreadNS.Events.ValueChanged, changeSpreadObj.valueChanged);
         SpreadJsObj.addDeleteBind(changeSpread, changeSpreadObj.deletePress);
@@ -620,6 +657,103 @@ $(document).ready(() => {
             });
         })
     }
+
+    if (change.shenpiPower) {
+        changeSpreadObj.editEnded = function (e, info) {
+            if (info.sheet.zh_setting) {
+                const select = SpreadJsObj.getSelectObject(info.sheet);
+                const col = info.sheet.zh_setting.cols[info.col];
+                // 未改变值则不提交
+                let validText = is_numeric(info.editingText) ? parseFloat(info.editingText) : (info.editingText ? trimInvalidChar(info.editingText) : '');
+                const orgValue = select[col.field];
+                if (orgValue == validText || ((!orgValue || orgValue === '') && (validText === ''))) {
+                    SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                    return;
+                }
+                select[col.field] = validText;
+                const data = {
+                    id: select.id,
+                };
+                if (col.field === 'ex_memo1' || col.field === 'ex_memo2') {
+                    data[col.field] = select[col.field];
+                }
+                console.log(data);
+
+                // 更新至服务器
+                postData(preUrl + '/list/save', { type:'update', updateData: data }, function (result) {
+                    changeList.splice(info.row, 1, select);
+                    SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                }, function () {
+                    select[col.field] = orgValue;
+                    if (col.field !== 'ex_memo1' && col.field !== 'ex_memo2') {
+                        select.spamount = orgValue;
+                    }
+                    SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                });
+            }
+        };
+        changeSpreadObj.clipboardPasted = function(e, info) {
+            const hint = {
+                cellError: {type: 'error', msg: '粘贴内容超出了表格范围'},
+                numberExpr: {type: 'error', msg: '不能粘贴其它非数字类型字符'},
+            };
+            const range = info.cellRange;
+            const sortData = info.sheet.zh_data || [];
+            const data = [];
+            for (let iRow = 0; iRow < range.rowCount; iRow++) {
+                let bPaste = true;
+                const curRow = range.row + iRow;
+                // const materialData = JSON.parse(JSON.stringify(sortData[curRow]));
+                const cLData = { id: sortData[curRow].id };
+                const hintRow = range.rowCount > 1 ? curRow : '';
+                let sameCol = 0;
+                for (let iCol = 0; iCol < range.colCount; iCol++) {
+                    const curCol = range.col + iCol;
+                    const colSetting = info.sheet.zh_setting.cols[curCol];
+                    if (!colSetting) continue;
+
+                    let validText = info.sheet.getText(curRow, curCol);
+                    validText = is_numeric(validText) ? parseFloat(validText) : (validText ? trimInvalidChar(validText) : null);
+                    const orgValue = sortData[curRow][colSetting.field];
+                    if (orgValue == validText || ((!orgValue || orgValue === '') && (validText === ''))) {
+                        sameCol++;
+                        if (range.colCount === sameCol)  {
+                            bPaste = false;
+                        }
+                        continue;
+                    }
+                    sortData[curRow][colSetting.field] = validText;
+                    if (colSetting.field === 'ex_memo1' || colSetting.field === 'ex_memo2') {
+                        cLData[colSetting.field] = validText;
+                    }
+                }
+                if (bPaste) {
+                    data.push(cLData);
+                    // rowData.push(curRow);
+                } else {
+                    SpreadJsObj.reLoadRowData(info.sheet, curRow);
+                }
+            }
+            if (data.length === 0) {
+                return;
+            }
+            console.log(data);
+            // 更新至服务器
+            postData(preUrl + '/list/save', { type:'paste_amount_rows', updateData: data }, function (result) {
+                changeList = result;
+                SpreadJsObj.loadSheetData(changeSpreadSheet, SpreadJsObj.DataType.Data, changeList);
+                changeSpreadObj.makeSjsFooter();
+            }, function () {
+                SpreadJsObj.reLoadRowData(info.sheet, info.cellRange.row, info.cellRange.rowCount);
+                return;
+            });
+        };
+        changeSpread.bind(spreadNS.Events.EditEnded, changeSpreadObj.editEnded);
+        changeSpread.bind(spreadNS.Events.ClipboardPasted, changeSpreadObj.clipboardPasted);
+        changeSpread.bind(spreadNS.Events.ValueChanged, changeSpreadObj.valueChanged);
+        SpreadJsObj.addDeleteBind(changeSpread, changeSpreadObj.deletePress);
+    }
+
     let changeListData;
     let gclGatherData;
     let dealBillList;
@@ -673,8 +807,23 @@ $(document).ready(() => {
             // }
         }
     });
+    // let timer = null
+    // const handleScroll = function (e) {
+    //     timer && clearTimeout(timer)
+    //     timer = setTimeout(() => {
+    //         console.log('hello');
+    //
+    //         // SpreadJsObj.initSpreadSettingEvents(changeSpreadSetting, changeCol);
+    //         // SpreadJsObj.initSheet(changeSpreadSheet, changeSpreadSetting);
+    //         // SpreadJsObj.loadSheetData(changeSpreadSheet, SpreadJsObj.DataType.Data, changeList);
+    //         // changeSpreadObj.makeSjsFooter();
+    //     }, 400);
+    // }
+    //
+    // document.querySelector('.sjs-height-0').addEventListener('scroll', handleScroll);
 });
 
+
 /**
  * 校验文件大小、格式
  * @param {Array} files 文件数组

+ 3 - 0
app/public/js/change_company.js

@@ -81,5 +81,8 @@ function selectCompanyHtml(data) {
     for (const s of data.select) {
         html.push('<option>'+ s.name +'</option>');
     }
+    for (const u of unitList) {
+        html.push('<option>'+ u.name +'</option>');
+    }
     $('#company').html(html);
 }

+ 130 - 2
app/public/js/change_information.js

@@ -74,7 +74,7 @@ $(document).ready(() => {
     if (cca !== null && cca !== undefined) {
         $('#customCheck1').prop('checked', cca !== 'false');
     }
-    changeSpreadSheet.setColumnVisible(5,$('#customCheck1').is(':checked'), GC.Spread.Sheets.SheetArea.viewport);
+    changeSpreadSheet.setColumnVisible(6,$('#customCheck1').is(':checked'), GC.Spread.Sheets.SheetArea.viewport);
     // 变更详情展示和隐藏
     $('.change-detail-checkbox').on('click', function (e) {
         if($(e.target).is('label')){
@@ -82,7 +82,7 @@ $(document).ready(() => {
         }
         // // 设置用户项目本地记录展示和隐藏情况
         setLocalCache('change-checkbox-account-'+ accountId, $(this).is(':checked'));
-        changeSpreadSheet.setColumnVisible(5,$(this).is(':checked'), GC.Spread.Sheets.SheetArea.viewport);
+        changeSpreadSheet.setColumnVisible(6,$(this).is(':checked'), GC.Spread.Sheets.SheetArea.viewport);
     });
 
     // 计算最新的变更总额和change的total_price是否一致,不一致则更新
@@ -419,6 +419,134 @@ $(document).ready(() => {
         czSpreadObj.makeSjsFooter();
     });
 
+    // 清单汇总信息获取
+    let hzSpread = null;
+    const hzSpreadSetting = {
+        cols: [
+            {title: '清单编号', colSpan: '1', rowSpan: '2', field: 'code', hAlign: 0, width: 80},
+            {title: '名称', colSpan: '1', rowSpan: '2', field: 'name', hAlign: 0, width: 120},
+            {title: '单位', colSpan: '1', rowSpan: '2', field: 'unit', hAlign: 1, width: 60},
+            {title: '单价', colSpan: '1', rowSpan: '2', field: 'unit_price', hAlign: 2, width: 60, type: 'Number', getValue: 'getValue.unit_price'},
+            {title: '申报变更|数量', colSpan: '2|1', rowSpan: '1|1', field: 'camount', hAlign: 2, width: 60, type: 'Number', getValue: 'getValue.camount'},
+            {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'ca_tp', hAlign: 2, width: 80, type: 'Number', getValue: 'getValue.ca_tp'},
+            {title: '审批变更|数量', colSpan: '2|1', rowSpan: '1|1', field: 'amount', hAlign: 2, width: 60, type: 'Number', getValue: 'getValue.amount', visible: auditStatus === 7},
+            {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'a_tp', hAlign: 2, width: 80, type: 'Number', getValue: 'getValue.a_tp', visible: auditStatus === 7},
+            {title: '审批变更|数量', colSpan: '2|1', rowSpan: '1|1', field: 'spamount', hAlign: 2, width: 60, type: 'Number', getValue: 'getValue.spamount', visible: [3,4,5,6,8].indexOf(auditStatus) !== -1},
+            {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'spa_tp', hAlign: 2, width: 80, type: 'Number', getValue: 'getValue.spa_tp', visible: [3,4,5,6,8].indexOf(auditStatus) !== -1},
+            {title: '审批变更|数量', colSpan: '2|1', rowSpan: '1|1', hAlign: 2, width: 60, type: 'Number', getValue: '', visible: [1,2,9].indexOf(auditStatus) !== -1},
+            {title: '|金额', colSpan: '|1', rowSpan: '|1', hAlign: 2, width: 80, type: 'Number', getValue: '', visible: [1,2,9].indexOf(auditStatus) !== -1},
+        ],
+        emptyRows: 0,
+        headRows: 2,
+        headRowHeight: [25, 25],
+        defaultRowHeight: 21,
+        headerFont: '12px 微软雅黑',
+        font: '12px 微软雅黑',
+        readOnly: true,
+        localCache: {
+            key: 'changes-hz',
+            colWidth: true,
+        }
+    };
+    const hzCol = {
+        getValue: {
+            unit_price: function(data) {
+                return ZhCalc.round(data.unit_price, unitPriceUnit);
+            },
+            camount: function (data) {
+                return ZhCalc.round(data.camount, findDecimal(data.unit));
+            },
+            ca_tp: function (data) {
+                return ZhCalc.round(data.ca_tp, totalPriceUnit);
+            },
+            amount: function (data) {
+                return ZhCalc.round(data.amount, findDecimal(data.unit));
+            },
+            a_tp: function (data) {
+                return ZhCalc.round(data.a_tp, totalPriceUnit);
+            },
+            spamount: function (data) {
+                return ZhCalc.round(data.spamount, findDecimal(data.unit));
+            },
+            spa_tp: function (data) {
+                return ZhCalc.round(data.spa_tp, totalPriceUnit);
+            },
+        }
+    };
+    const hzSpreadObj = {
+        makeBackColor: function () {
+            const rowCount = hzSpread.getActiveSheet().getRowCount();
+            for (let i = 0; i < rowCount; i++) {
+                if ([3,4,5,6,8].indexOf(auditStatus) !== -1 && hzSpread.getActiveSheet().zh_data[i].camount != hzSpread.getActiveSheet().zh_data[i].spamount) {
+                    hzSpread.getActiveSheet().getRange(i, -1, 1, -1).backColor('#ffeeba');
+                } else if (auditStatus === 7 && hzSpread.getActiveSheet().zh_data[i].camount != hzSpread.getActiveSheet().zh_data[i].amount) {
+                    hzSpread.getActiveSheet().getRange(i, -1, 1, -1).backColor('#ffeeba');
+                } else if ([1,2,9].indexOf(auditStatus) !== -1 && !(hzSpread.getActiveSheet().zh_data[i].camount === '' || hzSpread.getActiveSheet().zh_data[i].camount === 0 || hzSpread.getActiveSheet().zh_data[i].camount === null)) {
+                    hzSpread.getActiveSheet().getRange(i, -1, 1, -1).backColor('#ffeeba');
+                }
+            }
+        },
+        makeSjsFooter: function () {
+            // 增加汇总行并设为锁定禁止编辑状态
+            hzSpread.getActiveSheet().addRows(hzSpread.getActiveSheet().getRowCount(), 1);
+            hzSpread.getActiveSheet().setValue(hzSpread.getActiveSheet().getRowCount() - 1, 0, '合计');
+            hzSpread.getActiveSheet().setStyle(hzSpread.getActiveSheet().getRowCount() - 1, -1, style1);
+            hzSpreadObj.countSum();
+        },
+        countSum: function () {
+            const rowCount = hzSpread.getActiveSheet().getRowCount();
+            let cSum = 0,
+                sSum = 0,
+                spSum = 0;
+            for (let i = 0; i < rowCount - 1; i++) {
+                cSum = ZhCalc.add(cSum, hzSpread.getActiveSheet().getValue(i, 5));
+                sSum = ZhCalc.add(sSum, hzSpread.getActiveSheet().getValue(i, 7));
+                spSum = ZhCalc.add(spSum, hzSpread.getActiveSheet().getValue(i, 9));
+            }
+            hzSpread.getActiveSheet().setValue(hzSpread.getActiveSheet().getRowCount() - 1, 5, cSum !== 0 ? cSum : null);
+            hzSpread.getActiveSheet().setValue(hzSpread.getActiveSheet().getRowCount() - 1, 7, sSum !== 0 ? sSum : null);
+            hzSpread.getActiveSheet().setValue(hzSpread.getActiveSheet().getRowCount() - 1, 9, spSum !== 0 ? spSum : null);
+        },
+    };
+
+    $('#qdgather').on('shown.bs.modal', function () {
+        if (!hzSpread) {
+            hzSpread = SpreadJsObj.createNewSpread($('#hz-spread')[0]);
+            SpreadJsObj.initSpreadSettingEvents(hzSpreadSetting, hzCol);
+            SpreadJsObj.initSheet(hzSpread.getActiveSheet(), hzSpreadSetting);
+        }
+        const hzList = [];
+        const newChangeList = _.cloneDeep(changeList);
+        for (const cl of newChangeList) {
+            const hzIndex = _.findIndex(hzList, { code: cl.code, name: cl.name, unit: cl.unit, unit_price: cl.unit_price});
+            const audit_amount = cl.audit_amount ? cl.audit_amount.split(',') : '';
+            const amount = audit_amount ? parseFloat(audit_amount[audit_amount.length - 1]) : 0;
+            cl.ca_tp = ZhCalc.round(ZhCalc.mul(ZhCalc.round(cl.unit_price, unitPriceUnit), ZhCalc.round(cl.camount, findDecimal(cl.unit))), totalPriceUnit);
+            cl.spa_tp = ZhCalc.round(ZhCalc.mul(ZhCalc.round(cl.unit_price, unitPriceUnit), ZhCalc.round(cl.spamount, findDecimal(cl.unit))), totalPriceUnit);
+            cl.amount = amount;
+            cl.a_tp = ZhCalc.round(ZhCalc.mul(ZhCalc.round(cl.unit_price, unitPriceUnit), ZhCalc.round(cl.amount, findDecimal(cl.unit))), totalPriceUnit);
+            if (hzIndex !== -1) {
+                hzList[hzIndex].camount = ZhCalc.add(hzList[hzIndex].camount, cl.camount);
+                hzList[hzIndex].ca_tp = ZhCalc.add(hzList[hzIndex].ca_tp, cl.ca_tp);
+                hzList[hzIndex].spamount = ZhCalc.add(hzList[hzIndex].spamount, cl.spamount);
+                hzList[hzIndex].spa_tp = ZhCalc.add(hzList[hzIndex].spa_tp, cl.spa_tp);
+                hzList[hzIndex].amount = ZhCalc.add(hzList[hzIndex].amount, amount);
+                hzList[hzIndex].a_tp = ZhCalc.add(hzList[hzIndex].a_tp, cl.a_tp);
+            } else {
+                hzList.push(cl);
+            }
+        }
+        if (hzList.length > 0) {
+            // 按清单编号排序
+            hzList.sort(sortByCode);
+        }
+        console.log(hzList);
+        // // sjs设置
+        SpreadJsObj.loadSheetData(hzSpread.getActiveSheet(), SpreadJsObj.DataType.Data, hzList);
+        hzSpreadObj.makeBackColor();
+        hzSpreadObj.makeSjsFooter();
+    });
+
     $.subMenu({
         menu: '#sub-menu', miniMenu: '#sub-mini-menu', miniMenuList: '#mini-menu-list',
         toMenu: '#to-menu', toMiniMenu: '#to-mini-menu',

+ 23 - 13
app/public/js/change_information_approval.js

@@ -19,12 +19,14 @@ function getPasteHint (str, row = '') {
 $(document).ready(() => {
     const changeSpreadSetting = {
         cols: [
+            {title: '计价', colSpan: '1', rowSpan: '2', field: 'is_valuation', hAlign: 1, width: 50, cellType: 'checkbox', readOnly: true},
             {title: '清单编号', colSpan: '1', rowSpan: '2', field: 'code', hAlign: 0, width: 80, formatter: '@', readOnly: true},
             {title: '名称', colSpan: '1', rowSpan: '2', field: 'name', hAlign: 0, width: 120, formatter: '@', readOnly: true},
             {title: '单位', colSpan: '1', rowSpan: '2', field: 'unit', hAlign: 1, width: 60, formatter: '@', readOnly: true},
             {title: '单价', colSpan: '1', rowSpan: '2', field: 'unit_price', hAlign: 2, width: 60, type: 'Number', getValue: 'getValue.unit_price', readOnly: true},
             {title: '变更部位', colSpan: '1', rowSpan: '2', field: 'bwmx', hAlign: 0, width: 120, formatter: '@', readOnly: true},
             {title: '变更详情', colSpan: '1', rowSpan: '2', field: 'detail', hAlign: 0, width: 120, formatter: '@', readOnly: true},
+            {title: '计量上限(%)', colSpan: '1', rowSpan: '2', field: 'delimit', hAlign: 2, width: 60, formatter: '@', readOnly: true, visible: openChangeState},
             {title: '原设计|数量', colSpan: '2|1', rowSpan: '1|1', field: 'oamount', hAlign: 2, width: 60, type: 'Number', getValue: 'getValue.oamount', readOnly: true},
             {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'oa_tp', hAlign: 2, width: 80, type: 'Number', getValue: 'getValue.oa_tp', readOnly: true},
             {title: '申请变更增(+)减(-)|数量', colSpan: '2|1', rowSpan: '1|1', field: 'camount', hAlign: 2, width: 60, type: 'Number', getValue: 'getValue.camount', readOnly: true},
@@ -107,12 +109,13 @@ $(document).ready(() => {
             },
         },
     };
-
     const changeSpreadObj = {
         makeSjsFooter: function () {
             // 增加汇总行并设为锁定禁止编辑状态
             changeSpreadSheet.addRows(changeSpreadSheet.getRowCount(), 1);
             changeSpreadSheet.setValue(changeSpreadSheet.getRowCount() - 1, 0, '合计');
+            const cellType1 = new GC.Spread.Sheets.CellTypes.Text();
+            changeSpreadSheet.getCell(changeSpreadSheet.getRowCount() - 1, 0).cellType(cellType1);
             changeSpreadSheet.setStyle(changeSpreadSheet.getRowCount() - 1, -1, style1);
             changeSpreadObj.countSum();
         },
@@ -147,13 +150,20 @@ $(document).ready(() => {
             SpreadJsObj.loadSheetData(xmjSpread.getActiveSheet(), SpreadJsObj.DataType.Data, xmj);
         },
         selectionChanged: function (e, info) {
+            const sel = info.sheet.getSelections()[0];
+            const col = info.sheet.zh_setting.cols[sel.col];
             const data = SpreadJsObj.getSelectObject(info.sheet);
+            if (col && col.field === 'camount' && data) {
+                $('#camount-expr').val(data.camount_expr ? data.camount_expr : data.camount);
+            } else {
+                $('#camount-expr').val('');
+            }
             changeSpreadObj.resetXmjSpread(data);
         },
         setRowValueAndSum: function (data, row, col) {
             for (const j in aidList) {
-                const sum = ZhCalc.round(ZhCalc.mul(data.unit_price, parseFloat(changeSpreadSheet.getValue(row, 10 + parseInt(j)*2))), totalPriceUnit);
-                changeSpreadSheet.setValue(row, 11 + j*2, sum !== 0 ? sum : null);
+                const sum = ZhCalc.round(ZhCalc.mul(data.unit_price, parseFloat(changeSpreadSheet.getValue(row, startLimit + 3 + parseInt(j)*2))), totalPriceUnit);
+                changeSpreadSheet.setValue(row, startLimit + 4 + j*2, sum !== 0 ? sum : null);
             }
             // const sum = ZhCalc.round(ZhCalc.mul(data.unit_price, data.spamount), totalPriceUnit);
             // changeSpreadSheet.setValue(row, col+1, sum !== 0 ? sum : null);
@@ -164,11 +174,11 @@ $(document).ready(() => {
             let changed_sum = 0;
             for(let i = 0; i < rowCount - 1; i++){
                 audit_sum = ZhCalc.add(audit_sum, changeSpreadSheet.getValue(i, col+1));
-                changed_sum = ZhCalc.add(changed_sum, changeSpreadSheet.getValue(i, (11 + aidList.length*2)));
+                changed_sum = ZhCalc.add(changed_sum, changeSpreadSheet.getValue(i, (startLimit + 4 + aidList.length*2)));
             }
 
             changeSpreadSheet.setValue(changeSpreadSheet.getRowCount() - 1, col+1, audit_sum !== 0 ? audit_sum : null);
-            changeSpreadSheet.setValue(changeSpreadSheet.getRowCount() - 1, (11 + aidList.length*2), changed_sum !== 0 ? changed_sum : null);
+            changeSpreadSheet.setValue(changeSpreadSheet.getRowCount() - 1, (startLimit + 4 + aidList.length*2), changed_sum !== 0 ? changed_sum : null);
         },
         countSum: function() {
             const rowCount = changeSpreadSheet.getRowCount();
@@ -176,20 +186,20 @@ $(document).ready(() => {
                 cSum = 0,
                 cdSum = 0;
             for(let i = 0; i < rowCount - 1; i++){
-                oSum = ZhCalc.add(oSum, changeSpreadSheet.getValue(i, 7));
-                cSum = ZhCalc.add(cSum, changeSpreadSheet.getValue(i, 9));
-                cdSum = ZhCalc.add(cdSum, changeSpreadSheet.getValue(i, (11 + aidList.length*2)));
+                oSum = ZhCalc.add(oSum, changeSpreadSheet.getValue(i, startLimit));
+                cSum = ZhCalc.add(cSum, changeSpreadSheet.getValue(i, startLimit + 2));
+                cdSum = ZhCalc.add(cdSum, changeSpreadSheet.getValue(i, (startLimit + 4 + aidList.length*2)));
             }
-            changeSpreadSheet.setValue(changeSpreadSheet.getRowCount() - 1, 7, oSum !== 0 ? oSum : null);
-            changeSpreadSheet.setValue(changeSpreadSheet.getRowCount() - 1, 9, cSum !== 0 ? cSum : null);
-            changeSpreadSheet.setValue(changeSpreadSheet.getRowCount() - 1, (11 + aidList.length*2), cdSum !== 0 ? cdSum : null);
+            changeSpreadSheet.setValue(changeSpreadSheet.getRowCount() - 1, startLimit, oSum !== 0 ? oSum : null);
+            changeSpreadSheet.setValue(changeSpreadSheet.getRowCount() - 1, startLimit + 2, cSum !== 0 ? cSum : null);
+            changeSpreadSheet.setValue(changeSpreadSheet.getRowCount() - 1, (startLimit + 4 + aidList.length*2), cdSum !== 0 ? cdSum : null);
             // 用户的数据合计
             for (const j in aidList) {
                 let audit_sum = 0;
                 for(let i = 0; i < rowCount - 1; i++){
-                    audit_sum = ZhCalc.add(audit_sum, changeSpreadSheet.getValue(i, 11 + j*2));
+                    audit_sum = ZhCalc.add(audit_sum, changeSpreadSheet.getValue(i, startLimit + 4 + j*2));
                 }
-                changeSpreadSheet.setValue(changeSpreadSheet.getRowCount() - 1, 11 + j*2, audit_sum !== 0 ? audit_sum : null);
+                changeSpreadSheet.setValue(changeSpreadSheet.getRowCount() - 1, startLimit + 4 + j*2, audit_sum !== 0 ? audit_sum : null);
             }
         },
         deletePress: function (sheet) {

+ 402 - 48
app/public/js/change_information_set.js

@@ -94,12 +94,14 @@ let searchCodeList = [];
 $(document).ready(() => {
     const changeSpreadSetting = {
         cols: [
+            {title: '计价', colSpan: '1', rowSpan: '2', field: 'is_valuation', hAlign: 1, width: 50, cellType: 'checkbox', readOnly: 'readOnly.isEdit3'},
             {title: '清单编号', colSpan: '1', rowSpan: '2', field: 'code', hAlign: 0, width: 80, formatter: '@', readOnly: 'readOnly.isEdit2'},
             {title: '名称', colSpan: '1', rowSpan: '2', field: 'name', hAlign: 0, width: 120, formatter: '@', readOnly: 'readOnly.isEdit2'},
             {title: '单位', colSpan: '1', rowSpan: '2', field: 'unit', hAlign: 1, width: 60, formatter: '@', readOnly: 'readOnly.isEdit2', cellType: 'unit', comboItems: changeUnits, comboEdit: true},
             {title: '单价', colSpan: '1', rowSpan: '2', field: 'unit_price', hAlign: 2, width: 60, type: 'Number', readOnly: 'readOnly.isEdit2', getValue: 'getValue.unit_price'},
             {title: '变更部位', colSpan: '1', rowSpan: '2', field: 'bwmx', hAlign: 0, width: 120, formatter: '@', readOnly: 'readOnly.isEdit2'},
             {title: '变更详情', colSpan: '1', rowSpan: '2', field: 'detail', hAlign: 0, width: 120, formatter: '@', readOnly: false},
+            {title: '计量上限(%)', colSpan: '1', rowSpan: '2', field: 'delimit', hAlign: 2, width: 60, type: 'Number', readOnly: false, visible: openChangeState},
             {title: '原设计|数量', colSpan: '2|1', rowSpan: '1|1', field: 'oamount', hAlign: 2, width: 60, type: 'Number', readOnly: 'readOnly.isEdit', getValue: 'getValue.oamount'},
             {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'oa_tp', hAlign: 2, width: 80, type: 'Number', readOnly: true, getValue: 'getValue.oa_tp'},
             {title: '申请变更增(+)减(-)|数量', colSpan: '2|1', rowSpan: '1|1', field: 'camount', hAlign: 2, width: 60, type: 'Number', readOnly: false, getValue: 'getValue.camount'},
@@ -173,15 +175,20 @@ $(document).ready(() => {
             isEdit2: function (data) {
                 return !readOnly && (data.lid != 0 || (data.lid == 0 && _.findIndex(changeUsedData, { cbid: data.id }) !== -1));
             },
+            isEdit3: function (data) {
+                return !readOnly && _.findIndex(changeUsedData, { cbid: data.id }) === -1;
+            },
         },
     };
     // 数字只判断几个值(unit_price, oamount, camount)
-    const numField = ['unit_price', 'oamount', 'camount'];
+    const numField = ['unit_price', 'oamount'];
     const changeSpreadObj = {
         makeSjsFooter: function () {
             // 增加汇总行并设为锁定禁止编辑状态
             changeSpreadSheet.addRows(changeSpreadSheet.getRowCount(), 1);
             changeSpreadSheet.setValue(changeSpreadSheet.getRowCount() - 1, 0, '合计');
+            const cellType1 = new GC.Spread.Sheets.CellTypes.Text();
+            changeSpreadSheet.getCell(changeSpreadSheet.getRowCount() - 1, 0).cellType(cellType1);
             changeSpreadSheet.setStyle(changeSpreadSheet.getRowCount() - 1, -1, style1);
             changeSpreadObj.countSum();
         },
@@ -191,13 +198,13 @@ $(document).ready(() => {
                 cSum = 0,
                 cdSum = 0;
             for(var i = 0; i < rowCount - 1; i++){
-                oSum = ZhCalc.add(oSum, changeSpreadSheet.getValue(i, 7));
-                cSum = ZhCalc.add(cSum, changeSpreadSheet.getValue(i, 9));
-                cdSum = ZhCalc.add(cdSum, changeSpreadSheet.getValue(i, 11));
+                oSum = ZhCalc.add(oSum, changeSpreadSheet.getValue(i, startLimit));
+                cSum = ZhCalc.add(cSum, changeSpreadSheet.getValue(i, startLimit + 2));
+                cdSum = ZhCalc.add(cdSum, changeSpreadSheet.getValue(i, startLimit + 4));
             }
-            changeSpreadSheet.setValue(changeSpreadSheet.getRowCount() - 1, 7, oSum !== 0 ? oSum : null);
-            changeSpreadSheet.setValue(changeSpreadSheet.getRowCount() - 1, 9, cSum !== 0 ? cSum : null);
-            changeSpreadSheet.setValue(changeSpreadSheet.getRowCount() - 1, 11, cdSum !== 0 ? cdSum : null);
+            changeSpreadSheet.setValue(changeSpreadSheet.getRowCount() - 1, startLimit, oSum !== 0 ? oSum : null);
+            changeSpreadSheet.setValue(changeSpreadSheet.getRowCount() - 1, startLimit + 2, cSum !== 0 ? cSum : null);
+            changeSpreadSheet.setValue(changeSpreadSheet.getRowCount() - 1, startLimit + 4, cdSum !== 0 ? cdSum : null);
         },
         add: function () {
             let select = null;
@@ -236,6 +243,8 @@ $(document).ready(() => {
             postData(window.location.pathname + '/save', {type: 'batchadd', num, postData: select ? select.order : null}, function (result) {
                 if (result) {
                     changeList = result;
+                    SpreadJsObj.initSpreadSettingEvents(changeSpreadSetting, changeCol);
+                    SpreadJsObj.initSheet(changeSpreadSheet, changeSpreadSetting);
                     SpreadJsObj.loadSheetData(changeSpreadSheet, SpreadJsObj.DataType.Data, changeList);
                     changeSpreadObj.makeSjsFooter();
                     changeSpreadObj.resetXmjSpread();
@@ -393,6 +402,15 @@ $(document).ready(() => {
             }
             SpreadJsObj.loadSheetData(xmjSpread.getActiveSheet(), SpreadJsObj.DataType.Data, xmj);
         },
+        editStarting: function (e, info) {
+            const col = info.sheet.zh_setting.cols[info.col];
+            const select = SpreadJsObj.getSelectObject(info.sheet);
+            if (col.field === 'camount') {
+                if (select.camount_expr && select.camount_expr !== '') {
+                    info.sheet.getCell(info.row, info.col).text(select.camount_expr);
+                }
+            }
+        },
         selectionChanged: function (e, info) {
             const sel = info.sheet.getSelections()[0];
             const col = info.sheet.zh_setting.cols[sel.col];
@@ -400,6 +418,11 @@ $(document).ready(() => {
             if (col && col.field === 'del_list' && data && !_.find(changeUsedData, { cbid: data.id })) {
                 changeSpreadObj.del();
             }
+            if (col && col.field === 'camount' && data) {
+                $('#camount-expr').removeAttr('readonly').val(data.camount_expr ? data.camount_expr : data.camount);
+            } else {
+                $('#camount-expr').attr('readonly', true).val('');
+            }
             changeSpreadObj.resetXmjSpread(data);
             changeSpreadObj.refreshActn();
         },
@@ -439,34 +462,47 @@ $(document).ready(() => {
             if (info.sheet.zh_setting) {
                 const select = SpreadJsObj.getSelectObject(info.sheet);
                 const col = info.sheet.zh_setting.cols[info.col];
-                if (col.field === 'del_list') {
+                if (col.field === 'del_list' || col.field === 'is_valuation') {
                     return;
                 }
-                // 未改变值则不提交
                 let validText = is_numeric(info.editingText) && _.indexOf(numField, col.field) !== -1 ? parseFloat(info.editingText) : (info.editingText ? trimInvalidChar(info.editingText) : '');
-                const orgValue = select[col.field];
-                if (orgValue == validText || ((!orgValue || orgValue === '') && (validText === ''))) {
+                let orgValue;
+                if (col.field === 'camount') {
+                    orgValue = validText && validText !== ''
+                        ? (_.toNumber(validText) ? select.camount : select.camount_expr)
+                        : (select.camount_expr && select.camount_expr !== '' ? select.camount_expr : select.camount);
+                } else {
+                    orgValue = select[col.field];
+                }
+                if (orgValue == validText || ((!orgValue || orgValue === '') && (validText === '' || validText === null))) {
                     SpreadJsObj.reLoadRowData(info.sheet, info.row);
                     return;
                 }
+                // // 未改变值则不提交
+                // let validText = is_numeric(info.editingText) && _.indexOf(numField, col.field) !== -1 ? parseFloat(info.editingText) : (info.editingText ? trimInvalidChar(info.editingText) : '');
+                // const orgValue = select[col.field];
+                // if (orgValue == validText || ((!orgValue || orgValue === '') && (validText === ''))) {
+                //     SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                //     return;
+                // }
                 // 判断部分值是否输入的是数字判断和数据计算
-                if (col.type === 'Number') {
-                    if (isNaN(validText)) {
-                        toastr.error('不能输入其它非数字类型字符');
+                if(col.field === 'camount') {
+                    const exprQuantity = {
+                        expr: '',
+                        quantity: 0,
+                    };
+                    const [valid, msg] = changeSpreadObj._checkExpr(validText, exprQuantity);
+                    if (!valid) {
+                        toastr.error(msg);
                         SpreadJsObj.reLoadRowData(info.sheet, info.row);
                         return;
                     }
-                    if (col.field === 'unit_price') {
-                        validText = ZhCalc.round(validText, unitPriceUnit);
-                    } else {
-                        validText = ZhCalc.round(validText, findDecimal(select.unit)) || 0;
+                    if (isNaN(exprQuantity.quantity)) {
+                        toastr.error('不能输入其它非数字类型字符');
+                        SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                        return;
                     }
-                }
-                if (col.field === 'unit') {
-                    select.camount = ZhCalc.round(select.camount, findDecimal(validText)) || 0;
-                    select.oamount = ZhCalc.round(select.oamount, findDecimal(validText)) || 0;
-                }
-                if(col.field === 'camount') {
+                    validText = parseFloat(exprQuantity.quantity);
                     // 判断是否 正数必须大于等于限制值,负数必须小于等于限制值,否则无法更改
                     const usedInfo = _.find(changeUsedData, { cbid: select.id });
                     if (usedInfo && usedInfo.qty >= 0 && validText < usedInfo.qty) {
@@ -479,6 +515,41 @@ $(document).ready(() => {
                         return;
                     }
                     select.spamount = ZhCalc.round(validText, findDecimal(select.unit)) || 0;
+                    select.camount_expr = exprQuantity.expr;
+                }
+                if (col.type === 'Number') {
+                    if (isNaN(validText)) {
+                        toastr.error('不能输入其它非数字类型字符');
+                        SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                        return;
+                    }
+                    if (col.field === 'unit_price') {
+                        validText = ZhCalc.round(validText, unitPriceUnit);
+                    } else {
+                        validText = ZhCalc.round(validText, findDecimal(select.unit)) || 0;
+                    }
+                    if (col.field === 'delimit') {
+                        const reg = /^(\d{1,2}|100)$/;
+                        if (!(_.isNumber(validText) && reg.test(validText))) {
+                            toastr.error('计量上限默认值只能输入0-100之间的整数');
+                            SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                            return;
+                        }
+                        // 需要判断是否已调用,已调用的则不能低于原有值大小
+                        const usedInfo = _.find(changeUsedData, { cbid: select.id });
+                        if (usedInfo && usedInfo.qty) {
+                            const minLimit = Math.ceil(ZhCalc.mul(ZhCalc.div(usedInfo.qty, select.camount), 100));
+                            if (validText < minLimit) {
+                                toastr.error('计量上限值至少大于等于 ' + minLimit);
+                                SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                                return;
+                            }
+                        }
+                    }
+                }
+                if (col.field === 'unit') {
+                    select.camount = ZhCalc.round(select.camount, findDecimal(validText)) || 0;
+                    select.oamount = ZhCalc.round(select.oamount, findDecimal(validText)) || 0;
                 }
                 select[col.field] = validText;
                 // console.log(select);
@@ -505,7 +576,7 @@ $(document).ready(() => {
             };
             const range = info.cellRange;
             const sortData = info.sheet.zh_data || [];
-            if (info.cellRange.row + info.cellRange.rowCount > sortData.length) {
+            if (range.row + range.rowCount > sortData.length) {
                 toastMessageUniq(hint.cellError);
                 // SpreadJsObj.loadSheetData(materialSpread.getActiveSheet(), SpreadJsObj.DataType.Data, materialBillsData);
                 SpreadJsObj.reLoadSheetHeader(changeSpreadSheet);
@@ -513,7 +584,7 @@ $(document).ready(() => {
                 changeSpreadObj.makeSjsFooter();
                 return;
             }
-            if (sortData.length > 0 && range.col + range.colCount > 10) {
+            if (sortData.length > 0 && range.col + range.colCount > 14) {
                 toastMessageUniq(hint.cellError);
                 SpreadJsObj.reLoadSheetHeader(changeSpreadSheet);
                 SpreadJsObj.reLoadSheetData(changeSpreadSheet);
@@ -529,12 +600,12 @@ $(document).ready(() => {
                 const curRow = range.row + iRow;
                 // const materialData = JSON.parse(JSON.stringify(sortData[curRow]));
                 const cLData = { id: sortData[curRow].id };
-                const hintRow = range.rowCount > 1 ? curRow : '';
+                const hintRow = range.rowCount >= 1 ? curRow : '';
                 let sameCol = 0;
                 for (let iCol = 0; iCol < range.colCount; iCol++) {
                     const curCol = range.col + iCol;
                     let colSetting = info.sheet.zh_setting.cols[curCol];
-                    if ((needColAdd && curCol > 5) || (needCopyIgnore && curCol === 5)) { // 要判断是否已隐藏了变更详情,是则变更详情后面的curCol要+1
+                    if ((needColAdd && curCol > 6) || (needCopyIgnore && curCol === 6)) { // 要判断是否已隐藏了变更详情,是则变更详情后面的curCol要+1
                         const newCurCol = curCol + 1;
                         needColAdd = true;
                         colSetting = info.sheet.zh_setting.cols[newCurCol];
@@ -543,7 +614,14 @@ $(document).ready(() => {
 
                     let validText = info.sheet.getText(curRow, curCol);
                     validText = is_numeric(validText) && _.indexOf(numField, colSetting.field) !== -1 ? parseFloat(validText) : (validText ? trimInvalidChar(validText) : '');
-                    const orgValue = sortData[curRow][colSetting.field];
+                    let orgValue;
+                    if (colSetting.field === 'camount') {
+                        orgValue = validText && validText !== ''
+                            ? (_.toNumber(validText) ? sortData[curRow].camount : sortData[curRow].camount_expr)
+                            : (sortData[curRow].camount_expr && sortData[curRow].camount_expr !== '' ? sortData[curRow].camount_expr : sortData[curRow].camount);
+                    } else {
+                        orgValue = sortData[curRow][colSetting.field];
+                    }
                     if (orgValue == validText || ((!orgValue || orgValue === '') && (validText === ''))) {
                         sameCol++;
                         if (range.colCount === sameCol)  {
@@ -552,6 +630,37 @@ $(document).ready(() => {
                         continue;
                     }
 
+                    if(colSetting.field === 'camount') {
+                        const exprQuantity = {
+                            expr: '',
+                            quantity: 0,
+                        };
+                        const [valid, msg] = changeSpreadObj._checkExpr(validText, exprQuantity);
+                        if (!valid) {
+                            toastMessageUniq(getPasteHint(msg, hintRow));
+                            bPaste = false;
+                            continue;
+                        }
+                        if (isNaN(exprQuantity.quantity)) {
+                            toastMessageUniq(getPasteHint(hint.numberExpr, hintRow));
+                            bPaste = false;
+                            continue;
+                        }
+                        validText = parseFloat(exprQuantity.quantity);
+                        // 判断是否 正数必须大于等于限制值,负数必须小于等于限制值,否则无法更改
+                        const usedInfo = _.find(changeUsedData, { cbid: sortData[curRow].id });
+                        if (usedInfo && usedInfo.qty >= 0 && validText < usedInfo.qty) {
+                            toastr.error(hintRow ? '清单第' + (hintRow+1) + '行变更数值必须大于等于已调用值 ' + usedInfo.qty : '清单变更数值必须大于等于已调用值 ' + usedInfo.qty);
+                            bPaste = false;
+                            continue;
+                        } else if (usedInfo && usedInfo.qty < 0 && validText > usedInfo.qty) {
+                            toastr.error(hintRow ? '清单第' + (hintRow+1) + '行变更数值必须小于等于已调用值 ' + usedInfo.qty : '清单变更数值必须小于等于已调用值 ' + usedInfo.qty);
+                            bPaste = false;
+                            continue;
+                        }
+                        cLData.camount_expr = exprQuantity.expr;
+                        sortData[curRow].camount_expr = exprQuantity.expr;
+                    }
                     if (colSetting.type === 'Number') {
                         if (isNaN(validText)) {
                             toastMessageUniq(getPasteHint(hint.numberExpr, hintRow));
@@ -563,18 +672,23 @@ $(document).ready(() => {
                         } else {
                             validText = ZhCalc.round(validText, findDecimal(sortData[curRow].unit)) || 0;
                         }
-                        if(colSetting.field === 'camount') {
-                            // 判断是否 正数必须大于等于限制值,负数必须小于等于限制值,否则无法更改
-                            const usedInfo = _.find(changeUsedData, { cbid: sortData[curRow].id });
-                            if (usedInfo && usedInfo.qty >= 0 && validText < usedInfo.qty) {
-                                toastr.error(hintRow ? '清单' + (hintRow+1) + '行变更数值必须大于等于已调用值 ' + usedInfo.qty : '清单变更数值必须大于等于已调用值 ' + usedInfo.qty);
-                                bPaste = false;
-                                continue;
-                            } else if (usedInfo && usedInfo.qty < 0 && validText > usedInfo.qty) {
-                                toastr.error(hintRow ? '清单' + (hintRow+1) + '行变更数值必须小于等于已调用值 ' + usedInfo.qty : '清单变更数值必须小于等于已调用值 ' + usedInfo.qty);
+                        if (colSetting.field === 'delimit') {
+                            const reg = /^(\d{1,2}|100)$/;
+                            if (!(_.isNumber(validText) && reg.test(validText))) {
+                                toastr.error('清单第' + (hintRow+1) + '行计量上限默认值只能粘贴0-100之间的整数');
                                 bPaste = false;
                                 continue;
                             }
+                            // 需要判断是否已调用,已调用的则不能低于原有值大小
+                            const usedInfo = _.find(changeUsedData, { cbid: sortData[curRow].id });
+                            if (usedInfo && usedInfo.qty) {
+                                const minLimit = Math.ceil(ZhCalc.mul(ZhCalc.div(usedInfo.qty, sortData[curRow].camount), 100));
+                                if (validText < minLimit) {
+                                    toastr.error('清单第' + (hintRow+1) + '行计量上限值至少大于等于 ' + minLimit);
+                                    bPaste = false;
+                                    continue;
+                                }
+                            }
                         }
                     }
                     let unitdecimal = validText;
@@ -615,10 +729,159 @@ $(document).ready(() => {
                 return;
             });
         },
+        buttonClicked: function (e, info) {
+            if (info.sheet.zh_setting) {
+                const select = SpreadJsObj.getSelectObject(info.sheet);
+                const col = info.sheet.zh_setting.cols[info.col];
+                if (col.field === 'is_valuation') {
+                    if (info.sheet.isEditing()) {
+                        info.sheet.endEdit(true);
+                    }
+                    if (_.findIndex(changeUsedData, { cbid: select.id }) !== -1) {
+                        return;
+                    }
+                    select.is_valuation = info.sheet.getValue(info.row, info.col) ? 0 : 1;
+                    delete select.waitingLoading;
+                    // 更新至服务器
+                    postData(window.location.pathname + '/save', { type:'update', updateData: select }, function (result) {
+                        changeList.splice(info.row, 1, select);
+                        SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                    }, function () {
+                        select.is_valuation = info.sheet.getValue(info.row, info.col) ? 1 : 0;
+                        SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                    });
+                }
+            }
+        },
         valueChanged(e, info) {
             // 防止ctrl+z撤销数据
             SpreadJsObj.reLoadRowData(info.sheet, info.row);
-        }
+        },
+        _checkExprValid(expr) {
+            if (!expr) return [true, null];
+            const param = [];
+            let num = '', base = '';
+            for (let i = 0, iLen = expr.length; i < iLen; i++) {
+                if (/^[\d\.%]+/.test(expr[i])) {
+                    if (base !== '') {
+                        param.push({type: 'base', value: base});
+                        base = '';
+                    }
+                    num = num + expr[i];
+                } else if (expr[i] === '(') {
+                    if (num !== '') {
+                        param.push({type: 'num', value: num});
+                        num = '';
+                    }
+                    if (base !== '') {
+                        param.push({type: 'base', value: base});
+                        base = '';
+                    }
+                    param.push({type: 'left', value: '('});
+                } else if (expr[i] === ')') {
+                    if (num !== '') {
+                        param.push({type: 'num', value: num});
+                        num = '';
+                    }
+                    if (base !== '') {
+                        param.push({type: 'base', value: base});
+                        base = '';
+                    }
+                    param.push({type: 'right', value: ')'});
+                } else if (/^[\+\-*\/]/.test(expr[i])) {
+                    if (num !== '') {
+                        param.push({type: 'num', value: num});
+                        num = '';
+                    }
+                    if (base !== '') {
+                        param.push({type: 'base', value: base});
+                        base = '';
+                    }
+                    param.push({type: 'calc', value: expr[i]});
+                } else {
+                    return [false, '输入的表达式含有非法字符: ' + expr[i]];
+                }
+            }
+            if (num !== '') {
+                param.push({type: 'num', value: num});
+                num = '';
+            }
+            if (base !== '') {
+                param.push({type: 'base', value: base});
+                base = '';
+            }
+            if (param.length === 0) return true;
+            if (param.length > 1) {
+                if (param[0].value === '-') {
+                    param[1].value = '-' + param[1];
+                }
+                param.unshift();
+            }
+            const iLen = param.length;
+            let iLeftCount = 0, iRightCount = 0;
+            for (const [i, p] of param.entries()) {
+                if (p.type === 'calc') {
+                    if (i === 0 || i === iLen - 1)
+                        return [false, '输入的表达式非法:计算符号' + p.value + '前后应有数字'];
+                }
+                if (p.type === 'num') {
+                    num = p.value.replace('%', '');
+                    if (p.value.length - num.length > 1)
+                        return [false, '输入的表达式非法:' + p.value + '不是一个有效的数字'];
+                    num = _.toNumber(num);
+                    if (num === undefined || num === null || _.isNaN(num))
+                        return [false, '输入的表达式非法:' + p.value + '不是一个有效的数字'];
+                    if (i > 0) {
+                        if (param[i - 1].type !== 'calc' && param[i - 1].type !== 'left') {
+                            return [false, '输入的表达式非法:' + p.value + '前应有运算符'];
+                        } else if (param[i - 1].value === '/' && num === 0) {
+                            return [false, '输入的表达式非法:请勿除0'];
+                        }
+                    }
+                }
+                if (p.type === 'base') {
+                    if (i > 0 && (param[i - 1].type === 'num' || param[i - 1].type === 'right'))
+                        return [false, '输入的表达式非法:' + p.value + '前应有运算符'];
+                }
+                if (p.type === 'left') {
+                    iLeftCount += 1;
+                    if (i !== 0 && param[i-1].type !== 'calc')
+                        return [false, '输入的表达式非法:(前应有运算符'];
+                }
+                if (p.type === 'right') {
+                    iRightCount += 1;
+                    if (i !== iLen - 1 && param[i+1].type !== 'calc')
+                        return [false, '输入的表达式非法:)后应有运算符'];
+                    if (iRightCount > iLeftCount)
+                        return [false, '输入的表达式非法:")"前无对应的"("'];
+                }
+            }
+            if (iLeftCount > iRightCount)
+                return [false, '输入的表达式非法:"("后无对应的")"'];
+            return [true, ''];
+        },
+        _checkExpr: function (text, data) {
+            if (text) {
+                const num = _.toNumber(text);
+                if (num) {
+                    data.quantity = num;
+                    data.expr = '';
+                } else {
+                    const expr = $.trim(text).replace('\t', '').replace('=', '').toLowerCase();
+                    const [valid, msg] = this._checkExprValid(expr);
+                    if (!valid) return [valid, msg];
+                    data.expr = expr;
+                    data.quantity = ZhCalc.calcExpr.calcExprStrRpn(expr.replace('%', '/100'));
+                    // const ce = new CalcEvalMin();
+                    // data.quantity = ce.eval(expr);
+                    // console.log(data.quantity);
+                }
+            } else {
+                data.quantity = 0;
+                data.expr = '';
+            }
+            return [true, ''];
+        },
     };
 
     const preUrl = window.location.pathname.split('/').slice(0, 4).join('/');
@@ -736,18 +999,20 @@ $(document).ready(() => {
         $('#up-move').click(changeSpreadObj.upMove);
         $('#down-move').click(changeSpreadObj.downMove);
         changeSpread.bind(spreadNS.Events.EditEnded, changeSpreadObj.editEnded);
+        changeSpread.bind(spreadNS.Events.EditStarting, changeSpreadObj.editStarting);
         changeSpread.bind(spreadNS.Events.SelectionChanged, changeSpreadObj.selectionChanged);
         changeSpread.bind(spreadNS.Events.ClipboardPasted, changeSpreadObj.clipboardPasted);
         changeSpread.bind(spreadNS.Events.ValueChanged, changeSpreadObj.valueChanged);
+        changeSpread.bind(spreadNS.Events.ButtonClicked, changeSpreadObj.buttonClicked);
         SpreadJsObj.addDeleteBind(changeSpread, changeSpreadObj.deletePress);
-        changeSpreadSheet.getCell(-1, 12).foreColor('#dc3545');
+        changeSpreadSheet.getCell(-1, startLimit + 5).foreColor('#dc3545');
         const delCommand = {
             canUndo: false,
             execute: function (context, options, isUndo) {
                 const Commands = GC.Spread.Sheets.Commands;
                 const sel = changeSpreadSheet.getSelections()[0];
                 const col = changeSpreadSheet.zh_setting.cols[sel.col];
-                if (col && col.field !== 'changed_tp') {
+                if (col && col.field !== 'sa_tp') {
                     isUndo = true;
                 }
                 if (isUndo) {
@@ -837,7 +1102,7 @@ $(document).ready(() => {
                     name: '添加空白清单',
                     icon: 'fa-sign-in',
                     visible: function () {
-                        return changeOrder === 0;
+                        return changeOrder === 0 && openChangeWhiteList;
                     },
                     callback: function (key, opt) {
                         changeSpreadObj.add(changeSpreadSheet);
@@ -849,7 +1114,7 @@ $(document).ready(() => {
                     value: '2',
                     icon: 'fa-sign-in',
                     visible: function () {
-                        return changeOrder === 0;
+                        return changeOrder === 0 && openChangeWhiteList;
                     },
                     batchInsert: function (obj, root) {
                         if (_.toNumber(obj.value) > _.toNumber(obj.max)) {
@@ -890,7 +1155,7 @@ $(document).ready(() => {
                     name: '插入空白清单',
                     icon: 'fa-sign-in',
                     visible: function () {
-                        return changeOrder === 1;
+                        return changeOrder === 1 && openChangeWhiteList;
                     },
                     // disabled: function (key, opt) {
                     //     const select = SpreadJsObj.getSelectObject(changeSpreadSheet);
@@ -913,7 +1178,7 @@ $(document).ready(() => {
                     value: '2',
                     icon: 'fa-sign-in',
                     visible: function () {
-                        return changeOrder === 1;
+                        return changeOrder === 1 && openChangeWhiteList;
                     },
                     // disabled: function (key, opt) {
                     //     const select = SpreadJsObj.getSelectObject(changeSpreadSheet);
@@ -974,6 +1239,70 @@ $(document).ready(() => {
                 },
             }
         });
+
+        // 表达式判断
+        $('#camount-expr').change(function () {
+            if (this.readOnly) return;
+            let validText = $(this).val();
+            const sel = changeSpreadSheet.getSelections()[0];
+            const row = sel ? sel.row : -1;
+            const col = changeSpreadSheet.zh_setting.cols[sel.col];
+            if (row === -1 || !(col && col.field === 'camount')) return;
+            const select = SpreadJsObj.getSelectObject(changeSpreadSheet);
+            if (!select) return;
+            validText = is_numeric(validText) ? parseFloat(validText) : (validText ? trimInvalidChar(validText) : '');
+            let orgValue = validText && validText !== ''
+                    ? (_.toNumber(validText) ? select.camount : select.camount_expr)
+                    : (select.camount_expr && select.camount_expr !== '' ? select.camount_expr : select.camount);
+            const orgExprValue = _.clone(select.camount_expr);
+            if (orgValue == validText || ((!orgValue || orgValue === '') && (validText === '' || validText === null))) {
+                return;
+            }
+            const exprQuantity = {
+                expr: '',
+                quantity: 0,
+            };
+            const [valid, msg] = changeSpreadObj._checkExpr(validText, exprQuantity);
+            if (!valid) {
+                toastr.error(msg);
+                $(this).val(select.camount_expr ? select.camount_expr : select.camount);
+                return;
+            }
+            if (isNaN(exprQuantity.quantity)) {
+                toastr.error('不能输入其它非数字类型字符');
+                $(this).val(select.camount_expr ? select.camount_expr : select.camount);
+                return;
+            }
+            validText = parseFloat(exprQuantity.quantity);
+            // 判断是否 正数必须大于等于限制值,负数必须小于等于限制值,否则无法更改
+            const usedInfo = _.find(changeUsedData, { cbid: select.id });
+            if (usedInfo && usedInfo.qty >= 0 && validText < usedInfo.qty) {
+                toastr.error('清单变更数值必须大于等于已调用值 ' + usedInfo.qty);
+                $(this).val(select.camount_expr ? select.camount_expr : select.camount);
+                return;
+            } else if (usedInfo && usedInfo.qty < 0  && validText > usedInfo.qty) {
+                toastr.error('清单变更数值必须小于等于已调用值 ' + usedInfo.qty);
+                $(this).val(select.camount_expr ? select.camount_expr : select.camount);
+                return;
+            }
+            select.spamount = ZhCalc.round(validText, findDecimal(select.unit)) || 0;
+            select.camount_expr = exprQuantity.expr;
+            select.camount = validText;
+            // console.log(select);
+            delete select.waitingLoading;
+
+            // 更新至服务器
+            postData(window.location.pathname + '/save', { type:'update', updateData: select }, function (result) {
+                changeList.splice(row, 1, select);
+                SpreadJsObj.reLoadRowData(changeSpreadSheet, row);
+                changeSpreadObj.countSum();
+            }, function () {
+                select.camount = orgValue;
+                select.camount_expr = orgExprValue;
+                select.spamount = orgValue;
+                SpreadJsObj.reLoadRowData(changeSpreadSheet, row);
+            });
+        });
     }
 
     // 清单选中和移除
@@ -986,7 +1315,7 @@ $(document).ready(() => {
         const data_charu = $(this).attr('data-charu') ? $(this).attr('data-charu').split('$#$') : [];
         const isDeal = $(this).data('gcl') !== undefined ? true : false;
         let codeHtml = '<tr quantity="'+ $(this).children('td').eq(5).text() +'" gcl_id="" mx_id=""><td class="text-center">1</td><td colspan="7" class="colspan_1">&nbsp;</td><td class="colspan_2 text-center"><input type="checkbox"></td></tr>';
-        console.log(isDeal, isCheck);
+        // console.log(isDeal, isCheck);
         if (isDeal) {
             const lid = $(this).data('lid');
             let gcl = _.find(gclGatherData, function (item) {
@@ -1128,7 +1457,10 @@ $(document).ready(() => {
         postData(window.location.pathname + '/save', { type:'ledger_list', updateData: newLedgerList, postData: select ? select.order : null }, function (result) {
             changeList = result.changeList;
             changeUsedData = result.usedList;
+            SpreadJsObj.initSpreadSettingEvents(changeSpreadSetting, changeCol);
+            SpreadJsObj.initSheet(changeSpreadSheet, changeSpreadSetting);
             SpreadJsObj.loadSheetData(changeSpreadSheet, SpreadJsObj.DataType.Data, changeList);
+            // SpreadJsObj.reLoadSheetData(changeSpreadSheet);
             changeSpreadObj.makeSjsFooter();
             const select = SpreadJsObj.getSelectObject(changeSpreadSheet);
             changeSpreadObj.resetXmjSpread(select);
@@ -1527,7 +1859,7 @@ $(document).ready(() => {
         // 更新至服务器
         postData(window.location.pathname + '/save', { type:'info', updateData: changeInfo }, function (result) {
             $('.reduction-code').attr('data-code', $('input[name="code"]').val());
-            toastr.success(result);
+            toastr.success(result ? result.msg : '');
             $('#show-save-btn').hide();
             $('#sp-btn').show();
             $('.title-main').removeClass('bg-warning');
@@ -1536,6 +1868,18 @@ $(document).ready(() => {
             changeInfo.expr = changeInfo.expr.replace(/[\r\n]/g, '<br>');
             changeInfo.memo = changeInfo.memo.replace(/[\r\n]/g, '<br>');
             back_changeInfo = Object.assign({}, changeInfo);
+            if (result.changeList) {
+                deLimit = result.deLimit;
+                changeList = result.changeList;
+                SpreadJsObj.initSpreadSettingEvents(changeSpreadSetting, changeCol);
+                SpreadJsObj.initSheet(changeSpreadSheet, changeSpreadSetting);
+                SpreadJsObj.loadSheetData(changeSpreadSheet, SpreadJsObj.DataType.Data, changeList);
+                // SpreadJsObj.reLoadSheetData(changeSpreadSheet);
+                changeSpreadObj.makeSjsFooter();
+                const select = SpreadJsObj.getSelectObject(changeSpreadSheet);
+                changeSpreadObj.resetXmjSpread(select);
+                changeSpreadObj.refreshActn();
+            }
         });
         return false;
     });
@@ -1567,7 +1911,10 @@ $(document).ready(() => {
                     $('.order_text').text('插入');
                     changeOrder = newChangeOrder;
                 }
-                changeList = result;
+                changeList = result.changeList;
+                changeUsedData = result.usedList;
+                SpreadJsObj.initSpreadSettingEvents(changeSpreadSetting, changeCol);
+                SpreadJsObj.initSheet(changeSpreadSheet, changeSpreadSetting);
                 SpreadJsObj.loadSheetData(changeSpreadSheet, SpreadJsObj.DataType.Data, changeList);
                 changeSpreadObj.makeSjsFooter();
                 changeSpreadObj.resetXmjSpread(SpreadJsObj.getSelectObject(changeSpreadSheet));
@@ -1929,6 +2276,7 @@ function remakeChangeSpread(cOrder = changeOrder) {
                 unit_price: price,
                 oamount,
                 camount: scnum,
+                camount_expr: '',
                 detail: '',
                 lid,
                 xmj_code,
@@ -1938,6 +2286,8 @@ function remakeChangeSpread(cOrder = changeOrder) {
                 xmj_fxgc,
                 gcl_id,
                 mx_id,
+                is_valuation: 1,
+                delimit: deLimit,
             };
             const radionInfo = changeList.find(function (info) {
                 //return info.code === code && (info.lid == lid || parseInt(info.lid) === parseInt(lindex)) && gcl_id == info.gcl_id && (info.bwmx === bwmx || (info.bwmx === xmj_jldy && info.mx_id && info.mx_id === mx_id)) && parseInt(info.oamount) === parseInt(oamount);
@@ -1945,7 +2295,10 @@ function remakeChangeSpread(cOrder = changeOrder) {
             });
             if (radionInfo) {
                 trlist.camount = radionInfo.camount;
+                trlist.camount_expr = radionInfo.camount_expr;
                 trlist.detail = radionInfo.detail;
+                trlist.is_valuation = radionInfo.is_valuation;
+                trlist.delimit = radionInfo.delimit;
             }
             newTableList.push(trlist);
         }
@@ -2016,6 +2369,7 @@ function changeFormRemake() {
     $('#change_form select[name="quality"]').val(changeInfo.quality);
     $('#change_form select[name="company"]').val(changeInfo.company);
     $('#change_form input[name="charge"][value="'+ changeInfo.charge +'"]').prop('checked', true);
+    $('#change_form input[name="state"][value="'+ changeInfo.state +'"]').prop('checked', true);
     $('#change_form input[name="type[]"]').prop('checked', false);
     const typecheck = changeInfo.type.split(',');
     for (const type of typecheck) {

+ 27 - 17
app/public/js/change_information_show.js

@@ -10,12 +10,14 @@
 $(document).ready(() => {
     const changeSpreadSetting = {
         cols: [
+            {title: '计价', colSpan: '1', rowSpan: '2', field: 'is_valuation', hAlign: 1, width: 50, cellType: 'checkbox', readOnly: true},
             {title: '清单编号', colSpan: '1', rowSpan: '2', field: 'code', hAlign: 0, width: 80, formatter: '@'},
             {title: '名称', colSpan: '1', rowSpan: '2', field: 'name', hAlign: 0, width: 120, formatter: '@'},
             {title: '单位', colSpan: '1', rowSpan: '2', field: 'unit', hAlign: 1, width: 60, formatter: '@'},
             {title: '单价', colSpan: '1', rowSpan: '2', field: 'unit_price', hAlign: 2, width: 60, type: 'Number', getValue: 'getValue.unit_price'},
             {title: '变更部位', colSpan: '1', rowSpan: '2', field: 'bwmx', hAlign: 0, width: 120, formatter: '@'},
             {title: '变更详情', colSpan: '1', rowSpan: '2', field: 'detail', hAlign: 0, width: 120, formatter: '@'},
+            {title: '计量上限(%)', colSpan: '1', rowSpan: '2', field: 'delimit', hAlign: 2, width: 60, formatter: '@', visible: openChangeState},
             {title: '原设计|数量', colSpan: '2|1', rowSpan: '1|1', field: 'oamount', hAlign: 2, width: 60, type: 'Number', getValue: 'getValue.oamount'},
             {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'oa_tp', hAlign: 2, width: 80, type: 'Number', getValue: 'getValue.oa_tp'},
             {title: '申请变更增(+)减(-)|数量', colSpan: '2|1', rowSpan: '1|1', field: 'camount', hAlign: 2, width: 60, type: 'Number', getValue: 'getValue.camount'},
@@ -101,12 +103,13 @@ $(document).ready(() => {
             },
         },
     };
-
     const changeSpreadObj = {
         makeSjsFooter: function () {
             // 增加汇总行并设为锁定禁止编辑状态
             changeSpreadSheet.addRows(changeSpreadSheet.getRowCount(), 1);
             changeSpreadSheet.setValue(changeSpreadSheet.getRowCount() - 1, 0, '合计');
+            const cellType1 = new GC.Spread.Sheets.CellTypes.Text();
+            changeSpreadSheet.getCell(changeSpreadSheet.getRowCount() - 1, 0).cellType(cellType1);
             changeSpreadSheet.setStyle(changeSpreadSheet.getRowCount() - 1, -1, style1);
             changeSpreadObj.countSum();
         },
@@ -121,7 +124,14 @@ $(document).ready(() => {
             SpreadJsObj.loadSheetData(xmjSpread.getActiveSheet(), SpreadJsObj.DataType.Data, xmj);
         },
         selectionChanged: function (e, info) {
+            const sel = info.sheet.getSelections()[0];
+            const col = info.sheet.zh_setting.cols[sel.col];
             const data = SpreadJsObj.getSelectObject(info.sheet);
+            if (col && col.field === 'camount' && data) {
+                $('#camount-expr').val(data.camount_expr ? data.camount_expr : data.camount);
+            } else {
+                $('#camount-expr').val('');
+            }
             changeSpreadObj.resetXmjSpread(data);
         },
         setAuditValue: function () {
@@ -130,11 +140,11 @@ $(document).ready(() => {
             for (const j in aidList) {
                 for(let i = 0; i <= rowCount - 1; i++){
                     const data = {
-                        unit_price: changeSpreadSheet.getValue(i, 3),
-                        amount: parseFloat(changeSpreadSheet.getValue(i, 12 + parseInt(j)*2)),
+                        unit_price: changeSpreadSheet.getValue(i, 4),
+                        amount: parseFloat(changeSpreadSheet.getValue(i, startLimit + 5 + parseInt(j)*2)),
                     };
                     const sum = ZhCalc.round(ZhCalc.mul(data.unit_price, data.amount), totalPriceUnit);
-                    changeSpreadSheet.setValue(i, 13 + j*2, sum !== 0 ? sum : null);
+                    changeSpreadSheet.setValue(i, startLimit + 6 + j*2, sum !== 0 ? sum : null);
                 }
             }
         },
@@ -145,31 +155,31 @@ $(document).ready(() => {
                 sSum = 0,
                 cdSum = 0;
             for(let i = 0; i < rowCount - 1; i++){
-                oSum = ZhCalc.add(oSum, changeSpreadSheet.getValue(i, 7));
-                cSum = ZhCalc.add(cSum, changeSpreadSheet.getValue(i, 9));
-                sSum = ZhCalc.add(sSum, changeSpreadSheet.getValue(i, 11));
-                cdSum = ZhCalc.add(cdSum, changeSpreadSheet.getValue(i, (13 + aidList.length*2)));
+                oSum = ZhCalc.add(oSum, changeSpreadSheet.getValue(i, startLimit));
+                cSum = ZhCalc.add(cSum, changeSpreadSheet.getValue(i, startLimit + 2));
+                sSum = ZhCalc.add(sSum, changeSpreadSheet.getValue(i, startLimit + 4));
+                cdSum = ZhCalc.add(cdSum, changeSpreadSheet.getValue(i, (startLimit + 6 + aidList.length*2)));
             }
-            changeSpreadSheet.setValue(changeSpreadSheet.getRowCount() - 1, 7, oSum !== 0 ? oSum : null);
-            changeSpreadSheet.setValue(changeSpreadSheet.getRowCount() - 1, 9, cSum !== 0 ? cSum : null);
-            changeSpreadSheet.setValue(changeSpreadSheet.getRowCount() - 1, 11, sSum !== 0 ? sSum : null);
-            changeSpreadSheet.setValue(changeSpreadSheet.getRowCount() - 1, (13 + aidList.length*2), cdSum !== 0 ? cdSum : null);
+            changeSpreadSheet.setValue(changeSpreadSheet.getRowCount() - 1, startLimit, oSum !== 0 ? oSum : null);
+            changeSpreadSheet.setValue(changeSpreadSheet.getRowCount() - 1, startLimit + 2, cSum !== 0 ? cSum : null);
+            changeSpreadSheet.setValue(changeSpreadSheet.getRowCount() - 1, startLimit + 4, sSum !== 0 ? sSum : null);
+            changeSpreadSheet.setValue(changeSpreadSheet.getRowCount() - 1, (startLimit + 6 + aidList.length*2), cdSum !== 0 ? cdSum : null);
             // 用户的数据合计
             for (const j in aidList) {
                 let audit_sum = 0;
                 for(let i = 0; i < rowCount - 1; i++){
-                    audit_sum = ZhCalc.add(audit_sum, changeSpreadSheet.getValue(i, 13 + j*2));
+                    audit_sum = ZhCalc.add(audit_sum, changeSpreadSheet.getValue(i, startLimit + 6 + j*2));
                 }
-                changeSpreadSheet.setValue(changeSpreadSheet.getRowCount() - 1, 13 + j*2, audit_sum !== 0 ? audit_sum : null);
+                changeSpreadSheet.setValue(changeSpreadSheet.getRowCount() - 1, startLimit + 6 + j*2, audit_sum !== 0 ? audit_sum : null);
             }
         },
         showHideAudit: function (show = false) {
             const count = changeSpreadSetting.cols.length;
-            for (let i = 12; i < count - 2; i++) {
+            for (let i = startLimit + 5; i < count - 2; i++) {
                 changeSpreadSheet.setColumnVisible(i, show, GC.Spread.Sheets.SheetArea.viewport);
             }
-            changeSpreadSheet.setColumnVisible(10, !show, GC.Spread.Sheets.SheetArea.viewport);
-            changeSpreadSheet.setColumnVisible(11, !show, GC.Spread.Sheets.SheetArea.viewport);
+            changeSpreadSheet.setColumnVisible(startLimit + 3, !show, GC.Spread.Sheets.SheetArea.viewport);
+            changeSpreadSheet.setColumnVisible(startLimit + 4, !show, GC.Spread.Sheets.SheetArea.viewport);
         }
     };
 

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

@@ -227,8 +227,8 @@ $(document).ready(() => {
             changeSpreadSetting.cols.push(newColTp);
         }
     }
-    changeSpreadSetting.cols.push({title: '备注1', colSpan: '1', rowSpan: '2', field: 'ex_memo1', hAlign: 0, width: 110, formatter: '@', readOnly: 'readOnly.isEdit2'});
-    changeSpreadSetting.cols.push({title: '备注2', colSpan: '1', rowSpan: '2', field: 'ex_memo2', hAlign: 0, width: 110, formatter: '@', readOnly: 'readOnly.isEdit2'});
+    changeSpreadSetting.cols.push({title: '备注1', colSpan: '1', rowSpan: '2', field: 'ex_memo1', hAlign: 0, width: 110, formatter: '@', readOnly: 'readOnly.isEdit2', cellType: 'ellipsisAutoTip', scrollHeightClass: '.sjs-height-0'});
+    changeSpreadSetting.cols.push({title: '备注2', colSpan: '1', rowSpan: '2', field: 'ex_memo2', hAlign: 0, width: 110, formatter: '@', readOnly: 'readOnly.isEdit2', cellType: 'ellipsisAutoTip', scrollHeightClass: '.sjs-height-0'});
 
     const changeCol = {
         getValue: {

+ 1 - 0
app/public/js/change_revise.js

@@ -102,6 +102,7 @@ $(document).ready(() => {
     };
     sjsSettingObj.setFxTreeStyle(billsSpreadSetting, sjsSettingObj.FxTreeStyle.jz);
     if (thousandth) sjsSettingObj.setTpThousandthFormat(billsSpreadSetting);
+    sjsSettingObj.setNodeTypeCol(billsSpreadSetting.cols, [{field: 'node_type'}]);
     SpreadJsObj.initSpreadSettingEvents(billsSpreadSetting, billsCol);
     SpreadJsObj.initSheet(billsSheet, billsSpreadSetting);
     const posSpread = SpreadJsObj.createNewSpread($('#pos-spread')[0]);

+ 19 - 1
app/public/js/file_list.js

@@ -31,7 +31,7 @@ $(document).ready(() => {
                 html.push('<td width="20%" class="in-' + node.tree_level + '">');
                 if (node.is_folder) {
                     if (node.children.length > 0) {
-                        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="收起" id="'+ node.id +'"><i class="fa fa-minus-square-o"></i></span> <i class="fa fa-folder-o"></i> ', node.name);
                     } else {
                         html.push('<i class="fa fa-folder-o"></i> ', node.name);
                     }
@@ -83,6 +83,24 @@ $(document).ready(() => {
         };
 
         Utils.reloadTable();
+        $('body').on('click', '.fold-switch', function() {
+            const id = this.getAttribute('id');
+            const node = projectTree.getItems(id);
+            projectTree.setExpanded(node, !node.expanded);
+            const posterity = projectTree.getPosterity(node);
+            if (node.expanded) {
+                $(this).html(`<i class="fa fa-minus-square-o"></i>`);
+            } else {
+                $(this).html(`<i class="fa fa-plus-square-o"></i>`);
+            }
+            for (const p of posterity) {
+                if (p.visible) {
+                    $(`tr[tree_id=${p.id}]`).show();
+                } else {
+                    $(`tr[tree_id=${p.id}]`).hide();
+                }
+            }
+        });
         return { projectTree, TableObj, ...Utils };
     })({
         treeSetting: { id: 'id', pid: 'tree_pid', level: 'tree_level', order: 'tree_order', rootId: '-1' },

+ 10 - 5
app/public/js/ledger.js

@@ -2529,7 +2529,7 @@ $(document).ready(function() {
                  * @param sheet
                  */
                 deletePress: function (sheet) {
-                    if (!sheet.zh_setting || readOnly || sheet.zh_setting.readOnly) return;
+                    if (!sheet.zh_setting || sheet.zh_setting.readOnly) return;
 
                     const sortData = sheet.zh_data;
                     const datas = [];
@@ -2569,7 +2569,7 @@ $(document).ready(function() {
                     }
                 },
                 delete: function (sheet) {
-                    if (!sheet.zh_setting || readOnly || sheet.zh_setting.readOnly) return;
+                    if (!sheet.zh_setting || sheet.zh_setting.readOnly) return;
 
                     const sortData = sheet.zh_data;
                     const datas = [];
@@ -2805,9 +2805,14 @@ $(document).ready(function() {
                                     }
                                 }
                                 if (datas.length > 0) {
-                                    postData(window.location.pathname + '/update', {postType: 'update', postData: datas}, function (result) {
-                                        const refreshNode = ledgerTree.loadPostData(result);
-                                        treeOperationObj.refreshTree(ledgerSpread.getActiveSheet(), refreshNode);
+                                    doAfterHint({
+                                        fun: function() {
+                                            postData(window.location.pathname + '/update', {postType: 'update', postData: datas}, function (result) {
+                                                const refreshNode = ledgerTree.loadPostData(result);
+                                                treeOperationObj.refreshTree(ledgerSpread.getActiveSheet(), refreshNode);
+                                            });
+                                        },
+                                        hint: '此操作会替换台账里所有同编号、名称、单位的清单,请谨慎操作',
                                     });
                                 } else {
                                     toastr.warning('没有可应用的清单。');

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

@@ -1194,7 +1194,7 @@ $(document).ready(() => {
         {title: '清单编号', colSpan: '1', rowSpan: '2', field: 'code', hAlign: 0, width: 100, formatter: '@'},
         {title: '名称', colSpan: '1', rowSpan: '2', field: 'name', hAlign: 0, width: 150, formatter: '@'},
         {title: '单位', colSpan: '1', rowSpan: '2', field: 'unit', hAlign: 1, width: 70, formatter: '@'},
-        {title: '本期应耗', colSpan: '1', rowSpan: '2', field: 'quantity', hAlign: 0, width: 100, formatter: '@'},
+        {title: '本期应耗', colSpan: '1', rowSpan: '2', field: 'quantity', hAlign: 2, width: 100, formatter: '@'},
     ];
     const materialSourceSpread = SpreadJsObj.createNewSpread($('#material-source-spread')[0]);
     const materialSourceSpreadSetting = {

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

@@ -875,7 +875,7 @@ $(document).ready(() => {
                         const [valid, msg] = this._checkExprValid(expr);
                         if (!valid) return [valid, msg];
                         data.expr = expr;
-                        data.quantity = ZhCalc.calcExpr.calcExprStrRpn(expr);
+                        data.quantity = ZhCalc.calcExpr.calcExprStrRpn(expr.replace('%', '/100'));
                     }
                 } else {
                     data.quantity = 0;

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

@@ -1518,7 +1518,7 @@ $(document).ready(() => {
                         const [valid, msg] = this._checkExprValid(expr);
                         if (!valid) return [valid, msg];
                         data.expr = expr;
-                        data.quantity = ZhCalc.calcExpr.calcExprStrRpn(expr);
+                        data.quantity = ZhCalc.calcExpr.calcExprStrRpn(expr.replace('%', '/100'));
                         // const ce = new CalcEvalMin();
                         // data.quantity = ce.eval(expr);
                         // console.log(data.quantity);

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

@@ -100,7 +100,7 @@ $('a[data-target="#sp-list" ]').on('click', function () {
                 }
                 historyHTML.push(`<li class="timeline-list-item pb-2 ${ group.status === auditConst.status.uncheck && idx === auditHistory.length - 1 && auditHistory.length !== 1 ? 'is_uncheck' : ''}">`);
                 if (group.endYear) {
-                    historyHTML.push(`<div class="timeline-item-date">${group.beginYear}<span>${group.beginDate}</span><span>${group.beginTime}</span></div>`);
+                    historyHTML.push(`<div class="timeline-item-date">${group.endYear}<span>${group.endDate}</span><span>${group.endTime}</span></div>`);
                 }
                 if (index < his.length - 1) {
                     historyHTML.push('<div class="timeline-item-tail"></div>');

+ 208 - 0
app/public/js/sp_data.js

@@ -0,0 +1,208 @@
+$(document).ready(() => {
+    autoFlashHeight();
+    class QtyObj {
+        constructor(setting) {
+            this.spread = SpreadJsObj.createNewSpread(setting.obj);
+            this.sheet = this.spread.getActiveSheet();
+            SpreadJsObj.initSheet(this.sheet, setting.spreadSetting);
+            if (setting.treeSetting) {
+                this.tree = createNewPathTree('base', setting.treeSetting);
+                this.tree.loadDatas(setting.data);
+            } else {
+                this.data = setting.data;
+            }
+            this.type = setting.type;
+            if (setting.treeSetting) {
+                SpreadJsObj.loadSheetData(this.sheet, SpreadJsObj.DataType.Tree, this.tree);
+            } else {
+                SpreadJsObj.loadSheetData(this.sheet, SpreadJsObj.DataType.Data, this.data);
+            }
+
+            const self = this;
+            this.sheet.bind(spreadNS.Events.EditEnded, function(e, info) {
+                if (!info.sheet.zh_setting) {
+                    SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                    return;
+                }
+
+                const node = SpreadJsObj.getRowObject(info.sheet, info.row);
+                if (!node || (self.tree && node.children && node.children.length > 0)) {
+                    SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                    return;
+                }
+
+                const col = info.sheet.zh_setting.cols[info.col];
+                const data = {};
+
+                data.id = node.id;
+
+                const oldValue = node ? node[col.field] : null;
+                const newValue = trimInvalidChar(info.editingText);
+                if (oldValue == info.editingText || ((!oldValue || oldValue === '') && (newValue === ''))) {
+                    SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                    return;
+                }
+                if (col.type === 'Number') {
+                    const num = _.toNumber(newValue);
+                    if (num) {
+                        data[col.field] = num;
+                    }
+                } else {
+                    data[col.field] = newValue;
+                }
+
+                postData('info/save', { type: self.type, updateData: [data]}, function (result) {
+                    self.loadUpdateData(result);
+                    SpreadJsObj.reLoadSheetData(info.sheet);
+                }, function () {
+                    SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                });
+            });
+            this.sheet.bind(spreadNS.Events.ClipboardPasting, function(e, info) {
+                info.cancel = true;
+                const setting = info.sheet.zh_setting;
+
+                if (!setting) return;
+                const pasteData = info.pasteData.html
+                    ? SpreadJsObj.analysisPasteHtml(info.pasteData.html)
+                    : (info.pasteData.text === ''
+                        ? SpreadJsObj.Clipboard.getAnalysisPasteText()
+                        : SpreadJsObj.analysisPasteText(info.pasteData.text));
+
+                const uDatas = [];
+                for (let iRow = 0; iRow < info.cellRange.rowCount; iRow++) {
+                    const curRow = info.cellRange.row + iRow;
+                    const node = SpreadJsObj.getRowObject(info.sheet, curRow);
+                    if (self.tree && node.children && node.children.length > 0) continue;
+
+                    let bPaste = false;
+                    const data = {};
+                    for (let iCol = 0; iCol < info.cellRange.colCount; iCol++) {
+                        const curCol = info.cellRange.col + iCol;
+                        const colSetting = setting.cols[curCol];
+                        const value = trimInvalidChar(pasteData[iRow][iCol]);
+
+                        if (colSetting.type === 'Number') {
+                            const num = _.toNumber(value);
+                            if (num) {
+                                data[colSetting.field] = num;
+                                bPaste = true;
+                            }
+                        } else {
+                            data[colSetting.field] = value;
+                            bPaste = true;
+                        }
+                    }
+                    if (bPaste) {
+                        if (node) {
+                            data.id = node.id;
+                            uDatas.push(data);
+                        }
+                    }
+                }
+                if (uDatas.length > 0) {
+                    postData('info/save', { type: self.type, updateData: uDatas}, function (result) {
+                        self.loadUpdateData(result);
+                        SpreadJsObj.reLoadSheetData(info.sheet);
+                    });
+                } else {
+                    SpreadJsObj.reLoadSheetData(info.sheet);
+                }
+            });
+            SpreadJsObj.addDeleteBind(this.spread, function(sheet) {
+                if (!sheet.zh_setting) return;
+
+                const datas = [];
+                const sels = sheet.getSelections();
+                if (!sels || !sels[0]) return;
+
+                for (let iRow = sels[0].row; iRow < sels[0].row + sels[0].rowCount; iRow++) {
+                    let bDel = false;
+                    const node = SpreadJsObj.getRowObject(info.sheet, iRow);
+                    if (node) {
+                        const data = { id: node.id };
+                        for (let iCol = sels[0].col; iCol < sels[0].col + sels[0].colCount; iCol++) {
+                            const style = sheet.getStyle(iRow, iCol);
+                            if (!style.locked) {
+                                const colSetting = sheet.zh_setting.cols[iCol];
+                                data[colSetting.field] = colSetting.type === 'Number' ? 0 : '';
+                                bDel = true;
+                            }
+                        }
+                        if (bDel) {
+                            datas.push(data);
+                        }
+                    }
+                }
+                if (datas.length === 0) return;
+
+                postData('info/save', { type: self.type, updateData: datas}, function (result) {
+                    self.loadUpdateData(result);
+                    SpreadJsObj.reLoadSheetData(sheet);
+                }, function () {
+                    SpreadJsObj.reLoadSheetData(sheet);
+                });
+            });
+        }
+        loadUpdateData(data) {
+            for (const d of data) {
+                const source = this.tree
+                    ? this.tree.getItems(d.id)
+                    : this.data.find(x => { return x.id === d.id });
+                if (!source) continue;
+
+                source.dgn_qty = d.dgn_qty;
+                source.final_qty = d.final_qty;
+            }
+        }
+    }
+
+    const mainQtyObj = new QtyObj({
+        obj: $('#main_qty_spread')[0],
+        spreadSetting: {
+            cols: [
+                {title: '工程名称', colSpan: '1', rowSpan: '1', field: 'name', hAlign: 0, width: 300, formatter: '@', readOnly: true},
+                {title: '单位', colSpan: '1', rowSpan: '1', field: 'unit', hAlign: 1, width: 50, formatter: '@', readOnly: true},
+                {title: '设计', colSpan: '1', rowSpan: '1', field: 'dgn_qty', hAlign: 2, width: 100, type: 'Number'},
+                {title: '竣工', colSpan: '1', rowSpan: '1', field: 'final_qty', hAlign: 2, width: 100, type: 'Number'},
+            ],
+            emptyRows: 0,
+            headRows: 1,
+            headRowHeight: [32],
+            defaultRowHeight: 21,
+            headerFont: '12px 微软雅黑',
+            font: '12px 微软雅黑',
+        },
+        data: mainQty,
+        isTree: false,
+        type: 'main_quantity',
+    });
+    const gclQtyObj = new QtyObj({
+        obj: $('#gcl_qty_spread')[0],
+        spreadSetting: {
+            cols: [
+                {title: '工程名称', colSpan: '1', rowSpan: '1', field: 'name', hAlign: 0, width: 300, formatter: '@', readOnly: true, cellType: 'tree'},
+                {title: '单位', colSpan: '1', rowSpan: '1', field: 'unit', hAlign: 1, width: 50, formatter: '@', readOnly: true},
+                {title: '设计', colSpan: '1', rowSpan: '1', field: 'dgn_qty', hAlign: 2, width: 100, type: 'Number'},
+                {title: '竣工', colSpan: '1', rowSpan: '1', field: 'final_qty', hAlign: 2, width: 100, type: 'Number'},
+            ],
+            emptyRows: 0,
+            headRows: 1,
+            headRowHeight: [32],
+            defaultRowHeight: 21,
+            headerFont: '12px 微软雅黑',
+            font: '12px 微软雅黑',
+        },
+        data: gclQty,
+        type: 'gcl_quantity',
+        isTree: true,
+        treeSetting: {
+            id: 'id',
+            pid: 'pid',
+            order: 'order',
+            level: 'level',
+            fullPath: 'full_path',
+            rootId: -1,
+        }
+    })
+});

+ 11 - 9
app/public/js/spreadjs_rela/spreadjs_zh.js

@@ -627,7 +627,8 @@ const SpreadJsObj = {
         }
         if(colSetting.cellType === 'ellipsisAutoTip') {
             if (!sheet.extendCellType.ellipsisAutoTip) {
-                sheet.extendCellType.ellipsisAutoTip = this.CellType.getEllipsisTextAutoTipCellType();
+                const scrollHeightClass = colSetting.scrollHeightClass ? colSetting.scrollHeightClass : false;
+                sheet.extendCellType.ellipsisAutoTip = this.CellType.getEllipsisTextAutoTipCellType(scrollHeightClass);
             }
             sheet.getRange(-1, col, -1, 1).cellType(sheet.extendCellType.ellipsisAutoTip);
         }
@@ -1554,7 +1555,7 @@ const SpreadJsObj = {
          * 获取 带悬浮提示的CellType
          * @returns {TipCellType}
          */
-        getTipCellType: function () {
+        getTipCellType: function (scrollHeightClass = false) {
             const maxHintWidth = 200, indent = 15, borderIndent = 10;
             const TipCellType = function () {};
             // 继承 SpreadJs定义的 普通的TextCellType
@@ -1626,12 +1627,13 @@ const SpreadJsObj = {
                         }
                         const validWidth = $(window).width() - (pos.x + hitinfo.x + indent) - borderIndent;
                         const textWidth = this.getTextDisplayWidth(hitinfo, text, "9pt Arial");
+                        const scrollTop = scrollHeightClass ? $(scrollHeightClass).scrollTop() : 0;
                         if (validWidth >= maxHintWidth || textWidth <= validWidth) {
-                            $(div).html(text).css("top", pos.y + hitinfo.y + indent).css("left", pos.x + hitinfo.x + indent);
+                            $(div).html(text).css("top", pos.y + hitinfo.y - scrollTop + indent).css("left", pos.x + hitinfo.x + indent);
                         } else if (textWidth > maxHintWidth) {
-                            $(div).html(text).css("top", pos.y + hitinfo.y + indent).css("left", pos.x + hitinfo.x - indent - maxHintWidth);
+                            $(div).html(text).css("top", pos.y + hitinfo.y - scrollTop + indent).css("left", pos.x + hitinfo.x - indent - maxHintWidth);
                         } else {
-                            $(div).html(text).css("top", pos.y + hitinfo.y + indent).css("left", pos.x + hitinfo.x - indent - textWidth);
+                            $(div).html(text).css("top", pos.y + hitinfo.y - scrollTop + indent).css("left", pos.x + hitinfo.x - indent - textWidth);
                         }
                         this._toolTipElement = div;
                         $(div).show("fast");
@@ -1651,10 +1653,10 @@ const SpreadJsObj = {
 
             return new TipCellType();
         },
-        getAutoTipCellType: function () {
+        getAutoTipCellType: function (scrollHeightClass) {
             const AutoTipCellType = function () {};
             // 继承 TipCellType
-            AutoTipCellType.prototype = SpreadJsObj.CellType.getTipCellType();
+            AutoTipCellType.prototype = SpreadJsObj.CellType.getTipCellType(scrollHeightClass);
             const proto = AutoTipCellType.prototype;
             proto.showTip = function (hitinfo, text) {
                 return text && text !== '' && this.getTextDisplayWidth(hitinfo, text) > hitinfo.cellRect.width;
@@ -2342,10 +2344,10 @@ const SpreadJsObj = {
             };
             return new EllipsisTextCellType();
         },
-        getEllipsisTextAutoTipCellType: function () {
+        getEllipsisTextAutoTipCellType: function (scrollHeightClass) {
             const CellType = function () {};
             // 继承 TipCellType
-            CellType.prototype = SpreadJsObj.CellType.getAutoTipCellType();
+            CellType.prototype = SpreadJsObj.CellType.getAutoTipCellType(scrollHeightClass);
             const proto = CellType.prototype;
             const ellipsisTextCellType = SpreadJsObj.CellType.getEllipsisTextCellType();
             proto.getEllipsisText = ellipsisTextCellType.getEllipsisText;

+ 26 - 6
app/public/js/stage.js

@@ -375,10 +375,10 @@ $(document).ready(() => {
                     if (col.field === 'uamount') {
                         if (data.bamount > 0) {
                             const usedAmount = ZhCalc.add(data.uamount, data.pre_amount);
-                            return usedAmount < 0 || usedAmount > data.bamount ? spreadColor.stage.over : defaultColor;
+                            return usedAmount < 0 || usedAmount > data.limitAmount ? spreadColor.stage.over : defaultColor;
                         } else if (data.bamount < 0) {
                             const usedAmount = ZhCalc.add(data.uamount, data.pre_amount);
-                            return usedAmount > 0 || usedAmount < data.bamount ? spreadColor.stage.over : defaultColor;
+                            return usedAmount > 0 || usedAmount < data.limitAmount ? spreadColor.stage.over : defaultColor;
                         } else {
                             return data.uamount ? spreadColor.stage.over : defaultColor;
                         }
@@ -489,13 +489,13 @@ $(document).ready(() => {
                     if (c.uamount) {
                         if (c.bamount > 0) {
                             const usedAmount = ZhCalc.add(c.uamount, c.pre_amount);
-                            if (usedAmount < 0 || usedAmount > c.bamount) {
+                            if (usedAmount < 0 || usedAmount > c.limitAmount) {
                                 toastr.error('变更令:' + c.code + ' 超计,请修改本期计量后,再提交');
                                 return;
                             }
                         } else if (c.bamount < 0) {
                             const usedAmount = ZhCalc.add(c.uamount, c.pre_amount);
-                            if (usedAmount > 0 || usedAmount < c.bamount) {
+                            if (usedAmount > 0 || usedAmount < c.limitAmount) {
                                 toastr.error('变更令:' + c.code + ' 超计,请修改本期计量后,再提交');
                                 return;
                             }
@@ -524,10 +524,26 @@ $(document).ready(() => {
                 });
             })
         }
+        findDecimal(unit) {
+            let value = 3;
+            if (unit !== '') {
+                value = precision.other.value;
+                const changeUnits = precision;
+                for (const d in changeUnits) {
+                    if (changeUnits[d].unit !== undefined && changeUnits[d].unit === unit) {
+                        value = changeUnits[d].value;
+                        break;
+                    }
+                }
+            }
+            return value;
+        }
         _calculateAmount() {
             for (const c of this.changes) {
                 c.bamount = _.toNumber(c.b_amount);
-                c.vamount = ZhCalc.sub(ZhCalc.sub(c.bamount, c.used_amount), c.stage_used_amount);
+                const qtyDecimal = this.findDecimal(c.unit);
+                c.limitAmount = ZhCalc.mul(c.bamount, ZhCalc.div(c.delimit, 100, 2), qtyDecimal);
+                c.vamount = ZhCalc.sub(ZhCalc.sub(c.limitAmount, c.used_amount), c.stage_used_amount);
                 const uc = _.find(this.useChanges, {cid: c.cid, cbid: c.cbid, no_value: this.callData.noValue});
                 if (uc) {
                     c.org_uamount = uc.qty;
@@ -3805,6 +3821,7 @@ $(document).ready(() => {
 
             this.changeBillsSpreadSetting = {
                 cols: [
+                    {title: '计价', colSpan: '1', rowSpan: '1', field: 'is_valuation', hAlign: 1, width: 30, cellType: 'checkbox'},
                     {title: '清单编号', colSpan: '1', rowSpan: '1', field: 'code', hAlign: 0, width: 80, formatter: '@'},
                     {title: '名称', colSpan: '1', rowSpan: '1', field: 'name', hAlign: 0, width: 150, cellType: 'delayTip', getTip: getTipText},
                     {title: '单位', colSpan: '1', rowSpan: '1', field: 'unit', hAlign: 1, width: 50, formatter: '@'},
@@ -4160,7 +4177,10 @@ $(document).ready(() => {
                             key: 'less', title: '漏计', valid: true,
                             check: function (node) {
                                 if (node.quantity) {
-                                    return ZhCalc.sub(ZhCalc.add(node.quantity, node.end_qc_qty), node.end_gather_qty) > 0;
+                                    if (node.b_code === '204-1-g') {
+                                        console.log(ZhCalc.sub(ZhCalc.sum([node.quantity, node.end_qc_qty, node.end_qc_minus_qty]), node.end_gather_qty));
+                                    }
+                                    return ZhCalc.sub(ZhCalc.sum([node.quantity, node.end_qc_qty, node.end_qc_minus_qty]), node.end_gather_qty) > 0;
                                 } else if (node.total_price) {
                                     return ZhCalc.sub(ZhCalc.add(node.total_price, node.end_qc_tp), node.end_gather_tp) > 0;
                                 }

+ 11 - 9
app/public/js/stage_audit.js

@@ -308,21 +308,23 @@ $(document).ready(function () {
                 html.push('<td style="text-align: center">');
                 if (auditor.status === auditConst.status.checking && j === group.length - 1) {
                     html.push('<span class="dropdown mr-2">',
-                        '<a href="javascript: void(0)" class="add-audit" id="<%- auditor.aid %>_add_dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">增加</a>',
-                        makeSelectAudit(auditor.id+'_add', auditor.id, 'add'),'</div>', '</span>');
+                        `<a href="javascript: void(0)" class="add-audit" id="${auditor.aid}_add_dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">增加</a>`,
+                        makeSelectAudit(auditor.aid+'_add', auditor.aid, 'add'),'</div>', '</span>');
                 }
                 if (auditor.status === auditConst.status.uncheck) {
                     if (j === group.length - 1) {
                         html.push('<span class="dropdown mr-2">',
-                            '<a href="javascript: void(0)" class="add-audit" id="<%- auditor.aid %>_add_dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">增加</a>',
-                            makeSelectAudit(auditor.id+'_add', auditor.id, 'add'),'</div>', '</span>');
-                        html.push('<span class="dropdown mr-2">',
-                            '<a href="javascript: void(0)" class="add-audit" id="<%- auditor.aid %>_add_dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">平级</a>',
-                            makeSelectAudit(auditor.id+'_add-sibling', auditor.id, 'add-sibling'),'</div>', '</span>');
+                            `<a href="javascript: void(0)" class="add-audit" id="${auditor.aid}_add_dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">增加</a>`,
+                            makeSelectAudit(auditor.aid+'_add', auditor.aid, 'add'),'</div>', '</span>');
+                        if (auditor.audit_type !== auditType.key.common) {
+                            html.push('<span class="dropdown mr-2">',
+                                '<a href="javascript: void(0)" class="add-audit" id="<%- auditor.aid %>_add_dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">平级</a>',
+                                makeSelectAudit(auditor.aid+'_add-sibling', auditor.aid, 'add-sibling'),'</div>', '</span>');
+                        }
                     }
                     html.push('<span class="dropdown mr-2">',
-                        '<a href="javascript: void(0)" class="add-audit" id="<%- auditor.aid %>_add_dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">更换</a>',
-                        makeSelectAudit(auditor.id+'_change', auditor.id, 'change'),'</div>', '</span>');
+                        `<a href="javascript: void(0)" class="add-audit" id="${auditor.aid}_add_dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">更换</a>`,
+                        makeSelectAudit(auditor.aid+'_change', auditor.aid, 'change'),'</div>', '</span>');
                     html.push(`<span class="dropdown">
                                     <a href="javascript: void(0)" class="text-danger" title="移除" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">移除</a>
                                     <div class="dropdown-menu">

+ 20 - 17
app/public/js/stage_change.js

@@ -140,22 +140,23 @@ $(document).ready(() => {
     // 初始化变更清单spread
     const billsSpreadSetting = {
         cols: [
-            {title: '本期使用', colSpan: '1', rowSpan: '1', field: '', hAlign: 1, width: 55, formatter: '@', readOnly: true, cellType: 'image', img: function (data) {
+            {title: '本期使用', colSpan: '1', rowSpan: '1', field: '', hAlign: 1, width: 55, formatter: '@', cellType: 'image', img: function (data) {
                 if (data.cur_qty && !checkZero(data.cur_qty)) {
                     return $('#icon-ok')[0];
                 } else {
                     return null;
                 }
             }},
-            {title: '清单编号', colSpan: '1', rowSpan: '1', field: 'code', hAlign: 0, width: 80, formatter: '@', readOnly: true},
-            {title: '名称', colSpan: '1', rowSpan: '1', field: 'name', hAlign: 0, width: 150, type: '@', readOnly: true},
-            {title: '单位', colSpan: '1', rowSpan: '1', field: 'unit', hAlign: 1, width: 50, formatter: '@', readOnly: true},
-            {title: '单价', colSpan: '1', rowSpan: '1', field: 'unit_price', hAlign: 2, width: 60, type: 'Number', readOnly: true},
-            {title: '数量', colSpan: '1', rowSpan: '1', field: 'qty', hAlign: 2, width: 60, formatter: '@', readOnly: true},
-            {title: '金额', colSpan: '1', rowSpan: '1', field: 'tp', hAlign: 2, width: 60, formatter: '@', readOnly: true},
-            {title: '已变更', colSpan: '1', rowSpan: '1', field: 'used_qty', hAlign: 2, width: 60, type: 'Number', readOnly: true},
-            {title: '未变更', colSpan: '1', rowSpan: '1', field: 'valid_qty', hAlign: 2, width: 60, formatter: '@', readOnly: true},
-            {title: '变更部位', colSpan: '1', rowSpan: '1', field: 'bwmx', hAlign: 0, width: 100, formatter: '@', readOnly: true},
+            {title: '计价', colSpan: '1', rowSpan: '1', field: 'is_valuation', hAlign: 1, width: 30, cellType: 'checkbox'},
+            {title: '清单编号', colSpan: '1', rowSpan: '1', field: 'code', hAlign: 0, width: 80, formatter: '@'},
+            {title: '名称', colSpan: '1', rowSpan: '1', field: 'name', hAlign: 0, width: 150, type: '@'},
+            {title: '单位', colSpan: '1', rowSpan: '1', field: 'unit', hAlign: 1, width: 50, formatter: '@'},
+            {title: '单价', colSpan: '1', rowSpan: '1', field: 'unit_price', hAlign: 2, width: 60, type: 'Number'},
+            {title: '数量', colSpan: '1', rowSpan: '1', field: 'qty', hAlign: 2, width: 60, formatter: '@'},
+            {title: '金额', colSpan: '1', rowSpan: '1', field: 'tp', hAlign: 2, width: 60, formatter: '@'},
+            {title: '已变更', colSpan: '1', rowSpan: '1', field: 'used_qty', hAlign: 2, width: 60, type: 'Number'},
+            {title: '未变更', colSpan: '1', rowSpan: '1', field: 'valid_qty', hAlign: 2, width: 60, formatter: '@'},
+            {title: '变更部位', colSpan: '1', rowSpan: '1', field: 'bwmx', hAlign: 0, width: 100, formatter: '@'},
         ],
         emptyRows: 0,
         headRows: 1,
@@ -167,18 +168,19 @@ $(document).ready(() => {
         localCache: {
             key: 'stage-change-bills',
             colWidth: true,
-        }
+        },
+        readOnly: true,
     };
     const billsSpread = SpreadJsObj.createNewSpread($('#bills-spread')[0]);
     SpreadJsObj.initSheet(billsSpread.getActiveSheet(), billsSpreadSetting);
     // 初始化相关台账spread
     const posSpreadSetting = {
         cols: [
-            {title: '相关台账|项目节编号', colSpan: '5|1', rowSpan: '1|1', field: 'leaf_xmj_code', hAlign: 0, width: 120, formatter: '@', readOnly: true},
-            {title: '|名称', colSpan: '|1', rowSpan: '1', field: 'leaf_xmj_name', hAlign: 0, width: 150, formatter: '@', readOnly: true},
-            {title: '|计量单元', colSpan: '|1', rowSpan: '1', field: 'p_name', hAlign: 0, width: 150, type: '@', readOnly: true},
-            {title: '|0号台账数量', colSpan: '|1', rowSpan: '1', field: 'f_qty', hAlign: 2, width: 80, formatter: '@', readOnly: true},
-            {title: '|本期变更数量', colSpan: '|1', rowSpan: '1', field: 'qty', hAlign: 2, width: 80, type: 'Number', readOnly: true},
+            {title: '相关台账|项目节编号', colSpan: '5|1', rowSpan: '1|1', field: 'leaf_xmj_code', hAlign: 0, width: 120, formatter: '@'},
+            {title: '|名称', colSpan: '|1', rowSpan: '1', field: 'leaf_xmj_name', hAlign: 0, width: 150, formatter: '@'},
+            {title: '|计量单元', colSpan: '|1', rowSpan: '1', field: 'p_name', hAlign: 0, width: 150, type: '@'},
+            {title: '|0号台账数量', colSpan: '|1', rowSpan: '1', field: 'f_qty', hAlign: 2, width: 80, formatter: '@'},
+            {title: '|本期变更数量', colSpan: '|1', rowSpan: '1', field: 'qty', hAlign: 2, width: 80, type: 'Number'},
         ],
         emptyRows: 0,
         headRows: 2,
@@ -190,7 +192,8 @@ $(document).ready(() => {
         localCache: {
             key: 'stage-change-pos',
             colWidth: true,
-        }
+        },
+        readOnly: true,
     };
     const posSpread = SpreadJsObj.createNewSpread($('#pos-spread')[0]);
     SpreadJsObj.initSheet(posSpread.getActiveSheet(), posSpreadSetting);

+ 6 - 3
app/public/js/stage_pay.js

@@ -148,7 +148,7 @@ $(document).ready(() => {
                 }
             },
             {title: '扣款', colSpan: '1', rowSpan: '1', field: 'minus', hAlign: 1, width: 50, cellType: 'checkbox', readOnly: 'readOnly.minus'},
-            {title: '本期金额(表达式)', colSpan: '1', rowSpan: '1', field: 'tp', hAlign: 2, width: 120, readOnly: 'readOnly.tp', type: 'Number', /*cellType: 'tip', getTip: function (data) {return data ? data.expr : '';}*/},
+            {title: '本期金额(F)', colSpan: '1', rowSpan: '1', field: 'tp', hAlign: 2, width: 120, readOnly: 'readOnly.tp', type: 'Number', /*cellType: 'tip', getTip: function (data) {return data ? data.expr : '';}*/},
             {title: '截止上期金额',  colSpan: '1', rowSpan: '1', field: 'pre_tp', hAlign: 2, width: 100, readOnly: true, type: 'Number',},
             {title: '截止本期金额',  colSpan: '1', rowSpan: '1', field: 'end_tp', hAlign: 2, width: 100, readOnly: true, type: 'Number',},
             {title: '起扣金额',  colSpan: '1', rowSpan: '1', field: 'sprice', hAlign: 2, width: 100, readOnly: 'readOnly.sprice', type: 'Number', /*cellType: 'tip', getTip: function (data) {return data ? data.sexpr : '';}*/},
@@ -255,7 +255,10 @@ $(document).ready(() => {
             if (!data.expr) return false;
 
             for (const b of payCalc.bases) {
-                if (b.reg.test(data.expr)) return true;
+                // data.expr = bqwc*0.6, b.reg = /bqwc/gim 时, b.reg.test返回值时true时false
+                // 但是如果b.reg = /bqwc/im时,test又可以正常判断
+                // if (b.reg.test(data.expr)) return true;
+                if (data.expr.search(b.reg) !== -1) return true;
             }
             return false;
         },
@@ -596,7 +599,7 @@ $(document).ready(() => {
             if (data) {
                 if (col.field === 'tp') {
                     $('#expr').val(data.expr).attr('field', 'expr').attr('org', data.expr)
-                        .attr('readOnly', readOnly|| (payBase.isYF(data) || payBase.isWC(data)));
+                        .attr('readOnly', readOnly|| payCol.readOnly.tp(data));
                 } else if (col.field === 'sprice') {
                     $('#expr').val(data.sexpr).attr('field', 'sexpr').attr('org', data.sexpr)
                         .attr('readOnly', readOnly|| payCol.readOnly.sprice(data) || payBase.isYF(data));

+ 54 - 1
app/public/js/sub_project.js

@@ -20,7 +20,7 @@ $(document).ready(function() {
                 html.push('<td width="20%" class="in-' + node.tree_level + '">');
                 if (node.is_folder) {
                     if (node.children.length > 0) {
-                        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="收起" id="'+ node.id +'"><i class="fa fa-minus-square-o"></i></span> <i class="fa fa-folder-o"></i> ', node.name);
                     } else {
                         html.push('<i class="fa fa-folder-o"></i> ', node.name);
                     }
@@ -51,6 +51,9 @@ $(document).ready(function() {
                 html.push(`<td>`);
                 html.push('<button class="btn btn-outline-primary btn-sm ml-1" name="edit">编辑</button>');
                 html.push('<button class="btn btn-outline-danger btn-sm ml-1" name="del">删除</button>');
+                html.push('<button class="btn btn-outline-primary btn-sm ml-1" name="up"><i class="fa fa-arrow-up"></i></button>');
+                html.push('<button class="btn btn-outline-primary btn-sm ml-1" name="down"><i class="fa fa-arrow-down"></i></button>');
+                html.push('<button class="btn btn-outline-primary btn-sm ml-1" name="top">顶层</button>');
                 if (!node.is_folder) html.push('<button class="btn btn-outline-primary btn-sm ml-1" name="member">成员管理</button>');
                 html.push('</td>');
                 return html.join('');
@@ -127,6 +130,11 @@ $(document).ready(function() {
                 delete Utils.dragNode;
                 delete Utils.dropNode;
             },
+            move: function(node, type) {
+                postData('/subproj/move', { id: node.id, type }, function (result) {
+                    Utils.refreshTreeTable(result);
+                });
+            }
         };
 
         Utils.reloadTable();
@@ -163,6 +171,33 @@ $(document).ready(function() {
             }
             $('#del').modal('show');
         });
+        $('body').on('click', 'button[name=up]', function (e) {
+            const treeId = $(this).parent().parent().attr('tree_id');
+            const node = ProjectTree.getItems(treeId);
+            if (!ProjectTree.getPreSiblingNode(node)) {
+                toastr.warning('不可上移');
+                return;
+            }
+            Utils.move(node, 'up');
+        });
+        $('body').on('click', 'button[name=down]', function (e) {
+            const treeId = $(this).parent().parent().attr('tree_id');
+            const node = ProjectTree.getItems(treeId);
+            if (!ProjectTree.getNextSiblingNode(node)) {
+                toastr.warning('不可下移');
+                return;
+            }
+            Utils.move(node, 'down');
+        });
+        $('body').on('click', 'button[name=top]', function (e) {
+            const treeId = $(this).parent().parent().attr('tree_id');
+            const node = ProjectTree.getItems(treeId);
+            if (!ProjectTree.getParent(node)) {
+                toastr.warning('已是顶层节点');
+                return;
+            }
+            Utils.move(node, 'top');
+        });
         $('body').on('click', 'button[name=edit]', function(e) {
             const treeId = $(this).parent().parent().attr('tree_id');
             const node = ProjectTree.getItems(treeId);
@@ -183,6 +218,24 @@ $(document).ready(function() {
             $('#sm-management').attr('tree_id', treeId);
             $('#set-management').modal('show');
         });
+        $('body').on('click', '.fold-switch', function() {
+            const id = this.getAttribute('id');
+            const node = ProjectTree.getItems(id);
+            ProjectTree.setExpanded(node, !node.expanded);
+            const posterity = ProjectTree.getPosterity(node);
+            if (node.expanded) {
+                $(this).html(`<i class="fa fa-minus-square-o"></i>`);
+            } else {
+                $(this).html(`<i class="fa fa-plus-square-o"></i>`);
+            }
+            for (const p of posterity) {
+                if (p.visible) {
+                    $(`tr[tree_id=${p.id}]`).show();
+                } else {
+                    $(`tr[tree_id=${p.id}]`).hide();
+                }
+            }
+        });
         return { ProjectTree, TableObj, ...Utils };
     })({
         treeSetting: { id: 'id', pid: 'tree_pid', level: 'tree_level', order: 'tree_order', rootId: '-1' },

+ 7 - 0
app/router.js

@@ -65,6 +65,7 @@ module.exports = app => {
     // 项目管理对计量接口相关
     app.get('/management/account', api3managementCheck, 'loginController.account');
     app.get('/management/account/sync', api3managementCheck, 'loginController.syncProjectAccount');
+    app.get('/management/account/valid', api3managementCheck, 'loginController.syncValidAccount');
     app.get('/management/project', api3managementCheck, 'loginController.project');
     app.get('/management/proxy/project/vertify', sessionAuth, 'loginController.vertifyProject');
     app.get('/management/proxy/project/add', sessionAuth, 'loginController.addProject');
@@ -481,6 +482,7 @@ module.exports = app => {
     app.post('/tender/:id/change/approval', sessionAuth, tenderCheck, uncheckTenderCheck, tenderBuildCheck, 'changeController.approval');
     app.post('/tender/:id/change/check/again', sessionAuth, tenderCheck, uncheckTenderCheck, tenderBuildCheck, 'changeController.checkAgain');
     app.post('/tender/:id/change/check/revise', sessionAuth, tenderCheck, uncheckTenderCheck, tenderBuildCheck, 'changeController.checkRevise');
+    app.post('/tender/:id/change/cancel/revise', sessionAuth, tenderCheck, uncheckTenderCheck, tenderBuildCheck, 'changeController.cancelRevise');
 
     app.post('/tender/:id/change/:cid/check/codeRepeat', sessionAuth, tenderCheck, uncheckTenderCheck, 'changeController.checkCodeRepeat');
     app.post('/tender/:id/change/:cid/info/copy', sessionAuth, tenderCheck, uncheckTenderCheck, 'changeController.copyChange');
@@ -709,12 +711,17 @@ module.exports = app => {
     app.post('/subproj/addFolder', sessionAuth, projectManagerCheck, 'subProjController.addFolder');
     app.post('/subproj/addProj', sessionAuth, projectManagerCheck, 'subProjController.addProj');
     app.post('/subproj/dragTo', sessionAuth, projectManagerCheck, 'subProjController.dragTo');
+    app.post('/subproj/move', sessionAuth, projectManagerCheck, 'subProjController.move');
     app.post('/subproj/del', sessionAuth, projectManagerCheck, 'subProjController.del');
     app.post('/subproj/save', sessionAuth, projectManagerCheck, 'subProjController.save');
     app.post('/subproj/rela/save', sessionAuth, 'subProjController.saveRela');
     app.post('/subproj/rela', sessionAuth, 'subProjController.rela');
     app.post('/subproj/member', sessionAuth, projectManagerCheck, 'subProjController.member');
     app.post('/subproj/memberSave', sessionAuth, projectManagerCheck, 'subProjController.memberSave');
+    // 项目信息
+    app.get('/sp/:id/info', sessionAuth, subProjectCheck, 'subProjController.info');
+    app.get('/sp/:id/data', sessionAuth, subProjectCheck, 'subProjController.dataIndex');
+    app.post('/sp/:id/info/save', sessionAuth, subProjectCheck, 'subProjController.saveInfo');
     // 概算投资
     app.get('/budget', sessionAuth, 'budgetController.list');
     app.get('/budget/:id', sessionAuth, budgetCheck, 'budgetController.budgetInfo');

+ 30 - 0
app/service/budget.js

@@ -99,6 +99,7 @@ module.exports = app => {
             await this.ctx.service.budgetGu.initByTemplate(transaction, operate.insertId, budgetStd.gu_template_id);
             await this.ctx.service.budgetGai.initByTemplate(transaction, operate.insertId, budgetStd.gai_template_id);
             await this.ctx.service.budgetYu.initByTemplate(transaction, operate.insertId, budgetStd.yu_template_id);
+            await this.ctx.service.budgetZb.initByTemplate(transaction, operate.insertId, budgetStd.zb_template_id);
             return operate.insertId;
         }
 
@@ -139,6 +140,7 @@ module.exports = app => {
                 await transaction.delete(this.ctx.service.budgetGu.tableName, { bid: id });
                 await transaction.delete(this.ctx.service.budgetGai.tableName, { bid: id });
                 await transaction.delete(this.ctx.service.budgetYu.tableName, { bid: id });
+                await transaction.delete(this.ctx.service.budgetZb.tableName, { bid: id });
                 await transaction.commit();
                 return true;
             } catch (err) {
@@ -203,6 +205,31 @@ module.exports = app => {
             return result;
         }
 
+        async _getZbUpdateData(newDecimal, orgDecimal) {
+            if (newDecimal.qty >= orgDecimal.qty && newDecimal.up >= orgDecimal.up && newDecimal.tp === orgDecimal.tp) return [];
+            const datas = await this.ctx.service.budgetZb.getData(this.ctx.budget.id);
+            const result = [];
+            for (const d of datas) {
+                if (d.b_code) {
+                    if (!d.is_leaf) continue;
+                    const quantity = this.ctx.helper.round(d.quantity, newDecimal.qty);
+                    const unit_price = this.ctx.helper.round(d.unit_price, newDecimal.up);
+                    const total_price = this.ctx.helper.mul(unit_price, quantity, newDecimal.tp);
+                    if (quantity !== d.quantity || unit_price !== d.unit_price || total_price !== d.total_price) {
+                        result.push({ id: d.id, tree_id: d.tree_id, quantity, unit_price, total_price });
+                    }
+                } else {
+                    const dgn_qty1 = this.ctx.helper.round(d.dgn_qty1, newDecimal.qty);
+                    const dgn_qty2 = this.ctx.helper.round(d.dgn_qty2, newDecimal.qty);
+                    const total_price = d.is_leaf ? this.ctx.helper.round(d.total_price, newDecimal.tp) : 0;
+                    if (dgn_qty1 !== d.dgn_qty1 || dgn_qty2 !== d.dgn_qty2 || total_price !== d.total_price) {
+                        result.push({ id: d.id, tree_id: d.tree_id, dgn_qty1, dgn_qty2, total_price });
+                    }
+                }
+            }
+            return result;
+        }
+
         async saveDecimal(decimal, page) {
             const newDecimal = JSON.parse(JSON.stringify(this.ctx.budget.decimal));
             if (decimal.qty >= 0 && decimal.qty <= 6) newDecimal.qty = decimal.qty;
@@ -212,17 +239,20 @@ module.exports = app => {
             const guDatas = await this._getGuUpdateData(newDecimal, this.ctx.budget.decimal);
             const gaiDatas = await this._getGaiUpdateData(newDecimal, this.ctx.budget.decimal);
             const yuDatas = await this._getYuUpdateData(newDecimal, this.ctx.budget.decimal);
+            const zbDatas = await this._getZbUpdateData(newDecimal, this.ctx.budget.decimal);
             const conn = await this.db.beginTransaction();
             try {
                 const result = await conn.update(this.tableName, { id: this.ctx.budget.id, decimal: JSON.stringify(newDecimal) });
                 if (guDatas.length > 0) await conn.updateRows(this.ctx.service.budgetGu.tableName, guDatas);
                 if (gaiDatas.length > 0) await conn.updateRows(this.ctx.service.budgetGai.tableName, gaiDatas);
                 if (yuDatas.length > 0) await conn.updateRows(this.ctx.service.budgetYu.tableName, yuDatas);
+                if (zbDatas.length > 0) await conn.updateRows(this.ctx.service.budgetZb.tableName, zbDatas);
                 await conn.commit();
                 switch (page) {
                     case 'gu': return { update: guDatas };
                     case 'gai': return { update: gaiDatas };
                     case 'yu': return { update: yuDatas };
+                    case 'zb': return { update: zbDatas };
                 }
             } catch (err) {
                 await conn.rollback();

+ 28 - 0
app/service/budget_zb.js

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

+ 137 - 28
app/service/change.js

@@ -81,7 +81,7 @@ module.exports = app => {
             }
         }
 
-        async add(tenderId, userId, code, plan_code, name) {
+        async add(tenderId, userId, code, plan_code, name, delimit = 100) {
             const sql = 'SELECT COUNT(*) as count FROM ?? WHERE `tid` = ? AND ((`code` = ? AND `status` != ?) OR (`p_code` = ? AND `status` = ?))';
             const sqlParam = [this.tableName, tenderId, code, audit.flow.status.checked, code, audit.flow.status.checked];
             const codeCount = await this.db.queryOne(sql, sqlParam);
@@ -106,6 +106,8 @@ module.exports = app => {
                     code,
                     name,
                     plan_code,
+                    state: 3,
+                    delimit,
                     // tp_decimal: this.ctx.tender.info.decimal.tp,
                     // up_decimal: this.ctx.tender.info.decimal.up,
                 };
@@ -253,18 +255,19 @@ module.exports = app => {
          * @param {int} hadlimit - 分页
          * @return {object} list - 列表
          */
-        async getListByStatus(tenderId, status = 0, hadlimit = 1, sortBy = '', orderBy = '') {
+        async getListByStatus(tenderId, status = 0, hadlimit = 1, sortBy = '', orderBy = '', state = 0) {
             let sql = '';
             let sqlParam = '';
+            const stateSql = state ? ' AND a.state = ' + state : '';
             if (this.ctx.tender.isTourist && status === 0) {
-                sql = 'SELECT a.* FROM ?? As a WHERE a.tid = ?';
+                sql = 'SELECT a.* FROM ?? As a WHERE a.tid = ?' + stateSql;
                 sqlParam = [this.tableName, tenderId];
             } else {
                 switch (status) {
                     case 0: // 包含你的所有变更令
                         sql =
                             'SELECT a.* FROM ?? AS a WHERE a.tid = ? AND ' +
-                            '(a.uid = ? OR (a.status != ? AND a.cid IN (SELECT b.cid FROM ?? AS b WHERE b.uid = ? AND a.times = b.times GROUP BY b.cid)) OR a.status = ? )';
+                            '(a.uid = ? OR (a.status != ? AND a.cid IN (SELECT b.cid FROM ?? AS b WHERE b.uid = ? AND a.times = b.times GROUP BY b.cid)) OR a.status = ? )' + stateSql;
                         sqlParam = [
                             this.tableName,
                             tenderId,
@@ -276,14 +279,14 @@ module.exports = app => {
                         ];
                         break;
                     case 1: // 待处理(你的)
-                        sql = 'SELECT a.* FROM ?? as a WHERE cid in(SELECT b.cid FROM ?? as b WHERE tid = ? AND uid = ? AND status = ?)';
+                        sql = 'SELECT a.* FROM ?? as a WHERE cid in(SELECT b.cid FROM ?? as b WHERE tid = ? AND uid = ? AND status = ?)' + stateSql;
                         sqlParam = [this.tableName, this.ctx.service.changeAudit.tableName, tenderId, this.ctx.session.sessionUser.accountId, audit.flow.auditStatus.checking];
                         break;
                     case 5: // 待上报(所有的)PS:取未上报,退回,修订的变更令
                         sql =
                             'SELECT a.* FROM ?? AS a WHERE ' +
                             'a.cid IN (SELECT b.cid FROM ?? AS b WHERE b.uid = ? GROUP BY b.cid) AND ' +
-                            '(a.status = ? OR a.status = ? OR a.status = ?) AND a.tid = ?';
+                            '(a.status = ? OR a.status = ? OR a.status = ?) AND a.tid = ?' + stateSql;
                         sqlParam = [
                             this.tableName,
                             this.ctx.service.changeAudit.tableName,
@@ -299,11 +302,11 @@ module.exports = app => {
                         sql =
                             'SELECT a.* FROM ?? AS a WHERE ' +
                             'a.cid IN (SELECT b.cid FROM ?? AS b WHERE b.uid = ? AND a.times = b.times GROUP BY b.cid) AND ' +
-                            'a.status = ? AND a.tid = ?';
+                            'a.status = ? AND a.tid = ?' + stateSql;
                         sqlParam = [this.tableName, this.ctx.service.changeAudit.tableName, this.ctx.session.sessionUser.accountId, status, tenderId];
                         break;
                     case 3: // 已完成(所有的)
-                        sql = 'SELECT a.* FROM ?? AS a WHERE a.status = ? AND a.tid = ?';
+                        sql = 'SELECT a.* FROM ?? AS a WHERE a.status = ? AND a.tid = ?' + stateSql;
                         sqlParam = [this.tableName, status, tenderId];
                         break;
                     default:
@@ -335,9 +338,10 @@ module.exports = app => {
          * @param {int} status - 状态
          * @return {void}
          */
-        async getCountByStatus(tenderId, status) {
+        async getCountByStatus(tenderId, status, state = 0) {
+            const stateSql = state ? ' AND a.state = ' + state : '';
             if (this.ctx.tender.isTourist && status === 0) {
-                const sql5 = 'SELECT count(*) AS count FROM ?? WHERE tid = ? ORDER BY in_time DESC';
+                const sql5 = 'SELECT count(*) AS count FROM ?? AS a WHERE a.tid = ?' + stateSql + ' ORDER BY a.in_time DESC';
                 const sqlParam5 = [this.tableName, tenderId];
                 const result5 = await this.db.query(sql5, sqlParam5);
                 return result5[0].count;
@@ -346,7 +350,7 @@ module.exports = app => {
                 case 0: // 包含你的所有变更令
                     const sql =
                         'SELECT count(*) AS count FROM ?? AS a WHERE a.tid = ? AND ' +
-                        '(a.uid = ? OR (a.status != ? AND a.cid IN (SELECT b.cid FROM ?? AS b WHERE b.uid = ? AND a.times = b.times GROUP BY b.cid)) OR a.status = ? )';
+                        '(a.uid = ? OR (a.status != ? AND a.cid IN (SELECT b.cid FROM ?? AS b WHERE b.uid = ? AND a.times = b.times GROUP BY b.cid)) OR a.status = ? )' + stateSql;
                     const sqlParam = [
                         this.tableName,
                         tenderId,
@@ -364,7 +368,7 @@ module.exports = app => {
                     //     uid: this.ctx.session.sessionUser.accountId,
                     //     status: 2,
                     // });
-                    const sql6 = 'SELECT count(*) AS count FROM ?? as a WHERE cid in(SELECT b.cid FROM ?? as b WHERE tid = ? AND uid = ? AND status = ?)';
+                    const sql6 = 'SELECT count(*) AS count FROM ?? as a WHERE cid in(SELECT b.cid FROM ?? as b WHERE tid = ? AND uid = ? AND status = ?)' + stateSql;
                     const sqlParam6 = [this.tableName, this.ctx.service.changeAudit.tableName, tenderId, this.ctx.session.sessionUser.accountId, audit.flow.auditStatus.checking];
                     const result6 = await this.db.query(sql6, sqlParam6);
                     return result6[0].count;
@@ -372,7 +376,7 @@ module.exports = app => {
                     const sql2 =
                         'SELECT count(*) AS count FROM ?? AS a WHERE ' +
                         'a.cid IN (SELECT b.cid FROM ?? AS b WHERE b.uid = ? AND a.times = b.times GROUP BY b.cid) ' +
-                        'AND (a.status = ? OR a.status = ? OR a.status = ?) AND a.tid = ?';
+                        'AND (a.status = ? OR a.status = ? OR a.status = ?) AND a.tid = ?' + stateSql;
                     const sqlParam2 = [
                         this.tableName,
                         this.ctx.service.changeAudit.tableName,
@@ -388,12 +392,12 @@ module.exports = app => {
                 case 4: // 终止(所有的)
                     const sql3 =
                         'SELECT count(*) AS count FROM ?? AS a WHERE ' +
-                        'a.cid IN (SELECT b.cid FROM ?? AS b WHERE b.uid = ? AND a.times = b.times GROUP BY b.cid) AND a.status = ? AND a.tid = ?';
+                        'a.cid IN (SELECT b.cid FROM ?? AS b WHERE b.uid = ? AND a.times = b.times GROUP BY b.cid) AND a.status = ? AND a.tid = ?' + stateSql;
                     const sqlParam3 = [this.tableName, this.ctx.service.changeAudit.tableName, this.ctx.session.sessionUser.accountId, status, tenderId];
                     const result3 = await this.db.query(sql3, sqlParam3);
                     return result3[0].count;
                 case 3: // 已完成(所有的)
-                    const sql4 = 'SELECT count(*) AS count FROM ?? WHERE status = ? AND tid = ?';
+                    const sql4 = 'SELECT count(*) AS count FROM ?? AS a WHERE a.status = ? AND a.tid = ?' + stateSql;
                     const sqlParam4 = [this.tableName, status, tenderId];
                     const result4 = await this.db.query(sql4, sqlParam4);
                     return result4[0].count;
@@ -680,19 +684,65 @@ module.exports = app => {
          * @param {int} tenderId - 标段id
          * @return {void}
          */
-        async saveInfo(postData) {
+        async saveInfo(postData, delimit = 100) {
             // 初始化事务
             const transaction = await this.db.beginTransaction();
             let result = false;
             try {
+                result = {};
                 const options = {
                     where: {
                         cid: this.ctx.change.cid,
                     },
                 };
+                if (postData.state && parseInt(postData.state) !== this.ctx.change.state) {
+                    // 变更清单需要同步设置计量上限值
+                    // 判断是否存在已调用清单,并判断新上限值是否比它小,更新计量上限值
+                    const changeList = await this.ctx.service.changeAuditList.getList(this.ctx.change.cid);
+                    // 获取是否已存在调用变更令
+                    let changeUsedData = await this.ctx.service.stageChange.getFinalUsedData(this.ctx.tender.id, this.ctx.change.cid);
+                    changeUsedData = this._.orderBy(this._.filter(changeUsedData, function(item) {
+                        return item.qty !== null;
+                    }), ['sorder'], ['desc']);
+                    const useChangeUsedData = [];
+                    if (changeUsedData.length > 0) { // 防止未创建期时调用
+                        for (const cu of changeUsedData) {
+                            const index = this._.findIndex(useChangeUsedData, { cbid: cu.cbid });
+                            if (index !== -1) {
+                                useChangeUsedData[index].qty = this.ctx.helper.add(useChangeUsedData[index].qty, cu.qty);
+                            } else {
+                                useChangeUsedData.push(cu);
+                            }
+                        }
+                    }
+                    const updateList = [];
+                    for (const cl of changeList) {
+                        const one = {
+                            id: cl.id,
+                        };
+                        if (useChangeUsedData.length > 0 && this._.findIndex(useChangeUsedData, { cbid: cl.id }) !== -1) {
+                            // 获取比例值
+                            const uc = this._.find(useChangeUsedData, { cbid: cl.id });
+                            const minLimit = Math.ceil(this.ctx.helper.div(uc.qty, cl.camount) * 100);
+                            if (minLimit < delimit) {
+                                one.delimit = delimit;
+                                cl.delimit = delimit;
+                            }
+                        } else if (cl.delimit !== delimit) {
+                            one.delimit = delimit;
+                            cl.delimit = delimit;
+                        }
+                        if (!this._.isEqual(one, { id: cl.id })) updateList.push(one);
+                    }
+                    if (updateList.length > 0) await transaction.updateRows(this.ctx.service.changeAuditList.tableName, updateList);
+                    result.changeList = changeList;
+                    result.deLimit = delimit;
+                    if (this.ctx.change.delimit !== delimit) postData.delimit = delimit;
+                }
+
                 await transaction.update(this.tableName, postData, options);
                 await transaction.commit();
-                result = true;
+                result.msg = '保存成功';
             } catch (error) {
                 await transaction.rollback();
                 result = false;
@@ -734,7 +784,8 @@ module.exports = app => {
                 };
                 await transaction.update(this.tableName, postData, options);
                 await transaction.commit();
-                result = changeList;
+                result = { changeList,
+                    usedList: await this.ctx.service.stageChange.getFinalUsedData(this.ctx.tender.id, this.ctx.change.cid) };
             } catch (error) {
                 await transaction.rollback();
                 result = false;
@@ -1195,7 +1246,7 @@ module.exports = app => {
          * @param pos - 查询的部位
          * @return {Promise<*>} - 可用的变更令列表
          */
-        async getValidChanges(tender, stage, data, minusNoValue) {
+        async getValidChanges(tender, stage, data, noValue) {
             const bills = data.bills, pos = data.pos;
             const self = this;
             const getFilterPart = function(field, value) {
@@ -1210,8 +1261,8 @@ module.exports = app => {
             // if (data.pos) filter = filter + ' And ' + getFilterPart('cb.bwmx', pos.name);
             const sql =
                 'SELECT c.cid, c.code, c.name, c.w_code, c.p_code, c.peg, c.org_name, c.org_code, c.new_name, c.new_code,' +
-                '    c.content, c.basis, c.memo, c.type, c.class, c.quality, c.company, c.charge, ' +
-                '    cb.id As cbid, cb.code As b_code, cb.name As b_name, cb.unit As b_unit, cb.samount As b_amount, cb.detail As b_detail, cb.bwmx As b_bwmx, cb.gcl_id, ' +
+                '    c.content, c.basis, c.memo, c.type, c.class, c.quality, c.company, c.charge,' +
+                '    cb.id As cbid, cb.code As b_code, cb.name As b_name, cb.unit As b_unit, cb.samount As b_amount, cb.detail As b_detail, cb.bwmx As b_bwmx, cb.gcl_id, cb.is_valuation, cb.delimit,' +
                 '    scb.used_amount' +
                 '  FROM ' + this.tableName + ' As c ' +
                 '  Left Join ' + this.ctx.service.changeAuditList.tableName + ' As cb On c.cid = cb.cid ' +
@@ -1225,16 +1276,14 @@ module.exports = app => {
                 '  ORDER BY c.in_time';
             const sqlParam = [tender.id, stage.order, tender.id, audit.flow.status.checked];
             let changes = await this.db.query(sql, sqlParam);
-            if (minusNoValue) {
-                if (data.minus) {
+            if (noValue) {
+                if (data.noValue) {
                     changes = changes.filter(c => {
-                        c.bamount = c.b_amount ? parseFloat(c.b_amount) : 0;
-                        return c.bamount < 0;
+                        return !c.is_valuation;
                     });
                 } else {
                     changes = changes.filter(c => {
-                        c.bamount = c.b_amount ? parseFloat(c.b_amount) : 0;
-                        return c.bamount >= 0;
+                        return c.is_valuation;
                     });
                 }
             }
@@ -1340,6 +1389,7 @@ module.exports = app => {
                 const attList = await this.ctx.service.changeAtt.getAllDataByCondition({ where: { cid } });
                 await this.ctx.helper.delFiles(attList);
                 await this.transaction.delete(this.ctx.service.changeAtt.tableName, { cid });
+                await this.transaction.delete(this.ctx.service.changeHistory.tableName, { cid });
                 // if (attList.length !== 0) {
                 //     for (const att of attList) {
                 //         await fs.unlinkSync(path.join(this.app.baseDir, att.filepath));
@@ -1402,7 +1452,7 @@ module.exports = app => {
                     jobs: auditList[0].jobs,
                     company: auditList[0].company,
                     times: changeData.times,
-                    usite: lastauditInfo.usite + 1,
+                    usite: 0,
                     usort,
                     status: audit.flow.auditStatus.revise,
                     sin_time: new Date(),
@@ -1431,6 +1481,8 @@ module.exports = app => {
                 const changeList = await this.ctx.service.changeAuditList.getAllDataByCondition({
                     where: { cid: changeData.cid },
                 });
+                // 生成内容保存表至zh_change_history中,用于撤销修订回退
+                await this.ctx.service.changeHistory.saveHistory(this.transaction, changeData, changeList);
                 // 申请变更金额更新为上一次审批审批变更后数量,清空audit_amount值
                 const updateTpList = [];
                 for (const cl of changeList) {
@@ -1473,6 +1525,63 @@ module.exports = app => {
         }
 
         /**
+         * 撤销修订变更令
+         * @param { string } cid - 查询的清单
+         * @return {Promise<*>} - 可用的变更令列表
+         */
+        async cancelRevise(cid, times) {
+            // 初始化事务
+            this.transaction = await this.db.beginTransaction();
+            let result = false;
+            try {
+                const changeData = await this.getDataByCondition({ cid });
+                const pid = this.ctx.session.sessionProject.id;
+                // 获取所有审核人列表
+                const auditors = await this.ctx.service.changeAudit.getAllAuditors(changeData.tid);
+                // 添加到消息推送表
+                const noticeContent = await this.getNoticeContent(pid, changeData.tid, changeData.cid, this.ctx.session.sessionUser.accountId, '撤销重新审批');
+                const records = [];
+                auditors.forEach(auditor => {
+                    records.push({
+                        pid,
+                        type: pushType.change,
+                        uid: auditor.uid,
+                        status: audit.flow.status.checked,
+                        content: noticeContent,
+                    });
+                });
+                await this.transaction.insert('zh_notice', records);
+                await this.ctx.service.changeHistory.returnHistory(this.transaction, cid);
+                await this.transaction.delete(this.ctx.service.changeAudit.tableName, { cid, times });
+                await this.transaction.delete(this.ctx.service.changeHistory.tableName, { cid });
+
+                const lastauditInfo = await this.ctx.service.changeAudit.getLastUser(cid, times - 1, 1, 0);
+                // 新增一个撤销修订状态到审批流程中
+                const revise_audit = {
+                    tid: lastauditInfo.tid,
+                    cid,
+                    uid: this.ctx.session.sessionUser.accountId,
+                    name: lastauditInfo.name,
+                    jobs: lastauditInfo.jobs,
+                    company: lastauditInfo.company,
+                    times: times - 1,
+                    usite: 0,
+                    usort: lastauditInfo.usort + 1,
+                    status: audit.flow.auditStatus.cancelRevise,
+                    sin_time: new Date(),
+                };
+                await this.transaction.insert(this.ctx.service.changeAudit.tableName, revise_audit);
+                await this.transaction.commit();
+                result = true;
+            } catch (error) {
+                console.log(error);
+                await this.transaction.rollback();
+                result = false;
+            }
+            return result;
+        }
+
+        /**
          * 重新审批变更令
          * @param { string } cid - 查询的清单
          * @return {Promise<*>} - 可用的变更令列表

+ 3 - 2
app/service/change_audit.js

@@ -119,8 +119,8 @@ module.exports = app => {
             } else if (change.status === statusConst.revise && uid === change.uid) {
                 // 修订上报
                 return 9;
-            } else if (change.status === statusConst.back && uid !== change.uid) {
-                // 被退回但你不是原报人
+            } else if ((change.status === statusConst.back || change.status === statusConst.revise) && uid !== change.uid) {
+                // 被退回或修订中但你不是原报人
                 return 3;
             } else if (change.status === statusConst.checked) {
                 // 已完成
@@ -665,6 +665,7 @@ module.exports = app => {
                     c_name: this.ctx.change.name,
                 };
                 await this.ctx.helper.sendWechat(audit.uid, smsTypeConst.const.BG, smsTypeConst.judge.approval.toString(), wxConst.template.change, wechatData);
+                await transaction.delete(this.ctx.service.changeHistory.tableName, { cid });
                 await transaction.commit();
             } catch (err) {
                 await transaction.rollback();

+ 8 - 2
app/service/change_audit_list.js

@@ -67,7 +67,7 @@ module.exports = app => {
          * 添加空白变更清单
          * @return {void}
          */
-        async add(data) {
+        async add(data, delimit = 100) {
             if (!this.ctx.tender || !this.ctx.change) {
                 throw '数据错误';
             }
@@ -95,6 +95,7 @@ module.exports = app => {
                     unit_price: null,
                     oamount: 0,
                     camount: 0,
+                    camount_expr: '',
                     samount: '',
                     detail: '',
                     spamount: 0,
@@ -105,6 +106,8 @@ module.exports = app => {
                     xmj_fxgc: null,
                     gcl_id: '',
                     order,
+                    is_valuation: 1,
+                    delimit,
                 };
                 // 新增工料
                 const result = await transaction.insert(this.tableName, insertData);
@@ -123,7 +126,7 @@ module.exports = app => {
          * 批量添加空白变更清单
          * @return {void}
          */
-        async batchAdd(data) {
+        async batchAdd(data, delimit = 100) {
             if (!this.ctx.tender || !this.ctx.change) {
                 throw '数据错误';
             }
@@ -157,6 +160,7 @@ module.exports = app => {
                         unit_price: null,
                         oamount: 0,
                         camount: 0,
+                        camount_expr: '',
                         samount: '',
                         detail: '',
                         spamount: 0,
@@ -167,6 +171,8 @@ module.exports = app => {
                         xmj_fxgc: null,
                         gcl_id: '',
                         order: order ? order + i : null,
+                        is_valuation: 1,
+                        delimit,
                     };
                     insertArray.push(insertData);
                 }

+ 112 - 0
app/service/change_history.js

@@ -0,0 +1,112 @@
+'use strict';
+
+/**
+ * 变更新增部位插入记录表
+ *
+ * @author Mai
+ * @date
+ * @version
+ */
+
+module.exports = app => {
+
+    class ChangeHistory extends app.BaseService {
+        /**
+         * 构造函数
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        constructor(ctx) {
+            super(ctx);
+            this.tableName = 'change_history';
+        }
+
+        async saveHistory(transaction, data, list) {
+            // const info_json = {
+            //     code: data.code,
+            //     w_code: data.code,
+            //     p_code: data.p_code,
+            //     name: data.name,
+            //     plan_code: data.plan_code,
+            //     peg: data.peg,
+            //     org_name: data.org_name,
+            //     org_code: data.org_code,
+            //     new_name: data.new_name,
+            //     new_code: data.new_code,
+            //     content: data.content,
+            //     basis: data.basis,
+            //     expr: data.expr,
+            //     memo: data.memo,
+            //     type: data.type,
+            //     class: data.class,
+            //     quality: data.quality,
+            //     company: data.company,
+            //     charge: data.charge,
+            // };
+            await transaction.insert(this.tableName, {
+                tid: data.tid,
+                cid: data.cid,
+                info_json: JSON.stringify(data),
+                list_json: JSON.stringify(list),
+            });
+        }
+
+        async returnHistory(transaction, cid) {
+            const data = await transaction.get(this.tableName, { cid });
+            if (!data) throw '撤销前数据不存在,无法撤销';
+            const change_update = {};
+            const oldInfo = JSON.parse(data.info_json);
+            for (const key in oldInfo) {
+                if (key !== 'cid' && key !== 'in_time') {
+                    change_update[key] = oldInfo[key];
+                }
+            }
+            const options = {
+                where: {
+                    cid,
+                },
+            };
+            await transaction.update(this.ctx.service.change.tableName, change_update, options);
+            const oldList = JSON.parse(data.list_json);
+            // 更新已调用清单id值
+            const sql1 = 'SELECT a.* FROM ?? as b LEFT JOIN ?? as a ON b.cbid = a.id WHERE b.cid = ? GROUP BY b.cbid';
+            const sqlParam1 = [this.ctx.service.stageChange.tableName, this.ctx.service.changeAuditList.tableName, cid];
+            const usedList = await transaction.query(sql1, sqlParam1);
+            // 先删后插
+            await transaction.delete(this.ctx.service.changeAuditList.tableName, { cid });
+            await transaction.insert(this.ctx.service.changeAuditList.tableName, oldList);
+            // 可能需要更新stage_change和stage_change_final的cbid
+            if (usedList.length > 0) {
+                const updateList = [];
+                const sql2 = 'SELECT * FROM ?? WHERE `cid` = ? AND `lid` != "0"';
+                const sqlParam2 = [this.ctx.service.changeAuditList.tableName, cid];
+                const newList = await transaction.query(sql2, sqlParam2);
+                // const newList = await transaction.select(this.tableName, { where: { cid: this.ctx.change.cid } });
+                for (const used of usedList) {
+                    const findFilter = { lid: used.lid, gcl_id: used.gcl_id, bwmx: used.bwmx };
+                    const findFilter2 = { lid: used.lid, gcl_id: used.gcl_id, bwmx: used.bwmx };
+                    if (used.mx_id) findFilter2.mx_id = used.mx_id;
+                    const newone = this._.find(newList, findFilter2) || this._.find(newList, findFilter);
+                    if (newone && newone.id !== used.cbid) {
+                        updateList.push({
+                            row: {
+                                cbid: newone.id,
+                            },
+                            where: {
+                                cid,
+                                cbid: used.id,
+                            },
+                        });
+                    }
+                }
+                if (updateList.length > 0) {
+                    await transaction.updateRows(this.ctx.service.stageChange.tableName, updateList);
+                    await transaction.updateRows(this.ctx.service.stageChangeFinal.tableName, updateList);
+                }
+            }
+        }
+    }
+
+    return ChangeHistory;
+};

+ 9 - 0
app/service/ledger.js

@@ -726,6 +726,15 @@ module.exports = app => {
                 throw (err.stack ? '导入工程量数据出错': err);
             }
         }
+
+        async getMemoData(tid, columns) {
+            if (!columns || columns.length === 0) return [];
+
+            return await this.db.select(this.departTableName(tid), {
+                where: { tender_id: tid },
+                columns: ['id', ...columns],
+            });
+        }
     }
 
     return Ledger;

+ 3 - 2
app/service/ledger_revise.js

@@ -152,17 +152,18 @@ module.exports = app => {
                 ? await this.ctx.service.reviseAudit.getAuditors(preRevise.id, preRevise.ledger_times)
                 : await this.ctx.service.ledgerAudit.getAuditors(this.ctx.tender.id, this.ctx.tender.data.ledger_times);
             for (const a of auditors) {
+                if (newAuditors.find(x => { return x.audit_id === a.audit_id; })) continue;
                 newAuditors.push({
                     tender_id: this.ctx.tender.id,
                     rid: rid,
                     in_time: inTime,
-                    audit_order: a.audit_order,
+                    audit_order: newAuditors.length + 1,
                     audit_id: a.audit_id,
                     times: 1,
                     status: audit.status.uncheck,
                 });
             }
-            if (auditors) await transaction.insert(this.ctx.service.reviseAudit.tableName, newAuditors);
+            if (newAuditors.length > 0) await transaction.insert(this.ctx.service.reviseAudit.tableName, newAuditors);
         }
 
         /**

+ 5 - 2
app/service/material.js

@@ -92,7 +92,7 @@ module.exports = app => {
             if (materials.length > 0 && materials[0].status !== auditConst.status.checked) {
                 const material = materials[0];
                 const curAuditor = await this.ctx.service.materialAudit.getCurAuditor(material.id, material.times);
-                const isActive = curAuditor ? curAuditor.id === this.ctx.session.sessionUser.accountId : material.user_id === this.ctx.session.sessionUser.accountId;
+                const isActive = curAuditor ? curAuditor.aid === this.ctx.session.sessionUser.accountId : material.user_id === this.ctx.session.sessionUser.accountId;
                 if (isActive) {
                     material.curTimes = material.times;
                     material.curOrder = curAuditor ? curAuditor.order : 0;
@@ -116,7 +116,7 @@ module.exports = app => {
             if (materials.length > 0 && materials[0].status !== auditConst.status.checked) {
                 const material = materials[0];
                 const curAuditor = await this.ctx.service.materialAudit.getCurAuditor(material.id, material.times);
-                const isActive = curAuditor ? curAuditor.id === this.ctx.session.sessionUser.accountId : material.user_id === this.ctx.session.sessionUser.accountId;
+                const isActive = curAuditor ? curAuditor.aid === this.ctx.session.sessionUser.accountId : material.user_id === this.ctx.session.sessionUser.accountId;
                 if (isActive) {
                     material.curTimes = material.times;
                     material.curOrder = curAuditor ? curAuditor.order : 0;
@@ -221,11 +221,14 @@ module.exports = app => {
                     // 修改现行价格指数,并返回调差基数json
                     const ex_calc = await this.ctx.service.materialExponent.updateNewMaterial(transaction, newMaterial.id, this.ctx, newMaterial.stage_id, preMaterial.ex_calc, JSON.parse(newMaterial.decimal));
                     // 计算得出本期总金额
+                    // 找出当前人并更新tp_data
+                    const tp_data = await this.ctx.service.materialAudit.getTpData(transaction, newMaterial.id, JSON.parse(newMaterial.decimal));
                     const updateMaterialData = {
                         id: newMaterial.id,
                         m_tp,
                         m_tax_tp,
                         ex_calc: JSON.stringify(ex_calc),
+                        tp_data: JSON.stringify(tp_data),
                     };
                     await transaction.update(this.tableName, updateMaterialData);
                     // 删除material_list表冗余数据,减少表数据量

+ 24 - 21
app/service/material_audit.js

@@ -68,7 +68,7 @@ module.exports = app => {
 
         async getFinalAuditGroup(materialId, times = 1) {
             const sql =
-                'SELECT la.`aid`, pa.`name`, pa.`company`, pa.`role`, pa.`mobile`, pa.`telephone`, pa.`sign_path`, la.`times`, la.`mid`, Max(la.`order`) as max_order ' +
+                'SELECT la.`aid`, pa.`name`, pa.`company`, pa.`role`, pa.`mobile`, pa.`telephone`, pa.`sign_path`, la.`times`, la.`mid`, Max(la.`order`) as max_order, GROUP_CONCAT(la.tp_data) as tp_data ' +
                 '  FROM ?? AS la Left Join ?? AS pa On la.`aid` = pa.`id` ' +
                 '  WHERE la.`mid` = ? and la.`times` = ? GROUP BY la.`aid` ORDER BY la.`order`';
             const sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, materialId, times];
@@ -91,7 +91,7 @@ module.exports = app => {
          * @return {Promise<*>}
          */
         async getCurAuditor(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` ' +
+            const sql = 'SELECT la.`id`, 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` ' +
                 '  FROM ?? AS la Left Join ?? AS pa On la.`aid` = pa.`id` ' +
                 '  WHERE la.`mid` = ? and la.`status` = ? and la.`times` = ?';
             const sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, materialId, auditConst.status.checking, times];
@@ -204,27 +204,27 @@ module.exports = app => {
             return true;
         }
 
-        async getTpData(materialId) {
-            const materialInfo = await this.ctx.service.material.getDataById(materialId);
+        async getTpData(transaction, materialId, decimal = (this.ctx.material.decimal ? this.ctx.material.decimal : materialConst.decimal)) {
+            const materialInfo = await transaction.get(this.ctx.service.material.tableName, { id: materialId });
             const tp_data = {
-                m_tp: materialInfo.m_tp !== null ? this.ctx.helper.round(materialInfo.m_tp, this.ctx.material.decimal.tp) : null,
+                m_tp: materialInfo.m_tp !== null ? this.ctx.helper.round(materialInfo.m_tp, decimal.tp) : null,
                 tax_tp: materialInfo.material_tax ? (materialInfo.m_tax_tp ? materialInfo.m_tax_tp : materialInfo.m_tp) :
-                    (materialInfo.m_tp !== null ? this.ctx.helper.round(this.ctx.helper.mul(materialInfo.m_tp, 1+materialInfo.rate/100), this.ctx.material.decimal.tp) : null),
-                ex_tp: materialInfo.ex_tp !== null ? this.ctx.helper.round(materialInfo.ex_tp, this.ctx.material.decimal.tp) : null,
-                ex_tax_tp: materialInfo.ex_tp !== null ? this.ctx.helper.round(this.ctx.helper.mul(materialInfo.ex_tp, 1+materialInfo.exponent_rate/100), this.ctx.material.decimal.tp) : null,
+                    (materialInfo.m_tp !== null ? this.ctx.helper.round(this.ctx.helper.mul(materialInfo.m_tp, 1+materialInfo.rate/100), decimal.tp) : null),
+                ex_tp: materialInfo.ex_tp !== null ? this.ctx.helper.round(materialInfo.ex_tp, decimal.tp) : null,
+                ex_tax_tp: materialInfo.ex_tp !== null ? this.ctx.helper.round(this.ctx.helper.mul(materialInfo.ex_tp, 1+materialInfo.exponent_rate/100), decimal.tp) : null,
             }
             tp_data.total_tp = this.ctx.helper.add(tp_data.m_tp, tp_data.ex_tp);
             tp_data.total_tax_tp = !materialInfo.material_tax ? this.ctx.helper.add(tp_data.tax_tp, tp_data.ex_tax_tp) : tp_data.ex_tax_tp;
             if (materialInfo.is_stage_self) {
-                const materialStageList = await this.ctx.service.materialStage.getAllDataByCondition({ where: { mid: materialId }});
+                const materialStageList = await transaction.select(this.ctx.service.materialStage.tableName, { where: { mid: materialId } });
                 const stage_tp = [];
                 for (const ms of materialStageList) {
                     stage_tp.push({
                         id: ms.id,
                         sid: ms.sid,
                         order: ms.order,
-                        m_tp: this.ctx.helper.round(ms.m_tp, this.ctx.material.decimal.tp),
-                        m_tax_tp: this.ctx.helper.round(ms.m_tax_tp, this.ctx.material.decimal.tp),
+                        m_tp: this.ctx.helper.round(ms.m_tp, decimal.tp),
+                        m_tax_tp: this.ctx.helper.round(ms.m_tax_tp, decimal.tp),
                     });
                 }
                 tp_data.stage_tp = stage_tp;
@@ -250,9 +250,9 @@ module.exports = app => {
             }
 
             const transaction = await this.db.beginTransaction();
-            const tp_data = await this.getTpData(materialId);
             try {
-                await transaction.update(this.tableName, { id: audit.id, status: auditConst.status.checking, begin_time: new Date() });
+                const tp_data = await this.getTpData(transaction, materialId);
+                await transaction.update(this.tableName, { id: audit.id, status: auditConst.status.checking, begin_time: new Date(), tp_data: JSON.stringify(tp_data) });
                 await transaction.update(this.ctx.service.material.tableName, {
                     id: materialId, status: auditConst.status.checking, tp_data: JSON.stringify(tp_data),
                 });
@@ -315,11 +315,10 @@ module.exports = app => {
 
             const nextAudit = await this.getDataByCondition({ mid: materialId, times, order: audit.order + 1 });
 
-            // 获取当前总金额及独立单价期的金额,添加到tp_data中,报表使用
-            const tp_data = await this.getTpData(materialId);
-
             const transaction = await this.db.beginTransaction();
             try {
+                // 获取当前总金额及独立单价期的金额,添加到tp_data中,报表使用
+                const tp_data = await this.getTpData(transaction, materialId);
                 await transaction.update(this.tableName, { id: audit.id, status: checkData.checkType, opinion: checkData.opinion, end_time: time, tp_data: JSON.stringify(tp_data) });
 
                 // 获取推送必要信息
@@ -342,7 +341,7 @@ module.exports = app => {
                     // 复制一份下一审核人数据
                     // await this.ctx.service.stagePay.copyAuditStagePays(this.ctx.stage, this.ctx.stage.times, nextAudit.order, transaction);
                     // 流程至下一审批人
-                    await transaction.update(this.tableName, { id: nextAudit.id, status: auditConst.status.checking, begin_time: time });
+                    await transaction.update(this.tableName, { id: nextAudit.id, status: auditConst.status.checking, begin_time: time, tp_data: JSON.stringify(tp_data) });
 
                     // 同步 期信息
                     await transaction.update(this.ctx.service.material.tableName, {
@@ -500,8 +499,8 @@ module.exports = app => {
                 order++;
             }
             const transaction = await this.db.beginTransaction();
-            const tp_data = await this.getTpData(materialId);
             try {
+                const tp_data = await this.getTpData(transaction, materialId);
                 await transaction.update(this.tableName, { id: audit.id, status: checkData.checkType, opinion: checkData.opinion, end_time: time, tp_data: JSON.stringify(tp_data), });
                 // 添加到消息推送表
                 const noticeContent = await this.getNoticeContent(pid, audit.tid, materialId, audit.aid, checkData.opinion);
@@ -514,6 +513,7 @@ module.exports = app => {
                 await transaction.update(this.ctx.service.material.tableName, {
                     id: materialId, status: checkData.checkType,
                     times: times + 1,
+                    tp_data: JSON.stringify(tp_data),
                 });
                 // 拷贝新一次审核流程列表
                 await transaction.insert(this.tableName, auditors);
@@ -596,8 +596,8 @@ module.exports = app => {
             const preAuditor = auditors2[auditorIndex - 1];
             const noticeContent = await this.getNoticeContent(pid, audit.tid, materialId, audit.aid, checkData.opinion);
             const transaction = await this.db.beginTransaction();
-            const tp_data = await this.getTpData(materialId);
             try {
+                const tp_data = await this.getTpData(transaction, materialId);
                 // 添加到消息推送表
                 const records = [{ pid, type: pushType.material, uid: this.ctx.material.user_id, status: auditConst.status.checkNoPre, content: noticeContent }];
                 auditors2.forEach(audit => {
@@ -617,6 +617,7 @@ module.exports = app => {
                     tid: audit.tid, mid: audit.mid, aid: preAuditor.aid,
                     times: audit.times, order: audit.order + 1, status: auditConst.status.checking,
                     begin_time: time,
+                    tp_data: JSON.stringify(tp_data),
                 });
                 newAuditors.push({
                     tid: audit.tid, mid: audit.mid, aid: audit.aid,
@@ -713,6 +714,9 @@ module.exports = app => {
             }
             const transaction = await this.db.beginTransaction();
             try {
+                const materialInfo = await this.ctx.service.material.getDataById(materialId);
+                const material_decimal = materialInfo && materialInfo.decimal ? JSON.parse(materialInfo.decimal) : materialConst.decimal;
+                const tp_data = await this.getTpData(transaction, materialId, material_decimal);
                 // 当前审批人2次添加至流程中
                 const newAuditors = [];
                 newAuditors.push({
@@ -724,6 +728,7 @@ module.exports = app => {
                     tid: audit.tid, mid: audit.mid, aid: audit.aid,
                     times: audit.times, order: audit.order + 2, status: auditConst.status.checking,
                     begin_time: time,
+                    tp_data: JSON.stringify(tp_data),
                 });
                 await transaction.insert(this.tableName, newAuditors);
 
@@ -733,8 +738,6 @@ module.exports = app => {
                     id: materialId, status: auditConst.status.checking,
                 });
 
-                const materialInfo = await this.ctx.service.material.getDataById(materialId);
-                const material_decimal = materialInfo && materialInfo.decimal ? JSON.parse(materialInfo.decimal) : materialConst.decimal;
                 // 微信模板通知
                 const wechatData = {
                     qi: materialInfo.order,

+ 13 - 0
app/service/material_bills.js

@@ -775,6 +775,19 @@ module.exports = app => {
             //     updateData2.m_tax_tp = tp.tax_total_price;
             // }
             await transaction.update(this.ctx.service.material.tableName, updateData2);
+            // 找出当前人并更新tp_data
+            const tp_data = await this.ctx.service.materialAudit.getTpData(transaction, this.ctx.material.id);
+            if (this.ctx.material.status === auditConst.status.uncheck || this.ctx.material.status === auditConst.status.checkNo) {
+                await transaction.update(this.ctx.service.material.tableName, {
+                    id: this.ctx.material.id,
+                    tp_data: JSON.stringify(tp_data),
+                });
+            } else if (this.ctx.material.curAuditor) {
+                await transaction.update(this.ctx.service.materialAudit.tableName, {
+                    id: this.ctx.material.curAuditor.id,
+                    tp_data: JSON.stringify(tp_data),
+                });
+            }
             return tp.total_price;
         }
 

+ 15 - 0
app/service/material_exponent.js

@@ -220,6 +220,21 @@ module.exports = app => {
                 ex_expr: expr,
             });
             console.log(ex_tp, expr);
+            if (!mid) {
+                // 找出当前人并更新tp_data
+                const tp_data = await this.ctx.service.materialAudit.getTpData(transaction, this.ctx.material.id);
+                if (this.ctx.material.status === auditConst.status.uncheck || this.ctx.material.status === auditConst.status.checkNo) {
+                    await transaction.update(this.ctx.service.material.tableName, {
+                        id: this.ctx.material.id,
+                        tp_data: JSON.stringify(tp_data),
+                    });
+                } else if (this.ctx.material.curAuditor) {
+                    await transaction.update(this.ctx.service.materialAudit.tableName, {
+                        id: this.ctx.material.curAuditor.id,
+                        tp_data: JSON.stringify(tp_data),
+                    });
+                }
+            }
             return [ex_tp, expr];
         }
 

+ 15 - 1
app/service/material_list.js

@@ -290,7 +290,21 @@ module.exports = app => {
                 m_tp: tp.total_price,
                 m_tax_tp: tp.tax_total_price,
             };
-            return await transaction.update(this.ctx.service.material.tableName, updateData2);
+            const result = await transaction.update(this.ctx.service.material.tableName, updateData2);
+            // 找出当前人并更新tp_data
+            const tp_data = await this.ctx.service.materialAudit.getTpData(transaction, this.ctx.material.id);
+            if (this.ctx.material.status === auditConst.status.uncheck || this.ctx.material.status === auditConst.status.checkNo) {
+                await transaction.update(this.ctx.service.material.tableName, {
+                    id: this.ctx.material.id,
+                    tp_data: JSON.stringify(tp_data),
+                });
+            } else if (this.ctx.material.curAuditor) {
+                await transaction.update(this.ctx.service.materialAudit.tableName, {
+                    id: this.ctx.material.curAuditor.id,
+                    tp_data: JSON.stringify(tp_data),
+                });
+            }
+            return result;
         }
 
         /**

+ 1 - 1
app/service/payment_detail.js

@@ -25,7 +25,7 @@ module.exports = app => {
             if (details.length > 0 && details[0].status !== auditConst.status.checked) {
                 const detail = details[0];
                 const curAuditor = await this.ctx.service.paymentDetailAudit.getCurAuditor(detail.id, detail.times);
-                const isActive = curAuditor ? curAuditor.id === this.ctx.session.sessionUser.accountId : detail.uid === this.ctx.session.sessionUser.accountId;
+                const isActive = curAuditor ? curAuditor.aid === this.ctx.session.sessionUser.accountId : detail.uid === this.ctx.session.sessionUser.accountId;
                 if (isActive) {
                     detail.curTimes = detail.times;
                     detail.curOrder = curAuditor ? curAuditor.order : 0;

+ 9 - 0
app/service/pos.js

@@ -501,6 +501,15 @@ module.exports = app => {
         async deletePosData(transaction, tid, lid) {
             await transaction.delete(this.tableName, {tid: tid, lid: lid});
         }
+
+        async getMemoData(tid, columns) {
+            if (!columns || columns.length === 0) return [];
+
+            return await this.db.select(this.departTableName(tid), {
+                where: { tid },
+                columns: ['id', ...columns],
+            });
+        }
     }
 
     return Pos;

+ 31 - 2
app/service/report.js

@@ -8,7 +8,7 @@
  * @version
  */
 
-const BudgetSource = require('../lib/rm/budget');
+const BudgetSource = require('../lib/rm/tender_budget');
 const MaterialSource = require('../lib/rm/material');
 
 const rptCustomData = require('../lib/rptCustomData');
@@ -42,7 +42,6 @@ module.exports = app => {
             }
         }
 
-
         getFilter(sourceFilters) {
             const common = [], spec = [];
             for (const sf of sourceFilters) {
@@ -346,6 +345,10 @@ module.exports = app => {
                             runnableRst.push(service.stageRelaImBills.getAllDataByCondition({ where: { tid: params.tender_id, sid: params.stage_id } }));
                             runnableKey.push(filter);
                             break;
+                        case 'mem_budget_info':
+                            runnableRst.push(params.budget_id ? budgetSource.budgetInfo(params.budget_id) : budgetSource.tenderBudgetInfo(params.tender_id));
+                            runnableKey.push(filter);
+                            break;
                         case 'mem_budget_gu':
                             runnableRst.push(params.budget_id ? budgetSource.budgetGu(params.budget_id) : budgetSource.tenderGu(params.tender_id));
                             runnableKey.push(filter);
@@ -358,6 +361,10 @@ module.exports = app => {
                             runnableRst.push(params.budget_id ? budgetSource.budgetYu(params.budget_id) : budgetSource.tenderYu(params.tender_id));
                             runnableKey.push(filter);
                             break;
+                        case 'mem_budget_zb':
+                            runnableRst.push(params.budget_id ? budgetSource.budgetZb(params.budget_id) : budgetSource.tenderZb(params.tender_id));
+                            runnableKey.push(filter);
+                            break;
                         case 'mem_budget_final':
                             runnableRst.push(params.budget_id ? budgetSource.budgetFinal(params.budget_id) : budgetSource.tenderFinal(params.tender_id));
                             runnableKey.push(filter);
@@ -374,6 +381,10 @@ module.exports = app => {
                             runnableRst.push(params.budget_id ? budgetSource.budgetYu(params.budget_id, true) : budgetSource.tenderYu(params.tender_id, true));
                             runnableKey.push(filter);
                             break;
+                        case 'mem_budget_zb_filter':
+                            runnableRst.push(params.budget_id ? budgetSource.budgetZb(params.budget_id, true) : budgetSource.tenderZb(params.tender_id, true));
+                            runnableKey.push(filter);
+                            break;
                         case 'mem_budget_final_filter':
                             runnableRst.push(params.budget_id ? budgetSource.budgetFinal(params.budget_id, true) : budgetSource.tenderFinal(params.tender_id, true));
                             runnableKey.push(filter);
@@ -382,6 +393,17 @@ module.exports = app => {
                             runnableRst.push(service.reportMemory.getPmDeal());
                             runnableKey.push(filter);
                             break;
+                        case 'mem_schedule_month':
+                            runnableRst.push(service.scheduleMonth.getReportData(params.tender_id));
+                            runnableKey.push(filter);
+                            break;
+                        case 'mem_schedule_stage':
+                            runnableRst.push(service.scheduleStage.getReportData(params.tender_id));
+                            runnableKey.push(filter);
+                        case 'mem_schedule':
+                            runnableRst.push(service.schedule.getDataByCondition({ tid: params.tender_id }));
+                            runnableKey.push(filter);
+                            break;
                         default:
                             break;
                     }
@@ -517,6 +539,13 @@ module.exports = app => {
             return rptPayment.getReportData(params, sourceFilters, memFieldKeys, customDefine, customSelect);
         }
 
+        async budget(params, sourceFilters, memFieldKeys, customDefine, customSelect) {
+            const RptPayment = require('../lib/rm/budget');
+            const rptPayment = new RptPayment(this.ctx);
+
+            return rptPayment.getReportData(params, sourceFilters, memFieldKeys, customDefine, customSelect);
+        }
+
         async getReportData(source_type, params, sourceFilters, memFieldKeys, customDefine, customSelect) {
             const sourceType = sourceTypeConst.sourceTypeData.find(x => { return x.id === source_type; });
             if (!sourceType && !this[sourceType.key]) return {};

+ 5 - 0
app/service/schedule_month.js

@@ -107,6 +107,11 @@ module.exports = app => {
                 throw err;
             }
         }
+
+        async getReportData(tid) {
+            const sql = `SELECT *, (@sum := @sum + plan_tp) as sum_plan_tp FROM ${this.tableName}, (SELECT @SUM := 0)T where tid = ? ORDER BY yearmonth;`;
+            return await this.db.query(sql, [tid]);
+        }
     }
     return ScheduleMonth;
 };

+ 5 - 0
app/service/schedule_stage.js

@@ -132,6 +132,11 @@ module.exports = app => {
             };
             await transaction.update(this.ctx.service.schedule.tableName, stageData, stageOption);
         }
+
+        async getReportData(tid) {
+            const sql = `SELECT *, (@sum := @sum + tp) as sum_tp FROM ${this.tableName}, (SELECT @SUM := 0)T where tid = ? ORDER BY yearmonth;`;
+            return await this.db.query(sql, [tid]);
+        }
     }
     return ScheduleStage;
 };

+ 22 - 1
app/service/stage_audit.js

@@ -514,6 +514,7 @@ module.exports = app => {
                         };
                         await this.ctx.helper.sendWechat(users, smsTypeConst.const.JL, smsTypeConst.judge.approval.toString(), wxConst.template.stage, wechatData);
                     } else {
+                        await this.ctx.service.revisePrice.doPriceUsed(this.ctx.stage, transaction);
                         await this.ctx.service.tenderTag.saveTenderTag(this.ctx.tender.id, {stage_time: new Date()}, transaction);
                         const his_id = await this.ctx.service.ledgerHistory.checkBackupLedgerHistory(this.ctx.stage.tid, this.ctx.stage.id);
                         // 本期结束
@@ -1768,7 +1769,7 @@ module.exports = app => {
                     await this._timesDelete(this.ctx.stage.id, nowTimes, transaction);
                 }
                 // 添加上一次审批人
-                const sql = 'SELECT `tid`, `sid`, `aid`, `order` FROM ?? WHERE `sid` = ? and `times` = ? GROUP BY `aid` ORDER BY `id` ASC';
+                const sql = 'SELECT `tid`, `sid`, `aid`, `order`, `audit_type`, `audit_order` FROM ?? WHERE `sid` = ? and `times` = ? GROUP BY `aid` ORDER BY `id` ASC';
                 const sqlParam = [this.tableName, this.ctx.stage.id, nowTimes - 1];
                 const auditors = await this.db.query(sql, sqlParam);
                 let order = 1;
@@ -1803,6 +1804,7 @@ module.exports = app => {
                 });
                 // 同步 期信息
                 const time = new Date();
+                await this.ctx.service.tenderCache.updateStageCache4DelTimes(transaction, this.ctx.stage, nowTimes);
                 await transaction.update(this.ctx.service.stage.tableName, {
                     id: this.ctx.stage.id,
                     status: auditConst.status.checkNo,
@@ -1931,6 +1933,10 @@ module.exports = app => {
             try {
                 const auditors = await this.getAuditGroupByList(stageId, times);
                 const now_audit = this._.find(auditors, { aid: data.old_aid });
+                if (data.operate !== 'del') {
+                    const exist = await this.getDataByCondition({ sid: stageId, times, aid: data.new_aid });
+                    if (exist) throw '该审核人已存在,请勿重复添加';
+                }
                 if (data.operate === 'add') {
                     if (now_audit.status !== auditConst.status.uncheck && now_audit.status !== auditConst.status.checking) {
                         throw '当前人下无法操作新增';
@@ -2031,6 +2037,21 @@ module.exports = app => {
             return group;
         }
 
+        async getUniqUserGroup(stageId, times) {
+            const group = await this.getAuditorGroup(stageId, times);
+            const sql =
+                'SELECT pa.`id` As aid, pa.`name`, pa.`company`, pa.`role`, ? As times, ? As sid, 0 As `order`, 1 As audit_type, 0 As audit_order' +
+                '  FROM ' + this.ctx.service.stage.tableName + ' As s' +
+                '  LEFT JOIN ' + this.ctx.service.projectAccount.tableName + ' As pa' +
+                '  ON s.user_id = pa.id' +
+                '  WHERE s.id = ?';
+            const sqlParam = [times, stageId, stageId];
+            const user = await this.db.queryOne(sql, sqlParam);
+            user.audit_order = 0;
+            group.unshift([ user ]);
+            return this.ctx.helper.groupAuditorsUniq(group);
+        }
+
         async getAuditorHistory(stageId, times, reverse = false) {
             const history = [];
             if (times >= 1) {

+ 3 - 1
app/service/stage_bills.js

@@ -234,7 +234,9 @@ module.exports = app => {
                 d.negative_qc_qty = this.round(insertData.negative_qc_qty, precision.value);
                 d.negative_qc_tp = this.ctx.helper.mul(d.negative_qc_qty, ledgerData.unit_price, info.decimal.tp);
             }
-            d.qc_minus_qty = insertData.qc_minus_qty ? this.round(insertData.qc_minus_qty, precision.value) : 0;
+            if (insertData.qc_minus_qty !== undefined) {
+                d.qc_minus_qty = insertData.qc_minus_qty ? this.round(insertData.qc_minus_qty, precision.value) : 0;
+            }
             if (insertData.postil) {
                 d.postil = insertData.postil;
             }

+ 8 - 7
app/service/stage_change.js

@@ -89,9 +89,10 @@ class autoUseChange {
         this.changeDetail.push({
             tid: this.default.tid, sid: this.default.sid,
             lid: billsPos.lid, pid: billsPos.pid + '', cid: bills.cid, cbid: bills.id,
-            qty: bills.valid_qty, stimes: 1, sorder: 0, unit_price: bills.unit_price, minus, no_value: false,
+            qty: bills.valid_qty, stimes: 1, sorder: 0, unit_price: bills.unit_price, minus, no_value: !bills.is_valuation,
         });
 
+        if (!bills.is_valuation) return;
         if (billsPos.pid !== '-1') {
             let cp = this.changePos[billsPos.pid];
             if (!cp) {
@@ -582,13 +583,13 @@ module.exports = app => {
             });
             return filter.map(x => {
                 const b = bills.find(y => { return y.id === x.lid });
+                const rela_b = b
+                    ? {ledger_id: b ? b.ledger_id : -1, l_code: b.b_code, l_name: b.name, l_unit: b.unit, l_up: b.unit_price,
+                        l_deal_qty: b.deal_qty, l_deal_tp: b.deal_tp, l_qty: b.quantity, l_tp: b.total_price, l_drawing_code: b.drawing_code}
+                    : { ledger_id:  -1, l_code: '', l_name: '', l_unit: '', l_up: 0, l_deal_qty: 0, l_deal_tp: 0, l_qty: 0, l_tp: 0, l_drawing_code: '' };
                 const p = pos.find(y => { return y.id === x.pid });
-                return {
-                    ...x,
-                    ledger_id: b ? b.ledger_id : -1, l_code: b.b_code, l_name: b.name, l_unit: b.unit, l_up: b.unit_price,
-                    l_deal_qty: b.deal_qty, l_deal_tp: b.deal_tp, l_qty: b.quantity, l_tp: b.total_price, l_drawing_code: b.drawing_code,
-                    p_name: p ? p.name : '', p_drawing_code: p ? p.drawing_code : '', p_qty: p ? p.quantity : '',
-                };
+                const rela_p = p ? { p_name: p.name, p_drawing_code: p.drawing_code, p_qty: p.quantity } : { p_name: '', p_drawing_code: '', p_qty: 0 };
+                return { ...x, ...rela_b, ...rela_p };
             });
         }
 

+ 9 - 3
app/service/stage_change_final.js

@@ -85,8 +85,10 @@ module.exports = app => {
             if (!changeBills) return undefined;
 
             const qty = parseFloat(changeBills.samount);
+            const qtyDecimal = this.ctx.helper.findDecimal(changeBills.unit);
+            const limitQty = this.ctx.helper.mul(qty, this.ctx.helper.div(changeBills.delimit, 100, 2), qtyDecimal);
             const usedQty = await this.db.queryOne('Select SUM(qty) as qty FROM ' + this.tableName + ' WHERE cbid = ?', [cbid]);
-            return usedQty ? this.ctx.helper.sub(qty, usedQty.qty) : qty;
+            return usedQty ? this.ctx.helper.sub(limitQty, usedQty.qty) : limitQty;
         }
 
         async getListChangeBillsValidQty(tid, cbid) {
@@ -102,7 +104,9 @@ module.exports = app => {
 
             return changeBills.filter(cb => {
                 cb.qty = parseFloat(cb.samount);
-                cb.valid_qty = self.ctx.helper.sub(cb.qty, cb.used_qty);
+                const qtyDecimal = self.ctx.helper.findDecimal(changeBills.unit);
+                cb.limitQty = self.ctx.helper.mul(qty, self.ctx.helper.div(changeBills.delimit, 100, 2), qtyDecimal);
+                cb.valid_qty = self.ctx.helper.sub(cb.limitQty, cb.used_qty);
                 return cb.valid_qty;
             });
         }
@@ -120,7 +124,9 @@ module.exports = app => {
 
             return changeBills.filter(cb => {
                 cb.qty = parseFloat(cb.samount);
-                cb.valid_qty = self.ctx.helper.sub(cb.qty, cb.used_qty);
+                const qtyDecimal = self.ctx.helper.findDecimal(changeBills.unit);
+                cb.limitQty = self.ctx.helper.mul(qty, self.ctx.helper.div(changeBills.delimit, 100, 2), qtyDecimal);
+                cb.valid_qty = self.ctx.helper.sub(cb.limitQty, cb.used_qty);
                 return cb.valid_qty;
             });
         }

+ 17 - 15
app/service/stage_pay.js

@@ -208,7 +208,7 @@ module.exports = app => {
             const datas = saveData instanceof Array ? saveData : [saveData], updateDatas = [];
             const stagePays = await this.getStagePay(this.ctx.stage, this._.map(datas, 'pid'));
             for (const data of datas) {
-                const stagePay = stagePays.find(function (x) {
+                const stagePay = stagePays.find(x => {
                     return x.pid === data.pid;
                 });
                 const updateData = {id: stagePay.id};
@@ -227,9 +227,9 @@ module.exports = app => {
 
         /**
          * 计算
-         * @param stage
-         * @param transaction
-         * @returns {Promise<boolean>}
+         * @param {object} stage - 期数据
+         * @param {object} transaction - 事务
+         * @return {Promise<Array>} 计算结果(应付和实付数据)
          */
         async calcAllStagePays(stage, transaction) {
             if (!stage || !transaction) {
@@ -287,11 +287,11 @@ module.exports = app => {
 
         /**
          * 拷贝上一操作人数据 为 下一操作人数据
-         * @param stage - 期数据
-         * @param times - 下一操作人 该期第几次
-         * @param order - 下一操作人顺序
-         * @param transaction - 事务
-         * @returns {Promise<*>}
+         * @param {object} stage - 期数据
+         * @param {int} times - 下一操作人 该期第几次
+         * @param {int} order - 下一操作人顺序
+         * @param {object} transaction - 事务
+         * @return {Promise<*>} 初始化sql执行结果
          */
         async copyAuditStagePays(stage, times, order, transaction, curTimes = stage.curTimes, curOrder = stage.curOrder) {
             if (!stage || !transaction || !times || order === undefined) {
@@ -332,12 +332,14 @@ module.exports = app => {
         }
 
         /**
-         * 拷贝上一操作人数据 为 下一操作人数据
-         * @param stage - 期数据
-         * @param times - 下一操作人 该期第几次
-         * @param order - 下一操作人顺序
-         * @param transaction - 事务
-         * @returns {Promise<*>}
+         * 删除审批流程时,拷贝数据
+         * @param {object} stage - 期数据
+         * @param {int} times - 拷贝至 该期第几次
+         * @param {int} order - 拷贝至 槽作人顺序
+         * @param {int} copyTimes - 源 该期第几次
+         * @param {int} copyOrder - 源 操作人顺序
+         * @param {object} transaction - 事务
+         * @return {Promise<*>} 初始化sql执行结果
          */
         async copyStagePays4DeleteTimes(stage, times, order, copyTimes, copyOrder, transaction) {
             const sql = 'INSERT INTO ?? (`tid`, `sid`, `pid`, `stimes`, `sorder`, `name`, `tp`, `expr`, `pause`,' +

+ 10 - 10
app/service/stage_stash.js

@@ -265,14 +265,14 @@ module.exports = app => {
                                 insertChangeData.push(ncs);
                                 if (!ncs.no_value) {
                                     nps.qc_qty = this.ctx.helper.add(nps.qc_qty, ncs.qty);
+                                    if (ncs.minus) {
+                                        nps.negative_qc_qty = this.ctx.helper.add(nps.negative_qc_qty, ncs.qty);
+                                    } else {
+                                        nps.positive_qc_qty = this.ctx.helper.add(nps.positive_qc_qty, ncs.qty);
+                                    }
                                 } else {
                                     nps.qc_minus_qty = this.ctx.helper.add(nps.qc_minus_qty, ncs.qty);
                                 }
-                                if (ncs.minus) {
-                                    nps.negative_qc_qty = this.ctx.helper.add(nps.negative_qc_qty, ncs.qty);
-                                } else {
-                                    nps.positive_qc_qty = this.ctx.helper.add(nps.positive_qc_qty, ncs.qty);
-                                }
                             }
                         }
                         nbs.contract_qty = this.ctx.helper.add(nbs.contract_qty, nps.contract_qty);
@@ -300,14 +300,14 @@ module.exports = app => {
                                 insertChangeData.push(ncs);
                                 if (!ncs.no_value) {
                                     nbs.qc_qty = this.ctx.helper.add(nbs.qc_qty, ncs.qty);
+                                    if (ncs.minus) {
+                                        nbs.negative_qc_qty = this.ctx.helper.add(nbs.negative_qc_qty, ncs.qty);
+                                    } else {
+                                        nbs.positive_qc_qty = this.ctx.helper.add(nbs.positive_qc_qty, ncs.qty);
+                                    }
                                 } else {
                                     nbs.qc_minus_qty = this.ctx.helper.add(nbs.qc_minus_qty, ncs.qty);
                                 }
-                                if (ncs.minus) {
-                                    nbs.negative_qc_qty = this.ctx.helper.add(nbs.negative_qc_qty, ncs.qty);
-                                } else {
-                                    nbs.positive_qc_qty = this.ctx.helper.add(nbs.positive_qc_qty, ncs.qty);
-                                }
                             }
                         }
                         nbs.qc_tp = this.ctx.helper.mul(nbs.qc_qty, b.unit_price, decimal.tp);

+ 148 - 0
app/service/sub_proj_info.js

@@ -0,0 +1,148 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date
+ * @version
+ */
+
+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: 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, },
+        { id: 8, name: '涵洞', unit: 'm/道', dgn_qty: 0, final_qty: 0, },
+        { id: 9, name: '隧道', unit: 'm/座', dgn_qty: 0, final_qty: 0, },
+        { id: 10, name: '分离式立交', unit: '处', dgn_qty: 0, final_qty: 0, },
+        { id: 11, name: '通道、天桥', unit: '座', dgn_qty: 0, final_qty: 0, },
+        { id: 12, name: '平面交叉', unit: '处', dgn_qty: 0, final_qty: 0, },
+        { id: 13, name: '互通式立交', unit: 'km/处', dgn_qty: 0, final_qty: 0, },
+        { id: 14, name: '连接线长度、辅导长度', unit: 'km', dgn_qty: 0, final_qty: 0, },
+        { id: 15, name: '管理及养护房屋', unit: 'm2', dgn_qty: 0, final_qty: 0, },
+    ],
+    gcl_quantity: [
+        { id: 1, pid: -1, full_path: '1', level: 1, is_leaf: 1, order: 1, name: '主要人工消耗', unit: '工日', dgn_qty: 0, final_qty: 0, },
+        { id: 2, pid: -1, full_path: '2', level: 1, is_leaf: 0, order: 2, name: '主要材料消耗', unit: '', dgn_qty: 0, final_qty: 0, },
+        { id: 3, pid: 2, full_path: '2-3', level: 2, is_leaf: 1, order: 1, name: '钢材', unit: '吨', dgn_qty: 0, final_qty: 0, },
+        { id: 4, pid: 2, full_path: '2-4', level: 2, is_leaf: 1, order: 2, name: '沥青', unit: '吨', dgn_qty: 0, final_qty: 0, },
+        { id: 5, pid: 2, full_path: '2-5', level: 2, is_leaf: 1, order: 3, name: '汽油、柴油', unit: '吨', dgn_qty: 0, final_qty: 0, },
+        { id: 6, pid: 2, full_path: '2-6', level: 2, is_leaf: 1, order: 4, name: '水泥', unit: '吨', dgn_qty: 0, final_qty: 0, },
+        { id: 7, pid: 2, full_path: '2-7', level: 2, is_leaf: 1, order: 5, name: '碎石、砂', unit: 'm3', dgn_qty: 0, final_qty: 0, },
+        { id: 8, pid: 2, full_path: '2-8', level: 2, is_leaf: 1, order: 6, name: '电', unit: 'km.h', dgn_qty: 0, final_qty: 0, },
+        { id: 9, pid: -1, full_path: '9', level: 1, is_leaf: 1, order: 3, name: '主要机械消耗', unit: '台班', dgn_qty: 0, final_qty: 0, },
+    ],
+};
+
+module.exports = app => {
+
+    class SubProjInfo extends app.BaseService {
+
+        /**
+         * 构造函数
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        constructor(ctx) {
+            super(ctx);
+            this.tableName = 'sub_project_info';
+        }
+
+        async addInfo(id, project_id, transaction) {
+            const info = { id, project_id };
+            for (const di in defaultInfo) {
+                info[di] = JSON.stringify(defaultInfo[di]);
+            }
+
+            if (transaction) {
+                await transaction.insert(this.tableName, info);
+            } else {
+                await this.db.insert(this.tableName, info);
+            }
+            return info;
+        }
+
+        /**
+         * 获取标段相关信息
+         * @param tenderId
+         * @return {Promise<void>}
+         */
+        async getInfo(id) {
+            let info = await this.getDataByCondition({ id });
+            // 兼容不存在info的情况
+            if (!info) info = await this.addInfo(id, this.ctx.session.sessionProject.id);
+
+            for (const ai in defaultInfo) {
+                info[ai] = !info[ai] || info[ai] === '' ? defaultInfo[ai] : JSON.parse(info[ai]);
+            }
+            return info;
+        }
+
+        async getInfo4Report(id) {
+            let info = await this.getDataByCondition({ id });
+            // 兼容不存在info的情况
+            if (!info) info = await this.addInfo(id, this.ctx.session.sessionProject.id);
+
+            for (const ai in defaultInfo) {
+                info[ai] = !info[ai] || info[ai] === '' ? defaultInfo[ai] : JSON.parse(info[ai]);
+                info[ai].forEach(x => {
+                    x.type = ai;
+                });
+            }
+            return info;
+        }
+
+        async _saveCommonInfo(id, data) {
+            const updateData = { id };
+            for (const d in data) {
+                if (d === 'id' || d === 'project_id') continue;
+                if (defaultInfo[d]) continue;
+
+                updateData[d] = data[d];
+            }
+
+            await this.db.update(this.tableName, updateData);
+            return updateData;
+        }
+
+        async _saveArrayInfo(id, type, data) {
+            const info = await this.getInfo(id);
+            const source = info[type];
+            const result = [];
+            for (const d of data) {
+                const s = source.find(x => { return x.id === d.id });
+                if (d.dgn_qty !== undefined) s.dgn_qty = d.dgn_qty || 0;
+                if (d.final_qty !== undefined) s.final_qty = d.final_qty || 0;
+                result.push(s);
+            }
+            const updateData = { id };
+            updateData[type] = JSON.stringify(source);
+            await this.db.update(this.tableName, updateData);
+            return result;
+        }
+
+        /**
+         * 保存标段相关信息
+         *
+         * @param data
+         * @return {Promise<void>}
+         */
+        async saveInfo(id, data) {
+            switch (data.type) {
+                case 'main_quantity':
+                case 'gcl_quantity':
+                    return await this._saveArrayInfo(id, data.type, data.updateData);
+                default:
+                    return await this._saveCommonInfo(id, data.updateData);
+            }
+        }
+    }
+
+    return SubProjInfo;
+};

+ 109 - 13
app/service/sub_project.js

@@ -96,10 +96,35 @@ module.exports = app => {
         }
 
         async getLastChild(tree_pid) {
-            const result = await this.getAllDataByCondition({ where: { tree_pid }, orders: [['tree_order', 'desc']], limit: 1, offset: 0 });
+            const result = await this.getAllDataByCondition({ where: { tree_pid, project_id: this.ctx.session.sessionProject.id }, orders: [['tree_order', 'desc']], limit: 1, offset: 0 });
             return result[0];
         }
 
+        async getPosterityData(id){
+            const result = [];
+            let cur = await this.getAllDataByCondition({ where: { tree_pid: id, project_id: this.ctx.session.sessionProject.id } });
+            let iLevel = 1;
+            while (cur.length > 0 && iLevel < 6) {
+                result.push(...cur);
+                cur = await this.getAllDataByCondition({ where: { tree_pid: cur.map(x => { return x.id })} });
+                iLevel += 1;
+            }
+            return result;
+        }
+
+        async getStepNode(node, step) {
+            const tree_order = [];
+            while(step) {
+                tree_order.push(node.tree_order + step);
+                if (step > 0) {
+                    step = step - 1;
+                } else {
+                    step = step + 1;
+                }
+            }
+            return await this.getAllDataByCondition({ where: { tree_pid: node.tree_pid, tree_order, project_id: this.ctx.session.sessionProject.id }, orders: [['tree_order', 'asc']]});
+        }
+
         async addFolder(data) {
             const parent = await this.getDataById(data.tree_pid);
             if (parent && !parent.is_folder) throw '添加数据结构错误';
@@ -163,18 +188,6 @@ module.exports = app => {
             }
         }
 
-        async getPosterityData(id){
-            const result = [];
-            let cur = await this.getAllDataByCondition({ where: { tree_pid: id } });
-            let iLevel = 1;
-            while (cur.length > 0 && iLevel < 6) {
-                result.push(...cur);
-                cur = await this.getAllDataByCondition({ where: { tree_pid: cur.map(x => { return x.id })} });
-                iLevel += 1;
-            }
-            return result;
-        }
-
         async dragTo(data) {
             const dragNode = await this.getDataById(data.drag_id);
             const dropNode = await this.getDataById(data.drop_id);
@@ -206,6 +219,89 @@ module.exports = app => {
             return await this.getSubProject(this.ctx.session.sessionProject.id, this.ctx.session.sessionUser.accountId, this.ctx.session.sessionUser.is_admin);
         }
 
+        async _siblingMove(node, step) {
+            const stepNode = await this.getStepNode(node, step);
+
+            const conn = await this.db.beginTransaction();
+            try {
+                const updateData = [];
+                updateData.push({ id: node.id, tree_order: node.tree_order + step });
+                for (const sn of stepNode) {
+                    updateData.push({ id: node.id, tree_order: step > 0 ? sn.tree_order - 1 : sn.tree_order + 1 });
+                }
+                await conn.updateRows(this.tableName, updateData);
+                await conn.commit();
+            } catch (error) {
+                await conn.rollback();
+                throw error;
+            }
+        }
+
+        async _siblingMoveForce(node, step) {
+            const sibling = await this.getAllDataByCondition({ where: { tree_pid: node.tree_pid, project_id: this.ctx.session.sessionProject.id }, orders: [['tree_order', 'asc']] });
+            const nodeIndex = sibling.findIndex(x => { return x.id === node.id });
+            if (nodeIndex + step < 0) throw '移动数据结构错误';
+            if (nodeIndex + step > sibling.length - 1) throw '移动数据结构错误';
+
+            const conn = await this.db.beginTransaction();
+            try {
+                const updateData = [];
+                updateData.push({ id: node.id, tree_order: sibling[nodeIndex + step].tree_order });
+                while(step) {
+                    const stepNode = sibling[nodeIndex + step];
+                    if (step > 0) {
+                        updateData.push({ id: stepNode.id, tree_order: sibling[nodeIndex + step - 1].tree_order });
+                        step = step - 1;
+                    } else {
+                        updateData.push({ id: stepNode.id, tree_order: sibling[nodeIndex + step + 1].tree_order});
+                        step = step + 1;
+                    }
+                }
+                await conn.updateRows(this.tableName, updateData);
+                await conn.commit();
+            } catch (error) {
+                await conn.rollback();
+                throw error;
+            }
+        }
+
+        async _topMove(node) {
+            const lastChild = await this.getLastChild(rootId);
+            const posterity = await this.getPosterityData(node.id);
+
+            const conn = await this.db.beginTransaction();
+            try {
+                const updateData = { id: node.id, tree_pid: rootId, tree_level: 1, tree_order: lastChild ? lastChild.tree_order + 1 : 1 };
+                await conn.update(this.tableName, updateData);
+                if (node.tree_level !== 1 && posterity.length > 0) {
+                    const posterityUpdateData = posterity.map(x => {
+                        return { id: x.id, tree_level: x.tree_level - node.tree_level + 1 }
+                    });
+                    await conn.updateRows(this.tableName, posterityUpdateData);
+                }
+                // 升级原来的后项的order
+                await conn.query(`UPDATE ${this.tableName} SET tree_order = tree_order-1 WHERE tree_pid = ? AND tree_order > ?`, [node.tree_pid, node.tree_order]);
+                await conn.commit();
+            } catch (error) {
+                await conn.rollback();
+                throw error;
+            }
+        }
+
+        async move(data) {
+            const node = await this.getDataById(data.id);
+            if (!node) throw '移动数据结构错误';
+
+            switch(data.type) {
+                case 'up': await this._siblingMoveForce(node, -1); break;
+                case 'down': await this._siblingMoveForce(node, 1); break;
+                case 'top': await this._topMove(node); break;
+                default: throw '未知移动类型';
+            }
+
+            return await this.getSubProject(this.ctx.session.sessionProject.id, this.ctx.session.sessionUser.accountId, this.ctx.session.sessionUser.is_admin);
+        }
+
         async del(id) {
             const node = await this.getDataById(id);
             if (!node) throw '删除的数据不存在';

+ 36 - 0
app/service/tender_cache.js

@@ -188,6 +188,42 @@ module.exports = app => {
             }
         }
 
+        async updateStageCache4DelTimes(transaction, stage, times) {
+            const data = { id: stage.tid };
+            const tp = {
+                contract_tp: stage.contract_tp || 0, qc_tp: stage.qc_tp || 0, contract_pc_tp: stage.contract_pc_tp, qc_pc_tp: stage.qc_pc_tp, pc_tp: stage.pc_tp,
+                positive_qc_tp: stage.positive_qc_tp, positive_qc_pc_tp: stage.positive_qc_pc_tp, negative_qc_tp: stage.negative_qc_pc_tp, negative_qc_pc_tp: stage.negative_qc_pc_tp,
+                yf_tp: stage.yf_tp, sf_tp: stage.sf_tp,
+                pre_contract_tp: stage.pre_contract_tp, pre_qc_tp: stage.pre_qc_tp,
+                pre_positive_qc_tp: stage.pre_positive_qc_tp, pre_negative_qc_tp: stage.pre_positive_qc_tp,
+                pre_yf_tp: stage.pre_yf_tp, pre_sf_tp: stage.pre_sf_tp,
+            };
+            data.stage_flow_cur_uid = stage.user_id;
+            const curInfo = await this.ctx.service.projectAccount.getAccountCacheData(stage.user_id,
+                { order: stage.order, audit_order: 0, audit_type: auditConst.auditType.key.common, status: auditConst.stage.status.uncheck });
+            data.stage_flow_cur_info = JSON.stringify(curInfo);
+            const preAuditors = await this.ctx.service.stageAudit.getLastestAuditors(stage.id, times - 1, auditConst.stage.status.checkNo);
+            const preAuditorIds = preAuditors.map(x => { return x.aid; });
+            data.stage_flow_pre_uid = preAuditorIds.join(',');
+            data.stage_flow_pre_info = preAuditors.length > 0 ? JSON.stringify(preAuditors.map(preAuditor => { return {
+                order: stage.order, audit_type: preAuditor.audit_type, audit_order: preAuditor.order, status: preAuditor.status, time: preAuditor.end_time,
+                name: preAuditor.name, company: preAuditor.company, role: preAuditor.role, mobile: preAuditor.mobile, telephone: preAuditor.telephone,
+            }})) : '';
+            const his = stage.tp_history.find(x => { return x.times === times && x.order === preAuditors[0].order; });
+            if (his) {
+                tp.contract_tp = his.contract_tp;
+                tp.qc_tp = his.qc_tp;
+                tp.positive_qc_tp = his.positive_qc_tp;
+                tp.negative_qc_tp = his.negative_qc_tp;
+                tp.yf_tp = his.yf_tp;
+                tp.sf_tp = his.sf_tp;
+            }
+
+            data.stage_flow_cur_tp = JSON.stringify(tp);
+            data.stage_flow_pre_tp = JSON.stringify(tp);
+            await transaction.update(this.tableName, data);
+        }
+
         async updateStageCache4Start(transaction, stage, status, auditors, ledgerTp, stageTp) {
             const orgCache = await this.getDataById(stage.tid);
             const data = { id: stage.tid, stage_status: status };

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

@@ -23,6 +23,7 @@
     </div>
 </div>
 <script>
+    autoFlashHeight();
     const budgetList = JSON.parse(unescape('<%- escape(JSON.stringify(budgetList)) %>'));
     const category = JSON.parse(unescape('<%- escape(JSON.stringify(categoryData)) %>'));
 </script>

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

@@ -4,6 +4,7 @@
 <nav-menu title="投资估算" url="/budget/<%= ctx.budget.id %>/gu" ml="3" active="<%= ctx.url.indexOf('/gu') %>"></nav-menu>
 <nav-menu title="设计概算" url="/budget/<%= ctx.budget.id %>/gai%>" ml="3" active="<%= ctx.url.indexOf('/gai') %>"></nav-menu>
 <nav-menu title="施工图预算" url="/budget/<%= ctx.budget.id %>/yu" ml="3" active="<%= ctx.url.indexOf('/yu') %>"></nav-menu>
+<nav-menu title="招标预算" url="/budget/<%= ctx.budget.id %>/zb" ml="3" active="<%= ctx.url.indexOf('/zb') %>"></nav-menu>
 <% if (!ctx.budget.readOnly && ctx.url.indexOf('/compare') === -1 && ctx.url !== '/budget/' + ctx.budget.id) { %>
 <div class="contarl-box"><button class="btn btn-primary btn-sm btn-block" data-toggle="modal" data-target="#budget-set">设置</button></div>
 <% } %>

+ 21 - 4
app/view/change/index.ejs

@@ -41,7 +41,7 @@
                 </div>
                 <div class="d-inline-block">
                     <div class="btn-group">
-                        <button type="button" class="btn btn-sm btn-light text-primary dropdown-toggle" data-toggle="dropdown" id="zhankai"><% if (status !== 0) { %><%- filter.statusString[status] %>(<%- filter.count[status] %>)<% } else { %>全部<% } %></button>
+                        <button type="button" class="btn btn-sm btn-light text-primary dropdown-toggle" data-toggle="dropdown" id="zhankai" data-value="<%- status %>"><% if (status !== 0) { %><%- filter.statusString[status] %>(<%- filter.count[status] %>)<% } else { %>全部<% } %></button>
                         <div class="dropdown-menu" aria-labelledby="zhankai" id="status_select">
                             <% if (status !== 0) { %><a class="dropdown-item" data-val="0" href="javascript:void(0);">全部</a><% } %>
                             <% for (const fs in filter.status) { %>
@@ -51,6 +51,19 @@
                         </div>
                     </div>
                 </div>
+                <% if (ctx.session.sessionProject.page_show.openChangeState) { %>
+                <div class="d-inline-block">
+                    <div class="btn-group">
+                        <button type="button" class="btn btn-sm btn-light text-primary dropdown-toggle" data-toggle="dropdown" id="state_zhankai" data-value="<%- state %>">变更令状态:<% if (state !== 0) { %><%- ctx.helper._.find(changeState, { order: state }).name %>(<%- ctx.helper._.find(changeState, { order: state }).count %>)<% } else { %>全部<% } %></button>
+                        <div class="dropdown-menu" aria-labelledby="state_zhankai" id="state_select">
+                            <% if (state !== 0) { %><a class="dropdown-item" data-val="0" href="javascript:void(0);">全部</a><% } %>
+                            <% for (const cs of changeState) { %>
+                                <% if (cs.order !== state) { %><a class="dropdown-item" data-val="<%- cs.order %>" href="javascript:void(0);"><%- cs.name %>(<%- cs.count %>)</a><% } %>
+                            <% } %>
+                        </div>
+                    </div>
+                </div>
+                <% } %>
                 <div class="d-inline-block">
                     <span class="ml-3">本页小计:<%- page_total %>元</span><span class="ml-3">合计:<%- tp %>元</span>
                 </div>
@@ -73,9 +86,10 @@
                     <thead>
                     <tr>
                         <th width="18%" id="sort_change">申请编号/变更令号</th><th width="24%">变更工程名称</th>
-                        <th width="8%">变更性质</th><th width="8%">变更金额</th>
-                        <th width="8%">正变更金额</th><th width="8%">负变更金额</th>
-                        <th width="10%">审批状态</th><th width="12%">审批进度</th><th width="4%"></th>
+                        <th width="8%">变更性质</th><% if (ctx.session.sessionProject.page_show.openChangeState) { %><th width="8%">变更令状态</th><% } %>
+                        <th width="8%">变更金额</th><th width="8%">正变更金额</th>
+                        <th width="8%">负变更金额</th><th width="10%">审批状态</th>
+                        <th width="12%">审批进度</th><th width="4%"></th>
                     </tr>
                     </thead>
                     <tbody id="changeList">
@@ -89,6 +103,9 @@
                         <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>
+                        <% if (ctx.session.sessionProject.page_show.openChangeState) { %>
+                        <td><%- ctx.helper._.find(changeState, { order: c.state }).name %></td>
+                        <% } %>
                         <td style="text-align: right"><%= ctx.helper.roundNum(c.total_price, tpUnit) %></td>
                         <td style="text-align: right"><%= ctx.helper.roundNum(c.positive_tp, tpUnit) %></td>
                         <td style="text-align: right"><%= ctx.helper.roundNum(c.negative_tp, tpUnit) %></td>

+ 56 - 6
app/view/change/information.ejs

@@ -26,13 +26,15 @@
                 <% } %>
                 </div>
                 <% if (auditStatus === 1 || auditStatus === 2 || auditStatus === 9) { %>
-                    <div class="d-inline-block ml-3">
+                    <div class="d-inline-block ml-1">
                         <a href="#addlist" data-toggle="modal" class="btn btn-sm btn-light text-primary" id="open-list-modal" data-toggle="tooltip" data-placement="bottom" title="" data-original-title="添加清单"><i class="fa fa-plus" aria-hidden="true"></i> <span class="order_text"><% if (change.order_by === 0) { %>添加<% } else { %>插入<% } %></span>台账清单</a>
                     </div>
+                    <% if (ctx.session.sessionProject.page_show.openChangeWhiteList) { %>
                     <div class="d-inline-block mr-1">
                         <a href="javascript:void(0);" class="btn btn-sm btn-light text-primary" id="add-white-btn" data-original-title="添加清单"><i class="fa fa-plus" aria-hidden="true"></i> <span class="order_text"><% if (change.order_by === 0) { %>添加<% } else { %>插入<% } %></span>空白清单</a>
                     </div>
-                    <div class="d-inline-block mr-3">
+                    <% } %>
+                    <div class="d-inline-block mr-2">
                         <button type="button" class="btn btn-sm btn-light text-primary dropdown-toggle" data-toggle="dropdown" id="bpaixu">清单排序:<% if (change.order_by === 0) { %>清单编号<% } else { %>添加顺序<% } %></button>
                         <div class="dropdown-menu" aria-labelledby="bpaixu">
                             <ul class="list-unstyled px-3 mb-0">
@@ -57,16 +59,30 @@
                     </div>
                 <% } %>
                 <% if (showPlanBtn) { %>
-                <div class="d-inline-block ml-3">
+                <div class="d-inline-block mr-2">
                     <a class="btn btn-sm btn-primary" href="#bgfadb" data-toggle="modal" data-target="#bgfadb">差值对比</a>
                 </div>
                 <% } %>
+                <div class="d-inline-block mr-2">
+                    <a class="btn btn-sm btn-primary" href="#qdgather" data-toggle="modal" data-target="#qdgather">清单汇总</a>
+                </div>
+                <div class="d-inline-block mr-2">
+                    <div class="input-group input-group-sm">
+                        <div class="input-group-prepend">
+                            <span class="input-group-text" id="basic-addon1">表达式</span>
+                        </div>
+                        <input type="text" class="form-control form-control-sm m-0" id="camount-expr" readonly="readOnly" autocomplete="off">
+                    </div>
+                </div>
                 <div class="pull-right mr-3" id="sp-btn">
                     <!--info状态区分-->
                     <% if (auditStatus === 1) { %>
                         <a href="#sub-ap" data-category="up_change" data-toggle="modal" data-target="#sub-ap" class="btn btn-primary btn-sm">上报审批</a>
                     <% } else if (auditStatus === 2 || auditStatus === 9) { %>
                         <a href="#sub-sp2" data-category="up_change" data-toggle="modal" data-target="#sub-sp2" class="btn btn-primary btn-sm">重新上报</a>
+                        <% if (auditStatus === 9) { %>
+                            <a href="#sub-revoke" data-toggle="modal" data-target="#sub-revoke" class="btn btn-warning btn-sm">撤销修订</a>
+                        <% } %>
                     <% } else if (auditStatus === 3) { %>
                         <a href="#sp-list" data-toggle="modal" data-target="#sp-list" class="btn btn-outline-warning btn-sm text-muted">审批退回</a>
                     <% } else if (auditStatus === 4) { %>
@@ -100,7 +116,7 @@
                 <!--info状态区分-->
                 <% if (auditStatus === 1 || auditStatus === 2 || auditStatus === 9) { %>
                     <div class="pull-right px-3" id="show-save-btn" style="display: none">
-                        <span>您修改了变更信息,记得保存修改。</span>
+<!--                        <span>您修改了变更信息,记得保存修改。</span>-->
                         <button class="btn btn-sm btn-primary save_change_btn" id="save_change"><i class="fa fa-save"></i> 保存修改</button>
                         <button class="btn btn-sm btn-light" id="cancel_change">取消</button>
                     </div>
@@ -139,6 +155,19 @@
                         <div class="sjs-sh-1" style="overflow-y: auto;">
                             <% if (auditStatus === 1 || auditStatus === 2 || auditStatus === 9) { %>
                             <form class="p-2" action="/tender/<%- change.tid %>/change/<%- change.cid %>/information/save?_csrf_j=<%= ctx.csrf %>" method="post" id="change_form">
+                                <% if (ctx.session.sessionProject.page_show.openChangeState) { %>
+                                <div class="form-group">
+                                    <label>变更状态</label>
+                                    <div>
+                                        <% for (const cs of changeState) { %>
+                                        <div class="form-check form-check-inline">
+                                            <input class="form-check-input" type="radio" id="state_<%- cs.order %>" value="<%- cs.order %>" name="state" <% if (cs.order === change.state) { %>checked<% } %>>
+                                            <label class="form-check-label" for="state_<%- cs.order %>"><%- cs.name %></label>
+                                        </div>
+                                        <% } %>
+                                    </div>
+                                </div>
+                                <% } %>
                                 <div class="form-group">
                                     <label><b class="text-danger">*&nbsp;</b>申请编号</label>
                                     <a href="javascript:void(0);" class="pull-right reduction-code" data-toggle="tooltip" data-placement="bottom" title="" data-code="<%- change.code %>" data-original-title="重置"><i class="fa fa-repeat"></i></a>
@@ -223,6 +252,9 @@
                                         <% for (const company of companyList) { %>
                                             <option <% if (company.name === change.company) { %>selected<% } %>><%- company.name %></option>
                                         <% } %>
+                                        <% for (const u of unitList) { %>
+                                            <option <% if (u.name === change.company) { %>selected<% } %>><%- u.name %></option>
+                                        <% } %>
                                     </select>
                                 </div>
                                 <div class="form-group">
@@ -243,6 +275,18 @@
                             </form>
                             <% } else { %>
                             <form class="p-2">
+                                <% if (ctx.session.sessionProject.page_show.openChangeState) { %>
+                                <div class="form-group">
+                                    <label>变更状态</label>
+                                    <div>
+                                        <% const cState = ctx.helper._.find(changeState, { order: change.state }) %>
+                                        <div class="form-check form-check-inline">
+                                            <input class="form-check-input" type="radio" id="state_<%- cState.order %>" name="state" disabled checked>
+                                            <label class="form-check-label" for="state_<%- cState.order %>"><%- cState.name %></label>
+                                        </div>
+                                    </div>
+                                </div>
+                                <% } %>
                                 <div class="form-group">
                                     <label>申请编号</label>
                                     <input class="form-control form-control-sm" value="<%- change.code %>" type="text" readonly>
@@ -461,6 +505,8 @@
             }
         }
     }
+    const openChangeState = <%- ctx.session.sessionProject.page_show.openChangeState ? true : false %>;
+    const startLimit = 9;
 </script>
 <% if (auditStatus === 1 || auditStatus === 2 || auditStatus === 9) { %>
 <script>
@@ -473,6 +519,7 @@
     const changePosList = JSON.parse(unescape('<%- escape(JSON.stringify(changePosList)) %>'));
     const shenpi_status = <%- ctx.tender.info.shenpi.change %>;
     const shenpiConst = JSON.parse('<%- JSON.stringify(shenpiConst) %>');
+    const unitList = JSON.parse(unescape('<%- escape(JSON.stringify(unitList)) %>'));
     const changesUid = <%- change.uid %>;
 
     let back_changeInfo = {
@@ -490,17 +537,20 @@
         type: '<%- change.type %>',
         class: '<%- ctx.helper._.find(changeClass, { value: change.class }).checked ? change.class : changeClass[0].value %>',// 防止下拉不存在导致数据丢失上报问题
         quality: '<%- change.quality %>',
-        company: JSON.parse(unescape('<%- escape(JSON.stringify((change.company ? change.company : (companyList && companyList[0] ? companyList[0].name : '')))) %>')),
+        company: JSON.parse(unescape('<%- escape(JSON.stringify((change.company ? change.company : (companyList && companyList[0] ? companyList[0].name : (unitList && unitList[0] ? unitList[0].name : ''))))) %>')),
         charge: '<%- change.charge %>',
+        state: '<%- change.state %>',
         w_code: JSON.parse(unescape('<%- escape(JSON.stringify(change.w_code ? change.w_code : '')) %>')),
     };
     let changeInfo = Object.assign({}, back_changeInfo);
     let changeUsedData = JSON.parse(unescape('<%- escape(JSON.stringify(changeUsedData)) %>'));
     let changeOrder = parseInt('<%- change.order_by %>');
+    const openChangeWhiteList = <%- ctx.session.sessionProject.page_show.openChangeWhiteList %>;
+    let deLimit = parseInt('<%- deLimit %>');
     console.log(changeInfo);
     console.log(changeUsedData);
 </script>
-<script src="/public/js/change_information_set.js?202206211"></script>
+<script src="/public/js/change_information_set.js"></script>
 <script src="/public/js/change_audit.js"></script>
 <% } else if (auditStatus === 3 || auditStatus === 4 || auditStatus === 5 || auditStatus === 7 || auditStatus === 8) { %>
 <script>

+ 66 - 6
app/view/change/information_modal.ejs

@@ -293,7 +293,7 @@
                                                         <% if(index < auditors.length - 1) { %>
                                                             <div class="timeline-item-tail"></div>
                                                         <% } %>
-                                                        <% if(auditor.status === auditConst.auditStatus.checked) { %>
+                                                        <% if(auditor.status === auditConst.auditStatus.checked || auditor.status === auditConst.auditStatus.cancelRevise) { %>
                                                             <div class="timeline-item-icon bg-success text-light">
                                                                 <i class="fa fa-check"></i>
                                                             </div>
@@ -350,6 +350,31 @@
             </div>
         </div>
     <% } %>
+    <% if (auditStatus === 9) { %>
+        <!--撤销修订-->
+        <div class="modal fade" id="sub-revoke"  role="dialog" aria-labelledby="myModalLabel">
+            <div class="modal-dialog" role="document">
+                <form id="reviseForm" class="modal-content" method="post" action="/tender/<%- tender.id %>/change/cancel/revise" onsubmit="return false;">
+                    <div class="modal-header">
+                        <h5 class="modal-title" id="myModalLabel">撤销修订</h5>
+                        <button type="button" class="close" data-dismiss="modal" aria-label="Close">
+                            <span aria-hidden="true">&times;</span>
+                        </button>
+                    </div>
+                    <div class="modal-body">
+                        <h5>撤销修订,所有修改的数据将全部会被还原。</h5>
+                        <h5>确认撤销修订?</h5>
+                    </div>
+                    <div class="modal-footer">
+                        <input type="hidden" name="cid" value="<%= change.cid %>">
+                        <input type="hidden" name="_csrf_j" value="<%= ctx.csrf %>" />
+                        <button type="button" class="btn btn-secondary btn-sm" data-dismiss="modal">关闭</button>
+                        <button type="button" id="cancel-revise-btn" class="btn btn-primary btn-sm">确认</button>
+                    </div>
+                </form>
+            </div>
+        </div>
+    <% } %>
 <% } %>
 
 <% if (auditStatus === 3 || auditStatus === 4 || auditStatus === 5 || auditStatus === 7 || auditStatus === 8) { %>
@@ -423,7 +448,7 @@
                                                     <% if(index < auditors.length - 1) { %>
                                                         <div class="timeline-item-tail"></div>
                                                     <% } %>
-                                                    <% if(auditor.status === auditConst.auditStatus.checked) { %>
+                                                    <% if(auditor.status === auditConst.auditStatus.checked || auditor.status === auditConst.auditStatus.cancelRevise) { %>
                                                         <div class="timeline-item-icon bg-success text-light">
                                                             <i class="fa fa-check"></i>
                                                         </div>
@@ -553,7 +578,7 @@
                                                     <% if(index < auditors.length - 1) { %>
                                                         <div class="timeline-item-tail"></div>
                                                     <% } %>
-                                                    <% if(auditor.status === auditConst.auditStatus.checked) { %>
+                                                    <% if(auditor.status === auditConst.auditStatus.checked || auditor.status === auditConst.auditStatus.cancelRevise) { %>
                                                         <div class="timeline-item-icon bg-success text-light">
                                                             <i class="fa fa-check"></i>
                                                         </div>
@@ -642,7 +667,7 @@
                                                 <% if(index < auditList3.length - 1) { %>
                                                     <div class="timeline-item-tail"></div>
                                                 <% } %>
-                                                <% if(auditor.status === auditConst.auditStatus.checked) { %>
+                                                <% if(auditor.status === auditConst.auditStatus.checked || auditor.status === auditConst.auditStatus.cancelRevise) { %>
                                                     <div class="timeline-item-icon bg-success text-light">
                                                         <i class="fa fa-check"></i>
                                                     </div>
@@ -790,7 +815,7 @@
                                                     <% if(index < auditors.length - 1) { %>
                                                         <div class="timeline-item-tail"></div>
                                                     <% } %>
-                                                    <% if(auditor.status === auditConst.auditStatus.checked) { %>
+                                                    <% if(auditor.status === auditConst.auditStatus.checked || auditor.status === auditConst.auditStatus.cancelRevise) { %>
                                                         <div class="timeline-item-icon bg-success text-light">
                                                             <i class="fa fa-check"></i>
                                                         </div>
@@ -879,7 +904,7 @@
                                                 <% if(index < auditList3.length - 1) { %>
                                                     <div class="timeline-item-tail"></div>
                                                 <% } %>
-                                                <% if(auditor.status === auditConst.auditStatus.checked) { %>
+                                                <% if(auditor.status === auditConst.auditStatus.checked || auditor.status === auditConst.auditStatus.cancelRevise) { %>
                                                     <div class="timeline-item-icon bg-success text-light">
                                                         <i class="fa fa-check"></i>
                                                     </div>
@@ -1156,6 +1181,22 @@
     </div>
 </div>
 <% } %>
+<!--多部位同条清单汇总-->
+<div class="modal fade" id="qdgather" data-backdrop="static">
+    <div class="modal-dialog modal-lg" role="document">
+        <div class="modal-content">
+            <div class="modal-header">
+                <h5 class="modal-title">清单汇总</h5>
+            </div>
+            <div class="modal-body">
+                <div class="modal-height-300" id="hz-spread"></div>
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-sm btn-secondary" data-dismiss="modal">关闭</button>
+            </div>
+        </div>
+    </div>
+</div>
 <script type="text/javascript">
     const csrf = '<%= ctx.csrf %>';
     const authMobile = '<%= authMobile %>';
@@ -1260,4 +1301,23 @@
             }
         });
     })
+
+    $('#cancel-revise-btn').click(function () {
+        const data = {
+            cid: '<%- change.cid %>',
+        };
+        $.ajax({
+            url: '/tender/<%- tender.id %>/change/cancel/revise?_csrf_j=' + csrf,
+            type: 'post',
+            data: data,
+            dataTye: 'json',
+            success: function(response) {
+                if (response.err === 0) {
+                    window.location.href = response.url;
+                } else {
+                    toast(response.msg, 'error');
+                }
+            }
+        });
+    })
 </script>

+ 1 - 0
app/view/change/revise.ejs

@@ -150,4 +150,5 @@
     const posSpreadSetting = JSON.parse('<%- JSON.stringify(posSpread) %>');
     const thousandth = <%- ctx.tender.info.display.thousandth %>;
     const decimal = JSON.parse('<%- JSON.stringify(ctx.tender.info.decimal) %>');
+    const nodeType = JSON.parse('<%- JSON.stringify(nodeType) %>');
 </script>

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

@@ -23,6 +23,7 @@
     </div>
 </div>
 <script>
+    autoFlashHeight();
     const projectList = JSON.parse(unescape('<%- escape(JSON.stringify(projectList)) %>'));
     const category = JSON.parse(unescape('<%- escape(JSON.stringify(categoryData)) %>'));
 </script>

+ 1 - 1
app/view/layout/menu.ejs

@@ -27,7 +27,7 @@
             <li><a href="https://doc.zhzdwd.com/docs/yunjiliangAPI/yunjiliangAPI-1ccjk7h426enp" target="_blank" data-toggle="tooltip" data-placement="right" title="" data-original-title="数据接口">API</a></li>
         </ul>
         <ul class="nav nav-pills nav-stacked bg-nav">
-            <li <% if (ctx.controllerName === 'setting') { %>class="active"<% } %>><a href="/setting/info" data-toggle="tooltip" data-placement="right" title="" data-original-title="项目信息"><i class="fa fa-cogs"></i><span>项目信息</span></a></li>
+            <li <% if (ctx.controllerName === 'setting') { %>class="active"<% } %>><a href="/setting/info" data-toggle="tooltip" data-placement="right" title="" data-original-title="项目设置"><i class="fa fa-cogs"></i><span>项目设置</span></a></li>
         </ul>
         <div class="dropup mb-1 ml-1 mr-1">
             <a href="" class="btn btn-sm btn-light p-1 w-100" data-toggle="dropdown" aria-haspopup="false" aria-expanded="false">

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

@@ -453,3 +453,4 @@
 <% include ../shares/tender_select_modal.ejs %>
 <% include ../shares/db2full_code.ejs %>
 <% include ./audit_modal.ejs %>
+<% include ../shares/hint_modal.ejs %>

+ 1 - 1
app/view/measure/stage_modal.ejs

@@ -56,7 +56,7 @@
             </div>
             <div class="modal-body">
                 <div class="row">
-                    <div class="col-4">
+                    <div class="col-4 modal-height-500" style="overflow: auto">
                         <div class="card mt-3">
                             <ul class="list-group list-group-flush" id="auditor-list">
                             </ul>

+ 0 - 3
app/view/report/index.ejs

@@ -269,9 +269,6 @@
                             <div class="pageContainer">
                                 <canvas id="rptCanvas" height="820" width="920"></canvas>
                             </div>
-                            <!-- <div style="display: none;">
-                                <canvas id="invisibleCanvas" height="820" width="920"></canvas>
-                            </div> -->
                         </div>
                     </div>
                 </div>

+ 97 - 19
app/view/setting/fun.ejs

@@ -42,19 +42,12 @@
                                                 </div>
                                                 <div class="alert-warning p-1"><i class="fa Example of exclamation-circle fa-exclamation-circle "></i> 变更令开始调用后,负变更清单必须在本期全部调用</div>
                                             </div>
-                                        </div>
-                                    </div>
-                                </div>
-                            </div>
-                        </div>
-                        <div class="card mb-3">
-                            <div class="card-body">
-                                <h5 class="card-title">负变更</h5>
-                                <div class="form-group mb-4">
-                                    <div>
-                                        <div class="form-check form-check-inline">
-                                            <input class="form-check-input" type="checkbox" id="minusNoValue" name="minusNoValue" <% if (funRela.minusNoValue) { %>checked<% } %> onchange="updateSetting();">
-                                            <label class="form-check-label" for="minusNoValue">计量不计价</label>
+                                            <div>
+                                                <div class="form-check form-check-inline">
+                                                    <input class="form-check-input" type="checkbox" id="minusNoValue" name="minusNoValue" <% if (funRela.minusNoValue) { %>checked<% } %> onchange="updateSetting();">
+                                                    <label class="form-check-label" for="minusNoValue">计量不计价</label>
+                                                </div>
+                                            </div>
                                         </div>
                                     </div>
                                 </div>
@@ -171,9 +164,26 @@
                                         <div class="alert alert-dark py-1 px-2 mb-2" role="alert">
                                             功能设置
                                         </div>
-                                        <div class="my-2">
-                                            自定义变更类别
-                                            <a class="pull-right mr-3" href="#bgclass" data-toggle="modal" data-target="#bgclass">设置</a>
+                                        <div class="mb-1">
+                                            <div class="form-check form-check-inline">
+                                                <input class="form-check-input" type="checkbox" id="inlineCheckbox11" checked disabled>
+                                                <label class="form-check-label" for="inlineCheckbox11">开启自定义变更类别</label>
+                                            </div>
+                                            <a class="pull-right mr-3"  href="#bgclass" data-toggle="modal" data-target="#bgclass">设置</a>
+                                        </div>
+                                        <div class="mb-1">
+                                            <div class="form-check form-check-inline">
+                                                <input class="form-check-input" type="checkbox" id="openChangeState" <% if (ctx.session.sessionProject.page_show.openChangeState) { %>checked<% } %> onchange="updateSetting();">
+                                                <label class="form-check-label" for="openChangeState">开启变更令状态功能 </label>&nbsp;
+                                                <a href="javascript:void(0);"  data-toggle="tooltip" data-placement="bottom" title="" data-original-title="变更令提供临时、立项、批复状态,可设置计量上限"><i class="fa fa-question-circle "></i></a>
+                                            </div>
+                                            <a class="pull-right mr-3 <% if (!ctx.session.sessionProject.page_show.openChangeState) { %>text-secondary<% } %>" href="javascript:void(0);" id="openBgStatus">设置</a>
+                                        </div>
+                                        <div class="mb-1">
+                                            <div class="form-check form-check-inline">
+                                                <input class="form-check-input" type="checkbox" id="openChangeWhiteList" <% if (ctx.session.sessionProject.page_show.openChangeWhiteList) { %>checked<% } %> onchange="updateSetting();">
+                                                <label class="form-check-label" for="openChangeWhiteList">开启“添加空白清单”功能</label>
+                                            </div>
                                         </div>
                                     </div>
                                 </div>
@@ -237,6 +247,7 @@
 <script src="/public/js/setting.js"></script>
 <script>
     let changeClass = JSON.parse(unescape('<%- escape(JSON.stringify(funSet.change_class)) %>'));
+    let changeState = JSON.parse(unescape('<%- escape(JSON.stringify(funSet.change_state)) %>'));
     $(() => {
         autoFlashHeight();
         // 自定义变更类别
@@ -272,8 +283,40 @@
         $('#set_change_class_btn').click(function () {
             updateSetting(false, 1);
         });
+
+        $('#openBgStatus').click(function () {
+            if ($('#openChangeState').is(':checked')) {
+                $('#bgstatus').modal('show');
+            }
+        });
+
+        $("#bgstatus").on('show.bs.modal', function () {
+            let html = '';
+            for (const cs of changeState) {
+                html += `<tr data-order="${cs.order}">
+                        <td>${cs.name}</td>
+                        <td>
+                            <div class="input-group input-group-sm" style="width:90px">
+                                <input type="number" class="form-control" max="100" min="0" step="1" value="${cs.value}">
+                                <div class="input-group-append">
+                                    <span class="input-group-text" id="set_change_state_btn">%</span>
+                                </div>
+                            </div>
+                        </td>
+                    </tr>`;
+            }
+            $('#change_state_table').html(html);
+        });
+
+        $('#set_change_state_btn').click(function () {
+            if ($('#openChangeState').is(':checked')) {
+                updateSetting(false, 2);
+            } else {
+                toastr.error('未开启变更令状态功能,不能设置计量上限默认值');
+            }
+        });
     });
-    const updateSetting = function (tab = false, setChangeClass = false) {
+    const updateSetting = function (tab = false, set_type = 0) {
         if (!$('#openChangeApply')[0].checked && $('#openChangeProject')[0].checked && tab === 1) {
             $('#openChangeApply').prop('checked', true);
             $('#openChangePlan').prop('checked', true);
@@ -340,7 +383,8 @@
             $('#end_month').removeAttr('disabled');
             $('#end_day').removeAttr('disabled');
         }
-        if (setChangeClass) {
+        if (set_type === 1) {
+            // 自定义变更类别
             const change_class = [];
             let flag = false;
             let allUnchecked = true;
@@ -383,6 +427,29 @@
                 return;
             }
             pushData.change_class = change_class;
+        } else if (set_type === 2) {
+            // 变更令状态功能-设置计量调用上限
+            const change_state = [];
+            let flag = false;
+            const reg = /^(\d{1,2}|100)$/;
+            $('#change_state_table tr').each(function () {
+                const one_state = _.find(changeState, { order: parseInt($(this).attr('data-order'))});
+                const value = parseFloat($(this).find('input[type="number"]').val());
+                if (!(_.isNumber(value) && reg.test(value))) {
+                    toastr.error(one_state.name + '的计量上限默认值只能输入0-100之间的整数');
+                    flag = true;
+                }
+                const one_updateState = {
+                    order: one_state.order,
+                    name: one_state.name,
+                    value: value,
+                }
+                change_state.push(one_updateState);
+            });
+            if (flag) {
+                return;
+            }
+            pushData.change_state = change_state;
         }
         postData('/setting/fun/update', {
             imType: parseInt($('[name=im_type]:checked').val()),
@@ -395,6 +462,8 @@
             openChangeProject: $('#openChangeProject')[0].checked,
             openChangeApply: $('#openChangeApply')[0].checked,
             openChangePlan: $('#openChangePlan')[0].checked,
+            openChangeWhiteList: $('#openChangeWhiteList')[0].checked,
+            openChangeState: $('#openChangeState')[0].checked,
             openMaterialTax: $('#openMaterialTax')[0].checked,
             openMaterialChecklist: $('#openMaterialChecklist')[0].checked,
             openMaterialSelf: $('#openMaterialSelf')[0].checked,
@@ -402,10 +471,19 @@
             openStageStart: $('#openStageStart')[0].checked,
             addFunSet: _.size(pushData) !== 0 ? pushData : null,
         }, function (result) {
-            if (setChangeClass) {
+            if ($('#openChangeState').is(':checked')) {
+                $('#openBgStatus').removeClass('text-secondary');
+            } else {
+                $('#openBgStatus').addClass('text-secondary');
+            }
+            if (set_type === 1) {
                 changeClass = pushData.change_class;
                 toastr.success('设置成功');
                 $('#bgclass').modal('hide');
+            } else if (set_type === 2) {
+                changeState = pushData.change_state;
+                toastr.success('设置成功');
+                $('#bgstatus').modal('hide');
             }
         });
     }

+ 37 - 0
app/view/setting/fun_modal.ejs

@@ -28,3 +28,40 @@
         </div>
     </div>
 </div>
+<!--设置变更令状态对应的上限-->
+<div class="modal fade" id="bgstatus" data-backdrop="static">
+    <div class="modal-dialog" role="document">
+        <div class="modal-content">
+            <div class="modal-header">
+                <h5 class="modal-title">设置计量调用上限</h5>
+            </div>
+            <div class="modal-body">
+                <table class="table table-hover table-bordered">
+                    <thead class="text-center"><tr><th>状态</th><th>计量上限默认值</th></tr></thead>
+                    <tbody id="change_state_table">
+                    <% for (const cs of funSet.change_state) { %>
+                    <tr data-order="<%- cs.order %>">
+                        <td><%- cs.name %></td>
+                        <td>
+                            <div class="input-group input-group-sm" style="width:90px">
+                                <input type="number" class="form-control" min="0" max="100" step="1" value="<%- cs.value %>">
+                                <div class="input-group-append">
+                                    <span class="input-group-text">%</span>
+                                </div>
+                            </div>
+                        </td>
+                    </tr>
+                    <% } %>
+                    </tbody>
+                </table>
+            </div>
+            <div class="modal-footer">
+                <div class="mr-auto">
+                    <span class="text-warning">计量比例只用于初始化变更令清单</span>
+                </div>
+                <button type="button" class="btn btn-sm btn-secondary" data-dismiss="modal">关闭</button>
+                <button type="button" class="btn btn-sm btn-primary" id="set_change_state_btn">确认</button>
+            </div>
+        </div>
+    </div>
+</div>

+ 0 - 17
app/view/setting/info.ejs

@@ -54,23 +54,6 @@
                                 <label>创建时间</label>
                                 <input class="form-control form-control-sm" value="<%= dateStr%>" type="text" readonly>
                             </div>
-                            <legend>其他信息(报表显示)</legend>
-                            <div class="form-group">
-                                <label>主管部门</label>
-                                <input class="form-control form-control-sm" type="text" value="<%= projectData.rpt_authority %>" <% if (projectData.user_account !== ctx.session.sessionUser.account) { %>readonly<% } else { %>name="rpt_authority" id="rpt_authority"<% } %>>
-                            </div>
-                            <div class="form-group">
-                                <label>建设项目类别</label>
-                                <input class="form-control form-control-sm" type="text" value="<%= projectData.rpt_items %>" value="<%= projectData.rpt_authority %>" <% if (projectData.user_account !== ctx.session.sessionUser.account) { %>readonly<% } else { %>name="rpt_items" id="rpt_items"<% } %>>
-                            </div>
-                            <div class="form-group">
-                                <label>级别</label>
-                                <input class="form-control form-control-sm" type="text" value="<%= projectData.rpt_level %>" <% if (projectData.user_account !== ctx.session.sessionUser.account) { %>readonly<% } else { %>name="rpt_level" id="rpt_level"<% } %>>
-                            </div>
-                            <div class="form-group">
-                                <label>建设性质</label>
-                                <input class="form-control form-control-sm" type="text" value="<%= projectData.rpt_nature %>" <% if (projectData.user_account !== ctx.session.sessionUser.account) { %>readonly<% } else { %>name="rpt_nature" id="rpt_nature"<% } %>>
-                            </div>
                             <% if (projectData.user_account === ctx.session.sessionUser.account) { %></form><% } %>
                     </div>
                 </div>

+ 26 - 0
app/view/shares/hint_modal.ejs

@@ -0,0 +1,26 @@
+<div class="modal fade" id="common-hint" data-backdrop="static">
+    <div class="modal-dialog" role="document">
+        <div class="modal-content">
+            <div class="modal-header">
+                <h5 class="modal-title">确认</h5>
+            </div>
+            <div class="modal-body">
+                <h6 id="common-hint-str">此操作会替换台账里所有同编号、名称、单位的清单,请谨慎操作</h6>
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-sm btn-primary" id="common-hint-ok" data-dismiss="modal">确定</button>
+                <button type="button" class="btn btn-sm btn-secondary" data-dismiss="modal">取消</button>
+            </div>
+        </div>
+    </div>
+</div>
+<script>
+    const doAfterHint = function (option) {
+        $('#common-hint-ok').bind('click', option.fun);
+        if (option.hint) $('#common-hint-str').html(option.hint);
+        $('#common-hint').modal('show');
+        $('#common-hint').bind('hidden.bs.modal', function () {
+            $('#common-hint-ok').unbind('click');
+        });
+    }
+</script>

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

@@ -191,9 +191,9 @@
                                         <li class="timeline-list-item pb-2 <% if (group.status === auditConst.status.uncheck && idx === ctx.stage.auditHistory.length - 1 && ctx.stage.auditHistory.length !== 1) { %>is_uncheck<% } %>">
                                             <% if (group.endYear) { %>
                                             <div class="timeline-item-date">
-                                                <%- group.beginYear %>
-                                                <span><%- group.beginDate %></span>
-                                                <span><%- group.beginTime %></span>
+                                                <%- group.endYear %>
+                                                <span><%- group.endDate %></span>
+                                                <span><%- group.endTime %></span>
                                             </div>
                                             <% } %>
                                             <% if (index < his.length - 1) { %>
@@ -370,9 +370,9 @@
                                     <li class="timeline-list-item pb-2 <% if (group.status === auditConst.status.uncheck && idx === ctx.stage.auditHistory.length - 1 && ctx.stage.auditHistory.length !== 1) { %>is_uncheck<% } %>">
                                         <% if (group.endYear) { %>
                                         <div class="timeline-item-date">
-                                            <%- group.beginYear %>
-                                            <span><%- group.beginDate %></span>
-                                            <span><%- group.beginTime %></span>
+                                            <%- group.endYear %>
+                                            <span><%- group.endDate %></span>
+                                            <span><%- group.endTime %></span>
                                         </div>
                                         <% } %>
                                         <% if (index < his.length - 1) { %>
@@ -551,9 +551,9 @@
                                     <li class="timeline-list-item pb-2 <% if (group.status === auditConst.status.uncheck && idx === ctx.stage.auditHistory.length - 1 && ctx.stage.auditHistory.length !== 1) { %>is_uncheck<% } %>">
                                         <% if (his.endYear) { %>
                                         <div class="timeline-item-date">
-                                            <%- group.beginYear %>
-                                            <span><%- group.beginDate %></span>
-                                            <span><%- group.beginTime %></span>
+                                            <%- group.endYear %>
+                                            <span><%- group.endDate %></span>
+                                            <span><%- group.endTime %></span>
                                         </div>
                                         <% } %>
                                         <% if (index < his.length - 1) { %>

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

@@ -541,6 +541,7 @@
     const tender = JSON.parse(unescape('<%- escape(JSON.stringify(tender)) %>'));
     const tenderInfo = JSON.parse(unescape('<%- escape(JSON.stringify(ctx.tender.info)) %>'));
     const thousandth = <%- ctx.tender.info.display.thousandth %>;
+    const precision = JSON.parse('<%- JSON.stringify(ctx.tender.info.precision) %>');
     const measureType = JSON.parse('<%- JSON.stringify(measureType) %>');
     const stage = JSON.parse(unescape('<%- escape(JSON.stringify(ctx.stage)) %>'));
     const imType = JSON.parse('<%- JSON.stringify(imType) %>');

+ 136 - 0
app/view/sub_proj/data_index.ejs

@@ -0,0 +1,136 @@
+<% include ./sp_info_menu.ejs %>
+<div class="panel-content">
+    <div class="panel-title">
+        <div class="title-main">
+            <% include ./sp_info_mini_menu.ejs %>
+            <div class="d-inline-block">
+                项目信息
+            </div>
+        </div>
+        <div class="ml-auto"></div>
+    </div>
+    <div class="content-wrap">
+        <div class="c-body">
+            <div class="sjs-height-0" style="height: 570px;">
+                <div class="col-8 px-3">
+                    <form>
+                        <div class="py-2 font-weight-bold">主要建设规模</div>
+                        <div class="row">
+                            <div class="col-3">
+                                <div class="form-group">
+                                    <label for="">主线公路里程(km):</label>
+                                    <input type="text" class="form-control form-control-sm" name="mainline_length" value="<%- info.mainline_length %>" org="<%- info.mainline_length %>" placeholder="请输入" maxlength="20" oninput="limitMaxLength(this)" onblur="changeInfo(this);">
+                                </div>
+                            </div>
+                            <div class="col-3">
+                                <div class="form-group">
+                                    <label for="">支线里程(km):</label>
+                                    <input type="text" class="form-control form-control-sm" name="branch_length" value="<%- info.branch_length %>" org="<%- info.branch_length %>" placeholder="请输入" maxlength="20" oninput="limitMaxLength(this)" onblur="changeInfo(this)">
+                                </div>
+                            </div>
+                            <div class="col-3"></div>
+                        </div>
+                        <div class="py-2 font-weight-bold">主要技术指标</div>
+                        <div class="row">
+                            <div class="col-3">
+                                <div class="form-group">
+                                    <label for="">公路等级:</label>
+                                    <select class="form-control form-control-sm" name="road_level" value="<%- info.road_level %>" onchange="changeInfo(this);">
+                                        <option value="">请选择</option>
+                                        <option value="高速公路" <%if (info.road_level === '高速公路') { %>selected<% } %>>高速公路</option>
+                                        <option value="一级公路" <%if (info.road_level === '一级公路') { %>selected<% } %>>一级公路</option>
+                                        <option value="二级公路" <%if (info.road_level === '二级公路') { %>selected<% } %>>二级公路</option>
+                                        <option value="三级公路" <%if (info.road_level === '三级公路') { %>selected<% } %>>三级公路</option>
+                                        <option value="四级公路" <%if (info.road_level === '四级公路') { %>selected<% } %>>四级公路</option>
+                                    </select>
+                                </div>
+                            </div>
+                            <div class="col-3">
+                                <div class="form-group">
+                                    <label for="">公路设计速度(km/h):</label>
+                                    <input type="text" class="form-control form-control-sm" name="design_speed" value="<%- info.design_speed %>" org="<%- info.design_speed %>" placeholder="请输入" maxlength="20" oninput="limitMaxLength(this)" onblur="changeInfo(this)">
+                                </div>
+                            </div>
+                            <div class="col-3">
+                                <div class="form-group">
+                                    <label for="">设计荷载:</label>
+                                    <input type="text" class="form-control form-control-sm" name="design_load" value="<%- info.design_load %>" org="<%- info.design_load %>" placeholder="请输入" maxlength="20" oninput="limitMaxLength(this)" onblur="changeInfo(this)">
+                                </div>
+                            </div>
+                            <div class="col-3">
+                                <div class="form-group">
+                                    <label for="">路基宽度(m):</label>
+                                    <input type="text" class="form-control form-control-sm" name="bed_width" value="<%- info.bed_width %>" org="<%- info.bed_width %>" placeholder="请输入" maxlength="20" oninput="limitMaxLength(this)" onblur="changeInfo(this)">
+                                </div>
+                            </div>
+                            <div class="col-3">
+                                <div class="form-group">
+                                    <label for="">隧道净宽(m):</label>
+                                    <input type="text" class="form-control form-control-sm" name="tunnel_width" value="<%- info.tunnel_width %>" org="<%- info.tunnel_width %>" placeholder="请输入" maxlength="20" oninput="limitMaxLength(this)" onblur="changeInfo(this)">
+                                </div>
+                            </div>
+                            <div class="col-3">
+                                <div class="form-group">
+                                    <label for="">地震动峰值系数(m2):</label>
+                                    <input type="text" class="form-control form-control-sm" name="quake_peak_value" value="<%- info.quake_peak_value %>" org="<%- info.quake_peak_value %>" placeholder="请输入" maxlength="20" oninput="limitMaxLength(this)" onblur="changeInfo(this)">
+                                </div>
+                            </div>
+                        </div>
+                    </form>
+                </div>
+                <div class="col-12 px-3">
+                    <div class="row">
+                        <div class="col-6">
+                            <div class="py-2 font-weight-bold">主要工程数量</div>
+                            <div id="main_qty_spread" style="height: 400px"></div>
+                        </div>
+                        <div class="col-6">
+                            <div class="py-2 font-weight-bold">工料机消耗</div>
+                            <div id="gcl_qty_spread" style="height: 400px"></div>
+                        </div>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+</div>
+<script>
+    const mainQty = JSON.parse('<%- JSON.stringify(info.main_quantity )%>');
+    const gclQty = JSON.parse('<%- JSON.stringify(info.gcl_quantity )%>');
+    $.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();
+        }
+    });
+    // 根据Maxlength限制input输入
+    function limitMaxLength (obj) {
+        if (obj.value.length > obj.maxLength) {
+            obj.value = obj.value.substr(0, obj.maxLength);
+        }
+    }
+
+    function changeInfo(obj) {
+        const field = obj.getAttribute('name');
+        if (!field) return;
+        if (obj.getAttribute('org') === obj.value) return;
+
+        const updateData = {};
+        updateData[field] = obj.value;
+        postData('info/save', { updateData }, function (result) {
+            obj.setAttribute('org', obj.value);
+        }, function () {
+            obj.value = obj.getAttribute('org');
+        });
+    };
+</script>

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

@@ -29,6 +29,7 @@
     </div>
 </div>
 <script>
+    autoFlashHeight();
     const projectList = JSON.parse(unescape('<%- escape(JSON.stringify(projectList)) %>'));
     const category = JSON.parse(unescape('<%- escape(JSON.stringify(categoryData)) %>'));
 </script>

+ 266 - 0
app/view/sub_proj/info.ejs

@@ -0,0 +1,266 @@
+<% include ./sp_info_menu.ejs %>
+<div class="panel-content">
+    <div class="panel-title">
+        <div class="title-main">
+            <% include ./sp_info_mini_menu.ejs %>
+            <div class="d-inline-block">
+                项目信息
+            </div>
+        </div>
+        <div class="ml-auto"></div>
+    </div>
+    <div class="content-wrap">
+        <div class="c-body">
+            <div class="sjs-height-0" style="height: 570px;">
+                <div class="col-9 px-3">
+                    <div>
+                        <div class="py-2 font-weight-bold">项目信息</div>
+                        <div class="form-group">
+                            <label for="">建设项目名称:</label>
+                            <input type="text" class="form-control form-control-sm" name="proj_name" value="<%- info.proj_name %>" org="<%- info.proj_name %>" maxlength="100" placeholder="请输入建设项目名称" oninput="limitMaxLength(this)" onblur="changeInfo(this)">
+                        </div>
+                        <div class="form-group">
+                            <label for="">主管部门:</label>
+                            <input type="text" class="form-control form-control-sm" name="chief_department" value="<%- info.chief_department %>" org="<%- info.chief_department %>" maxlength="100" placeholder="指建设单位的主管部门" oninput="limitMaxLength(this)" onblur="changeInfo(this)">
+                        </div>
+                        <div class="row">
+                            <div class="col-3">
+                                <div class="form-group">
+                                    <label for="">建设项目类型:</label>
+                                    <select class="form-control form-control-sm" name="proj_type" value="<%- info.proj_type %>" org="<%- info.proj_type %>" onchange="changeInfo(this)">
+                                        <option value="">请选择</option>
+                                        <option value="大中型" <%if (info.proj_type === '大中型') { %>selected<% } %>>大中型</option>
+                                        <option value="小型" <%if (info.proj_type === '小型') { %>selected<% } %>>小型</option>
+                                    </select>
+                                </div>
+                            </div>
+                            <div class="col-3">
+                                <div class="form-group">
+                                    <label for="">建设性质:</label>
+                                    <select class="form-control form-control-sm" name="proj_quality" value="<%- info.proj_quality %>" org="<%- info.proj_quality %>" onchange="changeInfo(this)">
+                                        <option value="">请选择</option>
+                                        <option value="新建" <%if (info.proj_quality === '新建') { %>selected<% } %>>新建</option>
+                                        <option value="改建" <%if (info.proj_quality === '改建') { %>selected<% } %>>改建</option>
+                                        <option value="扩建" <%if (info.proj_quality === '扩建') { %>selected<% } %>>扩建</option>
+                                        <option value="改扩建" <%if (info.proj_quality === '改扩建') { %>selected<% } %>>改扩建</option>
+                                    </select>
+                                </div>
+                            </div>
+                            <div class="col-3">
+                                <div class="form-group">
+                                    <label for="">级别:</label>
+                                    <select class="form-control form-control-sm" name="proj_level" value="<%- info.proj_level %>" org="<%- info.proj_level %>" onchange="changeInfo(this)">
+                                        <option value="">请选择</option>
+                                        <option value="中央级" <%if (info.proj_level === '中央级') { %>selected<% } %>>中央级</option>
+                                        <option value="地方级" <%if (info.proj_level === '地方级') { %>selected<% } %>>地方级</option>
+                                        <option value="其他" <%if (info.proj_level === '其他') { %>selected<% } %>>其他</option>
+                                    </select>
+                                </div>
+                            </div>
+                            <div class="col-3"></div>
+                        </div>
+                        <div class="row">
+                            <div class="col-3">
+                                <div class="form-group">
+                                    <label for="">计划开工:</label>
+                                    <input type="date" class="form-control form-control-sm" name="plan_start_date" value="<%- info.plan_start_date %>" org="<%- info.plan_start_date %>" placeholder="请输入" onchange="changeInfo(this)">
+                                </div>
+                            </div>
+                            <div class="col-3">
+                                <div class="form-group">
+                                    <label for="">计划完成:</label>
+                                    <input type="date" class="form-control form-control-sm" name="plan_finish_date" value="<%- info.plan_finish_date %>" org="<%- info.plan_finish_date %>" placeholder="请输入" onchange="changeInfo(this)">
+                                </div>
+                            </div>
+                            <div class="col-3">
+                                <div class="form-group">
+                                    <label for="">实际开工:</label>
+                                    <input type="date" class="form-control form-control-sm" name="real_start_date" value="<%- info.real_start_date %>" org="<%- info.real_start_date %>" placeholder="请输入" onchange="changeInfo(this)">
+                                </div>
+                            </div>
+                            <div class="col-3">
+                                <div class="form-group">
+                                    <label for="">实际完成:</label>
+                                    <input type="date" class="form-control form-control-sm" name="real_finish_date" value="<%- info.real_finish_date %>" org="<%- info.real_finish_date %>"placeholder="请输入" onchange="changeInfo(this)">
+                                </div>
+                            </div>
+                        </div>
+                        <div class="py-2 font-weight-bold">立项批准(核准)情况:</div>
+                        <div class="row">
+                            <div class="col-6">
+                                <div class="form-group">
+                                    <label for="">部门:</label>
+                                    <input type="text" class="form-control form-control-sm" name="lx_department" value="<%- info.lx_department %>" org="<%- info.lx_department %>" maxlength="100" placeholder="请输入部门名称" oninput="limitMaxLength(this)" onblur="changeInfo(this)">
+                                </div>
+                            </div>
+                            <div class="col-3">
+                                <div class="form-group">
+                                    <label for="">日期:</label>
+                                    <input type="date" class="form-control form-control-sm" name="lx_date" value="<%- info.lx_date %>" org="<%- info.lx_date %>" placeholder="请输入" onchange="changeInfo(this)">
+                                </div>
+                            </div>
+                            <div class="col-3">
+                                <div class="form-group">
+                                    <label for="">文号:</label>
+                                    <input type="text" class="form-control form-control-sm" name="lx_code" value="<%- info.lx_code %>" org="<%- info.lx_code %>" placeholder="请输入文号" maxlength="50" oninput="limitMaxLength(this)" onblur="changeInfo(this)">
+                                </div>
+                            </div>
+                        </div>
+                        <div class="py-2 font-weight-bold">初步(修编)设计批准情况:</div>
+                        <div class="row">
+                            <div class="col-6">
+                                <div class="form-group">
+                                    <label for="">部门:</label>
+                                    <input type="text" class="form-control form-control-sm" name="cb_department" value="<%- info.cb_department %>" org="<%- info.cb_department %>" placeholder="请输入部门名称" maxlength="100" oninput="limitMaxLength(this)" onblur="changeInfo(this)">
+                                </div>
+                            </div>
+                            <div class="col-3">
+                                <div class="form-group">
+                                    <label for="">日期:</label>
+                                    <input type="date" class="form-control form-control-sm" name="cb_date" value="<%- info.cb_date %>" org="<%- info.cb_date %>" placeholder="请输入" onchange="changeInfo(this)">
+                                </div>
+                            </div>
+                            <div class="col-3">
+                                <div class="form-group">
+                                    <label for="">文号:</label>
+                                    <input type="text" class="form-control form-control-sm" name="cb_code" value="<%- info.cb_code %>" org="<%- info.cb_code %>" placeholder="请输入文号" maxlength="50" oninput="limitMaxLength(this)" onblur="changeInfo(this)">
+                                </div>
+                            </div>
+                        </div>
+                        <div class="py-2 font-weight-bold">施工许可批复情况:</div>
+                        <div class="row">
+                            <div class="col-6">
+                                <div class="form-group">
+                                    <label for="">部门:</label>
+                                    <input type="text" class="form-control form-control-sm" name="sg_department" value="<%- info.sg_department %>" org="<%- info.sg_department %>" placeholder="请输入部门名称" maxlength="100" oninput="limitMaxLength(this)" onblur="changeInfo(this)">
+                                </div>
+                            </div>
+                            <div class="col-3">
+                                <div class="form-group">
+                                    <label for="">日期:</label>
+                                    <input type="date" class="form-control form-control-sm" name="sg_date" value="<%- info.sg_date %>" org="<%- info.sg_date %>" placeholder="请输入" onchange="changeInfo(this)">
+                                </div>
+                            </div>
+                            <div class="col-3">
+                                <div class="form-group">
+                                    <label for="">文号:</label>
+                                    <input type="text" class="form-control form-control-sm" name="sg_code" value="<%- info.sg_code %>" org="<%- info.sg_code %>" placeholder="请输入文号" maxlength="50" oninput="limitMaxLength(this)" onblur="changeInfo(this)">
+                                </div>
+                            </div>
+                        </div>
+                        <div class="py-2 font-weight-bold">交工验收情况:</div>
+                        <div class="row">
+                            <div class="col-6">
+                                <div class="form-group">
+                                    <label for="">部门:</label>
+                                    <input type="text" class="form-control form-control-sm" name="jg_department" value="<%- info.jg_department %>" org="<%- info.jg_department %>" placeholder="请输入部门名称" maxlength="100" oninput="limitMaxLength(this)" onblur="changeInfo(this)">
+                                </div>
+                            </div>
+                            <div class="col-3">
+                                <div class="form-group">
+                                    <label for="">日期:</label>
+                                    <input type="date" class="form-control form-control-sm" name="jg_date" value="<%- info.jg_date %>" org="<%- info.jg_date %>" placeholder="请输入" onchange="changeInfo(this)">
+                                </div>
+                            </div>
+                            <div class="col-3">
+                                <div class="form-group">
+                                    <label for="">文号:</label>
+                                    <input type="text" class="form-control form-control-sm" name="jg_code" value="<%- info.jg_code %>" org="<%- info.jg_code %>" placeholder="请输入文号" maxlength="50" oninput="limitMaxLength(this)" onblur="changeInfo(this)">
+                                </div>
+                            </div>
+                        </div>
+                        <div class="row">
+                            <div class="col-3">
+                                <div class="form-group">
+                                    <label for="">工程质量评分:</label>
+                                    <input type="text" class="form-control form-control-sm" name="jg_quality_score" value="<%- info.jg_quality_score %>" org="<%- info.jg_quality_score %>" placeholder="请输入" maxlength="50" oninput="limitMaxLength(this)" onblur="changeInfo(this)">
+                                </div>
+                            </div>
+                            <div class="col-3">
+                                <div class="form-group">
+                                    <label for="">等级:</label>
+                                    <select class="form-control form-control-sm" name="jg_level" value="<%- info.jg_level %>" org="<%- info.jg_level %>" onchange="changeInfo(this)">
+                                        <option value="">请选择</option>
+                                        <option value="优良" <%if (info.jg_level === '优良') { %>selected<% } %>>优良</option>
+                                        <option value="合格" <%if (info.jg_level === '合格') { %>selected<% } %>>合格</option>
+                                        <option value="不合格" <%if (info.jg_level === '不合格') { %>selected<% } %>>不合格</option>
+                                    </select>
+                                </div>
+                            </div>
+                        </div>
+                        <div class="py-2 font-weight-bold">单位信息:</div>
+                        <div class="row">
+                            <div class="col-6">
+                                <div class="form-group">
+                                    <label for="">建设单位:</label>
+                                    <input type="text" class="form-control form-control-sm" name="unit_construction" value="<%- info.unit_construction %>" org="<%- info.unit_construction %>" placeholder="请输入" maxlength="100" oninput="limitMaxLength(this)" onblur="changeInfo(this)">
+                                </div>
+                            </div>
+                            <div class="col-6">
+                                <div class="form-group">
+                                    <label for="">质量监督机构:</label>
+                                    <input type="text" class="form-control form-control-sm" name="unit_qa" value="<%- info.unit_qa %>" org="<%- info.unit_qa %>" placeholder="请输入" maxlength="100" oninput="limitMaxLength(this)" onblur="changeInfo(this)">
+                                </div>
+                            </div>
+                            <div class="col-6">
+                                <div class="form-group">
+                                    <label for="">主要设计单位:</label>
+                                    <textarea class="form-control form-control-sm" name="unit_design" org="<%- info.unit_design %>" rows="3" maxlength="500" oninput="limitMaxLength(this)" onblur="changeInfo(this)"><%- info.unit_design %></textarea>
+                                </div>
+                            </div>
+                            <div class="col-6">
+                                <div class="form-group">
+                                    <label for="">主要监理单位:</label>
+                                    <textarea class="form-control form-control-sm" name="unit_supervision" org="<%- info.unit_supervision %>" rows="3" maxlength="500" oninput="limitMaxLength(this)" onblur="changeInfo(this)"><%- info.unit_supervision %></textarea>
+                                </div>
+                            </div>
+                            <div class="col-6">
+                                <div class="form-group">
+                                    <label for="">主要施工单位:</label>
+                                    <textarea class="form-control form-control-sm" name="unit_contract" org="<%- info.unit_contract %>" rows="3" maxlength="500" oninput="limitMaxLength(this)" onblur="changeInfo(this)"><%- info.unit_contract %></textarea>
+                                </div>
+                            </div>
+                        </div>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+</div>
+<script>
+    $.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();
+        }
+    });
+    // 根据Maxlength限制input输入
+    function limitMaxLength (obj) {
+        if (obj.value.length > obj.maxLength) {
+            obj.value = obj.value.substr(0, obj.maxLength);
+        }
+    }
+    function changeInfo(obj) {
+        const field = obj.getAttribute('name');
+        if (!field) return;
+        if (obj.getAttribute('org') === obj.value) return;
+
+        const updateData = {};
+        updateData[field] = obj.value;
+        postData('info/save', { updateData }, function (result) {
+            obj.setAttribute('org', obj.value);
+        }, function () {
+            obj.value = obj.getAttribute('org');
+        });
+    };
+</script>

+ 14 - 0
app/view/sub_proj/sp_info_menu.ejs

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

+ 3 - 0
app/view/sub_proj/sp_info_menu_list.ejs

@@ -0,0 +1,3 @@
+<nav-menu title="返回" url="/budget" tclass="text-primary" ml="1" icon="fa-chevron-left"></nav-menu>
+<nav-menu title="项目信息" url="/sp/<%- ctx.subProject.id %>/info" ml="3" active="<%= (ctx.url.indexOf('info') > 0 ? 1 : -1) %>"></nav-menu>
+<nav-menu title="数据指标" url="/sp/<%- ctx.subProject.id %>/data" ml="3" active="<%= (ctx.url.indexOf('data') > 0 ? 1 : -1) %>"></nav-menu>

+ 16 - 0
app/view/sub_proj/sp_info_mini_menu.ejs

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

+ 1 - 1
app/view/tender/detail.ejs

@@ -1229,7 +1229,7 @@
                 }
                 historyHTML.push(`<li class="timeline-list-item pb-2 ${ group.status === auditConst.status.uncheck && idx === auditHistory.length - 1 && auditHistory.length !== 1 ? 'is_uncheck' : ''}">`);
                 if (group.endYear) {
-                    historyHTML.push(`<div class="timeline-item-date ${textClass}">${group.beginYear}<span class="${textClass}">${group.beginDate}</span><span class="${textClass}">${group.beginTime}</span></div>`);
+                    historyHTML.push(`<div class="timeline-item-date ${textClass}">${group.endYear}<span class="${textClass}">${group.endDate}</span><span class="${textClass}">${group.endTime}</span></div>`);
                 }
                 if (index < his.length - 1) {
                     historyHTML.push('<div class="timeline-item-tail"></div>');

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

@@ -1914,7 +1914,7 @@
                 <div class="form-group">
                     <div class="custom-control custom-checkbox mb-2">
                         <input type="checkbox" class="custom-control-input" id="sc_minusNoValue" checked="">
-                        <label class="custom-control-label" for="sc_minusNoValue">计量台账-变更-计量不计价</label>
+                        <label class="custom-control-label" for="sc_minusNoValue">计量台账-变更-计量不计价</label>
                     </div>
                 </div>
                 <div class="form-group">

+ 1 - 1
app/view/tender/modal.ejs

@@ -168,7 +168,7 @@
                     }
                     historyHTML.push(`<li class="timeline-list-item pb-2 ${ group.status === auditConst2.status.uncheck && idx === auditHistory.length - 1 && auditHistory.length !== 1 ? 'is_uncheck' : ''}">`);
                     if (group.endYear) {
-                        historyHTML.push(`<div class="timeline-item-date">${group.beginYear}<span>${group.beginDate}</span><span>${group.beginTime}</span></div>`);
+                        historyHTML.push(`<div class="timeline-item-date">${group.endYear}<span>${group.endDate}</span><span>${group.endTime}</span></div>`);
                     }
                     if (index < his.length - 1) {
                         historyHTML.push('<div class="timeline-item-tail"></div>');

+ 22 - 0
config/web.js

@@ -1031,6 +1031,28 @@ const JsFiles = {
                 ],
                 mergeFile: 'sub_project',
             },
+            info: {
+                files: [
+                    '/public/js/component/menu.js',
+                ],
+                mergeFiles: [
+                    '/public/js/sub_menu.js',
+                ],
+                mergeFile: 'sp_info',
+            },
+            data: {
+                files: [
+                    '/public/js/component/menu.js',
+                    '/public/js/spreadjs/sheets/v11/gc.spread.sheets.all.11.2.2.min.js',
+                ],
+                mergeFiles: [
+                    '/public/js/path_tree.js',
+                    '/public/js/spreadjs_rela/spreadjs_zh.js',
+                    '/public/js/sub_menu.js',
+                    '/public/js/sp_data.js',
+                ],
+                mergeFile: 'sp_data',
+            }
         },
         file: {
             index: {

+ 0 - 0
publish.md


部分文件因为文件数量过多而无法显示