Bläddra i källkod

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

Tony Kang 7 månader sedan
förälder
incheckning
9bd9c41b20
83 ändrade filer med 10412 tillägg och 2337 borttagningar
  1. 12 12
      app/base/base_tree_service.js
  2. 1 0
      app/const/page_show.js
  3. 1 0
      app/const/tender_info.js
  4. 34 3
      app/controller/change_controller.js
  5. 4 0
      app/controller/measure_controller.js
  6. 119 6
      app/controller/pay_controller.js
  7. 6 0
      app/controller/stage_controller.js
  8. 21 20
      app/controller/tender_controller.js
  9. 6 0
      app/controller/wap_controller.js
  10. 1 0
      app/lib/analysis_excel.js
  11. 1 0
      app/lib/ledger.js
  12. 2 6
      app/lib/ybp_tree.js
  13. 1 1
      app/middleware/phase_pay_check.js
  14. 2 2
      app/public/css/main.css
  15. 5605 138
      app/public/js/change_information.js
  16. 3 0
      app/public/js/change_information_set.js
  17. 9 9
      app/public/js/change_revise.js
  18. 2 2
      app/public/js/global.js
  19. 16 14
      app/public/js/ledger.js
  20. 17 9
      app/public/js/material.js
  21. 1 1
      app/public/js/material_audit.js
  22. 59 12
      app/public/js/measure_compare.js
  23. 2 1
      app/public/js/path_tree.js
  24. 2 2
      app/public/js/payment_safe.js
  25. 992 0
      app/public/js/phase_pay_detail.js
  26. 21 0
      app/public/js/phase_pay_list.js
  27. 18 9
      app/public/js/revise.js
  28. 6 7
      app/public/js/shares/dsk.js
  29. 18 0
      app/public/js/shares/sjs_setting.js
  30. 1 1
      app/public/js/shares/tree_expr_calc.js
  31. 6 6
      app/public/js/sp_progress.js
  32. 37 0
      app/public/js/spreadjs_rela/spreadjs_zh.js
  33. 11 11
      app/public/js/stage.js
  34. 26 43
      app/public/js/stage_gather.js
  35. 1 1
      app/public/js/stage_pay.js
  36. 32 3
      app/public/js/zh_calc.js
  37. 6 2
      app/router.js
  38. 42 39
      app/service/change.js
  39. 6 9
      app/service/change_apply.js
  40. 63 19
      app/service/change_audit_list.js
  41. 6 8
      app/service/change_plan.js
  42. 4 6
      app/service/change_project.js
  43. 2 3
      app/service/financial_pay.js
  44. 6 1
      app/service/ledger.js
  45. 4 2
      app/service/material.js
  46. 20 5
      app/service/material_audit.js
  47. 1 1
      app/service/message.js
  48. 244 35
      app/service/phase_pay.js
  49. 587 0
      app/service/phase_pay_detail.js
  50. 5 3
      app/service/report_memory.js
  51. 6 2
      app/service/stage_audit.js
  52. 8 0
      app/service/stage_audit_ass.js
  53. 8 0
      app/service/stage_bonus.js
  54. 8 0
      app/service/stage_jgcl.js
  55. 8 0
      app/service/stage_other.js
  56. 8 0
      app/service/stage_safe_prod.js
  57. 8 0
      app/service/stage_temp_land.js
  58. 117 88
      app/view/change/information.ejs
  59. 2 2
      app/view/change/information_modal.ejs
  60. 2 0
      app/view/ledger/explode.ejs
  61. 2 2
      app/view/material/audit_modal.ejs
  62. 0 1
      app/view/material/file.ejs
  63. 8 0
      app/view/measure/compare.ejs
  64. 77 0
      app/view/phase_pay/detail.ejs
  65. 59 0
      app/view/phase_pay/detail_modal.ejs
  66. 25 44
      app/view/phase_pay/index.ejs
  67. 85 0
      app/view/phase_pay/modal.ejs
  68. 14 0
      app/view/phase_pay/sub_menu.ejs
  69. 3 0
      app/view/phase_pay/sub_menu_list.ejs
  70. 16 0
      app/view/phase_pay/sub_mini_menu.ejs
  71. 5 0
      app/view/revise/info.ejs
  72. 17 0
      app/view/shares/dsk_modal.ejs
  73. 38 1
      app/view/stage/gather.ejs
  74. 3 136
      app/view/tender/detail.ejs
  75. 1465 1464
      app/view/tender/detail_modal.ejs
  76. 2 0
      app/view/tender/tender_sub_menu.ejs
  77. 1 1
      app/view/wap/shenpi_stage.ejs
  78. 84 24
      build_min.js
  79. 35 2
      config/web.js
  80. 42 8
      db_script/material_audit_order.js
  81. 22 0
      sql/index.sql
  82. 4 110
      sql/update.sql
  83. 138 0
      sql/update20241205.sql

+ 12 - 12
app/base/base_tree_service.js

@@ -73,7 +73,7 @@ class TreeService extends Service {
             this.initSqlBuilder();
             this.sqlBuilder.setAndWhere(this.setting.mid, {
                 operate: '=',
-                value: mid,
+                value: this.db.escape(mid),
             });
             this.sqlBuilder.setAndWhere(this.setting.level, {
                 operate: '<=',
@@ -101,7 +101,7 @@ class TreeService extends Service {
     }
 
     async getDataByKidAndCount(mid, kid, count) {
-        if ((mid <= 0) || (kid <= 0)) return [];
+        if (!mid || (kid <= 0)) return [];
         const select = await this.getDataByKid(mid, kid);
         if (!select) throw '数据错误';
 
@@ -273,7 +273,7 @@ class TreeService extends Service {
      * @return {Promise<*>}
      */
     async getChildrenByParentId(mid, pid) {
-        if (mid <= 0 || !pid) return undefined;
+        if (!mid || !pid) return undefined;
         const pids = pid instanceof Array ? pid : [pid];
 
         this.initSqlBuilder();
@@ -299,7 +299,7 @@ class TreeService extends Service {
      * @return {Promise<void>}
      */
     async getPosterityByParentId(mid, pid) {
-        if (mid <= 0 || !pid) return undefined;
+        if (!mid || !pid) return undefined;
 
         const node = await this.getDataByKid(mid, pid);
 
@@ -335,7 +335,7 @@ class TreeService extends Service {
         const cacheKey = this.setting.keyPre + mid;
         let maxId = this.setting.cacheKey ? parseInt(await this.cache.get(cacheKey)) : undefined;
         if (!maxId) {
-            const sql = 'SELECT Max(??) As max_id FROM ?? Where ' + this.setting.mid + ' = ?';
+            const sql = 'SELECT Max(??) As max_id FROM ?? Where ' + this.db.escape(this.setting.mid) + ' = ?';
             const sqlParam = [this.setting.kid, this.tableName, mid];
             const queryResult = await this.db.queryOne(sql, sqlParam);
             maxId = queryResult.max_id || 0;
@@ -551,7 +551,7 @@ class TreeService extends Service {
      * @return {Array} - 被删除的数据
      */
     async deleteNode(mid, kid) {
-        if ((mid <= 0) || (kid <= 0)) return [];
+        if (!mid || (kid <= 0)) return [];
         const select = await this.getDataByKid(mid, kid);
         if (!select) throw '删除节点数据错误';
         const parent = await this.getDataByKid(mid, select[this.setting.pid]);
@@ -597,7 +597,7 @@ class TreeService extends Service {
     }
 
     async deleteNodes(mid, kid, count) {
-        if ((mid <= 0) || (kid <= 0) || (count <= 0)) return [];
+        if (!mid || (kid <= 0) || (count <= 0)) return [];
         const selects = await this.getDataByKidAndCount(mid, kid, count);
         const first = selects[0];
         const parent = await this.getDataByKid(mid, first[this.setting.pid]);
@@ -658,7 +658,7 @@ class TreeService extends Service {
      */
     async upMoveNode(mid, kid, count) {
         if (!count) count = 1;
-        if (!mid || (mid <= 0) || !kid || (kid <= 0)) return null;
+        if (!mid || !kid || (kid <= 0)) return null;
         const selects = await this.getDataByKidAndCount(mid, kid, count);
         if (selects.length !== count) throw '上移节点数据错误';
         const first = selects[0];
@@ -699,7 +699,7 @@ class TreeService extends Service {
      */
     async downMoveNode(mid, kid, count) {
         if (!count) count = 1;
-        if (!mid || (mid <= 0) || !kid || (kid <= 0)) return null;
+        if (!mid || !kid || (kid <= 0)) return null;
         const selects = await this.getDataByKidAndCount(mid, kid, count);
         if (selects.length !== count) {
             throw '下移节点数据错误';
@@ -794,7 +794,7 @@ class TreeService extends Service {
                 selfOperate: orderInc > 0 ? '+' : '-',
             });
             this.sqlBuilder.setAndWhere(this.setting.mid, {
-                value: select[this.setting.mid],
+                value: this.db.escape(select[this.setting.mid]),
                 operate: '=',
             });
             this.sqlBuilder.setAndWhere(this.setting.pid, {
@@ -820,7 +820,7 @@ class TreeService extends Service {
             const newSubStr = this.db.escape(select[this.setting.kid] + '-');
             const sqlArr = [];
             sqlArr.push('Update ?? SET `' + this.setting.fullPath + '` = Replace(`' + this.setting.fullPath + '`,' + oldSubStr + ',' + newSubStr + ') Where');
-            sqlArr.push('(`' + this.setting.mid + '` = ' + select[this.setting.mid] + ')');
+            sqlArr.push('(`' + this.setting.mid + '` = ' + this.db.escape(select[this.setting.mid]) + ')');
             sqlArr.push(' And (');
             for (const data of nexts) {
                 sqlArr.push('`' + this.setting.fullPath + '` Like ' + this.db.escape(data[this.setting.fullPath] + '%'));
@@ -1018,7 +1018,7 @@ class TreeService extends Service {
      * @return {Array} - 提交后的数据
      */
     async updateInfos(mid, datas) {
-        if (mid <= 0) throw '数据错误';
+        if (!mid) throw '数据错误';
 
         if (Array.isArray(datas)) {
             const updateDatas = [];

+ 1 - 0
app/const/page_show.js

@@ -28,6 +28,7 @@ const defaultSetting = {
     bwtz: 0,
     xxjd: 0,
     openMaterial: 1,
+    openIsolatePay: 0,
     stageExtra: 1,
     closeExportPdf: 0,
     closeExportExcel: 0,

+ 1 - 0
app/const/tender_info.js

@@ -200,6 +200,7 @@ const defaultInfo = {
     },
     fun_rela: {
         hintOver: true,
+        repel: false,
         stage_change: {
             minusNoValue: true,
         },

+ 34 - 3
app/controller/change_controller.js

@@ -399,6 +399,7 @@ module.exports = app => {
                 const whiteList = this.ctx.app.config.multipart.whitelist;
                 await this._getChangeAuditViewData(ctx);
                 const tender = ctx.tender;
+                const tenderData = await ctx.service.tender.getDataById(tender.id);
                 const change = ctx.change;
 
                 // 获取附件列表
@@ -485,6 +486,7 @@ module.exports = app => {
                     settlePos,
                     removeSettleNum,
                     deleteFilePermission,
+                    changeMode: tenderData.c_mode,
                 };
                 // 获取是否已存在调用变更令
                 let changeUsedData = await ctx.service.stageChange.getAllFinalUsedData(ctx.tender.id, change.cid);
@@ -506,6 +508,7 @@ module.exports = app => {
                 renderData.stageChangeNum = this.ctx.helper.sum(changeUsedData.map(x => { return Math.abs(x.qty); }));
                 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 } });
+                renderData.changeUnits = changeConst.units;
                 if (!change.readOnly || ctx.session.sessionUser.is_admin) {
                     // 获取所有项目参与者
                     const accountList = await ctx.service.projectAccount.getAllDataByCondition({
@@ -517,7 +520,6 @@ module.exports = app => {
                         const groupList = accountList.filter(item1 => item1.company === item.name);
                         return { groupName: item.name, groupList };
                     });
-                    renderData.changeUnits = changeConst.units;
                     // 获取公司列表
                     const companyList = await ctx.service.changeCompany.getAllDataByCondition({ where: { tid: ctx.tender.id } });
                     renderData.companyList = companyList;
@@ -722,12 +724,15 @@ module.exports = app => {
                         // 取所有工料表
                         responseData.data = await ctx.service.changeAuditList.getList(ctx.change.cid);
                         break;
-                        // 从新增部位页新增清单
+                    // 从新增部位页新增清单
                     case 'add-change-list':
                         if (ctx.change.status !== audit.change.status.uncheck && ctx.change.status !== audit.change.status.checkNo && ctx.change.status !== audit.change.status.revise) {
                             throw '该变更令正在审批中或已完成,无法操作清单数据';
                         }
-                        await ctx.service.changeAuditList.adds(data.postData);
+                        const projectData2 = await ctx.service.project.getDataById(ctx.session.sessionProject.id);
+                        const fun_set2 = await ctx.service.project.getFunSet(projectData2.fun_set);
+                        const stateInfo2 = ctx.helper._.find(fun_set2.change_state, { order: ctx.change.state });
+                        await ctx.service.changeAuditList.adds(data.postData, stateInfo2.value);
                         responseData.data = { changeList: await ctx.service.changeAuditList.getList(ctx.change.cid) };
                         break;
                     // 从新增部位页删除清单
@@ -757,6 +762,32 @@ module.exports = app => {
                             }
                         }
                         break;
+                    case 'change_mode':
+                        await ctx.service.tender.defaultUpdate({
+                            id: ctx.tender.id,
+                            c_mode: data.updateData ? 1 : 0,
+                        });
+                        const changeList2 = await ctx.service.changeAuditList.getList(ctx.change.cid);
+                        if (ctx.change.readOnly) {
+                            const auditList2 = await ctx.service.changeAudit.getListGroupByTimes(ctx.change.cid, ctx.change.times);
+                            for (const cl of changeList2) {
+                                const audit_amount = cl.audit_amount !== null && cl.audit_amount !== '' ? cl.audit_amount.split(',') : '';
+                                // 清单表页赋值
+                                for (const au in ctx.change.userGroups) {
+                                    if (ctx.change.userGroups[au][0].audit_order !== 0) {
+                                        cl['audit_amount_' + ctx.change.userGroups[au][0].audit_order] =
+                                            (ctx.change.userGroups[au][0].audit_type === auditType.key.and &&
+                                                ctx.helper._.findIndex(ctx.change.userGroups[au], { status: audit.change.status.checking }) !== -1 &&
+                                                ctx.helper._.findIndex(ctx.change.userGroups[au], { uid: ctx.session.sessionUser.accountId, status: audit.change.status.checked }) !== -1) ||
+                                            (ctx.change.shenpiPower && ctx.helper._.findIndex(ctx.change.userGroups[au], { uid: ctx.session.sessionUser.accountId }) !== -1) ?
+                                                cl.spamount : (audit_amount[au - 1] !== undefined ? parseFloat(audit_amount[au - 1]) : null);
+                                    }
+                                }
+                            }
+                        }
+                        // 取所有工料表
+                        responseData.data = changeList2;
+                        break;
                     default: throw '参数有误';
                 }
 

+ 4 - 0
app/controller/measure_controller.js

@@ -14,6 +14,7 @@ const auditConst = require('../const/audit').stage;
 const auditType = require('../const/audit').auditType;
 const moment = require('moment');
 const measureType = require('../const/tender').measureType;
+const stdConst = require('../const/standard');
 
 module.exports = app => {
 
@@ -267,6 +268,9 @@ module.exports = app => {
                         result.stages.push(data);
                     }
                 }
+                const zlj = JSON.parse(JSON.stringify(stdConst.zlj));
+                zlj.deal_bills_tp = ctx.tender.info.deal_param.zanLiePrice;
+                result.spec = {zlj: zlj, jrg: stdConst.jrg};
                 ctx.body = { err: 0, msg: '', data: result };
             } catch (err) {
                 this.log(err);

+ 119 - 6
app/controller/pay_controller.js

@@ -26,29 +26,142 @@ module.exports = app => {
         async index(ctx) {
             try {
                 const phasePays = await this.ctx.service.phasePay.getAllPhasePay(ctx.tender.id, 'DESC');
+                const relaStage = [];
+                for (const p of phasePays) {
+                    // todo 加载当前审批人
+                    // if (p.audit_status !== checked) await this.ctx.service.phasePay.loadUser(p);
+                    p.curAuditors = [];
+                    relaStage.push(...p.rela_stage);
+                }
+                const stages = await this.ctx.service.stage.getAllDataByCondition({ where: { tid: ctx.tender.id }, orders: [['order', 'AEC']] });
+                const validStages = stages.filter(s => {
+                    return !relaStage.find(r => { return s.id === r.id; });
+                });
                 this.ctx.service.phasePay.calculatePhasePay(phasePays);
                 const renderData = {
                     phasePays,
+                    validStages,
                     auditConst: audit.common,
+                    jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.phasePay.list)
                 };
-                await this.layout('phase_pay/index.ejs', renderData);
+                await this.layout('phase_pay/index.ejs', renderData, 'phase_pay/modal.ejs');
             } catch (err) {
                 ctx.helper.log(err);
             }
         }
 
-        async pay(ctx) {
+        async add(ctx) {
+            try {
+                if (ctx.session.sessionUser.accountId !== ctx.tender.data.user_id && ctx.tender.userAssistsId.indexOf(ctx.session.sessionUser.accountId) < 0) {
+                    throw '您无权创建计量期';
+                }
+                const date = ctx.request.body.date;
+                if (!date) throw '请选择支付年月';
+                const stage = ctx.request.body.stage;
+                if (!stage) throw '请选择计量期';
+                const memo = ctx.request.body.memo;
+
+                const pays = await ctx.service.phasePay.getAllPhasePay(ctx.tender.id, 'DESC');
+                const unCompleteCount = pays.filter(s => { return s.status !== audit.common.status.checked; }).length;
+                if (unCompleteCount.length > 0) throw `最新一起未审批通过,请审批通过后再新增`;
+                // 预留可以关联多期
+                const stages = await ctx.service.stage.getAllDataByCondition({ where: { tid: ctx.tender.id, order: stage } });
+
+                const newPhase = await ctx.service.phasePay.add(ctx.tender.id, stages, date, memo);
+                if (!newPhase) throw '新增期失败';
+                newPhase.curTimes = 1;
+                newPhase.curOrder = 0;
+                await ctx.service.phasePayDetail.calculateSave(newPhase);
+
+                ctx.redirect('/tender/' + ctx.tender.id + '/pay' + newPhase.phase_order);
+            } catch (err) {
+                this.log(err);
+                ctx.postError(err, '新增期失败');
+                ctx.redirect(ctx.request.header.referer);
+            }
+        }
+
+        async del() {
+
+        }
+
+        async save() {
+
+        }
+
+        async detail(ctx) {
             try {
-                const pays = await this.ctx.service.phasePay.getAllPhasePay(ctx.tender.id, 'DESC');
+                // await this.ctx.service.phasePayDetail.calculateSave(ctx.phasePay);
+                const pays = await this.ctx.service.phasePayDetail.getDetailData(ctx.phasePay);
+                const calcBase = this.ctx.service.phasePay.getPhasePayCalcBase(ctx.phasePay, ctx.tender.info);
+                const projectFunInfo = await this.ctx.service.project.getFunRela(ctx.session.sessionProject.id);
+                const lastStage = await this.ctx.service.stage.getLastestCompleteStage(ctx.tender.id);
                 const renderData = {
                     pays,
-                    jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.pay.list)
+                    calcBase,
+                    lockPayExpr: projectFunInfo.lockPayExpr,
+                    auditConst: audit.common,
+                    deadlineType: this.ctx.service.phasePayDetail.deadlineType,
+                    maxStageOrder: lastStage.order,
+                    jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.phasePay.detail)
                 };
-                await this.layout('pay/pay.ejs', renderData);
+                await this.layout('phase_pay/detail.ejs', renderData, 'phase_pay/detail_modal.ejs');
             } catch (err) {
                 ctx.helper.log(err);
                 ctx.postError(err, '读取合同支付数据错误');
-                ctx.redirect(this.request.headers.referer);
+                ctx.redirect(ctx.request.headers.referer);
+            }
+        }
+
+        async detailUpdate(ctx) {
+            try {
+                const data = JSON.parse(ctx.request.body.data);
+                if (!data.postType || !data.postData) throw '数据错误';
+                const responseData = { err: 0, msg: '', data: {} };
+
+                switch (data.postType) {
+                    case 'add':
+                        responseData.data = await this.ctx.service.phasePayDetail.addDetailNode(ctx.phasePay, data.postData.id, data.postData.count || 1);
+                        break;
+                    case 'delete':
+                        await this.ctx.service.phasePayDetail.deleteDetailNode(ctx.phasePay, data.postData.id, data.postData.count || 1);
+                        await this.ctx.service.phasePayDetail.calculateSave(ctx.phasePay);
+                        responseData.data.reload = await this.ctx.service.phasePayDetail.getDetailData(ctx.phasePay);
+                        break;
+                    case 'up-move':
+                        responseData.data = await this.ctx.service.phasePayDetail.upMoveDetailNode(ctx.phasePay, data.postData.id, data.postData.count || 1);
+                        break;
+                    case 'down-move':
+                        responseData.data = await this.ctx.service.phasePayDetail.downMoveDetailNode(ctx.phasePay, data.postData.id, data.postData.count || 1);
+                        break;
+                    case 'update':
+                        const updateDetail = await this.ctx.service.phasePayDetail.updateDetail(ctx.phasePay, data.postData);
+                        if (this.ctx.service.phasePayDetail.checkCalc(data.postData)) {
+                            await this.ctx.service.phasePayDetail.calculateSave(ctx.phasePay);
+                            responseData.data.reload = await this.ctx.service.phasePayDetail.getDetailData(ctx.phasePay);
+                        } else {
+                            responseData.data.update = updateDetail;
+                        }
+                        break;
+                    case 'calc':
+                        await this.ctx.service.phasePayDetail.calculateSave(ctx.phasePay);
+                        responseData.data.reload = await this.ctx.service.phasePayDetail.getDetailData(ctx.phasePay);
+                        break;
+                    case 'refreshBase':
+                        await this.ctx.service.phasePay.refreshCalcBase(ctx.phasePay);
+                        responseData.data.reload = await this.ctx.service.phasePayDetail.getDetailData(ctx.phasePay);
+                        responseData.data.calcBase = this.ctx.service.phasePay.getPhasePayCalcBase(ctx.phasePay, ctx.tender.info);
+                        responseData.data.calcBase.forEach(x => { x.formatValue = ctx.tender.info.display.thousandth ? ctx.helper.formatNum(x.value, '#,##0.######') : x.value; });
+                        responseData.data.addBase = ctx.phasePay.calc_base;
+                        break;
+                    default:
+                        throw '未知操作';
+                }
+                ctx.body = responseData;
+            } catch (err) {
+                console.log(err);
+                this.log(err);
+                ctx.body = this.ajaxErrorBody(err, '数据错误');
             }
         }
 

+ 6 - 0
app/controller/stage_controller.js

@@ -375,6 +375,12 @@ module.exports = app => {
                             const assistLocked = await ctx.service.stageAuditAss.getLockedId(ctx.stage);
                             const auditLocked = await ctx.service.stageAudit.getLockedId(ctx.stage);
                             responseData.data.locked = [...assistLocked, ...auditLocked];
+                            if (ctx.tender.info.fun_rela.repel && !ctx.stage.readOnly) {
+                                const assistComfirm = await ctx.service.stageAuditAss.getComfirmLockedId(ctx.stage);
+                                const auditComfirm = ctx.stage.curAuditors && ctx.stage.curAuditors.length > 0 && ctx.stage.curAuditors[0].audit_type === auditType.key.union
+                                    ? await ctx.service.stageAuditAss.getComfirmLockedId(ctx.stage) : [];
+                                responseData.data.locked.push(...assistComfirm, ...auditComfirm);
+                            }
                             break;
                         case 'pos':
                             if (hpack) {

+ 21 - 20
app/controller/tender_controller.js

@@ -557,26 +557,27 @@ module.exports = app => {
                 ];
                 // 地图
                 const tenderALLInfo = await ctx.service.tender.getDataById(tender.id);
-                const hadMap = tenderALLInfo.had_map === 0 ? 1 : tenderALLInfo.had_map;// 0为初始值,因为默认可能会变化,所以暂时把0都默认为1。共三种模式坐标模式1,图片模式2,无图模式3。
-                const tenderMapList = await ctx.service.tenderMap.getAllDataByCondition({ where: { tid: tender.id } });
+                const hadMap = tenderALLInfo.had_map === 0 ? 3 : tenderALLInfo.had_map;// 0为初始值,因为默认可能会变化,所以暂时把0都默认为1。共三种模式坐标模式1,图片模式2,无图模式3。
+                // const tenderMapList = await ctx.service.tenderMap.getAllDataByCondition({ where: { tid: tender.id } });
+                const tenderMapList = [];
                 // 默认坐标,否则则取办事处坐标
-                const projectData = await ctx.service.project.getDataById(ctx.session.sessionProject.id);
-                let map_json = {
-                    province: mapConst.map[0].province,
-                    lng: mapConst.map[0].lng,
-                    lat: mapConst.map[0].lat,
-                    level: 15,
-                };
-                if (projectData.map_json) {
-                    map_json = JSON.parse(projectData.map_json);
-                } else {
-                    const mapInfo = ctx.helper._.find(mapConst.map, { office: projectData.manager_office });
-                    if (mapInfo) {
-                        map_json.province = mapInfo.province;
-                        map_json.lng = mapInfo.lng;
-                        map_json.lat = mapInfo.lat;
-                    }
-                }
+                // const projectData = await ctx.service.project.getDataById(ctx.session.sessionProject.id);
+                // let map_json = {
+                //     province: mapConst.map[0].province,
+                //     lng: mapConst.map[0].lng,
+                //     lat: mapConst.map[0].lat,
+                //     level: 15,
+                // };
+                // if (projectData.map_json) {
+                //     map_json = JSON.parse(projectData.map_json);
+                // } else {
+                //     const mapInfo = ctx.helper._.find(mapConst.map, { office: projectData.manager_office });
+                //     if (mapInfo) {
+                //         map_json.province = mapInfo.province;
+                //         map_json.lng = mapInfo.lng;
+                //         map_json.lat = mapInfo.lat;
+                //     }
+                // }
                 const canFinish = await this.ctx.service.tender.checkTenderCanFinish(tender);
 
                 const projectSpread = await ctx.service.projectSpread.getProjectSpreadType(tender.project_id);
@@ -606,7 +607,7 @@ module.exports = app => {
                     jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.tender.tenderInfo),
                     gclChapter,
                     tenderMapList,
-                    map_json,
+                    // map_json,
                     fujianOssPath: ctx.app.config.fujianOssPath,
                     canFinish,
                     buildStatus: tenderConst.buildStatus,

+ 6 - 0
app/controller/wap_controller.js

@@ -335,6 +335,12 @@ module.exports = app => {
                 //     await this.ctx.service.stage.checkStageGatherData(lastStage);
                 // }
                 const stage = ctx.stage;
+                if (stage) {
+                    if (stage.status !== auditConst.stage.status.checked) await this.ctx.service.stage.checkStageGatherData(stage);
+                    stage.tp = this.ctx.helper.sum([stage.contract_tp, stage.qc_tp, stage.pc_tp]);
+                    stage.pre_tp = this.ctx.helper.add(stage.pre_contract_tp, stage.pre_qc_tp);
+                    stage.end_tp = this.ctx.helper.add(stage.pre_tp, stage.tp);
+                }
                 await ctx.service.stage.loadStageAuditViewData(stage);
                 const renderData = {
                     moment,

+ 1 - 0
app/lib/analysis_excel.js

@@ -578,6 +578,7 @@ class AnalysisExcelTree {
             node.total_price = this.ctx.helper.round(aeUtils.toNumber(row[this.colsDef.total_price]), this.decimal.tp);
             node.drawing_code = this.ctx.helper.replaceReturn(row[this.colsDef.drawing_code]);
             node.memo = this.ctx.helper.replaceReturn(row[this.colsDef.memo]);
+            this.ctx.helper.checkDgnQtyPrecision(node);
             return this.cacheTree.addXmjNode(node);
         } catch (error) {
             if (error.stack) {

+ 1 - 0
app/lib/ledger.js

@@ -1285,6 +1285,7 @@ class reviseTree extends billsTree {
 }
 
 module.exports = {
+    baseTree,
     billsTree,
     pos,
     filterTree,

+ 2 - 6
app/lib/ybp_tree.js

@@ -41,7 +41,7 @@ class YbpTree {
 
         if (!recursive) return;
         for (const c of children) {
-            this.sortChildren(c.children);
+            this.sortChildren(c.children, recursive);
         }
     }
     sort() {
@@ -69,7 +69,6 @@ class YbpTree {
                 if (!parent) {
                     parent = datas.find(x => { return x[setting.id] === d[setting.pid]; });
                     if (!parent) {
-                        // console.log(d[setting.pid]);
                         return null;
                     }
                     parent = _loadData(parent);
@@ -157,7 +156,6 @@ class YbpImportTree {
         const children = parent ? parent.children : this.children;
         return this.helper._.find(children, data);
         // return children.find(x => {
-        // return children.find(x => {
         //   return x.kind === data.kind &&
         //       x.code === data.code && x.b_code === data.b_code && x.name === data.name && x.unit === data.unit &&
         //       x.unit_price === data.unit_price;
@@ -198,10 +196,8 @@ class YbpImportTree {
             cur.quantity = isXmj ? 0 : node.quantity || 0;
             cur.total_fee = node.fees ? node.fees.marketCommon.totalFee : 0;
             cur.labour_fee = node.fees && node.fees.marketLabour ? node.fees.marketLabour.totalFee : 0;
+            cur.remark = node.remark;
         } else {
-            cur.dgn_qty1 = isXmj ? node.quantity || 0 : 0;
-            cur.dgn_qty2 = isXmj ? node.quantity2 || 0 : 0;
-            cur.quantity = isXmj ? 0 : node.quantity || 0;
             if (isXmj) {
                 cur.dgn_qty1 = this.helper.add(cur.dgn_qty1, node.quantity || 0);
                 cur.dgn_qty2 = this.helper.add(cur.dgn_qty2, node.quantity2 || 0);

+ 1 - 1
app/middleware/phase_pay_check.js

@@ -39,7 +39,7 @@ module.exports = options => {
             if (!phasePay) throw '合同支付数据错误';
 
             // 读取原报、审核人数据
-            yield this.service.phasePay.loadUser(phasePay);
+            yield this.service.phasePay.doCheckPhase(phasePay);
             this.phasePay = phasePay;
             yield next;
         } catch (err) {

+ 2 - 2
app/public/css/main.css

@@ -1236,8 +1236,8 @@ a.maintain-icon{
 a.maintain-icon span{
   position: absolute;
   left:55px;
-  height:53px;
-  line-height: 53px;
+  height:40px;
+  line-height: 40px;
   width:390px;
   top:0;
   display:none;

Filskillnaden har hållts tillbaka eftersom den är för stor
+ 5605 - 138
app/public/js/change_information.js


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

@@ -955,6 +955,8 @@ $(document).ready(() => {
     postData(preUrl + '/defaultBills', {}, function (result) {
         gclGatherModel.loadLedgerData(result.bills);
         gclGatherModel.loadPosData(result.pos);
+        ledgerList = result.bills;
+        posList = result.pos;
 
         gclGatherData = gclGatherModel.gatherGclData();
         gclGatherData = _.filter(gclGatherData, function (item) {
@@ -998,6 +1000,7 @@ $(document).ready(() => {
             });
         }
 
+        // 默认模式
         SpreadJsObj.initSpreadSettingEvents(changeSpreadSetting, changeCol);
         SpreadJsObj.initSheet(changeSpreadSheet, changeSpreadSetting);
         SpreadJsObj.loadSheetData(changeSpreadSheet, SpreadJsObj.DataType.Data, changeList);

+ 9 - 9
app/public/js/change_revise.js

@@ -751,7 +751,7 @@ $(document).ready(() => {
                             }
                         } else {
                             try {
-                                data[col.field] = math.evaluate(transExpr(text));
+                                data[col.field] = ZhCalc.mathCalcExpr(transExpr(text));
                                 if (exprInfo) {
                                     data[exprInfo.expr] = newValue;
                                 }
@@ -887,7 +887,7 @@ $(document).ready(() => {
                             data[colSetting.field] = num;
                         } else {
                             try {
-                                data[colSetting.field] = math.evaluate(transExpr(value));
+                                data[colSetting.field] = ZhCalc.mathCalcExpr(transExpr(value));
                                 const exprInfo = getExprInfo(colSetting.field);
                                 if (exprInfo) {
                                     data[exprInfo.expr] = value;
@@ -988,7 +988,7 @@ $(document).ready(() => {
                                 data[colSetting.field] = num;
                             } else {
                                 try {
-                                    data[colSetting.field] = math.evaluate(transExpr(value));
+                                    data[colSetting.field] = ZhCalc.mathCalcExpr(transExpr(value));
                                 } catch (err) {
                                     toastMessageUniq(hint.invalidExpr);
                                     continue;
@@ -1267,7 +1267,7 @@ $(document).ready(() => {
                     }
                 } else {
                     try {
-                        data[field] = math.evaluate(transExpr(newValue));
+                        data[field] = ZhCalc.mathCalcExpr(transExpr(newValue));
                         if (exprInfo) {
                             data[exprInfo.expr] = newValue;
                         }
@@ -1942,7 +1942,7 @@ $(document).ready(() => {
                         }
                     } else {
                         try {
-                            data.postData[col.field] = math.evaluate(transExpr(newText));
+                            data.postData[col.field] = ZhCalc.mathCalcExpr(transExpr(newText));
                             if (exprInfo) {
                                 data.postData[exprInfo.expr] = newText;
                             }
@@ -1958,7 +1958,7 @@ $(document).ready(() => {
 
                     if (newText) {
                         try {
-                            data.postData[exprInfo.qty] = math.evaluate(transExpr(newText));
+                            data.postData[exprInfo.qty] = ZhCalc.mathCalcExpr(transExpr(newText));
                             data.postData[exprInfo.expr] = newText;
                         } catch(err) {
                             toastr.error('输入的表达式非法');
@@ -2169,7 +2169,7 @@ $(document).ready(() => {
                             posData[colSetting.field] = num;
                         } else {
                             try {
-                                posData[colSetting.field] = math.evaluate(transExpr(posData[colSetting.field]));
+                                posData[colSetting.field] = ZhCalc.mathCalcExpr(transExpr(posData[colSetting.field]));
                                 const exprInfo = getExprInfo(colSetting.field);
                                 if (exprInfo) {
                                     posData[exprInfo.expr] = trimInvalidChar(info.sheet.getText(curRow, curCol));
@@ -2187,7 +2187,7 @@ $(document).ready(() => {
                             const exprInfo = getExprInfo(colSetting.field, true);
                             posData[exprInfo.expr] = trimInvalidChar(info.sheet.getText(curRow, curCol));
                             if (posData[exprInfo.expr] || !posData[exprInfo.qty]) {
-                                posData[exprInfo.qty] = math.evaluate(transExpr(posData[exprInfo.expr]));
+                                posData[exprInfo.qty] = ZhCalc.mathCalcExpr(transExpr(posData[exprInfo.expr]));
                             }
                             bPaste = true;
                         } catch (err) {
@@ -2264,7 +2264,7 @@ $(document).ready(() => {
                     if (exprInfo) data[exprInfo.expr] = '';
                 } else {
                     try {
-                        data[field] = math.evaluate(transExpr(newValue));
+                        data[field] = ZhCalc.mathCalcExpr(transExpr(newValue));
                         if (exprInfo) data[exprInfo.expr] = newValue;
                     } catch (err) {
                         toastr.error('输入的表达式非法');

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

@@ -45,9 +45,9 @@ function autoFlashHeight(){
         navOriginalHeight = getObjHeight($('.nav-top')) + getObjHeight($('.nav-bottom')) + getObjHeight($('.logo'));
     }
     if (navOriginalHeight > getObjHeight($('.main-nav'))) {
-        $('.bg-nav li a span').hide();
+        $('.bg-nav li:not([class="bg-danger"]) a span').hide();
     } else {
-        $('.bg-nav li a span').show();
+        $('.bg-nav li:not([class="bg-danger"]) a span').show();
     }
 
 };

+ 16 - 14
app/public/js/ledger.js

@@ -6,6 +6,7 @@
  * @version
  */
 
+
 const ckBillsSpread = window.location.pathname + '-billsSelect';
 function checkTzMeasureType () {
     return tender.measure_type === measureType.tz.value;
@@ -590,7 +591,7 @@ $(document).ready(function() {
                             data[col.field] = num;
                         } else {
                             try {
-                                data[col.field] = math.evaluate(transExpr(newValue));
+                                data[col.field] = ZhCalc.mathCalcExpr(transExpr(newValue));
                                 if (exprInfo) {
                                     data[exprInfo.expr] = newValue;
                                 }
@@ -666,7 +667,7 @@ $(document).ready(function() {
                                     bPaste = true;
                                 } else {
                                     try {
-                                        data[colSetting.field] = math.evaluate(transExpr(value));
+                                        data[colSetting.field] = ZhCalc.mathCalcExpr(transExpr(value));
                                         bPaste = true;
                                     } catch(err) {
                                         delete data[colSetting.field];
@@ -772,7 +773,7 @@ $(document).ready(function() {
                             bPaste = true;
                         } else {
                             try {
-                                data[colSetting.field] = math.evaluate(transExpr(value));
+                                data[colSetting.field] = ZhCalc.mathCalcExpr(transExpr(value));
                                 const exprInfo = getExprInfo(colSetting.field);
                                 if (exprInfo) {
                                     data[exprInfo.expr] = value;
@@ -1223,7 +1224,7 @@ $(document).ready(function() {
                     }
                 } else {
                     try {
-                        data[field] = math.evaluate(transExpr(newValue));
+                        data[field] = ZhCalc.mathCalcExpr(transExpr(newValue));
                         if (exprInfo) {
                             data[exprInfo.expr] = newValue;
                         }
@@ -2035,7 +2036,7 @@ $(document).ready(function() {
                         }
                     } else {
                         try {
-                            data.updateData[col.field] = math.evaluate(transExpr(newText));
+                            data.updateData[col.field] = ZhCalc.mathCalcExpr(transExpr(newText));
                             if (exprInfo) {
                                 data.updateData[exprInfo.expr] = newText;
                             }
@@ -2051,7 +2052,7 @@ $(document).ready(function() {
 
                     if (newText) {
                         try {
-                            data.updateData[exprInfo.qty] = math.evaluate(transExpr(newText));
+                            data.updateData[exprInfo.qty] = ZhCalc.mathCalcExpr(transExpr(newText));
                             data.updateData[exprInfo.expr] = newText;
                         } catch(err) {
                             toastr.error('输入的表达式非法');
@@ -2243,7 +2244,7 @@ $(document).ready(function() {
                                 bPaste = true;
                             } else {
                                 try {
-                                    posData[colSetting.field] = math.evaluate(transExpr(posData[colSetting.field]));
+                                    posData[colSetting.field] = ZhCalc.mathCalcExpr(transExpr(posData[colSetting.field]));
                                     const exprInfo = getExprInfo(colSetting.field);
                                     if (exprInfo) {
                                         posData[exprInfo.expr] = trimInvalidChar(info.sheet.getText(curRow, curCol));
@@ -2260,7 +2261,7 @@ $(document).ready(function() {
                                 const exprInfo = getExprInfo(colSetting.field, true);
                                 posData[exprInfo.expr] = trimInvalidChar(info.sheet.getText(curRow, curCol));
                                 if (posData[exprInfo.expr] || !posData[exprInfo.qty]) {
-                                    posData[exprInfo.qty] = math.evaluate(transExpr(posData[exprInfo.expr]));
+                                    posData[exprInfo.qty] = ZhCalc.mathCalcExpr(transExpr(posData[exprInfo.expr]));
                                 }
                                 bPaste = true;
                             } catch (err) {
@@ -2338,7 +2339,7 @@ $(document).ready(function() {
                     }
                 } else {
                     try {
-                        data[field] = math.evaluate(transExpr(newValue));
+                        data[field] = ZhCalc.mathCalcExpr(transExpr(newValue));
                         if (exprInfo) {
                             data[exprInfo.expr] = newValue;
                         }
@@ -2572,7 +2573,7 @@ $(document).ready(function() {
                 } else if (col.field === 'expr') {
                     try {
                         updateData.expr = newText;
-                        updateData.quantity = math.evaluate(transExpr(newText));
+                        updateData.quantity = ZhCalc.mathCalcExpr(transExpr(newText));
                     } catch(err) {
                         toastr.error('输入的计算式非法');
                         SpreadJsObj.reLoadRowData(info.sheet, info.row);
@@ -2598,7 +2599,7 @@ $(document).ready(function() {
                 } else if (col.field === 'expr') {
                     try {
                         addData.expr = newText;
-                        addData.quantity = math.evaluate(transExpr(newText));
+                        addData.quantity = ZhCalc.mathCalcExpr(transExpr(newText));
                     } catch(err) {
                         toastr.error('输入的计算式非法');
                         SpreadJsObj.reLoadRowData(info.sheet, info.row);
@@ -2689,7 +2690,7 @@ $(document).ready(function() {
                     if (col.field === 'expr') {
                         try {
                             targetData.expr = trimInvalidChar(value);
-                            targetData.quantity = math.evaluate(transExpr(targetData.expr));
+                            targetData.quantity = ZhCalc.mathCalcExpr(transExpr(targetData.expr));
                         } catch(err) {
                             toastMessageUniq(hint.expr);
                             targetData.expr = '';
@@ -4984,7 +4985,7 @@ $(document).ready(function() {
         $('#upload-ybp').modal('show');
     });
     $('#id-post-dsk').click(async function() {
-        if (dsk.checkBind()) {
+        if (await dsk.checkBind()) {
             $('#import-dsk').modal('hide');
             dsk.chooseSubject({
                 validCheck: function (node, tree) {
@@ -5002,6 +5003,7 @@ $(document).ready(function() {
                     return true;
                 },
                 callback: async function (subjects) {
+                    // console.log(await dsk.loadBills(subjects[0].compilationId, subjects[0].subjectId));
                     postData('ledger/dsk', {subjects}, function (result) {
                         ledgerTree.loadDatas(result.bills);
                         treeCalc.calculateAll(ledgerTree);
@@ -5013,7 +5015,7 @@ $(document).ready(function() {
                 }
             });
         } else {
-            window.open('/profile/sms');
+            $('#binddskuser').modal('show');
         }
     });
 });

+ 17 - 9
app/public/js/material.js

@@ -212,22 +212,30 @@ $(document).ready(() => {
             {title: '|调差金额(材料税)', colSpan: '|1', rowSpan: '|1', field: 'm_tax_tp', hAlign: 2, width: 100, type: 'Number', readOnly: true, getValue: 'getValue.m_tax_tp'},
             {title: '截止上期|调差金额', colSpan: '2|1', rowSpan: '1|1', field: 'pre_tp', hAlign: 2, width: 100, type: 'Number', readOnly: true},
             {title: '|调差金额(材料税)', colSpan: '|1', rowSpan: '|1', field: 'tax_pre_tp', hAlign: 2, width: 100, type: 'Number', readOnly: true},
-            {title: '截止本期|调差金额', colSpan: '2|1', rowSpan: '1|1', field: 'end_tp', hAlign: 2, width: 100, type: 'Number', readOnly: true, getValue: 'getValue.end_tp'},
-            {title: '|调差金额(材料税)', colSpan: '|1', rowSpan: '|1', field: 'tax_end_tp', hAlign: 2, width: 100, type: 'Number', readOnly: true, getValue: 'getValue.tax_end_tp'},
-            {title: '备注', colSpan: '1', rowSpan: '2', field: 'remark', hAlign: 0, width: 50, formatter: '@', cellType: 'ellipsisAutoTip', readOnly: 'readOnly.remark'},
-            {title: '是否汇总', colSpan: '1', rowSpan: '2', field: 'is_summary', hAlign: 1, width: 60, cellType: 'checkbox', readOnly: 'readOnly.isEdit'}
-        ])
+        ]);
+        if (!isStageSelf) {
+            materialSpreadSettingCols = _.concat(materialSpreadSettingCols, [
+                {title: '截止本期|调差金额', colSpan: '2|1', rowSpan: '1|1', field: 'end_tp', hAlign: 2, width: 100, type: 'Number', readOnly: true, getValue: 'getValue.end_tp'},
+                {title: '|调差金额(材料税)', colSpan: '|1', rowSpan: '|1', field: 'tax_end_tp', hAlign: 2, width: 100, type: 'Number', readOnly: true, getValue: 'getValue.tax_end_tp'},
+            ]);
+        }
     } else {
         materialSpreadSettingCols = _.concat(materialSpreadSettingCols, [
             {title: '本期材料调差|有效价差', colSpan: '3|1', rowSpan: '1|1', field: 'm_spread', hAlign: 2, width: 60, type: 'Number', readOnly: true, getValue: 'getValue.m_spread'},
             {title: '|本期应耗数量', colSpan: '|1', rowSpan: '|1', field: 'quantity', hAlign: 2, width: 90, type: 'Number', readOnly: true},
             {title: '|调差金额', colSpan: '|1', rowSpan: '|1', field: 'm_tp', hAlign: 2, width: 100, type: 'Number', readOnly: true, getValue: 'getValue.m_tp'},
             {title: '截止上期|调差金额', colSpan: '1|1', rowSpan: '1|1', field: 'pre_tp', hAlign: 2, width: 100, type: 'Number', readOnly: true},
-            {title: '截止本期|调差金额', colSpan: '1|1', rowSpan: '1|1', field: 'end_tp', hAlign: 2, width: 100, type: 'Number', readOnly: true, getValue: 'getValue.end_tp'},
-            {title: '备注', colSpan: '1', rowSpan: '2', field: 'remark', hAlign: 0, width: 50, formatter: '@', cellType: 'ellipsisAutoTip', readOnly: 'readOnly.remark'},
-            {title: '是否汇总', colSpan: '1', rowSpan: '2', field: 'is_summary', hAlign: 1, width: 60, cellType: 'checkbox', readOnly: 'readOnly.isEdit'}
-        ])
+        ]);
+        if (!isStageSelf) {
+            materialSpreadSettingCols = _.concat(materialSpreadSettingCols, [
+                {title: '截止本期|调差金额', colSpan: '1|1', rowSpan: '1|1', field: 'end_tp', hAlign: 2, width: 100, type: 'Number', readOnly: true, getValue: 'getValue.end_tp'},
+            ]);
+        }
     }
+    materialSpreadSettingCols = _.concat(materialSpreadSettingCols, [
+        {title: '备注', colSpan: '1', rowSpan: '2', field: 'remark', hAlign: 0, width: 50, formatter: '@', cellType: 'ellipsisAutoTip', readOnly: 'readOnly.remark'},
+        {title: '是否汇总', colSpan: '1', rowSpan: '2', field: 'is_summary', hAlign: 1, width: 60, cellType: 'checkbox', readOnly: 'readOnly.isEdit'},
+    ]);
     materialSpreadSetting.cols = materialSpreadSettingCols;
     const spCol = _.find(materialSpreadSetting.cols, {field: 'quantity'});
     spCol.readOnly = true;

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

@@ -59,7 +59,7 @@ $(document).ready(function () {
     })
 
     // 添加审批流程按钮逻辑
-    $('.book-list').on('click', '.book-list dt', function () {
+    $('body').on('click', '.book-list dt', function () {
         const idx = $(this).find('.acc-btn').attr('data-groupid')
         const type = $(this).find('.acc-btn').attr('data-type')
         if (type === 'hide') {

+ 59 - 12
app/public/js/measure_compare.js

@@ -38,13 +38,13 @@ const billsSpreadSetting = {
 const posSpreadSetting = {
     baseCols: [
         {title: '名称', colSpan: '1', rowSpan: '1', field: 'name', hAlign: 0, width: 230, formatter: '@'},
-        {title: '台账数量', colSpan: '1', rowSpan: '1', field: 'quantity', hAlign: 2, width: 60, type: 'Number'},
+        {title: '台账数量', colSpan: '1', rowSpan: '1', field: 'quantity', hAlign: 2, width: 80, type: 'Number'},
     ],
     extraCols: [
-        {title: '%s数量', colSpan: '1', rowSpan: '1', field: '{%s}_qty{%d}', hAlign: 2, width: 60, type: 'Number'},
+        {title: '%s\n数量', colSpan: '1', rowSpan: '1', field: '{%s}_qty{%d}', hAlign: 2, width: 80, type: 'Number'},
     ],
     specExtraCols: [
-        {title: '合计数量', colSpan: '1', rowSpan: '1', field: 'sum_{%s}_qty', hAlign: 2, width: 60, type: 'Number', },
+        {title: '合计数量', colSpan: '1', rowSpan: '1', field: 'sum_{%s}_qty', hAlign: 2, width: 80, type: 'Number', },
     ],
     emptyRows: 3,
     headRows: 1,
@@ -122,13 +122,13 @@ const leafXmjSpreadSetting = {
         {title: '细目', colSpan: '1', rowSpan: '2', field: 'jldy', hAlign: 0, width: 80, formatter: '@'},
         {title: '计量单元', colSpan: '1', rowSpan: '2', field: 'bwmx', hAlign: 0, width: 80, formatter: '@'},
         {title: '图册号', colSpan: '1', rowSpan: '2', field: 'drawing_code', hAlign: 0, width: 80, formatter: '@'},
-        {title: '台账数量', colSpan: '1', rowSpan: '2', field: 'quantity', hAlign: 2, width: 60, type: 'Number'},
+        {title: '台账数量', colSpan: '1', rowSpan: '2', field: 'quantity', hAlign: 2, width: 80, type: 'Number'},
     ],
     extraCols: [
-        {title: '%s数量', colSpan: '1', rowSpan: '2', field: '{%s}_qty{%d}', hAlign: 2, width: 60},
+        {title: '%s\n数量', colSpan: '1', rowSpan: '2', field: '{%s}_qty{%d}', hAlign: 2, width: 80},
     ],
     specExtraCols: [
-        {title: '合计数量', colSpan: '1', rowSpan: '2', field: 'sum_{%s}_qty', hAlign: 2, width: 60, type: 'Number', },
+        {title: '合计数量', colSpan: '1', rowSpan: '2', field: 'sum_{%s}_qty', hAlign: 2, width: 80, type: 'Number', },
     ],
     emptyRows: 0,
     headRows: 2,
@@ -177,6 +177,29 @@ const exportGclSpreadSetting = {
     readOnly: true,
 };
 
+const chapterSpreadSetting = {
+    baseCols: [
+        {title: '章节', colSpan: '1', rowSpan: '1', field: 'code', hAlign: 0, width: 80, formatter: '@'},
+        {title: '名称', colSpan: '1', rowSpan: '1', field: 'name', hAlign: 0, width: 230, formatter: '@'},
+        {title: '签约金额', colSpan: '1', rowSpan: '1', field: 'deal_bills_tp', hAlign: 2, width: 80, type: 'Number'},
+        {title: '台账金额', colSpan: '1', rowSpan: '1', field: 'total_price', hAlign: 2, width: 80, type: 'Number'},
+    ],
+    extraCols: [
+        {title: '%s\n金额', colSpan: '1', rowSpan: '1', field: '{%s}_tp{%d}', hAlign: 2, width: 80, type: 'Number', },
+    ],
+    specExtraCols: [
+        {title: '合计金额', colSpan: '1', rowSpan: '1', field: 'sum_{%s}_tp', hAlign: 2, width: 80, type: 'Number', },
+    ],
+    emptyRows: 0,
+    headRows: 1,
+    headRowHeight: [38],
+    headColWidth: [30],
+    defaultRowHeight: 21,
+    headerFont: '12px 微软雅黑',
+    font: '12px 微软雅黑',
+    readOnly: true,
+};
+
 function initSpreadSettingWithRoles(compareRoles) {
     function setSpreadSettingCols(setting, fieldSufs, Roles) {
         function addExtraCols(fieldSuf, Role) {
@@ -217,7 +240,7 @@ function initSpreadSettingWithRoles(compareRoles) {
             const tr = trs[r-1];
             if (tr) {
                 fieldSufs.push(r + '');
-                roles.push(tr.children[0].textContent);
+                roles.push(`${tr.children[0].textContent}(${tr.children[1].textContent})`);
             }
         }
     }
@@ -227,6 +250,7 @@ function initSpreadSettingWithRoles(compareRoles) {
     setSpreadSettingCols(gclSpreadSetting, fieldSufs, roles);
     setSpreadSettingCols(leafXmjSpreadSetting, fieldSufs, roles);
     setSpreadSettingCols(exportGclSpreadSetting, fieldSufs, roles);
+    setSpreadSettingCols(chapterSpreadSetting, fieldSufs, roles);
 }
 function calculateStageLedgerData(datas) {
     for (const d of datas) {
@@ -266,6 +290,12 @@ $(document).ready(() => {
     if (thousandth) sjsSettingObj.setTpThousandthFormat(leafXmjSpreadSetting);
     SpreadJsObj.initSheet(leafXmjSheet, leafXmjSpreadSetting);
 
+    const chapterSpread = SpreadJsObj.createNewSpread($('#chapter-spread')[0]);
+    const chapterSheet = chapterSpread.getActiveSheet();
+    sjsSettingObj.setGridSelectStyle(gclSpreadSetting, sjsSettingObj.FxTreeStyle.jz);
+    if (thousandth) sjsSettingObj.setTpThousandthFormat(chapterSpreadSetting);
+    SpreadJsObj.initSheet(chapterSheet, chapterSpreadSetting);
+
     $.subMenu({
         menu: '#sub-menu', miniMenu: '#sub-mini-menu', miniMenuList: '#mini-menu-list',
         toMenu: '#to-menu', toMiniMenu: '#to-mini-menu',
@@ -345,6 +375,8 @@ $(document).ready(() => {
         gclGatherData = gclGatherModel.gatherGclData();
         SpreadJsObj.loadSheetData(gclSheet, SpreadJsObj.DataType.Data, gclGatherData);
         loadLeafXmjData(0);
+        const chapterData = gclGatherModel.gatherChapterData(chapter, result.spec, ['deal_tp', 'total_price', 'sum_contract_tp', 'sum_qc_tp', 'sum_gather_tp']);
+        SpreadJsObj.loadSheetData(chapterSheet, SpreadJsObj.DataType.Data, chapterData);
     }, null, true);
     function loadPosData(iRow) {
         const node = iRow ? billsSheet.zh_tree.nodes[iRow] : SpreadJsObj.getSelectObject(billsSheet);
@@ -394,12 +426,14 @@ $(document).ready(() => {
     });
     const compareStages = [];
     $('#select-qi-ok').click(function () {
-        function refreshView () {
+        function refreshView (data) {
+            const gatherField = ['deal_tp', 'total_price', 'sum_contract_tp', 'sum_qc_tp', 'sum_gather_tp'];
             compareStages.length = 0;
             for (let order = 0, iLength = trs.length; order < iLength; order++) {
                 const tr = trs[order];
                 if ($('input', tr)[0].checked) {
                     compareStages.push(order + 1);
+                    gatherField.push(`contract_tp${order}`, `qc_tp${order}`, `gather_tp${order}`);
                 }
             }
             // setLocalCache(cCacheKey, compareStages.join(','));
@@ -418,6 +452,10 @@ $(document).ready(() => {
             gclGatherData = gclGatherModel.gatherGclData();
             SpreadJsObj.loadSheetData(gclSheet, SpreadJsObj.DataType.Data, gclGatherData);
             loadLeafXmjData(0);
+
+            const chapterData = gclGatherModel.gatherChapterData(chapter, data.spec, gatherField);
+            SpreadJsObj.reLoadSheetHeader(chapterSheet);
+            SpreadJsObj.loadSheetData(chapterSheet, SpreadJsObj.DataType.Data, chapterData);
         }
         let loadData = [], showData = [], trs = $('tr[stage-id]');
         for (let order = 0, iLength = trs.length; order < iLength; order++) {
@@ -440,7 +478,7 @@ $(document).ready(() => {
                 cTree.reCalcSumData(['contract_qty', 'contract_tp', 'qc_qty', 'qc_tp', 'gather_qty', 'gather_tp'], showData);
                 treeCalc.calculateAll(cTree);
                 cPos.reCalcSumData(['contract_qty', 'qc_qty', 'gather_qty'], showData);
-                refreshView();
+                refreshView(result);
                 $('#select-qi').modal('hide');
             }, null, true);
         } else {
@@ -505,9 +543,14 @@ $(document).ready(() => {
 
             SpreadExcelObj.exportSimpleXlsxSheet(exportGclSpreadSetting, data, $('.sidebar-title').attr('data-original-title') + "-多期比较.xlsx");
         };
+        const exportChapter = function () {
+            SpreadExcelObj.exportSimpleXlsxSheet(chapterSpreadSetting, chapterSheet.zh_data, $('.sidebar-title').attr('data-original-title') + "-多期比较.xlsx");
+        };
         const cur = $('.active[name=compareType]').attr('href');
         if (cur.indexOf('gcl') >= 0) {
             exportGcl();
+        } else if (cur.indexOf('chapter') >= 0) {
+            exportChapter();
         } else {
             exportLedger();
         }
@@ -518,12 +561,15 @@ $(document).ready(() => {
         SpreadJsObj.reLoadSheetHeader(billsSheet);
         SpreadJsObj.reloadColData(billsSheet, billsSpreadSetting.baseCols.length, compareStages.length * billsSpreadSetting.extraCols.length + billsSpreadSetting.specExtraCols.length);
         SpreadJsObj.reLoadSheetHeader(posSheet);
-        SpreadJsObj.reloadColData(posSheet, posSpreadSetting.baseCols.length, compareStages.length * posSpreadSetting.extraCols.length + billsSpreadSetting.specExtraCols.length);
+        SpreadJsObj.reloadColData(posSheet, posSpreadSetting.baseCols.length, compareStages.length * posSpreadSetting.extraCols.length + posSpreadSetting.specExtraCols.length);
 
         SpreadJsObj.reLoadSheetHeader(gclSheet);
-        SpreadJsObj.reloadColData(gclSheet, gclSpreadSetting.baseCols.length, compareStages.length * gclSpreadSetting.extraCols.length + billsSpreadSetting.specExtraCols.length);
+        SpreadJsObj.reloadColData(gclSheet, gclSpreadSetting.baseCols.length, compareStages.length * gclSpreadSetting.extraCols.length + gclSpreadSetting.specExtraCols.length);
         SpreadJsObj.reLoadSheetHeader(leafXmjSheet);
-        SpreadJsObj.reloadColData(leafXmjSheet, leafXmjSpreadSetting.baseCols.length, compareStages.length * leafXmjSpreadSetting.extraCols.length + billsSpreadSetting.specExtraCols.length);
+        SpreadJsObj.reloadColData(leafXmjSheet, leafXmjSpreadSetting.baseCols.length, compareStages.length * leafXmjSpreadSetting.extraCols.length + leafXmjSpreadSetting.specExtraCols.length);
+
+        SpreadJsObj.reLoadSheetHeader(chapterSheet);
+        SpreadJsObj.reloadColData(chapterSheet, chapterSpreadSetting.baseCols.length, compareStages.length * chapterSpreadSetting.extraCols.length + chapterSpreadSetting.specExtraCols.length);
     });
     $('[name=compareType]').click(function () {
         $('[name=compareType]').removeClass('active');
@@ -534,6 +580,7 @@ $(document).ready(() => {
         posSpread.refresh();
         gclSpread.refresh();
         leafXmjSpread.refresh();
+        chapterSpread.refresh();
     });
     $('#select-qi-all').click(function() {
         const check = this.checked;

+ 2 - 1
app/public/js/path_tree.js

@@ -1400,8 +1400,9 @@ const createNewPathTree = function (type, setting) {
             this.Locked = ids || [];
             for (const f of this.Locked) {
                 f.locked_ledger_id = f.ass_ledger_id ? f.ass_ledger_id.split(',') : (f.audit_ledger_id ? f.audit_ledger_id.split(',') : []);
-                for (const id of f.ass_ledger_id) {
+                for (const id of f.locked_ledger_id) {
                     const node = this.getItems(id);
+                    if (!node) continue;
                     node.locked = true;
                     const posterity = this.getPosterity(node);
                     for (const pn of posterity) {

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

@@ -345,7 +345,7 @@ $(document).ready(function() {
                         data[col.field] = num;
                     } else {
                         try {
-                            data[col.field] = math.evaluate(transExpr(text));
+                            data[col.field] = ZhCalc.mathCalcExpr(transExpr(text));
                         } catch(err) {
                             toastr.error('输入的表达式非法');
                             SpreadJsObj.reLoadRowData(info.sheet, info.row);
@@ -474,7 +474,7 @@ $(document).ready(function() {
                             data[colSetting.field] = num;
                         } else {
                             try {
-                                data[colSetting.field] = math.evaluate(transExpr(value));
+                                data[colSetting.field] = ZhCalc.mathCalcExpr(transExpr(value));
                                 bPaste = true;
                             } catch (err) {
                                 toastMessageUniq(hint.invalidExpr);

+ 992 - 0
app/public/js/phase_pay_detail.js

@@ -0,0 +1,992 @@
+'use strict';
+
+$(document).ready(() => {
+    const payUtils = {
+        tips: {
+            name: function(data) {
+                const tips = [];
+                if (data) {
+                    if (data.pause) tips.push('当前项已停用');
+                    if (!data.is_yf) tips.push('当前项不参与本期应付计算');
+                }
+                return tips.join('<br/>');
+            },
+            range_tp: function (data) {
+                if (!data || (!data.range_expr && !data.range_tp) || !data.dl_type) return '';
+
+                if (data.dl_type === 1) {
+                    return '计提期限为(当 计量期数 ≥ ' + data.dl_count + ')';
+                } else if (data.dl_type === 2) {
+                    switch (data.dl_tp_type) {
+                        case 'contract':
+                            return '计提期限为(累计合同计量 ≥ ' + data.dl_tp + ')';
+                        case 'qc':
+                            return '计提期限为(累计变更计量 ≥ ' + data.dl_tp + ')';
+                        case 'gather':
+                            return '计提期限为(累计完成计量 ≥ ' + data.dl_tp + ')';
+                    }
+                }
+            }
+        },
+        check: {
+            isFixed: function(data) {
+                return data.is_fixed;
+            },
+            isStarted: function (data) {
+                return data.pre_used;
+            },
+            isYf: function(data) {
+                return data.pay_type === 'bqyf';
+            },
+            isSf: function(data) {
+                return data.pay_type === 'bqsf';
+            },
+            isGatherValid: function(data) {
+                return !data.pay_type && (!data.children || data.children.length === 0);
+            },
+            isOwner: function(data) {
+                return data.create_user_id === userID;
+            },
+            isFinish: function(data) {
+                return data.pre_finish;
+            },
+            isYB: function() {
+                return userID === phasePay.create_user_id;
+            },
+            isOld: function(data) {
+                return data.phase_id !== data.create_phase_id;
+            },
+            isLock: function (data) {
+                const result = !!lockPayExpr && payUtils.check.isStarted(data) && payCalc.hasBase(data.expr);
+                return result;
+            },
+            tpReadOnly: function(data) {
+                return payUtils.check.isYf(data) || payUtils.check.isLock(data);
+            },
+            startTpReadOnly: function(data) {
+                if (payUtils.check.isOld(data)) {
+                    return payUtils.check.isStarted(data) || !payUtils.check.isYB(data) || payUtils.check.isLock(data);
+                } else {
+                    return payUtils.check.isWC(data) || payUtils.check.isSF(data) || payUtils.check.isYf(data) || !(payUtils.check.isOwner(data) || payUtils.check.isYB());
+                }
+            }
+        },
+        menuVisible: {
+            pause: function (data) {
+                if (payUtils.check.isOld(data)) {
+                    return payUtils.check.isYB();
+                } else {
+                    return payUtils.check.isOwner(data) || payUtils.check.isYB();
+                }
+            },
+            deadline: function (data) {
+                if (payUtils.check.isOld(data)) {
+                    return !payUtils.check.isFinish(data) && payUtils.check.isYB();
+                } else {
+                    return payUtils.check.isOwner(data) || payUtils.check.isYB();
+                }
+            }
+        },
+    };
+    const payCalc = (function (b, a) {
+        class PayCalc {
+            constructor (bases, add) {
+                this.percentReg = /((\d+)|((\d+)(\.\d+)))%/g;
+                this.bases = bases;
+                this.bases.sort(function (a, b) {
+                    return a.sort - b.sort;
+                });
+                for (const b of this.bases) {
+                    b.reg = new RegExp(b.code, 'igm');
+                }
+                this.addBase = add;
+                this.orderReg = /f\d+/ig;
+                this.nodeReg = /<<[a-z0-9\-]+>>/ig;
+            }
+            hasBase(expr) {
+                if (!expr) return false;
+                for (const b of this.bases) {
+                    if (data.expr.indexOf(b.code) >= 0) return true;
+                }
+                return false;
+            }
+            trans2OrderExpr(expr, payTree) {
+                const nodeParam = expr.match(this.nodeReg);
+                if (nodeParam) {
+                    for (const op of nodeParam) {
+                        const id = op.substring(2, op.length - 2);
+                        const payNode = payTree.nodes.find(x => { return x.uuid === id; });
+                        expr = expr.replace(op, payNode ? `f${payTree.getNodeIndex(payNode) + 1}` || '' : 0);
+                    }
+                }
+                return expr;
+            }
+            trans2NodeExpr(expr, payTree) {
+                const orderParam = expr.match(this.orderReg);
+                if (orderParam) {
+                    for (const op of orderParam) {
+                        const order = parseInt(op.substring(1, op.length));
+                        const payNode = payTree.nodes[order - 1];
+                        expr = expr.replace(op, payNode ? `<<${payNode.uuid}>>` || '' : 0);
+                    }
+                }
+                return expr;
+            }
+            checkExprValid(expr, invalidParam, selfId, payTree) {
+                if (!expr) return [true, ''];
+                const param = [];
+                let num = '', base = '';
+                let fixedIdParam;
+                for (let i = 0, iLen = expr.length; i < iLen; i++) {
+                    const subExpr = expr.substring(i, expr.length);
+                    if (/^[\d\.%]+/.test(expr[i])) {
+                        if (base !== '') {
+                            param.push({type: 'base', value: base});
+                            base = '';
+                        }
+                        num = num + expr[i];
+                    } else if (this.nodeReg.test(subExpr)) {
+                        if (num !== '') {
+                            param.push({type: 'num', value: num});
+                            num = '';
+                        }
+                        if (base !== '') {
+                            param.push({type: 'base', value: base});
+                            base = '';
+                        }
+                        // const node = this.nodeReg.exec(subExpr);
+                        const node = subExpr.match(this.nodeReg);
+                        param.push({type: 'node', value: node[0]});
+                        i = i + node[0].length - 1;
+                    } else if (/^[a-z]/.test(expr[i])) {
+                        if (num !== '') {
+                            param.push({type: 'num', value: num});
+                            num = '';
+                        }
+                        base = base + 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].type === 'num') {
+                        param[1].value = '-' + param[1].value;
+                        param.shift();
+                    }
+                }
+                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') {
+                        const baseParam = _.find(calcBase, {code: p.value});
+                        if (!baseParam)
+                            return [false, '输入的表达式非法:不存在计算基数' + p.value];
+                        if (invalidParam && invalidParam.indexOf(p.value) >= 0)
+                            return [false, '不可使用计算基数' + p.value];
+                        if (i > 0 && (param[i - 1].type === 'num' || param[i - 1].type === 'right'))
+                            return [false, '输入的表达式非法:' + p.value + '前应有运算符'];
+                    }
+                    if (p.type === 'node') {
+                        if (!selfId) return [false, '输入的表达式错误:不支持行号引用'];
+
+                        if ([`<<${selfId}>>`].indexOf(p.value) >= 0) return [false, '输入的表达式非法:请勿引用自己'];
+                        if (!fixedIdParam) {
+                            fixedIdParam = payTree.nodes.filter(x => { return x.is_fixed; }).map(x => { return `<<${x.uuid}>>`});
+                        }
+                        if (fixedIdParam.indexOf(p.value) >= 0) return [false, '输入的表达式非法:请勿引用固定项'];
+                    }
+                    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, '输入的表达式非法:"("后无对应的")"'];
+
+                if (selfId) {
+                    const circular = payCalc.checkCircularExpr(expr, selfId, payTree);
+                    // 当前循环计算不检查父项
+                    if (circular) return [false, '输入的表达式非法:循环引用'];
+                }
+                return [true, ''];
+            }
+            checkSfExpr(text, data, payNode, payTree) {
+                if (text) {
+                    const num = _.toNumber(text);
+                    if (num) {
+                        data.expr = num;
+                    } else {
+                        const expr = this.trans2NodeExpr($.trim(text).replace('\t', '').replace('=', '').toLowerCase(), payTree);
+                        const [valid, msg] = this.checkExprValid(expr, [], payNode.uuid, payTree);
+                        if (!valid) return [valid, msg];
+                        data.expr = expr;
+                    }
+                } else {
+                    data.tp = 0;
+                    data.expr = '';
+                }
+                return [true, ''];
+            }
+            checkExpr(text, data, payNode, payTree) {
+                if (text) {
+                    const num = _.toNumber(text);
+                    if (num) {
+                        data.tp = num;
+                        data.expr = '';
+                    } else {
+                        const expr = this.trans2NodeExpr($.trim(text).replace('\t', '').replace('=', '').toLowerCase(), payTree);
+                        const [valid, msg] = this.checkExprValid(expr, ['bqyf'], payNode.uuid, payTree);
+                        if (!valid) return [valid, msg];
+                        data.expr = expr;
+                        data.tp = 0;
+                    }
+                } else {
+                    data.tp = 0;
+                    data.expr = '';
+                }
+                return [true, ''];
+            }
+            checkRangeExpr(payNode, text, data) {
+                if (!payNode) return [false, '数据错误'];
+
+                const num = text ? _.toNumber(text) : 0;
+                let expr = text ? (num ? '' : text) : '';
+                expr = expr ? $.trim(expr).replace('\t', '').replace('=', '').toLowerCase() : '';
+                const [valid, msg] = this.checkExprValid(expr, ['bqwc', 'ybbqwc', 'bqht', 'bqbg', 'bqyf']);
+                if (!valid) return [valid, msg];
+
+                if (payUtils.check.isStarted(payNode)) {
+                    if (payUtils.check.isSf(payNode)) {
+                        const value = expr ? payCalc.calculateExpr(expr) : num;
+                        if (payNode.pre_tp && value < payNode.pre_tp) return [false, '截止上期已计量' + payNode.pre_tp + ',扣款限额请勿少于改值'];
+                        data.range_tp = num;
+                        data.range_expr = expr;
+                        return [true, ''];
+                    } else {
+                        // if (payNode.pre_finish) return [false, '已达扣款限额,请勿修改'];
+                        // const value = expr ? payCalc.calculateExpr(expr) : num;
+                        // if (payNode.pre_tp && value < payNode.pre_tp) return [false, '截止上期已计量' + payNode.pre_tp + ',扣款限额请勿少于改值'];
+                        // data.range_tp = num;
+                        // data.range_expr = expr;
+                        return [false, '已经开始使用,请勿修改扣款限额'];
+                    }
+                } else {
+                    data.range_tp = num;
+                    data.range_expr = expr;
+                    return [true, ''];
+                }
+            }
+            checkStartExpr(payNode, text, data) {
+                if (!payNode) return [false, '数据错误'];
+
+                const num = text ? _.toNumber(text) : 0;
+                let expr = text ? (num ? '' : text) : '';
+                expr = expr ? $.trim(expr).replace('\t', '').replace('=', '').toLowerCase() : '';
+                const [valid, msg] = this.checkExprValid(expr, ['bqwc', 'ybbqwc', 'bqht', 'bqbg', 'bqyf']);
+                if (!valid) return [valid, msg];
+
+                if (payUtils.check.isStarted(payNode)) {
+                    return [false, '已经开始计量,请勿修改起扣金额'];
+                } else {
+                    if (this.addBase.pre_gather_tp) {
+                        const value = expr ? payCalc.calculateExpr(expr) : num;
+                        if (this.addBase.pre_gather_tp && value < this.addBase.pre_gather_tp)
+                            return [false, '起扣金额请勿少于本期完成截止上期计量金额' + this.addBase.pre_gather_tp];
+                        data.start_tp = num;
+                        data.start_expr = expr;
+                        return [true, ''];
+                    } else {
+                        data.start_tp = num;
+                        data.start_expr = expr;
+                        return [true, ''];
+                    }
+                }
+            }
+            getExprInfo(field, converse = false) {
+                const exprField = [
+                    {qty: 'tp', expr: 'expr'},
+                    {qty: 'start_tp', expr: 'start_expr'},
+                    {qty: 'range_qty', expr: 'range_expr'},
+                ];
+                if (converse) return _.find(exprField, { expr: field });
+                return _.find(exprField, {qty: field});
+            }
+            getLeafOrder(data, parentReg, tree) {
+                if (!data) return [];
+                const defaultResult = data.uuid ? [`<<${data.uuid}>>`] : [];
+                if (!data.expr) return defaultResult;
+                const nodeParam = data.expr.match(this.nodeReg);
+                if (!nodeParam || nodeParam.length === 0) return defaultResult;
+
+                const result = [];
+                for (const op of nodeParam) {
+                    const id = op.substring(2, op.length - 2);
+                    if (data.uuid === id || op === parentReg) {
+                        result.push(op);
+                    } else {
+                        const payNode = tree.nodes.find(x => {return x.uuid === id; });
+                        const subOrderParam = this.getLeafOrder(payNode, data.uuid ? `<<${data.uuid}>>` : parentReg, tree);
+                        result.push(...subOrderParam);
+                    }
+                }
+                return result;
+            }
+            checkCircularExpr(expr, selfId, tree) {
+                const leafOrder = this.getLeafOrder({expr}, `<<${selfId}>>`, tree);
+
+                if (leafOrder.indexOf(`<<${selfId}>>`) >= 0) return true;
+                return false;
+            }
+            calculateExpr(expr) {
+                let formula = expr;
+                for (const b of this.bases) {
+                    formula = formula.replace(b.reg, b.value);
+                }
+                const percent = formula.match(this.percentReg);
+                if (percent) {
+                    for (const p of percent) {
+                        const v = math.evaluate(p.replace(new RegExp('%', 'gm'), '/100'));
+                        formula = formula.replace(p, v);
+                    }
+                }
+                try {
+                    const value = ZhCalc.mathCalcExpr(formula);
+                    return value;
+                } catch(err) {
+                    return 0;
+                }
+            }
+            refreshBaseHtml() {
+                const html = [];
+                for (const [i, b] of this.bases.entries()) {
+                    html.push('<tr>');
+                    html.push(`<td>${i+1}</td><td>${b.name}</td><td>${b.code}</td>`);
+                    if (b.code === 'bqyf') {
+                        html.push('<td class="text-right">--</td>');
+                    } else {
+                        html.push(`<td class="text-right">${b.formatValue}</td>`);
+                    }
+                    html.push('</tr>');
+                }
+                $('#base-list').html(html.join(''));
+            }
+            reloadBase(bases, add) {
+                this.bases = bases;
+                this.refreshBaseHtml();
+                this.bases.sort(function (a, b) {
+                    return a.sort - b.sort;
+                });
+                for (const b of this.bases) {
+                    b.reg = new RegExp(b.code, 'igm');
+                }
+                this.addBase = add;
+            }
+        }
+
+        return new PayCalc(b, a);
+    })(calcBase, addBase);
+    const payObj = (function() {
+        const spread = SpreadJsObj.createNewSpread($('#pay-spread')[0]);
+        const sheet = spread.getActiveSheet();
+        const spreadSetting = {
+            cols: [
+                {title: '名称', colSpan: '1', rowSpan: '1', field: 'name', hAlign: 0, width: 230, formatter: '@', cellType: 'tree', getTip: payUtils.tips.name},
+                {title: '本期金额(F)', colSpan: '1', rowSpan: '1', field: 'tp', hAlign: 2, width: 100, type: 'Number'},
+                {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: 'start_tp', hAlign: 2, width: 100, type: 'Number'},
+                {title: '付(扣)款限额',  colSpan: '1', rowSpan: '1', field: 'range_tp', hAlign: 2, width: 100, cellType: 'tip', type: 'Number', getTip: payUtils.tips.range_tp},
+                {title: '汇总',  colSpan: '1', rowSpan: '1', field: 'is_gather', hAlign: 1, width: 60, cellType: 'signalCheckbox', show: payUtils.check.isGatherValid},
+                {title: '附件', colSpan: '1', rowSpan: '1', field: 'attachment', hAlign: 0, width: 60, readOnly: true, cellType: 'imageBtn', normalImg: '#rela-file-icon', hoverImg: '#rela-file-hover', getValue: 'getValue.attachment'},
+                {title: '本期批注', colSpan: '1', rowSpan: '1', field: 'postil', hAlign: 0, width: 120, formatter: '@', cellType: 'autoTip'},
+            ],
+            emptyRows: 0,
+            headRows: 1,
+            headRowHeight: [32],
+            headColWidth: [30],
+            defaultRowHeight: 21,
+            headerFont: '12px 微软雅黑',
+            font: '12px 微软雅黑',
+            readOnly: readOnly,
+            localCache: {
+                key: 'phase-pay',
+                colWidth: true,
+            },
+            pos: SpreadJsObj.getObjPos($('#pay-spread')[0]),
+        };
+        sjsSettingObj.setFxTreeStyle(spreadSetting, sjsSettingObj.FxTreeStyle.phasePay);
+        SpreadJsObj.initSheet(sheet, spreadSetting);
+        const payTree = createNewPathTree('base', {
+            id: 'tree_id', pid: 'tree_pid', order: 'tree_order',
+            level: 'tree_level', isLeaf: 'tree_is_leaf', fullPath: 'tree_full_path',
+            rootId: -1,
+        });
+        const payEvent = {
+            refreshActn: function() {
+                const setObjEnable = function (obj, enable) {
+                    if (enable) {
+                        obj.removeClass('disabled');
+                    } else {
+                        obj.addClass('disabled');
+                    }
+                };
+                const select = SpreadJsObj.getSelectObject(sheet);
+                if (!select) {
+                    setObjEnable($('a[name=base-opr][type=add]'), false);
+                    setObjEnable($('a[name=base-opr][type=del]'), false);
+                    setObjEnable($('a[name=base-opr][type=up-move]'), false);
+                    setObjEnable($('a[name=base-opr][type=down-move]'), false);
+                    return;
+                }
+                const preNode = payTree.getPreSiblingNode(select);
+                setObjEnable($('a[name=base-opr][type=add]'), !readOnly && !payUtils.check.isSf(select) && !payUtils.check.isYf(select));
+                const delValid = !payUtils.check.isFixed(select) && !payUtils.check.isStarted(select);
+                setObjEnable($('a[name=base-opr][type=delete]'), !readOnly && delValid);
+                setObjEnable($('a[name=base-opr][type=up-move]'), !readOnly && !payUtils.check.isFixed(select) && preNode);
+                setObjEnable($('a[name=base-opr][type=down-move]'), !readOnly && !payUtils.check.isFixed(select) && !payTree.isLastSibling(select));
+            },
+            loadExprToInput: function() {
+                const sel = sheet.getSelections()[0];
+                const col = sheet.zh_setting.cols[sel.col];
+                const data = SpreadJsObj.getSelectObject(sheet);
+                if (data && (!data.children || data.children.length === 0)) {
+                    if (col.field === 'tp') {
+                        const expr = payCalc.trans2OrderExpr(data.expr, payTree);
+                        $('#pay-expr').val(expr).attr('field', 'expr').attr('org', expr)
+                            .attr('readOnly', readOnly|| payUtils.check.tpReadOnly(data));
+                    } else if (col.field === 'start_tp') {
+                        const expr = payCalc.trans2OrderExpr(data.start_expr, payTree) || data.start_tp;
+                        $('#pay-expr').val(expr).attr('field', 'start_expr').attr('org', expr)
+                            .attr('readOnly', readOnly|| payUtils.check.startTpReadOnly(data) || payUtils.check.isYf(data));
+                    } else if (col.field === 'range_tp') {
+                        const expr = payCalc.trans2OrderExpr(data.range_expr, payTree);
+                        $('#pay-expr').val(expr).attr('field', 'range_expr').attr('org', expr)
+                            .attr('readOnly', readOnly|| payUtils.check.rangeTpReadOnly(data) || payUtils.check.isYf(data));
+                    } else {
+                        $('#pay-expr').val('').attr('readOnly', true);
+                    }
+                    $('#pay-expr').attr('data-row', sel.row);
+                } else {
+                    $('#pay-expr').val('').attr('readOnly', true);
+                    $('#pay-expr').removeAttr('data-row');
+                }
+            },
+            refreshTree: function (data) {
+                SpreadJsObj.massOperationSheet(sheet, function () {
+                    const tree = sheet.zh_tree;
+                    // 处理删除
+                    if (data.delete) {
+                        data.delete.sort(function (a, b) {
+                            return b.deleteIndex - a.deleteIndex;
+                        });
+                        for (const d of data.delete) {
+                            sheet.deleteRows(d.deleteIndex, 1);
+                        }
+                    }
+                    // 处理新增
+                    if (data.create) {
+                        const newNodes = data.create;
+                        if (newNodes) {
+                            newNodes.sort(function (a, b) {
+                                return a.index - b.index;
+                            });
+                            for (const node of newNodes) {
+                                sheet.addRows(node.index, 1);
+                                SpreadJsObj.reLoadRowData(sheet, tree.nodes.indexOf(node), 1);
+                            }
+                        }
+                    }
+                    // 处理更新
+                    if (data.update) {
+                        const rows = [];
+                        for (const u of data.update) {
+                            rows.push(tree.nodes.indexOf(u));
+                        }
+                        SpreadJsObj.reLoadRowsData(sheet, rows);
+                    }
+                });
+            },
+            editStarting: function(e, info) {
+                const col = info.sheet.zh_setting.cols[info.col];
+                const select = SpreadJsObj.getSelectObject(info.sheet);
+                switch (col.field) {
+                    case 'name':
+                        info.cancel = payUtils.check.isFixed(select);
+                        break;
+                    case 'tp':
+                    case 'is_gather':
+                        info.cancel = select.children && select.children.length > 0;
+                        break;
+                    case 'start_tp':
+                    case 'range_tp':
+                        info.cancel = (select.children && select.children.length > 0) || payUtils.check.isYf(select);
+                        break;
+                    case 'is_gather':
+                        info.cancel = true;
+                        break;
+                }
+
+                if (col.field === 'tp') {
+                    if (select.expr && select.expr !== '') {
+                        info.sheet.getCell(info.row, info.col).text(payCalc.trans2OrderExpr(select.expr, payTree));
+                    }
+                } else if (col.field === 'start_tp') {
+                    if (select.start_expr && select.start_expr !== '') {
+                        info.sheet.getCell(info.row, info.col).text(select.start_expr);
+                    }
+                } else if (col.field === 'range_tp') {
+                    if (select.range_expr && select.range_expr !== '') {
+                        info.sheet.getCell(info.row, info.col).text(select.range_expr);
+                    }
+                }
+            },
+            editEnded: function(e, info) {
+                if (!info.sheet.zh_setting) return;
+
+                const select = SpreadJsObj.getSelectObject(info.sheet);
+                const col = info.sheet.zh_setting.cols[info.col];
+                if (col.field === 'is_gather') return;
+                // 未改变值则不提交
+                const validText = info.editingText ? info.editingText.replace('\n', '') : '';
+                let orgValue;
+                if (col.field === 'tp') {
+                    orgValue = select.expr ? payCalc.trans2OrderExpr(select.expr, payTree) : select.tp;
+                } else if (col.field === 'start_tp') {
+                    orgValue = select.start_expr ? select.start_expr : select.start_tp;
+                } else if (col.field === 'range_tp') {
+                    orgValue = select.range_expr ? select.range_expr : select.range_tp;
+                } else {
+                    orgValue = select[col.field];
+                }
+                orgValue = orgValue || '';
+                if (orgValue == validText) {
+                    SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                    return;
+                }
+
+                const data = { postType: 'update', postData: { id: select.id } };
+                switch(col.field) {
+                    case 'tp':
+                        const [tpValid, tpMsg] = payUtils.check.isSf(select)
+                            ? payCalc.checkSfExpr(validText, data.postData, select, payTree)
+                            : payCalc.checkExpr(validText, data.postData, select, payTree);
+                        if (!tpValid) {
+                            toastr.warning(tpMsg);
+                            SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                            return;
+                        }
+                        break;
+                    case 'start_tp':
+                        const [sValid, sMsg] = payCalc.checkStartExpr(select, validText, data.postData);
+                        if (!sValid) {
+                            toastr.warning(sMsg);
+                            SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                            return;
+                        }
+                        break;
+                    case 'range_tp':
+                        const [rValid, rMsg] = payCalc.checkRangeExpr(select, validText, data.postData);
+                        if (!rValid) {
+                            toastr.warning(rMsg);
+                            SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                            return;
+                        }
+                        break;
+                    default:
+                        if (col.type === 'Number') {
+                            data.postData[col.field] = _.toNumber(validText) || 0;
+                        } else {
+                            data.postData[col.field] = validText || '';
+                        }
+                        break;
+                }
+                postData('update', data, function (result) {
+                    if (result.reload) {
+                        payEvent.reloadPays(result.reload);
+                    } else {
+                        const refreshData = payTree.loadPostData(result);
+                        payEvent.refreshTree(refreshData);
+                    }
+                }, function () {
+                    SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                });
+            },
+            selectionChanged: function(e, info) {
+                if (info.newSelections) {
+                    if (!info.oldSelections || info.newSelections[0].row !== info.oldSelections[0].row) {
+                        payEvent.refreshActn();
+                    }
+                }
+                payEvent.loadExprToInput();
+            },
+            buttonClicked: function (e, info) {
+                if (!info.sheet.zh_setting) return;
+
+                const select = SpreadJsObj.getSelectObject(info.sheet);
+                const col = info.sheet.zh_setting.cols[info.col];
+                if (col.field !== 'is_gather') return;
+                if (!payUtils.check.isGatherValid(select)) return;
+                if (info.sheet.isEditing()) info.sheet.endEdit(true);
+
+                const data = {
+                    postType: 'update',
+                    postData: { id: select.id },
+                };
+                data.postData[col.field] = info.sheet.getValue(info.row, info.col) || 0;
+                // 更新至服务器
+                postData('update', data, function (result) {
+                    if (result.reload) payEvent.reloadPays(result.reload);
+                });
+            },
+            deletePress: function (sheet) {
+                if (!sheet.zh_setting) return;
+
+                const sel = sheet.getSelections()[0];
+                if (!sel) return;
+                const col = sheet.zh_setting.cols[sel.col];
+                if (col.readOnly === true) return;
+                if (sel.colCount > 1) toastr.warning('请勿同时删除多列数据');
+
+                const data = { postType: 'update', postData: [] };
+                for (let iRow = sel.row; iRow < sel.row + sel.rowCount; iRow ++) {
+                    const node = sheet.zh_tree.nodes[iRow];
+                    if (node && (!node.pay_type || col.field === 'postil')) {
+                        const updateData = { id: node.id };
+                        switch(col.field) {
+                            case 'tp':
+                                updateData.tp = 0;
+                                updateData.expr = '';
+                                break;
+                            case 'start_tp':
+                                updateData.start_tp = 0;
+                                updateData.start_expr = '';
+                                break;
+                            case 'range_tp':
+                                updateData.range_tp = 0;
+                                updateData.range_expr = '';
+                                break;
+                            case 'is_gather':
+                                updateData.is_gather = 0;
+                                break;
+                            default:
+                                updateData[col.field] = col.type === 'Number' ? 0 : '';
+                        }
+                        data.postData.push(updateData);
+                    }
+                }
+                postData('update', data, function (result) {
+                    if (result.reload) {
+                        payEvent.reloadPays(result.reload);
+                    } else {
+                        const refreshData = payTree.loadPostData(result);
+                        payEvent.refreshTree(refreshData);
+                    }
+                }, function () {
+                    SpreadJsObj.reLoadRowData(sheet, sel.row, sel.rowCount);
+                });
+            },
+            baseOpr: function(type) {
+                const self = this;
+                const node = SpreadJsObj.getSelectObject(sheet);
+
+                if (type === 'delete') {
+                    postData('update', { postType: 'delete', postData: { id: node.tree_id }}, function(result) {
+                        payEvent.reloadPays(result.reload);
+                    });
+                } else {
+                    postData('update', { postType: type, postData: { id: node.tree_id }}, function (result) {
+                        const refreshData = payTree.loadPostData(result);
+                        payEvent.refreshTree(refreshData);
+                        const sel = sheet.getSelections()[0];
+                        if (sel) {
+                            if (['up-move', 'down-move'].indexOf(type) > -1) {
+                                sheet.setSelection(payTree.getNodeIndex(node), sel.col, sel.rowCount, sel.colCount);
+                                SpreadJsObj.reloadRowsBackColor(sheet, [sel.row, payTree.getNodeIndex(node)]);
+                            } else if (type === 'add') {
+                                sheet.setSelection(payTree.getNodeIndex(refreshData.create[0]), sel.col, sel.rowCount, sel.colCount);
+                                SpreadJsObj.reloadRowsBackColor(sheet, [sel.row, payTree.getNodeIndex(refreshData.create[0])]);
+                            }
+                        }
+                        self.refreshActn(sheet);
+                    });
+                }
+            },
+            calculateAll: function() {
+                postData('update', { postType: 'calc', postData: {}}, function (result) {
+                    payEvent.reloadPays(result.reload);
+                });
+            },
+            reloadCalcBase: function() {
+                postData('update', { postType: 'refreshBase', postData: {}}, function (result) {
+                    payEvent.reloadPays(result.reload);
+                    payCalc.reloadBase(result.calcBase, result.addBase);
+                });
+            },
+            reloadPays: function(datas){
+                payTree.loadDatas(datas);
+                SpreadJsObj.loadSheetData(sheet, SpreadJsObj.DataType.Tree, payTree);
+                payEvent.refreshActn();
+            }
+        };
+        spread.bind(spreadNS.Events.SelectionChanged, payEvent.selectionChanged);
+        if (!readOnly) {
+            spread.bind(spreadNS.Events.EditStarting, payEvent.editStarting);
+            spread.bind(spreadNS.Events.EditEnded, payEvent.editEnded);
+            spread.bind(spreadNS.Events.ButtonClicked, payEvent.buttonClicked);
+            SpreadJsObj.addDeleteBind(spread, payEvent.deletePress);
+            $('a[name="base-opr"]').click(function () {
+                payEvent.baseOpr(this.getAttribute('type'));
+            });
+            $('#calc-all').click(function() {
+                payEvent.calculateAll();
+            });
+            $('#reload-calc-base').click(function() {
+                payEvent.reloadCalcBase();
+            });
+
+            const deadlineObj = {
+                payNode: null,
+                refreshHint: function() {
+                    const dlType = $('[name=dl-type]:checked').val();
+                    const dt = deadlineType[dlType];
+                    if (dlType && dt) {
+                        const dlValue = $('#dl-value').val();
+                        $('#range-hint').text(`当 ${dt.name} >= ${dlValue} 时 `);
+                        $('#dl-hint').show();
+                    } else {
+                        $('#dl-hint').hide();
+                    }
+                },
+                initView: function(data) {
+                    this.payNode = data;
+                    $('#dl-pay-name').html(data.name);
+                    // 模式
+                    if (data.dl_type) {
+                        $('[name=dl-type][value=' + data.dl_type +']')[0].checked = true;
+                    } else {
+                        $('#dl-type-none')[0].checked = true;
+                    }
+                    $('#dl-value').val(data.dl_value);
+                    this.refreshHint();
+                },
+                getDlCount: function() {
+                    try {
+                        const result = parseInt($('#dl-value').val());
+                        if (result <= 0) throw '限制值请输入正整数';
+                        return result;
+                    } catch (err) {
+                        toastr.warning('限制值请输入正整数');
+                        return 0;
+                    }
+                },
+                getDlTp: function() {
+                    try {
+                        const result = parseFloat($('#dl-value').val());
+                        return result;
+                    } catch (err) {
+                        toastr.warning('限制值请输入数值');
+                        return 0;
+                    }
+                },
+                getUpdateData: function() {
+                    const result = { postType: 'update', postData: { id: this.payNode.id } };
+                    result.postData.dl_type = $('[name=dl-type]:checked').val();
+                    if (result.postData.dl_type) {
+                        if (result.postData.dl_type === deadlineType.phaseCount.key) {
+                            result.postData.dl_value = this.getDlCount();
+                            if (result.postData.dl_value < phasePay.phase_order) {
+                                toastr.warning(`已计量至第${phasePay.phase_order}期,计提期限不可小于该期`);
+                                return null;
+                            }
+                        } else if (result.postData.dl_type === deadlineType.stageCount.key) {
+                            result.postData.dl_value = this.getDlCount();
+                            if (result.postData.dl_value < maxStageOrder) {
+                                toastr.warning(`已计量至第${maxStageOrder}期,计提期限不可小于该期`);
+                                return null;
+                            }
+                        } else {
+                            result.postData.dl_value = this.getDlTp();
+                            const dt = deadlineType[result.postData.dl_type];
+                            if (!dt) {
+                                toastr.warning('限制模式错误,请刷新页面重试');
+                                return null;
+                            }
+                            const compareValue = payCalc.addBase[`pre_${dt.key}_tp`];
+                            if (result.postData.dl_type < compareValue) {
+                                toastr.warning(`截止上期,${dt.name}已计量${compareValue},计提期限不可小于该值`);
+                                return null;
+                            }
+                        }
+                    } else {
+                        result.postData.dl_value = 0;
+                    }
+                    return result;
+                },
+            };
+            $('[name=dl-type]').change(deadlineObj.refreshHint);
+            $('#dl-value').change(deadlineObj.refreshHint);
+            $('#deadline-ok').click(function() {
+                const updateData = deadlineObj.getUpdateData();
+                if (!updateData) return;
+                postData('update', updateData, function(result) {
+                    payEvent.reloadPays(result.reload);
+                    $('#deadline').modal('hide');
+                });
+            });
+
+            // 右键菜单
+            $.contextMenu({
+                selector: '#pay-spread',
+                build: function ($trigger, e) {
+                    const target = SpreadJsObj.safeRightClickSelection($trigger, e, spread);
+                    return target.hitTestType === GC.Spread.Sheets.SheetArea.viewport || target.hitTestType === GC.Spread.Sheets.SheetArea.rowHeader;
+                },
+                items: {
+                    'start': {
+                        name: '启用',
+                        icon: 'fa-play',
+                        callback: function (key, opt) {
+                            const select = SpreadJsObj.getSelectObject(sheet);
+                            const data = {
+                                postType: 'update',
+                                postData: { id: select.id, is_pause: 0 }
+                            };
+                            // 更新至服务器
+                            postData('update', data, function (result) {
+                                payEvent.reloadPays(result.reload);
+                            });
+                        },
+                        visible: function (key, opt) {
+                            const select = SpreadJsObj.getSelectObject(sheet);
+                            return (!select.children || select.children.length ===0) && !readOnly  && select.is_pause && payUtils.menuVisible.pause(select);
+                        }
+                    },
+                    'stop': {
+                        name: '停用',
+                        icon: 'fa-pause',
+                        callback: function (key, opt) {
+                            const select = SpreadJsObj.getSelectObject(sheet);
+                            const data = {
+                                postType: 'update',
+                                postData: { id: select.id, is_pause: 1 }
+                            };
+                            // 更新至服务器
+                            postData('update', data, function (result) {
+                                payEvent.reloadPays(result.reload);
+                            });
+                        },
+                        visible: function (key, opt) {
+                            const select = SpreadJsObj.getSelectObject(sheet);
+                            return (!select.children || select.children.length === 0) && !readOnly && !select.is_pause && payUtils.menuVisible.pause(select);
+                        },
+                    },
+                    'setDeadline': {
+                        name: '设置计提期限',
+                        icon: 'fa-clipboard',
+                        callback: function (key, opt) {
+                            const select = SpreadJsObj.getSelectObject(sheet);
+                            if (select.range_tp) {
+                                deadlineObj.initView(select);
+                                $('#deadline').modal('show');
+                            } else {
+                                toastr.warning('计提期限用于达到条件时,即刻计量至付(扣)款限额,应先设置付(扣)款限额');
+                            }
+                        },
+                        visible: function (key, opt) {
+                            const select = SpreadJsObj.getSelectObject(sheet);
+                            return (!select.children || select.children.length === 0) && !readOnly && payUtils.menuVisible.deadline(select);
+                        }
+                    },
+                }
+            });
+        }
+
+        return { spread, sheet, payTree, loadDatas: payEvent.reloadPays }
+    })();
+    payObj.loadDatas(details);
+
+    // todo 加载审批列表
+    $.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();
+            payObj.spread.refresh();
+        }
+    });
+});

+ 21 - 0
app/public/js/phase_pay_list.js

@@ -0,0 +1,21 @@
+'use strict';
+
+$(document).ready(() => {
+    // todo 加载审批列表
+    $.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();
+        }
+    });
+});

+ 18 - 9
app/public/js/revise.js

@@ -147,6 +147,11 @@ $(document).ready(() => {
             if (posSpread) posSpread.refresh();
         },
     });
+    const gclGather = $.cs_gclGather({
+        selector: '#gcl-gather',
+        id: 'gcl-gather',
+        relaSheet: billsSpread.getActiveSheet(),
+    });
 
     // 初始化 节点树结构
     const treeSetting = {
@@ -580,7 +585,7 @@ $(document).ready(() => {
                             }
                         } else {
                             try {
-                                data[col.field] = math.evaluate(transExpr(text));
+                                data[col.field] = ZhCalc.mathCalcExpr(transExpr(text));
                                 if (exprInfo) {
                                     data[exprInfo.expr] = newValue;
                                 }
@@ -706,7 +711,7 @@ $(document).ready(() => {
                             data[colSetting.field] = num;
                         } else {
                             try {
-                                data[colSetting.field] = math.evaluate(transExpr(value));
+                                data[colSetting.field] = ZhCalc.mathCalcExpr(transExpr(value));
                                 const exprInfo = getExprInfo(colSetting.field);
                                 if (exprInfo) {
                                     data[exprInfo.expr] = value;
@@ -795,7 +800,7 @@ $(document).ready(() => {
                                 data[colSetting.field] = num;
                             } else {
                                 try {
-                                    data[colSetting.field] = math.evaluate(transExpr(value));
+                                    data[colSetting.field] = ZhCalc.mathCalcExpr(transExpr(value));
                                 } catch (err) {
                                     toastMessageUniq(hint.invalidExpr);
                                     continue;
@@ -1093,7 +1098,7 @@ $(document).ready(() => {
                     }
                 } else {
                     try {
-                        data[field] = math.evaluate(transExpr(newValue));
+                        data[field] = ZhCalc.mathCalcExpr(transExpr(newValue));
                         if (exprInfo) {
                             data[exprInfo.expr] = newValue;
                         }
@@ -1743,7 +1748,7 @@ $(document).ready(() => {
                     }
                 } else {
                     try {
-                        data.postData[col.field] = math.evaluate(transExpr(newText));
+                        data.postData[col.field] = ZhCalc.mathCalcExpr(transExpr(newText));
                         if (exprInfo) {
                             data.postData[exprInfo.expr] = newText;
                         }
@@ -1759,7 +1764,7 @@ $(document).ready(() => {
 
                 if (newText) {
                     try {
-                        data.postData[exprInfo.qty] = math.evaluate(transExpr(newText));
+                        data.postData[exprInfo.qty] = ZhCalc.mathCalcExpr(transExpr(newText));
                         data.postData[exprInfo.expr] = newText;
                     } catch(err) {
                         toastr.error('输入的表达式非法');
@@ -1958,7 +1963,7 @@ $(document).ready(() => {
                             posData[colSetting.field] = num;
                         } else {
                             try {
-                                posData[colSetting.field] = math.evaluate(transExpr(posData[colSetting.field]));
+                                posData[colSetting.field] = ZhCalc.mathCalcExpr(transExpr(posData[colSetting.field]));
                                 const exprInfo = getExprInfo(colSetting.field);
                                 if (exprInfo) {
                                     posData[exprInfo.expr] = trimInvalidChar(info.sheet.getText(curRow, curCol));
@@ -1973,7 +1978,7 @@ $(document).ready(() => {
                             const exprInfo = getExprInfo(colSetting.field, true);
                             posData[exprInfo.expr] = trimInvalidChar(info.sheet.getText(curRow, curCol));
                             if (posData[exprInfo.expr] || !posData[exprInfo.qty]) {
-                                posData[exprInfo.qty] = math.evaluate(transExpr(posData[exprInfo.expr]));
+                                posData[exprInfo.qty] = ZhCalc.mathCalcExpr(transExpr(posData[exprInfo.expr]));
                             }
                             bPaste = true;
                         } catch (err) {
@@ -2048,7 +2053,7 @@ $(document).ready(() => {
                     if (exprInfo) data[exprInfo.expr] = '';
                 } else {
                     try {
-                        data[field] = math.evaluate(transExpr(newValue));
+                        data[field] = ZhCalc.mathCalcExpr(transExpr(newValue));
                         if (exprInfo) data[exprInfo.expr] = newValue;
                     } catch (err) {
                         toastr.error('输入的表达式非法');
@@ -2738,6 +2743,7 @@ $(document).ready(() => {
             if (errorList) errorList.spread.refresh();
             if (checkList) checkList.spread.refresh();
             if (sumLoadMiss) sumLoadMiss.spread.refresh();
+            if (gclGather) gclGather.spread.refresh();
         }
     });
     $.subMenu({
@@ -2764,6 +2770,7 @@ $(document).ready(() => {
             if (errorList) errorList.spread.refresh();
             if (checkList) checkList.spread.refresh();
             if (sumLoadMiss) sumLoadMiss.spread.refresh();
+            if (gclGather) gclGather.spread.refresh();
         }
     });
 
@@ -2959,6 +2966,8 @@ $(document).ready(() => {
                 checkList.spread.refresh();
             } else if (tab.attr('content') === '#sum-load-miss') {
                 sumLoadMiss.spread.refresh();
+            } else if (tab.attr('content') === '#gcl-gather') {
+                gclGather.spread.refresh();
             }
         }
         else {// 收起工具栏

+ 6 - 7
app/public/js/shares/dsk.js

@@ -89,7 +89,6 @@ const dsk = (function () {
                     if (!parent) {
                         parent = datas.find(x => { return x[setting.id] === d[setting.pid]; });
                         if (!parent) {
-                            // console.log(d[setting.pid]);
                             return null;
                         }
                         parent = _loadData(parent);
@@ -117,12 +116,13 @@ const dsk = (function () {
         subjectSheet: null,
         compliation: null,
         loadCompilation: async function() {
-            if (this.compliation) return;
-
-            this.compliation = await loadCompilation();
-            for (const c of this.compliation) {
-                c.project = await loadProject(c.ID);
+            if (!this.compliation) {
+                this.compliation = await loadCompilation();
+                for (const c of this.compliation) {
+                    c.project = await loadProject(c.ID);
+                }
             }
+
             const html = [];
             for (const c of this.compliation) {
                 html.push(`<option value="${c.ID}">${c.name}</option>`);
@@ -189,7 +189,6 @@ const dsk = (function () {
             if (!compilation.subjectTree) {
                 for (const p of compilation.project) {
                     if (p.type === projectTypeKey.project) p.subjects = await loadProjectTree(compilation.ID, p.ID, false);
-                    console.log(p.subjects);
                 }
                 this.analysisSubjectTree(compilation);
             }

+ 18 - 0
app/public/js/shares/sjs_setting.js

@@ -1,6 +1,8 @@
 const sjsSettingObj = (function () {
     const FxTreeStyle = {
         jz: 'jianzhu',
+        contract: 'contract',
+        phasePay: 'phasePay',
     };
     const setJzFxTreeStyle = function (setting) {
         setting.selectedBackColor = '#fffacd';
@@ -56,6 +58,19 @@ const sjsSettingObj = (function () {
             }
         }
     };
+    const setPhasePayTreeStyle = function (setting) {
+        setting.selectedBackColor = '#fffacd';
+        setting.tree = {
+            getFont: function (sheet, data, row, col, defaultFont) {
+                if (sheet.zh_tree) {
+                    const levelField = sheet.zh_tree.setting.level;
+                    return data[levelField] === 1 ? 'bold ' + defaultFont : defaultFont;
+                } else {
+                    return defaultFont;
+                }
+            }
+        }
+    };
     const setFxTreeStyle = function (setting, tag) {
         switch (tag) {
             case FxTreeStyle.jz:
@@ -64,6 +79,9 @@ const sjsSettingObj = (function () {
             case FxTreeStyle.contract:
                 setContractFxTreeStyle(setting);
                 break;
+            case FxTreeStyle.phasePay:
+                setPhasePayTreeStyle(setting);
+                break;
         }
     };
 

+ 1 - 1
app/public/js/shares/tree_expr_calc.js

@@ -195,7 +195,7 @@ const TreeExprCalc = (function(){
             }
         }
         if (formula.indexOf('%') >= 0) formula = formula.replace(new RegExp('%', 'gm'), '/100');
-        return [formula, math.evaluate(formula)];
+        return [formula, ZhCalc.mathCalcExpr(formula)];
     };
     const addCache = function(expr) {
         const cache = { id: expr.id };

+ 6 - 6
app/public/js/sp_progress.js

@@ -49,7 +49,7 @@ $(document).ready(() => {
                     {title: '阶段/项目名称', colSpan: '1', rowSpan: '2', field: 'name', hAlign: 0, width: 210, formatter: '@', cellType: 'autoTip'},
                     {title: '成果编制|进度', colSpan: '3|1', rowSpan: '1|1', field: 'edit_progress', hAlign: 1, width: 80, formatter: '@', cellType: 'customizeCombo', comboItems: progressCombo },
                     {
-                        title: '|日期', colSpan: '|1', rowSpan: '|1', field: 'edit_date', hAlign: 2, width: 80, formatter: '@', readOnly: true,
+                        title: '|日期', colSpan: '|1', rowSpan: '|1', field: 'edit_date', hAlign: 2, width: 80, readOnly: true,
                         formatter: 'yyyy-MM-dd', cellType: 'activeImageBtn', normalImg: '#ellipsis-icon', indent: 5,
                         showImage: function (data) {
                             return data !== undefined && data !== null;
@@ -58,7 +58,7 @@ $(document).ready(() => {
                     {title: '|部门', colSpan: '|1', rowSpan: '|1', field: 'edit_department', hAlign: 2, width: 120, formatter: '@', },
                     {title: '报审情况|进度', colSpan: '3|1', rowSpan: '1|1', field: 'submit_progress', hAlign: 1, width: 80, formatter: '@', cellType: 'customizeCombo', comboItems: progressCombo },
                     {
-                        title: '|日期', colSpan: '|1', rowSpan: '|1', field: 'submit_date', hAlign: 2, width: 80, formatter: '@', readOnly: true,
+                        title: '|日期', colSpan: '|1', rowSpan: '|1', field: 'submit_date', hAlign: 2, width: 80, readOnly: true,
                         formatter: 'yyyy-MM-dd', cellType: 'activeImageBtn', normalImg: '#ellipsis-icon', indent: 5,
                         showImage: function (data) {
                             return data !== undefined && data !== null;
@@ -67,7 +67,7 @@ $(document).ready(() => {
                     {title: '|部门', colSpan: '|1', rowSpan: '|1', field: 'submit_department', hAlign: 2, width: 120, formatter: '@', },
                     {title: '批复情况|进度', colSpan: '4|1', rowSpan: '1|1', field: 'reply_progress', hAlign: 1, width: 80, formatter: '@', cellType: 'customizeCombo', comboItems: progressCombo },
                     {
-                        title: '|日期', colSpan: '|1', rowSpan: '|1', field: 'reply_date', hAlign: 2, width: 80, formatter: '@', readOnly: true,
+                        title: '|日期', colSpan: '|1', rowSpan: '|1', field: 'reply_date', hAlign: 2, width: 80, readOnly: true,
                         formatter: 'yyyy-MM-dd', cellType: 'activeImageBtn', normalImg: '#ellipsis-icon', indent: 5,
                         showImage: function (data) {
                             return data !== undefined && data !== null;
@@ -366,7 +366,7 @@ $(document).ready(() => {
                         data[col.field] = num;
                     } else {
                         try {
-                            data[col.field] = math.evaluate(transExpr(text));
+                            data[col.field] = ZhCalc.mathCalcExpr(transExpr(text));
                         } catch(err) {
                             toastr.error('输入的表达式非法');
                             SpreadJsObj.reLoadRowData(info.sheet, info.row);
@@ -480,7 +480,7 @@ $(document).ready(() => {
                             data[colSetting.field] = num;
                         } else {
                             try {
-                                data[colSetting.field] = math.evaluate(transExpr(value));
+                                data[colSetting.field] = ZhCalc.mathCalcExpr(transExpr(value));
                                 bPaste = true;
                             } catch (err) {
                                 toastMessageUniq(hint.invalidExpr);
@@ -614,4 +614,4 @@ $(document).ready(() => {
             }, 100);
         });
     })('a[name=showLevel]', progressObj.sheet);
-});
+});

+ 37 - 0
app/public/js/spreadjs_rela/spreadjs_zh.js

@@ -740,6 +740,13 @@ const SpreadJsObj = {
             sheet.getRange(-1, col, -1, 1).cellType(sheet.extendCellType.checkbox);
             sheet.getRange(-1, col, -1, 1).hAlign(col ? col.hAlign : spreadNS.HorizontalAlign.center);// 空白行不居中问题,checkbox要默认居中
         }
+        if (colSetting.cellType === 'signalCheckbox') {
+            if (!sheet.extendCellType.signalCheckbox) {
+                sheet.extendCellType.signalCheckbox = this.CellType.getSignalCheckboxCellType();
+            }
+            sheet.getRange(-1, col, -1, 1).cellType(sheet.extendCellType.signalCheckbox);
+            sheet.getRange(-1, col, -1, 1).hAlign(col ? col.hAlign : spreadNS.HorizontalAlign.center);// 空白行不居中问题,checkbox要默认居中
+        }
         if (colSetting.cellType === 'unit') {
             if (!sheet.extendCellType.unit) {
                 const comboEdit = colSetting.comboEdit ? colSetting.comboEdit : false;
@@ -2825,6 +2832,36 @@ const SpreadJsObj = {
             };
             return new stackedBarCellType();
         },
+        getSignalCheckboxCellType: function () {
+            const SignalCheckboxCellType = function () {};
+            SignalCheckboxCellType.prototype = new spreadNS.CellTypes.CheckBox();
+            const proto = SignalCheckboxCellType.prototype;
+            proto.showCheckBox = function (sheet, iRow, iCol) {
+                const col = sheet.zh_setting.cols[iCol];
+                if (col.show && Object.prototype.toString.apply(col.show) === "[object Function]") {
+                    const sortData = SpreadJsObj.getSortData(sheet);
+                    const data = sortData ? sortData[iRow] : null;
+                    return data ? col.show(data) : false;
+                } else {
+                    return col.show !== undefined && col.show !== null ? col.show : true ;
+                }
+            };
+            proto.paintValue = function (ctx, value, x, y, w, h, style, options) {
+                if (this.showCheckBox(options.sheet, options.row, options.col)) {
+                    spreadNS.CellTypes.CheckBox.prototype.paintValue.apply(this, arguments);
+                } else {
+                    spreadNS.CellTypes.Base.prototype.paintValue.apply(this, [ctx, '', x, y, w, h, style, options]);
+                }
+            };
+            // proto.paint = function (canvas, value, x, y, w, h, style, options) {
+            //     if (this.showCheckBox(options.sheet, options.row, options.col)) {
+            //         spreadNS.CellTypes.CheckBox.prototype.paintContent.apply(this, [canvas, value, x, y, w, h, style, options]);
+            //     } else {
+            //         spreadNS.CellTypes.Base.prototype.paintContent.apply(this, [canvas, value, x, y, w, h, style, options]);
+            //     }
+            // };
+            return new SignalCheckboxCellType();
+        },
     },
 
     RowHeader: {

+ 11 - 11
app/public/js/stage.js

@@ -431,7 +431,7 @@ $(document).ready(() => {
                             node[col.field] = num;
                         } else {
                             try {
-                                node[col.field] = math.evaluate(transExpr(info.editingText));
+                                node[col.field] = ZhCalc.mathCalcExpr(transExpr(info.editingText));
                             } catch(err) {
                                 toastr.error('输入的表达式非法');
                                 SpreadJsObj.reLoadRowData(info.sheet, info.row);
@@ -458,7 +458,7 @@ $(document).ready(() => {
                                 sortData[curRow][col.field] = num;
                             } else {
                                 try {
-                                    sortData[curRow][col.field] = math.evaluate(transExpr(info.sheet.getText(curRow, curCol)));
+                                    sortData[curRow][col.field] = ZhCalc.mathCalcExpr(transExpr(info.sheet.getText(curRow, curCol)));
                                 } catch(err) {
                                     // toastr.error('输入的表达式非法');
                                 }
@@ -583,7 +583,7 @@ $(document).ready(() => {
         _calculateAmount() {
             for (const c of this.changes) {
                 c.bamount = _.toNumber(c.b_amount);
-                const qtyDecimal = this.findDecimal(c.unit);
+                const qtyDecimal = this.findDecimal(c.b_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});
@@ -975,7 +975,7 @@ $(document).ready(() => {
                         newValue = num;
                     } else {
                         try {
-                            newValue = math.evaluate(transExpr(newValue));
+                            newValue = ZhCalc.mathCalcExpr(transExpr(newValue));
                         } catch(err) {
                             toastr.error('输入的表达式非法');
                             SpreadJsObj.reLoadRowData(info.sheet, info.row);
@@ -1189,7 +1189,7 @@ $(document).ready(() => {
                                 filterDgn = false;
                             } else {
                                 try {
-                                    dgnData[col.field] = math.evaluate(transExpr(text));
+                                    dgnData[col.field] = ZhCalc.mathCalcExpr(transExpr(text));
                                     filterDgn = false;
                                 } catch(err) {
                                     toastMessageUniq(pasteHint.invalidExpr);
@@ -1209,7 +1209,7 @@ $(document).ready(() => {
                                         filter = false;
                                     } else {
                                         try {
-                                            data[col.field] = math.evaluate(transExpr(text));
+                                            data[col.field] = ZhCalc.mathCalcExpr(transExpr(text));
                                             if (exprInfo) data[exprInfo.expr] = text;
                                             filter = false;
                                         } catch(err) {
@@ -1500,7 +1500,7 @@ $(document).ready(() => {
                 newValue = num;
             } else {
                 try {
-                    newValue = math.evaluate(transExpr(newValue));
+                    newValue = ZhCalc.mathCalcExpr(transExpr(newValue));
                 } catch(err) {
                     toastr.error('输入的表达式非法');
                     return;
@@ -2080,7 +2080,7 @@ $(document).ready(() => {
                             }
                         } else {
                             try {
-                                data.updateData[col.field] = math.evaluate(transExpr(newText));
+                                data.updateData[col.field] = ZhCalc.mathCalcExpr(transExpr(newText));
                                 if (exprInfo) {
                                     data.updateData[exprInfo.expr] = newText;
                                 }
@@ -2205,7 +2205,7 @@ $(document).ready(() => {
                                         }
                                     } else {
                                         try {
-                                            newData[colSetting.field] = math.evaluate(transExpr(newValue));
+                                            newData[colSetting.field] = ZhCalc.mathCalcExpr(transExpr(newValue));
                                             if (exprInfo) {
                                                 newData[exprInfo.expr] = newValue;
                                             }
@@ -2254,7 +2254,7 @@ $(document).ready(() => {
                                         if (exprInfo) newData[exprInfo.expr] = '';
                                     } else {
                                         try {
-                                            newData[colSetting.field] = math.evaluate(transExpr(newValue));
+                                            newData[colSetting.field] = ZhCalc.mathCalcExpr(transExpr(newValue));
                                             const exprInfo = getExprInfo(colSetting.field);
                                             if (exprInfo) newData[exprInfo.expr] = newValue;
                                         } catch(err) {
@@ -2488,7 +2488,7 @@ $(document).ready(() => {
                     if (exprInfo) data[exprInfo.expr] = '';
                 } else {
                     try {
-                        data[field] = math.evaluate(transExpr(newValue));
+                        data[field] = ZhCalc.mathCalcExpr(transExpr(newValue));
                         if (exprInfo) data[exprInfo.expr] = newValue;
                     } catch (err) {
                         toastr.error('输入的表达式非法');

+ 26 - 43
app/public/js/stage_gather.js

@@ -45,13 +45,13 @@ function generateChapterHtml(data) {
 
 $(document).ready(function () {
     const preUrl = window.location.pathname.split('/').slice(0, 6).join('/');
-    // let per = _.toNumber(getLocalCache('StageGatherOverPercent'));
-    // if (per && !_.isNaN(per)) {
-    //     $('#over-percent').val(per >= 50 ? (per <= 100 ? per : 100) : 50);
-    // }
-    //
-    // let overType = $('#' + getLocalCache('StageGatherOverType'))[0];
-    // if (overType) overType.checked = true;
+    let per = _.toNumber(getLocalCache('StageGatherOverPercent'));
+    if (per && !_.isNaN(per)) {
+        $('#over-percent').val(per >= 50 ? (per <= 100 ? per : 100) : 50);
+    }
+
+    let overType = $('#' + getLocalCache('StageGatherOverType'))[0];
+    if (overType) overType.checked = true;
     autoFlashHeight();
     // 初始化工程量清单
     const gclSpread = SpreadJsObj.createNewSpread($('#gcl-spread')[0]);
@@ -68,18 +68,10 @@ $(document).ready(function () {
     // 初始化所属项目节
     const leafXmjSpread = SpreadJsObj.createNewSpread($('#leaf-xmj-spread')[0]);
     if (thousandth) sjsSettingObj.setTpThousandthFormat(leafXmjSpreadSetting);
-    leafXmjSpreadSetting.getColor = function (sheet, data, row, col, defaultColor) {
-        if (!data) return defaultColor;
-        return data.overRange && hintOver ? spreadColor.stage.over : defaultColor;
-    };
     SpreadJsObj.initSheet(leafXmjSpread.getActiveSheet(), leafXmjSpreadSetting);
 
     const gatherLeafXmjSpread = SpreadJsObj.createNewSpread($('#leaf-xmj-gather-spread')[0]);
     if (thousandth) sjsSettingObj.setTpThousandthFormat(gatherLeafXmjSpreadSetting);
-    gatherLeafXmjSpreadSetting.getColor = function (sheet, data, row, col, defaultColor) {
-        if (!data) return defaultColor;
-        return data.overRange && hintOver ? spreadColor.stage.over : defaultColor;
-    };
     const gatherLeafXmjSheet = gatherLeafXmjSpread.getActiveSheet();
     SpreadJsObj.initSheet(gatherLeafXmjSheet, gatherLeafXmjSpreadSetting);
 
@@ -173,33 +165,14 @@ $(document).ready(function () {
                     : data.end_contract_tp < ZhCalc.mul(data[tpField], per) || data.end_contract_tp > 0;
             }
         };
-        const nPercent = checkOverInfo.coe;
+        const bQty = $('#customRadio1')[0].checked, bDealQty = $('#customRadio2')[0].checked;
+        const nPercent = Math.min(Math.max(ZhCalc.div(parseFloat($('#over-percent').val()), 100), 0.5), 1);
         for (const node of data) {
             if (node) {
-                const bOverRangeTz = checkOverInfo.checkTz ? billsGatherOver(node, 'final_1_qty', 'final_1_tp', nPercent) : false;
-                const bOverRangeDeal = checkOverInfo.checkDeal ? billsGatherOver(node, 'deal_final_1_qty', 'deal_final_1_tp', nPercent) : false;
-                node.overRange = bOverRangeTz || bOverRangeDeal;
-            }
-        }
-    }
-    function checkXmjOverRange(data, checkXmjList) {
-        const xmjGatherOver = function (data, qtyField, per) {
-            if (data.end_contract_qty) {
-                if (!data[qtyField]) return true;
-                return data[qtyField] > 0
-                    ? data.end_contract_qty > ZhCalc.mul(data[qtyField], per)
-                    : data.end_contract_qty < ZhCalc.mul(data[qtyField], per) || data.end_contract_qty > 0;
-            } else {
-                return false;
-            }
-        };
-        const nPercent = checkOverInfo.coe;
-        for (const node of data) {
-            if (!node || !node[checkXmjList] || node[checkXmjList].length === 0) continue;
-            for (const xmj of node[checkXmjList]) {
-                const bOverRangeTz = checkOverInfo.checkTz ? xmjGatherOver(xmj, 'final_1_qty', nPercent) : false;
-                const bOverRangeDeal = false; // checkOverInfo.checkDeal ? xmjGatherOver(xmj, 'deal_final_1_qty', nPercent) : false;
-                xmj.overRange = bOverRangeDeal || bOverRangeTz;
+                if (node.b_code === '103-4')console.log(node);
+                const bOverRangeTz = billsGatherOver(node, 'final_1_qty', 'final_1_tp', nPercent);
+                const bOverRangeDeal = billsGatherOver(node, 'deal_final_1_qty', 'deal_final_1_tp', nPercent);
+                node.overRange = bQty ? bOverRangeTz : (bDealQty ? bOverRangeDeal : bOverRangeTz || bOverRangeDeal);
             }
         }
     }
@@ -208,6 +181,19 @@ $(document).ready(function () {
         const iOldRow = info.oldSelections[0].row, iNewRow = info.newSelections[0].row;
         if (iNewRow !== iOldRow) loadRelaData(iNewRow);
     });
+    $('.custom-radio').click(() => {
+        setLocalCache('StageGatherOverType', $('input[name=customRadio]:checked').attr('id'));
+        checkOverRange(gclGatherData);
+        SpreadJsObj.reLoadSheetData(gclSpread.getActiveSheet());
+        // 收起菜单
+        $('.dropdown-menu').click();
+    });
+    $('#over-percent').change(function () {
+        this.value = this.value >= 50 ? (this.value <= 100 ? ZhCalc.round(this.value, 2) : 100) : 50;
+        setLocalCache('StageGatherOverPercent', this.value);
+        checkOverRange(gclGatherData);
+        SpreadJsObj.reLoadSheetData(gclSpread.getActiveSheet());
+    });
 
     postData(preUrl + '/load', { filter: 'ledger;pos;dealBills;spec;change;stageChange;preStageChange' }, function (result) {
         // 解析清单汇总数据
@@ -221,8 +207,6 @@ $(document).ready(function () {
         gclGatherModel.checkDiffer(gclGatherData);
         gclGatherModel.reGatherLeafXmj();
         checkOverRange(gclGatherData);
-        checkXmjOverRange(gclGatherData, 'leafXmjs');
-        checkXmjOverRange(gclGatherData, 'gatherLeafXmjs');
         // 加载清单数据
         SpreadJsObj.loadSheetData(gclSpread.getActiveSheet(), SpreadJsObj.DataType.Data, gclGatherData);
         loadRelaData(0);
@@ -307,7 +291,6 @@ $(document).ready(function () {
             col.visible = col.type === 'Number' || fields.indexOf(col.field) >= 0;
         }
         gclGatherModel.reGatherLeafXmj(fields);
-        checkXmjOverRange(gclGatherData, 'gatherLeafXmjs');
         SpreadJsObj.refreshColumnVisible(gatherLeafXmjSheet);
         loadGatherLeafXmjData();
     });

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

@@ -102,7 +102,7 @@ $(document).ready(() => {
                     }
                 }
                 try {
-                    const value = math.evaluate(formula);
+                    const value = ZhCalc.mathCalcExpr(formula);
                     return value;
                 } catch(err) {
                     return 0;

+ 32 - 3
app/public/js/zh_calc.js

@@ -1,12 +1,15 @@
 'use strict';
 
 /**
- *
+ * 1. 必须引用decimal.min.js
+ * 2. 如果使用ZhCalc.mathCalcExpr和ZhCalc.tranExpr必须引用math.min.js
  * @author Mai
  * @date
  * @version
  */
-;const zhBaseCalc = (function () {
+
+;
+const zhBaseCalc = (function () {
     const zeroPrecision = 12, mulPrecision = 12, divPrecision = 12;
 
     function digitLength (num) {
@@ -67,7 +70,9 @@
  * @type {{add, sub, mul, div, round}}
  */
 const ZhCalc = (function () {
+    let mathConfig = false;
     Decimal.set({precision: 50, defaults: true});
+    const percentReg = /((\d+)|((\d+)(\.\d+)))%/g;
     /**
      * 加法 num1 + num2
      * @param num1
@@ -281,5 +286,29 @@ const ZhCalc = (function () {
         },
     };
 
-    return {add, sum, sub, mul, div, round, isNonZero: zhBaseCalc.isNonZero, calcExpr: ExprCalc}
+    function checkMathConfig () {
+        if (!mathConfig) {
+            math && math.config({
+                number: 'BigNumber',
+            });
+            mathConfig = true;
+        }
+    }
+    function mathCalcExpr(expr) {
+        checkMathConfig();
+        return parseFloat(math.evaluate(expr));
+    }
+    function tansExpr(expr) {
+        let formula = $.trim(expr).replace('\t', '').replace('=', '');
+        const percent = formula.match(percentReg);
+        if (percent) {
+            for (const p of percent) {
+                const v = math.eval(p.replace(new RegExp('%', 'gm'), '/100'));
+                formula = formula.replace(p, v);
+            }
+        }
+        return formula;
+    }
+
+    return {add, sum, sub, mul, div, round, isNonZero: zhBaseCalc.isNonZero, calcExpr: ExprCalc, mathCalcExpr}
 })();

+ 6 - 2
app/router.js

@@ -392,8 +392,12 @@ module.exports = app => {
     app.get('/tender/:id/measure/stage/:order/pay/download/file/:fid', sessionAuth, 'stageController.payDownloadFile');
     app.post('/tender/:id/measure/stage/:order/pay/delete/file', sessionAuth, tenderCheck, uncheckTenderCheck, stageCheck, 'stageController.payDeleteFile');
 
-    app.get('/tender/:id/pay', sessionAuth, tenderCheck, uncheckTenderCheck, 'payController.index');
-    app.get('/tender/:id/pay/:order', sessionAuth, tenderCheck, uncheckTenderCheck, phasePayCheck, 'payController.pay');
+    app.get('/tender/:id/pay', sessionAuth, tenderCheck, 'payController.index');
+    app.post('/tender/:id/pay/add', sessionAuth, tenderCheck, 'payController.add');
+    app.post('/tender/:id/pay/del', sessionAuth, tenderCheck, 'payController.del');
+    app.post('/tender/:id/pay/save', sessionAuth, tenderCheck, 'payController.save');
+    app.get('/tender/:id/pay/:order/detail', sessionAuth, tenderCheck, phasePayCheck, 'payController.detail');
+    app.post('/tender/:id/pay/:order/update', sessionAuth, tenderCheck, phasePayCheck, 'payController.detailUpdate');
 
     // 变更概况
     app.get('/tender/:id/measure/stage/:order/change', sessionAuth, tenderCheck, uncheckTenderCheck, stageCheck, 'stageController.change');

+ 42 - 39
app/service/change.js

@@ -340,8 +340,8 @@ module.exports = app => {
                     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 = ? AND a.cid IN (SELECT b.cid FROM ?? AS b WHERE b.uid = ? AND a.times - 1 = b.times GROUP BY b.cid))' +
+                            ' (a.uid = ? OR (a.status != ? AND a.cid IN (SELECT b.cid FROM ?? AS b WHERE b.uid = ? GROUP BY b.cid))' +
+                            // ' OR (a.status = ? AND a.cid IN (SELECT b.cid FROM ?? AS b WHERE b.uid = ? GROUP BY b.cid))' +
                             ' OR a.status = ?)' + stateSql;
                         sqlParam = [
                             this.tableName,
@@ -350,9 +350,9 @@ module.exports = app => {
                             audit.change.status.uncheck,
                             this.ctx.service.changeAudit.tableName,
                             this.ctx.session.sessionUser.accountId,
-                            audit.change.status.checkNo,
-                            this.ctx.service.changeAudit.tableName,
-                            this.ctx.session.sessionUser.accountId,
+                            // audit.change.status.checkNo,
+                            // this.ctx.service.changeAudit.tableName,
+                            // this.ctx.session.sessionUser.accountId,
                             audit.change.status.checked,
                         ];
                         break;
@@ -364,8 +364,9 @@ module.exports = app => {
                         sql =
                             'SELECT a.* FROM ?? AS a WHERE' +
                             ' (a.status = ? OR a.status = ? OR a.status = ?) AND a.tid = ?' + stateSql +
-                            (this.ctx.session.sessionUser.is_admin ? '' : ' AND ((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 = ? AND a.cid IN (SELECT b.cid FROM ?? AS b WHERE b.uid = ? AND a.times - 1 = b.times GROUP BY b.cid)))');
+                            (this.ctx.session.sessionUser.is_admin ? '' : ' AND (a.status != ? AND a.cid IN (SELECT b.cid FROM ?? AS b WHERE b.uid = ? GROUP BY b.cid))'
+                            // ' OR (a.status = ? AND a.cid IN (SELECT b.cid FROM ?? AS b WHERE b.uid = ? GROUP BY b.cid)))'
+                            );
                         sqlParam = [
                             this.tableName,
                             audit.change.status.uncheck,
@@ -375,18 +376,18 @@ module.exports = app => {
                             audit.change.status.uncheck,
                             this.ctx.service.changeAudit.tableName,
                             this.ctx.session.sessionUser.accountId,
-                            audit.change.status.checkNo,
-                            this.ctx.service.changeAudit.tableName,
-                            this.ctx.session.sessionUser.accountId,
+                            // audit.change.status.checkNo,
+                            // this.ctx.service.changeAudit.tableName,
+                            // this.ctx.session.sessionUser.accountId,
                         ];
                         break;
                     case 2: // 进行中(所有的)
                     case 4: // 终止(所有的)
                         sql =
                             'SELECT a.* FROM ?? AS a WHERE ' +
-                            'a.status = ? AND a.tid = ?' + stateSql +
-                            (this.ctx.session.sessionUser.is_admin ? '' : ' AND a.cid IN (SELECT b.cid FROM ?? AS b WHERE b.uid = ? AND a.times = b.times GROUP BY b.cid)');
-                        sqlParam = [this.tableName, status, tenderId, this.ctx.service.changeAudit.tableName, this.ctx.session.sessionUser.accountId];
+                            '(a.status = ? OR a.status = ?) AND a.tid = ?' + stateSql +
+                            (this.ctx.session.sessionUser.is_admin ? '' : ' AND a.cid IN (SELECT b.cid FROM ?? AS b WHERE b.uid = ? GROUP BY b.cid)');
+                        sqlParam = [this.tableName, status, audit.change.status.checkNoPre, tenderId, this.ctx.service.changeAudit.tableName, this.ctx.session.sessionUser.accountId];
                         break;
                     case 3: // 已完成(所有的)
                         sql = 'SELECT a.* FROM ?? AS a WHERE a.status = ? AND a.tid = ?' + stateSql;
@@ -433,8 +434,8 @@ 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 = ? AND a.cid IN (SELECT b.cid FROM ?? AS b WHERE b.uid = ? AND a.times - 1 = b.times GROUP BY b.cid))' +
+                        '(a.uid = ? OR (a.status != ? AND a.cid IN (SELECT b.cid FROM ?? AS b WHERE b.uid = ? GROUP BY b.cid))' +
+                        // ' OR (a.status = ? AND a.cid IN (SELECT b.cid FROM ?? AS b WHERE b.uid = ? GROUP BY b.cid))' +
                         ' OR a.status = ? )' + stateSql;
                     const sqlParam = [
                         this.tableName,
@@ -443,9 +444,9 @@ module.exports = app => {
                         audit.change.status.uncheck,
                         this.ctx.service.changeAudit.tableName,
                         this.ctx.session.sessionUser.accountId,
-                        audit.change.status.checkNo,
-                        this.ctx.service.changeAudit.tableName,
-                        this.ctx.session.sessionUser.accountId,
+                        // audit.change.status.checkNo,
+                        // this.ctx.service.changeAudit.tableName,
+                        // this.ctx.session.sessionUser.accountId,
                         audit.change.status.checked,
                     ];
                     const result = await this.db.query(sql, sqlParam);
@@ -464,8 +465,9 @@ module.exports = app => {
                     const sql2 =
                         'SELECT count(*) AS count FROM ?? AS a WHERE' +
                         ' (a.status = ? OR a.status = ? OR a.status = ?) AND a.tid = ?' + stateSql +
-                        (this.ctx.session.sessionUser.is_admin ? '' : ' AND ((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 = ? AND a.cid IN (SELECT b.cid FROM ?? AS b WHERE b.uid = ? AND a.times - 1 = b.times GROUP BY b.cid)))');
+                        (this.ctx.session.sessionUser.is_admin ? '' : ' AND (a.status != ? AND a.cid IN (SELECT b.cid FROM ?? AS b WHERE b.uid = ? GROUP BY b.cid))'
+                        // ' OR (a.status = ? AND a.cid IN (SELECT b.cid FROM ?? AS b WHERE b.uid = ? GROUP BY b.cid)))'
+                        );
                     const sqlParam2 = [
                         this.tableName,
                         audit.change.status.uncheck,
@@ -475,9 +477,9 @@ module.exports = app => {
                         audit.change.status.uncheck,
                         this.ctx.service.changeAudit.tableName,
                         this.ctx.session.sessionUser.accountId,
-                        audit.change.status.checkNo,
-                        this.ctx.service.changeAudit.tableName,
-                        this.ctx.session.sessionUser.accountId,
+                        // audit.change.status.checkNo,
+                        // this.ctx.service.changeAudit.tableName,
+                        // this.ctx.session.sessionUser.accountId,
                     ];
                     const result2 = await this.db.query(sql2, sqlParam2);
                     return result2[0].count;
@@ -485,9 +487,9 @@ module.exports = app => {
                 case 4: // 终止(所有的)
                     const sql3 =
                         'SELECT count(*) AS count FROM ?? AS a WHERE ' +
-                        'a.status = ? AND a.tid = ?' + stateSql +
-                        (this.ctx.session.sessionUser.is_admin ? '' : ' AND a.cid IN (SELECT b.cid FROM ?? AS b WHERE b.uid = ? AND a.times = b.times GROUP BY b.cid)');
-                    const sqlParam3 = [this.tableName, status, tenderId, this.ctx.service.changeAudit.tableName, this.ctx.session.sessionUser.accountId];
+                        '(a.status = ? OR a.status = ?) AND a.tid = ?' + stateSql +
+                        (this.ctx.session.sessionUser.is_admin ? '' : ' AND a.cid IN (SELECT b.cid FROM ?? AS b WHERE b.uid = ? GROUP BY b.cid)');
+                    const sqlParam3 = [this.tableName, status, audit.change.status.checkNoPre, tenderId, this.ctx.service.changeAudit.tableName, this.ctx.session.sessionUser.accountId];
                     const result3 = await this.db.query(sql3, sqlParam3);
                     return result3[0].count;
                 case 3: // 已完成(所有的)
@@ -512,8 +514,8 @@ module.exports = app => {
                 case 0: // 包含你的所有变更令
                     const sql =
                         'SELECT SUM(cast (a.total_price as decimal(18,6))) AS total_price 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 = ? AND a.cid IN (SELECT b.cid FROM ?? AS b WHERE b.uid = ? AND a.times - 1 = b.times GROUP BY b.cid))' +
+                        '(a.uid = ? OR (a.status != ? AND a.cid IN (SELECT b.cid FROM ?? AS b WHERE b.uid = ? GROUP BY b.cid))' +
+                        // ' OR (a.status = ? AND a.cid IN (SELECT b.cid FROM ?? AS b WHERE b.uid = ? AND a.times - 1 = b.times GROUP BY b.cid))' +
                         ' OR a.status = ? )' + stateSql;
                     const sqlParam = [
                         this.tableName,
@@ -522,9 +524,9 @@ module.exports = app => {
                         audit.change.status.uncheck,
                         this.ctx.service.changeAudit.tableName,
                         this.ctx.session.sessionUser.accountId,
-                        audit.change.status.checkNo,
-                        this.ctx.service.changeAudit.tableName,
-                        this.ctx.session.sessionUser.accountId,
+                        // audit.change.status.checkNo,
+                        // this.ctx.service.changeAudit.tableName,
+                        // this.ctx.session.sessionUser.accountId,
                         audit.change.status.checked,
                     ];
                     const result = await this.db.query(sql, sqlParam);
@@ -538,8 +540,9 @@ module.exports = app => {
                     const sql2 =
                         'SELECT SUM(cast (a.total_price as decimal(18,6))) AS total_price FROM ?? AS a WHERE' +
                         ' (a.status = ? OR a.status = ? OR a.status = ?) AND a.tid = ?' + stateSql +
-                        (this.ctx.session.sessionUser.is_admin ? '' : ' AND ((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 = ? AND a.cid IN (SELECT b.cid FROM ?? AS b WHERE b.uid = ? AND a.times - 1 = b.times GROUP BY b.cid)))');
+                        (this.ctx.session.sessionUser.is_admin ? '' : ' AND (a.status != ? AND a.cid IN (SELECT b.cid FROM ?? AS b WHERE b.uid = ? GROUP BY b.cid))'
+                        // ' OR (a.status = ? AND a.cid IN (SELECT b.cid FROM ?? AS b WHERE b.uid = ? AND a.times - 1 = b.times GROUP BY b.cid)))'
+                        );
                     const sqlParam2 = [
                         this.tableName,
                         audit.change.status.uncheck,
@@ -549,9 +552,9 @@ module.exports = app => {
                         audit.change.status.uncheck,
                         this.ctx.service.changeAudit.tableName,
                         this.ctx.session.sessionUser.accountId,
-                        audit.change.status.checkNo,
-                        this.ctx.service.changeAudit.tableName,
-                        this.ctx.session.sessionUser.accountId,
+                        // audit.change.status.checkNo,
+                        // this.ctx.service.changeAudit.tableName,
+                        // this.ctx.session.sessionUser.accountId,
                     ];
                     const result2 = await this.db.query(sql2, sqlParam2);
                     return result2[0].total_price ? result2[0].total_price : 0;
@@ -559,9 +562,9 @@ module.exports = app => {
                 case 4: // 终止(所有的)
                     const sql3 =
                         'SELECT SUM(cast (a.total_price as decimal(18,6))) AS total_price FROM ?? AS a WHERE ' +
-                        'a.status = ? AND a.tid = ?' + stateSql +
-                        (this.ctx.session.sessionUser.is_admin ? '' : ' AND a.cid IN (SELECT b.cid FROM ?? AS b WHERE b.uid = ? AND a.times = b.times GROUP BY b.cid)');
-                    const sqlParam3 = [this.tableName, status, tenderId, this.ctx.service.changeAudit.tableName, this.ctx.session.sessionUser.accountId];
+                        '(a.status = ? OR a.status = ?) AND a.tid = ?' + stateSql +
+                        (this.ctx.session.sessionUser.is_admin ? '' : ' AND a.cid IN (SELECT b.cid FROM ?? AS b WHERE b.uid = ? GROUP BY b.cid)');
+                    const sqlParam3 = [this.tableName, status, audit.change.status.checkNoPre, tenderId, this.ctx.service.changeAudit.tableName, this.ctx.session.sessionUser.accountId];
                     const result3 = await this.db.query(sql3, sqlParam3);
                     return result3[0].total_price ? result3[0].total_price : 0;
                 case 3: // 已完成(所有的)

+ 6 - 9
app/service/change_apply.js

@@ -165,7 +165,7 @@ module.exports = app => {
                     case 0: // 包含你的所有变更立项
                         sql =
                             'SELECT a.*, p.name as account_name FROM ?? AS a LEFT JOIN ?? AS p On a.notice_uid = p.id WHERE a.tid = ? AND ' +
-                            '(a.uid = ? OR (a.status != ? AND a.id IN (SELECT b.caid FROM ?? AS b WHERE b.aid = ? AND a.times = b.times GROUP BY b.caid)) OR a.status = ?)';
+                            '(a.uid = ? OR (a.status != ? AND a.id IN (SELECT b.caid FROM ?? AS b WHERE b.aid = ? GROUP BY b.caid)) OR a.status = ?)';
                         sqlParam = [
                             this.tableName,
                             this.ctx.service.projectAccount.tableName,
@@ -184,7 +184,6 @@ module.exports = app => {
                     case 5: // 待上报(所有的)PS:取未上报,退回,修订的变更令
                         sql =
                             'SELECT a.*, p.name as account_name FROM ?? AS a LEFT JOIN ?? AS p On a.notice_uid = p.id WHERE ' +
-                            // 'a.id IN (SELECT b.caid FROM ?? AS b WHERE b.aid = ? AND a.times = b.times GROUP BY b.caid) AND ' +
                             'a.uid = ? AND a.tid = ? AND (a.status = ? OR a.status = ? OR a.status = ?)';
                         sqlParam = [
                             this.tableName,
@@ -201,7 +200,7 @@ module.exports = app => {
                     case 4: // 终止(所有的)
                         sql =
                             'SELECT a.*, p.name as account_name FROM ?? AS a LEFT JOIN ?? AS p On a.notice_uid = p.id WHERE ' +
-                            'a.status = ? AND a.tid = ? AND (a.uid = ? OR a.id IN (SELECT b.caid FROM ?? AS b WHERE b.aid = ? AND a.times = b.times GROUP BY b.caid))';
+                            'a.status = ? AND a.tid = ? AND (a.uid = ? OR a.id IN (SELECT b.caid FROM ?? AS b WHERE b.aid = ? GROUP BY b.caid))';
                         sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, status, tenderId, this.ctx.session.sessionUser.accountId, this.ctx.service.changeApplyAudit.tableName, this.ctx.session.sessionUser.accountId];
                         break;
                     case 3: // 已完成(所有的)
@@ -256,7 +255,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.id IN (SELECT b.caid FROM ?? AS b WHERE b.aid = ? AND a.times = b.times GROUP BY b.caid)) OR a.status = ?)';
+                        '(a.uid = ? OR (a.status != ? AND a.id IN (SELECT b.caid FROM ?? AS b WHERE b.aid = ? GROUP BY b.caid)) OR a.status = ?)';
                     const sqlParam = [
                         this.tableName,
                         tenderId,
@@ -281,7 +280,6 @@ module.exports = app => {
                 case 5: // 待上报(所有的)PS:取未上报,退回的变更立项
                     const sql2 =
                         'SELECT count(*) AS count FROM ?? AS a WHERE ' +
-                        // 'a.id IN (SELECT b.caid FROM ?? AS b WHERE b.aid = ? AND a.times = b.times GROUP BY b.caid) ' +
                         'a.uid = ? AND a.tid = ? AND (a.status = ? OR a.status = ? OR a.status = ?)';
                     const sqlParam2 = [
                         this.tableName,
@@ -298,7 +296,7 @@ module.exports = app => {
                 case 4: // 终止(所有的)
                     const sql3 =
                         'SELECT count(*) AS count FROM ?? AS a WHERE ' +
-                        'a.status = ? AND a.tid = ? AND (a.uid = ? OR a.id IN (SELECT b.caid FROM ?? AS b WHERE b.aid = ? AND a.times = b.times GROUP BY b.caid))';
+                        'a.status = ? AND a.tid = ? AND (a.uid = ? OR a.id IN (SELECT b.caid FROM ?? AS b WHERE b.aid = ? GROUP BY b.caid))';
                     const sqlParam3 = [this.tableName, status, tenderId, this.ctx.session.sessionUser.accountId, this.ctx.service.changeApplyAudit.tableName, this.ctx.session.sessionUser.accountId];
                     const result3 = await this.db.query(sql3, sqlParam3);
                     return result3[0].count;
@@ -329,7 +327,7 @@ module.exports = app => {
                 case 0: // 包含你的所有变更令
                     const sql =
                         'SELECT SUM(cast (a.total_price as decimal(18,6))) AS total_price FROM ?? AS a WHERE a.tid = ? AND ' +
-                        '(a.uid = ? OR (a.status != ? AND a.id IN (SELECT b.caid FROM ?? AS b WHERE b.aid = ? AND a.times = b.times GROUP BY b.caid)) OR a.status = ?)';
+                        '(a.uid = ? OR (a.status != ? AND a.id IN (SELECT b.caid FROM ?? AS b WHERE b.aid = ? GROUP BY b.caid)) OR a.status = ?)';
                     const sqlParam = [
                         this.tableName,
                         tenderId,
@@ -354,7 +352,6 @@ module.exports = app => {
                 case 5: // 待上报(所有的)PS:取未上报,退回的变更立项
                     const sql2 =
                         'SELECT SUM(cast (a.total_price as decimal(18,6))) AS total_price FROM ?? AS a WHERE ' +
-                        // 'a.id IN (SELECT b.caid FROM ?? AS b WHERE b.aid = ? AND a.times = b.times GROUP BY b.caid) ' +
                         'a.uid = ? AND a.tid = ? AND (a.status = ? OR a.status = ? OR a.status = ?)';
                     const sqlParam2 = [
                         this.tableName,
@@ -371,7 +368,7 @@ module.exports = app => {
                 case 4: // 终止(所有的)
                     const sql3 =
                         'SELECT SUM(cast (a.total_price as decimal(18,6))) AS total_price FROM ?? AS a WHERE ' +
-                        'a.status = ? AND a.tid = ? AND (a.uid = ? OR a.id IN (SELECT b.caid FROM ?? AS b WHERE b.aid = ? AND a.times = b.times GROUP BY b.caid))';
+                        'a.status = ? AND a.tid = ? AND (a.uid = ? OR a.id IN (SELECT b.caid FROM ?? AS b WHERE b.aid = ? GROUP BY b.caid))';
                     const sqlParam3 = [this.tableName, status, tenderId, this.ctx.session.sessionUser.accountId, this.ctx.service.changeApplyAudit.tableName, this.ctx.session.sessionUser.accountId];
                     const result3 = await this.db.query(sql3, sqlParam3);
                     return result3[0].total_price ? result3[0].total_price : 0;

+ 63 - 19
app/service/change_audit_list.js

@@ -129,7 +129,7 @@ module.exports = app => {
          * 添加台账清单(从新增部位页新增)
          * @return {void}
          */
-        async adds(datas) {
+        async adds(datas, delimit = 100) {
             if (!this.ctx.tender || !this.ctx.change) {
                 throw '数据错误';
             }
@@ -143,7 +143,7 @@ module.exports = app => {
                         // order以下的清单+1
                         await this._syncOrder(transaction, this.ctx.change.cid, order, '+');
                     } else {
-                        order = await this.count({ cid: this.ctx.change.cid });
+                        order = await this._getMaxOrder(this.ctx.change.cid);
                         order = order ? order + 1 : 1;
                     }
                 }
@@ -154,6 +154,7 @@ module.exports = app => {
                     d.spamount = d.spamount || null;
                     d.detail = d.detail || '';
                     d.samount = d.samount || '';
+                    d.delimit = delimit;
                     d.order = order ? order : null;
                     order = order ? order + 1 : null;
                     insertData.push(d);
@@ -192,7 +193,7 @@ module.exports = app => {
                         // order以下的清单+1
                         await this._syncOrder(transaction, this.ctx.change.cid, order, '+', num);
                     } else {
-                        order = await this.count({ cid: this.ctx.change.cid });
+                        order = await this._getMaxOrder(this.ctx.change.cid);
                         order = order ? order + 1 : 1;
                     }
                 }
@@ -245,6 +246,13 @@ module.exports = app => {
             }
         }
 
+        async _getMaxOrder(cid) {
+            const sql = 'SELECT MAX(`order`) AS `order` FROM ?? WHERE cid = ?';
+            const sqlParams = [this.tableName, cid];
+            const result = await this.db.queryOne(sql, sqlParams);
+            return result ? result.order : 0;
+        }
+
         /**
          * 删除变更清单
          * @param {int} id 清单id
@@ -259,8 +267,8 @@ module.exports = app => {
                 // 判断是否可删
                 await transaction.delete(this.tableName, { id: data.ids });
                 // // order以下的清单-1
-                if (this.ctx.change.order_by) {
-                    await this._syncOrder(transaction, this.ctx.change.cid, data.postData, '-', data.ids.length);
+                if (this.ctx.change.order_by && data.postData) {
+                    await this._syncOrder(transaction, this.ctx.change.cid, data.postData, '-', data.delLength ? data.delLength : data.ids.length);
                 }
                 // 重新算变更令总额
                 await this.calcCamountSum(transaction);
@@ -481,7 +489,7 @@ module.exports = app => {
             // const sqlParam = [this.tableName, this.change.cid];
             // const tp = await transaction.queryOne(sql, sqlParam);
             // 防止小数位不精确,采用取值计算
-            const sql = 'SELECT unit_price, spamount, is_valuation FROM ?? WHERE cid = ?';
+            const sql = 'SELECT unit_price, spamount, is_valuation, gcl_id, unit FROM ?? WHERE cid = ?';
             const sqlParam = [this.tableName, this.ctx.change.cid];
             const changeList = await transaction.query(sql, sqlParam);
             let total_price = 0;
@@ -490,18 +498,53 @@ module.exports = app => {
             let valuation_tp = 0;
             let unvaluation_tp = 0;
             const tp_decimal = this.ctx.change.tp_decimal ? this.ctx.change.tp_decimal : this.ctx.tender.info.decimal.tp;
-            for (const cl of changeList) {
-                const price = this.ctx.helper.mul(cl.unit_price, cl.spamount, tp_decimal);
-                total_price = this.ctx.helper.accAdd(total_price, price);
-                if (price >= 0) {
-                    positive_tp = this.ctx.helper.accAdd(positive_tp, price);
-                } else {
-                    negative_tp = this.ctx.helper.accAdd(negative_tp, price);
-                }
-                if (cl.is_valuation) {
-                    valuation_tp = this.ctx.helper.accAdd(valuation_tp, price);
+            const up_decimal = this.ctx.change.up_decimal ? this.ctx.change.up_decimal : this.ctx.tender.info.decimal.up;
+            const gclChangeList = this._.uniq(this._.map(changeList, 'gcl_id'));
+            for (const g of gclChangeList) {
+                if (g) {
+                    const list = this._.filter(changeList, { gcl_id: g });
+                    let spamount = 0;
+                    let valuation_amount = 0;
+                    let unvaluation_amount = 0;
+                    let unitPrice = 0;
+                    for (const cl of list) {
+                        if (cl.spamount) {
+                            spamount = this.ctx.helper.accAdd(spamount, cl.spamount);
+                            unitPrice = cl.unit_price;
+                            if (cl.is_valuation) {
+                                valuation_amount = this.ctx.helper.accAdd(valuation_amount, cl.spamount);
+                            } else {
+                                unvaluation_amount = this.ctx.helper.accAdd(unvaluation_amount, cl.spamount);
+                            }
+                        }
+                    }
+                    const price = this.ctx.helper.mul(spamount, this.ctx.helper.round(unitPrice, up_decimal), tp_decimal);
+                    const valuation_price = this.ctx.helper.mul(valuation_amount, this.ctx.helper.round(unitPrice, up_decimal), tp_decimal) || 0;
+                    const unvaluation_price = this.ctx.helper.mul(unvaluation_amount, this.ctx.helper.round(unitPrice, up_decimal), tp_decimal) || 0;
+                    valuation_tp = this.ctx.helper.accAdd(valuation_tp, valuation_price);
+                    unvaluation_tp = this.ctx.helper.accAdd(unvaluation_tp, unvaluation_price);
+                    total_price = this.ctx.helper.accAdd(total_price, price);
+                    if (price >= 0) {
+                        positive_tp = this.ctx.helper.accAdd(positive_tp, price);
+                    } else {
+                        negative_tp = this.ctx.helper.accAdd(negative_tp, price);
+                    }
                 } else {
-                    unvaluation_tp = this.ctx.helper.accAdd(unvaluation_tp, price);
+                    const list = this._.filter(changeList, { gcl_id: g });
+                    for (const cl of list) {
+                        const price = this.ctx.helper.mul(this.ctx.helper.round(cl.unit_price, up_decimal), cl.spamount, tp_decimal);
+                        total_price = this.ctx.helper.accAdd(total_price, price);
+                        if (price >= 0) {
+                            positive_tp = this.ctx.helper.accAdd(positive_tp, price);
+                        } else {
+                            negative_tp = this.ctx.helper.accAdd(negative_tp, price);
+                        }
+                        if (cl.is_valuation) {
+                            valuation_tp = this.ctx.helper.accAdd(valuation_tp, price);
+                        } else {
+                            unvaluation_tp = this.ctx.helper.accAdd(unvaluation_tp, price);
+                        }
+                    }
                 }
             }
             const updateData = {
@@ -513,6 +556,7 @@ module.exports = app => {
             };
             if (updateTpDecimal) {
                 updateData.tp_decimal = tp_decimal;
+                updateData.up_decimal = up_decimal;
             }
             const options = {
                 where: {
@@ -1045,7 +1089,7 @@ module.exports = app => {
                     const insertL = [
                         l.id, l.code, l.b_code, l.name, l.unit, l.source, l.remark, l.ledger_id,
                         l.ledger_pid, l.level, l.order, l.full_path, l.is_leaf, l.quantity, l.total_price,
-                        l.unit_price, l.drawing_code, l.memo, l.dgn_qty1, l.dgn_qty2, l.deal_qty, l.deal_tp,
+                        l.unit_price, l.drawing_code, l.memo, l.features, l.dgn_qty1, l.dgn_qty2, l.deal_qty, l.deal_tp,
                         l.sgfh_qty, l.sgfh_tp, l.sjcl_qty, l.sjcl_tp, l.qtcl_qty, l.qtcl_tp, l.node_type, l.crid, l.ccid,
                         l.tender_id, l.sgfh_expr, l.sjcl_expr, l.qtcl_expr, l.check_calc,
                         l.ex_memo1, l.ex_memo2, l.ex_memo3,
@@ -1058,7 +1102,7 @@ module.exports = app => {
                 const bSql = 'Insert Into ' +
                     this.ctx.service.ledger.tableName +
                     '  (id, code, b_code, name, unit, source, remark, ledger_id, ledger_pid, level, `order`, full_path, is_leaf,' +
-                    '     quantity, total_price, unit_price, drawing_code, memo, dgn_qty1, dgn_qty2, deal_qty, deal_tp,' +
+                    '     quantity, total_price, unit_price, drawing_code, memo, features, dgn_qty1, dgn_qty2, deal_qty, deal_tp,' +
                     '     sgfh_qty, sgfh_tp, sjcl_qty, sjcl_tp, qtcl_qty, qtcl_tp, node_type, crid, ccid, tender_id,' +
                     '     sgfh_expr, sjcl_expr, qtcl_expr, check_calc,' +
                     '     ex_memo1, ex_memo2, ex_memo3) VALUES ' + insertLedgerArr.join(',') + ';';

+ 6 - 8
app/service/change_plan.js

@@ -186,7 +186,7 @@ module.exports = app => {
                     case 0: // 包含你的所有变更方案
                         sql =
                             'SELECT a.*, p.name as account_name FROM ?? AS a LEFT JOIN ?? AS p On a.uid = p.id WHERE a.tid = ? AND ' +
-                            '(a.uid = ? OR (a.status != ? AND a.id IN (SELECT b.cpid FROM ?? AS b WHERE b.aid = ? AND a.times = b.times GROUP BY b.cpid)) OR a.status = ?)';
+                            '(a.uid = ? OR (a.status != ? AND a.id IN (SELECT b.cpid FROM ?? AS b WHERE b.aid = ? GROUP BY b.cpid)) OR a.status = ?)';
                         sqlParam = [
                             this.tableName,
                             this.ctx.service.projectAccount.tableName,
@@ -205,7 +205,6 @@ module.exports = app => {
                     case 5: // 待上报(所有的)PS:取未上报,退回,修订的变更令
                         sql =
                             'SELECT a.*, p.name as account_name FROM ?? AS a LEFT JOIN ?? AS p On a.uid = p.id WHERE ' +
-                            // 'a.id IN (SELECT b.cpid FROM ?? AS b WHERE b.aid = ? AND a.times = b.times GROUP BY b.cpid) AND ' +
                             'a.uid = ? AND a.tid = ? AND (a.status = ? OR a.status = ? OR a.status = ?)';
                         sqlParam = [
                             this.tableName,
@@ -222,7 +221,7 @@ module.exports = app => {
                     case 4: // 终止(所有的)
                         sql =
                             'SELECT a.*, p.name as account_name FROM ?? AS a LEFT JOIN ?? AS p On a.uid = p.id WHERE ' +
-                            'a.status = ? AND a.tid = ? AND (a.uid = ? OR a.id IN (SELECT b.cpid FROM ?? AS b WHERE b.aid = ? AND a.times = b.times GROUP BY b.cpid))';
+                            'a.status = ? AND a.tid = ? AND (a.uid = ? OR a.id IN (SELECT b.cpid FROM ?? AS b WHERE b.aid = ? GROUP BY b.cpid))';
                         sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, status, tenderId, this.ctx.session.sessionUser.accountId, this.ctx.service.changePlanAudit.tableName, this.ctx.session.sessionUser.accountId];
                         break;
                     case 3: // 已完成(所有的)
@@ -277,7 +276,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.id IN (SELECT b.cpid FROM ?? AS b WHERE b.aid = ? AND a.times = b.times GROUP BY b.cpid)) OR a.status = ?)';
+                        '(a.uid = ? OR (a.status != ? AND a.id IN (SELECT b.cpid FROM ?? AS b WHERE b.aid = ? GROUP BY b.cpid)) OR a.status = ?)';
                     const sqlParam = [
                         this.tableName,
                         tenderId,
@@ -302,7 +301,6 @@ module.exports = app => {
                 case 5: // 待上报(所有的)PS:取未上报,退回的变更立项
                     const sql2 =
                         'SELECT count(*) AS count FROM ?? AS a WHERE ' +
-                        // 'a.id IN (SELECT b.cpid FROM ?? AS b WHERE b.aid = ? AND a.times = b.times GROUP BY b.cpid) ' +
                         'a.uid = ? AND a.tid = ? AND (a.status = ? OR a.status = ? OR a.status = ?)';
                     const sqlParam2 = [
                         this.tableName,
@@ -319,7 +317,7 @@ module.exports = app => {
                 case 4: // 终止(所有的)
                     const sql3 =
                         'SELECT count(*) AS count FROM ?? AS a WHERE ' +
-                        'a.status = ? AND a.tid = ? AND (a.uid = ? OR a.id IN (SELECT b.cpid FROM ?? AS b WHERE b.aid = ? AND a.times = b.times GROUP BY b.cpid))';
+                        'a.status = ? AND a.tid = ? AND (a.uid = ? OR a.id IN (SELECT b.cpid FROM ?? AS b WHERE b.aid = ? GROUP BY b.cpid))';
                     const sqlParam3 = [this.tableName, status, tenderId, this.ctx.session.sessionUser.accountId, this.ctx.service.changePlanAudit.tableName, this.ctx.session.sessionUser.accountId];
                     const result3 = await this.db.query(sql3, sqlParam3);
                     return result3[0].count;
@@ -350,7 +348,7 @@ module.exports = app => {
                 case 0: // 包含你的所有变更令
                     const sql =
                         'SELECT SUM(cast (a.total_price as decimal(18,6))) AS total_price FROM ?? AS a WHERE a.tid = ? AND ' +
-                        '(a.uid = ? OR (a.status != ? AND a.id IN (SELECT b.cpid FROM ?? AS b WHERE b.aid = ? AND a.times = b.times GROUP BY b.cpid)) OR a.status = ?)';
+                        '(a.uid = ? OR (a.status != ? AND a.id IN (SELECT b.cpid FROM ?? AS b WHERE b.aid = ? GROUP BY b.cpid)) OR a.status = ?)';
                     const sqlParam = [
                         this.tableName,
                         tenderId,
@@ -392,7 +390,7 @@ module.exports = app => {
                 case 4: // 终止(所有的)
                     const sql3 =
                         'SELECT SUM(cast (a.total_price as decimal(18,6))) AS total_price FROM ?? AS a WHERE ' +
-                        'a.status = ? AND a.tid = ? AND (a.uid = ? OR a.id IN (SELECT b.cpid FROM ?? AS b WHERE b.aid = ? AND a.times = b.times GROUP BY b.cpid))';
+                        'a.status = ? AND a.tid = ? AND (a.uid = ? OR a.id IN (SELECT b.cpid FROM ?? AS b WHERE b.aid = ? GROUP BY b.cpid))';
                     const sqlParam3 = [this.tableName, status, tenderId, this.ctx.session.sessionUser.accountId, this.ctx.service.changePlanAudit.tableName, this.ctx.session.sessionUser.accountId];
                     const result3 = await this.db.query(sql3, sqlParam3);
                     return result3[0].total_price ? result3[0].total_price : 0;

+ 4 - 6
app/service/change_project.js

@@ -156,7 +156,7 @@ module.exports = app => {
                     case 0: // 包含你的所有变更立项
                         sql =
                             'SELECT a.*, p.name as account_name FROM ?? AS a LEFT JOIN ?? AS p On a.uid = p.id WHERE a.tid = ? AND' +
-                            ' (a.uid = ? OR (a.status != ? AND (a.id IN (SELECT b.cpid FROM ?? AS b WHERE b.aid = ? AND a.times = b.times GROUP BY b.cpid)' +
+                            ' (a.uid = ? OR (a.status != ? AND (a.id IN (SELECT b.cpid FROM ?? AS b WHERE b.aid = ? GROUP BY b.cpid)' +
                             ' OR a.id IN (SELECT c.cpid FROM ?? AS c WHERE c.aid = ? AND c.tid = ?)))' +
                             ' OR a.status = ? )';
                         sqlParam = [
@@ -188,7 +188,6 @@ module.exports = app => {
                     case 5: // 待上报(所有的)PS:取未上报,退回,修订的变更令
                         sql =
                             'SELECT a.*, p.name as account_name FROM ?? AS a LEFT JOIN ?? AS p On a.uid = p.id WHERE ' +
-                            // 'a.id IN (SELECT b.cpid FROM ?? AS b WHERE b.aid = ? AND a.times = b.times GROUP BY b.cpid) AND ' +
                             'a.uid = ? AND a.tid = ? AND (a.status = ? OR a.status = ? OR a.status = ?)';
                         sqlParam = [
                             this.tableName,
@@ -205,7 +204,7 @@ module.exports = app => {
                     case 4: // 终止(所有的)
                         sql =
                             'SELECT a.*, p.name as account_name FROM ?? AS a LEFT JOIN ?? AS p On a.uid = p.id WHERE ' +
-                            'a.status = ? AND a.tid = ? AND (a.uid = ? OR a.id IN (SELECT b.cpid FROM ?? AS b WHERE b.aid = ? AND a.times = b.times GROUP BY b.cpid) OR a.id IN (SELECT c.cpid FROM ?? AS c WHERE c.aid = ? AND c.tid = ?))';
+                            'a.status = ? AND a.tid = ? AND (a.uid = ? OR a.id IN (SELECT b.cpid FROM ?? AS b WHERE b.aid = ? GROUP BY b.cpid) OR a.id IN (SELECT c.cpid FROM ?? AS c WHERE c.aid = ? AND c.tid = ?))';
                         sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, status, tenderId, this.ctx.session.sessionUser.accountId, this.ctx.service.changeProjectAudit.tableName, this.ctx.session.sessionUser.accountId, this.ctx.service.changeProjectXsAudit.tableName, this.ctx.session.sessionUser.accountId, tenderId];
                         break;
                     case 3: // 已完成(所有的)
@@ -288,7 +287,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.id IN (SELECT b.cpid FROM ?? AS b WHERE b.aid = ? AND a.times = b.times GROUP BY b.cpid) OR a.id IN (SELECT c.cpid FROM ?? AS c WHERE c.aid = ? AND c.tid = ?))) OR a.status = ? )';
+                        '(a.uid = ? OR (a.status != ? AND (a.id IN (SELECT b.cpid FROM ?? AS b WHERE b.aid = ? GROUP BY b.cpid) OR a.id IN (SELECT c.cpid FROM ?? AS c WHERE c.aid = ? AND c.tid = ?))) OR a.status = ? )';
                     const sqlParam = [
                         this.tableName,
                         tenderId,
@@ -316,7 +315,6 @@ module.exports = app => {
                 case 5: // 待上报(所有的)PS:取未上报,退回的变更立项
                     const sql2 =
                         'SELECT count(*) AS count FROM ?? AS a WHERE ' +
-                        // 'a.id IN (SELECT b.cpid FROM ?? AS b WHERE b.aid = ? AND a.times = b.times GROUP BY b.cpid) ' +
                         'a.uid = ? AND a.tid = ? AND (a.status = ? OR a.status = ? OR a.status = ?)';
                     const sqlParam2 = [
                         this.tableName,
@@ -333,7 +331,7 @@ module.exports = app => {
                 case 4: // 终止(所有的)
                     const sql3 =
                         'SELECT count(*) AS count FROM ?? AS a WHERE ' +
-                        'a.status = ? AND a.tid = ? AND (a.uid = ? OR a.id IN (SELECT b.cpid FROM ?? AS b WHERE b.aid = ? AND a.times = b.times GROUP BY b.cpid) OR a.id IN (SELECT c.cpid FROM ?? AS c WHERE c.aid = ? AND c.tid = ?))';
+                        'a.status = ? AND a.tid = ? AND (a.uid = ? OR a.id IN (SELECT b.cpid FROM ?? AS b WHERE b.aid = ? GROUP BY b.cpid) OR a.id IN (SELECT c.cpid FROM ?? AS c WHERE c.aid = ? AND c.tid = ?))';
                     const sqlParam3 = [this.tableName, status, tenderId, this.ctx.session.sessionUser.accountId, this.ctx.service.changeProjectAudit.tableName, this.ctx.session.sessionUser.accountId, this.ctx.service.changeProjectXsAudit.tableName, this.ctx.session.sessionUser.accountId, tenderId];
                     const result3 = await this.db.query(sql3, sqlParam3);
                     return result3[0].count;

+ 2 - 3
app/service/financial_pay.js

@@ -74,7 +74,7 @@ module.exports = app => {
                 case 4: // 终止(所有的)
                     sql = 'SELECT a.* FROM ?? as a WHERE ' +
                         'a.status = ? AND a.spid = ?' + addSql + ' AND (a.uid = ? OR ' +
-                        'a.id IN (SELECT b.fpid FROM ?? AS b WHERE b.aid = ? AND a.times = b.times GROUP BY b.fpid))';
+                        'a.id IN (SELECT b.fpid FROM ?? AS b WHERE b.aid = ? GROUP BY b.fpid))';
                     sqlParam = [this.tableName, status, spid, this.ctx.session.sessionUser.accountId,
                         this.ctx.service.financialPayAudit.tableName, this.ctx.session.sessionUser.accountId];
                     break;
@@ -121,7 +121,6 @@ module.exports = app => {
                 case 5: // 待上报(所有的)PS:取未上报,退回的变更立项
                     const sql2 =
                         'SELECT count(*) AS count FROM ?? as a WHERE ' +
-                        // 'a.id IN (SELECT b.cpid FROM ?? AS b WHERE b.aid = ? AND a.times = b.times GROUP BY b.cpid) ' +
                         'a.spid = ?' + addSql + ' AND (a.status = ? OR a.status = ?)';
                     const sqlParam2 = [
                         this.tableName,
@@ -135,7 +134,7 @@ module.exports = app => {
                 case 4: // 终止(所有的)
                     const sql3 =
                         'SELECT count(*) AS count FROM ?? as a WHERE ' +
-                        'a.status = ? AND a.spid = ?' + addSql + ' AND (a.uid = ? OR a.id IN (SELECT b.fpid FROM ?? AS b WHERE b.aid = ? AND a.times = b.times GROUP BY b.fpid))';
+                        'a.status = ? AND a.spid = ?' + addSql + ' AND (a.uid = ? OR a.id IN (SELECT b.fpid FROM ?? AS b WHERE b.aid = ? GROUP BY b.fpid))';
                     const sqlParam3 = [this.tableName, status, spid, this.ctx.session.sessionUser.accountId, this.ctx.service.financialPayAudit.tableName, this.ctx.session.sessionUser.accountId];
                     const result3 = await this.db.query(sql3, sqlParam3);
                     return result3[0].count;

+ 6 - 1
app/service/ledger.js

@@ -712,6 +712,10 @@ module.exports = app => {
                 id: 'ledger_id', pid: 'ledger_pid', order: 'order', full_path: 'full_path', level: 'level', rootId: -1,
                 calcFields: [ 'total_price' ],
                 calc(node, helper, decimal) {
+                    helper.checkDgnQtyPrecision(node);
+                    const precision = helper.findPrecision(tender.info.precision, node.unit);
+                    node.quantity = helper.round(node.quantity, precision.value);
+                    node.unit_price = helper.round(node.unit_price, decimal.up);
                     if (!node.children || node.children.length === 0) {
                         node.total_price = helper.mul(node.quantity, node.unit_price, decimal.tp);
                     }
@@ -778,7 +782,8 @@ module.exports = app => {
                     total_price: isLeaf ? n.total_price || 0 : 0,
                     dgn_qty1: n.dgn_qty1 || 0,
                     dgn_qty2: n.dgn_qty2 || 0,
-                    memo: n.source.join(';'),
+                    memo: n.remark,
+                    // memo: n.source.join(';'),
                 });
                 if (!n.glj) continue;
                 for (const g of n.glj) {

+ 4 - 2
app/service/material.js

@@ -38,7 +38,8 @@ module.exports = app => {
             material.flowAuditorIds = this._.map(material.flowAuditors, 'aid');
             material.nextAuditors = material.curAuditors.length > 0 ? material.auditors.filter(x => { return x.order === material.curAuditors[0].order + 1; }) : [];
             material.nextAuditorIds = this._.map(material.nextAuditors, 'aid');
-            material.auditorGroups = this.ctx.helper.groupAuditors(material.auditors);
+            const newAuditors = material.auditors.filter(x => { return x.is_old === 0; });
+            material.auditorGroups = this.ctx.helper.groupAuditors(newAuditors);
             material.userGroups = this.ctx.helper.groupAuditorsUniq(material.auditorGroups);
             material.userGroups.unshift([{
                 aid: material.user.id, order: 0, times: material.times, audit_order: 0, audit_type: auditType.key.common,
@@ -55,7 +56,8 @@ module.exports = app => {
             // 获取审批流程中左边列表
             if (material.status === auditConst.status.checkNo && material.user_id !== this.ctx.session.sessionUser.accountId) {
                 const auditors = await this.ctx.service.materialAudit.getAuditors(material.id, times); // 全部参与的审批人
-                const auditorGroups = this.ctx.helper.groupAuditors(auditors);
+                const newAuditors = auditors.filter(x => { return x.is_old === 0; });
+                const auditorGroups = this.ctx.helper.groupAuditors(newAuditors);
                 material.auditors2 = this.ctx.helper.groupAuditorsUniq(auditorGroups);
                 material.auditors2.unshift([{
                     aid: material.user.id, order: 0, times: material.times - 1, audit_order: 0, audit_type: auditType.key.common,

+ 20 - 5
app/service/material_audit.js

@@ -55,7 +55,7 @@ module.exports = app => {
          * @return {Promise<*>}
          */
         async getAuditors(materialId, times = 1, order_sort = 'asc') {
-            const sql = 'SELECT la.id, la.aid, la.times, la.order, la.status, la.opinion, la.begin_time, la.end_time, la.audit_type, la.audit_order,' +
+            const sql = 'SELECT la.id, la.aid, la.times, la.order, la.status, la.opinion, la.is_old, la.begin_time, la.end_time, la.audit_type, la.audit_order,' +
                 '    pa.name, pa.company, pa.role, pa.mobile, pa.telephone, pa.sign_path' +
                 `  FROM ${this.tableName} la LEFT JOIN ${this.ctx.service.projectAccount.tableName} pa ON la.aid = pa.id` +
                 '  WHERE la.mid = ? AND la.times = ?' +
@@ -69,6 +69,20 @@ module.exports = app => {
             return result;
         }
 
+        async getLastestAuditors(materialId, times, status) {
+            const sql =
+                'SELECT la.`aid`, pa.`name`, pa.`company`, pa.`role`, pa.`mobile`, pa.`telephone`, la.audit_type, la.audit_order,' +
+                '    la.`times`, la.`order`, la.`status`, la.`opinion`, la.`begin_time`, la.`end_time` ' +
+                '    FROM ' + this.tableName + ' AS la' +
+                '    Left Join ' + this.ctx.service.projectAccount.tableName + ' AS pa ON la.`aid` = pa.`id`' +
+                '  WHERE la.`mid` = ? and la.`status` = ? and la.`times` = ?' +
+                '  ORDER BY `order` DESC';
+            const sqlParam = [materialId, status, times ? times: 1];
+            const result = await this.db.query(sql, sqlParam);
+            if (result.length === 0) return [];
+            return result.filter(x => { return x.order === result[0].order });
+        }
+
         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`, la.`audit_order`, la.`audit_type`, Max(la.`order`) as max_order, GROUP_CONCAT(la.tp_data) as tp_data ' +
@@ -1060,7 +1074,7 @@ module.exports = app => {
                     cur = await this.db.queryOne(`SELECT * From ${this.tableName} where mid = ? AND times = ? AND status = ? ORDER By times DESC, ` + '`order` DESC', [materialId, times, status]);
                     if (!cur) return [];
 
-                    sql = 'SELECT la.`aid`, pa.`name`, pa.`company`, pa.`role`, la.`times`, la.`mid`, la.`order`, la.audit_order, la.audit_type ' +
+                    sql = 'SELECT la.`aid`, pa.`name`, pa.`company`, pa.`role`, la.`times`, la.`mid`, la.`order`, la.`status`, la.audit_order, la.audit_type ' +
                         '  FROM ?? AS la Left Join ?? AS pa On la.`aid` = pa.`id` ' +
                         '  WHERE la.`mid` = ? and la.`order` = ? and times = ?';
                     sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, materialId, cur.order, times];
@@ -1070,7 +1084,7 @@ module.exports = app => {
                     cur = await this.db.queryOne(`SELECT * From ${this.tableName} where mid = ? AND times = ? AND status = ? ORDER By times DESC, ` + '`order` DESC', [materialId, parseInt(times) - 1, status]);
                     if (!cur) return [];
 
-                    sql = 'SELECT la.`aid`, pa.`name`, pa.`company`, pa.`role`, la.`times`, la.`mid`, la.`order`, la.audit_order, la.audit_type ' +
+                    sql = 'SELECT la.`aid`, pa.`name`, pa.`company`, pa.`role`, la.`times`, la.`mid`, la.`order`, la.`status`, la.audit_order, la.audit_type ' +
                         '  FROM ?? AS la Left Join ?? AS pa On la.`aid` = pa.`id` ' +
                         '  WHERE la.`mid` = ? and la.`order` = ? and la.`times` = ?';
                     sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, materialId, cur.order, parseInt(times) - 1];
@@ -1241,7 +1255,7 @@ module.exports = app => {
                 }
                 if (this.ctx.tender.info.shenpi.material === shenpiConst.sp_status.gdspl) {
                     const newAuditors = await transaction.select(this.tableName, { where: { mid: materialId, times } });
-                    const newAuditorGroup = this.ctx.helper.groupAuditors(newAuditors);
+                    const newAuditorGroup = this.ctx.helper.groupAuditors(newAuditors.filter(x => { return x.is_old === 0; }));
                     const uniqNewAuditorGroup = this.ctx.helper.groupAuditorsUniq(newAuditorGroup);
                     await this.ctx.service.shenpiAudit.updateAuditListWithAuditType(transaction, this.ctx.tender.id, this.ctx.tender.info.shenpi.material, shenpiConst.sp_type.material, uniqNewAuditorGroup, sp_group);
                 } else if (this.ctx.tender.info.shenpi.material === shenpiConst.sp_status.gdzs) {
@@ -1258,7 +1272,8 @@ module.exports = app => {
 
         async getAuditorGroup(materialId, times) {
             const auditors = await this.getAuditors(materialId, times); // 全部参与的审批人
-            return this.ctx.helper.groupAuditors(auditors);
+            const newAuditors = auditors.filter(x => { return x.is_old === 0; });
+            return this.ctx.helper.groupAuditors(newAuditors);
         }
 
         async getUserGroup(materialId, times) {

+ 1 - 1
app/service/message.js

@@ -117,7 +117,7 @@ module.exports = app => {
                 const operate = id === 0 ? await transaction.insert(this.tableName, data) :
                     await transaction.update(this.tableName, data);
 
-                const result = operate.affectedRows > 0 ? operate.insertId : false;
+                const result = operate.affectedRows > 0 ? (id === 0 ? operate.insertId : true) : false;
                 if (id === 0) {
                     const attList = await this.ctx.service.messageAtt.getAllDataByCondition({ where: { mid: 0, uid: user.accountId } });
                     if (attList.length > 0) {

+ 244 - 35
app/service/phase_pay.js

@@ -9,6 +9,27 @@
  */
 
 const audit = require('../const/audit').common;
+const calcBase = [
+    {name: '签约合同价', code: 'htj', sort: 10},
+    {name: '暂列金额', code: 'zlje', sort: 2},
+    {name: '签约合同价(不含暂列金)', code: 'htjszl', sort: 1},
+    {name: '签约开工预付款', code: 'kgyfk', sort: 2},
+    {name: '签约材料预付款', code: 'clyfk', sort: 2},
+    {name: '本期完成计量', code: 'bqwc', limit: true, sort: 10, checkStart: 'gather_tp'},
+    {name: '本期合同计量', code: 'bqht', limit: true, sort: 10, checkStart: 'contract_tp'},
+    {name: '本期变更计量', code: 'bqbg', limit: true, sort: 10},
+    {name: '本期清单完成计量', code: 'bqqdwc', limit: true, sort: 10},
+    {name: '本期清单合同计量', code: 'bqqdht', limit: true, sort: 10},
+    {name: '本期清单变更计量', code: 'bqqdbg', limit: true, sort: 10},
+    {name: '本期一般变更计量', code: 'ybbqbg', limit: true, sort: 5},
+    {name: '本期较大变更计量', code: 'jdbqbg', limit: true, sort: 5},
+    {name: '本期重大变更计量', code: 'zdbqbg', limit: true, sort: 5},
+    {name: '100章本期完成计量', code: 'ybbqwc', limit: true, sort: 1},
+    {name: '本期应付', code: 'bqyf', limit: true, ptNormalLimit: true, sort: 20},
+    {name: '奖金', code: 'bonus', limit: true, sort: 1},
+    {name: '罚金', code: 'fine', limit: true, sort: 1},
+    {name: '甲供材料', code: 'jgcl', limit: true, sort: 1},
+];
 
 module.exports = app => {
     class PhasePay extends app.BaseService {
@@ -25,6 +46,8 @@ module.exports = app => {
         }
 
         analysisPhasePay(data) {
+            if (!data) return;
+
             const datas = data instanceof Array ? data : [data];
             datas.forEach(x => {
                 x.rela_stage = x.rela_stage ? JSON.parse(x.rela_stage) : [];
@@ -42,7 +65,6 @@ module.exports = app => {
                 x.end_cut_tp = helper.add(x.cut_tp, x.pre_cut_tp);
                 x.end_sf_tp = helper.add(x.sf_tp, x.pre_sf_tp);
                 x.end_yf_tp = helper.add(x.yf_tp, x.pre_yf_tp);
-                if (thousandth)
                 for (const prop in x) {
                     if (prop.indexOf('_tp') > 0) {
                         x['display_' + prop] = formatNum(x[prop]);
@@ -77,11 +99,7 @@ module.exports = app => {
             return result;
         }
 
-        async loadUser(phasePay) {
-            // todo 加载审批人
-        }
-
-        async getNewOrder(tid) {
+        async getMaxOrder(tid) {
             const sql = 'SELECT Max(`phase_order`) As max_order FROM ' + this.tableName + ' Where `tid` = ?';
             const sqlParam = [tid];
             const result = await this.db.queryOne(sql, sqlParam);
@@ -89,7 +107,7 @@ module.exports = app => {
         }
 
         async _checkRelaStageConflict(relaStage, phasePay) {
-            const pays = this.getAllPhasePay(phasePay.tid);
+            const pays = await this.getAllPhasePay(phasePay.tid);
             for (const p of pays) {
                 if (p.id === phasePay.id) continue;
                 for (const s of relaStage) {
@@ -99,15 +117,144 @@ module.exports = app => {
             return false;
         }
 
-        async getCalcBase(relaStage) {
-            // todo 获取计算基数
+        async getCalcBase(relaStage, prePhase) {
+            const result = {};
+            for (const stage of relaStage) {
+                result.contract_tp = this.ctx.helper.add(result.contract_tp, stage.contract_tp);
+                result.qc_tp = this.ctx.helper.add(result.contract_tp, stage.qc_tp);
+                result.pc_tp = this.ctx.helper.add(result.pc_tp, stage.pc_tp);
+
+                const qdSum = await this.ctx.service.stageBills.getSumTotalPriceGcl(stage);
+                result.qd_contract_tp = qdSum.contract_tp || 0;
+                result.qd_qc_tp = qdSum.qc_tp || 0;
+                result.qd_pc_tp = qdSum.pc_tp || 0;
+
+                const sumGcl = await this.ctx.service.stageBills.getSumTotalPriceGcl(stage, '^[^0-9]*1[0-9]{2}(-|$)');
+                const sumPc = await this.ctx.service.stageBillsPc.getSumTotalPriceGcl(stage, '^[^0-9]*1[0-9]{2}(-|$)');
+                result.gather_100_tp = this.ctx.helper.sum([sumGcl.contract_tp, sumGcl.qc_tp, sumPc.pc_tp]);
+
+                const bg = await this.ctx.service.stage.getChangeSubtotal(stage);
+                result.common_bg_tp = bg.common || 0;
+                result.more_bg_tp = bg.more || 0;
+                result.great_bg_tp = bg.great || 0;
+            }
+            result.gather_tp = this.ctx.helper.sum([result.contract_tp, result.qc_tp, result.pc_tp]) || 0;
+            result.qd_gather_tp = this.ctx.helper.sum([result.qd_contract_tp, result.qd_qc_tp, result.qd_pc_tp]) || 0;
+            const bonusSum = await this.ctx.service.stageBonus.getSumTp(relaStage);
+            result.bonus_positive_tp = bonusSum.positive_tp || 0;
+            result.bonus_negative_tp = bonusSum.negative_tp || 0;
+            result.bonus_tp = bonusSum.sum_tp || 0;
+            const jgclSum = await this.ctx.service.stageJgcl.getSumTp(relaStage);
+            result.jgcl_tp = jgclSum.sum_tp || 0;
+            const otherSum = await this.ctx.service.stageOther.getSumTp(relaStage);
+            result.other_tp = otherSum.sum_tp || 0;
+            const safeProdSum = await this.ctx.service.stageSafeProd.getSumTp(relaStage);
+            result.safe_prod_tp = safeProdSum.sum_tp || 0;
+            const tempLandSum = await this.ctx.service.stageTempLand.getSumTp(relaStage);
+            result.temp_land_tp = tempLandSum.sum_tp || 0;
+
+            if (prePhase && prePhase.calc_base) {
+                result.pre_contract_tp = this.ctx.helper.add(prePhase.calc_base.contract_tp, prePhase.calc_base.pre_contract_tp);
+                result.pre_qc_tp = this.ctx.helper.add(prePhase.calc_base.qc_tp, prePhase.calc_base.pre_qc_tp);
+                result.pre_pc_tp = this.ctx.helper.add(prePhase.calc_base.pc_tp, prePhase.calc_base.pre_pc_tp);
+                result.pre_gather_tp = this.ctx.helper.add(prePhase.calc_base.gather_tp, prePhase.calc_base.pre_gather_tp);
+
+                result.pre_qd_contract_tp = this.ctx.helper.add(prePhase.calc_base.qd_contract_tp, prePhase.calc_base.pre_qd_contract_tp);
+                result.pre_qd_qc_tp = this.ctx.helper.add(prePhase.calc_base.qd_qc_tp, prePhase.calc_base.pre_qd_qc_tp);
+                result.pre_qd_pc_tp = this.ctx.helper.add(prePhase.calc_base.qd_pc_tp, prePhase.calc_base.pre_qd_pc_tp);
+                result.pre_qd_gather_tp = this.ctx.helper.add(prePhase.calc_base.qd_gather_tp, prePhase.calc_base.pre_qd_gather_tp);
+
+                result.pre_bonus_positive_tp = this.ctx.helper.add(prePhase.calc_base.bonus_positive_tp, prePhase.calc_base.pre_bonus_positive_tp);
+                result.pre_bonus_negative_tp = this.ctx.helper.add(prePhase.calc_base.bonus_negative_tp, prePhase.calc_base.pre_bonus_negative_tp);
+                result.pre_bonus_tp = this.ctx.helper.add(prePhase.calc_base.bonus_tp, prePhase.calc_base.pre_bonus_tp);
+                result.pre_jgcl_tp = this.ctx.helper.add(prePhase.calc_base.jgcl_tp, prePhase.calc_base.pre_jgcl_tp);
+                result.pre_other_tp = this.ctx.helper.add(prePhase.calc_base.other_tp, prePhase.calc_base.pre_other_tp);
+                result.pre_safe_prod_tp = this.ctx.helper.add(prePhase.calc_base.safe_prod_tp, prePhase.calc_base.pre_safe_prod_tp);
+                result.pre_temp_land_tp = this.ctx.helper.add(prePhase.calc_base.temp_land_tp, prePhase.calc_base.pre_temp_land_tp);
+            }
+            return result;
+        }
+
+        /**
+         * 获取 当期的 计算基数
+         * @return {Promise<any>}
+         */
+        getPhasePayCalcBase(phasePay, tenderInfo) {
+            const payCalcBase = JSON.parse(JSON.stringify(calcBase));
+            for (const cb of payCalcBase) {
+                switch (cb.code) {
+                    case 'htj':
+                        cb.value = tenderInfo.deal_param.contractPrice;
+                        break;
+                    case 'zlje':
+                        cb.value = tenderInfo.deal_param.zanLiePrice;
+                        break;
+                    case 'htjszl':
+                        cb.value = this.ctx.helper.sub(tenderInfo.deal_param.contractPrice, tenderInfo.deal_param.zanLiePrice);
+                        break;
+                    case 'kgyfk':
+                        cb.value = tenderInfo.deal_param.startAdvance;
+                        break;
+                    case 'clyfk':
+                        cb.value = tenderInfo.deal_param.materialAdvance;
+                        break;
+                    case 'bqwc':
+                        cb.value = phasePay.calc_base.gather_tp;
+                        break;
+                    case 'bqht':
+                        cb.value = phasePay.calc_base.contract_tp;
+                        break;
+                    case 'bqbg':
+                        cb.value = phasePay.calc_base.qc_tp;
+                        break;
+                    case 'bqqdwc':
+                        cb.value = phasePay.calc_base.qd_gather_tp;
+                        break;
+                    case 'bqqdht':
+                        cb.value = phasePay.calc_base.qd_contract_tp;
+                        break;
+                    case 'bqqdbg':
+                        cb.value = phasePay.calc_base.qd_qc_tp;
+                        break;
+                    case 'ybbqwc':
+                        cb.value = phasePay.calc_base.gather_100_tp;
+                        break;
+                    case 'ybbqbg':
+                        cb.value = phasePay.calc_base.common_bg_tp;
+                        break;
+                    case 'jdbqbg':
+                        cb.value = phasePay.calc_base.more_bg_tp;
+                        break;
+                    case 'zdbqbg':
+                        cb.value = phasePay.calc_base.great_bg_tp;
+                        break;
+                    case 'bonus':
+                        cb.value = phasePay.calc_base.bonus_positive_tp;
+                        break;
+                    case 'fine':
+                        cb.value = phasePay.calc_base.bonus_negative_tp;
+                        break;
+                    case 'jgcl':
+                        cb.value = phasePay.calc_base.jgcl_tp;
+                        break;
+                    case 'aqsc':
+                        cb.value = phasePay.calc_base.safe_prod_tp;
+                        break;
+                    case 'lsyd':
+                        cb.value = phasePay.calc_base.temp_land_tp;
+                        break;
+                    default:
+                        cb.value = 0;
+                }
+            }
+            return payCalcBase;
         }
 
         async add(tid, relaStage, phaseDate, memo) {
             if (!tid) throw '数据错误';
             const user_id = this.ctx.session.sessionUser.accountId;
 
-            const maxOrder = await this.getNewOrder(tid);
+            const maxOrder = await this.getMaxOrder();
             const data = {
                 id: this.uuid.v4(), tid: tid, create_user_id: user_id, update_user_id: user_id,
                 phase_order: maxOrder + 1, phase_date: phaseDate, memo,
@@ -116,14 +263,15 @@ module.exports = app => {
             };
             if (await this._checkRelaStageConflict(relaStage, data)) throw '选择的计量期,已被调用,请刷新页面后选择计量期新增合同支付';
 
-            const calcBase = await this.getCalcBase(relaStage);
+            const prePhase = maxOrder > 0 ? await this.getPhasePayByOrder(tid, maxOrder) : null;
+            const calcBase = await this.getCalcBase(relaStage, prePhase);
             data.calc_base = JSON.stringify(calcBase);
             const transaction = await this.db.beginTransaction();
             try {
                 const result = await transaction.insert(this.tableName, data);
                 if (result.affectedRows !== 1) throw '新增合同支付失败';
 
-                await this.ctx.service.phasePayDetail.initPhaseData(data);
+                await this.ctx.service.phasePayDetail.initPhaseData(transaction, data);
                 await transaction.commit();
                 return data;
             } catch(err) {
@@ -132,33 +280,94 @@ module.exports = app => {
             }
         }
 
-        async refreshCalcBase(id) {
-            const curPay = this.getPhasePay(id);
-            if (!curPay) throw '合同支付不存在, 请刷新页面重试';
-
-            const calcBase = await this.getCalcBase(relaStage);
-            await this.defaultUpdate({
-                id, update_user_id: this.ctx.session.sessionUser.accountId,
-                calc_base: JSON.stringify(calcBase),
-                rela_stage: JSON.stringify(relaStage.map(s => { return {stage_id: s.id, stage_order: s.order}; })),
-                calc_base_time: new Date()
-            });
+        async refreshCalcBase(phasePay) {
+            const prePhase = phasePay.phase_order > 1 ? await this.getPhasePayByOrder(phasePay.tid, phasePay.phase_order - 1) : null;
+            const relaStage = await this.ctx.service.stage.getAllDataByCondition({ where: { tid: phasePay.tid, order: phasePay.rela_stage.map(x => { return x.stage_order; }) } });
+            const calcBase = await this.getCalcBase(relaStage, prePhase);
+            const conn = await this.db.beginTransaction();
+            try {
+                await conn.update(this.tableName, {
+                    id: phasePay.id, update_user_id: this.ctx.session.sessionUser.accountId,
+                    calc_base: JSON.stringify(calcBase),
+                    calc_base_time: new Date()
+                });
+                phasePay.calc_base = calcBase;
+                await this.ctx.service.phasePayDetail.calculateSave(phasePay, conn);
+                await conn.commit();
+            } catch(err) {
+                await conn.rollback();
+                throw err;
+            }
+        }
+        async resetRelaStageId(phasePay, relaStage) {
+            if (await this._checkRelaStageConflict(relaStage, phasePay)) throw '选择的计量期,已被调用,请刷新页面后选择计量期新增合同支付';
+            const prePhase = prePhase.phase_order > 1 ? await this.getPhasePayByOrder(phasePay.tid, phasePay.phase_order - 1) : null;
+            const calcBase = await this.getCalcBase(relaStage, prePhase);
+            const rela_stage = relaStage.map(s => { return {stage_id: s.id, stage_order: s.order}; });
+            const conn = await this.db.beginTransaction();
+            try {
+                await conn.update(this.tableName, {
+                    id: phasePay.id, update_user_id: this.ctx.session.sessionUser.accountId,
+                    calc_base: JSON.stringify(calcBase),
+                    rela_stage: JSON.stringify(rela_stage),
+                    calc_base_time: new Date()
+                });
+                phasePay.calc_base = calcBase;
+                phasePay.rela_stage = relaStage;
+                await this.ctx.service.phasePayDetail.calculateSave(phasePay, conn);
+                await conn.commit();
+            } catch(err) {
+                await conn.rollback();
+                throw err;
+            }
         }
 
-        async resetRelaStageId(id, relaStage) {
-            const curPay = this.getPhasePay(id);
-            if (!curPay) throw '合同支付不存在, 请刷新页面重试';
-
-            if (await this._checkRelaStageConflict(relaStage, curPay)) throw '选择的计量期,已被调用,请刷新页面后选择计量期新增合同支付';
-            const calcBase = await this.getCalcBase(relaStage);
-            await this.defaultUpdate({
-                id, update_user_id: this.ctx.session.sessionUser.accountId,
-                calc_base: JSON.stringify(calcBase),
-                rela_stage: JSON.stringify(relaStage.map(s => { return {stage_id: s.id, stage_order: s.order}; })),
-                calc_base_time: new Date()
-            });
+        async loadUser(phasePay) {
+            // todo 加载审批人
+            phasePay.user = await this.ctx.service.projectAccount.getAccountInfoById(phasePay.create_user_id);
+            phasePay.curAuditors = [];
+            // phasePay.curAuditors = await this.ctx.service.phasePayAudit.getAllDataByCondition({
+            //     where: { phase_id: phasePay.id, audit_times: phasePay.audit_times, audit_status: audit.status.checking }
+            // });
+            phasePay.curAuditorIds = phasePay.curAuditors.map(x => { return x.audit_id; });
+            phasePay.flowAuditors = phasePay.curAuditors.length === 0 ? []
+                : await this.ctx.service.phasePayAudit.getAllDataByCondition({
+                    where: { phase_id: phasePay.id, audit_times: phasePay.audit_times, audit_sort: phasePay.curAuditors[0].audit_sort }
+                });
+            phasePay.flowAuditorIds = phasePay.curAuditors.map(x => { return x.audit_id; });
         }
+        async doCheckPhase(phasePay) {
+            await this.loadUser(phasePay);
+            const accountId = this.ctx.session.sessionUser.accountId;
 
+            if (phasePay.audit_status === audit.status.uncheck) {
+                phasePay.readOnly = accountId !== phasePay.create_user_id;
+                phasePay.curTimes = phasePay.audit_times;
+                phasePay.curOrder = 0;
+            } else if (phasePay.audit_status === audit.status.checkNo) {
+                phasePay.readOnly = accountId !== phasePay.create_user_id;
+                if (!phasePay.readOnly) {
+                    phasePay.curTimes = phasePay.times;
+                    phasePay.curOrder = 0;
+                } else {
+                    const checkNoAudit = await this.service.phasePayAudit.getDataByCondition({
+                        phase_id: phasePay.id, audit_times: phasePay.audit_times - 1, status: audit.status.checkNo,
+                    });
+                    phasePay.curTimes = phasePay.times - 1;
+                    phasePay.curOrder = checkNoAudit.audit_order;
+                }
+            } else if (phasePay.audit_status === audit.status.checked) {
+                phasePay.readOnly = true;
+                phasePay.curTimes = phasePay.audit_times;
+                phasePay.curOrder = phasePay.audit_max_order;
+            } else {
+                // 会签,会签人部分审批通过时,只读,但是curOrder需按原来的取值
+                phasePay.curTimes = phasePay.audit_times;
+                phasePay.curOrder = phasePay.flowAuditorIds.indexOf(accountId) < 0 ? phasePay.curAuditors[0].order : phasePay.curAuditors[0].order - 1;
+                phasePay.readOnly = !_.isEqual(stage.flowAuditorIds, stage.curAuditorIds);
+                phasePay.canCheck = phasePay.readOnly && stage.curAuditorIds.indexOf(accountId) > 0;
+            }
+        }
     }
 
     return PhasePay;

+ 587 - 0
app/service/phase_pay_detail.js

@@ -0,0 +1,587 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date
+ * @version
+ */
+
+const payType = {
+    bqsf: 'bqsf', bqyf: 'bqyf', gcjl: 'gcjl', qtfk: 'qtfk', qtkk: 'qtkk'
+};
+const defaultPays = [
+    { tree_id: 1, tree_pid: -1, tree_level: 1, tree_order: 1, tree_is_leaf: 1, tree_full_path: '1', pay_type: 'bqyf', is_minus: 0, is_fixed: 1, name: '本期应付' },
+    { tree_id: 2, tree_pid: -1, tree_level: 1, tree_order: 2, tree_is_leaf: 1, tree_full_path: '2', pay_type: 'bqsf', is_minus: 0, is_fixed: 1, name: '本期实付' },
+    { tree_id: 3, tree_pid: -1, tree_level: 1, tree_order: 3, tree_is_leaf: 0, tree_full_path: '3', pay_type: 'gcjl', is_minus: 0, is_fixed: 1, name: '工程计量款' },
+    { tree_id: 4, tree_pid: -1, tree_level: 1, tree_order: 4, tree_is_leaf: 0, tree_full_path: '4', pay_type: 'qtfk', is_minus: 0, is_fixed: 1, name: '其他付款项' },
+    { tree_id: 5, tree_pid: -1, tree_level: 1, tree_order: 5, tree_is_leaf: 0, tree_full_path: '5', pay_type: 'qtkk', is_minus: 1, is_fixed: 1, name: '其他扣款项' },
+    { tree_id: 6, tree_pid: 3, tree_level: 2, tree_order: 1, tree_is_leaf: 1, tree_full_path: '3-6', pay_type: '', is_minus: 0, is_fixed: 0, name: '本期完成计量', expr: 'bqwc' },
+    { tree_id: 7, tree_pid: 4, tree_level: 2, tree_order: 1, tree_is_leaf: 1, tree_full_path: '4-7', pay_type: '', is_minus: 0, is_fixed: 0, name: '新增付款项' },
+    { tree_id: 8, tree_pid: 5, tree_level: 2, tree_order: 1, tree_is_leaf: 1, tree_full_path: '5-8', pay_type: '', is_minus: 1, is_fixed: 0, name: '新增扣款项' },
+];
+const TreeService = require('../base/base_tree_service');
+const Ledger = require('../lib/ledger');
+const deadlineType = {
+    phaseCount: { key: 'phaseCount', type: '计量期数', name: '合同支付期数' },
+    stageCount: { key: 'stageCount', type: '计量期数', name: '计量期数' },
+    gather: { key: 'gather', type: '计量金额', name: '累计完成金额' },
+    contract: { key: 'contract', type: '计量金额', name: '累计合同金额' },
+    qc: { key: 'qc', type: '计量金额', name: '累计变更金额' },
+};
+const math = require('mathjs');
+math.config({
+    number: 'BigNumber',
+});
+const validField = ['name', 'is_pause', 'is_gather', 'start_tp', 'start_expr', 'range_tp', 'range_expr', 'tp', 'expr', 'postil', 'dl_type', 'dl_value'];
+const infoField = ['name', 'postil'];
+const calcField = validField.filter(x => { return infoField.indexOf(x) < 0; });
+
+class PayCalculator {
+    constructor (ctx, phasePay) {
+        this.ctx = ctx;
+        this.phasePay = phasePay;
+        this.percentReg = /((\d+)|((\d+)(\.\d+)))%/g;
+        this.tenderInfo = ctx.tender.info;
+        this.decimal = this.tenderInfo.decimal.pay ? this.tenderInfo.decimal.payTp : this.tenderInfo.decimal.tp;
+        this.orderReg = /f\d+/ig;
+        this.nodeReg = /<<[a-z0-9\-]+>>/ig;
+        /* 以下变量在调用calculate方法后获得
+        this.add;
+        this.pre;
+        this.cur;
+        this.yf;
+         */
+    }
+
+    /**
+     * 获取 计算基数
+     * @returns {Promise<void>}
+     */
+    getCalcBase () {
+        if (this.bases) return;
+
+        const bases = this.ctx.service.phasePay.getPhasePayCalcBase(this.phasePay, this.tenderInfo);
+        this.bases = bases.sort(function (a, b) {
+            return a.sort - b.sort;
+        });
+        for (const b of this.bases) {
+            b.reg = new RegExp(b.code, 'igm');
+        }
+    }
+    getCalcAdd() {
+        if (this.addBase) return;
+
+        const calc_base = this.phasePay.calc_base;
+        this.addBase = {
+            contract_tp: this.ctx.helper.add(calc_base.contract_tp, calc_base.pre_contract_tp),
+            qc_tp: this.ctx.helper.add(calc_base.qc_tp, calc_base.pre_qc_tp),
+            gather_tp: this.ctx.helper.add(calc_base.gather_tp, calc_base.pre_gather_tp),
+        };
+    }
+
+    _calculateTpExpr(pay, pays) {
+        let formula = pay.expr;
+        const nodeParam = pay.expr.match(this.nodeReg);
+        if (nodeParam) {
+            for (const op of nodeParam) {
+                const id = op.substring(2, op.length - 2);
+                const payNode = pays.find(x => { return x.uuid === id; });
+                formula = formula.replace(op, payNode ? payNode.tp || 0 : 0);
+            }
+        }
+        for (const b of this.bases) {
+            const value = b.checkStart && (!pay.pre_used && pay.start_tp)
+                ? this.ctx.helper.sub(this.addBase[pay.checkStart], pay.start_tp)
+                : b.value;
+            formula = formula.replace(b.reg, value);
+        }
+        const percent = formula.match(this.percentReg);
+        if (percent) {
+            for (const p of percent) {
+                const v = math.eval(p.replace(new RegExp('%', 'gm'), '/100'));
+                formula = formula.replace(p, v);
+            }
+        }
+        try {
+            // 使用mathjs计算 math.eval('17259401.95*0.9') = 15533461.754999999,正确应为 15533461.755
+            // const value = math.eval(formula);
+            // 使用逆波兰法四则运算,可防止出现误差,但是只支持四则运算,不支持科学运算
+            // const value = this.ctx.helper.calcExprStrRpn(formula);
+            // 使用mathjs的大数运算,可支持所有
+            const value = parseFloat(math.eval(formula));
+            return Number.isFinite(value) ? value : 0;
+        } catch(err) {
+            return 0;
+        }
+    }
+    _calculateExpr(expr) {
+        let formula = expr;
+        for (const b of this.bases) {
+            formula = formula.replace(b.reg, b.value ? b.value : 0);
+        }
+        const percent = formula.match(this.percentReg);
+        if (percent) {
+            for (const p of percent) {
+                const v = math.eval(p.replace(new RegExp('%', 'gm'), '/100'));
+                formula = formula.replace(p, v);
+            }
+        }
+        try {
+            // 使用mathjs计算 math.eval('17259401.95*0.9') = 15533461.754999999,正确应为 15533461.755
+            // const value = math.eval(formula);
+            // 使用逆波兰法四则运算,可防止出现误差,但是只支持四则运算,不支持科学运算
+            // const value = this.ctx.helper.calcExprStrRpn(formula);
+            // 使用mathjs的大数运算,可支持所有
+            const value = parseFloat(math.eval(formula));
+            return Number.isFinite(value) ? value : 0;
+        } catch(err) {
+            return 0;
+        }
+    }
+    /**
+     * 计算起扣金额、付(扣)款限额
+     *
+     * @param {Array} pays - (标段)合同支付数据
+     */
+    calculateStartRangePrice (payTree) {
+        for (const p of payTree.nodes) {
+            // 上一期已计量的合同支付项,不予计算起扣金额、扣款限额
+            if (p.pre_used) continue;
+
+            if (p.start_expr) {
+                p.start_tp = this.ctx.helper.round(this._calculateExpr(p.start_expr), this.decimal);
+            } else if (p.start_tp && !p.start_expr) {
+                p.start_tp = this.ctx.helper.round(p.start_tp, this.decimal);
+            }
+            if (p.range_expr) {
+                p.range_price = this.ctx.helper.round(this._calculateExpr(p.range_expr), this.decimal);
+            } else if (p.range_price && !p.range_expr) {
+                p.range_price = this.ctx.helper.round(p.range_price, this.decimal);
+            }
+        }
+    }
+    /**
+     * 检查是否到达 计提期限
+     * @param pay
+     */
+    _checkDeadline(pay) {
+        switch (pay.dl_type) {
+            case deadlineType.phaseCount.key:
+                return this.phasePay.phase_order >= pay.dl_value;
+            case deadlineType.stageCount.key:
+                const maxStageOrder = this.ctx.helper._.max(this.phasePay.rela_stage.map(x => { return x.stage_order; }));
+                return maxStageOrder >= pay.dl_value;
+            case deadlineType.gather.key:
+            case deadlineType.contract.key:
+            case deadlineType.qc.key:
+                const deadlineTp = this.addBase[pay.dl_type + '_tp'];
+                return deadlineTp >= pay.dl_value;
+            default :
+                return false;
+        }
+    }
+
+    getLeafOrder(data, pays, parentId) {
+        if (!data || !data.expr) return [];
+        const nodeParam = data.expr.match(this.nodeReg);
+        if (!nodeParam || nodeParam.length === 0) return [];
+
+        const result = [...nodeParam];
+        for (const op of nodeParam) {
+            const id = op.substring(2, op.length - 2);
+            if (id === data.uuid || id === parentId) {
+                result.push(op);
+            } else {
+                const subPay = pays.find(x => { return x.uuid === id; });
+                const sub = this.getLeafOrder(subPay, pays, data.uuid);
+                if (sub.length > 0) {
+                    result.push(...sub);
+                } else {
+                    result.push(op);
+                }
+            }
+        }
+        return this.ctx.helper._.uniq(result);
+    }
+    sortPaysByCalc(payTree) {
+        const calcArr = [];
+        for (const pay of payTree.nodes) {
+            if (pay.pay_type === payType.bqyf) {
+                pay.calcSort = 3;
+            } else if (pay.pay_type === payType.bqsf) {
+                pay.calcSort = 4;
+            } else if (pay.children && pay.children.length > 0) {
+                pay.calcSort = 2;
+            } else {
+                pay.calcSort = 1;
+                pay.calcLeaf = this.getLeafOrder(pay, payTree.nodes);
+            }
+            calcArr.push(pay);
+        }
+        calcArr.sort((x, y) => {
+            const calcSort = x.calcSort - y.calcSort;
+            if (calcSort) return calcSort;
+            if (x.calcSort === 2) return -(x.tree_level - y.tree_level);
+            return x.calcLeaf.length - y.calcLeaf.length;
+        });
+        return calcArr;
+    }
+
+    _calculateYf(yf, pays) {
+        yf.tp = 0;
+        for (const p of pays) {
+            if (p.payType || !p.is_gather || !p.tree_is_leaf) continue;
+            yf.tp = !p.is_minus ? this.ctx.helper.add(yf.tp, p.tp) : this.ctx.helper.sub(yf.tp, p.tp);
+        }
+        const bqyf = this.bases.find(function (x) {return x.code === 'bqyf'});
+        if (bqyf) bqyf.value = yf.tp;
+    }
+    _calculateSf(sf, pays) {
+        if (sf.expr) {
+            sf.tp = this.ctx.helper.round(this._calculateExpr(sf, pays), this.decimal);
+        } else {
+            const yf = pays.find(x => { return x.pay_type === payType.bqyf; });
+            sf.tp = yf.tp;
+        }
+        sf.tp = sf.range_tp ? Math.min(this.ctx.helper.sub(sf.range_tp, sf.pre_tp), sf.tp) : sf.tp;
+    }
+    _calculateGather(pay) {
+        pay.tp = 0;
+        for (const c of pay.children) {
+            if (c.children && c.children.length > 0) this._calculateGather(c);
+            if (!c.is_gather) continue;
+            pay.tp = this.ctx.helper.add(pay.tp, c.tp);
+        }
+    }
+    _calculateCommon(pay, pays) {
+        // 暂停计量|未达起扣金额时,不计算
+        if (pay.is_pause || (pay.start_tp && this.addBase.gather_tp < pay.start_tp)) {
+            pay.tp = 0;
+            return;
+        }
+        if (this._checkDeadline(pay)) {
+           pay.tp = this.ctx.helper.sub(pay.range_tp, pay.pre_tp);
+        } else if (pay.expr) {
+            pay.tp = this.ctx.helper.round(this._calculateTpExpr(pay, pays), this.decimal);
+        } else {
+            pay.tp = this.ctx.helper.round(pay.tp || 0, this.decimal);
+        }
+        pay.tp = pay.range_tp ? Math.min(this.ctx.helper.sub(pay.range_tp, pay.pre_tp), pay.tp) : pay.tp;
+    }
+    /**
+     * 计算本期、截止本期金额
+     * @param {Array} pays - (标段&期)合同支付数据
+     */
+    calculate(pays) {
+        for (const p of pays) {
+            switch (p.pay_type) {
+                case payType.bqyf:
+                    this._calculateYf(p, pays); break;
+                case payType.bqsf:
+                    this._calculateSf(p, pays); break;
+                case payType.gcjl:
+                case payType.qtfk:
+                case payType.qtkk:
+                    this._calculateGather(p); break;
+                default:
+                    this._calculateCommon(p, pays); break;
+            }
+            p.end_tp = this.ctx.helper.add(p.pre_tp, p.tp);
+        }
+    }
+
+    calculateAll(payTree) {
+        payTree.nodes.forEach(x => {
+            x.org_tp = x.tp || 0;
+            x.org_start_tp = x.start_tp || 0;
+            x.org_range_tp = x.range_tp || 0;
+        });
+        this.getCalcBase();
+        this.getCalcAdd();
+        this.calculateStartRangePrice(payTree);
+        const calcPays = this.sortPaysByCalc(payTree);
+        this.calculate(calcPays);
+        payTree.nodes.forEach(p => {
+            p.calcUpdate = !this.ctx.helper.numEqual(p.org_tp, p.tp) || !this.ctx.helper.numEqual(p.org_start_tp, p.start_tp) || !this.ctx.helper.numEqual(p.org_range_tp, p.range_tp);
+        });
+    }
+}
+
+class PhasePayDetail extends TreeService {
+
+    /**
+     * 构造函数
+     *
+     * @param {Object} ctx - egg全局变量
+     * @param {String} tableName - 表名
+     * @return {void}
+     */
+    constructor(ctx, setting) {
+        super(ctx, {
+            mid: 'master_id',
+            kid: 'tree_id',
+            pid: 'tree_pid',
+            order: 'tree_order',
+            level: 'tree_level',
+            isLeaf: 'tree_is_leaf',
+            fullPath: 'tree_full_path',
+            keyPre: '',
+            uuid: true,
+        });
+        this.tableName = 'phase_pay_detail';
+        this.deadlineType = deadlineType;
+    }
+
+    getMasterKey(phasePay) {
+        return phasePay.curTimes
+            ? `${phasePay.id}-${phasePay.curTimes}-${phasePay.curOrder}`
+            : `${phasePay.id}-${phasePay.audit_times}-${phasePay.audit_max_sort}`;
+    }
+
+    async initPhaseDataEmpty(conn, phasePay) {
+        const user_id = this.ctx.session.sessionUser.accountId;
+        const insertData = [];
+        for (const dp of defaultPays) {
+            insertData.push({
+                tid: phasePay.tid, phase_id: phasePay.id, create_user_id: user_id, update_user_id: user_id,
+                uuid: this.uuid.v4(), ...dp,
+            });
+        }
+        await conn.insert(this.tableName, insertData);
+    }
+
+    async initPhaseDataByPre(conn, phasePay, prePhase) {
+        const preData = await this.getAllDataByCondition({
+            where: { phase_id: prePhase.id, audit_times: prePhase.audit_times, audit_sort: audit_max_sort },
+        });
+        for (const pd of preData) {
+            delete pd.id;
+            pd.audit_times = 1;
+            pd.audit_sort = 0;
+        }
+        await conn.insert(this.tableName, preData);
+    }
+
+    async initPhaseDataByAudit(conn, phasePay, newTimes, newSort) {
+        const preData = await this.getDetailData(phasePay);
+        for (const pd of preData) {
+            delete pd.id;
+            pd.master_id = `${phasePay.id}-${newTimes}-${newSort}`;
+            pd.audit_times = newTimes;
+            pd.audit_sort = newSort;
+        }
+        await conn.insert(this.tableName, preData);
+    }
+
+    async initPhaseData(conn, phasePay){
+        if (!conn) throw '内部错误';
+        const prePhase = await this.ctx.service.phasePay.getPhasePayByOrder(phasePay.tid, phasePay.phase_order - 1);
+        if (prePhase) {
+            await this.initPhaseDataByPre(conn, phasePay, prePhase);
+        } else {
+            await this.initPhaseDataEmpty(conn, phasePay);
+        }
+    }
+
+    async getDetailData(phasePay) {
+        return await this.getAllDataByCondition({ where: { master_id: this.getMasterKey(phasePay)} });
+    }
+    calculate(phasePay, details) {
+        const payTree = new Ledger.baseTree(this.ctx, {
+            id: 'tree_id', pid: 'tree_pid', order: 'tree_order',
+            level: 'tree_level', isLeaf: 'tree_is_leaf', fullPath: 'tree_full_path',
+            rootId: -1, calcField: [],
+        });
+        payTree.loadDatas(details);
+        const payCalculator = new PayCalculator(this.ctx, phasePay);
+        payCalculator.calculateAll(payTree);
+        return payTree.getDefaultDatas();
+    }
+
+    async calculateSave(phasePay, transaction) {
+        const details = await this.getDetailData(phasePay);
+        if (details.length === 0) return false;
+
+        const calcResult = this.calculate(phasePay, details);
+        const updateData = calcResult.filter(x => { return x.calcUpdate; }).map(x => {
+            return { id: x.id, tp: x.tp, start_tp: x.start_tp, range_tp: x.range_tp, end_tp: x.end_tp };
+        });
+        if (updateData.length === 0) return false;
+
+        if (transaction) {
+            await transaction.updateRows(this.tableName, updateData);
+        } else {
+            await this.defaultUpdateRows(updateData);
+        }
+        return true;
+    }
+
+    _getDefaultData(data, phasePay, parent) {
+        data.uuid = this.uuid.v4();
+        data.tid = phasePay.tid;
+        data.phase_id = phasePay.id;
+        data.create_user_id = this.ctx.session.sessionUser.accountId;
+        data.update_user_id = this.ctx.session.sessionUser.accountId;
+        data.audit_times = phasePay.audit_times;
+        data.audit_sort = phasePay.audit_max_sort;
+        data.master_id = this.getMasterKey(phasePay);
+        if (parent) {
+            data.is_minus = parent.is_minus;
+        }
+    }
+
+    async addChild(phasePay, select, count = 1) {
+        if (select.payType === payType.bqsf || select.payType === payType.bqyf) throw '不可新增子项';
+        if (select.tree_level >= 2) throw '不可新增子项';
+
+        const masterId = this.getMasterKey(phasePay);
+        const children = await this.getChildrenByParentId(masterId, select[this.setting.kid]);
+        const maxId = await this._getMaxLid(masterId);
+        const insertData = [];
+        for (let i = 1; i <= count ; i++) {
+            const data = {
+                tree_id: maxId + i, tree_pid: select.tree_id, tree_order: children.length + 1 + i,
+                tree_level: select.tree_level + 1, tree_is_leaf: 1
+            };
+            data.tree_full_path = select.tree_full_path + '-' + data.tree_id;
+            this._getDefaultData(data, phasePay, select);
+            insertData.push(data);
+        }
+
+        const conn = await this.db.beginTransaction();
+        try {
+            const result = await conn.insert(this.tableName, insertData);
+            if (children.length === 0) {
+                await conn.update(this.tableName, { id: select.id, is_leaf: false });
+            }
+            await conn.commit();
+        } catch(err) {
+            conn.rollback();
+            throw err;
+        }
+        this._cacheMaxLid(masterId, maxId + 1);
+
+        // 查询应返回的结果
+        const resultData = {};
+        resultData.create = await this.getNextsData(masterId, select.tree_id, children.length);
+        if (children.length === 0) resultData.update = await this.getDataByKid(masterId, select.tree_id);
+        return resultData;
+    }
+
+    async addNext(phasePay, select, count = 1) {
+        const masterId = this.getMasterKey(phasePay);
+        this.transaction = await this.db.beginTransaction();
+        try {
+            if (select) await this._updateChildrenOrder(masterId, select[this.setting.pid], select[this.setting.order] + 1, count);
+            const newDatas = [];
+            const maxId = await this._getMaxLid(masterId);
+            for (let i = 1; i < count + 1; i++) {
+                const newData = {};
+                newData[this.setting.kid] = maxId + i;
+                newData[this.setting.pid] = select ? select[this.setting.pid] : this.rootId;
+                newData[this.setting.level] = select ? select[this.setting.level] : 1;
+                newData[this.setting.order] = select ? select[this.setting.order] + i : i;
+                newData[this.setting.fullPath] = newData[this.setting.level] > 1
+                    ? select[this.setting.fullPath].replace('-' + select[this.setting.kid], '-' + newData[this.setting.kid])
+                    : newData[this.setting.kid] + '';
+                newData[this.setting.isLeaf] = 1;
+                this._getDefaultData(newData, phasePay);
+                newDatas.push(newData);
+            }
+            const insertResult = await this.transaction.insert(this.tableName, newDatas);
+            this._cacheMaxLid(masterId, maxId + count);
+
+            if (insertResult.affectedRows !== count) throw '新增节点数据错误';
+            await this.transaction.commit();
+            this.transaction = null;
+        } catch (err) {
+            await this.transaction.rollback();
+            this.transaction = null;
+            throw err;
+        }
+
+        if (select) {
+            const createData = await this.getChildBetween(masterId, select[this.setting.pid], select[this.setting.order], select[this.setting.order] + count + 1);
+            const updateData = await this.getNextsData(masterId, select[this.setting.pid], select[this.setting.order] + count);
+            return {create: createData, update: updateData};
+        } else {
+            const createData = await this.getChildBetween(masterId, -1, 0, count + 1);
+            return {create: createData};
+        }
+    }
+
+    async addDetailNode(phasePay, targetId, count = 1) {
+        if (!phasePay) return null;
+
+        const select = targetId ? await this.getDataByKid(this.getMasterKey(phasePay), targetId) : null;
+        if (targetId && !select) throw '新增节点数据错误';
+
+        if (select[this.setting.level] === 1) {
+            return await this.addChild(phasePay, select, count);
+        } else {
+            return await this.addNext(phasePay, select, count);
+        }
+    }
+
+    async upMoveDetailNode(phasePay, targetId, count = 1) {
+        const masterId = this.getMasterKey(phasePay);
+        return await this.upMoveNode(masterId, targetId, count);
+    }
+
+    async downMoveDetailNode(phasePay, targetId, count = 1) {
+        const masterId = this.getMasterKey(phasePay);
+        return await this.downMoveNode(masterId, targetId, count);
+    }
+
+    async deleteDetailNode(phasePay, targetId, count = 1) {
+        const masterId = this.getMasterKey(phasePay);
+        return await this.delete(masterId, targetId, count);
+    }
+
+    _filterValidField(id, data) {
+        const ud = { id };
+        for (const prop in data) {
+            if (validField.indexOf(prop) >= 0) {
+                ud[prop] = data[prop];
+            }
+        }
+        return ud;
+    }
+
+    checkCalc(data) {
+        const datas = data instanceof Array ? data : [data];
+        for (const d of datas) {
+            for (const prop in d) {
+                if (calcField.indexOf(prop) >= 0) return true;
+            }
+        }
+        return false;
+    }
+
+    async updateDetail(phasePay, data) {
+        const masterId = this.getMasterKey(phasePay);
+        if (Array.isArray(data)) {
+            const orgData = await this.getAllDataByCondition({ where: { id: data.map(d => { return d.id; }) } });
+            const updateDatas = [];
+            for (const d of data) {
+                const node = orgData.find(x => { return x.id === d.id; });
+                if (!node || masterId !== node[this.setting.mid]) throw '提交数据错误';
+                const ud = this._filterValidField(node.id, d);
+                ud.update_user_id = this.ctx.session.sessionUser.accountId;
+                updateDatas.push(ud);
+            }
+            if (updateDatas.length > 0) await this.db.updateRows(this.tableName, updateDatas);
+        } else {
+            const node = await this.getDataById(data.id);
+            if (!node || masterId !== node[this.setting.mid]) throw '提交数据错误';
+            const updateData = this._filterValidField(node.id, data);
+            updateData.update_user_id = this.ctx.session.sessionUser.accountId;
+            await this.db.update(this.tableName, updateData);
+        }
+    }
+}
+
+module.exports = PhasePayDetail;
+

+ 5 - 3
app/service/report_memory.js

@@ -652,14 +652,15 @@ module.exports = app => {
             if (!this.ctx.stage) throw '期数据错误,请重试';
             if (this.stageValidRole && this.stageValidRole.length > 0) return;
 
-            const result = [{dataOrder: 0, flowOrder: 0, uid: this.ctx.stage.user_id}];
+            const result = [{dataOrder: 0, flowOrder: 0, uid: this.ctx.stage.user_id, audit_type: 0}];
             for (const auditorGroup of this.ctx.stage.auditorGroups) {
                 const hasChecked = auditorGroup.find(x => { return x.status === audit.stage.status.checked });
                 const hasOtherStatus = auditorGroup.find(x => { return x.status === audit.stage.status.checkNo || x.status === audit.stage.status.checkNoPre || x.status === audit.stage.status.checkAgain || x.status === audit.stage.status.checkCancel });
                 const isCur = auditorGroup.find(x => { return x.status === audit.stage.status.checking}) && (!this.ctx.stage.readOnly || this.ctx.stage.canCheck);
                 if ((hasChecked && !hasOtherStatus) || isCur) {
+                    const uid = auditorGroup.map(x => { return x.aid }).sort((x, y) => { return x - y; });
                     const role = result.find(function (r) {
-                        return r.uid === auditorGroup.map(x => { return x.aid }).join(',');
+                        return r.uid === uid.join(',') && r.audit_type === auditorGroup[0].audit_type;
                     });
                     if (role) {
                         role.dataOrder = auditorGroup[0].order;
@@ -667,7 +668,8 @@ module.exports = app => {
                         result.push({
                             dataOrder: auditorGroup[0].order,
                             flowOrder: auditorGroup[0].audit_order,
-                            uid: auditorGroup.map(x => { return x.aid }).join(',')
+                            uid: uid.join(','),
+                            audit_type: auditorGroup.audit_type,
                         })
                     }
                 }

+ 6 - 2
app/service/stage_audit.js

@@ -1811,7 +1811,7 @@ module.exports = app => {
                     cur = await this.db.queryOne(`SELECT * From ${this.tableName} where sid = ? AND times = ? AND status = ? ORDER By times DESC, ` + '`order` DESC', [stageId, times, status]);
                     if (!cur) return [];
 
-                    sql = 'SELECT la.`aid`, pa.`name`, pa.`company`, pa.`role`, la.`times`, la.`sid`, la.`order`, la.audit_order, la.audit_type, la.audit_ledger_id ' +
+                    sql = 'SELECT la.`aid`, pa.`name`, pa.`company`, pa.`role`, la.`times`, la.`sid`, la.`order`, la.`status`, la.audit_order, la.audit_type, la.audit_ledger_id ' +
                         '  FROM ?? AS la Left Join ?? AS pa On la.`aid` = pa.`id` ' +
                         '  WHERE la.`sid` = ? and la.`order` = ? and times = ?';
                     sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, stageId, cur.order, times];
@@ -1821,7 +1821,7 @@ module.exports = app => {
                     cur = await this.db.queryOne(`SELECT * From ${this.tableName} where sid = ? AND times = ? AND status = ? ORDER By times DESC, ` + '`order` DESC', [stageId, parseInt(times) - 1, status]);
                     if (!cur) return [];
 
-                    sql = 'SELECT la.`aid`, pa.`name`, pa.`company`, pa.`role`, la.`times`, la.`sid`, la.`order`, la.audit_order, la.audit_type, la.audit_ledger_id ' +
+                    sql = 'SELECT la.`aid`, pa.`name`, pa.`company`, pa.`role`, la.`times`, la.`sid`, la.`order`, la.`status`, la.audit_order, la.audit_type, la.audit_ledger_id ' +
                         '  FROM ?? AS la Left Join ?? AS pa On la.`aid` = pa.`id` ' +
                         '  WHERE la.`sid` = ? and la.`order` = ? and la.`times` = ?';
                     sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, stageId, cur.order, parseInt(times) - 1];
@@ -2363,6 +2363,10 @@ module.exports = app => {
         async getLockedId(stage) {
             return await this.getAllDataByCondition({ where: { sid: stage.id, times: stage.times, audit_locked: 1 } });
         }
+
+        async getComfirmLockedId(stage) {
+            return await this.getAllDataByCondition({ where: { sid: stage.id, times: stage.times, order: stage.curOrder, status: auditConst.status.checked } });
+        }
     }
 
     return StageAudit;

+ 8 - 0
app/service/stage_audit_ass.js

@@ -232,6 +232,14 @@ module.exports = app => {
             return await this.getAllDataByCondition({ where: { sid: stage.id, times: stage.times, locked: 1 } });
         }
 
+        async getComfirmLockedId(stage) {
+            if (stage.curAuditors && stage.curAuditors.length > 0) {
+                return await this.getAllDataByCondition({ where: { sid: stage.id, times: stage.times, user_id: stage.curAuditors.map(x => { return x.aid; }), confirm: 1}});
+            } else {
+                return await this.getAllDataByCondition({ where: { sid: stage.id, times: stage.times, user_id: stage.user_id, confirm: 1}});
+            }
+        }
+
         async getReportData(sid, stimes) {
             const sql = 'SELECT saa.tid, saa.sid, saa.times, saa.user_id, saa.ass_ledger_id, saa.ass_user_id, saa.name, saa.company, saa.role, pa.sign_path, pa.stamp_path' +
                 `   FROM ${this.tableName} saa LEFT JOIN ${this.ctx.service.projectAccount.tableName} pa ON saa.ass_user_id = pa.id` +

+ 8 - 0
app/service/stage_bonus.js

@@ -227,6 +227,14 @@ module.exports = app => {
             }
             await transaction.updateRows(this.tableName, updateDatas);
         }
+
+        async getSumTp(stage) {
+            const stages = stage instanceof Array ? stage : [stage];
+            const condition = {};
+            condition.sid = stage.length === 1 ? stage[0].id : stages.map(x => { return x.id; }).join(',');
+            const sql = `SELECT SUM(IF(tp > 0, tp, 0)) AS positive_tp, SUM(IF(tp < 0, tp, 0)) AS negative_tp, SUM(tp) AS sum_tp From ${this.tableName} ` + this.ctx.helper.whereSql(condition);
+            return await this.db.queryOne(sql);
+        }
     }
 
     return StageBonus;

+ 8 - 0
app/service/stage_jgcl.js

@@ -280,6 +280,14 @@ module.exports = app => {
             }
             await transaction.updateRows(this.tableName, updateDatas);
         }
+
+        async getSumTp(stage) {
+            const stages = stage instanceof Array ? stage : [stage];
+            const condition = {};
+            condition.sid = stage.length === 1 ? stage[0].id : stages.map(x => { return x.id; }).join(',');
+            const sql = `SELECT SUM(arrive_tp) AS arrive_tp, SUM(deduct_tp) AS deduct_tp FROM ${this.tableName} ` + this.ctx.helper.whereSql(condition);
+            return await this.db.queryOne(sql);
+        }
     }
 
     return StageJgcl;

+ 8 - 0
app/service/stage_other.js

@@ -242,6 +242,14 @@ module.exports = app => {
             }
             await transaction.updateRows(this.tableName, updateDatas);
         }
+
+        async getSumTp(stage) {
+            const stages = stage instanceof Array ? stage : [stage];
+            const condition = {};
+            condition.sid = stage.length === 1 ? stage[0].id : stages.map(x => { return x.id; }).join(',');
+            const sql = `SELECT SUM(tp) AS sum_tp FROM ${this.tableName} ` + this.ctx.helper.whereSql(condition);
+            return await this.db.queryOne(sql);
+        }
     }
 
     return StageOther;

+ 8 - 0
app/service/stage_safe_prod.js

@@ -277,6 +277,14 @@ module.exports = app => {
             }
             await transaction.updateRows(this.tableName, updateDatas);
         }
+
+        async getSumTp(stage) {
+            const stages = stage instanceof Array ? stage : [stage];
+            const condition = {};
+            condition.sid = stage.length === 1 ? stage[0].id : stages.map(x => { return x.id; }).join(',');
+            const sql = `SELECT SUM(tp) AS sum_tp FROM ${this.tableName} ` + this.ctx.helper.whereSql(condition);
+            return await this.db.queryOne(sql);
+        }
     }
 
     return StageSafeProd;

+ 8 - 0
app/service/stage_temp_land.js

@@ -246,6 +246,14 @@ module.exports = app => {
             }
             await transaction.updateRows(this.tableName, updateDatas);
         }
+
+        async getSumTp(stage) {
+            const stages = stage instanceof Array ? stage : [stage];
+            const condition = {};
+            condition.sid = stage.length === 1 ? stage[0].id : stages.map(x => { return x.id; }).join(',');
+            const sql = `SELECT SUM(tp) AS sum_tp FROM ${this.tableName} ` + this.ctx.helper.whereSql(condition);
+            return await this.db.queryOne(sql);
+        }
     }
 
     return StageTempLand;

+ 117 - 88
app/view/change/information.ejs

@@ -390,87 +390,129 @@
             <div class="c-body" id="right-view" style="width: 66.67%">
                 <div class="resize-x" id="right-spr" r-Type="width" div1="#left-view" div2="#right-view" title="调整大小" a-type="percent"><!--调整左右高度条--></div>
                 <!--上半部分-->
-                <div class="sjs-height-1 row w-100 sub-content">
-                    <div class="c-body" style="width: 100%">
-                        <div class="m-1" id="list-tab">
-                            <% if (!change.readOnly) { %>
-                                <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>-->
-                                    <a href="javascript:void(0);" id="set-site-btn" class="btn btn-sm btn-light text-primary" 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 class="w-100 sub-content">
+                    <div class="sjs-height-1">
+                        <div class="c-body" style="width: 100%">
+                            <div class="m-1" id="list-tab">
+                                <% if (!change.readOnly) { %>
+                                    <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>-->
+                                        <a href="javascript:void(0);" id="set-site-btn" class="btn btn-sm btn-light text-primary" 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 judge-hide">
+                                            <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-2 judge-hide">
+                                        <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">
+                                                <li class="mb-2">
+                                                    <div class="custom-control custom-radio">
+                                                        <input type="radio" class="custom-control-input" name="paixu" id="order_0" value="0" <% if (change.order_by === 0) { %>checked<% } %>>
+                                                        <label class="custom-control-label" for="order_0">清单编号</label>
+                                                    </div>
+                                                </li>
+                                                <li class="mb-2">
+                                                    <div class="custom-control custom-radio">
+                                                        <input type="radio" class="custom-control-input" name="paixu" id="order_1" value="1" <% if (change.order_by === 1) { %>checked<% } %>>
+                                                        <label class="custom-control-label" for="order_1">添加顺序</label>
+                                                    </div>
+                                                </li>
+                                            </ul>
+                                        </div>
+                                    </div>
+                                    <div class="d-inline-block mr-2 judge-hide" id="upAndMoveBtn" <% if (change.order_by === 0) { %>style="display: none!important;" <% } %>>
+                                        <a href="javascript:void(0)" id="up-move" class="btn btn-sm btn-light text-primary" data-toggle="tooltip" data-placement="bottom" title="" data-original-title="上移"><i class="fa fa-arrow-up" aria-hidden="true"></i></a>
+                                        <a href="javascript:void(0)" id="down-move" class="btn btn-sm btn-light text-primary" data-toggle="tooltip" data-placement="bottom" title="" data-original-title="下移"><i class="fa fa-arrow-down" aria-hidden="true"></i></a>
                                     </div>
                                 <% } %>
                                 <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">
-                                            <li class="mb-2">
-                                                <div class="custom-control custom-radio">
-                                                    <input type="radio" class="custom-control-input" name="paixu" id="order_0" value="0" <% if (change.order_by === 0) { %>checked<% } %>>
-                                                    <label class="custom-control-label" for="order_0">清单编号</label>
-                                                </div>
-                                            </li>
-                                            <li class="mb-2">
-                                                <div class="custom-control custom-radio">
-                                                    <input type="radio" class="custom-control-input" name="paixu" id="order_1" value="1" <% if (change.order_by === 1) { %>checked<% } %>>
-                                                    <label class="custom-control-label" for="order_1">添加顺序</label>
-                                                </div>
-                                            </li>
-                                        </ul>
+                                    <div class="custom-control custom-checkbox">
+                                        <input type="checkbox" class="custom-control-input change-detail-checkbox" id="customCheck1">
+                                        <label class="custom-control-label" for="customCheck1">变更详情</label>
                                     </div>
                                 </div>
-                                <div class="d-inline-block mr-2" id="upAndMoveBtn" <% if (change.order_by === 0) { %>style="display: none!important;" <% } %>>
-                                    <a href="javascript:void(0)" id="up-move" class="btn btn-sm btn-light text-primary" data-toggle="tooltip" data-placement="bottom" title="" data-original-title="上移"><i class="fa fa-arrow-up" aria-hidden="true"></i></a>
-                                    <a href="javascript:void(0)" id="down-move" class="btn btn-sm btn-light text-primary" data-toggle="tooltip" data-placement="bottom" title="" data-original-title="下移"><i class="fa fa-arrow-down" aria-hidden="true"></i></a>
-                                </div>
-                            <% } %>
-                            <div class="d-inline-block mr-2">
-                                <div class="custom-control custom-checkbox">
-                                    <input type="checkbox" class="custom-control-input change-detail-checkbox" id="customCheck1">
-                                    <label class="custom-control-label" for="customCheck1">变更详情</label>
-                                </div>
-                            </div>
-                            <% if (change.readOnly && !change.shenpiPower) { %>
-                                <div class="d-inline-block mr-2">
-                                    <div class="custom-control custom-checkbox">
-                                        <input type="checkbox" class="custom-control-input" id="show-table-detail">
-                                        <label class="custom-control-label" for="show-table-detail">审批过程</label>
+                                <% if (change.readOnly && !change.shenpiPower) { %>
+                                    <div class="d-inline-block mr-2">
+                                        <div class="custom-control custom-checkbox">
+                                            <input type="checkbox" class="custom-control-input" id="show-table-detail">
+                                            <label class="custom-control-label" for="show-table-detail">审批过程</label>
+                                        </div>
                                     </div>
+                                <% } %>
+                                <% if (showPlanBtn) { %>
+                                    <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>
-                            <% } %>
-                            <% if (showPlanBtn) { %>
                                 <div class="d-inline-block mr-2">
-                                    <a class="btn btn-sm btn-primary" href="#bgfadb" data-toggle="modal" data-target="#bgfadb">差值对比</a>
+                                    <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 judge-hide" id="camount-expr" readonly="readOnly" autocomplete="off">
+                                        <input type="text" class="form-control form-control-sm m-0 judge-show" id="ledger-camount-expr" readonly="readOnly" autocomplete="off" style="display: none">
+                                    </div>
                                 </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 class="d-inline-block pull-right">
+                                    <div class="btn-group btn-group-toggle group-tab">
+                                        <a class="btn btn-sm btn-light change-mode <% if (changeMode === 0) { %>active<% } %>" data-type="0" href="javascript:void(0);">清单模式</a>
+                                        <a class="btn btn-sm btn-light change-mode <% if (changeMode === 1) { %>active<% } %>" data-type="1" href="javascript:void(0);">台账模式</a>
                                     </div>
-                                    <input type="text" class="form-control form-control-sm m-0" id="camount-expr" readonly="readOnly" autocomplete="off">
                                 </div>
                             </div>
+                            <div class="sjs-height-change judge-hide" id="change-spread"></div>
+                            <div class="sjs-height-change judge-show" style="display: none;" id="ledger-spread"></div>
                         </div>
-                        <div class="sjs-height-change" id="change-spread"></div>
-                    </div>
-                </div>
-                <!--下半部分-->
-                <div class="bcontent-wrap">
-                    <div class="bc-bar mb-1">
-                        <ul class="nav nav-tabs">
-                            <li class="nav-item">
-                                <a class="nav-link active" href="#">所属项目节</a>
-                            </li>
-                        </ul>
                     </div>
-                    <div class="sp-wrap" id="xmj-spread">
+                    <!--下半部分-->
+                    <div class="bcontent-wrap">
+                        <div id="change-pos-resize" class="resize-y" r-Type="height" div1=".sjs-height-1" div2=".bcontent-wrap" title="调整大小"><!--调整上下高度条--></div>
+                        <div class="judge-hide">
+                            <div class="bc-bar mb-1">
+                                <ul class="nav nav-tabs">
+                                    <li class="nav-item">
+                                        <a class="nav-link active" href="#">所属项目节</a>
+                                    </li>
+                                    <li class="nav-item">
+                                        <div class="d-inline-block">
+                                            <div class="input-group input-group-sm ml-2">
+                                                <div class="input-group-prepend">
+                                                    <span class="input-group-text" id="basic-addon3">表达式</span>
+                                                </div>
+                                                <input type="text" class="form-control form-control-sm m-0" id="xmj-camount-expr" readonly="readOnly" autocomplete="off">
+                                            </div>
+                                        </div>
+                                    </li>
+                                </ul>
+                            </div>
+                            <div class="sp-wrap" id="xmj-spread"></div>
+                        </div>
+                        <div class="judge-show" style="display: none;">
+                            <div class="bc-bar mb-1">
+                                <ul class="nav nav-tabs">
+                                    <li class="nav-item">
+                                        <a class="nav-link active" href="#">计量单元</a>
+                                    </li>
+                                    <li class="nav-item">
+                                        <div class="d-inline-block">
+                                            <div class="input-group input-group-sm ml-2">
+                                                <div class="input-group-prepend">
+                                                    <span class="input-group-text" id="basic-addon2">表达式</span>
+                                                </div>
+                                                <input type="text" class="form-control form-control-sm m-0" id="pos-camount-expr" readonly="readOnly" autocomplete="off">
+                                            </div>
+                                        </div>
+                                    </li>
+                                </ul>
+                            </div>
+                            <div class="sp-wrap" id="pos-spread"></div>
+                        </div>
                     </div>
                 </div>
             </div>
@@ -478,6 +520,7 @@
     </div>
 </div>
 <script>
+    let changeMode = parseInt('<%- changeMode %>');
     const tenderName = JSON.parse(unescape('<%- escape(JSON.stringify(tender.data.name)) %>'));
     const tenderId = '<%- tender.id %>';
     const deleteFilePermission = <%- deleteFilePermission %>;
@@ -493,6 +536,7 @@
     let changeVp = parseFloat('<%- change.valuation_tp ? change.valuation_tp : 0 %>');
     let changeUp = parseFloat('<%- change.unvaluation_tp ? change.unvaluation_tp : 0 %>');
     const changeStatus = parseFloat('<%- change.status %>');
+    let changeOrder = parseInt('<%- change.order_by %>');
     const touristPermission = parseInt('<%- ctx.tender.touristPermission.file %>');
     const precision = JSON.parse('<%- JSON.stringify(precision) %>');
     const whiteList = JSON.parse('<%- JSON.stringify(whiteList) %>');
@@ -541,12 +585,14 @@
     }
     const changeLedgerList = JSON.parse(unescape('<%- escape(JSON.stringify(changeLedgerList)) %>'));
     const changePosList = JSON.parse(unescape('<%- escape(JSON.stringify(changePosList)) %>'));
-</script>
-<% if (!change.readOnly) { %>
-<script>
+    let ledgerList, posList;
     let changeUnits = JSON.parse('<%- JSON.stringify(changeUnits) %>');
     changeUnits = _.map(changeUnits, 'unit');
     changeUnits.push('');
+    let changeUsedData = JSON.parse(unescape('<%- escape(JSON.stringify(changeUsedData)) %>'));
+</script>
+<% if (!change.readOnly) { %>
+<script>
     const shenpi_status = <%- ctx.tender.info.shenpi.change %>;
     const shenpiConst = JSON.parse('<%- JSON.stringify(shenpiConst) %>');
     const unitList = JSON.parse(unescape('<%- escape(JSON.stringify(unitList)) %>'));
@@ -573,16 +619,12 @@
         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"></script>
 <script src="/public/js/change_audit.js"></script>
-<% } else if (change.readOnly && !change.shenpiPower) { %>
+<% } else { %>
 <script>
     const auditors2 = JSON.parse(unescape('<%- escape(JSON.stringify(change.auditors2)) %>'));
     auditors2.shift();
@@ -592,17 +634,4 @@
     }
     let w_code = JSON.parse(unescape('<%- escape(JSON.stringify(change.w_code ? change.w_code : '')) %>'));
 </script>
-<script src="/public/js/change_information_show.js?202206211"></script>
-<% } else if (change.shenpiPower) { %>
-<script>
-    const auditors2 = JSON.parse(unescape('<%- escape(JSON.stringify(change.auditors2)) %>'));
-    auditors2.shift();
-    const aidList = [];
-    for (let i = 0; i < auditors2.length; i++) {
-        aidList.push(auditors2[i][0].audit_order);
-    }
-    let changeUsedData = JSON.parse(unescape('<%- escape(JSON.stringify(changeUsedData)) %>'));
-    // console.log(changeUsedData);
-</script>
-<script src="/public/js/change_information_approval.js?202211041"></script>
 <% } %>

+ 2 - 2
app/view/change/information_modal.ejs

@@ -211,7 +211,7 @@
                 </div>
                 <div class="modal-footer">
                     <button class="btn btn-secondary btn-sm" data-dismiss="modal">关闭</button>
-                    <button class="btn btn-primary btn-sm" onclick="checkChangeFrom();">确认上报</button>
+                    <button class="btn btn-primary btn-sm checkChangeFrom">确认上报</button>
                 </div>
             </div>
         </div>
@@ -391,7 +391,7 @@
                 <div class="modal-footer">
                     <button type="button" class="btn btn-secondary btn-sm" data-dismiss="modal">关闭</button>
                     <% if((ctx.change.status === auditConst.status.checkNo || ctx.change.status === auditConst.status.revise) && ctx.session.sessionUser.accountId === ctx.change.uid) { %>
-                        <button class="btn btn-primary btn-sm sp-list-item" onclick="checkChangeFrom();">确认上报</button>
+                        <button class="btn btn-primary btn-sm sp-list-item checkChangeFrom">确认上报</button>
                     <% } %>
                 </div>
             </div>

+ 2 - 0
app/view/ledger/explode.ejs

@@ -42,9 +42,11 @@
                 <div class="d-inline-block ml-3">
                     <a id="exportLedger" class="btn btn-primary btn-sm" href="javascript: void(0)">导出台账Excel</a>
                 </div>
+                <% if (tender.ledger_status === auditConst.status.uncheck ) { %>
                 <div class="d-inline-block ml-1">
                     <a href="#import-dsk" data-toggle="modal" data-target="#import-dsk" class="btn btn-sm btn-primary">导入计价文件</a>
                 </div>
+                <% } %>
                 <% if (syncLedgerUrl) { %>
                 <div class="d-inline-block ml-1">
                     <a id="sync-ledger" class="btn btn-primary btn-sm" href="javascript: void(0)">同步台账</a>

+ 2 - 2
app/view/material/audit_modal.ejs

@@ -600,7 +600,7 @@
                                                                         <% if (auditor.status === auditConst.status.checking && auditor.aid === ctx.session.sessionUser.accountId) { %>
                                                                             <div class="col-12 py-1 bg-light">
                                                                                 <textarea class="form-control form-control-sm" name="opinion">不同意</textarea>
-                                                                                <div class="alert alert-warning mt-1 mb-0 p-2">
+                                                                                <div class="alert alert-warning mt-1 mb-0 p-2" id="reject-process">
                                                                                     <div class="form-check form-check-inline">
                                                                                         <input class="form-check-input" type="radio" name="checkType" id="inlineRadio1" value="<%- auditConst.status.checkNo %>">
                                                                                         <label class="form-check-label" for="inlineRadio1">退回原报 <%- ctx.material.user.name %></label>
@@ -609,7 +609,7 @@
                                                                                         <div class="form-check form-check-inline">
                                                                                             <input class="form-check-input" type="radio" name="checkType" id="inlineRadio2" value="<%- auditConst.status.checkNoPre %>">
                                                                                             <label class="form-check-label" for="inlineRadio2">退回上一审批人
-                                                                                                <% const pre = his.find(x => { return x.audit_order === auditor.audit_order - 1}); %>
+                                                                                                <% const pre = ctx.helper._.findLast(his, x => { return x.audit_order === auditor.audit_order - 1 }); %>
                                                                                                 <%- (pre.audit_type === auditType.key.common ? pre.auditors[0].name : `${pre.audit_order}审`)%>
                                                                                             </label>
                                                                                         </div>

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

@@ -122,7 +122,6 @@
 </div>
 <script>
   const auditors = JSON.parse('<%- JSON.stringify(auditors) %>');
-  const curAuditor = JSON.parse('<%- JSON.stringify(ctx.material.curAuditor) %>');
   const materialStatus = parseInt('<%- ctx.material.status %>');
   const materialUid = parseInt('<%- ctx.material.user_id %>');
   const auditConst = JSON.parse('<%- JSON.stringify(auditConst) %>');

+ 8 - 0
app/view/measure/compare.ejs

@@ -12,6 +12,9 @@
                         <a class="btn btn-sm btn-light" href="#tz" data-toggle="tab" name="compareType">
                             0号台账
                         </a>
+                        <a class="btn btn-sm btn-light" href="#chapter" data-toggle="tab" name="compareType">
+                            章节合计
+                        </a>
                     </div>
                 </div>
                 <div class="d-inline-block">
@@ -100,10 +103,15 @@
                         </div>
                     </div>
                 </div>
+                <div class="tab-pane active" id="chapter">
+                    <div class="sjs-height-0" id="chapter-spread">
+                    </div>
+                </div>
             </div>
         </div>
     </div>
 </div>
 <script>
     const thousandth = <%- ctx.tender.info.display.thousandth %>;
+    const chapter = JSON.parse('<%- JSON.stringify(ctx.tender.info.chapter) %>');
 </script>

+ 77 - 0
app/view/phase_pay/detail.ejs

@@ -0,0 +1,77 @@
+<% include ./sub_menu.ejs %>
+<div class="panel-content">
+    <div class="panel-title">
+        <div class="title-main d-flex">
+            <% include ./sub_mini_menu.ejs %>
+            <div>
+                <div class="d-inline-block">
+                    <a href="javascript: void(0)" class="btn btn-sm btn-light text-primary" data-toggle="tooltip" data-placement="bottom" title="" data-original-title="添加" name="base-opr" type="add"><i class="fa fa-plus" aria-hidden="true"></i></a>
+                    <a href="javascript: void(0)" class="btn btn-sm btn-light text-primary" data-toggle="tooltip" data-placement="bottom" title="" data-original-title="删除" name="base-opr" type="delete"><i class="fa fa-remove" aria-hidden="true"></i></a>
+                    <a href="javascript: void(0)" class="btn btn-sm btn-light text-primary" data-toggle="tooltip" data-placement="bottom" title="" data-original-title="下移" name="base-opr" type="down-move"><i class="fa fa-arrow-down" aria-hidden="true"></i></a>
+                    <a href="javascript: void(0)" class="btn btn-sm btn-light text-primary" data-toggle="tooltip" data-placement="bottom" title="" data-original-title="上移" name="base-opr" type="up-move"><i class="fa fa-arrow-up" aria-hidden="true"></i></a>
+                </div>
+                <div class="d-inline-block ">
+                    <div class="input-group input-group-sm ml-2 mt-1">
+                        <div class="input-group-prepend">
+                            <span class="input-group-text">表达式</span>
+                        </div>
+                        <input type="text" class="form-control m-0" style="width: 270px" value="" id="pay-expr">
+                    </div>
+                </div>
+            </div>
+            <div class="ml-auto">
+                <% if (!ctx.phasePay.readOnly) { %>
+                <button class="btn btn-sm btn-primary" data-toggle="tooltip" data-placement="bottom" title="" data-original-title="计算全部" id="calc-all"><i class="fa fa-play"></i></button>
+                <% } %>
+                <% if (ctx.phasePay.audit_status === auditConst.status.uncheck) { %>
+                <button class="btn btn-sm btn-primary" data-toggle="tooltip" data-placement="bottom" title="" data-original-title="刷新基数" id="reload-calc-base"><i class="fa fa-repeat"></i></button>
+                <% } %>
+            </div>
+        </div>
+    </div>
+    <div class="content-wrap">
+        <div class="c-header p-0"></div>
+        <div class="row w-100 sub-content">
+            <div class="c-body col-8">
+                <div class="sjs-height-1" id="pay-spread">
+                </div>
+            </div>
+            <div class="c-body col">
+                <div class="side-bar-1"></div>
+                <div class="sjs-sh-1">
+                    <table class="table table-bordered">
+                        <tr><th></th><th>可选基数</th><th>计算代号</th><th>值</th></tr>
+                        <tbody id="base-list">
+                        <% for (let iBase = 0; iBase < calcBase.length; iBase++) { %>
+                        <tr>
+                            <td><%- iBase + 1 %></td>
+                            <td><%- calcBase[iBase].name %></td>
+                            <td><%- calcBase[iBase].code %></td>
+                            <% if (calcBase[iBase].code === 'bqyf') { %>
+                            <td class="text-right">--</td>
+                            <% } else {%>
+                            <td class="text-right"><%- (ctx.tender.info.display.thousandth ? ctx.helper.formatNum(calcBase[iBase].value, '#,##0.######') : calcBase[iBase].value) %></td>
+                            <% } %>
+                        </tr>
+                        <% } %>
+                        </tbody>
+                    </table>
+                </div>
+            </div>
+        </div>
+    </div>
+</div>
+<div style="display: none">
+    <img src="/public/images/file_clip.png" id="rela-file-icon" />
+    <img src="/public/images/file_clip_hover.png" id="rela-file-hover" />
+</div>
+<script>
+    const lockPayExpr = <%- lockPayExpr %>;
+    const phasePay = JSON.parse('<%- JSON.stringify(ctx.phasePay) %>');
+    const readOnly = <%- ctx.phasePay.readOnly %>;
+    const maxStageOrder = <%- maxStageOrder %>;
+    const details = JSON.parse('<%- JSON.stringify(pays) %>');
+    const calcBase = JSON.parse('<%- JSON.stringify(calcBase) %>');
+    const addBase = JSON.parse('<%- JSON.stringify(ctx.phasePay.calc_base) %>');
+    const deadlineType = JSON.parse('<%- JSON.stringify(deadlineType) %>');
+</script>

+ 59 - 0
app/view/phase_pay/detail_modal.ejs

@@ -0,0 +1,59 @@
+<!--设置计提期限-->
+<div class="modal fade" id="deadline" 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">
+                <p>请设置付(扣)款项 <b id="dl-pay-name">本期应付</b> 的计提期限</p>
+                <div class="form-group">
+                    <label for="formGroupExampleInput">限制模式为:</label>
+                    <div>
+                        <div class="form-check form-check-inline">
+                            <input class="form-check-input" type="radio" name="dl-type" id="dl-type-none" value="none">
+                            <label class="form-check-label" for="dl-type-none">无</label>
+                        </div>
+                        <div class="form-check form-check-inline">
+                            <input class="form-check-input" type="radio" name="dl-type" id="inlineRadio2" value="phaseCount">
+                            <label class="form-check-label" for="inlineRadio2">合同支付期数</label>
+                        </div>
+                        <div class="form-check form-check-inline">
+                            <input class="form-check-input" type="radio" name="dl-type" id="inlineRadio3" value="stageCount">
+                            <label class="form-check-label" for="inlineRadio3">计量期数</label>
+                        </div>
+                        <br/>
+                        <div class="form-check form-check-inline">
+                            <input class="form-check-input" type="radio" name="dl-type" id="inlineRadio4" value="contract">
+                            <label class="form-check-label" for="inlineRadio4">累计合同金额</label>
+                        </div>
+                        <div class="form-check form-check-inline">
+                            <input class="form-check-input" type="radio" name="dl-type" id="inlineRadio5" value="qc">
+                            <label class="form-check-label" for="inlineRadio5">累计变更金额</label>
+                        </div>
+                        <div class="form-check form-check-inline">
+                            <input class="form-check-input" type="radio" name="dl-type" id="inlineRadio6" value="gather">
+                            <label class="form-check-label" for="inlineRadio6">累计完成金额</label>
+                        </div>
+                    </div>
+                </div>
+                <div class="form-group" dl-type="1">
+                    <label>限制值</label>
+                    <input class="form-control form-control-sm" id="dl-value" type="number">
+                </div>
+                <!--公用提示-->
+                <div id="dl-hint">
+                    <p>设置为:</p>
+                    <p id="range-hint" class="pl-3 text-danger">当 累计完成计量金额 >= 50000.00 时</p>
+                    <p class="pl-3">当期金额直接计量至(扣款限额 - 截止上期金额)</p>
+                </div>
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-secondary btn-sm" data-dismiss="modal">关闭</button>
+                <% if (!ctx.phasePay.readOnly) { %>
+                <button type="button" class="btn btn-primary btn-sm" id="deadline-ok">确定</button>
+                <% } %>
+            </div>
+        </div>
+    </div>
+</div>

+ 25 - 44
app/view/phase_pay/index.ejs

@@ -35,46 +35,46 @@
                     <% for (const pay of phasePays) { %>
                     <tr>
                         <td>
-                            <a href="<%- '/tender/' + pay.tid + '/pay/' + pay.phase_order %>" target="_blank">第 <%- pay.phase_order %> 期</a>
+                            <a href="<%- '/tender/' + pay.tid + '/pay/' + pay.phase_order + '/detail' %>" target="_blank">第 <%- pay.phase_order %> 期</a>
                             <% if (pay.audit_status !== auditConst.status.checked && pay.create_user_id === ctx.session.sessionUser.accountId) { %>
                             <a href="#edit" class="edit-pay" data-id="<%- pay.id %>" data-toggle="modal" data-target="#edit"><i class="fa fa-pencil-square-o "></i></a>
                             <% } %>
                         </td>
-                        <td class="text-center"><%- s.phase_date %></td>
+                        <td class="text-center"><%- pay.phase_date %></td>
                         <td class="text-center">
                             <% for (const s of pay.rela_stage) { %>
-                            <a href="<%- '/tender/' + pay.tid + '/measure/stage/' + s.sorder %>" target="_blank">第 <%- s.sorder %> 期</a>
+                            <a href="<%- '/tender/' + pay.tid + '/measure/stage/' + s.stage_order %>" target="_blank">第 <%- s.stage_order %> 期</a>
                             <% } %>
                         </td>
-                        <td class="text-right"><%- s.display_pay_tp %></td>
-                        <td class="text-right"><%- s.display_cut_tp %></td>
-                        <td class="text-right"><%- s.display_yf_tp %></td>
-                        <td class="text-right"><%- s.display_sf_tp %></td>
-                        <td class="text-right"><%- s.display_end_yf_tp %></td>
-                        <td class="text-right"><%- s.display_end_sf_tp %></td>
-                        <td class="<%- auditConst.auditProgressClass[s.status] %>">
-                            <% if (s.status === auditConst.status.checked && s.final_auditor_str) { %>
-                            <a href="#sp-list" data-toggle="modal" data-target="#sp-list" s-order="<%- s.order %>"><%- s.final_auditor_str %></a>
+                        <td class="text-right"><%- pay.display_pay_tp %></td>
+                        <td class="text-right"><%- pay.display_cut_tp %></td>
+                        <td class="text-right"><%- pay.display_yf_tp %></td>
+                        <td class="text-right"><%- pay.display_sf_tp %></td>
+                        <td class="text-right"><%- pay.display_end_yf_tp %></td>
+                        <td class="text-right"><%- pay.display_end_sf_tp %></td>
+                        <td class="<%- auditConst.info[pay.audit_status].class %>">
+                            <% if (pay.audit_status === auditConst.status.checked && pay.final_auditor_str) { %>
+                                <a href="#sp-list" data-toggle="modal" data-target="#sp-list" s-order="<%- pay.phase_order %>"><%- pay.final_auditor_str %></a>
                             <% } else { %>
-                            <% if (s.curAuditors.length > 0) { %>
-                            <% if (s.curAuditors[0].audit_type === auditType.key.common) { %>
-                            <a href="#sp-list" data-toggle="modal" data-target="#sp-list" s-order="<%- s.order %>"><%- s.curAuditors[0].name %><%if (s.curAuditors[0].role !== '' && s.curAuditors[0].role !== null) { %>-<%- s.curAuditors[0].role %><% } %></a>
-                            <% } else { %>
-                            <a href="#sp-list" data-toggle="modal" data-target="#sp-list" s-order="<%- s.order %>"><%- ctx.helper.transFormToChinese(s.curAuditors[0].audit_order) + '审' %></a>
-                            <% } %>
-                            <% } %>
+                                <% if (pay.curAuditors.length > 0) { %>
+                                    <% if (pay.curAuditors[0].audit_type === auditType.key.common) { %>
+                                        <a href="#sp-list" data-toggle="modal" data-target="#sp-list" s-order="<%- pay.phase_order %>"><%- pay.curAuditors[0].name %><%if (pay.curAuditors[0].role !== '' && pay.curAuditors[0].role !== null) { %>-<%- pay.curAuditors[0].role %><% } %></a>
+                                    <% } else { %>
+                                        <a href="#sp-list" data-toggle="modal" data-target="#sp-list" s-order="<%- pay.phase_order %>"><%- ctx.helper.transFormToChinese(pay.curAuditors[0].audit_order) + '审' %></a>
+                                    <% } %>
+                                <% } %>
                             <% } %>
-                            <%- auditConst.auditProgress[s.status] %>
+                            <%- auditConst.info[pay.audit_status].title %>
                         </td>
                         <td class="text-center">
-                            <% if (s.status === auditConst.status.uncheck && s.user_id === ctx.session.sessionUser.accountId) { %>
-                            <a href="<%- '/tender/' + ctx.tender.id + '/measure/stage/' + s.order %>" target="_blank" class="btn <%- auditConst.statusButtonClass[s.status] %> btn-sm"><%- auditConst.statusButton[s.status] %></a>
+                            <% if (pay.audit_status === auditConst.status.uncheck && pay.create_user_id === ctx.session.sessionUser.accountId) { %>
+                            <a href="<%- '/tender/' + pay.tid + '/pay/' + pay.phase_order %>" target="_blank" class="btn <%- auditConst.info[pay.audit_status].btnClass %> btn-sm"><%- auditConst.info[pay.audit_status].btnTitle %></a>
                             <% } else if (s.status === auditConst.status.checkNo && s.user_id === ctx.session.sessionUser.accountId) { %>
-                            <a href="<%- '/tender/' + ctx.tender.id + '/measure/stage/' + s.order %>" target="_blank" class="btn <%- auditConst.statusButtonClass[s.status] %> btn-sm"><%- auditConst.statusButton[s.status] %></a>
+                            <a href="<%- '/tender/' + pay.tid + '/pay/' + pay.phase_order %>" target="_blank" class="btn <%- auditConst.info[pay.audit_status].btnClass %> btn-sm"><%- auditConst.info[pay.audit_status].btnTitle %></a>
                             <% } else if ((s.status === auditConst.status.checking || s.status === auditConst.status.checkNoPre) && s.curAuditors && s.curAuditors.findIndex(x => { return x.aid === ctx.session.sessionUser.accountId; }) >= 0) { %>
-                            <a href="<%- '/tender/' + ctx.tender.id + '/measure/stage/' + s.order %>" target="_blank" class="btn <%- auditConst.statusButtonClass[s.status] %> btn-sm"><%- auditConst.statusButton[s.status] %></a>
+                            <a href="<%- '/tender/' + pay.tid + '/pay/' + pay.phase_order %>" target="_blank" class="btn <%- auditConst.info[pay.audit_status].btnClass %> btn-sm"><%- auditConst.info[pay.audit_status].btnTitle %></a>
                             <% } else { %>
-                            <span class="<%- auditConst.auditStringClass[s.status] %>"><%- auditConst.auditString[s.status] %></span>
+                            <span class="<%- auditConst.info[pay.audit_status].btnClass %>"><%- auditConst.info[pay.audit_status].btnTitle %></span>
                             <% } %>
                         </td>
                         <td> <%- pay.memo %></td>
@@ -86,22 +86,3 @@
         </div>
     </div>
 </div>
-<script src="/public/js/sub_menu.js"></script>
-<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();
-        }
-    });
-</script>

+ 85 - 0
app/view/phase_pay/modal.ejs

@@ -0,0 +1,85 @@
+<div class="modal" id="add-qi" data-backdrop="static" aria-modal="true" role="dialog">
+    <div class="modal-dialog" role="document">
+        <form class="modal-content" action="pay/add" method="POST" onsubmit="return checkAddValid();">
+            <div class="modal-header">
+                <h5 class="modal-title">添加新一期</h5>
+            </div>
+            <div class="modal-body">
+                <div class="form-group form-group-sm">
+                    <label>支付期</label>
+                    <input class="form-control form-control-sm" value="第 <%- (phasePays.length + 1) %> 期" type="text" readonly="">
+                    <input type="hidden" value="<%- (phasePays.length + 1) %>" name="phase_order">
+                </div>
+                <div class="form-group form-group-sm">
+                    <label>支付年月</label>
+                    <input class="datepicker-here form-control form-control-sm" name="date" placeholder="点击选择年月" data-view="months" data-min-view="months" data-date-format="yyyy-MM" data-language="zh" type="text">
+                </div>
+                <div class="form-group form-group-sm">
+                    <label>计量期</label>
+                    <select class="form-control form-control-sm" name="stage">
+                        <% for (const s of validStages) { %>
+                            <option value="<%- s.order %>" <%- (s.order === validStages[0].order ? 'selected' : '') %>>第 <%- s.order %> 期</option>
+                        <% } %>
+                    </select>
+                </div>
+                <div class="form-group form-group-sm">
+                    <label>支付期备注</label>
+                    <textarea class="form-control form-control-sm" rows="3" name="memo"></textarea>
+                </div>
+            </div>
+            <div class="modal-footer">
+                <input type="hidden" name="_csrf_j" value="<%= ctx.csrf %>" />
+                <button type="button" class="btn btn-sm btn-secondary" data-dismiss="modal">关闭</button>
+                <button type="submit" class="btn btn-sm btn-primary" id="add-qi-ok">确定</button>
+            </div>
+        </form>
+    </div>
+</div>
+<div class="modal" id="edit-qi" data-backdrop="static" aria-modal="true" role="dialog">
+    <div class="modal-dialog" role="document">
+        <div class="modal-content" action="pay/save" method="POST" onsubmit="return checkEditValid();">
+            <div class="modal-header">
+                <h5 class="modal-title">编辑</h5>
+            </div>
+            <div class="modal-body">
+                <div class="form-group form-group-sm">
+                    <label>支付期</label>
+                    <input class="form-control form-control-sm" value="第 <%- (phasePays.length + 1) %> 期" type="text" readonly="">
+                </div>
+                <div class="form-group form-group-sm">
+                    <label>支付年月</label>
+                    <input class="datepicker-here form-control form-control-sm" name="date" placeholder="点击选择年月" data-view="months" data-min-view="months" data-date-format="yyyy-MM" data-language="zh" type="text">
+                </div>
+                <div class="form-group form-group-sm">
+                    <label>支付期备注</label>
+                    <textarea class="form-control form-control-sm" rows="3" name="memo"></textarea>
+                </div>
+            </div>
+            <div class="modal-footer">
+                <input type="hidden" value="" name="phase_id">
+                <input type="hidden" name="_csrf_j" value="<%= ctx.csrf %>" />
+                <button type="button" class="btn btn-sm btn-secondary" data-dismiss="modal">关闭</button>
+                <button type="button" class="btn btn-sm btn-primary" id="add-qi-ok">确定</button>
+            </div>
+        </div>
+    </div>
+</div>
+<script>
+    $('.datepicker-here').datepicker({
+        autoClose: true,
+    });
+    const checkAddValid = function() {
+        if ($('[name=date]', '#add-qi').val() == '') {
+            toastr.error('请选择计量年月');
+            return false;
+        }
+
+        if ($('[name=stage]', '#add-qi').val() == '') {
+            toastr.error('请选择计量期');
+            return false;
+        }
+    }
+    const checkEditValid = function() {
+
+    }
+</script>

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

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

+ 3 - 0
app/view/phase_pay/sub_menu_list.ejs

@@ -0,0 +1,3 @@
+<nav-menu title="返回" url="/tender/<%= ctx.tender.id %>/pay" tclass="text-primary" ml="1" icon="fa-chevron-left"></nav-menu>
+<nav-menu title="支付明细" url="/tender/<%= ctx.tender.id %>/pay/<%= ctx.phasePay.order + '/detail'%>" ml="3" active="<%= (ctx.url.indexOf('detail') >= 0 ? 1 : -1) %>"></nav-menu>
+<nav-menu title="输出报表" url="/tender/<%= ctx.tender.id %>/pay/<%= ctx.phasePay.order + '/report'%>" ml="3" active="<%= (ctx.url.indexOf('report') >= 0 ? 1 : -1) %>"></nav-menu>

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

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

+ 5 - 0
app/view/revise/info.ejs

@@ -193,6 +193,8 @@
                     </div>
                     <div id="sum-load-miss" class="tab-pane tab-select-show">
                     </div>
+                    <div id="gcl-gather" class="tab-pane tab-select-show">
+                    </div>
                 </div>
             </div>
         </div>
@@ -233,6 +235,9 @@
                 <li class="nav-item">
                     <a class="nav-link" content="#sum-load-miss" id="sum-load-miss-tab" href="javascript: void(0);" style="display: none;">导入信息</a>
                 </li>
+                <li class="nav-item">
+                    <a class="nav-link" content="#gcl-gather" id="gcl-gather-tab" href="javascript: void(0);">清单汇总</a>
+                </li>
             </ul>
         </div>
     </div>

+ 17 - 0
app/view/shares/dsk_modal.ejs

@@ -1,3 +1,20 @@
+<div class="modal" id="binddskuser" data-backdrop="static" aria-modal="true" role="dialog">
+    <div class="modal-dialog" role="document" style="width:400px">
+        <div class="modal-content">
+            <div class="modal-header">
+                <h5 class="modal-title">授权账号</h5>
+            </div>
+            <div class="modal-body">
+                <span>当前账号未绑定大司空账号,请先<a href="/profile/sms">绑定大司空账号</a>。</span>
+            </div>
+            <div class="modal-footer">
+                <div class="ml-auto">
+                    <button type="button" class="btn btn-sm btn-secondary" data-dismiss="modal">取消</button>
+                </div>
+            </div>
+        </div>
+    </div>
+</div>
 <div class="modal fade" id="choose-dsk-subject" data-backdrop="static">
     <div class="modal-dialog modal-lgx" role="document" >
         <div class="modal-content">

+ 38 - 1
app/view/stage/gather.ejs

@@ -5,6 +5,44 @@
             <% include ./stage_sub_mini_menu.ejs %>
             <div>
                 <div class="d-inline-block">
+                    <div class="dropdown">
+                        <button class="btn btn-sm btn-light dropdown-toggle text-primary" type="button" id="dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
+                            超计显示
+                        </button>
+                        <div class="dropdown-menu" aria-labelledby="dropdownMenuButton" style="width:290px">
+                            <span class="dropdown-item-text text-danger">截止本期合同计量 大于</span>
+                            <form class="px-4 py-3">
+                                <div class="form-group">
+                                    <div class="custom-control custom-radio">
+                                        <input type="radio" id="customRadio1" name="customRadio" class="custom-control-input" checked="">
+                                        <label class="custom-control-label" for="customRadio1">台账数量</label>
+                                    </div>
+                                </div>
+                                <div class="form-group">
+                                    <div class="custom-control custom-radio">
+                                        <input type="radio" id="customRadio2" name="customRadio" class="custom-control-input">
+                                        <label class="custom-control-label" for="customRadio2">签约清单数量</label>
+                                    </div>
+                                </div>
+                                <div class="form-group mb-0">
+                                    <div class="custom-control custom-radio">
+                                        <input type="radio" id="customRadio3" name="customRadio" class="custom-control-input">
+                                        <label class="custom-control-label" for="customRadio3">台账数量 或 签约清单数量</label>
+                                    </div>
+                                </div>
+                            </form>
+                            <span class="dropdown-item-text text-danger">
+                            <div class="input-group input-group-sm">
+                                <input type="number" step="5" class="form-control form-control-sm m-0" placeholder="100" max="100" min="50" id="over-percent" value="100">
+                                <div class="input-group-append">
+                                    <span class="input-group-text" id="basic-addon2">%</span>
+                                </div>
+                            </div>
+                        </span>
+                        </div>
+                    </div>
+                </div>
+                <div class="d-inline-block">
                     <button href="#zjhj" class="btn btn-sm btn-light text-primary" data-toggle="modal" data-target="#zjhj">章节合计</button>
                 </div>
                 <% if (ctx.app.config.is_debug) { %>
@@ -102,6 +140,5 @@
     const chapter = JSON.parse('<%- JSON.stringify(ctx.tender.info.chapter) %>');
     const thousandth = <%- ctx.tender.info.display.thousandth %>;
     const hintOver = <%- hintOver %>;
-    const checkOverInfo = JSON.parse('<%- JSON.stringify(ctx.tender.info.checkOverInfo) %>');
     const tenderDecimal = JSON.parse(unescape('<%- escape(JSON.stringify(ctx.tender.info.decimal)) %>'));
 </script>

+ 3 - 136
app/view/tender/detail.ejs

@@ -221,9 +221,7 @@
                             <!--章节计量情况图-->
                             <div class="col-6 pl-0">
                                 <div class="card mb-3 <% if (!ctx.tender.info.display.dayMode) { %>bg-dark text-white<% } %>">
-                                    <% if (hadMap === 1) { %>
-                                        <div class="card-body" id="map" style="height: 388px; width: 100%;"></div>
-                                    <% } else if (hadMap === 2) { %>
+                                    <% if (hadMap === 2) { %>
                                         <div style="position:relative;height: 388px; width: 100%;">
                                             <% if (map_pic) { %>
                                             <% if (!ctx.tender.info.display.dayMode) { %>
@@ -395,7 +393,7 @@
                             <!--期进度表-->
                             <div class="col-6">
                                 <div class="card mb-3 <% if (!ctx.tender.info.display.dayMode) { %>bg-dark text-white<% } %> bottom-height">
-                                    <% if (hadMap === 1 || hadMap === 2) { %>
+                                    <% if (hadMap === 2) { %>
                                         <div class="card-header"><h6 class="mb-0">章节计量情况表</h6></div>
                                         <div class="card-body">
                                             <div id="jlchart3" style="height: 300px; width: 100%;"></div>
@@ -411,7 +409,7 @@
                             <!--月进度表-->
                             <div class="col-6 pl-0">
                                 <div class="card mb-3 bottom-height <% if (!ctx.tender.info.display.dayMode) { %>bg-dark text-white<% } %>">
-                                    <% if (hadMap === 1 || hadMap === 2) { %>
+                                    <% if (hadMap === 2) { %>
                                         <div class="card-header">
                                             <ul class="nav nav-tabs card-header-tabs panel-card-tabs <% if (ctx.tender.info.display.dayMode) { %>nav-white-tabs<% } %>" id="change-echarts">
                                                 <li class="nav-item">
@@ -1501,134 +1499,3 @@
         };
     })
 </script>
-<% if (hadMap === 1 || ctx.session.sessionUser.is_admin) { %>
-    <script src="/public/js/map/turf.min.js"></script>
-    <script src="/public/js/map/gcoord.js"></script>
-    <script>
-        const tenderMapList = JSON.parse(unescape('<%- escape(JSON.stringify(tenderMapList)) %>'));
-    </script>
-<% } %>
-<% if (hadMap === 1) { %>
-    <!--<script src="//bj.bcebos.com/v1/mapopen/api-demos/js/mapStyle.js"></script>-->
-    <script type="text/javascript" src="https://api.map.baidu.com/api?v=1.0&type=webgl&ak=C3hLZAjuWTaCdwnwoYY83APrwlPEj4v7"></script>
-    <script type="text/javascript">
-        // 画线
-        $(function () {
-            const map = new BMapGL.Map("map");
-            // 创建地图实例
-
-            const polyLineList = [];
-            const centerPoint = { lng: <%- map_json.lng %>, lat: <%- map_json.lat %> };
-            let level = <%- map_json.level %>;
-            const pointList = [];
-            const centerList = [];
-            for (const tm of tenderMapList) {
-                if (tm.map_json) {
-                    const map_json = JSON.parse(unescape(escape(tm.map_json)));
-                    const lngLatList = [];
-                    for (const mj of map_json) {
-                        const result = gcoord.transform([mj.lng, mj.lat], gcoord.WGS84, gcoord.BD09);
-                        lngLatList.push(new BMapGL.Point(result[0], result[1]));
-                    }
-                    polyLineList.push({
-                        map: lngLatList,
-                        color: tm.color,
-                    });
-                    const center = JSON.parse(tm.center);
-                    pointList.push(turf.point([center.lng, center.lat]));
-                    if (tm.tips) {
-                        const centerResult = gcoord.transform([map_json[Math.floor(map_json.length/2)].lng, map_json[Math.floor(map_json.length/2)].lat], gcoord.WGS84, gcoord.BD09);
-                        centerList.push({ map: new BMapGL.Point(centerResult[0], centerResult[1]), color: tm.color, tips: tm.tips });
-                    }
-                }
-            }
-            if (pointList.length > 0) {
-                let maxDistance = 0;
-                // 中心点计算
-                const features = turf.featureCollection(pointList);
-                const center = turf.center(features);
-                if (center && center.geometry && center.geometry.coordinates && center.geometry.coordinates.length === 2) {
-                    centerPoint.lng = _.round(center.geometry.coordinates[0], 4);
-                    centerPoint.lat = _.round(center.geometry.coordinates[1], 4);
-                }
-                // 地图级别计算
-                if (pointList.length === 1) {
-                    // 只有一条路线
-                    const map_json = JSON.parse(unescape(escape(tenderMapList[0].map_json)));
-                    for (const mj of map_json) {
-                        const distance = turf.distance(turf.point([mj.lng, mj.lat]), turf.point([centerPoint.lng, centerPoint.lat]));
-                        if (distance > maxDistance) {
-                            maxDistance = distance;
-                        }
-                    }
-                } else {
-                    for (const c of pointList) {
-                        const distance = turf.distance(c, turf.point([centerPoint.lng, centerPoint.lat]));
-                        if (distance > maxDistance) {
-                            maxDistance = distance;
-                        }
-                    }
-                }
-                if (maxDistance === 0) {
-                    level = 15;
-                } else {
-                    // 级别区间
-                    // const range = [0.02, 0.05, 0.1, 0.2, 0.5, 1, 2, 5, 10, 20, 25, 50, 100, 200, 500, 1000, 2000, 5000, 10000];
-                    const range = [10000, 5000, 2000, 1000, 500, 200, 100, 50, 25, 20, 10, 5, 2, 1, 0.5, 0.2, 0.1, 0.05, 0.02];
-                    level = _.findIndex(range, function (item) {
-                        return item <= maxDistance;
-                    })
-                    level = level + 1;
-                }
-                const result = gcoord.transform([centerPoint.lng, centerPoint.lat], gcoord.WGS84, gcoord.BD09);
-                centerPoint.lng = result[0];
-                centerPoint.lat = result[1];
-
-            }
-            // 初始化地图,设置中心点坐标和地图级别
-            const point = new BMapGL.Point(centerPoint.lng, centerPoint.lat);
-            map.centerAndZoom(point, level);
-            // const LocationControl = new BMapGL.LocationControl();  // 添加比例尺控件
-            // map.addControl(LocationControl);
-            map.enableScrollWheelZoom(true);
-            <% if (!ctx.tender.info.display.dayMode) { %>
-            map.setMapStyleV2({styleId: '20d4aea41cf71387395f2dc835f1c4b6'});
-            <% } %>
-            if (polyLineList.length > 0) {
-                for (const pl of polyLineList) {
-                    const polyline = new BMapGL.Polyline(pl.map, {strokeColor: pl.color, strokeWeight:10, strokeOpacity:1});
-                    map.addOverlay(polyline);
-                }
-            }
-            if (centerList.length > 0) {
-                for (const cl of centerList) {
-                    const opts = {
-                        position : cl.map,    // 指定文本标注所在的地理位置
-                        offset   : new BMapGL.Size(5, 0)    //设置文本偏移量
-                    }
-                    const label = new BMapGL.Label(cl.tips, opts);  // 创建文本标注对象
-                    label.setStyle({
-                        height: '18px',
-                        lineHeight : '18px',
-                        color : cl.color,
-                        borderRadius: '5px',
-                        borderColor: cl.color,
-                        padding: '0px 5px',
-                        fontSize : '12px',
-                        // fontFamily: '微软雅黑',
-                    });
-                    map.addOverlay(label);
-                    // const opts = {
-                    //     width: 0,
-                    //     height: 0,
-                    //     maxwidth: 220,
-                    //     title: '路线标注:',
-                    // };
-                    // const infoWindow = new BMapGL.InfoWindow(cl.tips, opts);
-                    // map.openInfoWindow(infoWindow, cl.map);
-                }
-            }
-        })
-
-    </script>
-<% } %>

Filskillnaden har hållts tillbaka eftersom den är för stor
+ 1465 - 1464
app/view/tender/detail_modal.ejs


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

@@ -33,9 +33,11 @@
             </ul>
         </div>
         <div class="nav-box">
+            <% if (ctx.session.sessionProject.page_show.openIsolatePay) { %>
             <ul class="nav-list list-unstyled">
                 <li <% if (ctx.url.indexOf('/tender/' + ctx.tender.id + '/pay') !== -1) { %>class="active"<% } %>><a href="/tender/<%- ctx.tender.id %>/pay" class="h3"><i class="fa fa-pie-chart fa-fw"></i> <span>合同支付</span></a></li>
             </ul>
+            <% } %>
         </div>
         <div class="nav-box">
             <% if (!ctx.session.sessionProject.page_show.openChangeProject && !ctx.session.sessionProject.page_show.openChangeApply && !ctx.session.sessionProject.page_show.openChangePlan) { %>

+ 1 - 1
app/view/wap/shenpi_stage.ejs

@@ -202,7 +202,7 @@
                         <input type="radio" id="customRadioInline1" name="checkType" class="custom-control-input" value="<%- auditConst.status.checkNo %>" <% if (audit.order === 1 || audit.aid === stage.auditors[0].aid) { %>checked<% } %>>
                         <label class="custom-control-label" for="customRadioInline1">退回原报 <%- stage.user.name %></label>
                     </div>
-                    <% if (stage.curAuditors[0].audit_order > 1) { %>
+                    <% if (stage.curAuditors && stage.curAuditors[0] && stage.curAuditors[0].audit_order > 1) { %>
                         <div class="custom-control custom-radio custom-control-inline">
                             <input class="custom-control-input" type="radio" name="checkType" id="customRadioInline2" value="<%- auditConst.status.checkNoPre %>" checked>
                             <label class="custom-control-label" for="customRadioInline2">退回上一审批人

+ 84 - 24
build_min.js

@@ -9,41 +9,101 @@
  */
 
 
+const crypto = require('crypto');
 const Uglyfy = require('uglify-es');
 const fs = require('fs');
 const JsFiles = require('./config/web');
 const version = require('./config/config.default')({ baseDir: __dirname + '/app', root: __dirname, name: 'calc' }).version;
 console.log(version);
 const savePath = __dirname + '/app' + JsFiles.webPath;
+const cachePath = __dirname + '/app' + JsFiles.cachePath;
+const cacheInfoFile = cachePath + 'cache.json';
+let cacheInfo = [];
+const newCacheInfo = [];
 
 if (!fs.existsSync(savePath)) fs.mkdirSync(savePath);
+if (!fs.existsSync(cachePath)) fs.mkdirSync(cachePath);
 
-for (const c in JsFiles.controller) {
-    const controller = JsFiles.controller[c];
-    for (const a in controller) {
-        const action = controller[a];
-        if (action.mergeFiles && action.mergeFile.length > 0) {
-            const minFileName = savePath + action.mergeFile + '.' + version + '.min.js';
-            console.log(minFileName);
-            let code = '';
-            for (const f of action.mergeFiles) {
-                code = code + fs.readFileSync(__dirname + '/app' + f, 'utf8');
+const loadCache = function () {
+    if (fs.existsSync(cacheInfoFile)) {
+        cacheInfo = require(cacheInfoFile);
+    }
+};
+
+const getMd5 = function (text) {
+    const hash = crypto.createHash('md5');
+    hash.update(text);
+    return hash.digest('hex');
+};
+
+const findCache = function (file) {
+    let cache = cacheInfo.find(x => { return x.file === file; });
+    if (!cache) {
+        cache = { file: file };
+        cacheInfo.push(cache);
+    }
+    if (!cache.new_md5) {
+        cache.text = fs.readFileSync(__dirname + '/app' + file, 'utf8');
+        cache.new_md5 = getMd5(cache.text);
+    }
+    return cache;
+};
+
+const checkUseCache = function(files) {
+    for (const f of files) {
+        const cache = findCache(f);
+        if (cache.md5 !== cache.new_md5) return false;
+    }
+    return true;
+};
+
+const mergeAndMin = function () {
+    console.time('min');
+    loadCache();
+    for (const c in JsFiles.controller) {
+        const controller = JsFiles.controller[c];
+        for (const a in controller) {
+            const action = controller[a];
+            if (action.mergeFiles && action.mergeFile.length > 0) {
+                const minFileName = savePath + action.mergeFile + '.' + version + '.min.js';
+                const cacheFileName = cachePath + action.mergeFile + '.cache.js';
+                const useCache = checkUseCache(action.mergeFiles);
+                if (useCache && fs.existsSync(cacheFileName)) {
+                    fs.copyFileSync(cacheFileName, minFileName);
+                } else {
+                    let code = '';
+                    for (const f of action.mergeFiles) {
+                        const cache = findCache(f);
+                        code = code + cache.text;
+                    }
+                    fs.writeFileSync(minFileName, Uglyfy.minify(code, { mangle: true }).code);
+                    fs.copyFileSync(minFileName, cacheFileName);
+                }
+                console.log(minFileName, `cache: ${useCache}`);
             }
-            fs.writeFileSync(minFileName, Uglyfy.minify(code, { mangle: true }).code);
         }
     }
-}
-
-let needMinCode = '';
-for (const nm of JsFiles.needMin) {
-    needMinCode = needMinCode + fs.readFileSync(__dirname + '/app' + nm, 'utf8');
-}
-const needMinFileName = __dirname + '/app/public/js/web/global.' + version + '.min.js';
-fs.writeFileSync(needMinFileName, Uglyfy.minify(needMinCode, { mangle: true }).code);
-
-const needVersionCss = ['/public/css/main'];
-for (const nvc of needVersionCss) {
-    fs.copyFileSync(__dirname + '/app' + nvc + '.css', __dirname + '/app' + nvc + '.' + version + '.css');
-}
+
+    let needMinCode = '';
+    for (const nm of JsFiles.needMin) {
+        needMinCode = needMinCode + fs.readFileSync(__dirname + '/app' + nm, 'utf8');
+    }
+
+    const needMinFileName = __dirname + '/app/public/js/web/global.' + version + '.min.js';
+    fs.writeFileSync(needMinFileName, Uglyfy.minify(needMinCode, { mangle: true }).code);
+
+    const needVersionCss = ['/public/css/main'];
+    for (const nvc of needVersionCss) {
+        fs.copyFileSync(__dirname + '/app' + nvc + '.css', __dirname + '/app' + nvc + '.' + version + '.css');
+    }
+
+    fs.writeFileSync(cacheInfoFile, JSON.stringify(cacheInfo.map(x => { return { file: x.file, md5: x.new_md5 || x.md5 }; })));
+    console.timeEnd('min');
+};
+
+mergeAndMin();
+
+
+
 
 

+ 35 - 2
config/web.js

@@ -33,6 +33,7 @@
  */
 const JsFiles = {
     webPath: '/public/js/web/',
+    cachePath: '/public/js/webCache/',
     commonFiles: [
         '/public/js/jquery/jquery-3.2.1.min.js',
         '/public/js/jquery/jquery-ui.js',
@@ -274,6 +275,7 @@ const JsFiles = {
                     '/public/js/spreadjs_rela/spreadjs_zh.js',
                     '/public/js/shares/sjs_setting.js',
                     '/public/js/shares/cs_tools.js',
+                    '/public/js/shares/cs_gcl_gather.js',
                     '/public/js/shares/merge_peg.js',
                     '/public/js/shares/new_tag.js',
                     '/public/js/zh_calc.js',
@@ -422,9 +424,14 @@ const JsFiles = {
                 mergeFile: 'stage_detail',
             },
             pay: {
-                files: ['/public/js/spreadjs/sheets/v11/gc.spread.sheets.all.11.2.2.min.js', '/public/js/math.min.js'],
+                files: [
+                    '/public/js/spreadjs/sheets/v11/gc.spread.sheets.all.11.2.2.min.js',
+                    '/public/js/decimal.min.js',
+                    '/public/js/math.min.js',
+                ],
                 mergeFiles: [
                     '/public/js/sub_menu.js',
+                    '/public/js/zh_calc.js',
                     '/public/js/spreadjs_rela/spreadjs_zh.js',
                     '/public/js/shares/sjs_setting.js',
                     '/public/js/shares/cs_tools.js',
@@ -680,7 +687,33 @@ const JsFiles = {
             },
         },
         phasePay: {
-
+            list: {
+                files: [
+                    '/public/js/datepicker/datepicker.min.js',
+                    '/public/js/datepicker/datepicker.zh.js',
+                ],
+                mergeFiles: [
+                    '/public/js/component/menu.js',
+                    '/public/js/sub_menu.js',
+                ],
+                mergeFile: 'phase_pay_list',
+            },
+            detail: {
+                files: [
+                    '/public/js/spreadjs/sheets/v11/gc.spread.sheets.all.11.2.2.min.js',
+                    '/public/js/decimal.min.js',
+                    '/public/js/math.min.js',
+                ],
+                mergeFiles: [
+                    '/public/js/component/menu.js',
+                    '/public/js/sub_menu.js',
+                    '/public/js/spreadjs_rela/spreadjs_zh.js',
+                    '/public/js/path_tree.js',
+                    '/public/js/shares/sjs_setting.js',
+                    '/public/js/phase_pay_detail.js',
+                ],
+                mergeFile: 'phase_pay_detail',
+            }
         },
         measure: {
             compare: {

+ 42 - 8
db_script/material_audit_order.js

@@ -1,5 +1,37 @@
 const BaseUtil = require('./baseUtils');
 const querySql = BaseUtil.querySql;
+const sp_status = {
+    sqspr: 1, // 授权审批人
+    gdspl: 2, // 固定审批流
+    gdzs: 3, // 固定终审
+};
+// 审批类型
+const sp_type = {
+    advance: 1,
+    ledger: 2,
+    revise: 3,
+    stage: 4,
+    change: 5,
+    material: 6,
+};
+const updateShenpiAudit = async function(tender, type) {
+    const shenpiData = await querySql('Select * From zh_shenpi_audit where tid = ? AND sp_type = ? AND sp_status = ? ORDER BY id', [tender.id, type, sp_status.gdspl]);
+    if (shenpiData.length === 0) return;
+
+    for (const [i, spd] of shenpiData.entries()) {
+        if (spd.audit_order > 0) continue;
+        await querySql('UPDATE zh_shenpi_audit SET audit_order = ? WHERE id = ?', [i+1, spd.id]);
+    }
+};
+
+const updateShenpi = async function(tender) {
+    // updateShenpiAudit(tender, sp_type.advance);
+    // updateShenpiAudit(tender, sp_type.ledger);
+    // updateShenpiAudit(tender, sp_type.revise);
+    // updateShenpiAudit(tender, sp_type.stage);
+    // updateShenpiAudit(tender, sp_type.change);
+    await updateShenpiAudit(tender, sp_type.material);
+};
 const errorStageTimes = [];
 const updateMaterialTimes = async function(material, times) {
     const auditData = await querySql('SELECT * FROM zh_material_audit WHERE mid = ? AND times = ? ORDER BY `order`', [material.id, times]);
@@ -38,10 +70,11 @@ const doCompleteTest = async function(id) {
         const tender = await querySql('Select * From zh_tender where id > ?', [id]);
         for (const t of tender) {
             console.log(`Update Tender ${t.name}(${t.id}):`);
-            const materials = await querySql('Select * From zh_material where tid = ?', [t.id]);
-            for (const s of materials) {
-                await updateMaterial(s);
-            }
+            await updateShenpi(t);
+            // const materials = await querySql('Select * From zh_material where tid = ?', [t.id]);
+            // for (const s of materials) {
+            //     await updateMaterial(s);
+            // }
         }
     } catch (err) {
         console.log(err);
@@ -58,10 +91,11 @@ const doComplete = async function() {
         const tenders = await querySql('Select * From zh_tender');
         for (const t of tenders) {
             console.log(`Update Tender ${t.name}(${t.id}):`);
-            const materials = await querySql('Select * From zh_material where tid = ?', [t.id]);
-            for (const s of materials) {
-                await updateMaterial(s);
-            }
+            await updateShenpi(t);
+            // const materials = await querySql('Select * From zh_material where tid = ?', [t.id]);
+            // for (const s of materials) {
+            //     await updateMaterial(s);
+            // }
         }
     } catch (err) {
         console.log(err);

+ 22 - 0
sql/index.sql

@@ -0,0 +1,22 @@
+
+-- 执行如下sql,执行时间为1.159s,最大扫描行3.01M,但是仅返回1条数据
+-- SELECT count(*) AS count FROM `zh_change_apply` AS a WHERE a.status=? AND a.tid=? AND (a.uid=? OR a.id IN (SELECT b.caid FROM `zh_change_apply_audit` AS b WHERE b.aid=? AND a.times=b.times GROUP BY b.caid))
+-- SELECT SUM(cast(a.total_price AS decimal(?,?))) AS total_price FROM `zh_change_apply` AS a WHERE a.tid=? AND (a.uid=? OR (a.status!=? AND a.id IN (SELECT b.caid FROM `zh_change_apply_audit` AS b WHERE b.aid=? AND a.times=b.times GROUP BY b.caid)) OR a.status=?)
+-- SELECT a.*,p.name AS account_name FROM `zh_change_apply` AS a LEFT JOIN `zh_project_account` AS p ON a.notice_uid=p.id WHERE a.tid=? AND (a.uid=? OR (a.status!=? AND a.id IN (SELECT b.caid FROM `zh_change_apply_audit` AS b WHERE b.aid=? AND a.times=b.times GROUP BY b.caid)) OR a.status=?) ORDER BY a.in_time DESC LIMIT ?,?
+-- 优化方案如下:
+-- 1. 所有类似sql,都去掉a.times=b.times,这个判断实际上没有任何必要,去掉后,执行时间为169ms
+-- 2. 所有类似的audit表,均添加如下索引,预计速度提升55倍(基于阿里云分析)
+ALTER TABLE `zh_change_apply_audit` ADD INDEX `idx_aid_caid` (`aid`, `caid`);
+ALTER TABLE `zh_change_audit` ADD INDEX `idx_uid_cid` (`uid`, `cid`);
+ALTER TABLE `zh_change_project_audit` ADD INDEX `idx_aid_cpid` (`aid`, `cpid`);
+ALTER TABLE `zh_change_plan_audit` ADD INDEX `idx_aid_cpid` (`aid`, `cpid`);
+ALTER TABLE `zh_financial_pay_audit` ADD INDEX `idx_aid_fpid` (`aid`, `fpid`);
+
+-- 执行如下sql,执行时间为1.55s,最大扫描行294.76K
+-- UPDATE `zh_change_audit_list` SET  `bwmx` = CASE  WHEN  `mx_id` = '138dc057-0773-44c6-bad2-160686cf107b' THEN 'K37+808.4~K37+868.4右线S-Ⅳd'  ELSE `bwmx` END  WHERE `mx_id` IN ('138dc057-0773-44c6-bad2-160686cf107b')
+-- updateDataByRevisePos方法下生成执行
+-- 优化方案,新增mx_id索引,预计性能提升9116倍
+ALTER TABLE `calculation`.`zh_change_audit_list` ADD INDEX `idx_mxid` (`mx_id`);
+
+ALTER TABLE `calculation`.`zh_stage_rela_pos_final` ADD INDEX `idx_relatid_sid` (`rela_tid`, `sid`);
+ALTER TABLE `calculation`.`zh_stage_rela_bills_final` ADD INDEX `idx_relatid_sid` (`rela_tid`, `sid`);

+ 4 - 110
sql/update.sql

@@ -7,118 +7,12 @@
 -- 表结构
 ------------------------------------
 
-ALTER TABLE `zh_sub_project_info`
-ADD COLUMN `proj_intro` text NULL COMMENT '项目概述' AFTER `project_id`,
-ADD COLUMN `proj_cur_status` text NULL COMMENT '当前状态' AFTER `proj_intro`,
-ADD COLUMN `proj_status` varchar(20) NOT NULL DEFAULT '' COMMENT '建设状态' AFTER `proj_level`,
-ADD COLUMN `lx_tp` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '立项-金额' AFTER `lx_code`,
-ADD COLUMN `lx_memo` text NULL COMMENT '立项-备注' AFTER `lx_tp`,
-ADD COLUMN `cb_tp` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '初步-金额' AFTER `cb_code`,
-ADD COLUMN `cb_memo` text NULL COMMENT '初步-备注' AFTER `cb_tp`,
-ADD COLUMN `sg_tp` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '施工许可批复-金额' AFTER `sg_code`,
-ADD COLUMN `sg_memo` text NULL COMMENT '施工许可批复-备注' AFTER `sg_tp`,
-ADD COLUMN `sgt_department` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '施工图-部门' AFTER `gcl_quantity`,
-ADD COLUMN `sgt_date` varchar(20) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '施工图-日期' AFTER `sgt_department`,
-ADD COLUMN `sgt_code` varchar(100) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '施工图-文号' AFTER `sgt_date`,
-ADD COLUMN `sgt_tp` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '施工图-金额' AFTER `sgt_code`,
-ADD COLUMN `sgt_memo` text NULL COMMENT '施工图-备注' AFTER `sgt_tp`,
-ADD COLUMN `pf_department` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '财评/造价站批复-部门' AFTER `sgt_memo`,
-ADD COLUMN `pf_date` varchar(20) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '财评/造价站批复-日期' AFTER `pf_department`,
-ADD COLUMN `pf_code` varchar(100) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '财评/造价站批复-文号' AFTER `pf_date`,
-ADD COLUMN `pf_tp` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '财评/造价站批复-金额' AFTER `pf_code`,
-ADD COLUMN `pf_memo` text NULL COMMENT '财评/造价站批复-备注' AFTER `pf_tp`,
-ADD COLUMN `zb_department` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '招标完成-部门' AFTER `pf_memo`,
-ADD COLUMN `zb_date` varchar(20) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '招标完成-日期' AFTER `zb_department`,
-ADD COLUMN `zb_code` varchar(100) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '招标完成-文号' AFTER `zb_date`,
-ADD COLUMN `zb_tp` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '招标完成-金额' AFTER `zb_code`,
-ADD COLUMN `zb_memo` text NULL COMMENT '招标完成-备注' AFTER `zb_tp`;
+ALTER TABLE `zh_tender`
+ADD COLUMN `c_mode` tinyint(1) NULL DEFAULT 0 COMMENT '变更清单模式' AFTER `c_rule_first`;
 
-ALTER TABLE `zh_sub_project_info`
-ADD COLUMN `jg_tp` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '交工-金额' AFTER `jg_level`,
-ADD COLUMN `jg_memo` text NULL COMMENT '交工-备注' AFTER `jg_tp`;
-
-CREATE TABLE `zh_sub_project_progress`  (
-  `id` varchar(36) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL COMMENT 'uuid',
-  `spid` varchar(36) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL COMMENT 'uuid',
-  `tree_id` int(11) NOT NULL COMMENT '节点id',
-  `tree_pid` int(11) NOT NULL COMMENT '父节点id',
-  `tree_order` tinyint(4) NOT NULL COMMENT '同级排序',
-  `tree_level` tinyint(4) NOT NULL COMMENT '层级',
-  `tree_full_path` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL COMMENT '层级定位辅助字段parent.full_path.ledger_id',
-  `tree_is_leaf` tinyint(4) UNSIGNED NOT NULL DEFAULT 1 COMMENT '是否叶子节点,界面显示辅助字段',
-  `code` varchar(50) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '编号/序号',
-  `name` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '阶段/项目名称',
-  `edit_progress` varchar(20) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '成果编制-进度',
-  `edit_date` varchar(20) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '成果编制-日期',
-  `edit_department` varchar(50) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '成果编制-部门',
-  `submit_progress` varchar(20) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '报审情况-进度',
-  `submit_date` varchar(20) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '报审情况-日期',
-  `submit_department` varchar(50) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '报审情况-部门',
-  `reply_progress` varchar(20) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '批复情况-进度',
-  `reply_date` varchar(20) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '批复情况-日期',
-  `reply_department` varchar(50) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '批复情况-部门',
-  `reply_code` varchar(50) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '批复情况-文号',
-  `memo` varchar(1000) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '备注',
-  `add_user_id` int(11) UNSIGNED NOT NULL DEFAULT 0 COMMENT '创建人',
-  `add_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
-  `update_user_id` int(11) UNSIGNED NOT NULL DEFAULT 0 COMMENT '最后编辑人',
-  `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '最后编辑时间',
-  PRIMARY KEY (`id`) USING BTREE
-) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_unicode_ci ROW_FORMAT = Dynamic;
-
-CREATE TABLE `zh_sub_project_file`  (
-  `id` varchar(36) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL COMMENT 'uuid',
-  `spid` varchar(36) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL COMMENT '项目id(sub_project.id)',
-  `type` varchar(20) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '类型(progress/push/...)',
-  `rela_id` varchar(36) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL COMMENT 'uuid(zh_sub_project_progress.id/zh_sub_project_push.id/...)',
-  `filename` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL COMMENT '文件名',
-  `fileext` varchar(10) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL COMMENT '文件后缀',
-  `filesize` int(11) NOT NULL COMMENT '文件大小',
-  `filepath` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL COMMENT '文件存储路径',
-  `user_id` int(11) UNSIGNED NOT NULL COMMENT '用户id(zh_project_account.id)',
-  `user_name` varchar(50) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '用户名(缓存)',
-  `user_company` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL COMMENT '公司(缓存)',
-  `user_role` varchar(50) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL COMMENT '角色(缓存)',
-  `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
-  `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '最后更新时间',
-  `is_deleted` tinyint(4) UNSIGNED NOT NULL DEFAULT 0 COMMENT '是否删除',
-  PRIMARY KEY (`id`) USING BTREE
-) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_unicode_ci ROW_FORMAT = Dynamic;
-
-CREATE TABLE `zh_message_attachment` (
-  `id` int(11) NOT NULL AUTO_INCREMENT,
-  `project_id` int(11) DEFAULT NULL COMMENT '项目id',
-  `mid` int(11) NOT NULL DEFAULT '0' COMMENT '通知id',
-  `uid` int(11) NOT NULL COMMENT '上传者id',
-  `filename` varchar(255) COLLATE utf8_unicode_ci NOT NULL COMMENT '文件名称',
-  `fileext` varchar(5) COLLATE utf8_unicode_ci NOT NULL COMMENT '文件后缀',
-  `filesize` varchar(255) COLLATE utf8_unicode_ci NOT NULL COMMENT '文件大小',
-  `filepath` varchar(255) COLLATE utf8_unicode_ci NOT NULL COMMENT '文件存储路径',
-  `upload_time` datetime NOT NULL COMMENT '上传时间',
-  PRIMARY KEY (`id`) USING BTREE,
-  KEY `idx_mid` (`mid`) USING BTREE
-) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci COMMENT='通知附件表';
-
-ALTER TABLE `zh_tender_info`
-ADD COLUMN `over_range_check` varchar(255) NOT NULL DEFAULT '' AFTER `s_type`;
-
-ALTER TABLE `zh_material_audit`
-ADD COLUMN `audit_type`  tinyint(4) UNSIGNED NOT NULL DEFAULT 1 COMMENT '审批类型(1个人,2会签,3或签)',
-ADD COLUMN `audit_order`  tinyint(4) UNSIGNED NOT NULL DEFAULT 0 COMMENT '审批顺序' AFTER `audit_type`;
-
-ALTER TABLE `zh_material`
-ADD COLUMN `final_auditor_str` varchar(50) NOT NULL DEFAULT '' COMMENT '终审人相关';
-
-ALTER TABLE `zh_filing_template`
-ADD COLUMN `tips` varchar(1000) NOT NULL DEFAULT '' COMMENT '提示' AFTER `filing_type`;
-ALTER TABLE `zh_filing`
-ADD COLUMN `tips` varchar(1000) NOT NULL DEFAULT '' COMMENT '提示' AFTER `file_count`;
-
-ALTER TABLE `zh_material_audit` ADD `is_old` TINYINT(1) NOT NULL DEFAULT '0' COMMENT '是否为旧流程(用于管理员修改流程时旧数据保留但不影响新流程)' AFTER `opinion`;
+ALTER TABLE `zh_change_ledger`
+ADD COLUMN `features` varchar(1000) CHARACTER SET utf8 COLLATE utf8_unicode_ci NULL DEFAULT '' COMMENT '项目特征' AFTER `memo`;
 
 ------------------------------------
 -- 表数据
 ------------------------------------
-
-UPDATE zh_tender_info ti LEFT JOIN zh_tender t ON ti.tid = t.id SET ti.over_range_check = '{"field":"deal","percent":100,"billsWithPos":"bills"}' WHERE t.measure_type = 2;
-UPDATE zh_tender_info ti LEFT JOIN zh_tender t ON ti.tid = t.id SET ti.over_range_check = '{"field":"tz","percent":100,"billsWithPos":"both"}' WHERE t.measure_type = 1;

+ 138 - 0
sql/update20241205.sql

@@ -0,0 +1,138 @@
+-- 请按如下分类提交sql!!!
+-- Version V3.5.46.1224
+-- uat 20241204
+-- prod 20241205
+
+------------------------------------
+-- 表结构
+------------------------------------
+
+ALTER TABLE `zh_sub_project_info`
+ADD COLUMN `proj_intro` text NULL COMMENT '项目概述' AFTER `project_id`,
+ADD COLUMN `proj_cur_status` text NULL COMMENT '当前状态' AFTER `proj_intro`,
+ADD COLUMN `proj_status` varchar(20) NOT NULL DEFAULT '' COMMENT '建设状态' AFTER `proj_level`,
+ADD COLUMN `lx_tp` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '立项-金额' AFTER `lx_code`,
+ADD COLUMN `lx_memo` text NULL COMMENT '立项-备注' AFTER `lx_tp`,
+ADD COLUMN `cb_tp` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '初步-金额' AFTER `cb_code`,
+ADD COLUMN `cb_memo` text NULL COMMENT '初步-备注' AFTER `cb_tp`,
+ADD COLUMN `sg_tp` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '施工许可批复-金额' AFTER `sg_code`,
+ADD COLUMN `sg_memo` text NULL COMMENT '施工许可批复-备注' AFTER `sg_tp`,
+ADD COLUMN `sgt_department` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '施工图-部门' AFTER `gcl_quantity`,
+ADD COLUMN `sgt_date` varchar(20) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '施工图-日期' AFTER `sgt_department`,
+ADD COLUMN `sgt_code` varchar(100) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '施工图-文号' AFTER `sgt_date`,
+ADD COLUMN `sgt_tp` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '施工图-金额' AFTER `sgt_code`,
+ADD COLUMN `sgt_memo` text NULL COMMENT '施工图-备注' AFTER `sgt_tp`,
+ADD COLUMN `pf_department` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '财评/造价站批复-部门' AFTER `sgt_memo`,
+ADD COLUMN `pf_date` varchar(20) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '财评/造价站批复-日期' AFTER `pf_department`,
+ADD COLUMN `pf_code` varchar(100) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '财评/造价站批复-文号' AFTER `pf_date`,
+ADD COLUMN `pf_tp` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '财评/造价站批复-金额' AFTER `pf_code`,
+ADD COLUMN `pf_memo` text NULL COMMENT '财评/造价站批复-备注' AFTER `pf_tp`,
+ADD COLUMN `zb_department` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '招标完成-部门' AFTER `pf_memo`,
+ADD COLUMN `zb_date` varchar(20) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '招标完成-日期' AFTER `zb_department`,
+ADD COLUMN `zb_code` varchar(100) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '招标完成-文号' AFTER `zb_date`,
+ADD COLUMN `zb_tp` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '招标完成-金额' AFTER `zb_code`,
+ADD COLUMN `zb_memo` text NULL COMMENT '招标完成-备注' AFTER `zb_tp`;
+
+ALTER TABLE `zh_sub_project_info`
+ADD COLUMN `jg_tp` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '交工-金额' AFTER `jg_level`,
+ADD COLUMN `jg_memo` text NULL COMMENT '交工-备注' AFTER `jg_tp`;
+
+CREATE TABLE `zh_sub_project_progress`  (
+  `id` varchar(36) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL COMMENT 'uuid',
+  `spid` varchar(36) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL COMMENT 'uuid',
+  `tree_id` int(11) NOT NULL COMMENT '节点id',
+  `tree_pid` int(11) NOT NULL COMMENT '父节点id',
+  `tree_order` tinyint(4) NOT NULL COMMENT '同级排序',
+  `tree_level` tinyint(4) NOT NULL COMMENT '层级',
+  `tree_full_path` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL COMMENT '层级定位辅助字段parent.full_path.ledger_id',
+  `tree_is_leaf` tinyint(4) UNSIGNED NOT NULL DEFAULT 1 COMMENT '是否叶子节点,界面显示辅助字段',
+  `code` varchar(50) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '编号/序号',
+  `name` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '阶段/项目名称',
+  `edit_progress` varchar(20) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '成果编制-进度',
+  `edit_date` varchar(20) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '成果编制-日期',
+  `edit_department` varchar(50) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '成果编制-部门',
+  `submit_progress` varchar(20) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '报审情况-进度',
+  `submit_date` varchar(20) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '报审情况-日期',
+  `submit_department` varchar(50) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '报审情况-部门',
+  `reply_progress` varchar(20) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '批复情况-进度',
+  `reply_date` varchar(20) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '批复情况-日期',
+  `reply_department` varchar(50) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '批复情况-部门',
+  `reply_code` varchar(50) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '批复情况-文号',
+  `memo` varchar(1000) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '备注',
+  `add_user_id` int(11) UNSIGNED NOT NULL DEFAULT 0 COMMENT '创建人',
+  `add_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+  `update_user_id` int(11) UNSIGNED NOT NULL DEFAULT 0 COMMENT '最后编辑人',
+  `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '最后编辑时间',
+  PRIMARY KEY (`id`) USING BTREE
+) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_unicode_ci ROW_FORMAT = Dynamic;
+
+CREATE TABLE `zh_sub_project_file`  (
+  `id` varchar(36) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL COMMENT 'uuid',
+  `spid` varchar(36) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL COMMENT '项目id(sub_project.id)',
+  `type` varchar(20) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '类型(progress/push/...)',
+  `rela_id` varchar(36) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL COMMENT 'uuid(zh_sub_project_progress.id/zh_sub_project_push.id/...)',
+  `filename` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL COMMENT '文件名',
+  `fileext` varchar(10) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL COMMENT '文件后缀',
+  `filesize` int(11) NOT NULL COMMENT '文件大小',
+  `filepath` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL COMMENT '文件存储路径',
+  `user_id` int(11) UNSIGNED NOT NULL COMMENT '用户id(zh_project_account.id)',
+  `user_name` varchar(50) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '用户名(缓存)',
+  `user_company` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL COMMENT '公司(缓存)',
+  `user_role` varchar(50) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL COMMENT '角色(缓存)',
+  `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+  `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '最后更新时间',
+  `is_deleted` tinyint(4) UNSIGNED NOT NULL DEFAULT 0 COMMENT '是否删除',
+  PRIMARY KEY (`id`) USING BTREE
+) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_unicode_ci ROW_FORMAT = Dynamic;
+
+CREATE TABLE `zh_sub_project_push`  (
+  `id` varchar(36) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL COMMENT 'uuid',
+  `spid` varchar(36) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL COMMENT '项目id(zh_sub_project.id)',
+  `push_order` int(11) UNSIGNED NOT NULL COMMENT '序号',
+  `push_date` varchar(20) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '日期',
+  `push_content` text CHARACTER SET utf8 COLLATE utf8_unicode_ci NULL COMMENT '记录内容',
+  `memo` tinytext CHARACTER SET utf8 COLLATE utf8_unicode_ci NULL COMMENT '备注',
+  `user_id` int(11) UNSIGNED NOT NULL COMMENT '创建人id',
+  `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+  `update_user_id` int(11) UNSIGNED NOT NULL COMMENT '最后修改人id',
+  `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '最后修改时间',
+  PRIMARY KEY (`id`) USING BTREE
+) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_unicode_ci ROW_FORMAT = Dynamic;
+
+CREATE TABLE `zh_message_attachment` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `project_id` int(11) DEFAULT NULL COMMENT '项目id',
+  `mid` int(11) NOT NULL DEFAULT '0' COMMENT '通知id',
+  `uid` int(11) NOT NULL COMMENT '上传者id',
+  `filename` varchar(255) COLLATE utf8_unicode_ci NOT NULL COMMENT '文件名称',
+  `fileext` varchar(5) COLLATE utf8_unicode_ci NOT NULL COMMENT '文件后缀',
+  `filesize` varchar(255) COLLATE utf8_unicode_ci NOT NULL COMMENT '文件大小',
+  `filepath` varchar(255) COLLATE utf8_unicode_ci NOT NULL COMMENT '文件存储路径',
+  `upload_time` datetime NOT NULL COMMENT '上传时间',
+  PRIMARY KEY (`id`) USING BTREE,
+  KEY `idx_mid` (`mid`) USING BTREE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci COMMENT='通知附件表';
+
+ALTER TABLE `zh_tender_info`
+ADD COLUMN `over_range_check` varchar(255) NOT NULL DEFAULT '' AFTER `s_type`;
+
+ALTER TABLE `zh_material_audit`
+ADD COLUMN `audit_type`  tinyint(4) UNSIGNED NOT NULL DEFAULT 1 COMMENT '审批类型(1个人,2会签,3或签)',
+ADD COLUMN `audit_order`  tinyint(4) UNSIGNED NOT NULL DEFAULT 0 COMMENT '审批顺序' AFTER `audit_type`;
+
+ALTER TABLE `zh_material`
+ADD COLUMN `final_auditor_str` varchar(50) NOT NULL DEFAULT '' COMMENT '终审人相关';
+
+ALTER TABLE `zh_filing_template`
+ADD COLUMN `tips` varchar(1000) NOT NULL DEFAULT '' COMMENT '提示' AFTER `filing_type`;
+ALTER TABLE `zh_filing`
+ADD COLUMN `tips` varchar(1000) NOT NULL DEFAULT '' COMMENT '提示' AFTER `file_count`;
+
+ALTER TABLE `zh_material_audit` ADD `is_old` TINYINT(1) NOT NULL DEFAULT '0' COMMENT '是否为旧流程(用于管理员修改流程时旧数据保留但不影响新流程)' AFTER `opinion`;
+
+------------------------------------
+-- 表数据
+------------------------------------
+
+UPDATE zh_tender_info ti LEFT JOIN zh_tender t ON ti.tid = t.id SET ti.over_range_check = '{"field":"deal","percent":100,"billsWithPos":"bills"}' WHERE t.measure_type = 2;
+UPDATE zh_tender_info ti LEFT JOIN zh_tender t ON ti.tid = t.id SET ti.over_range_check = '{"field":"tz","percent":100,"billsWithPos":"both"}' WHERE t.measure_type = 1;